mirror of
https://github.com/irmen/prog8.git
synced 2025-06-16 09:23:43 +00:00
Compare commits
719 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
eb94c678bd | |||
50d792a121 | |||
f0d4654917 | |||
4ce93b5d9d | |||
fb0d7a1908 | |||
bb7b063757 | |||
c495f54bbb | |||
1cc1f2d91d | |||
d837cc11f9 | |||
cbb7083307 | |||
d4a17dfad1 | |||
59f8b91e25 | |||
80113f9208 | |||
27f987f0ae | |||
3ae2597261 | |||
248e7b808c | |||
a983a896f2 | |||
68df1730f5 | |||
d62ab93b24 | |||
47297f7e31 | |||
b64d611e02 | |||
9fb9bcfebd | |||
dff9c5f53e | |||
d4a77321d2 | |||
2665618fa6 | |||
b5c5560af8 | |||
065587525e | |||
58e5d5c071 | |||
b44e76db57 | |||
2ce6bc5946 | |||
fe5b225732 | |||
d499e40a4b | |||
62a66d89c6 | |||
e1b26ae287 | |||
1c151f4a3f | |||
8917926996 | |||
b54a9b9831 | |||
f08906dba1 | |||
a6bba824d3 | |||
fd84152a2b | |||
3466106119 | |||
c79b587eea | |||
4862fb7db1 | |||
2136db0e61 | |||
2f0c0f6fcd | |||
7ddc01f883 | |||
126c2162f1 | |||
094c8ab94c | |||
efe2723874 | |||
bccfeb2fa2 | |||
d498d5445c | |||
5095d090cc | |||
6544fcdc36 | |||
e834924857 | |||
2c3b8a9819 | |||
309c82fc9e | |||
0f91ce6441 | |||
f29ec3b4e1 | |||
cc1fc869cf | |||
0431d3cddc | |||
a1cd202cd2 | |||
b842493cf0 | |||
4718f09cb7 | |||
e9c357a885 | |||
fb00ff74d1 | |||
b740b079db | |||
6394841041 | |||
3f4050c647 | |||
82f01d84c2 | |||
299ea72d70 | |||
50aa286d3a | |||
6f7322150f | |||
cc9965cc96 | |||
ae90a957c6 | |||
8cec032e7d | |||
3732ab1e62 | |||
fba149ee28 | |||
4661cba974 | |||
025be8cb7c | |||
3aea32551b | |||
8e8c112ff0 | |||
b0dda08e74 | |||
2c25df122a | |||
7cb5702b37 | |||
b7502c7eaa | |||
fed020825a | |||
1c411897df | |||
f94e241fb2 | |||
757cbfd1ba | |||
3de80319db | |||
f9617d777a | |||
9961a404ae | |||
776c844d02 | |||
03782a37a2 | |||
173663380b | |||
c6fdd65c63 | |||
d9546f9dc7 | |||
2a6b0f5db7 | |||
b4e1b42cec | |||
a8898a5993 | |||
e03c68b632 | |||
a0074de12b | |||
411bedcc46 | |||
07d8caf884 | |||
c0e83ef8df | |||
4dbf4b2005 | |||
61af72b906 | |||
17be722e2b | |||
16d7927d2f | |||
55a7a5d9d5 | |||
78d7849197 | |||
d5b12fb01d | |||
31f4e378aa | |||
8a26b7b248 | |||
87c28cfdbc | |||
1f5420010d | |||
a089c48378 | |||
3e5deda46c | |||
7500c6efd0 | |||
717b5f3b07 | |||
9f6fa60bf1 | |||
1e9586f635 | |||
44f9d5e69e | |||
7c9b8f7d43 | |||
845a99d623 | |||
3d7a4bf81a | |||
d4b3e35bd2 | |||
a59f7c75dc | |||
44fe2369d6 | |||
aaaab2cfcf | |||
9a3dab20dc | |||
20379b5927 | |||
34dcce67e4 | |||
0c7f107d01 | |||
1f89571aa5 | |||
7eed1ebbf8 | |||
12cb7d7abe | |||
c9b16dcbd9 | |||
dcab6d00bb | |||
a85743f241 | |||
14cabde5cf | |||
cc078503e3 | |||
2a0c3377f9 | |||
16454f5560 | |||
c1343a78f1 | |||
9d0c65c682 | |||
9e6408244f | |||
3581017489 | |||
9bc36b4d99 | |||
e8caf6d319 | |||
5b9cc9592f | |||
3cf87536ff | |||
cc452dffb8 | |||
e414d301a4 | |||
5ff79073f4 | |||
70462ffe6d | |||
158fe7596b | |||
f4f113da7b | |||
d6b6254b72 | |||
65fa8c4613 | |||
c1102393bb | |||
dbe048158c | |||
2b3382ff8e | |||
c970d899fa | |||
3c563d281a | |||
1794f704e7 | |||
ade7a4c398 | |||
5a27b035b0 | |||
e84bb8d94a | |||
5ed0893d96 | |||
89314a0e1a | |||
fd0abf61df | |||
ac70ae6a76 | |||
d83f49d84f | |||
ff1294207e | |||
a56956797a | |||
3242495b0b | |||
49eb7e7803 | |||
1d7f0d3537 | |||
31137743f0 | |||
2c69e10489 | |||
3a1fa9e069 | |||
2c08d2f9c6 | |||
4743cacb73 | |||
5f5a1447e0 | |||
a3004555a8 | |||
267c678292 | |||
6c50043a4a | |||
3ee1b2efdd | |||
75d8c832ad | |||
53a4379c45 | |||
29b3a7e94e | |||
0782f6ecf1 | |||
595e58ec46 | |||
060e05c868 | |||
f49eefad6f | |||
d68360461b | |||
343978d164 | |||
b11d10e2ff | |||
268856823a | |||
4bac5043b6 | |||
eb25b4c800 | |||
a079e44b02 | |||
e53c860f1a | |||
99121004bf | |||
6dd3371781 | |||
f473be8951 | |||
ebd38f27e6 | |||
a6c3251668 | |||
560047adee | |||
a86852874f | |||
6d44d6a901 | |||
968f02823f | |||
5d321d759e | |||
7de7d5234f | |||
b374af3526 | |||
b35430214b | |||
e96d3d4455 | |||
6a17f7a0ad | |||
c559682c0b | |||
6ce1277438 | |||
262e0bd6b9 | |||
755af6010e | |||
0298cf8b90 | |||
a6d0aecd66 | |||
ef6e364339 | |||
3b37e0f99d | |||
78fbbf7119 | |||
0ee43294c4 | |||
a81b82495c | |||
390043e9e8 | |||
e384822b2c | |||
730e08698d | |||
5497de4234 | |||
c71b78dee6 | |||
dfcb57a0b0 | |||
f219ae43f7 | |||
a9bbe0bc40 | |||
35aa954be8 | |||
78ddcf9db7 | |||
cd0fa9405a | |||
4462def8ea | |||
3f93b87745 | |||
9f302cc640 | |||
0a73125606 | |||
7780441524 | |||
8bec4eaa87 | |||
4434d31a3b | |||
51454c71c7 | |||
fb2796ac06 | |||
742b15357b | |||
ac6ed27052 | |||
f3c1783bf2 | |||
ce8853ab50 | |||
5e3e00fbad | |||
1dde49d644 | |||
fd19298a05 | |||
ede2b83ce4 | |||
fc47d3feb8 | |||
87446028e0 | |||
b200f9945f | |||
eebd4e5f18 | |||
1069b5f5d5 | |||
3e7e44acfe | |||
518c3bfd76 | |||
905d8a0c06 | |||
b57c02b0ba | |||
03d0411679 | |||
83ace753b2 | |||
ec2e7db23e | |||
c4615591c9 | |||
25e3b599e7 | |||
5502a3e3ee | |||
62ceace941 | |||
7114d3193c | |||
f6bc69139d | |||
f3fc2fe523 | |||
1e045b6a62 | |||
747c9604dd | |||
1e5b2e0be3 | |||
0820716e7b | |||
191707cd37 | |||
223bab21aa | |||
563122ac92 | |||
bc9d00922e | |||
d9d83248fe | |||
f2397527f1 | |||
bf3caaefe1 | |||
1aaf854ef7 | |||
ce40f6f862 | |||
a349599943 | |||
ac7faa8d25 | |||
747ee32e81 | |||
75fadaa24f | |||
e4ea1f1014 | |||
cd2c4e13da | |||
f5ba072294 | |||
87d6312a37 | |||
3af7d4c930 | |||
0fc3071a21 | |||
7f36d08acc | |||
b040e5ddad | |||
f36ce5e0ee | |||
ffbdac7e9a | |||
f2b03342ac | |||
52ff61470b | |||
28277469b6 | |||
aa98104d54 | |||
9be70bcbe7 | |||
3a6fae4447 | |||
06f0984fa1 | |||
77dc35dc6a | |||
ed43f7cd9b | |||
32405a1637 | |||
43cab3f247 | |||
5ea2f2d4db | |||
b8ae808b65 | |||
96ecbc9fe4 | |||
588133d418 | |||
2f1249489b | |||
95f7c9bad0 | |||
8811d2f7c5 | |||
d6ca1e6a12 | |||
b0ad66bd04 | |||
c1d2b4601b | |||
c265625ed1 | |||
52352d9d04 | |||
cc5898d010 | |||
8684f0c8f5 | |||
d05d8de447 | |||
29b7d91293 | |||
bcdf3f2b83 | |||
ee497d2ffb | |||
9f8ae485c3 | |||
3b32fb74f7 | |||
7ff1af3934 | |||
ae21e03e1d | |||
f0a504baec | |||
f83b9732ee | |||
86ff08e854 | |||
b911a95fc2 | |||
73b0cc4056 | |||
8e36a64d49 | |||
255c808b16 | |||
b311b5ef4a | |||
9a8ada7e75 | |||
32a9ab30fb | |||
5c8d7c1255 | |||
53045b9e36 | |||
d35d28ac5a | |||
993dcde985 | |||
0ecef00b13 | |||
13a5eabc09 | |||
b8624c72d9 | |||
49c5b2b107 | |||
1e89bea288 | |||
2c64f95b06 | |||
037c78cfaf | |||
de777b1308 | |||
b3df3c999d | |||
4790c3a793 |
20
.gitignore
vendored
20
.gitignore
vendored
@ -1,20 +1,20 @@
|
|||||||
**/.idea/
|
.idea/workspace.xml
|
||||||
/build/
|
.idea/discord.xml
|
||||||
/dist/
|
build/
|
||||||
/output/
|
dist/
|
||||||
|
output/
|
||||||
.*cache/
|
.*cache/
|
||||||
*.directory
|
*.directory
|
||||||
*.prg
|
*.prg
|
||||||
*.asm
|
|
||||||
*.bin
|
*.bin
|
||||||
*.labels.txt
|
*.labels.txt
|
||||||
*.vm.txt
|
*.vm.txt
|
||||||
*.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
|
||||||
@ -25,7 +25,7 @@ __pycache__/
|
|||||||
parser.out
|
parser.out
|
||||||
parsetab.py
|
parsetab.py
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
compiler/src/prog8_kotlin.jar
|
|
||||||
compiler/src/compiled_java
|
|
||||||
.attach_pid*
|
.attach_pid*
|
||||||
|
|
||||||
|
.gradle
|
||||||
|
/prog8compiler.jar
|
||||||
|
2
.idea/.gitignore
generated
vendored
Normal file
2
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
3
.idea/dictionaries/irmen.xml
generated
Normal file
3
.idea/dictionaries/irmen.xml
generated
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<component name="ProjectDictionaryState">
|
||||||
|
<dictionary name="irmen" />
|
||||||
|
</component>
|
20
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
20
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<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">
|
||||||
|
<option name="processCode" value="false" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
6
.idea/kotlinc.xml
generated
Normal file
6
.idea/kotlinc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Kotlin2JvmCompilerArguments">
|
||||||
|
<option name="jvmTarget" value="1.8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
19
.idea/libraries/KotlinJavaRuntime.xml
generated
Normal file
19
.idea/libraries/KotlinJavaRuntime.xml
generated
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="KotlinJavaRuntime">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" />
|
||||||
|
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
|
||||||
|
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test.jar!/" />
|
||||||
|
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7.jar!/" />
|
||||||
|
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES>
|
||||||
|
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" />
|
||||||
|
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect-sources.jar!/" />
|
||||||
|
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test-sources.jar!/" />
|
||||||
|
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7-sources.jar!/" />
|
||||||
|
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8-sources.jar!/" />
|
||||||
|
</SOURCES>
|
||||||
|
</library>
|
||||||
|
</component>
|
9
.idea/libraries/antlr_4_7_2_complete.xml
generated
Normal file
9
.idea/libraries/antlr_4_7_2_complete.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="antlr-4.7.2-complete">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.7.2-complete.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
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_7_2.xml
generated
Normal file
9
.idea/libraries/antlr_runtime_4_7_2.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="antlr-runtime-4.7.2">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.7.2.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>
|
10
.idea/libraries/unittest_libs.xml
generated
Normal file
10
.idea/libraries/unittest_libs.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="unittest-libs">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="file://$PROJECT_DIR$/compiler/lib" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
<jarDirectory url="file://$PROJECT_DIR$/compiler/lib" recursive="false" />
|
||||||
|
</library>
|
||||||
|
</component>
|
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>
|
22
.idea/misc.xml
generated
Normal file
22
.idea/misc.xml
generated
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<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">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
11
.idea/modules.xml
generated
Normal file
11
.idea/modules.xml
generated
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<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$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
124
.idea/uiDesigner.xml
generated
Normal file
124
.idea/uiDesigner.xml
generated
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Palette2">
|
||||||
|
<group name="Swing">
|
||||||
|
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="Button" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="RadioButton" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="CheckBox" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="Label" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||||
|
<preferred-size width="200" height="200" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||||
|
<preferred-size width="200" height="200" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||||
|
<preferred-size width="-1" height="20" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||||
|
</item>
|
||||||
|
</group>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
11
.travis.yml
Normal file
11
.travis.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
language: java
|
||||||
|
sudo: false
|
||||||
|
# jdk: openjdk8
|
||||||
|
# dist: xenial
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- chmod +x ./gradlew
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./gradlew test
|
||||||
|
|
145
README.md
145
README.md
@ -1,104 +1,120 @@
|
|||||||
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
|
[](https://saythanks.io/to/irmen)
|
||||||
===========================================================================
|
[](https://travis-ci.org/irmen/prog8)
|
||||||
|
[](https://prog8.readthedocs.io/)
|
||||||
|
|
||||||
|
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
||||||
|
============================================================================
|
||||||
|
|
||||||
*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
|
- reduction of source code length
|
||||||
- easier program understanding (because it's higher level, and more terse)
|
|
||||||
- option to automatically run the compiled program in the Vice emulator
|
|
||||||
- modularity, symbol scoping, subroutines
|
- modularity, symbol scoping, subroutines
|
||||||
- subroutines have enforced input- and output parameter definitions
|
- various data types other than just bytes (16-bit words, floats, strings)
|
||||||
- various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs)
|
- automatic variable allocations, automatic string and array variables and string sharing
|
||||||
- automatic variable allocations, automatic string variables and string sharing
|
- subroutines with an input- and output parameter signature
|
||||||
- constant folding in expressions (compile-time evaluation)
|
- constant folding in expressions
|
||||||
- automatic type conversions
|
- conditional branches
|
||||||
- floating point operations
|
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||||
|
- structs to group together sets of variables and manipulate them at once
|
||||||
|
- floating point operations (requires the C64 Basic ROM routines for this)
|
||||||
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||||
|
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
|
||||||
|
- 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``
|
||||||
|
|
||||||
|
Rapid edit-compile-run-debug cycle:
|
||||||
|
|
||||||
|
- use a modern PC to do the work on
|
||||||
|
- very quick compilation times
|
||||||
|
- 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
|
||||||
- conditional gotos
|
|
||||||
- various code optimizations (code structure, logical and numerical expressions, ...)
|
Prog8 is mainly targeted at the Commodore-64 machine.
|
||||||
|
Preliminary support for the [CommanderX16](https://www.commanderx16.com) is available as a second compilation target.
|
||||||
|
Contributions to improve these or to add support for other machines are welcome!
|
||||||
|
|
||||||
|
|
||||||
It is mainly targeted at the Commodore-64 machine at this time.
|
Documentation/manual
|
||||||
|
--------------------
|
||||||
|
https://prog8.readthedocs.io/
|
||||||
|
|
||||||
Documentation is online at https://prog8.readthedocs.io/
|
Required tools
|
||||||
|
--------------
|
||||||
|
|
||||||
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.
|
||||||
For other platforms it is very easy to compile it yourself (make ; make install).
|
For other platforms it is very easy to compile it yourself (make ; make install).
|
||||||
|
|
||||||
A **Java runtime (jre or jdk), version 8 or newer** is required to run the packaged compiler.
|
A **Java runtime (jre or jdk), version 8 or newer** is required to run a prepackaged version of the compiler.
|
||||||
If you want to build it from source, you'll need a Kotlin 1.3 SDK as well (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
|
||||||
------------
|
------------
|
||||||
|
|
||||||
When this code is compiled::
|
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||||
|
|
||||||
%import c64lib
|
%import c64textio
|
||||||
%import c64utils
|
%zeropage basicsafe
|
||||||
%import c64flt
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
ubyte[256] sieve
|
||||||
|
ubyte candidate_prime = 2
|
||||||
|
|
||||||
~ main {
|
|
||||||
sub start() {
|
sub start() {
|
||||||
; set text color and activate lowercase charset
|
memset(sieve, 256, false) ; clear the sieve
|
||||||
c64.COLOR = 13
|
txt.print("prime numbers up to 255:\n\n")
|
||||||
c64.VMCSB |= 2
|
ubyte amount=0
|
||||||
|
repeat {
|
||||||
; use optimized routine to write text
|
ubyte prime = find_next_prime()
|
||||||
c64scr.print("Hello!\n")
|
if prime==0
|
||||||
|
break
|
||||||
; use iteration to write text
|
txt.print_ub(prime)
|
||||||
str question = "How are you?\n"
|
txt.print(", ")
|
||||||
for ubyte char in question
|
amount++
|
||||||
c64.CHROUT(char)
|
}
|
||||||
|
|
||||||
; use indexed loop to write characters
|
|
||||||
str bye = "Goodbye!\n"
|
|
||||||
for ubyte c in 0 to len(bye)
|
|
||||||
c64.CHROUT(bye[c])
|
|
||||||
|
|
||||||
|
|
||||||
float clock_seconds = ((mkword(c64.TIME_LO, c64.TIME_MID) as float)
|
|
||||||
+ (c64.TIME_HI as float)*65536.0)
|
|
||||||
/ 60
|
|
||||||
float hours = floor(clock_seconds / 3600)
|
|
||||||
clock_seconds -= hours*3600
|
|
||||||
float minutes = floor(clock_seconds / 60)
|
|
||||||
clock_seconds = floor(clock_seconds - minutes * 60.0)
|
|
||||||
|
|
||||||
c64scr.print("system time in ti$ is ")
|
|
||||||
c64flt.print_f(hours)
|
|
||||||
c64.CHROUT(':')
|
|
||||||
c64flt.print_f(minutes)
|
|
||||||
c64.CHROUT(':')
|
|
||||||
c64flt.print_f(clock_seconds)
|
|
||||||
c64.CHROUT('\n')
|
c64.CHROUT('\n')
|
||||||
|
txt.print("number of primes (expected 54): ")
|
||||||
|
txt.print_ub(amount)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub find_next_prime() -> ubyte {
|
||||||
|
while sieve[candidate_prime] {
|
||||||
|
candidate_prime++
|
||||||
|
if candidate_prime==0
|
||||||
|
return 0 ; we wrapped; no more primes available
|
||||||
|
}
|
||||||
|
; found next one, mark the multiples and return it.
|
||||||
|
sieve[candidate_prime] = true
|
||||||
|
uword multiple = candidate_prime
|
||||||
|
|
||||||
|
while multiple < len(sieve) {
|
||||||
|
sieve[lsb(multiple)] = true
|
||||||
|
multiple += candidate_prime
|
||||||
|
}
|
||||||
|
return candidate_prime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
when compiled an ran on a C-64 you'll get:
|
||||||
|
|
||||||
you get a program that outputs this when loaded on a C-64:
|

|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
@ -109,3 +125,6 @@ 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:
|
||||||
|
|
||||||
|

|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
echo "Compiling the parser..."
|
|
||||||
java -jar ./parser/antlr/lib/antlr-4.7.2-complete.jar -o ./parser/src/prog8/parser -Xexact-output-dir -no-listener -no-visitor -package prog8.parser ./parser/antlr/prog8.g4
|
|
||||||
|
|
||||||
|
|
||||||
PARSER_CLASSES=./out/production/parser
|
|
||||||
COMPILER_JAR=prog8compiler.jar
|
|
||||||
ANTLR_RUNTIME=./parser/antlr/lib/antlr-runtime-4.7.2.jar
|
|
||||||
|
|
||||||
mkdir -p ${PARSER_CLASSES}
|
|
||||||
javac -d ${PARSER_CLASSES} -cp ${ANTLR_RUNTIME} ./parser/src/prog8/parser/prog8Lexer.java ./parser/src/prog8/parser/prog8Parser.java
|
|
||||||
|
|
||||||
echo "Compiling the compiler itself..."
|
|
||||||
kotlinc -verbose -include-runtime -d ${COMPILER_JAR} -cp ${ANTLR_RUNTIME}:${PARSER_CLASSES} ./compiler/src/prog8
|
|
||||||
|
|
||||||
echo "Finalizing the compiler jar file..."
|
|
||||||
# add the antlr parser classes
|
|
||||||
jar ufe ${COMPILER_JAR} prog8.CompilerMainKt -C ${PARSER_CLASSES} prog8
|
|
||||||
|
|
||||||
# add the resources
|
|
||||||
jar uf ${COMPILER_JAR} -C ./compiler/res .
|
|
||||||
|
|
||||||
# add the antlr runtime classes
|
|
||||||
rm -rf antlr_runtime_extraction
|
|
||||||
mkdir antlr_runtime_extraction
|
|
||||||
(cd antlr_runtime_extraction; jar xf ../${ANTLR_RUNTIME})
|
|
||||||
jar uf ${COMPILER_JAR} -C antlr_runtime_extraction org
|
|
||||||
rm -rf antlr_runtime_extraction
|
|
||||||
|
|
||||||
echo "Done!"
|
|
116
compiler/build.gradle
Normal file
116
compiler/build.gradle
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
buildscript {
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
// id "org.jetbrains.kotlin.jvm" version "1.4.0"
|
||||||
|
id 'application'
|
||||||
|
id 'org.jetbrains.dokka' version "0.9.18"
|
||||||
|
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: "kotlin"
|
||||||
|
apply plugin: "java"
|
||||||
|
|
||||||
|
targetCompatibility = 1.8
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
jcenter()
|
||||||
|
maven { url "https://dl.bintray.com/orangy/maven/" }
|
||||||
|
}
|
||||||
|
|
||||||
|
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':parser')
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
|
implementation 'org.antlr:antlr4-runtime:4.8'
|
||||||
|
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
|
||||||
|
// 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"
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
|
||||||
|
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
|
||||||
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
|
||||||
|
}
|
||||||
|
|
||||||
|
compileKotlin {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
// verbose = true
|
||||||
|
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileTestKotlin {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java {
|
||||||
|
srcDirs = ["${project.projectDir}/src"]
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
srcDirs = ["${project.projectDir}/res"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test {
|
||||||
|
java {
|
||||||
|
srcDirs = ["${project.projectDir}/test"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startScripts.enabled = true
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClassName = 'prog8.CompilerMainKt'
|
||||||
|
applicationName = 'p8compile'
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
archives shadowJar
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
archiveBaseName = 'prog8compiler'
|
||||||
|
archiveVersion = prog8version
|
||||||
|
// minimize()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test {
|
||||||
|
// Enable JUnit 5 (Gradle 4.6+).
|
||||||
|
useJUnitPlatform()
|
||||||
|
|
||||||
|
// Always run tests, even when nothing changed.
|
||||||
|
dependsOn 'cleanTest'
|
||||||
|
|
||||||
|
// Show test results.
|
||||||
|
testLogging {
|
||||||
|
events "skipped", "failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dokka {
|
||||||
|
outputFormat = 'html'
|
||||||
|
outputDirectory = "$buildDir/kdoc"
|
||||||
|
}
|
||||||
|
|
||||||
|
task wrapper(type: Wrapper) {
|
||||||
|
gradleVersion = '6.1.1'
|
||||||
|
}
|
@ -3,15 +3,17 @@
|
|||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
|
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
|
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
|
|
||||||
<orderEntry type="library" name="testlibs" 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="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 |
751
compiler/res/prog8lib/c64floats.asm
Normal file
751
compiler/res/prog8lib/c64floats.asm
Normal file
@ -0,0 +1,751 @@
|
|||||||
|
; --- low level floating point assembly routines for the C64
|
||||||
|
|
||||||
|
|
||||||
|
ub2float .proc
|
||||||
|
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
||||||
|
; clobbers A, Y
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
jsr FREADUY
|
||||||
|
_fac_to_mem ldx P8ZP_SCRATCH_W2
|
||||||
|
ldy P8ZP_SCRATCH_W2+1
|
||||||
|
jsr MOVMF
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
b2float .proc
|
||||||
|
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
||||||
|
; clobbers A, Y
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
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_X
|
||||||
|
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_X
|
||||||
|
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_X
|
||||||
|
jsr FREADSA
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_w2float .proc
|
||||||
|
; -- w2float operating on the stack
|
||||||
|
inx
|
||||||
|
ldy P8ESTACK_LO,x
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
jsr GIVAYF
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_ub2float .proc
|
||||||
|
; -- ub2float operating on the stack
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
tay
|
||||||
|
jsr FREADUY
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_uw2float .proc
|
||||||
|
; -- uw2float operating on the stack
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
ldy P8ESTACK_HI,x
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
jsr GIVUAYFAY
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_float2w .proc ; also used for float2b
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
jsr AYINT
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
lda $64
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
lda $65
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_float2uw .proc ; also used for float2ub
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
jsr GETADR
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
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
|
||||||
|
|
||||||
|
func_rndf .proc
|
||||||
|
; -- put a random floating point value on the stack
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #1
|
||||||
|
jsr FREADSA
|
||||||
|
jsr RND ; rng into fac1
|
||||||
|
ldx #<_rndf_rnum5
|
||||||
|
ldy #>_rndf_rnum5
|
||||||
|
jsr MOVMF ; fac1 to mem X/Y
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
lda #<_rndf_rnum5
|
||||||
|
ldy #>_rndf_rnum5
|
||||||
|
jmp push_float
|
||||||
|
_rndf_rnum5 .byte 0,0,0,0,0
|
||||||
|
.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
|
||||||
|
|
||||||
|
pop_float_fac2 .proc
|
||||||
|
; -- pops float from stack into FAC2
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jmp CONUPK
|
||||||
|
.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 P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
jsr prog8_lib.pop_index_times_5
|
||||||
|
jsr prog8_lib.add_a_to_zpword
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+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 _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_X
|
||||||
|
jsr MOVFM
|
||||||
|
lda #<FL_FONE
|
||||||
|
ldy #>FL_FONE
|
||||||
|
jsr FADD
|
||||||
|
ldx P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr MOVMF
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
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_X
|
||||||
|
lda #<FL_FONE
|
||||||
|
ldy #>FL_FONE
|
||||||
|
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_X
|
||||||
|
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_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 P8ZP_SCRATCH_REG_X
|
||||||
|
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_X
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr CONUPK ; fac2 = float1
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr FPWR
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
div_f .proc
|
||||||
|
; -- push f1/f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
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 P8ZP_SCRATCH_REG_X
|
||||||
|
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 P8ZP_SCRATCH_REG_X
|
||||||
|
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 P8ZP_SCRATCH_REG_X
|
||||||
|
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 P8ZP_SCRATCH_REG_X
|
||||||
|
jsr NEGOP
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
abs_f .proc
|
||||||
|
; -- push abs(float) on stack (as float)
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
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 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
|
||||||
|
|
||||||
|
func_sin .proc
|
||||||
|
; -- push sin(f) back onto stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
jsr SIN
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_cos .proc
|
||||||
|
; -- push cos(f) back onto stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
jsr COS
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_tan .proc
|
||||||
|
; -- push tan(f) back onto stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
jsr TAN
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_atan .proc
|
||||||
|
; -- push atan(f) back onto stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
jsr ATN
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_ln .proc
|
||||||
|
; -- push ln(f) back onto stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
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 P8ZP_SCRATCH_REG_X
|
||||||
|
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 P8ZP_SCRATCH_REG_X
|
||||||
|
jsr SQR
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rad .proc
|
||||||
|
; -- convert degrees to radians (d * pi / 180)
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
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 P8ZP_SCRATCH_REG_X
|
||||||
|
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 P8ZP_SCRATCH_REG_X
|
||||||
|
jsr FADDH
|
||||||
|
jsr INT
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_floor .proc
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG_X
|
||||||
|
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 P8ZP_SCRATCH_REG_X
|
||||||
|
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 P8ESTACK_LO,x ; array size
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1 ; times 5 because of float
|
||||||
|
jmp prog8_lib.func_any_b._entry
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_all_f .proc
|
||||||
|
inx
|
||||||
|
jsr prog8_lib.peek_address
|
||||||
|
lda P8ESTACK_LO,x ; array size
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1 ; times 5 because of float
|
||||||
|
tay
|
||||||
|
dey
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
clc
|
||||||
|
dey
|
||||||
|
adc (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
adc (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
adc (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
adc (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
cmp #0
|
||||||
|
beq +
|
||||||
|
cpy #255
|
||||||
|
bne -
|
||||||
|
lda #1
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
+ sta P8ESTACK_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 P8ZP_SCRATCH_REG_X
|
||||||
|
- sty P8ZP_SCRATCH_REG
|
||||||
|
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
|
||||||
|
+ ldy P8ZP_SCRATCH_REG
|
||||||
|
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 P8ZP_SCRATCH_REG_X
|
||||||
|
- sty P8ZP_SCRATCH_REG
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr FADD
|
||||||
|
ldy P8ZP_SCRATCH_REG
|
||||||
|
dey
|
||||||
|
cpy #255
|
||||||
|
beq +
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
clc
|
||||||
|
adc #5
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
bcc -
|
||||||
|
inc P8ZP_SCRATCH_W1+1
|
||||||
|
bne -
|
||||||
|
+ jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
sign_f .proc
|
||||||
|
jsr pop_float_fac1
|
||||||
|
jsr SIGN
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
set_0_array_float .proc
|
||||||
|
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ESTACK_LO,x
|
||||||
|
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 on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ESTACK_LO,x
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
swap_floats .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
|
File diff suppressed because it is too large
Load Diff
@ -6,238 +6,427 @@
|
|||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
|
||||||
~ c64 {
|
c64 {
|
||||||
memory ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
|
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||||
memory ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
|
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||||
memory ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
|
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||||
memory uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||||
memory uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||||
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
|
|
||||||
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
|
|
||||||
|
|
||||||
|
&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
|
||||||
|
|
||||||
memory ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
; the default addresses for the character screen chars and colors
|
||||||
memory ubyte TIME_MID = $a1 ; .. mid byte
|
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
||||||
memory ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
||||||
memory ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
|
||||||
memory ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
|
||||||
|
|
||||||
memory ubyte COLOR = $0286 ; cursor color
|
; the default locations of the 8 sprite pointers (store address of sprite / 64)
|
||||||
memory ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
&ubyte SPRPTR0 = 2040
|
||||||
memory uword CINV = $0314 ; IRQ vector
|
&ubyte SPRPTR1 = 2041
|
||||||
memory uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
&ubyte SPRPTR2 = 2042
|
||||||
memory uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
&ubyte SPRPTR3 = 2043
|
||||||
memory uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
&ubyte SPRPTR4 = 2044
|
||||||
|
&ubyte SPRPTR5 = 2045
|
||||||
|
&ubyte SPRPTR6 = 2046
|
||||||
|
&ubyte SPRPTR7 = 2047
|
||||||
|
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
|
||||||
|
|
||||||
; 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)
|
|
||||||
memory ubyte SPRPTR0 = 2040
|
|
||||||
memory ubyte SPRPTR1 = 2041
|
|
||||||
memory ubyte SPRPTR2 = 2042
|
|
||||||
memory ubyte SPRPTR3 = 2043
|
|
||||||
memory ubyte SPRPTR4 = 2044
|
|
||||||
memory ubyte SPRPTR5 = 2045
|
|
||||||
memory ubyte SPRPTR6 = 2046
|
|
||||||
memory ubyte SPRPTR7 = 2047
|
|
||||||
memory ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
|
|
||||||
|
|
||||||
|
|
||||||
; ---- VIC-II 6567/6569/856x registers ----
|
; ---- VIC-II 6567/6569/856x registers ----
|
||||||
|
|
||||||
memory ubyte SP0X = $d000
|
&ubyte SP0X = $d000
|
||||||
memory ubyte SP0Y = $d001
|
&ubyte SP0Y = $d001
|
||||||
memory ubyte SP1X = $d002
|
&ubyte SP1X = $d002
|
||||||
memory ubyte SP1Y = $d003
|
&ubyte SP1Y = $d003
|
||||||
memory ubyte SP2X = $d004
|
&ubyte SP2X = $d004
|
||||||
memory ubyte SP2Y = $d005
|
&ubyte SP2Y = $d005
|
||||||
memory ubyte SP3X = $d006
|
&ubyte SP3X = $d006
|
||||||
memory ubyte SP3Y = $d007
|
&ubyte SP3Y = $d007
|
||||||
memory ubyte SP4X = $d008
|
&ubyte SP4X = $d008
|
||||||
memory ubyte SP4Y = $d009
|
&ubyte SP4Y = $d009
|
||||||
memory ubyte SP5X = $d00a
|
&ubyte SP5X = $d00a
|
||||||
memory ubyte SP5Y = $d00b
|
&ubyte SP5Y = $d00b
|
||||||
memory ubyte SP6X = $d00c
|
&ubyte SP6X = $d00c
|
||||||
memory ubyte SP6Y = $d00d
|
&ubyte SP6Y = $d00d
|
||||||
memory ubyte SP7X = $d00e
|
&ubyte SP7X = $d00e
|
||||||
memory ubyte SP7Y = $d00f
|
&ubyte SP7Y = $d00f
|
||||||
memory ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
|
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
|
||||||
memory uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
|
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
|
||||||
|
|
||||||
memory ubyte MSIGX = $d010
|
&ubyte MSIGX = $d010
|
||||||
memory ubyte SCROLY = $d011
|
&ubyte SCROLY = $d011
|
||||||
memory ubyte RASTER = $d012
|
&ubyte RASTER = $d012
|
||||||
memory ubyte LPENX = $d013
|
&ubyte LPENX = $d013
|
||||||
memory ubyte LPENY = $d014
|
&ubyte LPENY = $d014
|
||||||
memory ubyte SPENA = $d015
|
&ubyte SPENA = $d015
|
||||||
memory ubyte SCROLX = $d016
|
&ubyte SCROLX = $d016
|
||||||
memory ubyte YXPAND = $d017
|
&ubyte YXPAND = $d017
|
||||||
memory ubyte VMCSB = $d018
|
&ubyte VMCSB = $d018
|
||||||
memory ubyte VICIRQ = $d019
|
&ubyte VICIRQ = $d019
|
||||||
memory ubyte IREQMASK = $d01a
|
&ubyte IREQMASK = $d01a
|
||||||
memory ubyte SPBGPR = $d01b
|
&ubyte SPBGPR = $d01b
|
||||||
memory ubyte SPMC = $d01c
|
&ubyte SPMC = $d01c
|
||||||
memory ubyte XXPAND = $d01d
|
&ubyte XXPAND = $d01d
|
||||||
memory ubyte SPSPCL = $d01e
|
&ubyte SPSPCL = $d01e
|
||||||
memory ubyte SPBGCL = $d01f
|
&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
|
||||||
|
|
||||||
memory ubyte EXTCOL = $d020 ; border color
|
|
||||||
memory ubyte BGCOL0 = $d021 ; screen color
|
|
||||||
memory ubyte BGCOL1 = $d022
|
|
||||||
memory ubyte BGCOL2 = $d023
|
|
||||||
memory ubyte BGCOL4 = $d024
|
|
||||||
memory ubyte SPMC0 = $d025
|
|
||||||
memory ubyte SPMC1 = $d026
|
|
||||||
memory ubyte SP0COL = $d027
|
|
||||||
memory ubyte SP1COL = $d028
|
|
||||||
memory ubyte SP2COL = $d029
|
|
||||||
memory ubyte SP3COL = $d02a
|
|
||||||
memory ubyte SP4COL = $d02b
|
|
||||||
memory ubyte SP5COL = $d02c
|
|
||||||
memory ubyte SP6COL = $d02d
|
|
||||||
memory ubyte SP7COL = $d02e
|
|
||||||
memory ubyte[8] SPCOL = $d027
|
|
||||||
|
|
||||||
|
|
||||||
; ---- end of VIC-II registers ----
|
; ---- end of VIC-II registers ----
|
||||||
|
|
||||||
; ---- CIA 6526 1 & 2 registers ----
|
; ---- CIA 6526 1 & 2 registers ----
|
||||||
|
|
||||||
memory ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive
|
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
|
||||||
memory ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port
|
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
|
||||||
memory ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
|
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
|
||||||
memory ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
|
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
|
||||||
memory ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
|
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
|
||||||
memory ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
|
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
|
||||||
memory ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
|
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
|
||||||
memory ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
|
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
|
||||||
memory ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
|
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
|
||||||
memory ubyte CIA1TODSEC = $DC09 ; time of day, seconds
|
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
|
||||||
memory ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
|
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
|
||||||
memory ubyte CIA1TODHR = $DC0B ; time of day, hours
|
&ubyte CIA1TODHR = $DC0B ; time of day, hours
|
||||||
memory ubyte CIA1SDR = $DC0C ; Serial Data Register
|
&ubyte CIA1SDR = $DC0C ; Serial Data Register
|
||||||
memory ubyte CIA1ICR = $DC0D
|
&ubyte CIA1ICR = $DC0D
|
||||||
memory ubyte CIA1CRA = $DC0E
|
&ubyte CIA1CRA = $DC0E
|
||||||
memory ubyte CIA1CRB = $DC0F
|
&ubyte CIA1CRB = $DC0F
|
||||||
|
|
||||||
memory ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
|
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
|
||||||
memory ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
|
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
|
||||||
memory ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
|
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
|
||||||
memory ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
|
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
|
||||||
memory ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
|
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
|
||||||
memory ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
|
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
|
||||||
memory ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
|
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
|
||||||
memory ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
|
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
|
||||||
memory ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
|
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
|
||||||
memory ubyte CIA2TODSEC = $DD09 ; time of day, seconds
|
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
|
||||||
memory ubyte CIA2TODMIN = $DD0A ; time of day, minutes
|
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
|
||||||
memory ubyte CIA2TODHR = $DD0B ; time of day, hours
|
&ubyte CIA2TODHR = $DD0B ; time of day, hours
|
||||||
memory ubyte CIA2SDR = $DD0C ; Serial Data Register
|
&ubyte CIA2SDR = $DD0C ; Serial Data Register
|
||||||
memory ubyte CIA2ICR = $DD0D
|
&ubyte CIA2ICR = $DD0D
|
||||||
memory ubyte CIA2CRA = $DD0E
|
&ubyte CIA2CRA = $DD0E
|
||||||
memory ubyte CIA2CRB = $DD0F
|
&ubyte CIA2CRB = $DD0F
|
||||||
|
|
||||||
; ---- end of CIA registers ----
|
; ---- end of CIA registers ----
|
||||||
|
|
||||||
; ---- SID 6581/8580 registers ----
|
; ---- SID 6581/8580 registers ----
|
||||||
|
|
||||||
memory ubyte FREQLO1 = $D400 ; channel 1 freq lo
|
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
|
||||||
memory ubyte FREQHI1 = $D401 ; channel 1 freq hi
|
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
|
||||||
memory uword FREQ1 = $D400 ; channel 1 freq (word)
|
&uword FREQ1 = $D400 ; channel 1 freq (word)
|
||||||
memory ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
|
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
|
||||||
memory ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
|
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
|
||||||
memory uword PW1 = $D402 ; channel 1 pulse width (word)
|
&uword PW1 = $D402 ; channel 1 pulse width (word)
|
||||||
memory ubyte CR1 = $D404 ; channel 1 voice control register
|
&ubyte CR1 = $D404 ; channel 1 voice control register
|
||||||
memory ubyte AD1 = $D405 ; channel 1 attack & decay
|
&ubyte AD1 = $D405 ; channel 1 attack & decay
|
||||||
memory ubyte SR1 = $D406 ; channel 1 sustain & release
|
&ubyte SR1 = $D406 ; channel 1 sustain & release
|
||||||
memory ubyte FREQLO2 = $D407 ; channel 2 freq lo
|
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
|
||||||
memory ubyte FREQHI2 = $D408 ; channel 2 freq hi
|
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
|
||||||
memory uword FREQ2 = $D407 ; channel 2 freq (word)
|
&uword FREQ2 = $D407 ; channel 2 freq (word)
|
||||||
memory ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
|
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
|
||||||
memory ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
|
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
|
||||||
memory uword PW2 = $D409 ; channel 2 pulse width (word)
|
&uword PW2 = $D409 ; channel 2 pulse width (word)
|
||||||
memory ubyte CR2 = $D40B ; channel 2 voice control register
|
&ubyte CR2 = $D40B ; channel 2 voice control register
|
||||||
memory ubyte AD2 = $D40C ; channel 2 attack & decay
|
&ubyte AD2 = $D40C ; channel 2 attack & decay
|
||||||
memory ubyte SR2 = $D40D ; channel 2 sustain & release
|
&ubyte SR2 = $D40D ; channel 2 sustain & release
|
||||||
memory ubyte FREQLO3 = $D40E ; channel 3 freq lo
|
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
|
||||||
memory ubyte FREQHI3 = $D40F ; channel 3 freq hi
|
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
|
||||||
memory uword FREQ3 = $D40E ; channel 3 freq (word)
|
&uword FREQ3 = $D40E ; channel 3 freq (word)
|
||||||
memory ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
|
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
|
||||||
memory ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
|
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
|
||||||
memory uword PW3 = $D410 ; channel 3 pulse width (word)
|
&uword PW3 = $D410 ; channel 3 pulse width (word)
|
||||||
memory ubyte CR3 = $D412 ; channel 3 voice control register
|
&ubyte CR3 = $D412 ; channel 3 voice control register
|
||||||
memory ubyte AD3 = $D413 ; channel 3 attack & decay
|
&ubyte AD3 = $D413 ; channel 3 attack & decay
|
||||||
memory ubyte SR3 = $D414 ; channel 3 sustain & release
|
&ubyte SR3 = $D414 ; channel 3 sustain & release
|
||||||
memory ubyte FCLO = $D415 ; filter cutoff lo (2-0)
|
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
|
||||||
memory ubyte FCHI = $D416 ; filter cutoff hi (10-3)
|
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
|
||||||
memory uword FC = $D415 ; filter cutoff (word)
|
&uword FC = $D415 ; filter cutoff (word)
|
||||||
memory ubyte RESFILT = $D417 ; filter resonance and routing
|
&ubyte RESFILT = $D417 ; filter resonance and routing
|
||||||
memory ubyte MVOL = $D418 ; filter mode and main volume control
|
&ubyte MVOL = $D418 ; filter mode and main volume control
|
||||||
memory ubyte POTX = $D419 ; potentiometer X
|
&ubyte POTX = $D419 ; potentiometer X
|
||||||
memory ubyte POTY = $D41A ; potentiometer Y
|
&ubyte POTY = $D41A ; potentiometer Y
|
||||||
memory ubyte OSC3 = $D41B ; channel 3 oscillator value read
|
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
|
||||||
memory ubyte ENV3 = $D41C ; channel 3 envelope value read
|
&ubyte ENV3 = $D41C ; channel 3 envelope value read
|
||||||
|
|
||||||
; ---- end of SID registers ----
|
; ---- end of SID registers ----
|
||||||
|
|
||||||
|
|
||||||
|
; ---- C64 ROM kernal routines ----
|
||||||
|
|
||||||
; ---- C64 basic 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(A,X,Y) ; (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) ; (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(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(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
|
||||||
|
romsub $FFE4 = GETIN() clobbers(X,Y) -> 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 c64scr.plot for a 'safe' wrapper that preserves X.
|
||||||
|
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||||
|
|
||||||
asmsub CLEARSCR () -> clobbers(A,X,Y) -> () = $E544 ; clear the screen
|
; ---- end of C64 ROM kernal routines ----
|
||||||
asmsub HOMECRSR () -> clobbers(A,X,Y) -> () = $E566 ; cursor to top left of screen
|
|
||||||
|
|
||||||
|
|
||||||
; ---- end of C64 basic 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
|
||||||
|
tax
|
||||||
|
tay
|
||||||
|
clc
|
||||||
|
clv
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
; ---- C64 kernal routines ----
|
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 STROUT (uword strptr @ AY) -> clobbers(A, X, Y) -> () = $AB1E ; print null-terminated string (use c64scr.print instead)
|
asmsub set_irqvec() clobbers(A) {
|
||||||
asmsub IRQDFRT () -> clobbers(A,X,Y) -> () = $EA31 ; default IRQ routine
|
%asm {{
|
||||||
asmsub IRQDFEND () -> clobbers(A,X,Y) -> () = $EA81 ; default IRQ end/cleanup
|
sei
|
||||||
asmsub CINT () -> clobbers(A,X,Y) -> () = $FF81 ; (alias: SCINIT) initialize screen editor and video chip
|
lda #<_irq_handler
|
||||||
asmsub IOINIT () -> clobbers(A, X) -> () = $FF84 ; initialize I/O devices (CIA, SID, IRQ)
|
sta c64.CINV
|
||||||
asmsub RAMTAS () -> clobbers(A,X,Y) -> () = $FF87 ; initialize RAM, tape buffer, screen
|
lda #>_irq_handler
|
||||||
asmsub RESTOR () -> clobbers(A,X,Y) -> () = $FF8A ; restore default I/O vectors
|
sta c64.CINV+1
|
||||||
asmsub VECTOR (ubyte dir @ Pc, uword userptr @ XY) -> clobbers(A,Y) -> () = $FF8D ; read/set I/O vector table
|
cli
|
||||||
asmsub SETMSG (ubyte value @ A) -> clobbers() -> () = $FF90 ; set Kernal message control flag
|
rts
|
||||||
asmsub SECOND (ubyte address @ A) -> clobbers(A) -> () = $FF93 ; (alias: LSTNSA) send secondary address after LISTEN
|
_irq_handler jsr _irq_handler_init
|
||||||
asmsub TKSA (ubyte address @ A) -> clobbers(A) -> () = $FF96 ; (alias: TALKSA) send secondary address after TALK
|
jsr irq.irq
|
||||||
asmsub MEMTOP (ubyte dir @ Pc, uword address @ XY) -> clobbers() -> (uword @ XY) = $FF99 ; read/set top of memory pointer
|
jsr _irq_handler_end
|
||||||
asmsub MEMBOT (ubyte dir @ Pc, uword address @ XY) -> clobbers() -> (uword @ XY) = $FF9C ; read/set bottom of memory pointer
|
jmp c64.IRQDFRT ; continue with normal kernel irq routine
|
||||||
asmsub SCNKEY () -> clobbers(A,X,Y) -> () = $FF9F ; scan the keyboard
|
|
||||||
asmsub SETTMO (ubyte timeout @ A) -> clobbers() -> () = $FFA2 ; set time-out flag for IEEE bus
|
_irq_handler_init
|
||||||
asmsub ACPTR () -> clobbers() -> (ubyte @ A) = $FFA5 ; (alias: IECIN) input byte from serial bus
|
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
|
||||||
asmsub CIOUT (ubyte databyte @ A) -> clobbers() -> () = $FFA8 ; (alias: IECOUT) output byte to serial bus
|
stx IRQ_X_REG
|
||||||
asmsub UNTLK () -> clobbers(A) -> () = $FFAB ; command serial bus device to UNTALK
|
lda P8ZP_SCRATCH_B1
|
||||||
asmsub UNLSN () -> clobbers(A) -> () = $FFAE ; command serial bus device to UNLISTEN
|
sta IRQ_SCRATCH_ZPB1
|
||||||
asmsub LISTEN (ubyte device @ A) -> clobbers(A) -> () = $FFB1 ; command serial bus device to LISTEN
|
lda P8ZP_SCRATCH_REG
|
||||||
asmsub TALK (ubyte device @ A) -> clobbers(A) -> () = $FFB4 ; command serial bus device to TALK
|
sta IRQ_SCRATCH_ZPREG
|
||||||
asmsub READST () -> clobbers() -> (ubyte @ A) = $FFB7 ; read I/O status word
|
lda P8ZP_SCRATCH_REG_X
|
||||||
asmsub SETLFS (ubyte logical @ A, ubyte device @ X, ubyte address @ Y) -> clobbers() -> () = $FFBA ; set logical file parameters
|
sta IRQ_SCRATCH_ZPREGX
|
||||||
asmsub SETNAM (ubyte namelen @ A, str filename @ XY) -> clobbers() -> () = $FFBD ; set filename parameters
|
lda P8ZP_SCRATCH_W1
|
||||||
asmsub OPEN () -> clobbers(A,X,Y) -> () = $FFC0 ; (via 794 ($31A)) open a logical file
|
sta IRQ_SCRATCH_ZPWORD1
|
||||||
asmsub CLOSE (ubyte logical @ A) -> clobbers(A,X,Y) -> () = $FFC3 ; (via 796 ($31C)) close a logical file
|
lda P8ZP_SCRATCH_W1+1
|
||||||
asmsub CHKIN (ubyte logical @ X) -> clobbers(A,X) -> () = $FFC6 ; (via 798 ($31E)) define an input channel
|
sta IRQ_SCRATCH_ZPWORD1+1
|
||||||
asmsub CHKOUT (ubyte logical @ X) -> clobbers(A,X) -> () = $FFC9 ; (via 800 ($320)) define an output channel
|
lda P8ZP_SCRATCH_W2
|
||||||
asmsub CLRCHN () -> clobbers(A,X) -> () = $FFCC ; (via 802 ($322)) restore default devices
|
sta IRQ_SCRATCH_ZPWORD2
|
||||||
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.
|
lda P8ZP_SCRATCH_W2+1
|
||||||
asmsub CHROUT (ubyte char @ A) -> clobbers() -> () = $FFD2 ; (via 806 ($326)) output a character
|
sta IRQ_SCRATCH_ZPWORD2+1
|
||||||
asmsub LOAD (ubyte verify @ A, uword address @ XY) -> clobbers() -> (ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y) = $FFD5 ; (via 816 ($330)) load from device
|
; stack protector; make sure we don't clobber the top of the evaluation stack
|
||||||
asmsub SAVE (ubyte zp_startaddr @ A, uword endaddr @ XY) -> clobbers() -> (ubyte @ Pc, ubyte @ A) = $FFD8 ; (via 818 ($332)) save to a device
|
dex
|
||||||
asmsub SETTIM (ubyte low @ A, ubyte middle @ X, ubyte high @ Y) -> clobbers() -> () = $FFDB ; set the software clock
|
dex
|
||||||
asmsub RDTIM () -> clobbers() -> (ubyte @ A, ubyte @ X, ubyte @ Y) = $FFDE ; read the software clock
|
dex
|
||||||
asmsub STOP () -> clobbers(A,X) -> (ubyte @ Pz, ubyte @ Pc) = $FFE1 ; (via 808 ($328)) check the STOP key
|
dex
|
||||||
asmsub GETIN () -> clobbers(X,Y) -> (ubyte @ A) = $FFE4 ; (via 810 ($32A)) get a character
|
dex
|
||||||
asmsub CLALL () -> clobbers(A,X) -> () = $FFE7 ; (via 812 ($32C)) close all files
|
dex
|
||||||
asmsub UDTIM () -> clobbers(A,X) -> () = $FFEA ; update the software clock
|
cld
|
||||||
asmsub SCREEN () -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFED ; read number of screen rows and columns
|
rts
|
||||||
asmsub PLOT (ubyte dir @ Pc, ubyte col @ Y, ubyte row @ X) -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFF0 ; read/set position of cursor on screen. See c64scr.PLOT for a 'safe' wrapper that preserves X.
|
|
||||||
asmsub IOBASE () -> clobbers() -> (uword @ XY) = $FFF3 ; read base address of I/O devices
|
_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_ZPREGX
|
||||||
|
sta P8ZP_SCRATCH_REG_X
|
||||||
|
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_ZPREGX .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 ----
|
||||||
|
|
||||||
; ---- end of C64 kernal routines ----
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
545
compiler/res/prog8lib/c64textio.p8
Normal file
545
compiler/res/prog8lib/c64textio.p8
Normal file
@ -0,0 +1,545 @@
|
|||||||
|
; 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
|
||||||
|
|
||||||
|
|
||||||
|
%import c64lib
|
||||||
|
%import conv
|
||||||
|
|
||||||
|
|
||||||
|
txt {
|
||||||
|
|
||||||
|
asmsub clear_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||||
|
; ---- clear 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
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_left_full (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_X
|
||||||
|
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_X
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_right_full (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_X
|
||||||
|
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_X
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_up_full (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_X
|
||||||
|
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_X
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_down_full (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_X
|
||||||
|
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_X
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_X
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
jsr c64.CHROUT
|
||||||
|
txa
|
||||||
|
jsr c64.CHROUT
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
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_X
|
||||||
|
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_X
|
||||||
|
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_X
|
||||||
|
pha
|
||||||
|
cmp #0
|
||||||
|
bpl +
|
||||||
|
lda #'-'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
+ pla
|
||||||
|
jsr conv.byte2decimal
|
||||||
|
jsr print_ub._print_byte_digits
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_X
|
||||||
|
bcc +
|
||||||
|
pha
|
||||||
|
lda #'$'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
+ jsr conv.ubyte2hex
|
||||||
|
jsr c64.CHROUT
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
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_X
|
||||||
|
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_X
|
||||||
|
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_X
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ ldx P8ZP_SCRATCH_REG_X
|
||||||
|
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_X
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
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 @Y, ubyte row @A) clobbers(A) {
|
||||||
|
; ---- set the character in SCRATCH_ZPB1 on the screen matrix at the given position
|
||||||
|
%asm {{
|
||||||
|
sty P8ZP_SCRATCH_REG
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda _screenrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
lda _screenrows,y
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_REG
|
||||||
|
sta _mod+1
|
||||||
|
bcc +
|
||||||
|
inc _mod+2
|
||||||
|
+ lda P8ZP_SCRATCH_B1
|
||||||
|
_mod sta $ffff ; modified
|
||||||
|
rts
|
||||||
|
|
||||||
|
_screenrows .word $0400 + range(0, 1000, 40)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub getchr (ubyte col @Y, ubyte row @A) clobbers(Y) -> ubyte @ A {
|
||||||
|
; ---- get the character in the screen matrix at the given location
|
||||||
|
%asm {{
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda setchr._screenrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
lda setchr._screenrows,y
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
sta _mod+1
|
||||||
|
bcc _mod
|
||||||
|
inc _mod+2
|
||||||
|
_mod lda $ffff ; modified
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub setclr (ubyte col @Y, ubyte row @A) clobbers(A) {
|
||||||
|
; ---- set the color in SCRATCH_ZPB1 on the screen matrix at the given position
|
||||||
|
%asm {{
|
||||||
|
sty P8ZP_SCRATCH_REG
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda _colorrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
lda _colorrows,y
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_REG
|
||||||
|
sta _mod+1
|
||||||
|
bcc +
|
||||||
|
inc _mod+2
|
||||||
|
+ lda P8ZP_SCRATCH_B1
|
||||||
|
_mod sta $ffff ; modified
|
||||||
|
rts
|
||||||
|
|
||||||
|
_colorrows .word $d800 + range(0, 1000, 40)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub getclr (ubyte col @Y, ubyte row @A) clobbers(Y) -> ubyte @ A {
|
||||||
|
; ---- get the color in the screen color matrix at the given location
|
||||||
|
%asm {{
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda setclr._colorrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
lda setclr._colorrows,y
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
sta _mod+1
|
||||||
|
bcc _mod
|
||||||
|
inc _mod+2
|
||||||
|
_mod lda $ffff ; modified
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
|
||||||
|
; ---- 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 color
|
||||||
|
_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_X
|
||||||
|
tax
|
||||||
|
clc
|
||||||
|
jsr c64.PLOT
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
361
compiler/res/prog8lib/conv.p8
Normal file
361
compiler/res/prog8lib/conv.p8
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
; 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_X
|
||||||
|
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_X
|
||||||
|
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 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
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
201
compiler/res/prog8lib/cx16lib.p8
Normal file
201
compiler/res/prog8lib/cx16lib.p8
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
; 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
|
||||||
|
|
||||||
|
|
||||||
|
c64 {
|
||||||
|
|
||||||
|
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||||
|
|
||||||
|
; STROUT --> use screen.print
|
||||||
|
; CLEARSCR -> use screen.clear_screen
|
||||||
|
; HOMECRSR -> use screen.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(A,X,Y) ; (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) ; (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(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(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
|
||||||
|
romsub $FFE4 = GETIN() clobbers(X,Y) -> 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 screen.plot for a 'safe' wrapper that preserves X.
|
||||||
|
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cx16 {
|
||||||
|
|
||||||
|
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
||||||
|
; spelling of the names is taken from the Commander X-16 rom sources
|
||||||
|
|
||||||
|
; the sixteen virtual 16-bit registers
|
||||||
|
&ubyte r0 = $02
|
||||||
|
&ubyte r0L = $02
|
||||||
|
&ubyte r0H = $03
|
||||||
|
&ubyte r1 = $04
|
||||||
|
&ubyte r1L = $04
|
||||||
|
&ubyte r1H = $05
|
||||||
|
&ubyte r2 = $06
|
||||||
|
&ubyte r2L = $06
|
||||||
|
&ubyte r2H = $07
|
||||||
|
&ubyte r3 = $08
|
||||||
|
&ubyte r3L = $08
|
||||||
|
&ubyte r3H = $09
|
||||||
|
&ubyte r4 = $0a
|
||||||
|
&ubyte r4L = $0a
|
||||||
|
&ubyte r4H = $0b
|
||||||
|
&ubyte r5 = $0c
|
||||||
|
&ubyte r5L = $0c
|
||||||
|
&ubyte r5H = $0d
|
||||||
|
&ubyte r6 = $0e
|
||||||
|
&ubyte r6L = $0e
|
||||||
|
&ubyte r6H = $0f
|
||||||
|
&ubyte r7 = $10
|
||||||
|
&ubyte r7L = $10
|
||||||
|
&ubyte r7H = $11
|
||||||
|
&ubyte r8 = $12
|
||||||
|
&ubyte r8L = $12
|
||||||
|
&ubyte r8H = $13
|
||||||
|
&ubyte r9 = $14
|
||||||
|
&ubyte r9L = $14
|
||||||
|
&ubyte r9H = $15
|
||||||
|
&ubyte r10 = $16
|
||||||
|
&ubyte r10L = $16
|
||||||
|
&ubyte r10H = $17
|
||||||
|
&ubyte r11 = $18
|
||||||
|
&ubyte r11L = $18
|
||||||
|
&ubyte r11H = $19
|
||||||
|
&ubyte r12 = $1a
|
||||||
|
&ubyte r12L = $1a
|
||||||
|
&ubyte r12H = $1b
|
||||||
|
&ubyte r13 = $1c
|
||||||
|
&ubyte r13L = $1c
|
||||||
|
&ubyte r13H = $1d
|
||||||
|
&ubyte r14 = $1e
|
||||||
|
&ubyte r14L = $1e
|
||||||
|
&ubyte r14H = $1f
|
||||||
|
&ubyte r15 = $20
|
||||||
|
&ubyte r15L = $20
|
||||||
|
&ubyte r15H = $21
|
||||||
|
|
||||||
|
|
||||||
|
; TODO subroutine args + soubroutine returnvalues + clobber registers
|
||||||
|
|
||||||
|
; supported C128 additions
|
||||||
|
romsub $ff4a = close_all()
|
||||||
|
romsub $ff59 = lkupla()
|
||||||
|
romsub $ff5c = lkupsa()
|
||||||
|
romsub $ff5f = screen_set_mode()
|
||||||
|
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
|
||||||
|
romsub $ff65 = pfkey()
|
||||||
|
romsub $ff6e = jsrfar()
|
||||||
|
romsub $ff74 = fetch()
|
||||||
|
romsub $ff77 = stash()
|
||||||
|
romsub $ff7a = cmpare()
|
||||||
|
romsub $ff7d = primm()
|
||||||
|
|
||||||
|
; X16 additions
|
||||||
|
romsub $ff44 = macptr()
|
||||||
|
romsub $ff47 = enter_basic()
|
||||||
|
romsub $ff68 = mouse_config()
|
||||||
|
romsub $ff6b = mouse_get()
|
||||||
|
romsub $ff71 = mouse_scan()
|
||||||
|
romsub $ff53 = joystick_scan()
|
||||||
|
romsub $ff56 = joystick_get()
|
||||||
|
romsub $ff4d = clock_set_date_time()
|
||||||
|
romsub $ff50 = clock_get_date_time()
|
||||||
|
|
||||||
|
; high level graphics & fonts
|
||||||
|
romsub $ff20 = GRAPH_init()
|
||||||
|
romsub $ff23 = GRAPH_clear()
|
||||||
|
romsub $ff26 = GRAPH_set_window()
|
||||||
|
romsub $ff29 = GRAPH_set_colors()
|
||||||
|
romsub $ff2c = GRAPH_draw_line()
|
||||||
|
romsub $ff2f = GRAPH_draw_rect()
|
||||||
|
romsub $ff32 = GRAPH_move_rect()
|
||||||
|
romsub $ff35 = GRAPH_draw_oval()
|
||||||
|
romsub $ff38 = GRAPH_draw_image()
|
||||||
|
romsub $ff3b = GRAPH_set_font()
|
||||||
|
romsub $ff3e = GRAPH_get_char_size()
|
||||||
|
romsub $ff41 = GRAPH_put_char()
|
||||||
|
|
||||||
|
; TODO framebuffer API not yet included, include it
|
||||||
|
|
||||||
|
romsub $fef0 = sprite_set_image()
|
||||||
|
romsub $fef3 = sprite_set_position()
|
||||||
|
romsub $fee4 = memory_fill()
|
||||||
|
romsub $fee7 = memory_copy()
|
||||||
|
romsub $feea = memory_crc()
|
||||||
|
romsub $feed = memory_decompress()
|
||||||
|
romsub $fedb = console_init()
|
||||||
|
romsub $fede = console_put_char()
|
||||||
|
romsub $fee1 = console_get_char()
|
||||||
|
romsub $fed8 = console_put_image()
|
||||||
|
romsub $fed5 = console_set_paging_message()
|
||||||
|
romsub $fed2 = kbdbuf_put()
|
||||||
|
romsub $fecf = entropy_get()
|
||||||
|
romsub $fecc = monitor()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; ---- 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
|
||||||
|
lda #0
|
||||||
|
sta $00
|
||||||
|
sta $01
|
||||||
|
jsr c64.IOINIT
|
||||||
|
jsr c64.RESTOR
|
||||||
|
jsr c64.CINT
|
||||||
|
lda #0
|
||||||
|
tax
|
||||||
|
tay
|
||||||
|
clc
|
||||||
|
clv
|
||||||
|
cli
|
||||||
|
lda #66
|
||||||
|
clc
|
||||||
|
jsr console_put_char
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
240
compiler/res/prog8lib/cx16textio.p8
Normal file
240
compiler/res/prog8lib/cx16textio.p8
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
; 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
|
||||||
|
|
||||||
|
|
||||||
|
%import cx16lib
|
||||||
|
%import conv
|
||||||
|
|
||||||
|
|
||||||
|
txt {
|
||||||
|
|
||||||
|
asmsub clear_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||||
|
; ---- clear the character screen with the given fill character and character color.
|
||||||
|
; (assumes screen and color matrix are at their default addresses)
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
brk ; TODO
|
||||||
|
}}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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_X
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
jsr c64.CHROUT
|
||||||
|
txa
|
||||||
|
jsr c64.CHROUT
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
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_X
|
||||||
|
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_X
|
||||||
|
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_X
|
||||||
|
pha
|
||||||
|
cmp #0
|
||||||
|
bpl +
|
||||||
|
lda #'-'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
+ pla
|
||||||
|
jsr conv.byte2decimal
|
||||||
|
jsr print_ub._print_byte_digits
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_X
|
||||||
|
bcc +
|
||||||
|
pha
|
||||||
|
lda #'$'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
+ jsr conv.ubyte2hex
|
||||||
|
jsr c64.CHROUT
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
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_X
|
||||||
|
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_X
|
||||||
|
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_X
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ ldx P8ZP_SCRATCH_REG_X
|
||||||
|
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_X
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
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 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_X
|
||||||
|
tax
|
||||||
|
clc
|
||||||
|
jsr c64.PLOT
|
||||||
|
ldx P8ZP_SCRATCH_REG_X
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
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", ""
|
||||||
}
|
}
|
||||||
|
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
|
prog8_lib {
|
||||||
|
|
||||||
~ prog8_lib {
|
|
||||||
%asminclude "library:prog8lib.asm", ""
|
%asminclude "library:prog8lib.asm", ""
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
1.1 (beta)
|
4.0
|
||||||
|
@ -1,216 +1,143 @@
|
|||||||
package prog8
|
package prog8
|
||||||
|
|
||||||
import prog8.ast.*
|
import kotlinx.cli.*
|
||||||
import prog8.compiler.*
|
import prog8.ast.base.AstException
|
||||||
import prog8.compiler.target.c64.AsmGen
|
import prog8.compiler.CompilationResult
|
||||||
import prog8.compiler.target.c64.C64Zeropage
|
import prog8.compiler.compileProgram
|
||||||
import prog8.optimizing.constantFold
|
import prog8.compiler.target.CompilationTarget
|
||||||
import prog8.optimizing.optimizeStatements
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
import prog8.optimizing.simplifyExpressions
|
import prog8.compiler.target.c64.Petscii
|
||||||
|
import prog8.compiler.target.c64.codegen.AsmGen
|
||||||
|
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
import prog8.parser.importModule
|
import java.io.IOException
|
||||||
import java.io.File
|
import java.nio.file.FileSystems
|
||||||
import java.io.PrintStream
|
import java.nio.file.Path
|
||||||
import java.lang.Exception
|
import java.nio.file.StandardWatchEventKinds
|
||||||
import java.nio.file.Paths
|
import java.time.LocalDateTime
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
import kotlin.system.measureTimeMillis
|
|
||||||
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
|
|
||||||
// check if the user wants to launch the VM instead
|
|
||||||
if("-vm" in args) {
|
|
||||||
val newArgs = args.toMutableList()
|
|
||||||
newArgs.remove("-vm")
|
|
||||||
return stackVmMain(newArgs.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
printSoftwareHeader("compiler")
|
printSoftwareHeader("compiler")
|
||||||
|
|
||||||
if (args.isEmpty())
|
|
||||||
usage()
|
|
||||||
compileMain(args)
|
compileMain(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printSoftwareHeader(what: String) {
|
internal fun printSoftwareHeader(what: String) {
|
||||||
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
|
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
|
||||||
println("\nProg8 $what by Irmen de Jong (irmen@razorvine.net)")
|
println("\nProg8 $what v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
|
||||||
println("Version: $buildVersion")
|
|
||||||
println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
|
println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun compileMain(args: Array<String>) {
|
fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
|
||||||
var emulatorToStart = ""
|
|
||||||
var moduleFile = ""
|
|
||||||
var writeVmCode = false
|
|
||||||
var writeAssembly = true
|
|
||||||
for (arg in args) {
|
|
||||||
if(arg=="-emu")
|
|
||||||
emulatorToStart = "x64"
|
|
||||||
else if(arg=="-emu2")
|
|
||||||
emulatorToStart = "x64sc"
|
|
||||||
else if(arg=="-writevm")
|
|
||||||
writeVmCode = true
|
|
||||||
else if(arg=="-noasm")
|
|
||||||
writeAssembly = false
|
|
||||||
else if(!arg.startsWith("-"))
|
|
||||||
moduleFile = arg
|
|
||||||
else
|
|
||||||
usage()
|
|
||||||
}
|
|
||||||
if(moduleFile.isBlank())
|
|
||||||
usage()
|
|
||||||
|
|
||||||
val filepath = Paths.get(moduleFile).normalize()
|
|
||||||
var programname = "?"
|
private fun compileMain(args: Array<String>) {
|
||||||
|
val cli = CommandLineInterface("prog8compiler")
|
||||||
|
val startEmulator by cli.flagArgument("-emu", "auto-start the Vice C-64 emulator after successful compilation")
|
||||||
|
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
|
||||||
|
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
|
||||||
|
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
|
||||||
|
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||||
|
val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently 'c64' and 'cx16' available", "c64")
|
||||||
|
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val totalTime = measureTimeMillis {
|
cli.parse(args)
|
||||||
// import main module and process additional imports
|
} catch (e: Exception) {
|
||||||
println("Parsing...")
|
exitProcess(1)
|
||||||
val moduleAst = importModule(filepath)
|
}
|
||||||
moduleAst.linkParents()
|
|
||||||
var namespace = moduleAst.definingScope()
|
|
||||||
|
|
||||||
// determine special compiler options
|
when(compilationTarget) {
|
||||||
|
"c64" -> {
|
||||||
val compilerOptions = determineCompilationOptions(moduleAst)
|
with(CompilationTarget) {
|
||||||
|
name = "Commodore-64"
|
||||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
machine = C64MachineDefinition
|
||||||
throw ParsingFailedError("${moduleAst.position} BASIC launcher requires output type PRG.")
|
encodeString = { str, altEncoding ->
|
||||||
|
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
// perform initial syntax checks and constant folding
|
}
|
||||||
println("Syntax check...")
|
decodeString = { bytes, altEncoding ->
|
||||||
val heap = HeapValues()
|
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||||
val time1= measureTimeMillis {
|
}
|
||||||
moduleAst.checkIdentifiers(heap)
|
asmGenerator = ::AsmGen
|
||||||
}
|
|
||||||
//println(" time1: $time1")
|
|
||||||
val time2 = measureTimeMillis {
|
|
||||||
moduleAst.constantFold(namespace, heap)
|
|
||||||
}
|
|
||||||
//println(" time2: $time2")
|
|
||||||
val time3 = measureTimeMillis {
|
|
||||||
moduleAst.reorderStatements(namespace,heap) // reorder statements to please the compiler later
|
|
||||||
}
|
|
||||||
//println(" time3: $time3")
|
|
||||||
val time4 = measureTimeMillis {
|
|
||||||
moduleAst.checkValid(namespace, compilerOptions, heap) // check if tree is valid
|
|
||||||
}
|
|
||||||
//println(" time4: $time4")
|
|
||||||
|
|
||||||
// optimize the parse tree
|
|
||||||
println("Optimizing...")
|
|
||||||
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers(heap) // useful for checking symbol usage later?
|
|
||||||
while (true) {
|
|
||||||
// keep optimizing expressions and statements until no more steps remain
|
|
||||||
val optsDone1 = moduleAst.simplifyExpressions(namespace, heap)
|
|
||||||
val optsDone2 = moduleAst.optimizeStatements(namespace, heap)
|
|
||||||
if (optsDone1 + optsDone2 == 0)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime
|
|
||||||
moduleAst.checkValid(namespace, compilerOptions, heap) // check if final tree is valid
|
|
||||||
moduleAst.checkRecursion(namespace) // check if there are recursive subroutine calls
|
|
||||||
|
|
||||||
// namespace.debugPrint()
|
|
||||||
|
|
||||||
// compile the syntax tree into stackvmProg form, and optimize that
|
|
||||||
val compiler = Compiler(moduleAst, namespace, heap)
|
|
||||||
val intermediate = compiler.compile(compilerOptions)
|
|
||||||
intermediate.optimize()
|
|
||||||
|
|
||||||
if(writeVmCode) {
|
|
||||||
val stackVmFilename = intermediate.name + ".vm.txt"
|
|
||||||
val stackvmFile = PrintStream(File(stackVmFilename), "utf-8")
|
|
||||||
intermediate.writeCode(stackvmFile)
|
|
||||||
stackvmFile.close()
|
|
||||||
println("StackVM program code written to '$stackVmFilename'")
|
|
||||||
}
|
|
||||||
|
|
||||||
if(writeAssembly) {
|
|
||||||
val zeropage = C64Zeropage(compilerOptions)
|
|
||||||
intermediate.allocateZeropage(zeropage)
|
|
||||||
val assembly = AsmGen(compilerOptions, intermediate, heap, zeropage).compileToAssembly()
|
|
||||||
assembly.assemble(compilerOptions)
|
|
||||||
programname = assembly.name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
"cx16" -> {
|
||||||
|
with(CompilationTarget) {
|
||||||
} catch (px: ParsingFailedError) {
|
name = "Commander X16"
|
||||||
System.err.print("\u001b[91m") // bright red
|
machine = CX16MachineDefinition
|
||||||
System.err.println(px.message)
|
encodeString = { str, altEncoding ->
|
||||||
System.err.print("\u001b[0m") // reset
|
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
exitProcess(1)
|
|
||||||
} catch (x: Exception) {
|
|
||||||
print("\u001b[91m") // bright red
|
|
||||||
println("\n* internal error *")
|
|
||||||
print("\u001b[0m") // reset
|
|
||||||
System.out.flush()
|
|
||||||
throw x
|
|
||||||
} catch (x: NotImplementedError) {
|
|
||||||
print("\u001b[91m") // bright red
|
|
||||||
println("\n* internal error: missing feature/code *")
|
|
||||||
print("\u001b[0m") // reset
|
|
||||||
System.out.flush()
|
|
||||||
throw x
|
|
||||||
}
|
|
||||||
|
|
||||||
if(emulatorToStart.isNotEmpty()) {
|
|
||||||
println("\nStarting C-64 emulator $emulatorToStart...")
|
|
||||||
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programname.vice-mon-list",
|
|
||||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programname+".prg")
|
|
||||||
val process = ProcessBuilder(cmdline).inheritIO().start()
|
|
||||||
process.waitFor()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun determineCompilationOptions(moduleAst: Module): CompilationOptions {
|
|
||||||
val options = moduleAst.statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
|
|
||||||
val outputType = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%output" }
|
|
||||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
|
||||||
val launcherType = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
|
|
||||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
|
||||||
moduleAst.loadAddress = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%address" }
|
|
||||||
as? Directive)?.args?.single()?.int ?: 0
|
|
||||||
val zpoption: String? = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
|
|
||||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
|
||||||
val floatsEnabled = options.any { it.name == "enable_floats" }
|
|
||||||
val zpType: ZeropageType =
|
|
||||||
if (zpoption == null)
|
|
||||||
if(floatsEnabled) ZeropageType.BASICSAFE else ZeropageType.KERNALSAFE
|
|
||||||
else
|
|
||||||
try {
|
|
||||||
ZeropageType.valueOf(zpoption)
|
|
||||||
} catch (x: IllegalArgumentException) {
|
|
||||||
ZeropageType.KERNALSAFE
|
|
||||||
// error will be printed by the astchecker
|
|
||||||
}
|
}
|
||||||
val zpReserved = moduleAst.statements
|
decodeString = { bytes, altEncoding ->
|
||||||
.asSequence()
|
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||||
.filter { it is Directive && it.directive == "%zpreserved" }
|
}
|
||||||
.map { (it as Directive).args }
|
asmGenerator = ::AsmGen
|
||||||
.map { it[0].int!!..it[1].int!! }
|
}
|
||||||
.toList()
|
}
|
||||||
|
else -> {
|
||||||
|
System.err.println("invalid compilation target. Available are: c64, cx16")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return CompilationOptions(
|
val outputPath = pathFrom(outputDir)
|
||||||
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
if(!outputPath.toFile().isDirectory) {
|
||||||
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
System.err.println("Output path doesn't exist")
|
||||||
zpType, zpReserved, floatsEnabled
|
exitProcess(1)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun usage() {
|
if(watchMode && moduleFiles.size<=1) {
|
||||||
System.err.println("Missing argument(s):")
|
val watchservice = FileSystems.getDefault().newWatchService()
|
||||||
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")
|
while(true) {
|
||||||
System.err.println(" [-writevm] write intermediate vm code to a file as well")
|
val filepath = pathFrom(moduleFiles.single()).normalize()
|
||||||
System.err.println(" [-noasm] don't create assembly code")
|
println("Continuous watch mode active. Main module: $filepath")
|
||||||
System.err.println(" [-vm] launch the prog8 virtual machine instead of the compiler")
|
|
||||||
System.err.println(" modulefile main module file to compile")
|
try {
|
||||||
exitProcess(1)
|
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
|
||||||
|
println("Imported files (now watching:)")
|
||||||
|
for (importedFile in compilationResult.importedFiles) {
|
||||||
|
print(" ")
|
||||||
|
println(importedFile)
|
||||||
|
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||||
|
}
|
||||||
|
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||||
|
val event = watchservice.take()
|
||||||
|
for(changed in event.pollEvents()) {
|
||||||
|
val changedPath = changed.context() as Path
|
||||||
|
println(" change detected: $changedPath")
|
||||||
|
}
|
||||||
|
event.reset()
|
||||||
|
println("\u001b[H\u001b[2J") // clear the screen
|
||||||
|
} catch (x: Exception) {
|
||||||
|
throw x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for(filepathRaw in moduleFiles) {
|
||||||
|
val filepath = pathFrom(filepathRaw).normalize()
|
||||||
|
val compilationResult: CompilationResult
|
||||||
|
try {
|
||||||
|
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
|
||||||
|
if(!compilationResult.success)
|
||||||
|
exitProcess(1)
|
||||||
|
} catch (x: ParsingFailedError) {
|
||||||
|
exitProcess(1)
|
||||||
|
} catch (x: AstException) {
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startEmulator) {
|
||||||
|
if (compilationResult.programName.isEmpty())
|
||||||
|
println("\nCan't start emulator because no program was assembled.")
|
||||||
|
else if(startEmulator) {
|
||||||
|
CompilationTarget.machine.launchEmulator(compilationResult.programName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
package prog8
|
|
||||||
|
|
||||||
import prog8.stackvm.*
|
|
||||||
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()
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,238 +0,0 @@
|
|||||||
package prog8.ast
|
|
||||||
|
|
||||||
import prog8.compiler.HeapValues
|
|
||||||
import prog8.functions.BuiltinFunctions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the validity of all identifiers (no conflicts)
|
|
||||||
* Also builds a list of all (scoped) symbol definitions
|
|
||||||
* Also makes sure that subroutine's parameters also become local variable decls in the subroutine's scope.
|
|
||||||
* Finally, it also makes sure the datatype of all Var decls and sub Return values is set correctly.
|
|
||||||
*/
|
|
||||||
|
|
||||||
fun Module.checkIdentifiers(heap: HeapValues): MutableMap<String, IStatement> {
|
|
||||||
val checker = AstIdentifiersChecker(heap)
|
|
||||||
this.process(checker)
|
|
||||||
|
|
||||||
// add any anonymous variables for heap values that are used, and replace literalvalue by identifierref
|
|
||||||
for (variable in checker.anonymousVariablesFromHeap) {
|
|
||||||
val scope = variable.first.definingScope()
|
|
||||||
scope.statements.add(variable.second)
|
|
||||||
val parent = variable.first.parent
|
|
||||||
when {
|
|
||||||
parent is Assignment && parent.value === variable.first -> {
|
|
||||||
val idref = IdentifierReference(listOf("auto_heap_value_${variable.first.heapId}"), variable.first.position)
|
|
||||||
idref.linkParents(parent)
|
|
||||||
parent.value = idref
|
|
||||||
}
|
|
||||||
parent is IFunctionCall -> {
|
|
||||||
val parameterPos = parent.arglist.indexOf(variable.first)
|
|
||||||
val idref = IdentifierReference(listOf("auto_heap_value_${variable.first.heapId}"), variable.first.position)
|
|
||||||
idref.linkParents(parent)
|
|
||||||
parent.arglist[parameterPos] = idref
|
|
||||||
}
|
|
||||||
else -> TODO("replace literalvalue by identifierref: $variable (in $parent)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printErrors(checker.result(), name)
|
|
||||||
return checker.symbols
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
|
||||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
|
||||||
|
|
||||||
var symbols: MutableMap<String, IStatement> = mutableMapOf()
|
|
||||||
private set
|
|
||||||
|
|
||||||
fun result(): List<AstException> {
|
|
||||||
return checkResult
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun nameError(name: String, position: Position, existing: IStatement) {
|
|
||||||
checkResult.add(NameError("name conflict '$name', first defined in ${existing.position.file} line ${existing.position.line}", position))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(block: Block): IStatement {
|
|
||||||
val scopedName = block.scopedname
|
|
||||||
val existing = symbols[scopedName]
|
|
||||||
if(existing!=null) {
|
|
||||||
nameError(block.name, block.position, existing)
|
|
||||||
} else {
|
|
||||||
symbols[scopedName] = block
|
|
||||||
}
|
|
||||||
return super.process(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(functionCall: FunctionCall): IExpression {
|
|
||||||
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
|
|
||||||
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
|
|
||||||
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, functionCall.position)
|
|
||||||
typecast.linkParents(functionCall.parent)
|
|
||||||
return super.process(typecast)
|
|
||||||
}
|
|
||||||
return super.process(functionCall)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(decl: VarDecl): IStatement {
|
|
||||||
// first, check if there are datatype errors on the vardecl
|
|
||||||
decl.datatypeErrors.forEach { checkResult.add(it) }
|
|
||||||
|
|
||||||
// now check the identifier
|
|
||||||
if(decl.name in BuiltinFunctions)
|
|
||||||
// the builtin functions can't be redefined
|
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
|
|
||||||
|
|
||||||
val scopedName = decl.scopedname
|
|
||||||
val existing = symbols[scopedName]
|
|
||||||
if(existing!=null) {
|
|
||||||
nameError(decl.name, decl.position, existing)
|
|
||||||
} else {
|
|
||||||
symbols[scopedName] = decl
|
|
||||||
}
|
|
||||||
return super.process(decl)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(subroutine: Subroutine): IStatement {
|
|
||||||
if(subroutine.name in BuiltinFunctions) {
|
|
||||||
// the builtin functions can't be redefined
|
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
|
|
||||||
} else {
|
|
||||||
if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
|
||||||
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
|
||||||
|
|
||||||
val scopedName = subroutine.scopedname
|
|
||||||
val existing = symbols[scopedName]
|
|
||||||
if (existing != null) {
|
|
||||||
nameError(subroutine.name, subroutine.position, existing)
|
|
||||||
} else {
|
|
||||||
symbols[scopedName] = subroutine
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that there are no local variables that redefine the subroutine's parameters
|
|
||||||
val allDefinedNames = subroutine.allLabelsAndVariables()
|
|
||||||
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
|
||||||
val paramsToCheck = paramNames.intersect(allDefinedNames)
|
|
||||||
for(name in paramsToCheck) {
|
|
||||||
val thing = subroutine.getLabelOrVariable(name)!!
|
|
||||||
if(thing.position != subroutine.position)
|
|
||||||
nameError(name, thing.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 allDefinedNames }
|
|
||||||
.forEach {
|
|
||||||
val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null, subroutine.position)
|
|
||||||
vardecl.linkParents(subroutine)
|
|
||||||
subroutine.statements.add(0, vardecl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.process(subroutine)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(label: Label): IStatement {
|
|
||||||
if(label.name in BuiltinFunctions) {
|
|
||||||
// the builtin functions can't be redefined
|
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", label.position))
|
|
||||||
} else {
|
|
||||||
val scopedName = label.scopedname
|
|
||||||
val existing = symbols[scopedName]
|
|
||||||
if (existing != null) {
|
|
||||||
nameError(label.name, label.position, existing)
|
|
||||||
} else {
|
|
||||||
symbols[scopedName] = label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.process(label)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(forLoop: ForLoop): IStatement {
|
|
||||||
// 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.decltype!=null)
|
|
||||||
checkResult.add(SyntaxError("register loop variables cannot be explicitly declared with a datatype", forLoop.position))
|
|
||||||
if(forLoop.loopRegister == Register.X)
|
|
||||||
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
|
|
||||||
} else if(forLoop.loopVar!=null) {
|
|
||||||
val varName = forLoop.loopVar.nameInSource.last()
|
|
||||||
if(forLoop.decltype!=null) {
|
|
||||||
val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first())
|
|
||||||
if(existing==null) {
|
|
||||||
// create the local scoped for loop variable itself
|
|
||||||
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, true, null, varName, null, forLoop.loopVar.position)
|
|
||||||
vardecl.linkParents(forLoop.body)
|
|
||||||
forLoop.body.statements.add(0, vardecl)
|
|
||||||
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if(forLoop.iterable !is RangeExpr) {
|
|
||||||
val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), 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, true, null, ForLoop.iteratorLoopcounterVarname, null, forLoop.loopVar.position)
|
|
||||||
vardecl.linkParents(forLoop.body)
|
|
||||||
forLoop.body.statements.add(0, vardecl)
|
|
||||||
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.process(forLoop)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(assignTarget: AssignTarget): AssignTarget {
|
|
||||||
if(assignTarget.register==Register.X)
|
|
||||||
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
|
|
||||||
return super.process(assignTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(returnStmt: Return): IStatement {
|
|
||||||
if(returnStmt.values.isNotEmpty()) {
|
|
||||||
// possibly adjust any literal values returned, into the desired returning data type
|
|
||||||
val subroutine = returnStmt.definingSubroutine()!!
|
|
||||||
if(subroutine.returntypes.size!=returnStmt.values.size)
|
|
||||||
return returnStmt // mismatch in number of return values, error will be printed later.
|
|
||||||
val newValues = mutableListOf<IExpression>()
|
|
||||||
for(returnvalue in returnStmt.values.zip(subroutine.returntypes)) {
|
|
||||||
val lval = returnvalue.first as? LiteralValue
|
|
||||||
if(lval!=null) {
|
|
||||||
val adjusted = lval.intoDatatype(returnvalue.second)
|
|
||||||
if(adjusted!=null && adjusted !== lval)
|
|
||||||
newValues.add(adjusted)
|
|
||||||
else
|
|
||||||
newValues.add(lval)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
newValues.add(returnvalue.first)
|
|
||||||
}
|
|
||||||
returnStmt.values = newValues
|
|
||||||
}
|
|
||||||
return super.process(returnStmt)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal val anonymousVariablesFromHeap = mutableSetOf<Pair<LiteralValue, VarDecl>>()
|
|
||||||
|
|
||||||
override fun process(literalValue: LiteralValue): LiteralValue {
|
|
||||||
if(literalValue.heapId!=null && literalValue.parent !is VarDecl) {
|
|
||||||
// a literal value that's not declared as a variable, which refers to something on the heap.
|
|
||||||
// we need to introduce an auto-generated variable for this to be able to refer to the value!
|
|
||||||
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "auto_heap_value_${literalValue.heapId}", literalValue, literalValue.position)
|
|
||||||
anonymousVariablesFromHeap.add(Pair(literalValue, variable))
|
|
||||||
}
|
|
||||||
return super.process(literalValue)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
package prog8.ast
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for the occurrence of recursive subroutine calls
|
|
||||||
*/
|
|
||||||
|
|
||||||
fun Module.checkRecursion(namespace: INameScope) {
|
|
||||||
val checker = AstRecursionChecker(namespace)
|
|
||||||
this.process(checker)
|
|
||||||
printErrors(checker.result(), name)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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.associate { it to false }.toMutableMap()
|
|
||||||
val recStack = uniqueVertices.associate { it to 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class AstRecursionChecker(private val namespace: INameScope) : IAstProcessor {
|
|
||||||
private val callGraph = DirectedGraph<INameScope>()
|
|
||||||
|
|
||||||
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 process(functionCall: FunctionCallStatement): IStatement {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return super.process(functionCall)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(functionCall: FunctionCall): IExpression {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return super.process(functionCall)
|
|
||||||
}
|
|
||||||
}
|
|
432
compiler/src/prog8/ast/AstToSourceCode.kt
Normal file
432
compiler/src/prog8/ast/AstToSourceCode.kt
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
package prog8.ast
|
||||||
|
|
||||||
|
import prog8.ast.antlr.escape
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.NumericDatatypes
|
||||||
|
import prog8.ast.base.VarDeclType
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.processing.IAstVisitor
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
|
||||||
|
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
|
||||||
|
private var scopelevel = 0
|
||||||
|
|
||||||
|
private fun indent(s: String) = " ".repeat(scopelevel) + s
|
||||||
|
private fun outputln(text: String) = output(text + "\n")
|
||||||
|
private fun outputlni(s: Any) = outputln(indent(s.toString()))
|
||||||
|
private fun outputi(s: Any) = output(indent(s.toString()))
|
||||||
|
|
||||||
|
override fun visit(program: Program) {
|
||||||
|
outputln("============= PROGRAM ${program.name} (FROM AST) ===============")
|
||||||
|
super.visit(program)
|
||||||
|
outputln("============= END PROGRAM ${program.name} (FROM AST) ===========")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(module: Module) {
|
||||||
|
if(!module.isLibraryModule) {
|
||||||
|
outputln("; ----------- module: ${module.name} -----------")
|
||||||
|
super.visit(module)
|
||||||
|
}
|
||||||
|
else outputln("; library module skipped: ${module.name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(block: Block) {
|
||||||
|
val addr = if(block.address!=null) block.address.toHex() else ""
|
||||||
|
outputln("~ ${block.name} $addr {")
|
||||||
|
scopelevel++
|
||||||
|
for(stmt in block.statements) {
|
||||||
|
outputi("")
|
||||||
|
stmt.accept(this)
|
||||||
|
output("\n")
|
||||||
|
}
|
||||||
|
scopelevel--
|
||||||
|
outputln("}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(expr: PrefixExpression) {
|
||||||
|
if(expr.operator.any { it.isLetter() })
|
||||||
|
output(" ${expr.operator} ")
|
||||||
|
else
|
||||||
|
output(expr.operator)
|
||||||
|
expr.expression.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(expr: BinaryExpression) {
|
||||||
|
expr.left.accept(this)
|
||||||
|
if(expr.operator.any { it.isLetter() })
|
||||||
|
output(" ${expr.operator} ")
|
||||||
|
else
|
||||||
|
output(expr.operator)
|
||||||
|
expr.right.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(directive: Directive) {
|
||||||
|
output("${directive.directive} ")
|
||||||
|
for(arg in directive.args) {
|
||||||
|
when {
|
||||||
|
arg.int!=null -> output(arg.int.toString())
|
||||||
|
arg.name!=null -> output(arg.name)
|
||||||
|
arg.str!=null -> output("\"${arg.str}\"")
|
||||||
|
}
|
||||||
|
if(arg!==directive.args.last())
|
||||||
|
output(",")
|
||||||
|
}
|
||||||
|
output("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun datatypeString(dt: DataType): String {
|
||||||
|
return when(dt) {
|
||||||
|
in NumericDatatypes -> dt.toString().toLowerCase()
|
||||||
|
DataType.STR -> dt.toString().toLowerCase()
|
||||||
|
DataType.ARRAY_UB -> "ubyte["
|
||||||
|
DataType.ARRAY_B -> "byte["
|
||||||
|
DataType.ARRAY_UW -> "uword["
|
||||||
|
DataType.ARRAY_W -> "word["
|
||||||
|
DataType.ARRAY_F -> "float["
|
||||||
|
DataType.STRUCT -> "" // the name of the struct is enough
|
||||||
|
else -> "?????2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(structDecl: StructDecl) {
|
||||||
|
outputln("struct ${structDecl.name} {")
|
||||||
|
scopelevel++
|
||||||
|
for(decl in structDecl.statements) {
|
||||||
|
outputi("")
|
||||||
|
decl.accept(this)
|
||||||
|
output("\n")
|
||||||
|
}
|
||||||
|
scopelevel--
|
||||||
|
outputlni("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
VarDeclType.VAR -> {}
|
||||||
|
VarDeclType.CONST -> output("const ")
|
||||||
|
VarDeclType.MEMORY -> output("&")
|
||||||
|
}
|
||||||
|
output(decl.struct?.name ?: "")
|
||||||
|
output(datatypeString(decl.datatype))
|
||||||
|
if(decl.arraysize!=null) {
|
||||||
|
decl.arraysize!!.index.accept(this)
|
||||||
|
}
|
||||||
|
if(decl.isArray)
|
||||||
|
output("]")
|
||||||
|
|
||||||
|
if(decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
|
||||||
|
output(" @zp")
|
||||||
|
output(" ${decl.name} ")
|
||||||
|
if(decl.value!=null) {
|
||||||
|
output("= ")
|
||||||
|
decl.value?.accept(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(subroutine: Subroutine) {
|
||||||
|
output("\n")
|
||||||
|
if(subroutine.isAsmSubroutine) {
|
||||||
|
outputi("asmsub ${subroutine.name} (")
|
||||||
|
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
||||||
|
val reg =
|
||||||
|
when {
|
||||||
|
param.second.stack -> "stack"
|
||||||
|
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
|
||||||
|
param.second.statusflag!=null -> param.second.statusflag.toString()
|
||||||
|
else -> "?????1"
|
||||||
|
}
|
||||||
|
output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
|
||||||
|
if(param.first!==subroutine.parameters.last())
|
||||||
|
output(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
outputi("sub ${subroutine.name} (")
|
||||||
|
for(param in subroutine.parameters) {
|
||||||
|
output("${datatypeString(param.type)} ${param.name}")
|
||||||
|
if(param!==subroutine.parameters.last())
|
||||||
|
output(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output(") ")
|
||||||
|
if(subroutine.asmClobbers.isNotEmpty()) {
|
||||||
|
output("-> clobbers (")
|
||||||
|
val regs = subroutine.asmClobbers.toList().sorted()
|
||||||
|
for(r in regs) {
|
||||||
|
output(r.toString())
|
||||||
|
if(r!==regs.last())
|
||||||
|
output(",")
|
||||||
|
}
|
||||||
|
output(") ")
|
||||||
|
}
|
||||||
|
if(subroutine.returntypes.any()) {
|
||||||
|
val rt = subroutine.returntypes.single()
|
||||||
|
output("-> ${datatypeString(rt)} ")
|
||||||
|
}
|
||||||
|
if(subroutine.asmAddress!=null)
|
||||||
|
outputln("= ${subroutine.asmAddress.toHex()}")
|
||||||
|
else {
|
||||||
|
outputln("{ ")
|
||||||
|
scopelevel++
|
||||||
|
outputStatements(subroutine.statements)
|
||||||
|
scopelevel--
|
||||||
|
outputi("}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun outputStatements(statements: List<Statement>) {
|
||||||
|
for(stmt in statements) {
|
||||||
|
outputi("")
|
||||||
|
stmt.accept(this)
|
||||||
|
output("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(functionCall: FunctionCall) {
|
||||||
|
printout(functionCall as IFunctionCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
|
printout(functionCallStatement as IFunctionCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printout(call: IFunctionCall) {
|
||||||
|
call.target.accept(this)
|
||||||
|
output("(")
|
||||||
|
for(arg in call.args) {
|
||||||
|
arg.accept(this)
|
||||||
|
if(arg!==call.args.last())
|
||||||
|
output(", ")
|
||||||
|
}
|
||||||
|
output(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(identifier: IdentifierReference) {
|
||||||
|
output(identifier.nameInSource.joinToString("."))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(jump: Jump) {
|
||||||
|
output("goto ")
|
||||||
|
when {
|
||||||
|
jump.address!=null -> output(jump.address.toHex())
|
||||||
|
jump.generatedLabel!=null -> output(jump.generatedLabel)
|
||||||
|
jump.identifier!=null -> jump.identifier.accept(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(ifStatement: IfStatement) {
|
||||||
|
output("if ")
|
||||||
|
ifStatement.condition.accept(this)
|
||||||
|
output(" ")
|
||||||
|
ifStatement.truepart.accept(this)
|
||||||
|
if(ifStatement.elsepart.statements.isNotEmpty()) {
|
||||||
|
output(" else ")
|
||||||
|
ifStatement.elsepart.accept(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(branchStatement: BranchStatement) {
|
||||||
|
output("if_${branchStatement.condition.toString().toLowerCase()} ")
|
||||||
|
branchStatement.truepart.accept(this)
|
||||||
|
if(branchStatement.elsepart.statements.isNotEmpty()) {
|
||||||
|
output(" else ")
|
||||||
|
branchStatement.elsepart.accept(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(range: RangeExpr) {
|
||||||
|
range.from.accept(this)
|
||||||
|
output(" to ")
|
||||||
|
range.to.accept(this)
|
||||||
|
output(" step ")
|
||||||
|
range.step.accept(this)
|
||||||
|
output(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(label: Label) {
|
||||||
|
output("\n")
|
||||||
|
output("${label.name}:")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(numLiteral: NumericLiteralValue) {
|
||||||
|
output(numLiteral.number.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(string: StringLiteralValue) {
|
||||||
|
output("\"${escape(string.value)}\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(array: ArrayLiteralValue) {
|
||||||
|
outputListMembers(array.value.asSequence(), '[', ']')
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun outputListMembers(array: Sequence<Expression>, openchar: Char, closechar: Char) {
|
||||||
|
var counter = 0
|
||||||
|
output(openchar.toString())
|
||||||
|
scopelevel++
|
||||||
|
for (v in array) {
|
||||||
|
v.accept(this)
|
||||||
|
if (v !== array.last())
|
||||||
|
output(", ")
|
||||||
|
counter++
|
||||||
|
if (counter > 16) {
|
||||||
|
outputln("")
|
||||||
|
outputi("")
|
||||||
|
counter = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scopelevel--
|
||||||
|
output(closechar.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(assignment: Assignment) {
|
||||||
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
|
if(binExpr!=null && binExpr.left isSameAs assignment.target
|
||||||
|
&& binExpr.operator !in setOf("and", "or", "xor")
|
||||||
|
&& binExpr.operator !in comparisonOperators) {
|
||||||
|
// we only support the inplace assignments of the form A = A <operator> <value>
|
||||||
|
assignment.target.accept(this)
|
||||||
|
output(" ${binExpr.operator}= ")
|
||||||
|
binExpr.right.accept(this)
|
||||||
|
} else {
|
||||||
|
assignment.target.accept(this)
|
||||||
|
output(" = ")
|
||||||
|
assignment.value.accept(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(postIncrDecr: PostIncrDecr) {
|
||||||
|
postIncrDecr.target.accept(this)
|
||||||
|
output(postIncrDecr.operator)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(breakStmt: Break) {
|
||||||
|
output("break")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(forLoop: ForLoop) {
|
||||||
|
output("for ")
|
||||||
|
forLoop.loopVar.accept(this)
|
||||||
|
output(" in ")
|
||||||
|
forLoop.iterable.accept(this)
|
||||||
|
output(" ")
|
||||||
|
forLoop.body.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(whileLoop: WhileLoop) {
|
||||||
|
output("while ")
|
||||||
|
whileLoop.condition.accept(this)
|
||||||
|
output(" ")
|
||||||
|
whileLoop.body.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(repeatLoop: RepeatLoop) {
|
||||||
|
output("repeat ")
|
||||||
|
repeatLoop.iterations?.accept(this)
|
||||||
|
output(" ")
|
||||||
|
repeatLoop.body.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(untilLoop: UntilLoop) {
|
||||||
|
output("do ")
|
||||||
|
untilLoop.body.accept(this)
|
||||||
|
output(" until ")
|
||||||
|
untilLoop.untilCondition.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(returnStmt: Return) {
|
||||||
|
output("return ")
|
||||||
|
returnStmt.value?.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||||
|
arrayIndexedExpression.identifier.accept(this)
|
||||||
|
output("[")
|
||||||
|
arrayIndexedExpression.arrayspec.index.accept(this)
|
||||||
|
output("]")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(assignTarget: AssignTarget) {
|
||||||
|
assignTarget.memoryAddress?.accept(this)
|
||||||
|
assignTarget.identifier?.accept(this)
|
||||||
|
assignTarget.arrayindexed?.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(scope: AnonymousScope) {
|
||||||
|
outputln("{")
|
||||||
|
scopelevel++
|
||||||
|
outputStatements(scope.statements)
|
||||||
|
scopelevel--
|
||||||
|
outputi("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(typecast: TypecastExpression) {
|
||||||
|
output("(")
|
||||||
|
typecast.expression.accept(this)
|
||||||
|
output(" as ${datatypeString(typecast.type)}) ")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(memread: DirectMemoryRead) {
|
||||||
|
output("@(")
|
||||||
|
memread.addressExpression.accept(this)
|
||||||
|
output(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(memwrite: DirectMemoryWrite) {
|
||||||
|
output("@(")
|
||||||
|
memwrite.addressExpression.accept(this)
|
||||||
|
output(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(addressOf: AddressOf) {
|
||||||
|
output("&")
|
||||||
|
addressOf.identifier.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(inlineAssembly: InlineAssembly) {
|
||||||
|
outputlni("%asm {{")
|
||||||
|
outputln(inlineAssembly.assembly)
|
||||||
|
outputlni("}}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||||
|
output(builtinFunctionStatementPlaceholder.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(whenStatement: WhenStatement) {
|
||||||
|
output("when ")
|
||||||
|
whenStatement.condition.accept(this)
|
||||||
|
outputln(" {")
|
||||||
|
scopelevel++
|
||||||
|
whenStatement.choices.forEach { it.accept(this) }
|
||||||
|
scopelevel--
|
||||||
|
outputlni("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(whenChoice: WhenChoice) {
|
||||||
|
val choiceValues = whenChoice.values
|
||||||
|
if(choiceValues==null)
|
||||||
|
outputi("else -> ")
|
||||||
|
else {
|
||||||
|
outputi("")
|
||||||
|
for(value in choiceValues) {
|
||||||
|
value.accept(this)
|
||||||
|
if(value !== choiceValues.last())
|
||||||
|
output(",")
|
||||||
|
}
|
||||||
|
output(" -> ")
|
||||||
|
}
|
||||||
|
if(whenChoice.statements.statements.size==1)
|
||||||
|
whenChoice.statements.statements.single().accept(this)
|
||||||
|
else
|
||||||
|
whenChoice.statements.accept(this)
|
||||||
|
outputln("")
|
||||||
|
}
|
||||||
|
}
|
334
compiler/src/prog8/ast/AstToplevel.kt
Normal file
334
compiler/src/prog8/ast/AstToplevel.kt
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
package prog8.ast
|
||||||
|
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.Expression
|
||||||
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstVisitor
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.functions.BuiltinFunctions
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
|
interface Node {
|
||||||
|
val position: Position
|
||||||
|
var parent: Node // will be linked correctly later (late init)
|
||||||
|
fun linkParents(parent: Node)
|
||||||
|
|
||||||
|
fun definingModule(): Module {
|
||||||
|
if(this is Module)
|
||||||
|
return this
|
||||||
|
return findParentNode<Module>(this)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun definingSubroutine(): Subroutine? = findParentNode<Subroutine>(this)
|
||||||
|
|
||||||
|
fun definingScope(): INameScope {
|
||||||
|
val scope = findParentNode<INameScope>(this)
|
||||||
|
if(scope!=null) {
|
||||||
|
return scope
|
||||||
|
}
|
||||||
|
if(this is Label && this.name.startsWith("builtin::")) {
|
||||||
|
return BuiltinFunctionScopePlaceholder
|
||||||
|
}
|
||||||
|
if(this is GlobalNamespace)
|
||||||
|
return this
|
||||||
|
throw FatalAstException("scope missing from $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceChildNode(node: Node, replacement: Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFunctionCall {
|
||||||
|
var target: IdentifierReference
|
||||||
|
var args: MutableList<Expression>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface INameScope {
|
||||||
|
val name: String
|
||||||
|
val position: Position
|
||||||
|
val statements: MutableList<Statement>
|
||||||
|
val parent: Node
|
||||||
|
|
||||||
|
fun linkParents(parent: Node)
|
||||||
|
|
||||||
|
fun subScope(name: String): INameScope? {
|
||||||
|
for(stmt in statements) {
|
||||||
|
when(stmt) {
|
||||||
|
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
|
||||||
|
is ForLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
|
is UntilLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
|
is WhileLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
|
is BranchStatement -> {
|
||||||
|
if(stmt.truepart.name==name) return stmt.truepart
|
||||||
|
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||||
|
}
|
||||||
|
is IfStatement -> {
|
||||||
|
if(stmt.truepart.name==name) return stmt.truepart
|
||||||
|
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||||
|
}
|
||||||
|
is WhenStatement -> {
|
||||||
|
val scope = stmt.choices.firstOrNull { it.statements.name==name }
|
||||||
|
if(scope!=null)
|
||||||
|
return scope.statements
|
||||||
|
}
|
||||||
|
is INameScope -> if(stmt.name==name) return stmt
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLabelOrVariable(name: String): Statement? {
|
||||||
|
// this is called A LOT and could perhaps be optimized a bit more,
|
||||||
|
// but adding a memoization cache didn't make much of a practical runtime difference
|
||||||
|
for (stmt in statements) {
|
||||||
|
if (stmt is VarDecl && stmt.name==name) return stmt
|
||||||
|
if (stmt is Label && stmt.name==name) return stmt
|
||||||
|
if (stmt is AnonymousScope) {
|
||||||
|
val sub = stmt.getLabelOrVariable(name)
|
||||||
|
if(sub!=null)
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allDefinedSymbols(): List<Pair<String, Statement>> {
|
||||||
|
return statements.mapNotNull {
|
||||||
|
when (it) {
|
||||||
|
is Label -> it.name to it
|
||||||
|
is VarDecl -> it.name to it
|
||||||
|
is Subroutine -> it.name to it
|
||||||
|
is Block -> it.name to it
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lookup(scopedName: List<String>, localContext: Node) : Statement? {
|
||||||
|
if(scopedName.size>1) {
|
||||||
|
// a scoped name can a) refer to a member of a struct, or b) refer to a name in another module.
|
||||||
|
// try the struct first.
|
||||||
|
val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl
|
||||||
|
val struct = thing?.struct
|
||||||
|
if (struct != null) {
|
||||||
|
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
|
||||||
|
// return ref to the mangled name variable
|
||||||
|
val mangled = mangledStructMemberName(thing.name, scopedName.last())
|
||||||
|
return thing.definingScope().getLabelOrVariable(mangled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
|
||||||
|
for(module in localContext.definingModule().program.modules) {
|
||||||
|
var scope: INameScope? = module
|
||||||
|
for(name in scopedName.dropLast(1)) {
|
||||||
|
scope = scope?.subScope(name)
|
||||||
|
if(scope==null)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if(scope!=null) {
|
||||||
|
val result = scope.getLabelOrVariable(scopedName.last())
|
||||||
|
if(result!=null)
|
||||||
|
return result
|
||||||
|
return scope.subScope(scopedName.last()) as Statement?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
// unqualified name, find the scope the localContext is in, look in that first
|
||||||
|
var statementScope = localContext
|
||||||
|
while(statementScope !is ParentSentinel) {
|
||||||
|
val localScope = statementScope.definingScope()
|
||||||
|
val result = localScope.getLabelOrVariable(scopedName[0])
|
||||||
|
if (result != null)
|
||||||
|
return result
|
||||||
|
val subscope = localScope.subScope(scopedName[0]) as Statement?
|
||||||
|
if (subscope != null)
|
||||||
|
return subscope
|
||||||
|
// not found in this scope, look one higher up
|
||||||
|
statementScope = statementScope.parent
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
|
||||||
|
fun containsNoVars() = statements.all { it !is VarDecl }
|
||||||
|
fun containsNoCodeNorVars() = !containsCodeOrVars()
|
||||||
|
|
||||||
|
fun remove(stmt: Statement) {
|
||||||
|
if(!statements.remove(stmt))
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAssignable {
|
||||||
|
// just a tag for now
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*********** Everything starts from here, the Program; zero or more modules *************/
|
||||||
|
|
||||||
|
class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||||
|
val namespace = GlobalNamespace(modules)
|
||||||
|
|
||||||
|
val definedLoadAddress: Int
|
||||||
|
get() = modules.first().loadAddress
|
||||||
|
|
||||||
|
var actualLoadAddress: Int = 0
|
||||||
|
|
||||||
|
fun entrypoint(): Subroutine? {
|
||||||
|
val mainBlocks = allBlocks().filter { it.name=="main" }
|
||||||
|
if(mainBlocks.size > 1)
|
||||||
|
throw FatalAstException("more than one 'main' block")
|
||||||
|
return if(mainBlocks.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
mainBlocks[0].subScope("start") as Subroutine?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(value) = throw FatalAstException("can't set parent of program")
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
modules.forEach {
|
||||||
|
it.linkParents(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
override var statements: MutableList<Statement>,
|
||||||
|
override val position: Position,
|
||||||
|
val isLibraryModule: Boolean,
|
||||||
|
val source: Path) : Node, INameScope {
|
||||||
|
|
||||||
|
override lateinit var parent: Node
|
||||||
|
lateinit var program: Program
|
||||||
|
val importedBy = mutableListOf<Module>()
|
||||||
|
val imports = mutableSetOf<Module>()
|
||||||
|
|
||||||
|
var loadAddress: Int = 0 // can be set with the %address directive
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
statements.forEach {it.linkParents(this)}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)"
|
||||||
|
|
||||||
|
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 {
|
||||||
|
override val name = "<<<global>>>"
|
||||||
|
override val position = Position("<<<global>>>", 0, 0, 0)
|
||||||
|
override val statements = mutableListOf<Statement>()
|
||||||
|
override var parent: Node = ParentSentinel
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
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? {
|
||||||
|
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
|
||||||
|
// builtin functions always exist, return a dummy localContext for them
|
||||||
|
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
|
||||||
|
builtinPlaceholder.parent = ParentSentinel
|
||||||
|
return builtinPlaceholder
|
||||||
|
}
|
||||||
|
|
||||||
|
if(scopedName.size>1) {
|
||||||
|
// a scoped name can a) refer to a member of a struct, or b) refer to a name in another module.
|
||||||
|
// try the struct first.
|
||||||
|
val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl
|
||||||
|
val struct = thing?.struct
|
||||||
|
if (struct != null) {
|
||||||
|
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
|
||||||
|
// return ref to the mangled name variable
|
||||||
|
val mangled = mangledStructMemberName(thing.name, scopedName.last())
|
||||||
|
return thing.definingScope().getLabelOrVariable(mangled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// lookup something from the module.
|
||||||
|
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
|
||||||
|
is Label, is VarDecl, is Block, is Subroutine, is StructDecl -> stmt
|
||||||
|
null -> null
|
||||||
|
else -> throw SyntaxError("invalid identifier target type", stmt.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object BuiltinFunctionScopePlaceholder : INameScope {
|
||||||
|
override val name = "<<builtin-functions-scope-placeholder>>"
|
||||||
|
override val position = Position("<<placeholder>>", 0, 0, 0)
|
||||||
|
override var statements = mutableListOf<Statement>()
|
||||||
|
override var parent: Node = ParentSentinel
|
||||||
|
override fun linkParents(parent: Node) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// prefix for struct member variables
|
||||||
|
internal fun mangledStructMemberName(varName: String, memberName: String) = "prog8struct_${varName}_$memberName"
|
@ -1,43 +0,0 @@
|
|||||||
package prog8.ast
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks that are specific for imported modules.
|
|
||||||
*/
|
|
||||||
|
|
||||||
fun Module.checkImportedValid() {
|
|
||||||
val checker = ImportedAstChecker()
|
|
||||||
this.linkParents()
|
|
||||||
this.process(checker)
|
|
||||||
printErrors(checker.result(), name)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class ImportedAstChecker : IAstProcessor {
|
|
||||||
private val checkResult: MutableList<SyntaxError> = mutableListOf()
|
|
||||||
|
|
||||||
fun result(): List<SyntaxError> {
|
|
||||||
return checkResult
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module check: most global directives don't apply for imported modules
|
|
||||||
*/
|
|
||||||
override fun process(module: Module) {
|
|
||||||
super.process(module)
|
|
||||||
val newStatements : MutableList<IStatement> = mutableListOf()
|
|
||||||
|
|
||||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
|
||||||
for (sourceStmt in module.statements) {
|
|
||||||
val stmt = sourceStmt.process(this)
|
|
||||||
if(stmt is Directive && stmt.parent is Module) {
|
|
||||||
if(stmt.directive in moduleLevelDirectives) {
|
|
||||||
printWarning("ignoring module directive because it was imported", stmt.position, stmt.directive)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newStatements.add(stmt)
|
|
||||||
}
|
|
||||||
module.statements = newStatements
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,247 +0,0 @@
|
|||||||
package prog8.ast
|
|
||||||
|
|
||||||
import prog8.compiler.HeapValues
|
|
||||||
|
|
||||||
fun Module.reorderStatements(namespace: INameScope, heap: HeapValues) {
|
|
||||||
val initvalueCreator = VarInitValueCreator()
|
|
||||||
this.process(initvalueCreator)
|
|
||||||
|
|
||||||
val checker = StatementReorderer(namespace, heap)
|
|
||||||
this.process(checker)
|
|
||||||
}
|
|
||||||
|
|
||||||
const val initvarsSubName="prog8_init_vars" // the name of the subroutine that should be called for every block to initialize its variables
|
|
||||||
|
|
||||||
|
|
||||||
private class StatementReorderer(private val namespace: INameScope, private val heap: HeapValues): IAstProcessor {
|
|
||||||
// Reorders the statements in a way the compiler needs.
|
|
||||||
// - '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.
|
|
||||||
// - in every scope:
|
|
||||||
// -- the directives '%output', '%launcher', '%zeropage', '%zpreserved', '%address' and '%option' will come first.
|
|
||||||
// -- all vardecls then follow.
|
|
||||||
// -- the remaining statements then follow in their original order.
|
|
||||||
//
|
|
||||||
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
|
|
||||||
// - all other subroutines will be moved to the end of their block.
|
|
||||||
|
|
||||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
|
||||||
|
|
||||||
override fun process(module: Module) {
|
|
||||||
super.process(module)
|
|
||||||
|
|
||||||
val (blocks, other) = module.statements.partition { it is Block }
|
|
||||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
|
|
||||||
|
|
||||||
val mainBlock = module.statements.single { it is Block && it.name=="main" }
|
|
||||||
if((mainBlock as Block).address==null) {
|
|
||||||
module.statements.remove(mainBlock)
|
|
||||||
module.statements.add(0, mainBlock)
|
|
||||||
}
|
|
||||||
val varDecls = module.statements.filterIsInstance<VarDecl>()
|
|
||||||
module.statements.removeAll(varDecls)
|
|
||||||
module.statements.addAll(0, varDecls)
|
|
||||||
|
|
||||||
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
|
|
||||||
module.statements.removeAll(directives)
|
|
||||||
module.statements.addAll(0, directives)
|
|
||||||
|
|
||||||
// TODO make sure user-defined blocks come BEFORE library blocks
|
|
||||||
|
|
||||||
sortConstantAssignments(module.statements)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(block: Block): IStatement {
|
|
||||||
|
|
||||||
val subroutines = block.statements.filterIsInstance<Subroutine>()
|
|
||||||
var numSubroutinesAtEnd = 0
|
|
||||||
// move all subroutines to the end of the block
|
|
||||||
for (subroutine in subroutines) {
|
|
||||||
if(subroutine.name!="start" || block.name!="main") {
|
|
||||||
block.statements.remove(subroutine)
|
|
||||||
block.statements.add(subroutine)
|
|
||||||
}
|
|
||||||
numSubroutinesAtEnd++
|
|
||||||
}
|
|
||||||
// move the "start" subroutine to the top
|
|
||||||
if(block.name=="main") {
|
|
||||||
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let {
|
|
||||||
block.statements.remove(it)
|
|
||||||
block.statements.add(0, it)
|
|
||||||
numSubroutinesAtEnd--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure there is a 'return' in front of the first subroutine
|
|
||||||
// (if it isn't the first statement in the block itself, and isn't the program's entrypoint)
|
|
||||||
if(numSubroutinesAtEnd>0 && block.statements.size > (numSubroutinesAtEnd+1)) {
|
|
||||||
val firstSub = block.statements[block.statements.size - numSubroutinesAtEnd] as Subroutine
|
|
||||||
if(firstSub.name != "start" && block.name != "main") {
|
|
||||||
val stmtBeforeFirstSub = block.statements[block.statements.size - numSubroutinesAtEnd - 1]
|
|
||||||
if (stmtBeforeFirstSub !is Return
|
|
||||||
&& stmtBeforeFirstSub !is Jump
|
|
||||||
&& stmtBeforeFirstSub !is Subroutine
|
|
||||||
&& stmtBeforeFirstSub !is BuiltinFunctionStatementPlaceholder) {
|
|
||||||
val ret = Return(emptyList(), stmtBeforeFirstSub.position)
|
|
||||||
ret.linkParents(block)
|
|
||||||
block.statements.add(block.statements.size - numSubroutinesAtEnd, ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val varDecls = block.statements.filter { it is 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)
|
|
||||||
|
|
||||||
sortConstantAssignments(block.statements)
|
|
||||||
|
|
||||||
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.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.process(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(subroutine: Subroutine): IStatement {
|
|
||||||
super.process(subroutine)
|
|
||||||
|
|
||||||
sortConstantAssignments(subroutine.statements)
|
|
||||||
|
|
||||||
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
|
|
||||||
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(emptyList(), subroutine.position)
|
|
||||||
returnStmt.linkParents(subroutine)
|
|
||||||
subroutine.statements.add(returnStmt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return subroutine
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(scope: AnonymousScope): AnonymousScope {
|
|
||||||
scope.statements = scope.statements.map { it.process(this)}.toMutableList()
|
|
||||||
sortConstantAssignments(scope.statements)
|
|
||||||
return scope
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sortConstantAssignments(statements: MutableList<IStatement>) {
|
|
||||||
// sort assignments by datatype and value, so multiple initializations with the same value can be optimized (to load the value just once)
|
|
||||||
val result = mutableListOf<IStatement>()
|
|
||||||
val stmtIter = statements.iterator()
|
|
||||||
for(stmt in stmtIter) {
|
|
||||||
if(stmt is Assignment) {
|
|
||||||
val constval = stmt.value.constValue(namespace, heap)
|
|
||||||
if(constval!=null) {
|
|
||||||
val (sorted, trailing) = sortConstantAssignmentSequence(stmt, stmtIter)
|
|
||||||
result.addAll(sorted)
|
|
||||||
if(trailing!=null)
|
|
||||||
result.add(trailing)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
result.add(stmt)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
result.add(stmt)
|
|
||||||
}
|
|
||||||
statements.clear()
|
|
||||||
statements.addAll(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sortConstantAssignmentSequence(first: Assignment, stmtIter: MutableIterator<IStatement>): Pair<List<Assignment>, IStatement?> {
|
|
||||||
val sequence= mutableListOf(first)
|
|
||||||
var trailing: IStatement? = null
|
|
||||||
while(stmtIter.hasNext()) {
|
|
||||||
val next = stmtIter.next()
|
|
||||||
if(next is Assignment) {
|
|
||||||
val constValue = next.value.constValue(namespace, heap)
|
|
||||||
if(constValue==null) {
|
|
||||||
trailing = next
|
|
||||||
break
|
|
||||||
}
|
|
||||||
sequence.add(next)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
trailing=next
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val sorted = sequence.sortedWith(compareBy({it.value.resultingDatatype(namespace, heap)}, {it.singleTarget?.shortString(true)}))
|
|
||||||
return Pair(sorted, trailing)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class VarInitValueCreator: IAstProcessor {
|
|
||||||
// Replace the var decl with an assignment and add a new vardecl with the default constant value.
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
|
||||||
|
|
||||||
override fun process(module: Module) {
|
|
||||||
super.process(module)
|
|
||||||
|
|
||||||
// add any new vardecls to the various scopes
|
|
||||||
for(decl in vardeclsToAdd)
|
|
||||||
for(d in decl.value) {
|
|
||||||
d.linkParents(decl.key as Node)
|
|
||||||
decl.key.statements.add(0, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun process(decl: VarDecl): IStatement {
|
|
||||||
super.process(decl)
|
|
||||||
if(decl.type!=VarDeclType.VAR || decl.value==null)
|
|
||||||
return decl
|
|
||||||
|
|
||||||
if(decl.datatype in NumericDatatypes) {
|
|
||||||
val scope = decl.definingScope()
|
|
||||||
if(scope !in vardeclsToAdd)
|
|
||||||
vardeclsToAdd[scope] = mutableListOf()
|
|
||||||
vardeclsToAdd[scope]!!.add(decl.asDefaultValueDecl(null))
|
|
||||||
val declvalue = decl.value!!
|
|
||||||
val value =
|
|
||||||
if(declvalue is LiteralValue) {
|
|
||||||
val converted = declvalue.intoDatatype(decl.datatype)
|
|
||||||
converted ?: declvalue
|
|
||||||
}
|
|
||||||
else
|
|
||||||
declvalue
|
|
||||||
return VariableInitializationAssignment(
|
|
||||||
AssignTarget(null, IdentifierReference(decl.scopedname.split("."), decl.position), null, null, decl.position),
|
|
||||||
null,
|
|
||||||
value,
|
|
||||||
decl.position
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return decl
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
675
compiler/src/prog8/ast/antlr/Antr2Kotlin.kt
Normal file
675
compiler/src/prog8/ast/antlr/Antr2Kotlin.kt
Normal file
@ -0,0 +1,675 @@
|
|||||||
|
package prog8.ast.antlr
|
||||||
|
|
||||||
|
import org.antlr.v4.runtime.IntStream
|
||||||
|
import org.antlr.v4.runtime.ParserRuleContext
|
||||||
|
import org.antlr.v4.runtime.tree.TerminalNode
|
||||||
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.target.CompilationTarget
|
||||||
|
import prog8.parser.CustomLexer
|
||||||
|
import prog8.parser.prog8Parser
|
||||||
|
import java.io.CharConversionException
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
|
/***************** Antlr Extension methods to create AST ****************/
|
||||||
|
|
||||||
|
private data class NumericLiteral(val number: Number, val datatype: DataType)
|
||||||
|
|
||||||
|
internal fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
|
||||||
|
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
|
||||||
|
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 {
|
||||||
|
val customTokensource = this.start.tokenSource as? CustomLexer
|
||||||
|
val filename =
|
||||||
|
when {
|
||||||
|
customTokensource!=null -> customTokensource.modulePath.fileName.toString()
|
||||||
|
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
|
||||||
|
else -> File(start.inputStream.sourceName).name
|
||||||
|
}
|
||||||
|
// note: be ware of TAB characters in the source text, they count as 1 column...
|
||||||
|
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement {
|
||||||
|
val blockstatements = block_statement().map {
|
||||||
|
when {
|
||||||
|
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
|
||||||
|
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
|
||||||
|
it.directive()!=null -> it.directive().toAst()
|
||||||
|
it.inlineasm()!=null -> it.inlineasm().toAst()
|
||||||
|
else -> throw FatalAstException("weird block statement $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
|
||||||
|
statement().asSequence().map { it.toAst() }.toMutableList()
|
||||||
|
|
||||||
|
private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
|
||||||
|
vardecl()?.let { return it.toAst() }
|
||||||
|
|
||||||
|
varinitializer()?.let {
|
||||||
|
val vd = it.vardecl()
|
||||||
|
return VarDecl(
|
||||||
|
VarDeclType.VAR,
|
||||||
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
|
vd.arrayindex()?.toAst(),
|
||||||
|
vd.varname.text,
|
||||||
|
null,
|
||||||
|
it.expression().toAst(),
|
||||||
|
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||||
|
false,
|
||||||
|
it.toPosition()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
structvarinitializer()?.let {
|
||||||
|
val vd = it.structvardecl()
|
||||||
|
return VarDecl(
|
||||||
|
VarDeclType.VAR,
|
||||||
|
DataType.STRUCT,
|
||||||
|
ZeropageWish.NOT_IN_ZEROPAGE,
|
||||||
|
null,
|
||||||
|
vd.varname.text,
|
||||||
|
vd.structname.text,
|
||||||
|
it.expression().toAst(),
|
||||||
|
isArray = false,
|
||||||
|
autogeneratedDontRemove = false,
|
||||||
|
position = it.toPosition()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
structvardecl()?.let {
|
||||||
|
return VarDecl(
|
||||||
|
VarDeclType.VAR,
|
||||||
|
DataType.STRUCT,
|
||||||
|
ZeropageWish.NOT_IN_ZEROPAGE,
|
||||||
|
null,
|
||||||
|
it.varname.text,
|
||||||
|
it.structname.text,
|
||||||
|
null,
|
||||||
|
isArray = false,
|
||||||
|
autogeneratedDontRemove = false,
|
||||||
|
position = it.toPosition()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
constdecl()?.let {
|
||||||
|
val cvarinit = it.varinitializer()
|
||||||
|
val vd = cvarinit.vardecl()
|
||||||
|
return VarDecl(
|
||||||
|
VarDeclType.CONST,
|
||||||
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
|
vd.arrayindex()?.toAst(),
|
||||||
|
vd.varname.text,
|
||||||
|
null,
|
||||||
|
cvarinit.expression().toAst(),
|
||||||
|
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||||
|
false,
|
||||||
|
cvarinit.toPosition()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryvardecl()?.let {
|
||||||
|
val mvarinit = it.varinitializer()
|
||||||
|
val vd = mvarinit.vardecl()
|
||||||
|
return VarDecl(
|
||||||
|
VarDeclType.MEMORY,
|
||||||
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
|
vd.arrayindex()?.toAst(),
|
||||||
|
vd.varname.text,
|
||||||
|
null,
|
||||||
|
mvarinit.expression().toAst(),
|
||||||
|
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||||
|
false,
|
||||||
|
mvarinit.toPosition()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return Assignment(it.assign_target().toAst(), it.expression().toAst(), it.toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
augassignment()?.let {
|
||||||
|
// replace A += X with A = A + X
|
||||||
|
val target = it.assign_target().toAst()
|
||||||
|
val oper = it.operator.text.substringBefore('=')
|
||||||
|
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
|
||||||
|
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
postincrdecr()?.let {
|
||||||
|
return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
val directive = directive()?.toAst()
|
||||||
|
if(directive!=null) return directive
|
||||||
|
|
||||||
|
val label = labeldef()?.toAst()
|
||||||
|
if(label!=null) return label
|
||||||
|
|
||||||
|
val jump = unconditionaljump()?.toAst()
|
||||||
|
if(jump!=null) return jump
|
||||||
|
|
||||||
|
val fcall = functioncall_stmt()?.toAst()
|
||||||
|
if(fcall!=null) return fcall
|
||||||
|
|
||||||
|
val ifstmt = if_stmt()?.toAst()
|
||||||
|
if(ifstmt!=null) return ifstmt
|
||||||
|
|
||||||
|
val returnstmt = returnstmt()?.toAst()
|
||||||
|
if(returnstmt!=null) return returnstmt
|
||||||
|
|
||||||
|
val subroutine = subroutinedeclaration()?.toAst()
|
||||||
|
if(subroutine!=null) return subroutine
|
||||||
|
|
||||||
|
val asm = inlineasm()?.toAst()
|
||||||
|
if(asm!=null) return asm
|
||||||
|
|
||||||
|
val branchstmt = branch_stmt()?.toAst()
|
||||||
|
if(branchstmt!=null) return branchstmt
|
||||||
|
|
||||||
|
val forloop = forloop()?.toAst()
|
||||||
|
if(forloop!=null) return forloop
|
||||||
|
|
||||||
|
val untilloop = untilloop()?.toAst()
|
||||||
|
if(untilloop!=null) return untilloop
|
||||||
|
|
||||||
|
val whileloop = whileloop()?.toAst()
|
||||||
|
if(whileloop!=null) return whileloop
|
||||||
|
|
||||||
|
val repeatloop = repeatloop()?.toAst()
|
||||||
|
if(repeatloop!=null) return repeatloop
|
||||||
|
|
||||||
|
val breakstmt = breakstmt()?.toAst()
|
||||||
|
if(breakstmt!=null) return breakstmt
|
||||||
|
|
||||||
|
val whenstmt = whenstmt()?.toAst()
|
||||||
|
if(whenstmt!=null) return whenstmt
|
||||||
|
|
||||||
|
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 params = asmsub_params()?.toAst() ?: emptyList()
|
||||||
|
val returns = asmsub_returns()?.toAst() ?: emptyList()
|
||||||
|
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
|
||||||
|
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
|
||||||
|
val normalReturntypes = 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) }
|
||||||
|
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AsmSubroutineParameter(name: String,
|
||||||
|
type: DataType,
|
||||||
|
val registerOrPair: RegisterOrPair?,
|
||||||
|
val statusflag: Statusflag?,
|
||||||
|
val stack: Boolean,
|
||||||
|
position: Position) : SubroutineParameter(name, type, position)
|
||||||
|
|
||||||
|
private class AsmSubroutineReturn(val type: DataType,
|
||||||
|
val registerOrPair: RegisterOrPair?,
|
||||||
|
val statusflag: Statusflag?,
|
||||||
|
val stack: Boolean,
|
||||||
|
val position: Position)
|
||||||
|
|
||||||
|
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
||||||
|
= 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,
|
||||||
|
!it.stack?.text.isNullOrEmpty(), toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||||
|
= asmsub_param().map {
|
||||||
|
val vardecl = it.vardecl()
|
||||||
|
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
|
||||||
|
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 '$name'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister,
|
||||||
|
!it.stack?.text.isNullOrEmpty(), toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
||||||
|
val void = this.VOID() != null
|
||||||
|
val location = scoped_identifier().toAst()
|
||||||
|
return if(expression_list() == null)
|
||||||
|
FunctionCallStatement(location, mutableListOf(), void, toPosition())
|
||||||
|
else
|
||||||
|
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
|
||||||
|
val location = scoped_identifier().toAst()
|
||||||
|
return if(expression_list() == null)
|
||||||
|
FunctionCall(location, mutableListOf(), toPosition())
|
||||||
|
else
|
||||||
|
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.InlineasmContext.toAst() =
|
||||||
|
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
||||||
|
|
||||||
|
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
|
||||||
|
return Return(expression()?.toAst(), toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
|
||||||
|
val address = integerliteral()?.toAst()?.number?.toInt()
|
||||||
|
val identifier = scoped_identifier()?.toAst()
|
||||||
|
return Jump(address, identifier, null, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.LabeldefContext.toAst(): Statement =
|
||||||
|
Label(children[0].text, toPosition())
|
||||||
|
|
||||||
|
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
|
||||||
|
return Subroutine(identifier().text,
|
||||||
|
sub_params()?.toAst() ?: emptyList(),
|
||||||
|
sub_return_part()?.toAst() ?: emptyList(),
|
||||||
|
emptyList(),
|
||||||
|
emptyList(),
|
||||||
|
emptySet(),
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
statement_block()?.toAst() ?: mutableListOf(),
|
||||||
|
toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
|
||||||
|
val returns = sub_returns() ?: return emptyList()
|
||||||
|
return returns.datatype().map { it.toAst() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||||
|
vardecl().map {
|
||||||
|
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
|
||||||
|
SubroutineParameter(it.varname.text, datatype, it.toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
|
||||||
|
val identifier = scoped_identifier()
|
||||||
|
return when {
|
||||||
|
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
|
||||||
|
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
|
||||||
|
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
||||||
|
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.ArrayindexContext.toAst() : ArrayIndex =
|
||||||
|
ArrayIndex(expression().toAst(), toPosition())
|
||||||
|
|
||||||
|
private fun prog8Parser.DirectiveContext.toAst() : Directive =
|
||||||
|
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")
|
||||||
|
|
||||||
|
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||||
|
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
|
||||||
|
val integer: Int
|
||||||
|
var datatype = DataType.UBYTE
|
||||||
|
when (radix) {
|
||||||
|
10 -> {
|
||||||
|
integer = try {
|
||||||
|
text.toInt()
|
||||||
|
} catch(x: NumberFormatException) {
|
||||||
|
throw AstException("${toPosition()} invalid decimal literal ${x.message}")
|
||||||
|
}
|
||||||
|
datatype = when(integer) {
|
||||||
|
in 0..255 -> DataType.UBYTE
|
||||||
|
in -128..127 -> DataType.BYTE
|
||||||
|
in 0..65535 -> DataType.UWORD
|
||||||
|
in -32768..32767 -> DataType.WORD
|
||||||
|
else -> DataType.FLOAT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
if(text.length>8)
|
||||||
|
datatype = DataType.UWORD
|
||||||
|
try {
|
||||||
|
integer = text.toInt(2)
|
||||||
|
} catch(x: NumberFormatException) {
|
||||||
|
throw AstException("${toPosition()} invalid binary literal ${x.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
16 -> {
|
||||||
|
if(text.length>2)
|
||||||
|
datatype = DataType.UWORD
|
||||||
|
try {
|
||||||
|
integer = text.toInt(16)
|
||||||
|
} catch(x: NumberFormatException) {
|
||||||
|
throw AstException("${toPosition()} invalid hexadecimal literal ${x.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw FatalAstException("invalid radix")
|
||||||
|
}
|
||||||
|
return NumericLiteral(integer, if (forceWord) DataType.UWORD else datatype)
|
||||||
|
}
|
||||||
|
val terminal: TerminalNode = children[0] as TerminalNode
|
||||||
|
val integerPart = this.intpart.text
|
||||||
|
return when (terminal.symbol.type) {
|
||||||
|
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null)
|
||||||
|
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null)
|
||||||
|
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null)
|
||||||
|
else -> throw FatalAstException(terminal.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||||
|
|
||||||
|
val litval = literalvalue()
|
||||||
|
if(litval!=null) {
|
||||||
|
val booleanlit = litval.booleanliteral()?.toAst()
|
||||||
|
return if(booleanlit!=null) {
|
||||||
|
NumericLiteralValue.fromBoolean(booleanlit, litval.toPosition())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val intLit = litval.integerliteral()?.toAst()
|
||||||
|
when {
|
||||||
|
intLit!=null -> when(intLit.datatype) {
|
||||||
|
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number.toShort(), litval.toPosition())
|
||||||
|
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number.toShort(), litval.toPosition())
|
||||||
|
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number.toInt(), litval.toPosition())
|
||||||
|
DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number.toInt(), litval.toPosition())
|
||||||
|
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number.toDouble(), litval.toPosition())
|
||||||
|
else -> throw FatalAstException("invalid datatype for numeric literal")
|
||||||
|
}
|
||||||
|
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
|
||||||
|
litval.stringliteral()!=null -> litval.stringliteral().toAst()
|
||||||
|
litval.charliteral()!=null -> {
|
||||||
|
try {
|
||||||
|
val cc=litval.charliteral()
|
||||||
|
NumericLiteralValue(DataType.UBYTE, CompilationTarget.encodeString(
|
||||||
|
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
|
||||||
|
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
|
||||||
|
} catch (ce: CharConversionException) {
|
||||||
|
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
litval.arrayliteral()!=null -> {
|
||||||
|
val array = litval.arrayliteral().toAst()
|
||||||
|
// 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.
|
||||||
|
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
|
||||||
|
}
|
||||||
|
else -> throw FatalAstException("invalid parsed literal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(scoped_identifier()!=null)
|
||||||
|
return scoped_identifier().toAst()
|
||||||
|
|
||||||
|
if(bop!=null)
|
||||||
|
return BinaryExpression(left.toAst(), bop.text, right.toAst(), toPosition())
|
||||||
|
|
||||||
|
if(prefix!=null)
|
||||||
|
return PrefixExpression(prefix.text, expression(0).toAst(), toPosition())
|
||||||
|
|
||||||
|
val funcall = functioncall()?.toAst()
|
||||||
|
if(funcall!=null) return funcall
|
||||||
|
|
||||||
|
if (rangefrom!=null && rangeto!=null) {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
if(childCount==3 && children[0].text=="(" && children[2].text==")")
|
||||||
|
return expression(0).toAst() // expression within ( )
|
||||||
|
|
||||||
|
if(arrayindexed()!=null)
|
||||||
|
return arrayindexed().toAst()
|
||||||
|
|
||||||
|
if(typecast()!=null)
|
||||||
|
return TypecastExpression(expression(0).toAst(), typecast().datatype().toAst(), false, toPosition())
|
||||||
|
|
||||||
|
if(directmemory()!=null)
|
||||||
|
return DirectMemoryRead(directmemory().expression().toAst(), toPosition())
|
||||||
|
|
||||||
|
if(addressof()!=null)
|
||||||
|
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return ArrayIndexedExpression(scoped_identifier().toAst(),
|
||||||
|
arrayindex().toAst(),
|
||||||
|
toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
|
||||||
|
|
||||||
|
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
|
||||||
|
IdentifierReference(listOf(text), toPosition())
|
||||||
|
|
||||||
|
private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference =
|
||||||
|
IdentifierReference(NAME().map { it.text }, toPosition())
|
||||||
|
|
||||||
|
private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble()
|
||||||
|
|
||||||
|
private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
|
||||||
|
"true" -> true
|
||||||
|
"false" -> false
|
||||||
|
else -> throw FatalAstException(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
|
||||||
|
expression().map { it.toAst() }.toTypedArray()
|
||||||
|
|
||||||
|
private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
|
||||||
|
val condition = expression().toAst()
|
||||||
|
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
|
val elseStatements = else_part()?.toAst() ?: mutableListOf()
|
||||||
|
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
|
||||||
|
?: statement().toPosition())
|
||||||
|
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
|
||||||
|
return IfStatement(condition, trueScope, elseScope, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
|
||||||
|
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
|
||||||
|
val branchcondition = branchcondition().toAst()
|
||||||
|
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
|
val elseStatements = else_part()?.toAst() ?: mutableListOf()
|
||||||
|
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
|
||||||
|
?: statement().toPosition())
|
||||||
|
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
|
||||||
|
return BranchStatement(branchcondition, trueScope, elseScope, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
|
||||||
|
|
||||||
|
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
||||||
|
val loopvar = identifier().toAst()
|
||||||
|
val iterable = expression()!!.toAst()
|
||||||
|
val scope =
|
||||||
|
if(statement()!=null)
|
||||||
|
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
|
||||||
|
else
|
||||||
|
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
|
||||||
|
return ForLoop(loopvar, iterable, scope, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
|
||||||
|
|
||||||
|
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
||||||
|
val condition = expression().toAst()
|
||||||
|
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
|
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||||
|
?: statement().toPosition())
|
||||||
|
return WhileLoop(condition, scope, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
|
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||||
|
?: statement().toPosition())
|
||||||
|
return UntilLoop(scope, untilCondition, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
|
||||||
|
val condition = expression().toAst()
|
||||||
|
val choices = this.when_choice()?.map { it.toAst() }?.toMutableList() ?: mutableListOf()
|
||||||
|
return WhenStatement(condition, choices, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
|
||||||
|
val values = expression_list()?.toAst()
|
||||||
|
val stmt = statement()?.toAst()
|
||||||
|
val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
|
||||||
|
if(stmt!=null)
|
||||||
|
stmtBlock.add(stmt)
|
||||||
|
val scope = AnonymousScope(stmtBlock, toPosition())
|
||||||
|
return WhenChoice(values, scope, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.VardeclContext.toAst(): VarDecl {
|
||||||
|
return VarDecl(
|
||||||
|
VarDeclType.VAR,
|
||||||
|
datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
|
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
|
arrayindex()?.toAst(),
|
||||||
|
varname.text,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
ARRAYSIG() != null || arrayindex() != null,
|
||||||
|
false,
|
||||||
|
toPosition()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
|
||||||
|
|
||||||
|
internal fun unescape(str: String, position: Position): String {
|
||||||
|
val result = mutableListOf<Char>()
|
||||||
|
val iter = str.iterator()
|
||||||
|
while(iter.hasNext()) {
|
||||||
|
val c = iter.nextChar()
|
||||||
|
if(c=='\\') {
|
||||||
|
val ec = iter.nextChar()
|
||||||
|
result.add(when(ec) {
|
||||||
|
'\\' -> '\\'
|
||||||
|
'n' -> '\n'
|
||||||
|
'r' -> '\r'
|
||||||
|
'"' -> '"'
|
||||||
|
'u' -> {
|
||||||
|
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
|
||||||
|
}
|
||||||
|
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
result.add(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.joinToString("")
|
||||||
|
}
|
172
compiler/src/prog8/ast/base/Base.kt
Normal file
172
compiler/src/prog8/ast/base/Base.kt
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package prog8.ast.base
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.compiler.target.CompilationTarget
|
||||||
|
|
||||||
|
|
||||||
|
/**************************** AST Data classes ****************************/
|
||||||
|
|
||||||
|
enum class DataType {
|
||||||
|
UBYTE, // pass by value
|
||||||
|
BYTE, // pass by value
|
||||||
|
UWORD, // pass by value
|
||||||
|
WORD, // pass by value
|
||||||
|
FLOAT, // pass by value
|
||||||
|
STR, // pass by reference
|
||||||
|
ARRAY_UB, // pass by reference
|
||||||
|
ARRAY_B, // pass by reference
|
||||||
|
ARRAY_UW, // pass by reference
|
||||||
|
ARRAY_W, // pass by reference
|
||||||
|
ARRAY_F, // pass by reference
|
||||||
|
STRUCT; // pass by reference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
|
||||||
|
*/
|
||||||
|
infix fun isAssignableTo(targetType: DataType) =
|
||||||
|
when(this) {
|
||||||
|
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
||||||
|
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
||||||
|
UWORD -> targetType in setOf(UWORD, FLOAT)
|
||||||
|
WORD -> targetType in setOf(WORD, FLOAT)
|
||||||
|
FLOAT -> targetType == FLOAT
|
||||||
|
STR -> targetType == STR
|
||||||
|
in ArrayDatatypes -> targetType == this
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
|
||||||
|
|
||||||
|
infix fun largerThan(other: DataType) =
|
||||||
|
when(this) {
|
||||||
|
in ByteDatatypes -> false
|
||||||
|
in WordDatatypes -> other in ByteDatatypes
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
|
||||||
|
infix fun equalsSize(other: DataType) =
|
||||||
|
when(this) {
|
||||||
|
in ByteDatatypes -> other in ByteDatatypes
|
||||||
|
in WordDatatypes -> other in WordDatatypes
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun memorySize(): Int {
|
||||||
|
return when(this) {
|
||||||
|
in ByteDatatypes -> 1
|
||||||
|
in WordDatatypes -> 2
|
||||||
|
FLOAT -> CompilationTarget.machine.FLOAT_MEM_SIZE
|
||||||
|
in PassByReferenceDatatypes -> CompilationTarget.machine.POINTER_MEM_SIZE
|
||||||
|
else -> -9999999
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class CpuRegister {
|
||||||
|
A,
|
||||||
|
X,
|
||||||
|
Y
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class RegisterOrPair {
|
||||||
|
A,
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
AX,
|
||||||
|
AY,
|
||||||
|
XY;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val names by lazy { values().map { it.toString()} }
|
||||||
|
}
|
||||||
|
|
||||||
|
} // only used in parameter and return value specs in asm subroutines
|
||||||
|
|
||||||
|
enum class Statusflag {
|
||||||
|
Pc,
|
||||||
|
Pz,
|
||||||
|
Pv,
|
||||||
|
Pn;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val names by lazy { values().map { it.toString()} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class BranchCondition {
|
||||||
|
CS,
|
||||||
|
CC,
|
||||||
|
EQ,
|
||||||
|
Z,
|
||||||
|
NE,
|
||||||
|
NZ,
|
||||||
|
VS,
|
||||||
|
VC,
|
||||||
|
MI,
|
||||||
|
NEG,
|
||||||
|
PL,
|
||||||
|
POS
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class VarDeclType {
|
||||||
|
VAR,
|
||||||
|
CONST,
|
||||||
|
MEMORY
|
||||||
|
}
|
||||||
|
|
||||||
|
val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
|
||||||
|
val WordDatatypes = setOf(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 ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
|
||||||
|
val IterableDatatypes = setOf(
|
||||||
|
DataType.STR,
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B,
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W,
|
||||||
|
DataType.ARRAY_F)
|
||||||
|
val PassByValueDatatypes = NumericDatatypes
|
||||||
|
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
|
||||||
|
val ArrayElementTypes = mapOf(
|
||||||
|
DataType.STR to DataType.UBYTE,
|
||||||
|
DataType.ARRAY_B to DataType.BYTE,
|
||||||
|
DataType.ARRAY_UB to DataType.UBYTE,
|
||||||
|
DataType.ARRAY_W to DataType.WORD,
|
||||||
|
DataType.ARRAY_UW to DataType.UWORD,
|
||||||
|
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
|
||||||
|
// (useful to figure out in what namespace/block something is defined, etc)
|
||||||
|
inline fun <reified T> findParentNode(node: Node): T? {
|
||||||
|
var candidate = node.parent
|
||||||
|
while(candidate !is T && candidate !is ParentSentinel)
|
||||||
|
candidate = candidate.parent
|
||||||
|
return if(candidate is ParentSentinel)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
candidate as T
|
||||||
|
}
|
||||||
|
|
||||||
|
object ParentSentinel : Node {
|
||||||
|
override val position = Position("<<sentinel>>", 0, 0, 0)
|
||||||
|
override var parent: Node = this
|
||||||
|
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) {
|
||||||
|
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DUMMY = Position("<dummy>", 0, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
44
compiler/src/prog8/ast/base/ErrorReporting.kt
Normal file
44
compiler/src/prog8/ast/base/ErrorReporting.kt
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package prog8.ast.base
|
||||||
|
|
||||||
|
import prog8.parser.ParsingFailedError
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorReporter {
|
||||||
|
private enum class MessageSeverity {
|
||||||
|
WARNING,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position)
|
||||||
|
|
||||||
|
private val messages = mutableListOf<CompilerMessage>()
|
||||||
|
private val alreadyReportedMessages = mutableSetOf<String>()
|
||||||
|
|
||||||
|
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
|
||||||
|
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
|
||||||
|
|
||||||
|
fun handle() {
|
||||||
|
var numErrors = 0
|
||||||
|
var numWarnings = 0
|
||||||
|
messages.forEach {
|
||||||
|
when(it.severity) {
|
||||||
|
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
|
||||||
|
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
|
||||||
|
}
|
||||||
|
val msg = "${it.position} ${it.severity} ${it.message}".trim()
|
||||||
|
if(msg !in alreadyReportedMessages) {
|
||||||
|
System.err.println(msg)
|
||||||
|
alreadyReportedMessages.add(msg)
|
||||||
|
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()
|
||||||
|
}
|
18
compiler/src/prog8/ast/base/Errors.kt
Normal file
18
compiler/src/prog8/ast/base/Errors.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package prog8.ast.base
|
||||||
|
|
||||||
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
|
||||||
|
open class FatalAstException (override var message: String) : Exception(message)
|
||||||
|
|
||||||
|
open class AstException (override var message: String) : Exception(message)
|
||||||
|
|
||||||
|
open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
|
||||||
|
override fun toString() = "$position Syntax error: $message"
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpressionError(message: String, val position: Position) : AstException(message) {
|
||||||
|
override fun toString() = "$position Error: $message"
|
||||||
|
}
|
||||||
|
|
||||||
|
class UndefinedSymbolError(symbol: IdentifierReference)
|
||||||
|
: SyntaxError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
70
compiler/src/prog8/ast/base/Extensions.kt
Normal file
70
compiler/src/prog8/ast/base/Extensions.kt
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package prog8.ast.base
|
||||||
|
|
||||||
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.processing.*
|
||||||
|
import prog8.compiler.CompilationOptions
|
||||||
|
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||||
|
|
||||||
|
|
||||||
|
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
|
||||||
|
val checker = AstChecker(this, compilerOptions, errors)
|
||||||
|
checker.visit(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
|
||||||
|
val fixer = BeforeAsmGenerationAstChanger(this, errors)
|
||||||
|
fixer.visit(this)
|
||||||
|
fixer.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.reorderStatements() {
|
||||||
|
val reorder = StatementReorderer(this)
|
||||||
|
reorder.visit(this)
|
||||||
|
reorder.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||||
|
val caster = TypecastsAdder(this, errors)
|
||||||
|
caster.visit(this)
|
||||||
|
caster.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.verifyFunctionArgTypes() {
|
||||||
|
val fixer = VerifyFunctionArgTypes(this)
|
||||||
|
fixer.visit(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Module.checkImportedValid() {
|
||||||
|
val imr = ImportedModuleDirectiveRemover()
|
||||||
|
imr.visit(this, this.parent)
|
||||||
|
imr.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.checkRecursion(errors: ErrorReporter) {
|
||||||
|
val checker = AstRecursionChecker(namespace, errors)
|
||||||
|
checker.visit(this)
|
||||||
|
checker.processMessages(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||||
|
|
||||||
|
val checker2 = AstIdentifiersChecker(this, errors)
|
||||||
|
checker2.visit(this)
|
||||||
|
|
||||||
|
if(errors.isEmpty()) {
|
||||||
|
val transforms = AstVariousTransforms(this)
|
||||||
|
transforms.visit(this)
|
||||||
|
transforms.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
853
compiler/src/prog8/ast/expressions/AstExpressions.kt
Normal file
853
compiler/src/prog8/ast/expressions/AstExpressions.kt
Normal file
@ -0,0 +1,853 @@
|
|||||||
|
package prog8.ast.expressions
|
||||||
|
|
||||||
|
import prog8.ast.*
|
||||||
|
import prog8.ast.antlr.escape
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstVisitor
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.target.CompilationTarget
|
||||||
|
import prog8.functions.BuiltinFunctions
|
||||||
|
import prog8.functions.CannotEvaluateException
|
||||||
|
import prog8.functions.NotConstArgumentException
|
||||||
|
import prog8.functions.builtinFunctionReturnType
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
|
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||||
|
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||||
|
|
||||||
|
|
||||||
|
sealed class Expression: Node {
|
||||||
|
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||||
|
abstract fun accept(visitor: IAstVisitor)
|
||||||
|
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||||
|
abstract fun referencesIdentifiers(vararg name: String): Boolean
|
||||||
|
abstract fun inferType(program: Program): InferredTypes.InferredType
|
||||||
|
|
||||||
|
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
|
||||||
|
|
||||||
|
infix fun isSameAs(other: Expression): Boolean {
|
||||||
|
if(this===other)
|
||||||
|
return true
|
||||||
|
return when(this) {
|
||||||
|
is IdentifierReference ->
|
||||||
|
(other is IdentifierReference && other.nameInSource==nameInSource)
|
||||||
|
is PrefixExpression ->
|
||||||
|
(other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
||||||
|
is BinaryExpression ->
|
||||||
|
(other is BinaryExpression && other.operator==operator
|
||||||
|
&& other.left isSameAs left
|
||||||
|
&& other.right isSameAs right)
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
(other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
||||||
|
&& other.arrayspec.index isSameAs arrayspec.index)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PrefixExpression(val operator: String, var expression: Expression, override val position: Position) : Expression() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
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 accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
||||||
|
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 {
|
||||||
|
return "Prefix($operator $expression)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BinaryExpression(var left: Expression, var operator: String, var right: Expression, override val position: Position) : Expression() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
left.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 {
|
||||||
|
return "[$left $operator $right]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
|
||||||
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
|
val leftDt = left.inferType(program)
|
||||||
|
val rightDt = right.inferType(program)
|
||||||
|
return when (operator) {
|
||||||
|
"+", "-", "*", "**", "%", "/" -> {
|
||||||
|
if (!leftDt.isKnown || !rightDt.isKnown)
|
||||||
|
InferredTypes.unknown()
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
InferredTypes.knownFor(commonDatatype(
|
||||||
|
leftDt.typeOrElse(DataType.BYTE),
|
||||||
|
rightDt.typeOrElse(DataType.BYTE),
|
||||||
|
null, null).first)
|
||||||
|
} catch (x: FatalAstException) {
|
||||||
|
InferredTypes.unknown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"&" -> leftDt
|
||||||
|
"|" -> leftDt
|
||||||
|
"^" -> leftDt
|
||||||
|
"and", "or", "xor",
|
||||||
|
"<", ">",
|
||||||
|
"<=", ">=",
|
||||||
|
"==", "!=" -> InferredTypes.knownFor(DataType.UBYTE)
|
||||||
|
"<<", ">>" -> leftDt
|
||||||
|
else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun commonDatatype(leftDt: DataType, rightDt: DataType,
|
||||||
|
left: Expression?, right: Expression?): Pair<DataType, Expression?> {
|
||||||
|
// byte + byte -> byte
|
||||||
|
// byte + word -> word
|
||||||
|
// word + byte -> word
|
||||||
|
// word + word -> word
|
||||||
|
// a combination with a float will be float (but give a warning about this!)
|
||||||
|
|
||||||
|
return when (leftDt) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when (rightDt) {
|
||||||
|
DataType.UBYTE -> Pair(DataType.UBYTE, null)
|
||||||
|
DataType.BYTE -> Pair(DataType.BYTE, left)
|
||||||
|
DataType.UWORD -> Pair(DataType.UWORD, left)
|
||||||
|
DataType.WORD -> Pair(DataType.WORD, left)
|
||||||
|
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||||
|
else -> Pair(leftDt, null) // non-numeric datatype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
when (rightDt) {
|
||||||
|
DataType.UBYTE -> Pair(DataType.BYTE, right)
|
||||||
|
DataType.BYTE -> Pair(DataType.BYTE, null)
|
||||||
|
DataType.UWORD -> Pair(DataType.WORD, left)
|
||||||
|
DataType.WORD -> Pair(DataType.WORD, left)
|
||||||
|
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||||
|
else -> Pair(leftDt, null) // non-numeric datatype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when (rightDt) {
|
||||||
|
DataType.UBYTE -> Pair(DataType.UWORD, right)
|
||||||
|
DataType.BYTE -> Pair(DataType.WORD, right)
|
||||||
|
DataType.UWORD -> Pair(DataType.UWORD, null)
|
||||||
|
DataType.WORD -> Pair(DataType.WORD, left)
|
||||||
|
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||||
|
else -> Pair(leftDt, null) // non-numeric datatype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
when (rightDt) {
|
||||||
|
DataType.UBYTE -> Pair(DataType.WORD, right)
|
||||||
|
DataType.BYTE -> Pair(DataType.WORD, right)
|
||||||
|
DataType.UWORD -> Pair(DataType.WORD, right)
|
||||||
|
DataType.WORD -> Pair(DataType.WORD, null)
|
||||||
|
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||||
|
else -> Pair(leftDt, null) // non-numeric datatype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
Pair(DataType.FLOAT, right)
|
||||||
|
}
|
||||||
|
else -> Pair(leftDt, null) // non-numeric datatype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||||
|
val arrayspec: ArrayIndex,
|
||||||
|
override val position: Position) : Expression(), IAssignable {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
identifier.linkParents(this)
|
||||||
|
arrayspec.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===identifier -> identifier = replacement as IdentifierReference
|
||||||
|
node===arrayspec.index -> arrayspec.index = replacement as Expression
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
|
||||||
|
|
||||||
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
|
val target = identifier.targetStatement(program.namespace)
|
||||||
|
if (target is VarDecl) {
|
||||||
|
return when (target.datatype) {
|
||||||
|
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
||||||
|
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(target.datatype))
|
||||||
|
else -> InferredTypes.unknown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return InferredTypes.unknown()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypecastExpression(var expression: Expression, var type: DataType, val implicit: Boolean, override val position: Position) : Expression() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
expression.linkParents(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: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
||||||
|
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
||||||
|
override fun constValue(program: Program): NumericLiteralValue? {
|
||||||
|
val cv = expression.constValue(program) ?: return null
|
||||||
|
val cast = cv.cast(type)
|
||||||
|
return if(cast.isValid)
|
||||||
|
cast.valueOrZero()
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Typecast($expression as $type)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AddressOf(var identifier: IdentifierReference, override val position: Position) : Expression() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
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 referencesIdentifiers(vararg name: String) = false
|
||||||
|
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
|
||||||
|
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(), IAssignable {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
this.addressExpression.linkParents(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: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifiers(vararg name: String) = false
|
||||||
|
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
||||||
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "DirectMemoryRead($addressExpression)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||||
|
val number: Number, // can be byte, word or float depending on the type
|
||||||
|
override val position: Position) : Expression() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromBoolean(bool: Boolean, position: Position) =
|
||||||
|
NumericLiteralValue(DataType.UBYTE, if (bool) 1 else 0, position)
|
||||||
|
|
||||||
|
fun optimalNumeric(value: Number, position: Position): NumericLiteralValue {
|
||||||
|
return if(value is Double) {
|
||||||
|
NumericLiteralValue(DataType.FLOAT, value, position)
|
||||||
|
} else {
|
||||||
|
when (val intval = value.toInt()) {
|
||||||
|
in 0..255 -> NumericLiteralValue(DataType.UBYTE, intval, position)
|
||||||
|
in -128..127 -> NumericLiteralValue(DataType.BYTE, intval, position)
|
||||||
|
in 0..65535 -> NumericLiteralValue(DataType.UWORD, intval, position)
|
||||||
|
in -32768..32767 -> NumericLiteralValue(DataType.WORD, intval, position)
|
||||||
|
else -> NumericLiteralValue(DataType.FLOAT, intval.toDouble(), position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun optimalInteger(value: Int, position: Position): NumericLiteralValue {
|
||||||
|
return when (value) {
|
||||||
|
in 0..255 -> NumericLiteralValue(DataType.UBYTE, value, position)
|
||||||
|
in -128..127 -> NumericLiteralValue(DataType.BYTE, value, position)
|
||||||
|
in 0..65535 -> NumericLiteralValue(DataType.UWORD, value, position)
|
||||||
|
in -32768..32767 -> NumericLiteralValue(DataType.WORD, value, position)
|
||||||
|
else -> throw FatalAstException("integer overflow: $value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val asBooleanValue: Boolean = number.toDouble() != 0.0
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("can't replace here")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun referencesIdentifiers(vararg name: String) = false
|
||||||
|
override fun constValue(program: Program) = 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 inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
||||||
|
|
||||||
|
override fun hashCode(): Int = Objects.hash(type, number)
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if(other==null || other !is NumericLiteralValue)
|
||||||
|
return false
|
||||||
|
return number.toDouble()==other.number.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
|
||||||
|
|
||||||
|
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)
|
||||||
|
return CastValue(true, this)
|
||||||
|
val numval = number.toDouble()
|
||||||
|
when(type) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
if(targettype== DataType.BYTE && numval <= 127)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
|
if(targettype== DataType.WORD || targettype== DataType.UWORD)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
|
if(targettype== DataType.FLOAT)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
if(targettype== DataType.UBYTE && numval >= 0)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
|
if(targettype== DataType.UWORD && numval >= 0)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
|
if(targettype== DataType.WORD)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
|
if(targettype== DataType.FLOAT)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
if(targettype== DataType.BYTE && numval <= 127)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
|
if(targettype== DataType.UBYTE && numval <= 255)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
|
if(targettype== DataType.WORD && numval <= 32767)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
|
if(targettype== DataType.FLOAT)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
|
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
|
if(targettype== DataType.UWORD && numval >=0)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
|
if(targettype== DataType.FLOAT)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
|
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
|
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
|
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
|
||||||
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
return CastValue(false, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var heapIdSequence = 0 // unique ids for strings and arrays "on the heap"
|
||||||
|
|
||||||
|
class StringLiteralValue(val value: String,
|
||||||
|
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
|
||||||
|
override val position: Position) : Expression() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
val heapId = ++heapIdSequence
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("can't replace here")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun referencesIdentifiers(vararg name: String) = false
|
||||||
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
|
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 inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STR)
|
||||||
|
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
|
||||||
|
override fun hashCode(): Int = Objects.hash(value, altEncoding)
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if(other==null || other !is StringLiteralValue)
|
||||||
|
return false
|
||||||
|
return value==other.value && altEncoding == other.altEncoding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred because not all array literals hava a known type yet
|
||||||
|
val value: Array<Expression>,
|
||||||
|
override val position: Position) : Expression() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
val heapId = ++heapIdSequence
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
value.forEach {it.linkParents(this)}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
|
||||||
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
|
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 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)
|
||||||
|
override fun hashCode(): Int = Objects.hash(value, type)
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if(other==null || other !is ArrayLiteralValue)
|
||||||
|
return false
|
||||||
|
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.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)
|
||||||
|
else -> InferredTypes.InferredType.unknown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cast(targettype: DataType): ArrayLiteralValue? {
|
||||||
|
if(type.istype(targettype))
|
||||||
|
return this
|
||||||
|
if(targettype in ArrayDatatypes) {
|
||||||
|
val elementType = ArrayElementTypes.getValue(targettype)
|
||||||
|
val castArray = value.map{
|
||||||
|
val num = it as? NumericLiteralValue
|
||||||
|
if(num==null) {
|
||||||
|
// an array of UWORDs could possibly also contain AddressOfs, other stuff can't be casted
|
||||||
|
if (elementType != DataType.UWORD || it !is AddressOf)
|
||||||
|
return null // can't cast a value of the array, abort
|
||||||
|
it
|
||||||
|
} else {
|
||||||
|
val cast = num.cast(elementType)
|
||||||
|
if(cast.isValid)
|
||||||
|
cast.valueOrZero()
|
||||||
|
else
|
||||||
|
return null // can't cast a value of the array, abort
|
||||||
|
}
|
||||||
|
}.toTypedArray()
|
||||||
|
return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position)
|
||||||
|
}
|
||||||
|
return null // invalid type conversion from $this to $targettype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RangeExpr(var from: Expression,
|
||||||
|
var to: Expression,
|
||||||
|
var step: Expression,
|
||||||
|
override val position: Position) : Expression() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
from.linkParents(this)
|
||||||
|
to.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 accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
|
||||||
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
|
val fromDt=from.inferType(program)
|
||||||
|
val toDt=to.inferType(program)
|
||||||
|
return when {
|
||||||
|
!fromDt.isKnown || !toDt.isKnown -> InferredTypes.unknown()
|
||||||
|
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.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR)
|
||||||
|
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)
|
||||||
|
else -> InferredTypes.knownFor(DataType.ARRAY_UB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun toString(): String {
|
||||||
|
return "RangeExpr(from $from, to $to, step $step, pos=$position)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun size(): Int? {
|
||||||
|
val fromLv = (from as? NumericLiteralValue)
|
||||||
|
val toLv = (to as? NumericLiteralValue)
|
||||||
|
if(fromLv==null || toLv==null)
|
||||||
|
return null
|
||||||
|
return toConstantIntegerRange()?.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toConstantIntegerRange(): IntProgression? {
|
||||||
|
val fromVal: Int
|
||||||
|
val toVal: Int
|
||||||
|
val fromString = from as? StringLiteralValue
|
||||||
|
val toString = to as? StringLiteralValue
|
||||||
|
if(fromString!=null && toString!=null ) {
|
||||||
|
// string range -> int range over character values
|
||||||
|
fromVal = CompilationTarget.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
|
||||||
|
toVal = CompilationTarget.encodeString(toString.value, fromString.altEncoding)[0].toInt()
|
||||||
|
} else {
|
||||||
|
val fromLv = from as? NumericLiteralValue
|
||||||
|
val toLv = to as? NumericLiteralValue
|
||||||
|
if(fromLv==null || toLv==null)
|
||||||
|
return null // non-constant range
|
||||||
|
// integer range
|
||||||
|
fromVal = fromLv.number.toInt()
|
||||||
|
toVal = toLv.number.toInt()
|
||||||
|
}
|
||||||
|
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
|
||||||
|
return makeRange(fromVal, toVal, stepVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||||
|
return when {
|
||||||
|
fromVal <= toVal -> when {
|
||||||
|
stepVal <= 0 -> IntRange.EMPTY
|
||||||
|
stepVal == 1 -> fromVal..toVal
|
||||||
|
else -> fromVal..toVal step stepVal
|
||||||
|
}
|
||||||
|
else -> when {
|
||||||
|
stepVal >= 0 -> IntRange.EMPTY
|
||||||
|
stepVal == -1 -> fromVal downTo toVal
|
||||||
|
else -> fromVal downTo toVal step abs(stepVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
fun targetStatement(namespace: INameScope) =
|
||||||
|
if(nameInSource.size==1 && nameInSource[0] in BuiltinFunctions)
|
||||||
|
BuiltinFunctionStatementPlaceholder(nameInSource[0], position)
|
||||||
|
else
|
||||||
|
namespace.lookup(nameInSource, this)
|
||||||
|
|
||||||
|
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
|
||||||
|
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) {
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("can't replace here")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun constValue(program: Program): NumericLiteralValue? {
|
||||||
|
val node = program.namespace.lookup(nameInSource, this)
|
||||||
|
?: throw UndefinedSymbolError(this)
|
||||||
|
val vardecl = node as? VarDecl
|
||||||
|
if(vardecl==null) {
|
||||||
|
return null
|
||||||
|
} else if(vardecl.type!= VarDeclType.CONST) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return vardecl.value?.constValue(program)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "IdentifierRef($nameInSource)"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
|
||||||
|
|
||||||
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
|
return when (val targetStmt = targetStatement(program.namespace)) {
|
||||||
|
is VarDecl -> InferredTypes.knownFor(targetStmt.datatype)
|
||||||
|
is StructDecl -> InferredTypes.knownFor(DataType.STRUCT)
|
||||||
|
else -> InferredTypes.InferredType.unknown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun memberOfStruct(namespace: INameScope) = this.targetVarDecl(namespace)?.struct
|
||||||
|
|
||||||
|
fun heapId(namespace: INameScope): Int {
|
||||||
|
val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this)
|
||||||
|
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
|
||||||
|
return when (value) {
|
||||||
|
is IdentifierReference -> value.heapId(namespace)
|
||||||
|
is StringLiteralValue -> value.heapId
|
||||||
|
is ArrayLiteralValue -> value.heapId
|
||||||
|
else -> throw FatalAstException("requires a reference value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FunctionCall(override var target: IdentifierReference,
|
||||||
|
override var args: MutableList<Expression>,
|
||||||
|
override val position: Position) : Expression(), IFunctionCall {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
target.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)
|
||||||
|
|
||||||
|
private fun constValue(program: Program, withDatatypeCheck: Boolean): NumericLiteralValue? {
|
||||||
|
// if the function is a built-in function and the args are consts, should try to const-evaluate!
|
||||||
|
// lenghts of arrays and strings are constants that are determined at compile time!
|
||||||
|
if(target.nameInSource.size>1) return null
|
||||||
|
try {
|
||||||
|
var resultValue: NumericLiteralValue? = null
|
||||||
|
val func = BuiltinFunctions[target.nameInSource[0]]
|
||||||
|
if(func!=null) {
|
||||||
|
val exprfunc = func.constExpressionFunc
|
||||||
|
if(exprfunc!=null)
|
||||||
|
resultValue = exprfunc(args, position, program)
|
||||||
|
else if(func.returntype==null)
|
||||||
|
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(withDatatypeCheck) {
|
||||||
|
val resultDt = this.inferType(program)
|
||||||
|
if(resultValue==null || resultDt istype resultValue.type)
|
||||||
|
return resultValue
|
||||||
|
throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position")
|
||||||
|
} else {
|
||||||
|
return resultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(x: NotConstArgumentException) {
|
||||||
|
// const-evaluating the builtin function call failed.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
catch(x: CannotEvaluateException) {
|
||||||
|
// const-evaluating the builtin function call failed.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "FunctionCall(target=$target, pos=$position)"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || args.any{it.referencesIdentifiers(*name)}
|
||||||
|
|
||||||
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
|
val constVal = constValue(program ,false)
|
||||||
|
if(constVal!=null)
|
||||||
|
return InferredTypes.knownFor(constVal.type)
|
||||||
|
val stmt = target.targetStatement(program.namespace) ?: return InferredTypes.unknown()
|
||||||
|
when (stmt) {
|
||||||
|
is BuiltinFunctionStatementPlaceholder -> {
|
||||||
|
if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" ||
|
||||||
|
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
|
||||||
|
return InferredTypes.void() // these have no return value
|
||||||
|
}
|
||||||
|
return builtinFunctionReturnType(target.nameInSource[0], this.args, program)
|
||||||
|
}
|
||||||
|
is Subroutine -> {
|
||||||
|
if(stmt.returntypes.isEmpty())
|
||||||
|
return InferredTypes.void() // no return value
|
||||||
|
if(stmt.returntypes.size==1)
|
||||||
|
return InferredTypes.knownFor(stmt.returntypes[0])
|
||||||
|
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
|
||||||
|
}
|
||||||
|
else -> return InferredTypes.unknown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
compiler/src/prog8/ast/expressions/InferredTypes.kt
Normal file
60
compiler/src/prog8/ast/expressions/InferredTypes.kt
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package prog8.ast.expressions
|
||||||
|
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
object InferredTypes {
|
||||||
|
class InferredType private constructor(val isUnknown: Boolean, val isVoid: Boolean, private var datatype: DataType?) {
|
||||||
|
init {
|
||||||
|
require(!(datatype!=null && (isUnknown || isVoid))) { "invalid combination of args" }
|
||||||
|
}
|
||||||
|
|
||||||
|
val isKnown = datatype!=null
|
||||||
|
fun typeOrElse(alternative: DataType) = if(isUnknown || isVoid) alternative else datatype!!
|
||||||
|
infix fun istype(type: DataType): Boolean = if(isUnknown || isVoid) false else this.datatype==type
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun unknown() = InferredType(isUnknown = true, isVoid = false, datatype = null)
|
||||||
|
fun void() = InferredType(isUnknown = false, isVoid = true, datatype = null)
|
||||||
|
fun known(type: DataType) = InferredType(isUnknown = false, isVoid = false, datatype = type)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if(other !is InferredType)
|
||||||
|
return false
|
||||||
|
return isVoid==other.isVoid && datatype==other.datatype
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return when {
|
||||||
|
datatype!=null -> datatype.toString()
|
||||||
|
isVoid -> "<void>"
|
||||||
|
else -> "<unknown>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = Objects.hash(isVoid, datatype)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val unknownInstance = InferredType.unknown()
|
||||||
|
private val voidInstance = InferredType.void()
|
||||||
|
private val knownInstances = mapOf(
|
||||||
|
DataType.UBYTE to InferredType.known(DataType.UBYTE),
|
||||||
|
DataType.BYTE to InferredType.known(DataType.BYTE),
|
||||||
|
DataType.UWORD to InferredType.known(DataType.UWORD),
|
||||||
|
DataType.WORD to InferredType.known(DataType.WORD),
|
||||||
|
DataType.FLOAT to InferredType.known(DataType.FLOAT),
|
||||||
|
DataType.STR to InferredType.known(DataType.STR),
|
||||||
|
DataType.ARRAY_UB to InferredType.known(DataType.ARRAY_UB),
|
||||||
|
DataType.ARRAY_B to InferredType.known(DataType.ARRAY_B),
|
||||||
|
DataType.ARRAY_UW to InferredType.known(DataType.ARRAY_UW),
|
||||||
|
DataType.ARRAY_W to InferredType.known(DataType.ARRAY_W),
|
||||||
|
DataType.ARRAY_F to InferredType.known(DataType.ARRAY_F),
|
||||||
|
DataType.STRUCT to InferredType.known(DataType.STRUCT)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun void() = voidInstance
|
||||||
|
fun unknown() = unknownInstance
|
||||||
|
fun knownFor(type: DataType) = knownInstances.getValue(type)
|
||||||
|
}
|
1295
compiler/src/prog8/ast/processing/AstChecker.kt
Normal file
1295
compiler/src/prog8/ast/processing/AstChecker.kt
Normal file
File diff suppressed because it is too large
Load Diff
159
compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt
Normal file
159
compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.target.CompilationTarget
|
||||||
|
import prog8.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
|
||||||
|
private var blocks = mutableMapOf<String, Block>()
|
||||||
|
|
||||||
|
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||||
|
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(module: Module) {
|
||||||
|
blocks.clear() // blocks may be redefined within a different module
|
||||||
|
|
||||||
|
super.visit(module)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(block: Block) {
|
||||||
|
if(block.name in CompilationTarget.machine.opcodeNames)
|
||||||
|
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
|
||||||
|
|
||||||
|
val existing = blocks[block.name]
|
||||||
|
if(existing!=null)
|
||||||
|
nameError(block.name, block.position, existing)
|
||||||
|
else
|
||||||
|
blocks[block.name] = block
|
||||||
|
|
||||||
|
super.visit(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(decl: VarDecl) {
|
||||||
|
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||||
|
|
||||||
|
if(decl.name in BuiltinFunctions)
|
||||||
|
errors.err("builtin function cannot be redefined", decl.position)
|
||||||
|
|
||||||
|
if(decl.name in CompilationTarget.machine.opcodeNames)
|
||||||
|
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
||||||
|
|
||||||
|
if(decl.datatype==DataType.STRUCT) {
|
||||||
|
if (decl.structHasBeenFlattened)
|
||||||
|
return super.visit(decl) // don't do this multiple times
|
||||||
|
|
||||||
|
if (decl.struct == null) {
|
||||||
|
errors.err("undefined struct type", decl.position)
|
||||||
|
return super.visit(decl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes })
|
||||||
|
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
|
||||||
|
|
||||||
|
if (decl.value is NumericLiteralValue) {
|
||||||
|
errors.err("you cannot initialize a struct using a single value", decl.position)
|
||||||
|
return super.visit(decl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decl.value != null && decl.value !is ArrayLiteralValue) {
|
||||||
|
errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
|
||||||
|
return super.visit(decl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val existing = program.namespace.lookup(listOf(decl.name), decl)
|
||||||
|
if (existing != null && existing !== decl)
|
||||||
|
nameError(decl.name, decl.position, existing)
|
||||||
|
|
||||||
|
super.visit(decl)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(subroutine: Subroutine) {
|
||||||
|
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
|
||||||
|
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
||||||
|
} else if(subroutine.name in BuiltinFunctions) {
|
||||||
|
// the builtin functions can't be redefined
|
||||||
|
errors.err("builtin function cannot be redefined", subroutine.position)
|
||||||
|
} else {
|
||||||
|
// already reported elsewhere:
|
||||||
|
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
||||||
|
// checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
||||||
|
|
||||||
|
val existing = program.namespace.lookup(listOf(subroutine.name), subroutine)
|
||||||
|
if (existing != null && existing !== subroutine)
|
||||||
|
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
|
||||||
|
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||||
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||||
|
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
||||||
|
val paramsToCheck = paramNames.intersect(namesInSub)
|
||||||
|
for(name in paramsToCheck) {
|
||||||
|
val labelOrVar = subroutine.getLabelOrVariable(name)
|
||||||
|
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
|
||||||
|
nameError(name, labelOrVar.position, subroutine)
|
||||||
|
val sub = subroutine.statements.singleOrNull { it is Subroutine && it.name==name}
|
||||||
|
if(sub!=null)
|
||||||
|
nameError(name, sub.position, subroutine)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||||
|
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.visit(subroutine)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(label: Label) {
|
||||||
|
if(label.name in CompilationTarget.machine.opcodeNames)
|
||||||
|
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
||||||
|
|
||||||
|
if(label.name in BuiltinFunctions) {
|
||||||
|
// the builtin functions can't be redefined
|
||||||
|
errors.err("builtin function cannot be redefined", label.position)
|
||||||
|
} else {
|
||||||
|
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
|
||||||
|
for(el in existing) {
|
||||||
|
if(el === label || el.name != label.name)
|
||||||
|
continue
|
||||||
|
else {
|
||||||
|
nameError(label.name, label.position, el)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.visit(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(string: StringLiteralValue) {
|
||||||
|
if (string.value.length !in 1..255)
|
||||||
|
errors.err("string literal length must be between 1 and 255", string.position)
|
||||||
|
|
||||||
|
super.visit(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(structDecl: StructDecl) {
|
||||||
|
for(member in structDecl.statements){
|
||||||
|
val decl = member as? VarDecl
|
||||||
|
if(decl!=null && decl.datatype !in NumericDatatypes)
|
||||||
|
errors.err("structs can only contain numerical types", decl.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.visit(structDecl)
|
||||||
|
}
|
||||||
|
}
|
118
compiler/src/prog8/ast/processing/AstRecursionChecker.kt
Normal file
118
compiler/src/prog8/ast/processing/AstRecursionChecker.kt
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.base.ErrorReporter
|
||||||
|
import prog8.ast.base.Position
|
||||||
|
import prog8.ast.expressions.FunctionCall
|
||||||
|
import prog8.ast.statements.FunctionCallStatement
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
|
|
||||||
|
|
||||||
|
internal class AstRecursionChecker(private val namespace: INameScope,
|
||||||
|
private val errors: ErrorReporter) : IAstVisitor {
|
||||||
|
private val callGraph = DirectedGraph<INameScope>()
|
||||||
|
|
||||||
|
fun processMessages(modulename: String) {
|
||||||
|
val cycle = callGraph.checkForCycle()
|
||||||
|
if(cycle.isEmpty())
|
||||||
|
return
|
||||||
|
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
||||||
|
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", Position.DUMMY)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
161
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
161
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
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 after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(functionCallStatement.target.nameInSource == listOf("swap")) {
|
||||||
|
// if x and y are both just identifiers, do not rewrite (there should be asm generation for that)
|
||||||
|
// otherwise:
|
||||||
|
// rewrite swap(x,y) as follows:
|
||||||
|
// - declare a temp variable of the same datatype
|
||||||
|
// - temp = x, x = y, y= temp
|
||||||
|
val first = functionCallStatement.args[0]
|
||||||
|
val second = functionCallStatement.args[1]
|
||||||
|
if(first !is IdentifierReference && second !is IdentifierReference) {
|
||||||
|
val dt = first.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
val tempname = "prog8_swaptmp_${functionCallStatement.hashCode()}"
|
||||||
|
val tempvardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, tempname, null, null, isArray = false, autogeneratedDontRemove = true, position = first.position)
|
||||||
|
val tempvar = IdentifierReference(listOf(tempname), first.position)
|
||||||
|
val assignTemp = Assignment(
|
||||||
|
AssignTarget(tempvar, null, null, first.position),
|
||||||
|
first,
|
||||||
|
first.position
|
||||||
|
)
|
||||||
|
val assignFirst = Assignment(
|
||||||
|
AssignTarget.fromExpr(first),
|
||||||
|
second,
|
||||||
|
first.position
|
||||||
|
)
|
||||||
|
val assignSecond = Assignment(
|
||||||
|
AssignTarget.fromExpr(second),
|
||||||
|
tempvar,
|
||||||
|
first.position
|
||||||
|
)
|
||||||
|
val scope = AnonymousScope(mutableListOf(tempvardecl, assignTemp, assignFirst, assignSecond), first.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(functionCallStatement, scope, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
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 before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
when {
|
||||||
|
expr.left is StringLiteralValue ->
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
expr.right is StringLiteralValue ->
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(string.parent !is VarDecl) {
|
||||||
|
// 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() as Node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
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 && litval2!=array) {
|
||||||
|
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() as Node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(expr.operator == "+" && operand is StringLiteralValue) {
|
||||||
|
// concatenate two strings
|
||||||
|
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
|
||||||
|
}
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
}
|
442
compiler/src/prog8/ast/processing/AstWalker.kt
Normal file
442
compiler/src/prog8/ast/processing/AstWalker.kt
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
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: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
if(parent is INameScope) {
|
||||||
|
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
|
||||||
|
throw FatalAstException("attempt to remove non-existing node $node")
|
||||||
|
} else {
|
||||||
|
throw FatalAstException("parent of a remove modification is not an INameScope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SetExpression(val setter: (newExpr: Expression) -> Unit, val newExpr: Expression, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
setter(newExpr)
|
||||||
|
newExpr.linkParents(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertFirst(val stmt: Statement, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
if(parent is INameScope) {
|
||||||
|
parent.statements.add(0, stmt)
|
||||||
|
stmt.linkParents(parent)
|
||||||
|
} else {
|
||||||
|
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
if(parent is INameScope) {
|
||||||
|
parent.statements.add(stmt)
|
||||||
|
stmt.linkParents(parent)
|
||||||
|
} else {
|
||||||
|
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
if(parent is INameScope) {
|
||||||
|
val idx = parent.statements.indexOfFirst { it===after } + 1
|
||||||
|
parent.statements.add(idx, stmt)
|
||||||
|
stmt.linkParents(parent)
|
||||||
|
} else {
|
||||||
|
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertBefore(val before: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
if(parent is INameScope) {
|
||||||
|
val idx = parent.statements.indexOfFirst { it===before }
|
||||||
|
parent.statements.add(idx, stmt)
|
||||||
|
stmt.linkParents(parent)
|
||||||
|
} else {
|
||||||
|
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
parent.replaceChildNode(node, replacement)
|
||||||
|
replacement.linkParents(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwapOperands(val expr: BinaryExpression): IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
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)
|
||||||
|
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.untilCondition.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.identifier.accept(this, arrayIndexedExpression)
|
||||||
|
arrayIndexedExpression.arrayspec.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
179
compiler/src/prog8/ast/processing/IAstVisitor.kt
Normal file
179
compiler/src/prog8/ast/processing/IAstVisitor.kt
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
|
interface IAstVisitor {
|
||||||
|
fun visit(program: Program) {
|
||||||
|
program.modules.forEach { it.accept(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(module: Module) {
|
||||||
|
module.statements.forEach{ it.accept(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(expr: PrefixExpression) {
|
||||||
|
expr.expression.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(expr: BinaryExpression) {
|
||||||
|
expr.left.accept(this)
|
||||||
|
expr.right.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(directive: Directive) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(block: Block) {
|
||||||
|
block.statements.forEach { it.accept(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(decl: VarDecl) {
|
||||||
|
decl.value?.accept(this)
|
||||||
|
decl.arraysize?.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(subroutine: Subroutine) {
|
||||||
|
subroutine.statements.forEach { it.accept(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(functionCall: FunctionCall) {
|
||||||
|
functionCall.target.accept(this)
|
||||||
|
functionCall.args.forEach { it.accept(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
|
functionCallStatement.target.accept(this)
|
||||||
|
functionCallStatement.args.forEach { it.accept(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(identifier: IdentifierReference) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(jump: Jump) {
|
||||||
|
jump.identifier?.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(ifStatement: IfStatement) {
|
||||||
|
ifStatement.condition.accept(this)
|
||||||
|
ifStatement.truepart.accept(this)
|
||||||
|
ifStatement.elsepart.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(branchStatement: BranchStatement) {
|
||||||
|
branchStatement.truepart.accept(this)
|
||||||
|
branchStatement.elsepart.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(range: RangeExpr) {
|
||||||
|
range.from.accept(this)
|
||||||
|
range.to.accept(this)
|
||||||
|
range.step.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(label: Label) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(numLiteral: NumericLiteralValue) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(string: StringLiteralValue) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(array: ArrayLiteralValue) {
|
||||||
|
array.value.forEach { v->v.accept(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(assignment: Assignment) {
|
||||||
|
assignment.target.accept(this)
|
||||||
|
assignment.value.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(postIncrDecr: PostIncrDecr) {
|
||||||
|
postIncrDecr.target.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(breakStmt: Break) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(forLoop: ForLoop) {
|
||||||
|
forLoop.loopVar.accept(this)
|
||||||
|
forLoop.iterable.accept(this)
|
||||||
|
forLoop.body.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(whileLoop: WhileLoop) {
|
||||||
|
whileLoop.condition.accept(this)
|
||||||
|
whileLoop.body.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(repeatLoop: RepeatLoop) {
|
||||||
|
repeatLoop.iterations?.accept(this)
|
||||||
|
repeatLoop.body.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(untilLoop: UntilLoop) {
|
||||||
|
untilLoop.untilCondition.accept(this)
|
||||||
|
untilLoop.body.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(returnStmt: Return) {
|
||||||
|
returnStmt.value?.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||||
|
arrayIndexedExpression.identifier.accept(this)
|
||||||
|
arrayIndexedExpression.arrayspec.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(assignTarget: AssignTarget) {
|
||||||
|
assignTarget.arrayindexed?.accept(this)
|
||||||
|
assignTarget.identifier?.accept(this)
|
||||||
|
assignTarget.memoryAddress?.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(scope: AnonymousScope) {
|
||||||
|
scope.statements.forEach { it.accept(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(typecast: TypecastExpression) {
|
||||||
|
typecast.expression.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(memread: DirectMemoryRead) {
|
||||||
|
memread.addressExpression.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(memwrite: DirectMemoryWrite) {
|
||||||
|
memwrite.addressExpression.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(addressOf: AddressOf) {
|
||||||
|
addressOf.identifier.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(inlineAssembly: InlineAssembly) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(nopStatement: NopStatement) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(whenStatement: WhenStatement) {
|
||||||
|
whenStatement.condition.accept(this)
|
||||||
|
whenStatement.choices.forEach { it.accept(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(whenChoice: WhenChoice) {
|
||||||
|
whenChoice.values?.forEach { it.accept(this) }
|
||||||
|
whenChoice.statements.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(structDecl: StructDecl) {
|
||||||
|
structDecl.statements.forEach { it.accept(this) }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.statements.Directive
|
||||||
|
|
||||||
|
|
||||||
|
internal class ImportedModuleDirectiveRemover: AstWalker() {
|
||||||
|
/**
|
||||||
|
* Most global directives don't apply for imported modules, so remove them
|
||||||
|
*/
|
||||||
|
|
||||||
|
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(directive.directive in moduleLevelDirectives) {
|
||||||
|
return listOf(IAstModification.Remove(directive, parent))
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
//
|
||||||
|
//}
|
228
compiler/src/prog8/ast/processing/StatementReorderer.kt
Normal file
228
compiler/src/prog8/ast/processing/StatementReorderer.kt
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.*
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
|
|
||||||
|
internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||||
|
// Reorders the statements in a way the compiler needs.
|
||||||
|
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||||
|
// - library blocks are put last.
|
||||||
|
// - blocks are ordered by address, where blocks without address are placed last.
|
||||||
|
// - in every scope, most directives and vardecls are moved to the top.
|
||||||
|
// - the 'start' subroutine is moved to the top.
|
||||||
|
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
||||||
|
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
|
||||||
|
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
|
||||||
|
// - 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")
|
||||||
|
|
||||||
|
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||||
|
val (blocks, other) = module.statements.partition { it is Block }
|
||||||
|
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
|
||||||
|
|
||||||
|
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||||
|
if(mainBlock!=null && mainBlock.address==null) {
|
||||||
|
module.statements.remove(mainBlock)
|
||||||
|
module.statements.add(0, mainBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
reorderVardeclsAndDirectives(module.statements)
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
|
||||||
|
val varDecls = statements.filterIsInstance<VarDecl>()
|
||||||
|
statements.removeAll(varDecls)
|
||||||
|
statements.addAll(0, varDecls)
|
||||||
|
|
||||||
|
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
|
||||||
|
statements.removeAll(directives)
|
||||||
|
statements.addAll(0, directives)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(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
|
||||||
|
decl.value = null
|
||||||
|
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() as Node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
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 before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
val valueType = assignment.value.inferType(program)
|
||||||
|
val targetType = assignment.target.inferType(program, assignment)
|
||||||
|
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
|
||||||
|
val assignments = if (assignment.value is ArrayLiteralValue) {
|
||||||
|
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
|
||||||
|
} else {
|
||||||
|
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
|
||||||
|
}
|
||||||
|
if(assignments.isNotEmpty()) {
|
||||||
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
|
||||||
|
modifications.add(IAstModification.Remove(assignment, parent))
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): 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)
|
||||||
|
throw FatalAstException("element count mismatch")
|
||||||
|
|
||||||
|
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, program: Program): List<Assignment> {
|
||||||
|
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(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
|
||||||
|
assign.linkParents(structAssignment)
|
||||||
|
assign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ArrayLiteralValue -> {
|
||||||
|
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
||||||
|
}
|
||||||
|
else -> throw FatalAstException("strange struct value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
206
compiler/src/prog8/ast/processing/TypecastsAdder.kt
Normal file
206
compiler/src/prog8/ast/processing/TypecastsAdder.kt
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// determine common datatype and add typecast as required to make left and right equal types
|
||||||
|
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr.left, expr.right)
|
||||||
|
if(toFix!=null) {
|
||||||
|
return when {
|
||||||
|
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||||
|
expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr))
|
||||||
|
toFix===expr.right -> listOf(IAstModification.ReplaceNode(
|
||||||
|
expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr))
|
||||||
|
else -> throw FatalAstException("confused binary expression side")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
// see if a typecast is needed to convert the value's type into the proper target type
|
||||||
|
val valueItype = assignment.value.inferType(program)
|
||||||
|
val targetItype = assignment.target.inferType(program, assignment)
|
||||||
|
if(targetItype.isKnown && valueItype.isKnown) {
|
||||||
|
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||||
|
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||||
|
if (valuetype != targettype) {
|
||||||
|
if (valuetype isAssignableTo targettype) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||||
|
return afterFunctionCallArgs(functionCall, functionCall.definingScope())
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
|
||||||
|
when(val sub = call.target.targetStatement(scope)) {
|
||||||
|
is Subroutine -> {
|
||||||
|
for(arg in sub.parameters.zip(call.args.withIndex())) {
|
||||||
|
val argItype = arg.second.value.inferType(program)
|
||||||
|
if(argItype.isKnown) {
|
||||||
|
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||||
|
val requiredType = arg.first.type
|
||||||
|
if (requiredType != argtype) {
|
||||||
|
if (argtype isAssignableTo requiredType) {
|
||||||
|
modifications += IAstModification.ReplaceNode(
|
||||||
|
call.args[arg.second.index],
|
||||||
|
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.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.
|
||||||
|
modifications += IAstModification.ReplaceNode(
|
||||||
|
call.args[arg.second.index],
|
||||||
|
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
|
||||||
|
call as Node)
|
||||||
|
} else if(arg.second.value is NumericLiteralValue) {
|
||||||
|
val cast = (arg.second.value as NumericLiteralValue).cast(requiredType)
|
||||||
|
if(cast.isValid)
|
||||||
|
modifications += IAstModification.ReplaceNode(
|
||||||
|
call.args[arg.second.index],
|
||||||
|
cast.valueOrZero(),
|
||||||
|
call as Node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is BuiltinFunctionStatementPlaceholder -> {
|
||||||
|
val func = BuiltinFunctions.getValue(sub.name)
|
||||||
|
for (arg in func.parameters.zip(call.args.withIndex())) {
|
||||||
|
val argItype = arg.second.value.inferType(program)
|
||||||
|
if (argItype.isKnown) {
|
||||||
|
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||||
|
if (arg.first.possibleDatatypes.any { argtype == it })
|
||||||
|
continue
|
||||||
|
for (possibleType in arg.first.possibleDatatypes) {
|
||||||
|
if (argtype isAssignableTo possibleType) {
|
||||||
|
modifications += IAstModification.ReplaceNode(
|
||||||
|
call.args[arg.second.index],
|
||||||
|
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
|
||||||
|
call as Node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> { }
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
// warn about any implicit type casts to Float, because that may not be intended
|
||||||
|
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||||
|
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||||
|
// make sure the memory address is an uword
|
||||||
|
val dt = memread.addressExpression.inferType(program)
|
||||||
|
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||||
|
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||||
|
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
||||||
|
// make sure the memory address is an uword
|
||||||
|
val dt = memwrite.addressExpression.inferType(program)
|
||||||
|
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||||
|
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||||
|
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||||
|
// add a typecast to the return type if it doesn't match the subroutine's signature
|
||||||
|
val returnValue = returnStmt.value
|
||||||
|
if(returnValue!=null) {
|
||||||
|
val subroutine = returnStmt.definingSubroutine()!!
|
||||||
|
if(subroutine.returntypes.size==1) {
|
||||||
|
val subReturnType = subroutine.returntypes.first()
|
||||||
|
if (returnValue.inferType(program).istype(subReturnType))
|
||||||
|
return noModifications
|
||||||
|
if (returnValue is NumericLiteralValue) {
|
||||||
|
val cast = returnValue.cast(subroutine.returntypes.single())
|
||||||
|
if(cast.isValid)
|
||||||
|
returnStmt.value = cast.valueOrZero()
|
||||||
|
} else {
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
returnValue,
|
||||||
|
TypecastExpression(returnValue, subReturnType, true, returnValue.position),
|
||||||
|
returnStmt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
}
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
61
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
61
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.expressions.FunctionCall
|
||||||
|
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
||||||
|
import prog8.ast.statements.FunctionCallStatement
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
|
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 {
|
||||||
|
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
|
||||||
|
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
|
||||||
|
val target = call.target.targetStatement(scope)
|
||||||
|
if (target is Subroutine) {
|
||||||
|
// asmsub types are not checked specifically at this time
|
||||||
|
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 { 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 }
|
||||||
|
for (x in argtypes.zip(paramtypes).withIndex()) {
|
||||||
|
if (x.value.first !in x.value.second) {
|
||||||
|
val actual = x.value.first.toString()
|
||||||
|
val expected = x.value.second.toString()
|
||||||
|
return "argument ${x.index + 1} type mismatch, was: $actual expected: $expected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
955
compiler/src/prog8/ast/statements/AstStatements.kt
Normal file
955
compiler/src/prog8/ast/statements/AstStatements.kt
Normal file
@ -0,0 +1,955 @@
|
|||||||
|
package prog8.ast.statements
|
||||||
|
|
||||||
|
import prog8.ast.*
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstVisitor
|
||||||
|
|
||||||
|
|
||||||
|
sealed class Statement : Node {
|
||||||
|
abstract fun accept(visitor: IAstVisitor)
|
||||||
|
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||||
|
|
||||||
|
fun makeScopedName(name: String): String {
|
||||||
|
// easy way out is to always return the full scoped name.
|
||||||
|
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
|
||||||
|
// and like this, we can cache the name even,
|
||||||
|
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
|
||||||
|
val scope = mutableListOf<String>()
|
||||||
|
var statementScope = this.parent
|
||||||
|
while(statementScope !is ParentSentinel && statementScope !is Module) {
|
||||||
|
if(statementScope is INameScope) {
|
||||||
|
scope.add(0, statementScope.name)
|
||||||
|
}
|
||||||
|
statementScope = statementScope.parent
|
||||||
|
}
|
||||||
|
if(name.isNotEmpty())
|
||||||
|
scope.add(name)
|
||||||
|
return scope.joinToString(".")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun definingBlock(): Block {
|
||||||
|
if(this is Block)
|
||||||
|
return this
|
||||||
|
return findParentNode<Block>(this)!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
|
||||||
|
override var parent: Node = ParentSentinel
|
||||||
|
override fun linkParents(parent: Node) {}
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
|
||||||
|
|
||||||
|
class Block(override val name: String,
|
||||||
|
val address: Int?,
|
||||||
|
override var statements: MutableList<Statement>,
|
||||||
|
val isInLibrary: Boolean,
|
||||||
|
override val position: Position) : Statement(), INameScope {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
statements.forEach {it.linkParents(this)}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Statement)
|
||||||
|
val idx = statements.indexOfFirst { it ===node }
|
||||||
|
statements[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Block(name=$name, address=$address, ${statements.size} statements)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
args.forEach{it.linkParents(this)}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Label(val name: String, override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Label(name=$name, pos=$position)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class Return(var value: Expression?, override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
value?.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression)
|
||||||
|
value = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Return($value, pos=$position)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Break(override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent=parent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum class ZeropageWish {
|
||||||
|
REQUIRE_ZEROPAGE,
|
||||||
|
PREFER_ZEROPAGE,
|
||||||
|
DONTCARE,
|
||||||
|
NOT_IN_ZEROPAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open class VarDecl(val type: VarDeclType,
|
||||||
|
private val declaredDatatype: DataType,
|
||||||
|
val zeropage: ZeropageWish,
|
||||||
|
var arraysize: ArrayIndex?,
|
||||||
|
val name: String,
|
||||||
|
private val structName: String?,
|
||||||
|
var value: Expression?,
|
||||||
|
val isArray: Boolean,
|
||||||
|
val autogeneratedDontRemove: Boolean,
|
||||||
|
override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
var struct: StructDecl? = null // set later (because at parse time, we only know the name)
|
||||||
|
private set
|
||||||
|
var structHasBeenFlattened = false // set later
|
||||||
|
private set
|
||||||
|
|
||||||
|
// prefix for literal values that are turned into a variable on the heap
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var autoHeapValueSequenceNumber = 0
|
||||||
|
|
||||||
|
fun createAuto(string: StringLiteralValue): VarDecl {
|
||||||
|
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
|
||||||
|
return VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, string,
|
||||||
|
isArray = false, autogeneratedDontRemove = true, position = string.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createAuto(array: ArrayLiteralValue): VarDecl {
|
||||||
|
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
|
||||||
|
val declaredType = ArrayElementTypes.getValue(array.type.typeOrElse(DataType.STRUCT))
|
||||||
|
val arraysize = ArrayIndex.forArray(array)
|
||||||
|
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
|
||||||
|
isArray = true, autogeneratedDontRemove = true, position = array.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defaultZero(dt: DataType, position: Position) = when(dt) {
|
||||||
|
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
|
||||||
|
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
|
||||||
|
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
|
||||||
|
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
|
||||||
|
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
|
||||||
|
else -> throw FatalAstException("can only determine default zero value for a numeric type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
|
||||||
|
val datatype =
|
||||||
|
if (!isArray) declaredDatatype
|
||||||
|
else when (declaredDatatype) {
|
||||||
|
DataType.UBYTE -> DataType.ARRAY_UB
|
||||||
|
DataType.BYTE -> DataType.ARRAY_B
|
||||||
|
DataType.UWORD -> DataType.ARRAY_UW
|
||||||
|
DataType.WORD -> DataType.ARRAY_W
|
||||||
|
DataType.FLOAT -> DataType.ARRAY_F
|
||||||
|
else -> {
|
||||||
|
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position))
|
||||||
|
DataType.ARRAY_UB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
arraysize?.linkParents(this)
|
||||||
|
value?.linkParents(this)
|
||||||
|
if(structName!=null) {
|
||||||
|
val structStmt = definingScope().lookup(listOf(structName), this)
|
||||||
|
if(structStmt!=null)
|
||||||
|
struct = definingScope().lookup(listOf(structName), this) as StructDecl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression && node===value)
|
||||||
|
value = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun zeroElementValue() = defaultZero(declaredDatatype, position)
|
||||||
|
|
||||||
|
fun flattenStructMembers(): MutableList<Statement> {
|
||||||
|
val result = struct!!.statements.withIndex().map {
|
||||||
|
val member = it.value as VarDecl
|
||||||
|
val initvalue = if(value!=null) (value as ArrayLiteralValue).value[it.index] else null
|
||||||
|
VarDecl(
|
||||||
|
VarDeclType.VAR,
|
||||||
|
member.datatype,
|
||||||
|
ZeropageWish.NOT_IN_ZEROPAGE,
|
||||||
|
member.arraysize,
|
||||||
|
mangledStructMemberName(name, member.name),
|
||||||
|
struct!!.name,
|
||||||
|
initvalue,
|
||||||
|
member.isArray,
|
||||||
|
true,
|
||||||
|
member.position
|
||||||
|
)
|
||||||
|
}.toMutableList<Statement>()
|
||||||
|
structHasBeenFlattened = true
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a vardecl used only for subroutine parameters
|
||||||
|
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
|
||||||
|
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, null, false, true, position)
|
||||||
|
|
||||||
|
|
||||||
|
class ArrayIndex(var index: Expression, override val position: Position) : Node {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
index.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression && node===index)
|
||||||
|
index = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun forArray(v: ArrayLiteralValue): ArrayIndex {
|
||||||
|
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun accept(visitor: IAstVisitor) = index.accept(visitor)
|
||||||
|
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return("ArrayIndex($index, pos=$position)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun constIndex() = (index as? NumericLiteralValue)?.number?.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
this.target.linkParents(this)
|
||||||
|
value.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===target -> target = replacement as AssignTarget
|
||||||
|
node===value -> value = replacement as Expression
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return("Assignment(target: $target, value: $value, pos=$position)")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the assigment value an expression that references the assignment target itself?
|
||||||
|
* The expression can be a BinaryExpression, PrefixExpression or TypecastExpression (possibly with one sub-cast).
|
||||||
|
*/
|
||||||
|
val isAugmentable: Boolean
|
||||||
|
get() {
|
||||||
|
val binExpr = value as? BinaryExpression
|
||||||
|
if(binExpr!=null) {
|
||||||
|
if(binExpr.left isSameAs target)
|
||||||
|
return true // A = A <operator> Something
|
||||||
|
|
||||||
|
if(binExpr.operator in associativeOperators) {
|
||||||
|
if (binExpr.left !is BinaryExpression && binExpr.right isSameAs target)
|
||||||
|
return true // A = v <associative-operator> A
|
||||||
|
|
||||||
|
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||||
|
if(leftBinExpr?.operator == binExpr.operator) {
|
||||||
|
// one of these?
|
||||||
|
// A = (A <associative-operator> x) <same-operator> y
|
||||||
|
// A = (x <associative-operator> A) <same-operator> y
|
||||||
|
// A = (x <associative-operator> y) <same-operator> A
|
||||||
|
return leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target || binExpr.right isSameAs target
|
||||||
|
}
|
||||||
|
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||||
|
if(rightBinExpr?.operator == binExpr.operator) {
|
||||||
|
// one of these?
|
||||||
|
// A = y <associative-operator> (A <same-operator> x)
|
||||||
|
// A = y <associative-operator> (x <same-operator> y)
|
||||||
|
// A = A <associative-operator> (x <same-operator> y)
|
||||||
|
return rightBinExpr.left isSameAs target || rightBinExpr.right isSameAs target || binExpr.left isSameAs target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val prefixExpr = value as? PrefixExpression
|
||||||
|
if(prefixExpr!=null)
|
||||||
|
return prefixExpr.expression isSameAs target
|
||||||
|
|
||||||
|
val castExpr = value as? TypecastExpression
|
||||||
|
if(castExpr!=null) {
|
||||||
|
val subCast = castExpr.expression as? TypecastExpression
|
||||||
|
return if(subCast!=null) subCast.expression isSameAs target else castExpr.expression isSameAs target
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AssignTarget(var identifier: IdentifierReference?,
|
||||||
|
var arrayindexed: ArrayIndexedExpression?,
|
||||||
|
val memoryAddress: DirectMemoryWrite?,
|
||||||
|
override val position: Position) : Node {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
identifier?.linkParents(this)
|
||||||
|
arrayindexed?.linkParents(this)
|
||||||
|
memoryAddress?.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===identifier -> identifier = replacement as IdentifierReference
|
||||||
|
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromExpr(expr: Expression): AssignTarget {
|
||||||
|
return when (expr) {
|
||||||
|
is IdentifierReference -> AssignTarget(expr, null, null, expr.position)
|
||||||
|
is ArrayIndexedExpression -> AssignTarget(null, expr, null, expr.position)
|
||||||
|
is DirectMemoryRead -> AssignTarget(null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
|
||||||
|
else -> throw FatalAstException("invalid expression object $expr")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
|
||||||
|
if(identifier!=null) {
|
||||||
|
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
|
||||||
|
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(arrayindexed!=null) {
|
||||||
|
return arrayindexed!!.inferType(program)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memoryAddress!=null)
|
||||||
|
return InferredTypes.knownFor(DataType.UBYTE)
|
||||||
|
|
||||||
|
return InferredTypes.unknown()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toExpression(): Expression {
|
||||||
|
return when {
|
||||||
|
identifier!=null -> identifier!!
|
||||||
|
arrayindexed!=null -> arrayindexed!!
|
||||||
|
memoryAddress!=null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
|
||||||
|
else -> throw FatalAstException("invalid assignmenttarget $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
infix fun isSameAs(value: Expression): Boolean {
|
||||||
|
return when {
|
||||||
|
this.memoryAddress!=null -> {
|
||||||
|
// if the target is a memory write, and the value is a memory read, they're the same if the address matches
|
||||||
|
if(value is DirectMemoryRead)
|
||||||
|
this.memoryAddress.addressExpression isSameAs value.addressExpression
|
||||||
|
else
|
||||||
|
false
|
||||||
|
}
|
||||||
|
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
|
||||||
|
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
|
||||||
|
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
|
||||||
|
value.arrayspec.constIndex()!=null &&
|
||||||
|
arrayindexed!!.arrayspec.constIndex()!=null &&
|
||||||
|
value.arrayspec.constIndex()==arrayindexed!!.arrayspec.constIndex()
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSameAs(other: AssignTarget, program: Program): Boolean {
|
||||||
|
if(this===other)
|
||||||
|
return true
|
||||||
|
if(this.identifier!=null && other.identifier!=null)
|
||||||
|
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
|
||||||
|
if(this.memoryAddress!=null && other.memoryAddress!=null) {
|
||||||
|
val addr1 = this.memoryAddress.addressExpression.constValue(program)
|
||||||
|
val addr2 = other.memoryAddress.addressExpression.constValue(program)
|
||||||
|
return addr1!=null && addr2!=null && addr1==addr2
|
||||||
|
}
|
||||||
|
if(this.arrayindexed!=null && other.arrayindexed!=null) {
|
||||||
|
if(this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
|
||||||
|
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
|
||||||
|
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
|
||||||
|
return x1!=null && x2!=null && x1==x2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isNotMemory(namespace: INameScope): Boolean {
|
||||||
|
if(this.memoryAddress!=null)
|
||||||
|
return false
|
||||||
|
if(this.arrayindexed!=null) {
|
||||||
|
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
|
||||||
|
if(targetStmt!=null)
|
||||||
|
return targetStmt.type!= VarDeclType.MEMORY
|
||||||
|
}
|
||||||
|
if(this.identifier!=null) {
|
||||||
|
val targetStmt = this.identifier!!.targetVarDecl(namespace)
|
||||||
|
if(targetStmt!=null)
|
||||||
|
return targetStmt.type!= VarDeclType.MEMORY
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
target.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is AssignTarget && node===target)
|
||||||
|
target = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Jump(val address: Int?,
|
||||||
|
val identifier: IdentifierReference?,
|
||||||
|
val generatedLabel: String?, // used in code generation scenarios
|
||||||
|
override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
identifier?.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FunctionCallStatement(override var target: IdentifierReference,
|
||||||
|
override var args: MutableList<Expression>,
|
||||||
|
val void: Boolean,
|
||||||
|
override val position: Position) : Statement(), IFunctionCall {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
target.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 accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "FunctionCallStatement(target=$target, pos=$position)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnonymousScope(override var statements: MutableList<Statement>,
|
||||||
|
override val position: Position) : INameScope, Statement() {
|
||||||
|
override val name: String
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var sequenceNumber = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
name = "<anon-$sequenceNumber>" // make sure it's an invalid soruce code identifier so user source code can never produce it
|
||||||
|
sequenceNumber++
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
statements.forEach { it.linkParents(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Statement)
|
||||||
|
val idx = statements.indexOfFirst { it===node }
|
||||||
|
statements[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
class NopStatement(override val position: Position): Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the subroutine class covers both the normal user-defined subroutines,
|
||||||
|
// and also the predefined/ROM/register-based subroutines.
|
||||||
|
// (multiple return types can only occur for the latter type)
|
||||||
|
class Subroutine(override val name: String,
|
||||||
|
val parameters: List<SubroutineParameter>,
|
||||||
|
val returntypes: List<DataType>,
|
||||||
|
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||||
|
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||||
|
val asmClobbers: Set<CpuRegister>,
|
||||||
|
val asmAddress: Int?,
|
||||||
|
val isAsmSubroutine: Boolean,
|
||||||
|
override var statements: MutableList<Statement>,
|
||||||
|
override val position: Position) : Statement(), INameScope {
|
||||||
|
|
||||||
|
override lateinit var parent: Node
|
||||||
|
val scopedname: String by lazy { makeScopedName(name) }
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
parameters.forEach { it.linkParents(this) }
|
||||||
|
statements.forEach { it.linkParents(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Statement)
|
||||||
|
val idx = statements.indexOfFirst { it===node }
|
||||||
|
statements[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||||
|
|
||||||
|
fun amountOfRtsInAsm(): Int = statements
|
||||||
|
.asSequence()
|
||||||
|
.filter { it is InlineAssembly }
|
||||||
|
.map { (it as InlineAssembly).assembly }
|
||||||
|
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open class SubroutineParameter(val name: String,
|
||||||
|
val type: DataType,
|
||||||
|
override val position: Position) : Node {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("can't replace anything in a subroutineparameter node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IfStatement(var condition: Expression,
|
||||||
|
var truepart: AnonymousScope,
|
||||||
|
var elsepart: AnonymousScope,
|
||||||
|
override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
condition.linkParents(this)
|
||||||
|
truepart.linkParents(this)
|
||||||
|
elsepart.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===condition -> condition = replacement as Expression
|
||||||
|
node===truepart -> truepart = replacement as AnonymousScope
|
||||||
|
node===elsepart -> elsepart = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class BranchStatement(var condition: BranchCondition,
|
||||||
|
var truepart: AnonymousScope,
|
||||||
|
var elsepart: AnonymousScope,
|
||||||
|
override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
truepart.linkParents(this)
|
||||||
|
elsepart.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===truepart -> truepart = replacement as AnonymousScope
|
||||||
|
node===elsepart -> elsepart = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ForLoop(var loopVar: IdentifierReference,
|
||||||
|
var iterable: Expression,
|
||||||
|
var body: AnonymousScope,
|
||||||
|
override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent=parent
|
||||||
|
loopVar.linkParents(this)
|
||||||
|
iterable.linkParents(this)
|
||||||
|
body.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===loopVar -> loopVar = replacement as IdentifierReference
|
||||||
|
node===iterable -> iterable = replacement as Expression
|
||||||
|
node===body -> body = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loopVarDt(program: Program) = loopVar.inferType(program)
|
||||||
|
}
|
||||||
|
|
||||||
|
class WhileLoop(var condition: Expression,
|
||||||
|
var body: AnonymousScope,
|
||||||
|
override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
condition.linkParents(this)
|
||||||
|
body.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===condition -> condition = replacement as Expression
|
||||||
|
node===body -> body = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
iterations?.linkParents(this)
|
||||||
|
body.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===iterations -> iterations = replacement as Expression
|
||||||
|
node===body -> body = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
class UntilLoop(var body: AnonymousScope,
|
||||||
|
var untilCondition: Expression,
|
||||||
|
override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
untilCondition.linkParents(this)
|
||||||
|
body.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===untilCondition -> untilCondition = replacement as Expression
|
||||||
|
node===body -> body = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
class WhenStatement(var condition: Expression,
|
||||||
|
var choices: MutableList<WhenChoice>,
|
||||||
|
override val position: Position): Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
condition.linkParents(this)
|
||||||
|
choices.forEach { it.linkParents(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
if(node===condition)
|
||||||
|
condition = replacement as Expression
|
||||||
|
else {
|
||||||
|
val idx = choices.withIndex().find { it.value===node }!!.index
|
||||||
|
choices[idx] = replacement as WhenChoice
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
|
||||||
|
// only gives sensible results when the choices are all valid (constant integers)
|
||||||
|
val result = mutableListOf<Pair<List<Int>?, WhenChoice>>()
|
||||||
|
for(choice in choices) {
|
||||||
|
if(choice.values==null)
|
||||||
|
result.add(null to choice)
|
||||||
|
else {
|
||||||
|
val values = choice.values!!.map { it.constValue(program)?.number?.toInt() }
|
||||||
|
if(values.contains(null))
|
||||||
|
result.add(null to choice)
|
||||||
|
else
|
||||||
|
result.add(values.filterNotNull() to choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
class WhenChoice(var values: List<Expression>?, // if null, this is the 'else' part
|
||||||
|
var statements: AnonymousScope,
|
||||||
|
override val position: Position) : Node {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
values?.forEach { it.linkParents(this) }
|
||||||
|
statements.linkParents(this)
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is AnonymousScope && node===statements)
|
||||||
|
statements = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Choice($values at $position)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StructDecl(override val name: String,
|
||||||
|
override var statements: MutableList<Statement>, // actually, only vardecls here
|
||||||
|
override val position: Position): Statement(), INameScope {
|
||||||
|
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
this.statements.forEach { it.linkParents(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Statement)
|
||||||
|
val idx = statements.indexOfFirst { it===node }
|
||||||
|
statements[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
val numberOfElements: Int
|
||||||
|
get() = this.statements.size
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
|
fun nameOfFirstMember() = (statements.first() as VarDecl).name
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirectMemoryWrite(var addressExpression: Expression, override val position: Position) : Node {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
this.addressExpression.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression && node===addressExpression)
|
||||||
|
addressExpression = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "DirectMemoryWrite($addressExpression)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
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)
|
132
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
132
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package prog8.compiler
|
||||||
|
|
||||||
|
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> {
|
||||||
|
if (decl.value == null && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||||
|
// a numeric vardecl without an initial value is initialized with zero.
|
||||||
|
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.
|
||||||
|
if(!assignment.isAugmentable
|
||||||
|
&& assignment.target.identifier != null
|
||||||
|
&& assignment.target.isNotMemory(program.namespace)) {
|
||||||
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
|
if(binExpr!=null && binExpr.operator !in comparisonOperators) {
|
||||||
|
if(binExpr.left !is BinaryExpression) {
|
||||||
|
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.InsertBefore(assignment, assignLeft, parent),
|
||||||
|
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
|
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||||
|
val sub = scope.definingSubroutine()
|
||||||
|
if (sub != null) {
|
||||||
|
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
||||||
|
var conflicts = false
|
||||||
|
decls.forEach {
|
||||||
|
val existing = existingVariables[it.name]
|
||||||
|
if (existing != null) {
|
||||||
|
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
|
||||||
|
conflicts = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!conflicts) {
|
||||||
|
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
|
||||||
|
return 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.InsertFirst(assign, scope)
|
||||||
|
} + decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
|
||||||
|
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
|
// 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 as Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
if(sourceDt in PassByReferenceDatatypes) {
|
||||||
|
if(typecast.type==DataType.UWORD) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
212
compiler/src/prog8/compiler/Main.kt
Normal file
212
compiler/src/prog8/compiler/Main.kt
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
package prog8.compiler
|
||||||
|
|
||||||
|
import prog8.ast.AstToSourceCode
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.statements.Directive
|
||||||
|
import prog8.compiler.target.CompilationTarget
|
||||||
|
import prog8.optimizer.UnusedCodeRemover
|
||||||
|
import prog8.optimizer.constantFold
|
||||||
|
import prog8.optimizer.optimizeStatements
|
||||||
|
import prog8.optimizer.simplifyExpressions
|
||||||
|
import prog8.parser.ModuleImporter
|
||||||
|
import prog8.parser.ParsingFailedError
|
||||||
|
import prog8.parser.moduleName
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
|
|
||||||
|
class CompilationResult(val success: Boolean,
|
||||||
|
val programAst: Program,
|
||||||
|
val programName: String,
|
||||||
|
val importedFiles: List<Path>)
|
||||||
|
|
||||||
|
|
||||||
|
fun compileProgram(filepath: Path,
|
||||||
|
optimize: Boolean,
|
||||||
|
writeAssembly: Boolean,
|
||||||
|
outputDir: Path): CompilationResult {
|
||||||
|
var programName = ""
|
||||||
|
lateinit var programAst: Program
|
||||||
|
lateinit var importedFiles: List<Path>
|
||||||
|
val errors = ErrorReporter()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val totalTime = measureTimeMillis {
|
||||||
|
// import main module and everything it needs
|
||||||
|
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
|
||||||
|
programAst = ast
|
||||||
|
importedFiles = imported
|
||||||
|
processAst(programAst, errors, compilationOptions)
|
||||||
|
if (optimize)
|
||||||
|
optimizeAst(programAst, errors)
|
||||||
|
postprocessAst(programAst, errors, compilationOptions)
|
||||||
|
|
||||||
|
// printAst(programAst)
|
||||||
|
|
||||||
|
if(writeAssembly)
|
||||||
|
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
|
||||||
|
}
|
||||||
|
System.out.flush()
|
||||||
|
System.err.flush()
|
||||||
|
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||||
|
return CompilationResult(true, programAst, programName, importedFiles)
|
||||||
|
|
||||||
|
} catch (px: ParsingFailedError) {
|
||||||
|
System.err.print("\u001b[91m") // bright red
|
||||||
|
System.err.println(px.message)
|
||||||
|
System.err.print("\u001b[0m") // reset
|
||||||
|
} catch (ax: AstException) {
|
||||||
|
System.err.print("\u001b[91m") // bright red
|
||||||
|
System.err.println(ax.toString())
|
||||||
|
System.err.print("\u001b[0m") // reset
|
||||||
|
} catch (x: Exception) {
|
||||||
|
print("\u001b[91m") // bright red
|
||||||
|
println("\n* internal error *")
|
||||||
|
print("\u001b[0m") // reset
|
||||||
|
System.out.flush()
|
||||||
|
throw x
|
||||||
|
} catch (x: NotImplementedError) {
|
||||||
|
print("\u001b[91m") // bright red
|
||||||
|
println("\n* internal error: missing feature/code *")
|
||||||
|
print("\u001b[0m") // reset
|
||||||
|
System.out.flush()
|
||||||
|
throw x
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompilationResult(false, Program("failed", mutableListOf()), programName, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
|
||||||
|
println("Parsing...")
|
||||||
|
val importer = ModuleImporter()
|
||||||
|
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||||
|
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 mach9ine and compiler options we may have to include some libraries
|
||||||
|
CompilationTarget.machine.importLibs(compilerOptions, importer, programAst)
|
||||||
|
|
||||||
|
// always import prog8lib and math
|
||||||
|
importer.importLibraryModule(programAst, "math")
|
||||||
|
importer.importLibraryModule(programAst, "prog8lib")
|
||||||
|
errors.handle()
|
||||||
|
return Triple(programAst, compilerOptions, importedFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||||
|
val mainModule = program.modules.first()
|
||||||
|
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
|
||||||
|
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||||
|
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
|
||||||
|
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" }
|
||||||
|
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 floatsEnabled = allOptions.any { it.name == "enable_floats" }
|
||||||
|
val zpType: ZeropageType =
|
||||||
|
if (zpoption == null)
|
||||||
|
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
|
||||||
|
else
|
||||||
|
try {
|
||||||
|
ZeropageType.valueOf(zpoption)
|
||||||
|
} catch (x: IllegalArgumentException) {
|
||||||
|
ZeropageType.KERNALSAFE
|
||||||
|
// error will be printed by the astchecker
|
||||||
|
}
|
||||||
|
val zpReserved = mainModule.statements
|
||||||
|
.asSequence()
|
||||||
|
.filter { it is Directive && it.directive == "%zpreserved" }
|
||||||
|
.map { (it as Directive).args }
|
||||||
|
.map { it[0].int!!..it[1].int!! }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
return CompilationOptions(
|
||||||
|
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
||||||
|
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
||||||
|
zpType, zpReserved, floatsEnabled
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
|
// perform initial syntax checks and processings
|
||||||
|
println("Processing...")
|
||||||
|
programAst.checkIdentifiers(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.constantFold(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.reorderStatements()
|
||||||
|
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.optimizeStatements(errors)
|
||||||
|
programAst.constantFold(errors) // because simplified statements and expressions could give rise to more constants that can be folded away:
|
||||||
|
errors.handle()
|
||||||
|
if (optsDone1 + optsDone2 == 0)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
val remover = UnusedCodeRemover(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()
|
||||||
|
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
|
||||||
|
errors.handle()
|
||||||
|
programAst.verifyFunctionArgTypes()
|
||||||
|
}
|
||||||
|
|
||||||
|
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.machine.initializeZeropage(compilerOptions)
|
||||||
|
val assembly = CompilationTarget.asmGenerator(
|
||||||
|
programAst,
|
||||||
|
errors,
|
||||||
|
CompilationTarget.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()
|
||||||
|
}
|
||||||
|
|
@ -1,33 +1,43 @@
|
|||||||
package prog8.compiler
|
package prog8.compiler
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.base.*
|
||||||
|
|
||||||
|
|
||||||
class ZeropageDepletedError(message: String) : Exception(message)
|
class ZeropageDepletedError(message: String) : Exception(message)
|
||||||
|
|
||||||
|
|
||||||
abstract class Zeropage(private 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_REG_X : Int // temp storage for register X (the evaluation stack pointer)
|
||||||
|
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.
|
||||||
|
|
||||||
val allowedDatatypes = NumericDatatypes
|
val allowedDatatypes = NumericDatatypes
|
||||||
|
|
||||||
fun available() = 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 } ) {"same 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)
|
||||||
|
throw CompilerException("zero page usage has been disabled")
|
||||||
|
|
||||||
val size =
|
val size =
|
||||||
when (datatype) {
|
when (datatype) {
|
||||||
DataType.UBYTE, DataType.BYTE -> 1
|
in ByteDatatypes -> 1
|
||||||
DataType.UWORD, DataType.WORD -> 2
|
in WordDatatypes -> 2
|
||||||
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")
|
||||||
}
|
}
|
||||||
@ -36,13 +46,13 @@ abstract class Zeropage(private 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)
|
||||||
}
|
}
|
||||||
@ -61,4 +71,12 @@ abstract class Zeropage(private val options: CompilationOptions) {
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
package prog8.compiler.intermediate
|
|
||||||
|
|
||||||
import prog8.stackvm.Syscall
|
|
||||||
|
|
||||||
open class Instruction(val opcode: Opcode,
|
|
||||||
val arg: Value? = null,
|
|
||||||
val arg2: Value? = null,
|
|
||||||
val callLabel: String? = null,
|
|
||||||
val callLabel2: String? = null)
|
|
||||||
{
|
|
||||||
lateinit var next: Instruction
|
|
||||||
var nextAlt: Instruction? = null
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
val argStr = arg?.toString() ?: ""
|
|
||||||
val result =
|
|
||||||
when {
|
|
||||||
opcode==Opcode.LINE -> "_line $callLabel"
|
|
||||||
opcode==Opcode.INLINE_ASSEMBLY -> "inline_assembly"
|
|
||||||
opcode==Opcode.SYSCALL -> {
|
|
||||||
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
|
|
||||||
"syscall $syscall"
|
|
||||||
}
|
|
||||||
opcode in opcodesWithVarArgument -> {
|
|
||||||
// opcodes that manipulate a variable
|
|
||||||
"${opcode.toString().toLowerCase()} ${callLabel?:""} ${callLabel2?:""}".trimEnd()
|
|
||||||
}
|
|
||||||
callLabel==null -> "${opcode.toString().toLowerCase()} $argStr"
|
|
||||||
else -> "${opcode.toString().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,504 +0,0 @@
|
|||||||
package prog8.compiler.intermediate
|
|
||||||
|
|
||||||
import prog8.ast.*
|
|
||||||
import prog8.compiler.CompilerException
|
|
||||||
import prog8.compiler.HeapValues
|
|
||||||
import prog8.compiler.Zeropage
|
|
||||||
import prog8.compiler.ZeropageDepletedError
|
|
||||||
import java.io.PrintStream
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
|
|
||||||
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val importedFrom: Path) {
|
|
||||||
|
|
||||||
class ProgramBlock(val scopedname: String,
|
|
||||||
val shortname: String,
|
|
||||||
var address: Int?,
|
|
||||||
val instructions: MutableList<Instruction> = mutableListOf(),
|
|
||||||
val variables: MutableMap<String, Value> = mutableMapOf(),
|
|
||||||
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
|
|
||||||
val labels: MutableMap<String, Instruction> = mutableMapOf(),
|
|
||||||
val force_output: Boolean)
|
|
||||||
{
|
|
||||||
val numVariables: Int
|
|
||||||
get() { return variables.size }
|
|
||||||
val numInstructions: Int
|
|
||||||
get() { return instructions.filter { it.opcode!= Opcode.LINE }.size }
|
|
||||||
val variablesMarkedForZeropage: MutableSet<String> = mutableSetOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
|
|
||||||
val blocks = mutableListOf<ProgramBlock>()
|
|
||||||
val memory = mutableMapOf<Int, List<Value>>()
|
|
||||||
private lateinit var currentBlock: ProgramBlock
|
|
||||||
|
|
||||||
val numVariables: Int
|
|
||||||
get() = blocks.sumBy { it.numVariables }
|
|
||||||
val numInstructions: Int
|
|
||||||
get() = blocks.sumBy { it.numInstructions }
|
|
||||||
|
|
||||||
fun allocateZeropage(zeropage: Zeropage) {
|
|
||||||
// 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.key in block.variablesMarkedForZeropage }
|
|
||||||
if (zpVariables.isNotEmpty()) {
|
|
||||||
for (variable in zpVariables) {
|
|
||||||
try {
|
|
||||||
val address = zeropage.allocate(variable.key, variable.value.type, null)
|
|
||||||
allocatedZeropageVariables[variable.key] = Pair(address, variable.value.type)
|
|
||||||
} catch (x: ZeropageDepletedError) {
|
|
||||||
printWarning(x.toString() + " variable ${variable.key} 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 stackvm 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!!.asBooleanValue
|
|
||||||
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, Value(DataType.UBYTE, ins0.arg!!.integerValue() and 255))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.MSB -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_BYTE, Value(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, Value(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, Value(DataType.UWORD, ins0.arg!!.integerValue()))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_B_TO_W -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_WORD, Value(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, Value(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")
|
|
||||||
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 -> {
|
|
||||||
val value = when(decl.datatype) {
|
|
||||||
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
|
|
||||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
|
|
||||||
val litval = (decl.value as LiteralValue)
|
|
||||||
if(litval.heapId==null)
|
|
||||||
throw CompilerException("string should already be in the heap")
|
|
||||||
Value(decl.datatype, litval.heapId)
|
|
||||||
}
|
|
||||||
DataType.ARRAY_B, DataType.ARRAY_W,
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_UW, DataType.ARRAY_F -> {
|
|
||||||
val litval = (decl.value as LiteralValue)
|
|
||||||
if(litval.heapId==null)
|
|
||||||
throw CompilerException("array should already be in the heap")
|
|
||||||
Value(decl.datatype, litval.heapId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentBlock.variables[scopedname] = value
|
|
||||||
if(decl.zeropage)
|
|
||||||
currentBlock.variablesMarkedForZeropage.add(scopedname)
|
|
||||||
}
|
|
||||||
VarDeclType.MEMORY -> {
|
|
||||||
// note that constants are all folded away, but assembly code may still refer to them
|
|
||||||
val lv = decl.value as LiteralValue
|
|
||||||
if(lv.type!=DataType.UWORD && lv.type!=DataType.UBYTE)
|
|
||||||
throw CompilerException("expected integer memory address $lv")
|
|
||||||
currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, 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 LiteralValue
|
|
||||||
if(lv.type in IntegerDatatypes)
|
|
||||||
currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun instr(opcode: Opcode, arg: Value? = null, arg2: Value? = 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(scopedname: String, shortname: String, address: Int?, options: Set<String>) {
|
|
||||||
currentBlock = ProgramBlock(scopedname, shortname, address, force_output="force_output" in options)
|
|
||||||
blocks.add(currentBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun writeCode(out: PrintStream, embeddedLabels: Boolean=true) {
|
|
||||||
out.println("; stackVM program code for '$name'")
|
|
||||||
out.println("%memory")
|
|
||||||
if(memory.isNotEmpty())
|
|
||||||
TODO("add support for writing/reading initial memory values")
|
|
||||||
out.println("%end_memory")
|
|
||||||
out.println("%heap")
|
|
||||||
heap.allEntries().forEach {
|
|
||||||
when {
|
|
||||||
it.value.str!=null ->
|
|
||||||
out.println("${it.key} ${it.value.type.toString().toLowerCase()} \"${escape(it.value.str!!)}\"")
|
|
||||||
it.value.array!=null ->
|
|
||||||
out.println("${it.key} ${it.value.type.toString().toLowerCase()} ${it.value.array!!.toList()}")
|
|
||||||
it.value.doubleArray!=null ->
|
|
||||||
out.println("${it.key} ${it.value.type.toString().toLowerCase()} ${it.value.doubleArray!!.toList()}")
|
|
||||||
else -> throw CompilerException("invalid heap entry $it")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.println("%end_heap")
|
|
||||||
for(blk in blocks) {
|
|
||||||
out.println("\n%block ${blk.scopedname} ${blk.address?.toString(16) ?: ""}")
|
|
||||||
|
|
||||||
out.println("%variables")
|
|
||||||
for(variable in blk.variables) {
|
|
||||||
val valuestr = variable.value.toString()
|
|
||||||
out.println("${variable.key} ${variable.value.type.toString().toLowerCase()} $valuestr")
|
|
||||||
}
|
|
||||||
out.println("%end_variables")
|
|
||||||
out.println("%memorypointers")
|
|
||||||
for(iconst in blk.memoryPointers) {
|
|
||||||
out.println("${iconst.key} ${iconst.value.second.toString().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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,292 +0,0 @@
|
|||||||
package prog8.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)
|
|
||||||
|
|
||||||
// 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_UB,
|
|
||||||
POW_B,
|
|
||||||
POW_UW,
|
|
||||||
POW_W,
|
|
||||||
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
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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,478 +0,0 @@
|
|||||||
package prog8.compiler.intermediate
|
|
||||||
|
|
||||||
import prog8.ast.*
|
|
||||||
import java.lang.Exception
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.pow
|
|
||||||
|
|
||||||
|
|
||||||
class ValueException(msg: String?) : Exception(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class Value(val type: DataType, numericvalueOrHeapId: Number) {
|
|
||||||
private var byteval: Short? = null
|
|
||||||
private var wordval: Int? = null
|
|
||||||
private var floatval: Double? = null
|
|
||||||
var heapId: Int = -1
|
|
||||||
private set
|
|
||||||
val asBooleanValue: Boolean
|
|
||||||
|
|
||||||
init {
|
|
||||||
when(type) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
if(numericvalueOrHeapId.toInt() !in 0..255)
|
|
||||||
throw ValueException("value out of range: $numericvalueOrHeapId")
|
|
||||||
byteval = numericvalueOrHeapId.toShort()
|
|
||||||
asBooleanValue = byteval != (0.toShort())
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
if(numericvalueOrHeapId.toInt() !in -128..127)
|
|
||||||
throw ValueException("value out of range: $numericvalueOrHeapId")
|
|
||||||
byteval = numericvalueOrHeapId.toShort()
|
|
||||||
asBooleanValue = byteval != (0.toShort())
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
if(numericvalueOrHeapId.toInt() !in 0..65535)
|
|
||||||
throw ValueException("value out of range: $numericvalueOrHeapId")
|
|
||||||
wordval = numericvalueOrHeapId.toInt()
|
|
||||||
asBooleanValue = wordval != 0
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
if(numericvalueOrHeapId.toInt() !in -32768..32767)
|
|
||||||
throw ValueException("value out of range: $numericvalueOrHeapId")
|
|
||||||
wordval = numericvalueOrHeapId.toInt()
|
|
||||||
asBooleanValue = wordval != 0
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
floatval = numericvalueOrHeapId.toDouble()
|
|
||||||
asBooleanValue = floatval != 0.0
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
if(numericvalueOrHeapId !is Int || numericvalueOrHeapId<0)
|
|
||||||
throw ValueException("for non-numeric types, the value should be a integer heapId >= 0")
|
|
||||||
heapId = numericvalueOrHeapId
|
|
||||||
asBooleanValue=true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE -> "ub:%02x".format(byteval)
|
|
||||||
DataType.BYTE -> {
|
|
||||||
if(byteval!!<0)
|
|
||||||
"b:-%02x".format(abs(byteval!!.toInt()))
|
|
||||||
else
|
|
||||||
"b:%02x".format(byteval)
|
|
||||||
}
|
|
||||||
DataType.UWORD -> "uw:%04x".format(wordval)
|
|
||||||
DataType.WORD -> {
|
|
||||||
if(wordval!!<0)
|
|
||||||
"w:-%04x".format(abs(wordval!!))
|
|
||||||
else
|
|
||||||
"w:%04x".format(wordval)
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> "f:$floatval"
|
|
||||||
else -> "heap:$heapId"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun numericValue(): Number {
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> byteval!!
|
|
||||||
DataType.UWORD, DataType.WORD -> wordval!!
|
|
||||||
DataType.FLOAT -> floatval!!
|
|
||||||
else -> throw ValueException("invalid datatype for numeric value: $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun integerValue(): Int {
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> byteval!!.toInt()
|
|
||||||
DataType.UWORD, DataType.WORD -> wordval!!
|
|
||||||
DataType.FLOAT -> throw ValueException("float to integer loss of precision")
|
|
||||||
else -> throw ValueException("invalid datatype for integer value: $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
val bh = byteval?.hashCode() ?: 0x10001234
|
|
||||||
val wh = wordval?.hashCode() ?: 0x01002345
|
|
||||||
val fh = floatval?.hashCode() ?: 0x00103456
|
|
||||||
return bh xor wh xor fh xor heapId.hashCode() xor type.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if(other==null || other !is Value)
|
|
||||||
return false
|
|
||||||
if(type==other.type)
|
|
||||||
return if (type in IterableDatatypes) heapId==other.heapId else compareTo(other)==0
|
|
||||||
return compareTo(other)==0 // note: datatype doesn't matter
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun compareTo(other: Value): Int {
|
|
||||||
return if (type in NumericDatatypes && other.type in NumericDatatypes)
|
|
||||||
numericValue().toDouble().compareTo(other.numericValue().toDouble())
|
|
||||||
else throw ValueException("comparison can only be done between two numeric values")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): Value {
|
|
||||||
if(leftDt!=rightDt)
|
|
||||||
throw ValueException("left and right datatypes are not the same")
|
|
||||||
if(result.toDouble() < 0 ) {
|
|
||||||
return when(leftDt) {
|
|
||||||
DataType.UBYTE, DataType.UWORD -> {
|
|
||||||
// storing a negative number in an unsigned one is done by storing the 2's complement instead
|
|
||||||
val number = abs(result.toDouble().toInt())
|
|
||||||
if(leftDt==DataType.UBYTE)
|
|
||||||
Value(DataType.UBYTE, (number xor 255) + 1)
|
|
||||||
else
|
|
||||||
Value(DataType.UBYTE, (number xor 65535) + 1)
|
|
||||||
}
|
|
||||||
DataType.BYTE -> Value(DataType.BYTE, result.toInt())
|
|
||||||
DataType.WORD -> Value(DataType.WORD, result.toInt())
|
|
||||||
DataType.FLOAT -> Value(DataType.FLOAT, result)
|
|
||||||
else -> throw ValueException("$op on non-numeric type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return when(leftDt) {
|
|
||||||
DataType.UBYTE -> Value(DataType.UBYTE, result.toInt() and 255)
|
|
||||||
DataType.BYTE -> Value(DataType.BYTE, result.toInt())
|
|
||||||
DataType.UWORD -> Value(DataType.UWORD, result.toInt() and 65535)
|
|
||||||
DataType.WORD -> Value(DataType.WORD, result.toInt())
|
|
||||||
DataType.FLOAT -> Value(DataType.FLOAT, result)
|
|
||||||
else -> throw ValueException("$op on non-numeric type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun add(other: Value): Value {
|
|
||||||
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
|
|
||||||
throw ValueException("floating point loss of precision on type $type")
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
val result = v1.toDouble() + v2.toDouble()
|
|
||||||
return arithResult(type, result, other.type, "add")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sub(other: Value): Value {
|
|
||||||
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
|
|
||||||
throw ValueException("floating point loss of precision on type $type")
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
val result = v1.toDouble() - v2.toDouble()
|
|
||||||
return arithResult(type, result, other.type, "sub")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mul(other: Value): Value {
|
|
||||||
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
|
|
||||||
throw ValueException("floating point loss of precision on type $type")
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
val result = v1.toDouble() * v2.toDouble()
|
|
||||||
return arithResult(type, result, other.type, "mul")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun div(other: Value): Value {
|
|
||||||
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
|
|
||||||
throw ValueException("floating point loss of precision on type $type")
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
if(v2.toDouble()==0.0) {
|
|
||||||
when (type) {
|
|
||||||
DataType.UBYTE -> return Value(DataType.UBYTE, 255)
|
|
||||||
DataType.BYTE -> return Value(DataType.BYTE, 127)
|
|
||||||
DataType.UWORD -> return Value(DataType.UWORD, 65535)
|
|
||||||
DataType.WORD -> return Value(DataType.WORD, 32767)
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val result = v1.toDouble() / v2.toDouble()
|
|
||||||
// NOTE: integer division returns integer result!
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE -> Value(DataType.UBYTE, result)
|
|
||||||
DataType.BYTE -> Value(DataType.BYTE, result)
|
|
||||||
DataType.UWORD -> Value(DataType.UWORD, result)
|
|
||||||
DataType.WORD -> Value(DataType.WORD, result)
|
|
||||||
DataType.FLOAT -> Value(DataType.FLOAT, result)
|
|
||||||
else -> throw ValueException("div on non-numeric type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remainder(other: Value): Value? {
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
val result = v1.toDouble() % v2.toDouble()
|
|
||||||
return arithResult(type, result, other.type, "remainder")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun pow(other: Value): Value {
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
val result = v1.toDouble().pow(v2.toDouble())
|
|
||||||
return arithResult(type, result, other.type,"pow")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shl(): Value {
|
|
||||||
val v = integerValue()
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE -> return Value(type, (v shl 1) and 255)
|
|
||||||
DataType.BYTE -> {
|
|
||||||
if(v<0)
|
|
||||||
Value(type, -((-v shl 1) and 255))
|
|
||||||
else
|
|
||||||
Value(type, (v shl 1) and 255)
|
|
||||||
}
|
|
||||||
DataType.UWORD -> return Value(type, (v shl 1) and 65535)
|
|
||||||
DataType.WORD -> {
|
|
||||||
if(v<0)
|
|
||||||
Value(type, -((-v shl 1) and 65535))
|
|
||||||
else
|
|
||||||
Value(type, (v shl 1) and 65535)
|
|
||||||
}
|
|
||||||
else -> throw ValueException("invalid type for shl: $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shr(): Value {
|
|
||||||
val v = integerValue()
|
|
||||||
return when(type){
|
|
||||||
DataType.UBYTE -> Value(type, (v ushr 1) and 255)
|
|
||||||
DataType.BYTE -> Value(type, v shr 1)
|
|
||||||
DataType.UWORD -> Value(type, (v ushr 1) and 65535)
|
|
||||||
DataType.WORD -> Value(type, v shr 1)
|
|
||||||
else -> throw ValueException("invalid type for shr: $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rol(carry: Boolean): Pair<Value, Boolean> {
|
|
||||||
// 9 or 17 bit rotate left (with carry))
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
val v = byteval!!.toInt()
|
|
||||||
val newCarry = (v and 0x80) != 0
|
|
||||||
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0)
|
|
||||||
Pair(Value(DataType.UBYTE, newval), newCarry)
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
val v = wordval!!
|
|
||||||
val newCarry = (v and 0x8000) != 0
|
|
||||||
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0)
|
|
||||||
Pair(Value(DataType.UWORD, newval), newCarry)
|
|
||||||
}
|
|
||||||
else -> throw ValueException("rol can only work on byte/word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ror(carry: Boolean): Pair<Value, Boolean> {
|
|
||||||
// 9 or 17 bit rotate right (with carry)
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
val v = byteval!!.toInt()
|
|
||||||
val newCarry = v and 1 != 0
|
|
||||||
val newval = (v ushr 1) or (if(carry) 0x80 else 0)
|
|
||||||
Pair(Value(DataType.UBYTE, newval), newCarry)
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
val v = wordval!!
|
|
||||||
val newCarry = v and 1 != 0
|
|
||||||
val newval = (v ushr 1) or (if(carry) 0x8000 else 0)
|
|
||||||
Pair(Value(DataType.UWORD, newval), newCarry)
|
|
||||||
}
|
|
||||||
else -> throw ValueException("ror2 can only work on byte/word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rol2(): Value {
|
|
||||||
// 8 or 16 bit rotate left
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
val v = byteval!!.toInt()
|
|
||||||
val carry = (v and 0x80) ushr 7
|
|
||||||
val newval = (v and 0x7f shl 1) or carry
|
|
||||||
Value(DataType.UBYTE, newval)
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
val v = wordval!!
|
|
||||||
val carry = (v and 0x8000) ushr 15
|
|
||||||
val newval = (v and 0x7fff shl 1) or carry
|
|
||||||
Value(DataType.UWORD, newval)
|
|
||||||
}
|
|
||||||
else -> throw ValueException("rol2 can only work on byte/word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ror2(): Value {
|
|
||||||
// 8 or 16 bit rotate right
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
val v = byteval!!.toInt()
|
|
||||||
val carry = v and 1 shl 7
|
|
||||||
val newval = (v ushr 1) or carry
|
|
||||||
Value(DataType.UBYTE, newval)
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
val v = wordval!!
|
|
||||||
val carry = v and 1 shl 15
|
|
||||||
val newval = (v ushr 1) or carry
|
|
||||||
Value(DataType.UWORD, newval)
|
|
||||||
}
|
|
||||||
else -> throw ValueException("ror2 can only work on byte/word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun neg(): Value {
|
|
||||||
return when(type) {
|
|
||||||
DataType.BYTE -> Value(DataType.BYTE, -(byteval!!))
|
|
||||||
DataType.WORD -> Value(DataType.WORD, -(wordval!!))
|
|
||||||
DataType.FLOAT -> Value(DataType.FLOAT, -(floatval)!!)
|
|
||||||
else -> throw ValueException("neg can only work on byte/word/float")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun abs(): Value {
|
|
||||||
return when(type) {
|
|
||||||
DataType.BYTE -> Value(DataType.BYTE, abs(byteval!!.toInt()))
|
|
||||||
DataType.WORD -> Value(DataType.WORD, abs(wordval!!))
|
|
||||||
DataType.FLOAT -> Value(DataType.FLOAT, abs(floatval!!))
|
|
||||||
else -> throw ValueException("abs can only work on byte/word/float")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bitand(other: Value): Value {
|
|
||||||
val v1 = integerValue()
|
|
||||||
val v2 = other.integerValue()
|
|
||||||
val result = v1 and v2
|
|
||||||
return Value(type, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bitor(other: Value): Value {
|
|
||||||
val v1 = integerValue()
|
|
||||||
val v2 = other.integerValue()
|
|
||||||
val result = v1 or v2
|
|
||||||
return Value(type, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bitxor(other: Value): Value {
|
|
||||||
val v1 = integerValue()
|
|
||||||
val v2 = other.integerValue()
|
|
||||||
val result = v1 xor v2
|
|
||||||
return Value(type, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun and(other: Value) = Value(DataType.UBYTE, if (this.asBooleanValue && other.asBooleanValue) 1 else 0)
|
|
||||||
fun or(other: Value) = Value(DataType.UBYTE, if (this.asBooleanValue || other.asBooleanValue) 1 else 0)
|
|
||||||
fun xor(other: Value) = Value(DataType.UBYTE, if (this.asBooleanValue xor other.asBooleanValue) 1 else 0)
|
|
||||||
fun not() = Value(DataType.UBYTE, if (this.asBooleanValue) 0 else 1)
|
|
||||||
|
|
||||||
fun inv(): Value {
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE -> Value(DataType.UBYTE, byteval!!.toInt().inv() and 255)
|
|
||||||
DataType.UWORD -> Value(DataType.UWORD, wordval!!.inv() and 65535)
|
|
||||||
else -> throw ValueException("inv can only work on byte/word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inc(): Value {
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE -> Value(DataType.UBYTE, (byteval!! + 1) and 255)
|
|
||||||
DataType.UWORD -> Value(DataType.UWORD, (wordval!! + 1) and 65535)
|
|
||||||
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! + 1)
|
|
||||||
else -> throw ValueException("inc can only work on byte/word/float")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun dec(): Value {
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE -> Value(DataType.UBYTE, (byteval!! - 1) and 255)
|
|
||||||
DataType.UWORD -> Value(DataType.UWORD, (wordval!! - 1) and 65535)
|
|
||||||
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! - 1)
|
|
||||||
else -> throw ValueException("dec can only work on byte/word/float")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun msb(): Value {
|
|
||||||
return when(type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> Value(DataType.UBYTE, 0)
|
|
||||||
DataType.UWORD, DataType.WORD -> Value(DataType.UBYTE, wordval!! ushr 8 and 255)
|
|
||||||
else -> throw ValueException("msb can only work on (u)byte/(u)word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cast(targetType: DataType): Value {
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
when (targetType) {
|
|
||||||
DataType.UBYTE -> this
|
|
||||||
DataType.BYTE -> {
|
|
||||||
if(byteval!!<=127)
|
|
||||||
Value(DataType.BYTE, byteval!!)
|
|
||||||
else
|
|
||||||
Value(DataType.BYTE, -(256-byteval!!))
|
|
||||||
}
|
|
||||||
DataType.UWORD -> Value(DataType.UWORD, numericValue())
|
|
||||||
DataType.WORD -> Value(DataType.WORD, numericValue())
|
|
||||||
DataType.FLOAT -> Value(DataType.FLOAT, numericValue())
|
|
||||||
else -> throw ValueException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
when (targetType) {
|
|
||||||
DataType.BYTE -> this
|
|
||||||
DataType.UBYTE -> Value(DataType.UBYTE, integerValue() and 255)
|
|
||||||
DataType.UWORD -> Value(DataType.UWORD, integerValue() and 65535)
|
|
||||||
DataType.WORD -> Value(DataType.WORD, integerValue())
|
|
||||||
DataType.FLOAT -> Value(DataType.FLOAT, numericValue())
|
|
||||||
else -> throw ValueException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
when (targetType) {
|
|
||||||
DataType.BYTE, DataType.UBYTE -> Value(DataType.UBYTE, integerValue() and 255)
|
|
||||||
DataType.UWORD -> this
|
|
||||||
DataType.WORD -> {
|
|
||||||
if(integerValue()<=32767)
|
|
||||||
Value(DataType.WORD, integerValue())
|
|
||||||
else
|
|
||||||
Value(DataType.WORD, -(65536-integerValue()))
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> Value(DataType.FLOAT, numericValue())
|
|
||||||
else -> throw ValueException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
when (targetType) {
|
|
||||||
DataType.BYTE, DataType.UBYTE -> Value(DataType.UBYTE, integerValue() and 255)
|
|
||||||
DataType.UWORD -> Value(DataType.UWORD, integerValue() and 65535)
|
|
||||||
DataType.WORD -> this
|
|
||||||
DataType.FLOAT -> Value(DataType.FLOAT, numericValue())
|
|
||||||
else -> throw ValueException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
when (targetType) {
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val integer=numericValue().toInt()
|
|
||||||
if(integer in -128..127)
|
|
||||||
Value(DataType.BYTE, integer)
|
|
||||||
else
|
|
||||||
throw ValueException("overflow when casting float to byte: $this")
|
|
||||||
}
|
|
||||||
DataType.UBYTE -> Value(DataType.UBYTE, numericValue().toInt() and 255)
|
|
||||||
DataType.UWORD -> Value(DataType.UWORD, numericValue().toInt() and 65535)
|
|
||||||
DataType.WORD -> {
|
|
||||||
val integer=numericValue().toInt()
|
|
||||||
if(integer in -32768..32767)
|
|
||||||
Value(DataType.WORD, integer)
|
|
||||||
else
|
|
||||||
throw ValueException("overflow when casting float to word: $this")
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> this
|
|
||||||
else -> throw ValueException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw ValueException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
18
compiler/src/prog8/compiler/target/CompilationTarget.kt
Normal file
18
compiler/src/prog8/compiler/target/CompilationTarget.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package prog8.compiler.target
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.ErrorReporter
|
||||||
|
import prog8.compiler.CompilationOptions
|
||||||
|
import prog8.compiler.Zeropage
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
|
internal interface CompilationTarget {
|
||||||
|
companion object {
|
||||||
|
lateinit var name: String
|
||||||
|
lateinit var machine: IMachineDefinition
|
||||||
|
lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short>
|
||||||
|
lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String
|
||||||
|
lateinit var asmGenerator: (Program, ErrorReporter, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
|
||||||
|
}
|
||||||
|
}
|
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)
|
||||||
|
}
|
34
compiler/src/prog8/compiler/target/IMachineDefinition.kt
Normal file
34
compiler/src/prog8/compiler/target/IMachineDefinition.kt
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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 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 initSystemProcname: String
|
||||||
|
val cpu: String
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -2,55 +2,72 @@ 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.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")
|
||||||
|
|
||||||
fun assemble(options: CompilationOptions) {
|
override fun assemble(options: CompilationOptions) {
|
||||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
|
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
||||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", "-Wall", "-Wno-strict-bool",
|
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||||
"-Werror", "-Wno-error=long-branch", "--dump-labels", "--vice-labels", "-l", viceMonListFile, "--no-monitor")
|
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||||
|
"--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.")
|
||||||
"$name.prg"
|
prgFile
|
||||||
}
|
}
|
||||||
OutputType.RAW -> {
|
OutputType.RAW -> {
|
||||||
command.add("--nostart")
|
command.add("--nostart")
|
||||||
println("\nCreating raw binary.")
|
println("\nCreating raw binary.")
|
||||||
"$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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
246
compiler/src/prog8/compiler/target/c64/C64MachineDefinition.kt
Normal file
246
compiler/src/prog8/compiler/target/c64/C64MachineDefinition.kt
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
package prog8.compiler.target.c64
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.compiler.*
|
||||||
|
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 = "6502"
|
||||||
|
|
||||||
|
// 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 val initSystemProcname = "c64.init_system"
|
||||||
|
|
||||||
|
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 "c64flt.FL_ZERO"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "c64flt.FL_PIVAL"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_N32768"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FONE"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRHLF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRTWO"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_NEGHLF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "c64flt.FL_LOG2"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "c64flt.FL_TENC"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "c64flt.FL_NZMIL"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FHALF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "c64flt.FL_LOGEB2"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_PIHALF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_TWOPI"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FR4"
|
||||||
|
else -> {
|
||||||
|
// attempt to correct for a few rounding issues
|
||||||
|
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
|
||||||
|
3.1415926536 -> return "c64flt.FL_PIVAL"
|
||||||
|
1.4142135624 -> return "c64flt.FL_SQRTWO"
|
||||||
|
0.7071067812 -> return "c64flt.FL_SQRHLF"
|
||||||
|
0.6931471806 -> return "c64flt.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, "c64lib")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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
|
||||||
|
override val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require(SCRATCH_B1 !in free)
|
||||||
|
require(SCRATCH_REG !in free)
|
||||||
|
require(SCRATCH_REG_X !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,157 +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
|
|
||||||
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
const val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
|
|
||||||
const val ESTACK_HI = 0xcf00 // $cf00-$cfff inclusive
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
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) {
|
|
||||||
// add the Zp addresses that are just used by BASIC routines to the free list
|
|
||||||
free.addAll(listOf(0x09, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
|
||||||
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
|
||||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
|
||||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53, 0x6f, 0x70))
|
|
||||||
}
|
|
||||||
// add the other free Zp addresses
|
|
||||||
// these are valid for the C-64 (when no RS232 I/O is performed):
|
|
||||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0d, 0x0e,
|
|
||||||
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
|
||||||
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
1124
compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt
Normal file
1124
compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,8 @@
|
|||||||
package prog8.compiler.target.c64
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
|
||||||
|
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
||||||
|
|
||||||
import prog8.compiler.toHex
|
|
||||||
|
|
||||||
fun optimizeAssembly(lines: MutableList<String>): Int {
|
fun optimizeAssembly(lines: MutableList<String>): Int {
|
||||||
|
|
||||||
@ -8,67 +10,114 @@ 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 = optimizeStoreLoadSame(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)
|
||||||
|
numberOfOptimizations++
|
||||||
|
}
|
||||||
|
|
||||||
|
mods = optimizeStoreLoadSame(linesByFour)
|
||||||
|
if(mods.isNotEmpty()) {
|
||||||
|
apply(mods, lines)
|
||||||
|
linesByFour = getLinesBy(lines, 4)
|
||||||
|
numberOfOptimizations++
|
||||||
|
}
|
||||||
|
|
||||||
|
mods= optimizeJsrRts(linesByFour)
|
||||||
|
if(mods.isNotEmpty()) {
|
||||||
|
apply(mods, lines)
|
||||||
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
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)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO more assembly optimizations?
|
// TODO more assembly optimizations
|
||||||
|
|
||||||
return numberOfOptimizations
|
return numberOfOptimizations
|
||||||
}
|
}
|
||||||
|
|
||||||
fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
|
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
|
||||||
// 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.
|
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
|
||||||
val removeLines = mutableListOf<Int>()
|
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
|
||||||
for(lines in linesByFour) {
|
if(modification.remove)
|
||||||
if(lines[0].value.trim()=="sta ${ESTACK_LO.toHex()},x" &&
|
lines.removeAt(modification.lineIndex)
|
||||||
lines[1].value.trim()=="dex" &&
|
else
|
||||||
lines[2].value.trim()=="inx" &&
|
lines[modification.lineIndex] = modification.replacement!!
|
||||||
lines[3].value.trim()=="lda ${ESTACK_LO.toHex()},x") {
|
|
||||||
removeLines.add(lines[0].index)
|
|
||||||
removeLines.add(lines[1].index)
|
|
||||||
removeLines.add(lines[2].index)
|
|
||||||
removeLines.add(lines[3].index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return removeLines
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Int> {
|
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)
|
||||||
|
|
||||||
// optimize sequential assignments of the same value to various targets (bytes, words, floats)
|
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
// the when statement (on bytes) generates a sequence of:
|
||||||
|
// lda $ce01,x
|
||||||
|
// cmp #$20
|
||||||
|
// beq check_prog8_s72choice_32
|
||||||
|
// lda $ce01,x
|
||||||
|
// cmp #$21
|
||||||
|
// beq check_prog8_s73choice_33
|
||||||
|
// the repeated lda can be removed
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
for(lines in linesByFour) {
|
||||||
|
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
|
||||||
|
lines[1].value.trim().startsWith("cmp ") &&
|
||||||
|
lines[2].value.trim().startsWith("beq ") &&
|
||||||
|
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
|
||||||
|
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
|
||||||
|
// this is a lot harder for word values because the instruction sequence varies.
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
for(lines in linesByFour) {
|
||||||
|
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
|
||||||
|
lines[1].value.trim()=="dex" &&
|
||||||
|
lines[2].value.trim()=="inx" &&
|
||||||
|
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
|
mods.add(Modification(lines[3].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
|
||||||
|
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
|
||||||
// the float one is the one that requires 2*7=14 lines of code to check...
|
// 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 work on opcodes, and never even create the inefficient asm...
|
// @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()
|
||||||
@ -86,9 +135,9 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
|||||||
val thirdvalue = fifth.substring(4)
|
val thirdvalue = fifth.substring(4)
|
||||||
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 same 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,8 +145,8 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
|||||||
val firstvalue = first.substring(4)
|
val firstvalue = first.substring(4)
|
||||||
val secondvalue = third.substring(4)
|
val secondvalue = third.substring(4)
|
||||||
if(firstvalue==secondvalue) {
|
if(firstvalue==secondvalue) {
|
||||||
// lda value / sta ? / lda same-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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,24 +165,20 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
|||||||
|
|
||||||
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 pairs of 2
|
|
||||||
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()
|
||||||
@ -151,26 +196,40 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
|
|||||||
val firstLoc = first.substring(4)
|
val firstLoc = first.substring(4)
|
||||||
val secondLoc = second.substring(4)
|
val secondLoc = second.substring(4)
|
||||||
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
|
||||||
}
|
}
|
@ -0,0 +1,509 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.FunctionCallStatement
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
import prog8.functions.FSignature
|
||||||
|
|
||||||
|
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature) {
|
||||||
|
translateFunctioncall(fcall, func, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
|
||||||
|
translateFunctioncall(fcall, func, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean) {
|
||||||
|
val functionName = fcall.target.nameInSource.last()
|
||||||
|
if (discardResult) {
|
||||||
|
if (func.pure)
|
||||||
|
return // can just ignore the whole function call altogether
|
||||||
|
else if (func.returntype != null)
|
||||||
|
throw AssemblyError("discarding result of non-pure function $fcall")
|
||||||
|
}
|
||||||
|
|
||||||
|
when (functionName) {
|
||||||
|
"msb" -> funcMsb(fcall)
|
||||||
|
"lsb" -> funcLsb(fcall)
|
||||||
|
"mkword" -> funcMkword(fcall, func)
|
||||||
|
"abs" -> funcAbs(fcall, func)
|
||||||
|
"swap" -> funcSwap(fcall)
|
||||||
|
"strlen" -> funcStrlen(fcall)
|
||||||
|
"min", "max", "sum" -> funcMinMaxSum(fcall, functionName)
|
||||||
|
"any", "all" -> funcAnyAll(fcall, functionName)
|
||||||
|
"sgn" -> funcSgn(fcall, func)
|
||||||
|
"sin", "cos", "tan", "atan",
|
||||||
|
"ln", "log2", "sqrt", "rad",
|
||||||
|
"deg", "round", "floor", "ceil",
|
||||||
|
"rdnf" -> funcVariousFloatFuncs(fcall, func, functionName)
|
||||||
|
"rol" -> funcRol(fcall)
|
||||||
|
"rol2" -> funcRol2(fcall)
|
||||||
|
"ror" -> funcRor(fcall)
|
||||||
|
"ror2" -> funcRor2(fcall)
|
||||||
|
"sort" -> funcSort(fcall)
|
||||||
|
"reverse" -> funcReverse(fcall)
|
||||||
|
"rsave" -> {
|
||||||
|
// save cpu status flag and all registers A, X, Y.
|
||||||
|
// see http://6502.org/tutorials/register_preservation.html
|
||||||
|
asmgen.out(" php | sta P8ZP_SCRATCH_REG | pha | txa | pha | tya | pha | lda P8ZP_SCRATCH_REG")
|
||||||
|
}
|
||||||
|
"rrestore" -> {
|
||||||
|
// restore all registers and cpu status flag
|
||||||
|
asmgen.out(" pla | tay | pla | tax | pla | plp")
|
||||||
|
}
|
||||||
|
"clear_carry" -> asmgen.out(" clc")
|
||||||
|
"set_carry" -> asmgen.out(" sec")
|
||||||
|
"clear_irqd" -> asmgen.out(" cli")
|
||||||
|
"set_irqd" -> asmgen.out(" sei")
|
||||||
|
else -> {
|
||||||
|
translateFunctionArguments(fcall.args, func)
|
||||||
|
asmgen.out(" jsr prog8_lib.func_$functionName")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcReverse(fcall: IFunctionCall) {
|
||||||
|
val variable = fcall.args.single()
|
||||||
|
if (variable is IdentifierReference) {
|
||||||
|
val decl = variable.targetVarDecl(program.namespace)!!
|
||||||
|
val varName = asmgen.asmVariableName(variable)
|
||||||
|
val numElements = decl.arraysize!!.constIndex()
|
||||||
|
when (decl.datatype) {
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda #$numElements
|
||||||
|
jsr prog8_lib.reverse_b
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda #$numElements
|
||||||
|
jsr prog8_lib.reverse_w
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda #$numElements
|
||||||
|
jsr prog8_lib.reverse_f
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcSort(fcall: IFunctionCall) {
|
||||||
|
val variable = fcall.args.single()
|
||||||
|
if (variable is IdentifierReference) {
|
||||||
|
val decl = variable.targetVarDecl(program.namespace)!!
|
||||||
|
val varName = asmgen.asmVariableName(variable)
|
||||||
|
val numElements = decl.arraysize!!.constIndex()
|
||||||
|
when (decl.datatype) {
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda #$numElements
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
""")
|
||||||
|
asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda #$numElements
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
""")
|
||||||
|
asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcRor2(fcall: IFunctionCall) {
|
||||||
|
val what = fcall.args.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror2_array_ub")
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmVariableName(what)
|
||||||
|
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror2_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmVariableName(what)
|
||||||
|
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcRor(fcall: IFunctionCall) {
|
||||||
|
val what = fcall.args.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror_array_ub")
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" ror ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta (+) + 1
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta (+) + 2
|
||||||
|
+ ror ${'$'}ffff ; modified
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmVariableName(what)
|
||||||
|
asmgen.out(" ror $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmVariableName(what)
|
||||||
|
asmgen.out(" ror $variable+1 | ror $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcRol2(fcall: IFunctionCall) {
|
||||||
|
val what = fcall.args.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol2_array_ub")
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmVariableName(what)
|
||||||
|
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol2_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmVariableName(what)
|
||||||
|
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcRol(fcall: IFunctionCall) {
|
||||||
|
val what = fcall.args.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol_array_ub")
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" rol ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta (+) + 1
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta (+) + 2
|
||||||
|
+ rol ${'$'}ffff ; modified
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmVariableName(what)
|
||||||
|
asmgen.out(" rol $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmVariableName(what)
|
||||||
|
asmgen.out(" rol $variable | rol $variable+1")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, functionName: String) {
|
||||||
|
translateFunctionArguments(fcall.args, func)
|
||||||
|
asmgen.out(" jsr c64flt.func_$functionName")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcSgn(fcall: IFunctionCall, func: FSignature) {
|
||||||
|
translateFunctionArguments(fcall.args, func)
|
||||||
|
val dt = fcall.args.single().inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> asmgen.out(" jsr math.sign_ub")
|
||||||
|
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
|
||||||
|
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
|
||||||
|
DataType.WORD -> asmgen.out(" jsr math.sign_w")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.sign_f")
|
||||||
|
else -> throw AssemblyError("weird type $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcAnyAll(fcall: IFunctionCall, functionName: String) {
|
||||||
|
outputPushAddressAndLenghtOfArray(fcall.args[0])
|
||||||
|
val dt = fcall.args.single().inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
||||||
|
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
|
||||||
|
else -> throw AssemblyError("weird type $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcMinMaxSum(fcall: IFunctionCall, functionName: String) {
|
||||||
|
outputPushAddressAndLenghtOfArray(fcall.args[0])
|
||||||
|
val dt = fcall.args.single().inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
|
||||||
|
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
||||||
|
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
|
||||||
|
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
||||||
|
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
|
||||||
|
else -> throw AssemblyError("weird type $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcStrlen(fcall: IFunctionCall) {
|
||||||
|
val name = asmgen.asmVariableName(fcall.args[0] as IdentifierReference)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$name
|
||||||
|
ldy #>$name
|
||||||
|
jsr prog8_lib.strlen
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex""")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcSwap(fcall: IFunctionCall) {
|
||||||
|
val first = fcall.args[0]
|
||||||
|
val second = fcall.args[1]
|
||||||
|
if(first is IdentifierReference && second is IdentifierReference) {
|
||||||
|
val firstName = asmgen.asmVariableName(first)
|
||||||
|
val secondName = asmgen.asmVariableName(second)
|
||||||
|
val dt = first.inferType(program)
|
||||||
|
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) {
|
||||||
|
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | tya | sta $secondName")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
|
||||||
|
asmgen.out("""
|
||||||
|
ldy $firstName
|
||||||
|
lda $secondName
|
||||||
|
sta $firstName
|
||||||
|
sty $secondName
|
||||||
|
ldy $firstName+1
|
||||||
|
lda $secondName+1
|
||||||
|
sta $firstName+1
|
||||||
|
sty $secondName+1
|
||||||
|
""")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(dt.istype(DataType.FLOAT)) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$firstName
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda #>$firstName
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
lda #<$secondName
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
lda #>$secondName
|
||||||
|
sta P8ZP_SCRATCH_W2+1
|
||||||
|
jsr c64flt.swap_floats
|
||||||
|
""")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// other types of swap() calls should have been replaced by a different statement sequence involving a temp variable
|
||||||
|
throw AssemblyError("no asm generation for swap funccall $fcall")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
|
||||||
|
translateFunctionArguments(fcall.args, func)
|
||||||
|
val dt = fcall.args.single().inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcMkword(fcall: IFunctionCall, func: FSignature) {
|
||||||
|
// trick: push the args in reverse order (msb first, lsb second) this saves some instructions
|
||||||
|
asmgen.translateExpression(fcall.args[1])
|
||||||
|
asmgen.translateExpression(fcall.args[0])
|
||||||
|
asmgen.out(" inx | lda P8ESTACK_LO,x | sta P8ESTACK_HI+1,x")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcMsb(fcall: IFunctionCall) {
|
||||||
|
val arg = fcall.args.single()
|
||||||
|
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
|
||||||
|
throw AssemblyError("msb required word argument")
|
||||||
|
if (arg is NumericLiteralValue)
|
||||||
|
throw AssemblyError("msb(const) should have been const-folded away")
|
||||||
|
if (arg is IdentifierReference) {
|
||||||
|
val sourceName = asmgen.asmVariableName(arg)
|
||||||
|
asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(arg)
|
||||||
|
asmgen.out(" lda P8ESTACK_HI+1,x | sta P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcLsb(fcall: IFunctionCall) {
|
||||||
|
val arg = fcall.args.single()
|
||||||
|
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
|
||||||
|
throw AssemblyError("lsb required word argument")
|
||||||
|
if (arg is NumericLiteralValue)
|
||||||
|
throw AssemblyError("lsb(const) should have been const-folded away")
|
||||||
|
if (arg is IdentifierReference) {
|
||||||
|
val sourceName = asmgen.asmVariableName(arg)
|
||||||
|
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(arg)
|
||||||
|
// just ignore any high-byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
|
||||||
|
arg as IdentifierReference
|
||||||
|
val identifierName = asmgen.asmVariableName(arg)
|
||||||
|
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.constIndex()!!
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$identifierName
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
lda #>$identifierName
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex
|
||||||
|
lda #$size
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FSignature) {
|
||||||
|
args.forEach {
|
||||||
|
asmgen.translateExpression(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,514 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
import prog8.functions.BuiltinFunctions
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
internal fun translateExpression(expression: Expression) {
|
||||||
|
when(expression) {
|
||||||
|
is PrefixExpression -> translateExpression(expression)
|
||||||
|
is BinaryExpression -> translateExpression(expression)
|
||||||
|
is ArrayIndexedExpression -> translateExpression(expression)
|
||||||
|
is TypecastExpression -> translateExpression(expression)
|
||||||
|
is AddressOf -> translateExpression(expression)
|
||||||
|
is DirectMemoryRead -> translateExpression(expression)
|
||||||
|
is NumericLiteralValue -> translateExpression(expression)
|
||||||
|
is IdentifierReference -> translateExpression(expression)
|
||||||
|
is FunctionCall -> translateExpression(expression)
|
||||||
|
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
||||||
|
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expression: FunctionCall) {
|
||||||
|
val functionName = expression.target.nameInSource.last()
|
||||||
|
val builtinFunc = BuiltinFunctions[functionName]
|
||||||
|
if (builtinFunc != null) {
|
||||||
|
asmgen.translateFunctioncallExpression(expression, builtinFunc)
|
||||||
|
} else {
|
||||||
|
val sub = expression.target.targetSubroutine(program.namespace)!!
|
||||||
|
asmgen.translateFunctionCall(expression)
|
||||||
|
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||||
|
for ((_, reg) in returns) {
|
||||||
|
if (!reg.stack) {
|
||||||
|
// result value in cpu or status registers, put it on the stack
|
||||||
|
if (reg.registerOrPair != null) {
|
||||||
|
when (reg.registerOrPair) {
|
||||||
|
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||||
|
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||||
|
RegisterOrPair.X -> {
|
||||||
|
// return value in X register has been discarded, just push a zero
|
||||||
|
asmgen.out(" lda #0 | sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
RegisterOrPair.AX -> {
|
||||||
|
// return value in X register has been discarded, just push a zero in this place
|
||||||
|
asmgen.out(" sta P8ESTACK_LO,x | lda #0 | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
RegisterOrPair.XY -> {
|
||||||
|
// return value in X register has been discarded, just push a zero in this place
|
||||||
|
asmgen.out(" lda #0 | sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: TypecastExpression) {
|
||||||
|
translateExpression(expr.expression)
|
||||||
|
when(expr.expression.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {}
|
||||||
|
DataType.UWORD, DataType.WORD -> asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_ub2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
// sign extend
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
ora #$7f
|
||||||
|
bmi +
|
||||||
|
lda #0
|
||||||
|
+ sta P8ESTACK_HI+1,x""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_b2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.BYTE, DataType.UBYTE -> {}
|
||||||
|
DataType.WORD, DataType.UWORD -> {}
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_uw2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.BYTE, DataType.UBYTE -> {}
|
||||||
|
DataType.WORD, DataType.UWORD -> {}
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_w2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.UBYTE -> asmgen.out(" jsr c64flt.stack_float2uw")
|
||||||
|
DataType.BYTE -> asmgen.out(" jsr c64flt.stack_float2w")
|
||||||
|
DataType.UWORD -> asmgen.out(" jsr c64flt.stack_float2uw")
|
||||||
|
DataType.WORD -> asmgen.out(" jsr c64flt.stack_float2w")
|
||||||
|
DataType.FLOAT -> {}
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: AddressOf) {
|
||||||
|
val name = asmgen.asmVariableName(expr.identifier)
|
||||||
|
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: DirectMemoryRead) {
|
||||||
|
when(expr.addressExpression) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val address = (expr.addressExpression as NumericLiteralValue).number.toInt()
|
||||||
|
asmgen.out(" lda ${address.toHex()} | sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
// the identifier is a pointer variable, so read the value from the address in it
|
||||||
|
asmgen.loadByteFromPointerIntoA(expr.addressExpression as IdentifierReference)
|
||||||
|
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
translateExpression(expr.addressExpression)
|
||||||
|
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack")
|
||||||
|
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: NumericLiteralValue) {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
|
||||||
|
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
||||||
|
lda #<${expr.number.toHex()}
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
lda #>${expr.number.toHex()}
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex
|
||||||
|
""")
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
val floatConst = asmgen.getFloatAsmConst(expr.number.toDouble())
|
||||||
|
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: IdentifierReference) {
|
||||||
|
val varname = asmgen.asmVariableName(expr)
|
||||||
|
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
|
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | lda $varname+1 | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out(" lda #<$varname | ldy #>$varname| jsr c64flt.push_float")
|
||||||
|
}
|
||||||
|
in IterableDatatypes -> {
|
||||||
|
asmgen.out(" lda #<$varname | sta P8ESTACK_LO,x | lda #>$varname | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("stack push weird variable type $expr")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40)
|
||||||
|
private val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40)
|
||||||
|
|
||||||
|
private fun translateExpression(expr: BinaryExpression) {
|
||||||
|
val leftIDt = expr.left.inferType(program)
|
||||||
|
val rightIDt = expr.right.inferType(program)
|
||||||
|
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||||
|
throw AssemblyError("can't infer type of both expression operands")
|
||||||
|
|
||||||
|
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
// see if we can apply some optimized routines
|
||||||
|
when(expr.operator) {
|
||||||
|
">>" -> {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||||
|
if(amount!=null) {
|
||||||
|
when (leftDt) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
if (amount <= 2)
|
||||||
|
repeat(amount) { asmgen.out(" lsr P8ESTACK_LO+1,x") }
|
||||||
|
else {
|
||||||
|
asmgen.out(" lda P8ESTACK_LO+1,x")
|
||||||
|
repeat(amount) { asmgen.out(" lsr a") }
|
||||||
|
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
if (amount <= 2)
|
||||||
|
repeat(amount) { asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x") }
|
||||||
|
else {
|
||||||
|
asmgen.out(" lda P8ESTACK_LO+1,x | sta P8ZP_SCRATCH_B1")
|
||||||
|
repeat(amount) { asmgen.out(" asl a | ror P8ZP_SCRATCH_B1 | lda P8ZP_SCRATCH_B1") }
|
||||||
|
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
var left = amount
|
||||||
|
while (left >= 7) {
|
||||||
|
asmgen.out(" jsr math.shift_right_uw_7")
|
||||||
|
left -= 7
|
||||||
|
}
|
||||||
|
if (left in 0..2)
|
||||||
|
repeat(left) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||||
|
else
|
||||||
|
asmgen.out(" jsr math.shift_right_uw_$left")
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
var left = amount
|
||||||
|
while (left >= 7) {
|
||||||
|
asmgen.out(" jsr math.shift_right_w_7")
|
||||||
|
left -= 7
|
||||||
|
}
|
||||||
|
if (left in 0..2)
|
||||||
|
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||||
|
else
|
||||||
|
asmgen.out(" jsr math.shift_right_w_$left")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"<<" -> {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||||
|
if(amount!=null) {
|
||||||
|
if (leftDt in ByteDatatypes) {
|
||||||
|
if (amount <= 2)
|
||||||
|
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
|
||||||
|
else {
|
||||||
|
asmgen.out(" lda P8ESTACK_LO+1,x")
|
||||||
|
repeat(amount) { asmgen.out(" asl a") }
|
||||||
|
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var left = amount
|
||||||
|
while (left >= 7) {
|
||||||
|
asmgen.out(" jsr math.shift_left_w_7")
|
||||||
|
left -= 7
|
||||||
|
}
|
||||||
|
if (left in 0..2)
|
||||||
|
repeat(left) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
|
||||||
|
else
|
||||||
|
asmgen.out(" jsr math.shift_left_w_$left")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"*" -> {
|
||||||
|
val value = expr.right.constValue(program)
|
||||||
|
if(value!=null) {
|
||||||
|
if(rightDt in IntegerDatatypes) {
|
||||||
|
val amount = value.number.toInt()
|
||||||
|
when(rightDt) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
if(amount in optimizedByteMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr math.mul_byte_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
if(amount in optimizedByteMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr math.mul_byte_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(amount.absoluteValue in optimizedByteMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr prog8_lib.neg_b | jsr math.mul_byte_${amount.absoluteValue}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
if(amount in optimizedWordMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr math.mul_word_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
if(amount in optimizedWordMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr math.mul_word_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(amount.absoluteValue in optimizedWordMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr prog8_lib.neg_w | jsr math.mul_word_${amount.absoluteValue}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the general, non-optimized cases
|
||||||
|
translateExpression(expr.left)
|
||||||
|
translateExpression(expr.right)
|
||||||
|
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
||||||
|
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
|
||||||
|
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
|
||||||
|
|
||||||
|
when (leftDt) {
|
||||||
|
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
||||||
|
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
||||||
|
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
|
||||||
|
else -> throw AssemblyError("non-numerical datatype")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: PrefixExpression) {
|
||||||
|
translateExpression(expr.expression)
|
||||||
|
val type = expr.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
when(expr.operator) {
|
||||||
|
"+" -> {}
|
||||||
|
"-" -> {
|
||||||
|
when(type) {
|
||||||
|
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.neg_f")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"~" -> {
|
||||||
|
when(type) {
|
||||||
|
in ByteDatatypes ->
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
eor #255
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
""")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"not" -> {
|
||||||
|
when(type) {
|
||||||
|
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
|
||||||
|
val index = arrayExpr.arrayspec.index
|
||||||
|
val elementDt = arrayExpr.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
val arrayVarName = asmgen.asmVariableName(arrayExpr.identifier)
|
||||||
|
if(index is NumericLiteralValue) {
|
||||||
|
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||||
|
when(elementDt) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird element type")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when(elementDt) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||||
|
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||||
|
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | lda $arrayVarName+1,y | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.A)
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #>$arrayVarName
|
||||||
|
clc
|
||||||
|
adc #<$arrayVarName
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jsr c64flt.push_float""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird dt")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
|
||||||
|
when(operator) {
|
||||||
|
"**" -> throw AssemblyError("** operator requires floats")
|
||||||
|
"*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
|
||||||
|
"/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
|
||||||
|
"%" -> {
|
||||||
|
if(types==DataType.BYTE)
|
||||||
|
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||||
|
asmgen.out(" jsr prog8_lib.remainder_ub")
|
||||||
|
}
|
||||||
|
"+" -> asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
clc
|
||||||
|
adc P8ESTACK_LO+1,x
|
||||||
|
inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
""")
|
||||||
|
"-" -> asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
sec
|
||||||
|
sbc P8ESTACK_LO+1,x
|
||||||
|
inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
""")
|
||||||
|
"<<" -> asmgen.out(" jsr prog8_lib.shiftleft_b")
|
||||||
|
">>" -> asmgen.out(" jsr prog8_lib.shiftright_b")
|
||||||
|
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
|
||||||
|
">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
|
||||||
|
"<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
|
||||||
|
">=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b")
|
||||||
|
"==" -> asmgen.out(" jsr prog8_lib.equal_b")
|
||||||
|
"!=" -> asmgen.out(" jsr prog8_lib.notequal_b")
|
||||||
|
"&" -> asmgen.out(" jsr prog8_lib.bitand_b")
|
||||||
|
"^" -> asmgen.out(" jsr prog8_lib.bitxor_b")
|
||||||
|
"|" -> asmgen.out(" jsr prog8_lib.bitor_b")
|
||||||
|
"and" -> asmgen.out(" jsr prog8_lib.and_b")
|
||||||
|
"or" -> asmgen.out(" jsr prog8_lib.or_b")
|
||||||
|
"xor" -> asmgen.out(" jsr prog8_lib.xor_b")
|
||||||
|
else -> throw AssemblyError("invalid operator $operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateBinaryOperatorWords(operator: String, types: DataType) {
|
||||||
|
when(operator) {
|
||||||
|
"**" -> throw AssemblyError("** operator requires floats")
|
||||||
|
"*" -> asmgen.out(" jsr prog8_lib.mul_word")
|
||||||
|
"/" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
|
||||||
|
"%" -> {
|
||||||
|
if(types==DataType.WORD)
|
||||||
|
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||||
|
asmgen.out(" jsr prog8_lib.remainder_uw")
|
||||||
|
}
|
||||||
|
"+" -> asmgen.out(" jsr prog8_lib.add_w")
|
||||||
|
"-" -> asmgen.out(" jsr prog8_lib.sub_w")
|
||||||
|
"<<" -> throw AssemblyError("<< should not operate via stack")
|
||||||
|
">>" -> throw AssemblyError(">> should not operate via stack")
|
||||||
|
"<" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w")
|
||||||
|
">" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w")
|
||||||
|
"<=" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
|
||||||
|
">=" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
|
||||||
|
"==" -> asmgen.out(" jsr prog8_lib.equal_w")
|
||||||
|
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w")
|
||||||
|
"&" -> asmgen.out(" jsr prog8_lib.bitand_w")
|
||||||
|
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
|
||||||
|
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
|
||||||
|
"and" -> asmgen.out(" jsr prog8_lib.and_w")
|
||||||
|
"or" -> asmgen.out(" jsr prog8_lib.or_w")
|
||||||
|
"xor" -> asmgen.out(" jsr prog8_lib.xor_w")
|
||||||
|
else -> throw AssemblyError("invalid operator $operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateBinaryOperatorFloats(operator: String) {
|
||||||
|
when(operator) {
|
||||||
|
"**" -> asmgen.out(" jsr c64flt.pow_f")
|
||||||
|
"*" -> asmgen.out(" jsr c64flt.mul_f")
|
||||||
|
"/" -> asmgen.out(" jsr c64flt.div_f")
|
||||||
|
"+" -> asmgen.out(" jsr c64flt.add_f")
|
||||||
|
"-" -> asmgen.out(" jsr c64flt.sub_f")
|
||||||
|
"<" -> asmgen.out(" jsr c64flt.less_f")
|
||||||
|
">" -> asmgen.out(" jsr c64flt.greater_f")
|
||||||
|
"<=" -> asmgen.out(" jsr c64flt.lesseq_f")
|
||||||
|
">=" -> asmgen.out(" jsr c64flt.greatereq_f")
|
||||||
|
"==" -> asmgen.out(" jsr c64flt.equal_f")
|
||||||
|
"!=" -> asmgen.out(" jsr c64flt.notequal_f")
|
||||||
|
"%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype")
|
||||||
|
else -> throw AssemblyError("invalid operator $operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
618
compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt
Normal file
618
compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt
Normal file
@ -0,0 +1,618 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.expressions.RangeExpr
|
||||||
|
import prog8.ast.statements.ForLoop
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
|
||||||
|
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
|
||||||
|
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
|
||||||
|
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
internal fun translate(stmt: ForLoop) {
|
||||||
|
val iterableDt = stmt.iterable.inferType(program)
|
||||||
|
if(!iterableDt.isKnown)
|
||||||
|
throw AssemblyError("can't determine iterable dt")
|
||||||
|
when(stmt.iterable) {
|
||||||
|
is RangeExpr -> {
|
||||||
|
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||||
|
if(range==null) {
|
||||||
|
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr)
|
||||||
|
} else {
|
||||||
|
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) {
|
||||||
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
|
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||||
|
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
|
||||||
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
|
val stepsize=range.step.constValue(program)!!.number.toInt()
|
||||||
|
when(iterableDt) {
|
||||||
|
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||||
|
if (stepsize==1 || stepsize==-1) {
|
||||||
|
|
||||||
|
// bytes, step 1 or -1
|
||||||
|
|
||||||
|
val incdec = if(stepsize==1) "inc" else "dec"
|
||||||
|
// loop over byte range via loopvar
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
asmgen.translateExpression(range.from)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta $varname
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
sta $modifiedLabel+1
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
$modifiedLabel cmp #0 ; modified
|
||||||
|
beq $endLabel
|
||||||
|
$incdec $varname
|
||||||
|
jmp $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// bytes, step >= 2 or <= -2
|
||||||
|
|
||||||
|
// loop over byte range via loopvar
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
asmgen.translateExpression(range.from)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta $varname
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
sta $modifiedLabel+1
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname""")
|
||||||
|
if(stepsize>0) {
|
||||||
|
asmgen.out("""
|
||||||
|
clc
|
||||||
|
adc #$stepsize
|
||||||
|
sta $varname
|
||||||
|
$modifiedLabel cmp #0 ; modified
|
||||||
|
bcc $loopLabel
|
||||||
|
beq $loopLabel""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
sec
|
||||||
|
sbc #${stepsize.absoluteValue}
|
||||||
|
sta $varname
|
||||||
|
$modifiedLabel cmp #0 ; modified
|
||||||
|
bcs $loopLabel""")
|
||||||
|
}
|
||||||
|
asmgen.out("""
|
||||||
|
$endLabel inx""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
|
when {
|
||||||
|
|
||||||
|
// words, step 1 or -1
|
||||||
|
|
||||||
|
stepsize == 1 || stepsize == -1 -> {
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
assignLoopvar(stmt, range)
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
sta $modifiedLabel+1
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
sta $modifiedLabel2+1
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname+1
|
||||||
|
$modifiedLabel cmp #0 ; modified
|
||||||
|
bne +
|
||||||
|
lda $varname
|
||||||
|
$modifiedLabel2 cmp #0 ; modified
|
||||||
|
beq $endLabel""")
|
||||||
|
if(stepsize==1) {
|
||||||
|
asmgen.out("""
|
||||||
|
+ inc $varname
|
||||||
|
bne $loopLabel
|
||||||
|
inc $varname+1
|
||||||
|
jmp $loopLabel
|
||||||
|
""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
+ lda $varname
|
||||||
|
bne +
|
||||||
|
dec $varname+1
|
||||||
|
+ dec $varname
|
||||||
|
jmp $loopLabel""")
|
||||||
|
}
|
||||||
|
asmgen.out(endLabel)
|
||||||
|
asmgen.out(" inx")
|
||||||
|
}
|
||||||
|
stepsize > 0 -> {
|
||||||
|
|
||||||
|
// (u)words, step >= 2
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
sta $modifiedLabel+1
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
sta $modifiedLabel2+1
|
||||||
|
""")
|
||||||
|
assignLoopvar(stmt, range)
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.out(loopLabel)
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
|
||||||
|
if (iterableDt == DataType.ARRAY_UW) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
clc
|
||||||
|
adc #<$stepsize
|
||||||
|
sta $varname
|
||||||
|
lda $varname+1
|
||||||
|
adc #>$stepsize
|
||||||
|
sta $varname+1
|
||||||
|
$modifiedLabel cmp #0 ; modified
|
||||||
|
bcc $loopLabel
|
||||||
|
bne $endLabel
|
||||||
|
$modifiedLabel2 lda #0 ; modified
|
||||||
|
cmp $varname
|
||||||
|
bcc $endLabel
|
||||||
|
bcs $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
clc
|
||||||
|
adc #<$stepsize
|
||||||
|
sta $varname
|
||||||
|
lda $varname+1
|
||||||
|
adc #>$stepsize
|
||||||
|
sta $varname+1
|
||||||
|
$modifiedLabel2 lda #0 ; modified
|
||||||
|
cmp $varname
|
||||||
|
$modifiedLabel lda #0 ; modified
|
||||||
|
sbc $varname+1
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bpl $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
|
||||||
|
// (u)words, step <= -2
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
sta $modifiedLabel+1
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
sta $modifiedLabel2+1
|
||||||
|
""")
|
||||||
|
assignLoopvar(stmt, range)
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.out(loopLabel)
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
|
||||||
|
if(iterableDt==DataType.ARRAY_UW) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
sec
|
||||||
|
sbc #<${stepsize.absoluteValue}
|
||||||
|
sta $varname
|
||||||
|
lda $varname+1
|
||||||
|
sbc #>${stepsize.absoluteValue}
|
||||||
|
sta $varname+1
|
||||||
|
$modifiedLabel cmp #0 ; modified
|
||||||
|
bcc $endLabel
|
||||||
|
bne $loopLabel
|
||||||
|
lda $varname
|
||||||
|
$modifiedLabel2 cmp #0 ; modified
|
||||||
|
bcs $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
sec
|
||||||
|
sbc #<${stepsize.absoluteValue}
|
||||||
|
sta $varname
|
||||||
|
pha
|
||||||
|
lda $varname+1
|
||||||
|
sbc #>${stepsize.absoluteValue}
|
||||||
|
sta $varname+1
|
||||||
|
pla
|
||||||
|
$modifiedLabel2 cmp #0 ; modified
|
||||||
|
lda $varname+1
|
||||||
|
$modifiedLabel sbc #0 ; modified
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bpl $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("range expression can only be byte or word")
|
||||||
|
}
|
||||||
|
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) {
|
||||||
|
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), variable=stmt.loopVar)
|
||||||
|
val src = AsmAssignSource.fromAstSource(range.from, program).adjustDataTypeToTarget(target)
|
||||||
|
val assign = AsmAssignment(src, target, false, range.position)
|
||||||
|
asmgen.translateNormalAssignment(assign)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
|
||||||
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
|
val iterableName = asmgen.asmVariableName(ident)
|
||||||
|
val decl = ident.targetVarDecl(program.namespace)!!
|
||||||
|
when(iterableDt) {
|
||||||
|
DataType.STR -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$iterableName
|
||||||
|
ldy #>$iterableName
|
||||||
|
sta $loopLabel+1
|
||||||
|
sty $loopLabel+2
|
||||||
|
$loopLabel lda ${65535.toHex()} ; modified
|
||||||
|
beq $endLabel
|
||||||
|
sta ${asmgen.asmVariableName(stmt.loopVar)}""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
inc $loopLabel+1
|
||||||
|
bne $loopLabel
|
||||||
|
inc $loopLabel+2
|
||||||
|
bne $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
|
val length = decl.arraysize!!.constIndex()!!
|
||||||
|
val indexVar = asmgen.makeLabel("for_index")
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #0
|
||||||
|
$loopLabel sty $indexVar
|
||||||
|
lda $iterableName,y
|
||||||
|
sta ${asmgen.asmVariableName(stmt.loopVar)}""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
if(length<=255) {
|
||||||
|
asmgen.out("""
|
||||||
|
ldy $indexVar
|
||||||
|
iny
|
||||||
|
cpy #$length
|
||||||
|
beq $endLabel
|
||||||
|
bne $loopLabel""")
|
||||||
|
} else {
|
||||||
|
// length is 256
|
||||||
|
asmgen.out("""
|
||||||
|
ldy $indexVar
|
||||||
|
iny
|
||||||
|
bne $loopLabel
|
||||||
|
beq $endLabel""")
|
||||||
|
}
|
||||||
|
if(length>=16 && asmgen.zeropage.available() > 0) {
|
||||||
|
// allocate index var on ZP
|
||||||
|
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||||
|
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
$indexVar .byte 0""")
|
||||||
|
}
|
||||||
|
asmgen.out(endLabel)
|
||||||
|
}
|
||||||
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
|
val length = decl.arraysize!!.constIndex()!! * 2
|
||||||
|
val indexVar = asmgen.makeLabel("for_index")
|
||||||
|
val loopvarName = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #0
|
||||||
|
$loopLabel sty $indexVar
|
||||||
|
lda $iterableName,y
|
||||||
|
sta $loopvarName
|
||||||
|
lda $iterableName+1,y
|
||||||
|
sta $loopvarName+1""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
if(length<=127) {
|
||||||
|
asmgen.out("""
|
||||||
|
ldy $indexVar
|
||||||
|
iny
|
||||||
|
iny
|
||||||
|
cpy #$length
|
||||||
|
beq $endLabel
|
||||||
|
bne $loopLabel""")
|
||||||
|
} else {
|
||||||
|
// length is 128 words, 256 bytes
|
||||||
|
asmgen.out("""
|
||||||
|
ldy $indexVar
|
||||||
|
iny
|
||||||
|
iny
|
||||||
|
bne $loopLabel
|
||||||
|
beq $endLabel""")
|
||||||
|
}
|
||||||
|
if(length>=16 && asmgen.zeropage.available() > 0) {
|
||||||
|
// allocate index var on ZP
|
||||||
|
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||||
|
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
$indexVar .byte 0""")
|
||||||
|
}
|
||||||
|
asmgen.out(endLabel)
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> {
|
||||||
|
throw AssemblyError("for loop with floating point variables is not supported")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("can't iterate over $iterableDt")
|
||||||
|
}
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
|
||||||
|
if (range.isEmpty() || range.step==0)
|
||||||
|
throw AssemblyError("empty range or step 0")
|
||||||
|
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
|
||||||
|
if(range.step==1 && range.last>range.first) return translateForSimpleByteRangeAsc(stmt, range)
|
||||||
|
if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range)
|
||||||
|
}
|
||||||
|
else if(iterableDt==DataType.ARRAY_W || iterableDt==DataType.ARRAY_UW) {
|
||||||
|
if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range)
|
||||||
|
if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range)
|
||||||
|
}
|
||||||
|
|
||||||
|
// not one of the easy cases, generate more complex code...
|
||||||
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
|
when(iterableDt) {
|
||||||
|
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||||
|
// loop over byte range via loopvar, step >= 2 or <= -2
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${range.first}
|
||||||
|
sta $varname
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
when (range.step) {
|
||||||
|
0, 1, -1 -> {
|
||||||
|
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
if(range.last==255) {
|
||||||
|
asmgen.out("""
|
||||||
|
inc $varname
|
||||||
|
beq $endLabel
|
||||||
|
inc $varname
|
||||||
|
bne $loopLabel""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
cmp #${range.last}
|
||||||
|
beq $endLabel
|
||||||
|
inc $varname
|
||||||
|
inc $varname
|
||||||
|
jmp $loopLabel""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-2 -> {
|
||||||
|
when (range.last) {
|
||||||
|
0 -> asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
beq $endLabel
|
||||||
|
dec $varname
|
||||||
|
dec $varname
|
||||||
|
jmp $loopLabel""")
|
||||||
|
1 -> asmgen.out("""
|
||||||
|
dec $varname
|
||||||
|
beq $endLabel
|
||||||
|
dec $varname
|
||||||
|
bne $loopLabel""")
|
||||||
|
else -> asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
cmp #${range.last}
|
||||||
|
beq $endLabel
|
||||||
|
dec $varname
|
||||||
|
dec $varname
|
||||||
|
jmp $loopLabel""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// step <= -3 or >= 3
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
cmp #${range.last}
|
||||||
|
beq $endLabel
|
||||||
|
clc
|
||||||
|
adc #${range.step}
|
||||||
|
sta $varname
|
||||||
|
jmp $loopLabel""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
asmgen.out(endLabel)
|
||||||
|
}
|
||||||
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
|
// loop over word range via loopvar, step >= 2 or <= -2
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
when (range.step) {
|
||||||
|
0, 1, -1 -> {
|
||||||
|
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// word, step >= 2 or <= -2
|
||||||
|
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${range.first}
|
||||||
|
ldy #>${range.first}
|
||||||
|
sta $varname
|
||||||
|
sty $varname+1
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
cmp #<${range.last}
|
||||||
|
bne +
|
||||||
|
lda $varname+1
|
||||||
|
cmp #>${range.last}
|
||||||
|
bne +
|
||||||
|
beq $endLabel
|
||||||
|
+ lda $varname
|
||||||
|
clc
|
||||||
|
adc #<${range.step}
|
||||||
|
sta $varname
|
||||||
|
lda $varname+1
|
||||||
|
adc #>${range.step}
|
||||||
|
sta $varname+1
|
||||||
|
jmp $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("range expression can only be byte or word")
|
||||||
|
}
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateForSimpleByteRangeAsc(stmt: ForLoop, range: IntProgression) {
|
||||||
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${range.first}
|
||||||
|
sta $varname
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
if (range.last == 255) {
|
||||||
|
asmgen.out("""
|
||||||
|
inc $varname
|
||||||
|
bne $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
cmp #${range.last}
|
||||||
|
beq $endLabel
|
||||||
|
inc $varname
|
||||||
|
jmp $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateForSimpleByteRangeDesc(stmt: ForLoop, range: IntProgression) {
|
||||||
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${range.first}
|
||||||
|
sta $varname
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
when (range.last) {
|
||||||
|
0 -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
beq $endLabel
|
||||||
|
dec $varname
|
||||||
|
jmp $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
asmgen.out("""
|
||||||
|
dec $varname
|
||||||
|
jmp $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
cmp #${range.last}
|
||||||
|
beq $endLabel
|
||||||
|
dec $varname
|
||||||
|
jmp $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateForSimpleWordRangeAsc(stmt: ForLoop, range: IntProgression) {
|
||||||
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${range.first}
|
||||||
|
ldy #>${range.first}
|
||||||
|
sta $varname
|
||||||
|
sty $varname+1
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
cmp #<${range.last}
|
||||||
|
bne +
|
||||||
|
lda $varname+1
|
||||||
|
cmp #>${range.last}
|
||||||
|
bne +
|
||||||
|
beq $endLabel
|
||||||
|
+ inc $varname
|
||||||
|
bne $loopLabel
|
||||||
|
inc $varname+1
|
||||||
|
jmp $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateForSimpleWordRangeDesc(stmt: ForLoop, range: IntProgression) {
|
||||||
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${range.first}
|
||||||
|
ldy #>${range.first}
|
||||||
|
sta $varname
|
||||||
|
sty $varname+1
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
cmp #<${range.last}
|
||||||
|
bne +
|
||||||
|
lda $varname+1
|
||||||
|
cmp #>${range.last}
|
||||||
|
bne +
|
||||||
|
beq $endLabel
|
||||||
|
+ lda $varname
|
||||||
|
bne +
|
||||||
|
dec $varname+1
|
||||||
|
+ dec $varname
|
||||||
|
jmp $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,202 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
|
import prog8.ast.statements.SubroutineParameter
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.target.c64.codegen.assignment.*
|
||||||
|
|
||||||
|
|
||||||
|
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
||||||
|
// output the code to setup the parameters and perform the actual call
|
||||||
|
// 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 saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult()
|
||||||
|
if(saveX)
|
||||||
|
asmgen.out(" stx P8ZP_SCRATCH_REG_X") // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
||||||
|
|
||||||
|
val subName = asmgen.asmSymbolName(stmt.target)
|
||||||
|
if(stmt.args.isNotEmpty()) {
|
||||||
|
if(sub.asmParameterRegisters.isEmpty()) {
|
||||||
|
// 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, sub.parameters.withIndex().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")
|
||||||
|
|
||||||
|
if(saveX)
|
||||||
|
asmgen.out(" ldx P8ZP_SCRATCH_REG_X") // restore X again
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||||
|
// this is called when one or more of the arguments are 'complex' and
|
||||||
|
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||||
|
for (arg in stmt.args.reversed())
|
||||||
|
asmgen.translateExpression(arg)
|
||||||
|
for (regparam in sub.asmParameterRegisters) {
|
||||||
|
when {
|
||||||
|
regparam.statusflag==Statusflag.Pc -> {
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
pha
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
bcs ++
|
||||||
|
+ clc
|
||||||
|
+ pla""")
|
||||||
|
}
|
||||||
|
regparam.statusflag!=null -> {
|
||||||
|
throw AssemblyError("can only use Carry as status flag parameter")
|
||||||
|
}
|
||||||
|
regparam.registerOrPair!=null -> {
|
||||||
|
val tgt = AsmAssignTarget.fromRegisters(regparam.registerOrPair, program, asmgen)
|
||||||
|
val source = AsmAssignSource(SourceStorageKind.STACK, program, tgt.datatype)
|
||||||
|
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
|
||||||
|
asmgen.translateNormalAssignment(asgn)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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("arg type unknown")
|
||||||
|
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||||
|
throw AssemblyError("argument type incompatible")
|
||||||
|
|
||||||
|
val scopedParamVar = (sub.scopedname+"."+parameter.value.name).split(".")
|
||||||
|
val identifier = IdentifierReference(scopedParamVar, sub.position)
|
||||||
|
identifier.linkParents(value.parent)
|
||||||
|
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, variable = identifier)
|
||||||
|
val source = AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(tgt)
|
||||||
|
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
|
||||||
|
asmgen.translateNormalAssignment(asgn)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||||
|
// pass argument via a register parameter
|
||||||
|
val valueIDt = value.inferType(program)
|
||||||
|
if(!valueIDt.isKnown)
|
||||||
|
throw AssemblyError("arg type unknown")
|
||||||
|
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||||
|
throw AssemblyError("argument type incompatible")
|
||||||
|
|
||||||
|
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.asmVariableName(value)
|
||||||
|
asmgen.out("""
|
||||||
|
pha
|
||||||
|
lda $sourceName
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
bcs ++
|
||||||
|
+ clc
|
||||||
|
+ pla
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(value)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
pha
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
bcs ++
|
||||||
|
+ clc
|
||||||
|
+ pla
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else throw AssemblyError("can only use Carry as status flag parameter")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// via register or register pair
|
||||||
|
val target = AsmAssignTarget.fromRegisters(register!!, program, asmgen)
|
||||||
|
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||||
|
val addr = AddressOf(value as IdentifierReference, Position.DUMMY)
|
||||||
|
AsmAssignSource.fromAstSource(addr, program).adjustDataTypeToTarget(target)
|
||||||
|
} else {
|
||||||
|
AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||||
|
if(argType isAssignableTo paramType)
|
||||||
|
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.
|
||||||
|
// strings are assignable to UWORD, for example, and vice versa
|
||||||
|
if(argType==DataType.STR && paramType==DataType.UWORD)
|
||||||
|
return true
|
||||||
|
if(argType==DataType.UWORD && paramType == DataType.STR)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.statements.PostIncrDecr
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
|
||||||
|
|
||||||
|
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
internal fun translate(stmt: PostIncrDecr) {
|
||||||
|
val incr = stmt.operator=="++"
|
||||||
|
val targetIdent = stmt.target.identifier
|
||||||
|
val targetMemory = stmt.target.memoryAddress
|
||||||
|
val targetArrayIdx = stmt.target.arrayindexed
|
||||||
|
when {
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val what = asmgen.asmVariableName(targetIdent)
|
||||||
|
when (stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)) {
|
||||||
|
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||||
|
in WordDatatypes -> {
|
||||||
|
if(incr)
|
||||||
|
asmgen.out(" inc $what | bne + | inc $what+1 |+")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
lda $what
|
||||||
|
bne +
|
||||||
|
dec $what+1
|
||||||
|
+ dec $what
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out(" lda #<$what | ldy #>$what")
|
||||||
|
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("need numeric type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetMemory!=null -> {
|
||||||
|
when (val addressExpr = targetMemory.addressExpression) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val what = addressExpr.number.toHex()
|
||||||
|
asmgen.out(if(incr) " inc $what" else " dec $what")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val what = asmgen.asmVariableName(addressExpr)
|
||||||
|
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)
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.identifier)
|
||||||
|
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
when(index) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||||
|
when(elementDt) {
|
||||||
|
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||||
|
in WordDatatypes -> {
|
||||||
|
if(incr)
|
||||||
|
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
lda $asmArrayvarname+$indexValue
|
||||||
|
bne +
|
||||||
|
dec $asmArrayvarname+$indexValue+1
|
||||||
|
+ dec $asmArrayvarname+$indexValue
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
|
||||||
|
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("need numeric type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||||
|
asmgen.out(" stx P8ZP_SCRATCH_REG_X | tax")
|
||||||
|
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 c64flt.inc_var_f""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird array elt dt")
|
||||||
|
}
|
||||||
|
asmgen.out(" ldx P8ZP_SCRATCH_REG_X")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird target type ${stmt.target}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen.assignment
|
||||||
|
|
||||||
|
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.compiler.AssemblyError
|
||||||
|
import prog8.compiler.target.c64.codegen.AsmGen
|
||||||
|
|
||||||
|
|
||||||
|
internal enum class TargetStorageKind {
|
||||||
|
VARIABLE,
|
||||||
|
ARRAY,
|
||||||
|
MEMORY,
|
||||||
|
REGISTER,
|
||||||
|
STACK
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum class SourceStorageKind {
|
||||||
|
LITERALNUMBER,
|
||||||
|
VARIABLE,
|
||||||
|
ARRAY,
|
||||||
|
MEMORY,
|
||||||
|
REGISTER,
|
||||||
|
STACK, // value is already present on stack
|
||||||
|
EXPRESSION, // expression in ast-form, still to be evaluated
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||||
|
program: Program,
|
||||||
|
asmgen: AsmGen,
|
||||||
|
val datatype: DataType,
|
||||||
|
val variable: IdentifierReference? = null,
|
||||||
|
val array: ArrayIndexedExpression? = null,
|
||||||
|
val memory: DirectMemoryWrite? = null,
|
||||||
|
val register: RegisterOrPair? = null,
|
||||||
|
val origAstTarget: AssignTarget? = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||||
|
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
|
||||||
|
val vardecl by lazy { variable?.targetVarDecl(program.namespace)!! }
|
||||||
|
val asmVarname by lazy {
|
||||||
|
if(variable!=null)
|
||||||
|
asmgen.asmVariableName(variable)
|
||||||
|
else
|
||||||
|
asmgen.asmVariableName(array!!.identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
lateinit var origAssign: AsmAssignment
|
||||||
|
|
||||||
|
init {
|
||||||
|
if(variable!=null && vardecl.type == VarDeclType.CONST)
|
||||||
|
throw AssemblyError("can't assign to a constant")
|
||||||
|
if(register!=null && datatype !in IntegerDatatypes)
|
||||||
|
throw AssemblyError("register must be integer type")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
|
||||||
|
val dt = inferType(program, assign).typeOrElse(DataType.STRUCT)
|
||||||
|
when {
|
||||||
|
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, variable=identifier, origAstTarget = this)
|
||||||
|
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, array = arrayindexed, origAstTarget = this)
|
||||||
|
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, memory = memoryAddress, origAstTarget = this)
|
||||||
|
else -> throw AssemblyError("weird target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromRegisters(registers: RegisterOrPair, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
||||||
|
when(registers) {
|
||||||
|
RegisterOrPair.A,
|
||||||
|
RegisterOrPair.X,
|
||||||
|
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, register = registers)
|
||||||
|
RegisterOrPair.AX,
|
||||||
|
RegisterOrPair.AY,
|
||||||
|
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, register = registers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||||
|
private val program: Program,
|
||||||
|
val datatype: DataType,
|
||||||
|
val variable: IdentifierReference? = null,
|
||||||
|
val array: ArrayIndexedExpression? = null,
|
||||||
|
val memory: DirectMemoryRead? = null,
|
||||||
|
val register: CpuRegister? = null,
|
||||||
|
val number: NumericLiteralValue? = null,
|
||||||
|
val expression: Expression? = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||||
|
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
|
||||||
|
val vardecl by lazy { variable?.targetVarDecl(program.namespace)!! }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromAstSource(value: Expression, program: Program): AsmAssignSource {
|
||||||
|
val cv = value.constValue(program)
|
||||||
|
if(cv!=null)
|
||||||
|
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, cv.type, number = cv)
|
||||||
|
|
||||||
|
return when(value) {
|
||||||
|
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, value.type, number = cv)
|
||||||
|
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||||
|
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
AsmAssignSource(SourceStorageKind.VARIABLE, program, dt, variable = value)
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
AsmAssignSource(SourceStorageKind.MEMORY, program, DataType.UBYTE, memory = value)
|
||||||
|
}
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
AsmAssignSource(SourceStorageKind.ARRAY, program, dt, array = value)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, dt, expression = value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAstValue(): Expression = when(kind) {
|
||||||
|
SourceStorageKind.LITERALNUMBER -> number!!
|
||||||
|
SourceStorageKind.VARIABLE -> variable!!
|
||||||
|
SourceStorageKind.ARRAY -> array!!
|
||||||
|
SourceStorageKind.MEMORY -> memory!!
|
||||||
|
SourceStorageKind.EXPRESSION -> expression!!
|
||||||
|
SourceStorageKind.REGISTER -> throw AssemblyError("cannot get a register source as Ast node")
|
||||||
|
SourceStorageKind.STACK -> throw AssemblyError("cannot get a stack source as Ast node")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withAdjustedDt(newType: DataType) =
|
||||||
|
AsmAssignSource(kind, program, newType, variable, array, memory, register, number, expression)
|
||||||
|
|
||||||
|
fun adjustDataTypeToTarget(target: AsmAssignTarget): AsmAssignSource {
|
||||||
|
// allow some signed/unsigned relaxations
|
||||||
|
if(target.datatype!=datatype) {
|
||||||
|
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
|
||||||
|
return withAdjustedDt(target.datatype)
|
||||||
|
} else if(target.datatype in WordDatatypes && datatype in WordDatatypes) {
|
||||||
|
return withAdjustedDt(target.datatype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class AsmAssignment(val source: AsmAssignSource,
|
||||||
|
val target: AsmAssignTarget,
|
||||||
|
val isAugmentable: Boolean,
|
||||||
|
val position: Position) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(source.datatype==target.datatype) {"source and target datatype must be identical"}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,955 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen.assignment
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.target.c64.codegen.AsmGen
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
|
||||||
|
|
||||||
|
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
|
||||||
|
|
||||||
|
fun translate(assignment: Assignment) {
|
||||||
|
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
|
||||||
|
val source = AsmAssignSource.fromAstSource(assignment.value, program).adjustDataTypeToTarget(target)
|
||||||
|
|
||||||
|
val assign = AsmAssignment(source, target, assignment.isAugmentable, assignment.position)
|
||||||
|
target.origAssign = assign
|
||||||
|
|
||||||
|
if(assign.isAugmentable)
|
||||||
|
augmentableAsmGen.translate(assign)
|
||||||
|
else
|
||||||
|
translateNormalAssignment(assign)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun translateNormalAssignment(assign: AsmAssignment) {
|
||||||
|
when(assign.source.kind) {
|
||||||
|
SourceStorageKind.LITERALNUMBER -> {
|
||||||
|
// simple case: assign a constant number
|
||||||
|
val num = assign.source.number!!.number
|
||||||
|
when (assign.source.datatype) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toShort())
|
||||||
|
DataType.UWORD, DataType.WORD -> assignConstantWord(assign.target, num.toInt())
|
||||||
|
DataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
|
||||||
|
else -> throw AssemblyError("weird numval type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SourceStorageKind.VARIABLE -> {
|
||||||
|
// simple case: assign from another variable
|
||||||
|
val variable = assign.source.variable!!
|
||||||
|
when (assign.source.datatype) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable)
|
||||||
|
DataType.UWORD, DataType.WORD -> assignVariableWord(assign.target, variable)
|
||||||
|
DataType.FLOAT -> assignVariableFloat(assign.target, variable)
|
||||||
|
in PassByReferenceDatatypes -> assignAddressOf(assign.target, variable)
|
||||||
|
else -> throw AssemblyError("unsupported assignment target type ${assign.target.datatype}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SourceStorageKind.ARRAY -> {
|
||||||
|
val value = assign.source.array!!
|
||||||
|
val elementDt = assign.source.datatype
|
||||||
|
val index = value.arrayspec.index
|
||||||
|
val arrayVarName = asmgen.asmVariableName(value.identifier)
|
||||||
|
if (index is NumericLiteralValue) {
|
||||||
|
// constant array index value
|
||||||
|
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||||
|
when (elementDt) {
|
||||||
|
in ByteDatatypes ->
|
||||||
|
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
|
||||||
|
in WordDatatypes ->
|
||||||
|
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
|
||||||
|
DataType.FLOAT ->
|
||||||
|
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
|
||||||
|
else ->
|
||||||
|
throw AssemblyError("weird array type")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when (elementDt) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
|
||||||
|
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
|
||||||
|
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | lda $arrayVarName+1,y | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.A)
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #>$arrayVarName
|
||||||
|
clc
|
||||||
|
adc #<$arrayVarName
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jsr c64flt.push_float""")
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
throw AssemblyError("weird array elt type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assignStackValue(assign.target)
|
||||||
|
}
|
||||||
|
SourceStorageKind.MEMORY -> {
|
||||||
|
val value = assign.source.memory!!
|
||||||
|
when (value.addressExpression) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
||||||
|
assignMemoryByte(assign.target, address, null)
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
assignMemoryByte(assign.target, null, value.addressExpression as IdentifierReference)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(value.addressExpression)
|
||||||
|
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | inx")
|
||||||
|
assignRegisterByte(assign.target, CpuRegister.A)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SourceStorageKind.EXPRESSION -> {
|
||||||
|
val value = assign.source.expression!!
|
||||||
|
when(value) {
|
||||||
|
is AddressOf -> assignAddressOf(assign.target, value.identifier)
|
||||||
|
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
|
||||||
|
is IdentifierReference -> throw AssemblyError("source kind should have been variable")
|
||||||
|
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
|
||||||
|
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
|
||||||
|
// is TypecastExpression -> {
|
||||||
|
// if(assign.target.kind == TargetStorageKind.STACK) {
|
||||||
|
// asmgen.translateExpression(value)
|
||||||
|
// assignStackValue(assign.target)
|
||||||
|
// } else {
|
||||||
|
// println("!!!!TYPECAST to ${assign.target.kind} $value")
|
||||||
|
// // TODO maybe we can do the typecast on the target directly instead of on the stack?
|
||||||
|
// asmgen.translateExpression(value)
|
||||||
|
// assignStackValue(assign.target)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// is FunctionCall -> {
|
||||||
|
// if (assign.target.kind == TargetStorageKind.STACK) {
|
||||||
|
// asmgen.translateExpression(value)
|
||||||
|
// assignStackValue(assign.target)
|
||||||
|
// } else {
|
||||||
|
// val functionName = value.target.nameInSource.last()
|
||||||
|
// val builtinFunc = BuiltinFunctions[functionName]
|
||||||
|
// if (builtinFunc != null) {
|
||||||
|
// println("!!!!BUILTIN-FUNCCALL target=${assign.target.kind} $value") // TODO optimize certain functions?
|
||||||
|
// }
|
||||||
|
// asmgen.translateExpression(value)
|
||||||
|
// assignStackValue(assign.target)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
else -> {
|
||||||
|
// everything else just evaluate via the stack.
|
||||||
|
asmgen.translateExpression(value)
|
||||||
|
assignStackValue(assign.target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SourceStorageKind.REGISTER -> {
|
||||||
|
assignRegisterByte(assign.target, assign.source.register!!)
|
||||||
|
}
|
||||||
|
SourceStorageKind.STACK -> {
|
||||||
|
assignStackValue(assign.target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignStackValue(target: AsmAssignTarget) {
|
||||||
|
when(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
when (target.datatype) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
|
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}")
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta ${target.asmVarname}
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta ${target.asmVarname}+1
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${target.asmVarname}
|
||||||
|
ldy #>${target.asmVarname}
|
||||||
|
jsr c64flt.pop_float
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> {
|
||||||
|
asmgen.out(" inx")
|
||||||
|
storeByteViaRegisterAInMemoryAddress("P8ESTACK_LO,x", target.memory!!)
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
val index = target.array!!.arrayspec.index
|
||||||
|
when {
|
||||||
|
target.constArrayIndexValue!=null -> {
|
||||||
|
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||||
|
when(target.datatype) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta ${target.asmVarname}+$scaledIdx
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta ${target.asmVarname}+$scaledIdx+1
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${target.asmVarname}+$scaledIdx
|
||||||
|
ldy #>${target.asmVarname}+$scaledIdx
|
||||||
|
jsr c64flt.pop_float
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index is IdentifierReference -> {
|
||||||
|
when(target.datatype) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||||
|
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname},y")
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta ${target.asmVarname},y
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta ${target.asmVarname}+1,y
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #>${target.asmVarname}
|
||||||
|
clc
|
||||||
|
adc #<${target.asmVarname}
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jsr c64flt.pop_float""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||||
|
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER -> {
|
||||||
|
when (target.datatype) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
|
when(target.register!!) {
|
||||||
|
RegisterOrPair.A -> asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||||
|
RegisterOrPair.X -> throw AssemblyError("can't use X here")
|
||||||
|
RegisterOrPair.Y -> asmgen.out(" inx | ldy P8ESTACK_LO,x")
|
||||||
|
else -> throw AssemblyError("can't assign byte to register pair word")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD, in PassByReferenceDatatypes -> {
|
||||||
|
when(target.register!!) {
|
||||||
|
RegisterOrPair.AX -> throw AssemblyError("can't use X here")
|
||||||
|
RegisterOrPair.AY-> asmgen.out(" inx | lda P8ESTACK_LO,x | ldy P8ESTACK_HI,x")
|
||||||
|
RegisterOrPair.XY-> throw AssemblyError("can't use X here")
|
||||||
|
else -> throw AssemblyError("can't assign word to single byte register")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.STACK -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignAddressOf(target: AsmAssignTarget, name: IdentifierReference) {
|
||||||
|
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(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$sourceName
|
||||||
|
ldy #>$sourceName
|
||||||
|
sta ${target.asmVarname}
|
||||||
|
sty ${target.asmVarname}+1
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> {
|
||||||
|
throw AssemblyError("no asm gen for assign address $sourceName to memory word $target")
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
throw AssemblyError("no asm gen for assign address $sourceName to array ${target.asmVarname}")
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER -> {
|
||||||
|
when(target.register!!) {
|
||||||
|
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||||
|
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
|
||||||
|
else -> throw AssemblyError("can't load address in a single 8-bit register")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.STACK -> {
|
||||||
|
val srcname = asmgen.asmVariableName(name)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$srcname
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
lda #>$srcname
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignVariableWord(target: AsmAssignTarget, variable: IdentifierReference) {
|
||||||
|
val sourceName = asmgen.asmVariableName(variable)
|
||||||
|
when(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $sourceName
|
||||||
|
ldy $sourceName+1
|
||||||
|
sta ${target.asmVarname}
|
||||||
|
sty ${target.asmVarname}+1
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> {
|
||||||
|
throw AssemblyError("no asm gen for assign wordvar $sourceName to memory ${target.memory}")
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
val index = target.array!!.arrayspec.index
|
||||||
|
when {
|
||||||
|
target.constArrayIndexValue!=null -> {
|
||||||
|
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||||
|
when(target.datatype) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $sourceName
|
||||||
|
sta ${target.asmVarname}+$scaledIdx
|
||||||
|
lda $sourceName+1
|
||||||
|
sta ${target.asmVarname}+$scaledIdx+1
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$sourceName
|
||||||
|
ldy #>$sourceName
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda #<${target.asmVarname}+$scaledIdx
|
||||||
|
ldy #>${target.asmVarname}+$scaledIdx
|
||||||
|
jsr c64flt.copy_float
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index is IdentifierReference -> {
|
||||||
|
when(target.datatype) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||||
|
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $sourceName
|
||||||
|
sta ${target.asmVarname},y
|
||||||
|
lda $sourceName+1
|
||||||
|
sta ${target.asmVarname}+1,y
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #<$sourceName
|
||||||
|
sty P8ZP_SCRATCH_W1
|
||||||
|
ldy #>$sourceName
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #>${target.asmVarname}
|
||||||
|
clc
|
||||||
|
adc #<${target.asmVarname}
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jsr c64flt.copy_float""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | lda $sourceName+1 | sta P8ESTACK_HI,x | dex")
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||||
|
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER -> {
|
||||||
|
when(target.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 -> throw AssemblyError("can't load word in a single 8-bit register")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.STACK -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #$sourceName
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
lda #$sourceName+1
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignVariableFloat(target: AsmAssignTarget, variable: IdentifierReference) {
|
||||||
|
val sourceName = asmgen.asmVariableName(variable)
|
||||||
|
when(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $sourceName
|
||||||
|
sta ${target.asmVarname}
|
||||||
|
lda $sourceName+1
|
||||||
|
sta ${target.asmVarname}+1
|
||||||
|
lda $sourceName+2
|
||||||
|
sta ${target.asmVarname}+2
|
||||||
|
lda $sourceName+3
|
||||||
|
sta ${target.asmVarname}+3
|
||||||
|
lda $sourceName+4
|
||||||
|
sta ${target.asmVarname}+4
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
// TODO optimize this, but the situation doesn't occur very often
|
||||||
|
// if(target.constArrayIndexValue!=null) {
|
||||||
|
// TODO("const index ${target.constArrayIndexValue}")
|
||||||
|
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||||
|
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||||
|
// }
|
||||||
|
val index = target.array!!.arrayspec.index
|
||||||
|
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname} | jsr c64flt.pop_float_to_indexed_var")
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
|
||||||
|
TargetStorageKind.REGISTER -> throw AssemblyError("can't assign float to register")
|
||||||
|
TargetStorageKind.STACK -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignVariableByte(target: AsmAssignTarget, variable: IdentifierReference) {
|
||||||
|
val sourceName = asmgen.asmVariableName(variable)
|
||||||
|
when(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $sourceName
|
||||||
|
sta ${target.asmVarname}
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> {
|
||||||
|
storeByteViaRegisterAInMemoryAddress(sourceName, target.memory!!)
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
val index = target.array!!.arrayspec.index
|
||||||
|
when {
|
||||||
|
target.constArrayIndexValue!=null -> {
|
||||||
|
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||||
|
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||||
|
}
|
||||||
|
index is IdentifierReference -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||||
|
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||||
|
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER -> {
|
||||||
|
when(target.register!!) {
|
||||||
|
RegisterOrPair.A -> asmgen.out(" lda $sourceName")
|
||||||
|
RegisterOrPair.X -> asmgen.out(" ldx $sourceName")
|
||||||
|
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName")
|
||||||
|
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx #0")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy #0")
|
||||||
|
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy #0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.STACK -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #$sourceName
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
|
||||||
|
require(target.datatype in ByteDatatypes)
|
||||||
|
when(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
asmgen.out(" st${register.name.toLowerCase()} ${target.asmVarname}")
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> {
|
||||||
|
storeRegisterInMemoryAddress(register, target.memory!!)
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
val index = target.array!!.arrayspec.index
|
||||||
|
when (index) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val memindex = index.number.toInt()
|
||||||
|
when (register) {
|
||||||
|
CpuRegister.A -> asmgen.out(" sta ${target.asmVarname}+$memindex")
|
||||||
|
CpuRegister.X -> asmgen.out(" stx ${target.asmVarname}+$memindex")
|
||||||
|
CpuRegister.Y -> asmgen.out(" sty ${target.asmVarname}+$memindex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
when (register) {
|
||||||
|
CpuRegister.A -> {}
|
||||||
|
CpuRegister.X -> asmgen.out(" txa")
|
||||||
|
CpuRegister.Y -> asmgen.out(" tya")
|
||||||
|
}
|
||||||
|
asmgen.out(" ldy ${asmgen.asmVariableName(index)} | sta ${target.asmVarname},y")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.saveRegister(register)
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.restoreRegister(register)
|
||||||
|
when (register) {
|
||||||
|
CpuRegister.A -> asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||||
|
CpuRegister.X -> asmgen.out(" stx P8ZP_SCRATCH_B1")
|
||||||
|
CpuRegister.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1")
|
||||||
|
}
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
tay
|
||||||
|
lda P8ZP_SCRATCH_B1
|
||||||
|
sta ${target.asmVarname},y
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER -> {
|
||||||
|
when(register) {
|
||||||
|
CpuRegister.A -> when(target.register!!) {
|
||||||
|
RegisterOrPair.A -> {}
|
||||||
|
RegisterOrPair.X -> { asmgen.out(" tax") }
|
||||||
|
RegisterOrPair.Y -> { asmgen.out(" tay") }
|
||||||
|
else -> throw AssemblyError("attempt to assign byte to register pair word")
|
||||||
|
}
|
||||||
|
CpuRegister.X -> when(target.register!!) {
|
||||||
|
RegisterOrPair.A -> { asmgen.out(" txa") }
|
||||||
|
RegisterOrPair.X -> { }
|
||||||
|
RegisterOrPair.Y -> { asmgen.out(" txy") }
|
||||||
|
else -> throw AssemblyError("attempt to assign byte to register pair word")
|
||||||
|
}
|
||||||
|
CpuRegister.Y -> when(target.register!!) {
|
||||||
|
RegisterOrPair.A -> { asmgen.out(" tya") }
|
||||||
|
RegisterOrPair.X -> { asmgen.out(" tyx") }
|
||||||
|
RegisterOrPair.Y -> { }
|
||||||
|
else -> throw AssemblyError("attempt to assign byte to register pair word")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.STACK -> {
|
||||||
|
when(register) {
|
||||||
|
CpuRegister.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||||
|
CpuRegister.X -> throw AssemblyError("can't use X here")
|
||||||
|
CpuRegister.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
|
||||||
|
when(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
if (word ushr 8 == word and 255) {
|
||||||
|
// lsb=msb
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${(word and 255).toHex()}
|
||||||
|
sta ${target.asmVarname}
|
||||||
|
sta ${target.asmVarname}+1
|
||||||
|
""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${word.toHex()}
|
||||||
|
ldy #>${word.toHex()}
|
||||||
|
sta ${target.asmVarname}
|
||||||
|
sty ${target.asmVarname}+1
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> {
|
||||||
|
throw AssemblyError("no asm gen for assign word $word to memory ${target.memory}")
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
// TODO optimize this, but the situation doesn't occur very often
|
||||||
|
// if(target.constArrayIndexValue!=null) {
|
||||||
|
// TODO("const index ${target.constArrayIndexValue}")
|
||||||
|
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||||
|
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||||
|
// }
|
||||||
|
val index = target.array!!.arrayspec.index
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda #<${word.toHex()}
|
||||||
|
sta ${target.asmVarname},y
|
||||||
|
lda #>${word.toHex()}
|
||||||
|
sta ${target.asmVarname}+1,y
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER -> {
|
||||||
|
when(target.register!!) {
|
||||||
|
RegisterOrPair.AX -> asmgen.out(" lda #<${word.toHex()} | ldx #>${word.toHex()}")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" lda #<${word.toHex()} | ldy #>${word.toHex()}")
|
||||||
|
RegisterOrPair.XY -> asmgen.out(" ldx #<${word.toHex()} | ldy #>${word.toHex()}")
|
||||||
|
else -> throw AssemblyError("can't assign word to single byte register")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.STACK -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${word.toHex()}
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
lda #>${word.toHex()}
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
|
||||||
|
when(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname} ")
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> {
|
||||||
|
storeByteViaRegisterAInMemoryAddress("#${byte.toHex()}", target.memory!!)
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
val index = target.array!!.arrayspec.index
|
||||||
|
when {
|
||||||
|
target.constArrayIndexValue!=null -> {
|
||||||
|
val indexValue = target.constArrayIndexValue!!
|
||||||
|
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname}+$indexValue")
|
||||||
|
}
|
||||||
|
index is IdentifierReference -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
|
||||||
|
asmgen.out(" lda #<${byte.toHex()} | sta ${target.asmVarname},y")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
ldy P8ESTACK_LO,x
|
||||||
|
lda #${byte.toHex()}
|
||||||
|
sta ${target.asmVarname},y
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER -> when(target.register!!) {
|
||||||
|
RegisterOrPair.A -> asmgen.out(" lda #${byte.toHex()}")
|
||||||
|
RegisterOrPair.X -> asmgen.out(" ldx #${byte.toHex()}")
|
||||||
|
RegisterOrPair.Y -> asmgen.out(" ldy #${byte.toHex()}")
|
||||||
|
else -> throw AssemblyError("can't assign byte to word register apir")
|
||||||
|
}
|
||||||
|
TargetStorageKind.STACK -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${byte.toHex()}
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignConstantFloat(target: AsmAssignTarget, float: Double) {
|
||||||
|
if (float == 0.0) {
|
||||||
|
// optimized case for float zero
|
||||||
|
when(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #0
|
||||||
|
sta ${target.asmVarname}
|
||||||
|
sta ${target.asmVarname}+1
|
||||||
|
sta ${target.asmVarname}+2
|
||||||
|
sta ${target.asmVarname}+3
|
||||||
|
sta ${target.asmVarname}+4
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
// TODO optimize this, but the situation doesn't occur very often
|
||||||
|
// if(target.constArrayIndexValue!=null) {
|
||||||
|
// TODO("const index ${target.constArrayIndexValue}")
|
||||||
|
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||||
|
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||||
|
// }
|
||||||
|
val index = target.array!!.arrayspec.index
|
||||||
|
if (index is NumericLiteralValue) {
|
||||||
|
val indexValue = index.number.toInt() * DataType.FLOAT.memorySize()
|
||||||
|
asmgen.out("""
|
||||||
|
lda #0
|
||||||
|
sta ${target.asmVarname}+$indexValue
|
||||||
|
sta ${target.asmVarname}+$indexValue+1
|
||||||
|
sta ${target.asmVarname}+$indexValue+2
|
||||||
|
sta ${target.asmVarname}+$indexValue+3
|
||||||
|
sta ${target.asmVarname}+$indexValue+4
|
||||||
|
""")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${target.asmVarname}
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda #>${target.asmVarname}
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
jsr c64flt.set_0_array_float
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
|
||||||
|
TargetStorageKind.REGISTER -> throw AssemblyError("can't assign float to register")
|
||||||
|
TargetStorageKind.STACK -> {
|
||||||
|
val floatConst = asmgen.getFloatAsmConst(float)
|
||||||
|
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// non-zero value
|
||||||
|
val constFloat = asmgen.getFloatAsmConst(float)
|
||||||
|
when(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $constFloat
|
||||||
|
sta ${target.asmVarname}
|
||||||
|
lda $constFloat+1
|
||||||
|
sta ${target.asmVarname}+1
|
||||||
|
lda $constFloat+2
|
||||||
|
sta ${target.asmVarname}+2
|
||||||
|
lda $constFloat+3
|
||||||
|
sta ${target.asmVarname}+3
|
||||||
|
lda $constFloat+4
|
||||||
|
sta ${target.asmVarname}+4
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
// TODO optimize this, but the situation doesn't occur very often
|
||||||
|
// if(target.constArrayIndexValue!=null) {
|
||||||
|
// TODO("const index ${target.constArrayIndexValue}")
|
||||||
|
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||||
|
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||||
|
// }
|
||||||
|
val index = target.array!!.arrayspec.index
|
||||||
|
val arrayVarName = target.asmVarname
|
||||||
|
if (index is NumericLiteralValue) {
|
||||||
|
val indexValue = index.number.toInt() * DataType.FLOAT.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.translateExpression(index)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${constFloat}
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda #>${constFloat}
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
lda #<${arrayVarName}
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
lda #>${arrayVarName}
|
||||||
|
sta P8ZP_SCRATCH_W2+1
|
||||||
|
jsr c64flt.set_array_float
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
|
||||||
|
TargetStorageKind.REGISTER -> throw AssemblyError("can't assign float to register")
|
||||||
|
TargetStorageKind.STACK -> {
|
||||||
|
val floatConst = asmgen.getFloatAsmConst(float)
|
||||||
|
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignMemoryByte(target: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
|
||||||
|
if (address != null) {
|
||||||
|
when(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda ${address.toHex()}
|
||||||
|
sta ${target.asmVarname}
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> {
|
||||||
|
storeByteViaRegisterAInMemoryAddress(address.toHex(), target.memory!!)
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
throw AssemblyError("no asm gen for assign memory byte at $address to array ${target.asmVarname}")
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER -> when(target.register!!) {
|
||||||
|
RegisterOrPair.A -> asmgen.out(" lda ${address.toHex()}")
|
||||||
|
RegisterOrPair.X -> asmgen.out(" ldx ${address.toHex()}")
|
||||||
|
RegisterOrPair.Y -> asmgen.out(" ldy ${address.toHex()}")
|
||||||
|
else -> throw AssemblyError("can't assign byte to word register apir")
|
||||||
|
}
|
||||||
|
TargetStorageKind.STACK -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda ${address.toHex()}
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (identifier != null) {
|
||||||
|
when(target.kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
asmgen.loadByteFromPointerIntoA(identifier)
|
||||||
|
asmgen.out(" sta ${target.asmVarname}")
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> {
|
||||||
|
val sourceName = asmgen.asmVariableName(identifier)
|
||||||
|
storeByteViaRegisterAInMemoryAddress(sourceName, target.memory!!)
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
throw AssemblyError("no asm gen for assign memory byte $identifier to array ${target.asmVarname} ")
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER -> {
|
||||||
|
asmgen.loadByteFromPointerIntoA(identifier)
|
||||||
|
when(target.register!!) {
|
||||||
|
RegisterOrPair.A -> {}
|
||||||
|
RegisterOrPair.X -> asmgen.out(" tax")
|
||||||
|
RegisterOrPair.Y -> asmgen.out(" tay")
|
||||||
|
else -> throw AssemblyError("can't assign byte to word register apir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.STACK -> {
|
||||||
|
asmgen.loadByteFromPointerIntoA(identifier)
|
||||||
|
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun storeByteViaRegisterAInMemoryAddress(ldaInstructionArg: String, memoryAddress: DirectMemoryWrite) {
|
||||||
|
val addressExpr = memoryAddress.addressExpression
|
||||||
|
val addressLv = addressExpr as? NumericLiteralValue
|
||||||
|
when {
|
||||||
|
addressLv != null -> {
|
||||||
|
asmgen.out(" lda $ldaInstructionArg | sta ${addressLv.number.toHex()}")
|
||||||
|
}
|
||||||
|
addressExpr is IdentifierReference -> {
|
||||||
|
asmgen.storeByteIntoPointer(addressExpr, ldaInstructionArg)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(addressExpr)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta P8ZP_SCRATCH_W2+1
|
||||||
|
lda $ldaInstructionArg
|
||||||
|
ldy #0
|
||||||
|
sta (P8ZP_SCRATCH_W2),y""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun storeRegisterInMemoryAddress(register: CpuRegister, memoryAddress: DirectMemoryWrite) {
|
||||||
|
// this is optimized for register A.
|
||||||
|
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 -> {
|
||||||
|
when (register) {
|
||||||
|
CpuRegister.A -> {}
|
||||||
|
CpuRegister.X -> asmgen.out(" txa")
|
||||||
|
CpuRegister.Y -> asmgen.out(" tya")
|
||||||
|
}
|
||||||
|
asmgen.storeByteIntoPointer(addressExpr, null)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.saveRegister(register)
|
||||||
|
asmgen.translateExpression(addressExpr)
|
||||||
|
asmgen.restoreRegister(CpuRegister.A)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
ldy P8ESTACK_LO,x
|
||||||
|
sty P8ZP_SCRATCH_W2
|
||||||
|
ldy P8ESTACK_HI,x
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy #0
|
||||||
|
sta (P8ZP_SCRATCH_W2),y""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun popAndWriteArrayvalueWithUnscaledIndexA(elementDt: DataType, asmArrayvarname: String) {
|
||||||
|
when (elementDt) {
|
||||||
|
in ByteDatatypes ->
|
||||||
|
asmgen.out(" tay | inx | lda P8ESTACK_LO,x | sta $asmArrayvarname,y")
|
||||||
|
in WordDatatypes ->
|
||||||
|
asmgen.out(" asl a | tay | inx | lda P8ESTACK_LO,x | sta $asmArrayvarname,y | lda P8ESTACK_HI,x | sta $asmArrayvarname+1,y")
|
||||||
|
DataType.FLOAT ->
|
||||||
|
// scaling * 5 is done in the subroutine that's called
|
||||||
|
asmgen.out("""
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
lda #<$asmArrayvarname
|
||||||
|
ldy #>$asmArrayvarname
|
||||||
|
jsr c64flt.pop_float_to_indexed_var
|
||||||
|
""")
|
||||||
|
else ->
|
||||||
|
throw AssemblyError("weird array type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
124
compiler/src/prog8/compiler/target/cx16/CX16MachineDefinition.kt
Normal file
124
compiler/src/prog8/compiler/target/cx16/CX16MachineDefinition.kt
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package prog8.compiler.target.cx16
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.compiler.*
|
||||||
|
import prog8.compiler.target.IMachineDefinition
|
||||||
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
|
import prog8.parser.ModuleImporter
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
internal object CX16MachineDefinition: IMachineDefinition {
|
||||||
|
|
||||||
|
override val cpu = "65c02"
|
||||||
|
|
||||||
|
// 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 = 0x8000
|
||||||
|
|
||||||
|
// 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 = 0x0400 // $0400-$04ff inclusive
|
||||||
|
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
|
||||||
|
|
||||||
|
override lateinit var zeropage: Zeropage
|
||||||
|
override val initSystemProcname = "cx16.init_system"
|
||||||
|
|
||||||
|
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
|
||||||
|
|
||||||
|
override fun getFloatRomConst(number: Double): String? = null // TODO Does Cx16 have ROM float locations?
|
||||||
|
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
|
||||||
|
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||||
|
importer.importLibraryModule(program, "cx16lib")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun launchEmulator(programName: String) {
|
||||||
|
for(emulator in listOf("x16emu")) {
|
||||||
|
println("\nStarting Commander X16 emulator $emulator...")
|
||||||
|
val cmdline = listOf(emulator, "-rom", "/usr/share/x16-rom/rom.bin", "-scale", "2",
|
||||||
|
"-run", "-prg", 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 initializeZeropage(compilerOptions: CompilationOptions) {
|
||||||
|
zeropage = CX16Zeropage(compilerOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
||||||
|
// TODO add 65C02 opcodes
|
||||||
|
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 CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
|
|
||||||
|
override val SCRATCH_B1 = 0x79 // temp storage for a single byte
|
||||||
|
override val SCRATCH_REG = 0x7a // temp storage for a register
|
||||||
|
override val SCRATCH_REG_X = 0x7b // temp storage for register X (the evaluation stack pointer)
|
||||||
|
override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d
|
||||||
|
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
|
||||||
|
|
||||||
|
|
||||||
|
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
|
||||||
|
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
|
||||||
|
ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
|
||||||
|
else -> ExitProgramStrategy.SYSTEM_RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (options.floats && options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||||
|
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
|
||||||
|
|
||||||
|
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||||
|
|
||||||
|
when (options.zeropage) {
|
||||||
|
ZeropageType.FULL -> {
|
||||||
|
free.addAll(0x22..0xff)
|
||||||
|
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||||
|
}
|
||||||
|
ZeropageType.KERNALSAFE -> {
|
||||||
|
free.addAll(0x22..0x7f)
|
||||||
|
free.addAll(0xa9..0xff)
|
||||||
|
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||||
|
}
|
||||||
|
ZeropageType.BASICSAFE -> {
|
||||||
|
free.addAll(0x22..0x7f)
|
||||||
|
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||||
|
}
|
||||||
|
ZeropageType.DONTUSE -> {
|
||||||
|
free.clear() // don't use zeropage at all
|
||||||
|
}
|
||||||
|
else -> throw CompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||||
|
}
|
||||||
|
|
||||||
|
require(SCRATCH_B1 !in free)
|
||||||
|
require(SCRATCH_REG !in free)
|
||||||
|
require(SCRATCH_REG_X !in free)
|
||||||
|
require(SCRATCH_W1 !in free)
|
||||||
|
require(SCRATCH_W2 !in free)
|
||||||
|
|
||||||
|
for (reserved in options.zpReserved)
|
||||||
|
reserve(reserved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,416 +1,427 @@
|
|||||||
package prog8.functions
|
package prog8.functions
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.StructDecl
|
||||||
|
import prog8.ast.statements.VarDecl
|
||||||
import prog8.compiler.CompilerException
|
import prog8.compiler.CompilerException
|
||||||
import prog8.compiler.HeapValues
|
import kotlin.math.*
|
||||||
import kotlin.math.PI
|
|
||||||
import kotlin.math.cos
|
|
||||||
import kotlin.math.log2
|
|
||||||
import kotlin.math.sin
|
|
||||||
|
|
||||||
|
|
||||||
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
|
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
|
||||||
|
|
||||||
class FunctionSignature(val pure: Boolean, // does it have side effects?
|
|
||||||
val parameters: List<BuiltinFunctionParam>,
|
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
|
||||||
val returntype: DataType?,
|
|
||||||
val constExpressionFunc: ((args: List<IExpression>, position: Position, namespace: INameScope, heap: HeapValues) -> LiteralValue)? = null)
|
|
||||||
|
class FSignature(val pure: Boolean, // does it have side effects?
|
||||||
|
val parameters: List<FParam>,
|
||||||
|
val returntype: DataType?,
|
||||||
|
val constExpressionFunc: ConstExpressionCaller? = null)
|
||||||
|
|
||||||
|
|
||||||
val BuiltinFunctions = mapOf(
|
val BuiltinFunctions = mapOf(
|
||||||
// this set of function have no return value and operate in-place:
|
// this set of function have no return value and operate in-place:
|
||||||
"rol" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
"rol" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
"ror" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
"rol2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
"ror2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||||
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||||
// these few have a return value depending on the argument(s):
|
// these few have a return value depending on the argument(s):
|
||||||
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.max()!! }}, // type depends on args
|
"max" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
||||||
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.min()!! }}, // type depends on args
|
"min" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
||||||
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.sum() }}, // type depends on args
|
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||||
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
||||||
|
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||||
|
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
|
||||||
// normal functions follow:
|
// normal functions follow:
|
||||||
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sin) },
|
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||||
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||||
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
"sin8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||||
"sin16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
"sin8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||||
"sin16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
"sin16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||||
"cos" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::cos) },
|
"sin16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||||
"cos8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
"cos" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||||
"cos8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
"cos8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||||
"cos16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
"cos8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||||
"cos16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
"cos16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||||
"tan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::tan) },
|
"cos16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||||
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::atan) },
|
"tan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||||
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::log) },
|
"atan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||||
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, ::log2) },
|
"ln" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
|
||||||
// TODO: sqrt() should have integer versions too
|
"log2" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
|
||||||
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sqrt) },
|
"sqrt16" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||||
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toRadians) },
|
"sqrt" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||||
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toDegrees) },
|
"rad" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||||
"avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.FLOAT, ::builtinAvg),
|
"deg" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||||
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::round) },
|
"round" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||||
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::floor) },
|
"floor" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||||
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::ceil) },
|
"ceil" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||||
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), DataType.UWORD, ::builtinLen),
|
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||||
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.any { v -> v != 0.0} }},
|
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||||
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.all { v -> v != 0.0} }},
|
"lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
|
||||||
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { x: Int -> x and 255 }},
|
"msb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
|
||||||
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { x: Int -> x ushr 8 and 255}},
|
"mkword" to FSignature(true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||||
"mkword" to FunctionSignature(true, listOf(
|
"rnd" to FSignature(true, emptyList(), DataType.UBYTE),
|
||||||
BuiltinFunctionParam("lsb", setOf(DataType.UBYTE)),
|
"rndw" to FSignature(true, emptyList(), DataType.UWORD),
|
||||||
BuiltinFunctionParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
"rndf" to FSignature(true, emptyList(), DataType.FLOAT),
|
||||||
"rnd" to FunctionSignature(true, emptyList(), DataType.UBYTE),
|
"exit" to FSignature(false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
|
||||||
"rndw" to FunctionSignature(true, emptyList(), DataType.UWORD),
|
"rsave" to FSignature(false, emptyList(), null),
|
||||||
"rndf" to FunctionSignature(true, emptyList(), DataType.FLOAT),
|
"rrestore" to FSignature(false, emptyList(), null),
|
||||||
"rsave" to FunctionSignature(false, emptyList(), null),
|
"set_carry" to FSignature(false, emptyList(), null),
|
||||||
"rrestore" to FunctionSignature(false, emptyList(), null),
|
"clear_carry" to FSignature(false, emptyList(), null),
|
||||||
"set_carry" to FunctionSignature(false, emptyList(), null),
|
"set_irqd" to FSignature(false, emptyList(), null),
|
||||||
"clear_carry" to FunctionSignature(false, emptyList(), null),
|
"clear_irqd" to FSignature(false, emptyList(), null),
|
||||||
"set_irqd" to FunctionSignature(false, emptyList(), null),
|
"read_flags" to FSignature(false, emptyList(), DataType.UBYTE),
|
||||||
"clear_irqd" to FunctionSignature(false, emptyList(), null),
|
"swap" to FSignature(false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
||||||
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
|
"memcopy" to FSignature(false, listOf(
|
||||||
"memcopy" to FunctionSignature(false, listOf(
|
FParam("from", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("from", IterableDatatypes + setOf(DataType.UWORD)),
|
FParam("to", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("to", IterableDatatypes + setOf(DataType.UWORD)),
|
FParam("numbytes", setOf(DataType.UBYTE))), null),
|
||||||
BuiltinFunctionParam("numbytes", IntegerDatatypes)), null),
|
"memset" to FSignature(false, listOf(
|
||||||
"memset" to FunctionSignature(false, listOf(
|
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
|
FParam("numbytes", setOf(DataType.UWORD)),
|
||||||
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
|
FParam("bytevalue", ByteDatatypes)), null),
|
||||||
BuiltinFunctionParam("bytevalue", setOf(DataType.UBYTE, DataType.BYTE))), null),
|
"memsetw" to FSignature(false, listOf(
|
||||||
"memsetw" to FunctionSignature(false, listOf(
|
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
|
FParam("numwords", setOf(DataType.UWORD)),
|
||||||
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
|
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
||||||
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
|
||||||
"vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
|
"substr" to FSignature(false, listOf(
|
||||||
"vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
|
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||||
"vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null),
|
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||||
"vm_write_char" to FunctionSignature(false, listOf(BuiltinFunctionParam("char", setOf(DataType.UBYTE))), null),
|
FParam("start", setOf(DataType.UBYTE)),
|
||||||
"vm_write_str" to FunctionSignature(false, listOf(BuiltinFunctionParam("string", StringDatatypes)), null),
|
FParam("length", setOf(DataType.UBYTE))), null),
|
||||||
"vm_input_str" to FunctionSignature(false, listOf(BuiltinFunctionParam("intovar", StringDatatypes)), null),
|
"leftstr" to FSignature(false, listOf(
|
||||||
"vm_gfx_clearscr" to FunctionSignature(false, listOf(BuiltinFunctionParam("color", setOf(DataType.UBYTE))), null),
|
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||||
"vm_gfx_pixel" to FunctionSignature(false, listOf(
|
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("x", IntegerDatatypes),
|
FParam("length", setOf(DataType.UBYTE))), null),
|
||||||
BuiltinFunctionParam("y", IntegerDatatypes),
|
"rightstr" to FSignature(false, listOf(
|
||||||
BuiltinFunctionParam("color", IntegerDatatypes)), null),
|
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||||
"vm_gfx_line" to FunctionSignature(false, listOf(
|
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("x1", IntegerDatatypes),
|
FParam("length", setOf(DataType.UBYTE))), null)
|
||||||
BuiltinFunctionParam("y1", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("x2", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("y2", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("color", IntegerDatatypes)), null),
|
|
||||||
"vm_gfx_text" to FunctionSignature(false, listOf(
|
|
||||||
BuiltinFunctionParam("x", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("y", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("color", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("text", StringDatatypes)),
|
|
||||||
null)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
|
||||||
|
|
||||||
fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespace: INameScope, heap: HeapValues): DataType? {
|
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
|
||||||
|
|
||||||
fun datatypeFromListArg(arglist: IExpression): DataType {
|
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
|
||||||
if(arglist is LiteralValue) {
|
|
||||||
if(arglist.type==DataType.ARRAY_UB || arglist.type==DataType.ARRAY_UW || arglist.type==DataType.ARRAY_F) {
|
fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0
|
||||||
val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace, heap)}
|
|
||||||
if(dt.any { it!=DataType.UBYTE && it!=DataType.UWORD && it!=DataType.FLOAT}) {
|
fun builtinAll(array: List<Number>): Number = if(array.all { it.toDouble()!=0.0 }) 1 else 0
|
||||||
throw FatalAstException("fuction $function only accepts arrayspec of numeric values")
|
|
||||||
}
|
|
||||||
if(dt.any { it==DataType.FLOAT }) return DataType.FLOAT
|
fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): InferredTypes.InferredType {
|
||||||
if(dt.any { it==DataType.UWORD }) return DataType.UWORD
|
|
||||||
return DataType.UBYTE
|
fun datatypeFromIterableArg(arglist: Expression): DataType {
|
||||||
|
if(arglist is ArrayLiteralValue) {
|
||||||
|
val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
|
||||||
|
if(dt.any { it !in NumericDatatypes }) {
|
||||||
|
throw FatalAstException("fuction $function only accepts array of numeric values")
|
||||||
}
|
}
|
||||||
|
if(DataType.FLOAT in dt) return DataType.FLOAT
|
||||||
|
if(DataType.UWORD in dt) return DataType.UWORD
|
||||||
|
if(DataType.WORD in dt) return DataType.WORD
|
||||||
|
if(DataType.BYTE in dt) return DataType.BYTE
|
||||||
|
return DataType.UBYTE
|
||||||
}
|
}
|
||||||
if(arglist is IdentifierReference) {
|
if(arglist is IdentifierReference) {
|
||||||
val dt = arglist.resultingDatatype(namespace, heap)
|
val idt = arglist.inferType(program)
|
||||||
return when(dt) {
|
if(!idt.isKnown)
|
||||||
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT,
|
throw FatalAstException("couldn't determine type of iterable $arglist")
|
||||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> dt
|
return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
|
||||||
DataType.ARRAY_UB -> DataType.UBYTE
|
DataType.STR, in NumericDatatypes -> dt
|
||||||
DataType.ARRAY_B -> DataType.BYTE
|
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
|
||||||
DataType.ARRAY_UW -> DataType.UWORD
|
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
|
||||||
DataType.ARRAY_W -> DataType.WORD
|
|
||||||
DataType.ARRAY_F -> DataType.FLOAT
|
|
||||||
null -> throw FatalAstException("function requires one argument which is an arrayspec $function")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw FatalAstException("function requires one argument which is an arrayspec $function")
|
throw FatalAstException("function '$function' requires one argument which is an iterable")
|
||||||
}
|
}
|
||||||
|
|
||||||
val func = BuiltinFunctions.getValue(function)
|
val func = BuiltinFunctions.getValue(function)
|
||||||
if(func.returntype!=null)
|
if(func.returntype!=null)
|
||||||
return func.returntype
|
return InferredTypes.knownFor(func.returntype)
|
||||||
// function has return values, but the return type depends on the arguments
|
// function has return values, but the return type depends on the arguments
|
||||||
|
|
||||||
return when (function) {
|
return when (function) {
|
||||||
"max", "min", "abs" -> {
|
"abs" -> {
|
||||||
val dt = datatypeFromListArg(args.single())
|
val dt = args.single().inferType(program)
|
||||||
when(dt) {
|
if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
|
||||||
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT -> dt
|
return dt
|
||||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.UBYTE
|
else
|
||||||
DataType.ARRAY_UB -> DataType.UBYTE
|
throw FatalAstException("weird datatype passed to abs $dt")
|
||||||
DataType.ARRAY_B -> DataType.BYTE
|
}
|
||||||
DataType.ARRAY_UW -> DataType.UWORD
|
"max", "min" -> {
|
||||||
DataType.ARRAY_W -> DataType.WORD
|
when(val dt = datatypeFromIterableArg(args.single())) {
|
||||||
DataType.ARRAY_F -> DataType.FLOAT
|
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
||||||
|
in NumericDatatypes -> InferredTypes.knownFor(dt)
|
||||||
|
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
|
||||||
|
else -> InferredTypes.unknown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"sum" -> {
|
"sum" -> {
|
||||||
val dt=datatypeFromListArg(args.single())
|
when(datatypeFromIterableArg(args.single())) {
|
||||||
when(dt) {
|
DataType.UBYTE, DataType.UWORD -> InferredTypes.knownFor(DataType.UWORD)
|
||||||
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
|
DataType.BYTE, DataType.WORD -> InferredTypes.knownFor(DataType.WORD)
|
||||||
DataType.BYTE, DataType.WORD -> DataType.WORD
|
DataType.FLOAT -> InferredTypes.knownFor(DataType.FLOAT)
|
||||||
DataType.FLOAT -> DataType.FLOAT
|
DataType.ARRAY_UB, DataType.ARRAY_UW -> InferredTypes.knownFor(DataType.UWORD)
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_UW -> DataType.UWORD
|
DataType.ARRAY_B, DataType.ARRAY_W -> InferredTypes.knownFor(DataType.WORD)
|
||||||
DataType.ARRAY_B, DataType.ARRAY_W -> DataType.WORD
|
DataType.ARRAY_F -> InferredTypes.knownFor(DataType.FLOAT)
|
||||||
DataType.ARRAY_F -> DataType.FLOAT
|
DataType.STR -> InferredTypes.knownFor(DataType.UWORD)
|
||||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.UWORD
|
else -> InferredTypes.unknown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> return null
|
"len" -> {
|
||||||
|
// a length can be >255 so in that case, the result is an UWORD instead of an UBYTE
|
||||||
|
// but to avoid a lot of code duplication we simply assume UWORD in all cases for now
|
||||||
|
return InferredTypes.knownFor(DataType.UWORD)
|
||||||
|
}
|
||||||
|
else -> return InferredTypes.unknown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
||||||
|
class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
|
||||||
|
|
||||||
|
|
||||||
private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue {
|
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
if(constval.type!=DataType.FLOAT)
|
val float = constval.number.toDouble()
|
||||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
|
||||||
|
|
||||||
val float = constval.asNumericValue?.toDouble()!!
|
|
||||||
return numericLiteral(function(float), args[0].position)
|
return numericLiteral(function(float), args[0].position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun oneDoubleArgOutputWord(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue {
|
private fun oneDoubleArgOutputWord(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
if(constval.type!=DataType.FLOAT)
|
val float = constval.number.toDouble()
|
||||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
return NumericLiteralValue(DataType.WORD, function(float).toInt(), args[0].position)
|
||||||
return LiteralValue(DataType.WORD, wordvalue=function(constval.asNumericValue!!.toDouble()).toInt(), position=args[0].position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Int)->Number): LiteralValue {
|
private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Number): NumericLiteralValue {
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("built-in function requires one integer argument", position)
|
throw SyntaxError("built-in function requires one integer argument", position)
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
if(constval.type!=DataType.UBYTE && constval.type!=DataType.UWORD)
|
if(constval.type != DataType.UBYTE && constval.type!= DataType.UWORD)
|
||||||
throw SyntaxError("built-in function requires one integer argument", position)
|
throw SyntaxError("built-in function requires one integer argument", position)
|
||||||
|
|
||||||
val integer = constval.asNumericValue?.toInt()!!
|
val integer = constval.number.toInt()
|
||||||
return numericLiteral(function(integer).toInt(), args[0].position)
|
return numericLiteral(function(integer).toInt(), args[0].position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position,
|
private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Number>)->Number): NumericLiteralValue {
|
||||||
namespace:INameScope, heap: HeapValues,
|
|
||||||
function: (arg: Collection<Double>)->Number): LiteralValue {
|
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
||||||
val iterable = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
|
||||||
|
|
||||||
val result = if(iterable.arrayvalue != null) {
|
val array= args[0] as? ArrayLiteralValue ?: throw NotConstArgumentException()
|
||||||
val constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue }
|
val constElements = array.value.map{it.constValue(program)?.number}
|
||||||
if(null in constants)
|
if(constElements.contains(null))
|
||||||
throw NotConstArgumentException()
|
throw NotConstArgumentException()
|
||||||
function(constants.map { it!!.toDouble() }).toDouble()
|
|
||||||
} else {
|
return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
|
||||||
when(iterable.type) {
|
|
||||||
DataType.UBYTE, DataType.UWORD, DataType.FLOAT -> throw SyntaxError("function expects an iterable type", position)
|
|
||||||
else -> {
|
|
||||||
if(iterable.heapId==null)
|
|
||||||
throw FatalAstException("iterable value should be on the heap")
|
|
||||||
val array = heap.get(iterable.heapId).array ?: throw SyntaxError("function expects an iterable type", position)
|
|
||||||
function(array.map { it.toDouble() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return numericLiteral(result, args[0].position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position,
|
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
namespace:INameScope, heap: HeapValues,
|
// 1 arg, type = float or int, result type= isSameAs as argument type
|
||||||
function: (arg: Collection<Double>)->Boolean): LiteralValue {
|
|
||||||
if(args.size!=1)
|
|
||||||
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
|
||||||
val iterable = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
|
||||||
|
|
||||||
val result = if(iterable.arrayvalue != null) {
|
|
||||||
val constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue }
|
|
||||||
if(null in constants)
|
|
||||||
throw NotConstArgumentException()
|
|
||||||
function(constants.map { it!!.toDouble() })
|
|
||||||
} else {
|
|
||||||
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array argument", position)
|
|
||||||
function(array.map { it.toDouble() })
|
|
||||||
}
|
|
||||||
return LiteralValue.fromBoolean(result, position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
|
||||||
// 1 arg, type = float or int, result type= same as argument type
|
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("abs requires one numeric argument", position)
|
throw SyntaxError("abs requires one numeric argument", position)
|
||||||
|
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val number = constval.asNumericValue
|
return when (constval.type) {
|
||||||
return when (number) {
|
in IntegerDatatypes -> numericLiteral(abs(constval.number.toInt()), args[0].position)
|
||||||
is Int, is Byte, is Short -> numericLiteral(Math.abs(number.toInt()), args[0].position)
|
DataType.FLOAT -> numericLiteral(abs(constval.number.toDouble()), args[0].position)
|
||||||
is Double -> numericLiteral(Math.abs(number.toDouble()), args[0].position)
|
|
||||||
else -> throw SyntaxError("abs requires one numeric argument", position)
|
else -> throw SyntaxError("abs requires one numeric argument", position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinAvg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
|
// 1 arg, type = anything, result type = ubyte
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("avg requires array argument", position)
|
throw SyntaxError("sizeof requires one argument", position)
|
||||||
val iterable = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
if(args[0] !is IdentifierReference)
|
||||||
|
throw SyntaxError("sizeof argument should be an identifier", position)
|
||||||
|
|
||||||
val result = if(iterable.arrayvalue!=null) {
|
val dt = args[0].inferType(program)
|
||||||
val constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue }
|
if(dt.isKnown) {
|
||||||
if (null in constants)
|
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
|
||||||
throw NotConstArgumentException()
|
?: throw CannotEvaluateException("sizeof", "no target")
|
||||||
(constants.map { it!!.toDouble() }).average()
|
|
||||||
|
fun structSize(target: StructDecl) =
|
||||||
|
NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position)
|
||||||
|
|
||||||
|
return when {
|
||||||
|
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
|
||||||
|
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
||||||
|
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
|
||||||
|
numericLiteral(elementDt.memorySize() * length, position)
|
||||||
|
}
|
||||||
|
dt.istype(DataType.STRUCT) -> {
|
||||||
|
when (target) {
|
||||||
|
is VarDecl -> structSize(target.struct!!)
|
||||||
|
is StructDecl -> structSize(target)
|
||||||
|
else -> throw CompilerException("weird struct type $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
||||||
|
else -> NumericLiteralValue(DataType.UBYTE, dt.typeOrElse(DataType.STRUCT).memorySize(), position)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw SyntaxError("sizeof invalid argument type", position)
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("avg requires array argument", position)
|
|
||||||
array.average()
|
|
||||||
}
|
|
||||||
return numericLiteral(result, args[0].position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
// note: in some cases the length is > 255 and so we have to return a UWORD type instead of a byte.
|
if (args.size != 1)
|
||||||
|
throw SyntaxError("strlen requires one argument", position)
|
||||||
|
val argument=args[0]
|
||||||
|
if(argument is StringLiteralValue)
|
||||||
|
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
|
||||||
|
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
|
||||||
|
if(vardecl!=null) {
|
||||||
|
if(vardecl.datatype!=DataType.STR)
|
||||||
|
throw SyntaxError("strlen must have string argument", position)
|
||||||
|
if(vardecl.autogeneratedDontRemove) {
|
||||||
|
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw NotConstArgumentException()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
|
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("len requires one argument", position)
|
throw SyntaxError("len requires one argument", position)
|
||||||
var argument = args[0].constValue(namespace, heap)
|
|
||||||
if(argument==null) {
|
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
|
||||||
if(args[0] !is IdentifierReference)
|
var arraySize = directMemVar?.arraysize?.constIndex()
|
||||||
throw SyntaxError("len over weird argument ${args[0]}", position)
|
if(arraySize != null)
|
||||||
val target = (args[0] as IdentifierReference).targetStatement(namespace)
|
return NumericLiteralValue.optimalInteger(arraySize, position)
|
||||||
val argValue = (target as? VarDecl)?.value
|
if(args[0] is ArrayLiteralValue)
|
||||||
argument = argValue?.constValue(namespace, heap)
|
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
|
||||||
?: throw NotConstArgumentException()
|
if(args[0] !is IdentifierReference)
|
||||||
}
|
throw SyntaxError("len argument should be an identifier", position)
|
||||||
return when(argument.type) {
|
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
?: throw CannotEvaluateException("len", "no target vardecl")
|
||||||
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize
|
|
||||||
if(arraySize>256)
|
return when(target.datatype) {
|
||||||
throw CompilerException("array length exceeds byte limit ${argument.position}")
|
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F -> {
|
||||||
LiteralValue(DataType.UWORD, wordvalue=arraySize, position=args[0].position)
|
arraySize = target.arraysize?.constIndex()
|
||||||
|
if(arraySize==null)
|
||||||
|
throw CannotEvaluateException("len", "arraysize unknown")
|
||||||
|
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_F -> {
|
DataType.STR -> {
|
||||||
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize
|
val refLv = target.value as StringLiteralValue
|
||||||
if(arraySize>256)
|
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
|
||||||
throw CompilerException("array length exceeds byte limit ${argument.position}")
|
|
||||||
LiteralValue(DataType.UWORD, wordvalue=arraySize, position=args[0].position)
|
|
||||||
}
|
}
|
||||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
|
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
|
||||||
val str = argument.strvalue(heap)
|
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
|
||||||
if(str.length>255)
|
else -> throw CompilerException("weird datatype")
|
||||||
throw CompilerException("string length exceeds byte limit ${argument.position}")
|
|
||||||
LiteralValue(DataType.UWORD, wordvalue=str.length, position=args[0].position)
|
|
||||||
}
|
|
||||||
DataType.UBYTE, DataType.BYTE,
|
|
||||||
DataType.UWORD, DataType.WORD,
|
|
||||||
DataType.FLOAT -> throw SyntaxError("len of weird argument ${args[0]}", position)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun builtinMkword(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 2)
|
if (args.size != 2)
|
||||||
throw SyntaxError("mkword requires lsb and msb arguments", position)
|
throw SyntaxError("mkword requires msb and lsb arguments", position)
|
||||||
val constLsb = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val constMsb = args[1].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constLsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val result = (constMsb.asIntegerValue!! shl 8) or constLsb.asIntegerValue!!
|
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
|
||||||
return LiteralValue(DataType.UWORD, wordvalue = result, position = position)
|
return NumericLiteralValue(DataType.UWORD, result, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinSin8(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("sin8 requires one argument", position)
|
throw SyntaxError("sin8 requires one argument", position)
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.BYTE, bytevalue = (127.0* sin(rad)).toShort(), position = position)
|
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinSin8u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("sin8u requires one argument", position)
|
throw SyntaxError("sin8u requires one argument", position)
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.UBYTE, bytevalue = (128.0+127.5*sin(rad)).toShort(), position = position)
|
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinCos8(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("cos8 requires one argument", position)
|
throw SyntaxError("cos8 requires one argument", position)
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.BYTE, bytevalue = (127.0* cos(rad)).toShort(), position = position)
|
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinCos8u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("cos8u requires one argument", position)
|
throw SyntaxError("cos8u requires one argument", position)
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5*cos(rad)).toShort(), position = position)
|
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinSin16(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("sin16 requires one argument", position)
|
throw SyntaxError("sin16 requires one argument", position)
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.WORD, wordvalue = (32767.0* sin(rad)).toInt(), position = position)
|
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinSin16u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("sin16u requires one argument", position)
|
throw SyntaxError("sin16u requires one argument", position)
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5*sin(rad)).toInt(), position = position)
|
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinCos16(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("cos16 requires one argument", position)
|
throw SyntaxError("cos16 requires one argument", position)
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.WORD, wordvalue = (32767.0* cos(rad)).toInt(), position = position)
|
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinCos16u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("cos16u requires one argument", position)
|
throw SyntaxError("cos16u requires one argument", position)
|
||||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5* cos(rad)).toInt(), position = position)
|
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun numericLiteral(value: Number, position: Position): LiteralValue {
|
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
|
if (args.size != 1)
|
||||||
|
throw SyntaxError("sgn requires one argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toInt().toShort(), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
|
||||||
val floatNum=value.toDouble()
|
val floatNum=value.toDouble()
|
||||||
val tweakedValue: Number =
|
val tweakedValue: Number =
|
||||||
if(floatNum==Math.floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
|
if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
|
||||||
floatNum.toInt() // we have an integer disguised as a float.
|
floatNum.toInt() // we have an integer disguised as a float.
|
||||||
else
|
else
|
||||||
floatNum
|
floatNum
|
||||||
|
|
||||||
return when(tweakedValue) {
|
return when(tweakedValue) {
|
||||||
is Int -> LiteralValue.optimalNumeric(value.toInt(), position)
|
is Int -> NumericLiteralValue.optimalInteger(value.toInt(), position)
|
||||||
is Short -> LiteralValue.optimalNumeric(value.toInt(), position)
|
is Short -> NumericLiteralValue.optimalInteger(value.toInt(), position)
|
||||||
is Byte -> LiteralValue(DataType.UBYTE, bytevalue = value.toShort(), position = position)
|
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
|
||||||
is Double -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position)
|
is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
||||||
is Float -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position)
|
is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
||||||
else -> throw FatalAstException("invalid number type ${value::class}")
|
else -> throw FatalAstException("invalid number type ${value::class}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
211
compiler/src/prog8/optimizer/CallGraph.kt
Normal file
211
compiler/src/prog8/optimizer/CallGraph.kt
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
package prog8.optimizer
|
||||||
|
|
||||||
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.ParentSentinel
|
||||||
|
import prog8.ast.expressions.FunctionCall
|
||||||
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.processing.IAstVisitor
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.loadAsmIncludeFile
|
||||||
|
|
||||||
|
private val alwaysKeepSubroutines = setOf(
|
||||||
|
Pair("main", "start"),
|
||||||
|
Pair("irq", "irq")
|
||||||
|
)
|
||||||
|
|
||||||
|
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
||||||
|
private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
|
||||||
|
class CallGraph(private val program: Program) : IAstVisitor {
|
||||||
|
|
||||||
|
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||||
|
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||||
|
val calls = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||||
|
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||||
|
|
||||||
|
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
|
||||||
|
val usedSymbols = mutableSetOf<Statement>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
visit(program)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) {
|
||||||
|
fun findSubs(scope: INameScope) {
|
||||||
|
scope.statements.forEach {
|
||||||
|
if (it is Subroutine)
|
||||||
|
sub(it)
|
||||||
|
if (it is INameScope)
|
||||||
|
findSubs(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findSubs(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(program: Program) {
|
||||||
|
super.visit(program)
|
||||||
|
|
||||||
|
program.modules.forEach {
|
||||||
|
it.importedBy.clear()
|
||||||
|
it.imports.clear()
|
||||||
|
|
||||||
|
it.importedBy.addAll(importedBy.getValue(it))
|
||||||
|
it.imports.addAll(imports.getValue(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
val rootmodule = program.modules.first()
|
||||||
|
rootmodule.importedBy.add(rootmodule) // don't discard root module
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(block: Block) {
|
||||||
|
if (block.definingModule().isLibraryModule) {
|
||||||
|
// make sure the block is not removed
|
||||||
|
addNodeAndParentScopes(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.visit(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(directive: Directive) {
|
||||||
|
val thisModule = directive.definingModule()
|
||||||
|
if (directive.directive == "%import") {
|
||||||
|
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
|
||||||
|
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
|
||||||
|
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
|
||||||
|
} else if (directive.directive == "%asminclude") {
|
||||||
|
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
|
||||||
|
val scope = directive.definingScope()
|
||||||
|
scanAssemblyCode(asm, directive, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.visit(directive)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(identifier: IdentifierReference) {
|
||||||
|
// track symbol usage
|
||||||
|
val target = identifier.targetStatement(this.program.namespace)
|
||||||
|
if (target != null) {
|
||||||
|
addNodeAndParentScopes(target)
|
||||||
|
}
|
||||||
|
super.visit(identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addNodeAndParentScopes(stmt: Statement) {
|
||||||
|
usedSymbols.add(stmt)
|
||||||
|
var node: Node = stmt
|
||||||
|
do {
|
||||||
|
if (node is INameScope && node is Statement) {
|
||||||
|
usedSymbols.add(node)
|
||||||
|
}
|
||||||
|
node = node.parent
|
||||||
|
} while (node !is Module && node !is ParentSentinel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(subroutine: Subroutine) {
|
||||||
|
if (Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|
||||||
|
|| subroutine.definingModule().isLibraryModule) {
|
||||||
|
// make sure the entrypoint is mentioned in the used symbols
|
||||||
|
addNodeAndParentScopes(subroutine)
|
||||||
|
}
|
||||||
|
super.visit(subroutine)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(decl: VarDecl) {
|
||||||
|
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
|
||||||
|
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
|
||||||
|
addNodeAndParentScopes(decl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decl.datatype == DataType.STRUCT)
|
||||||
|
addNodeAndParentScopes(decl)
|
||||||
|
|
||||||
|
super.visit(decl)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(functionCall: FunctionCall) {
|
||||||
|
val otherSub = functionCall.target.targetSubroutine(program.namespace)
|
||||||
|
if (otherSub != null) {
|
||||||
|
functionCall.definingSubroutine()?.let { thisSub ->
|
||||||
|
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||||
|
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.visit(functionCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
|
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||||
|
if (otherSub != null) {
|
||||||
|
functionCallStatement.definingSubroutine()?.let { thisSub ->
|
||||||
|
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||||
|
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.visit(functionCallStatement)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(jump: Jump) {
|
||||||
|
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
|
||||||
|
if (otherSub != null) {
|
||||||
|
jump.definingSubroutine()?.let { thisSub ->
|
||||||
|
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||||
|
calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.visit(jump)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(structDecl: StructDecl) {
|
||||||
|
usedSymbols.add(structDecl)
|
||||||
|
usedSymbols.addAll(structDecl.statements)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(inlineAssembly: InlineAssembly) {
|
||||||
|
// parse inline asm for subroutine calls (jmp, jsr)
|
||||||
|
val scope = inlineAssembly.definingScope()
|
||||||
|
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
|
||||||
|
super.visit(inlineAssembly)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
|
||||||
|
asm.lines().forEach { line ->
|
||||||
|
val matches = asmJumpRx.matchEntire(line)
|
||||||
|
if (matches != null) {
|
||||||
|
val jumptarget = matches.groups[2]?.value
|
||||||
|
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
|
||||||
|
val node = program.namespace.lookup(jumptarget.split('.'), context)
|
||||||
|
if (node is Subroutine) {
|
||||||
|
calls[scope] = calls.getValue(scope).plus(node)
|
||||||
|
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||||
|
} else if (jumptarget.contains('.')) {
|
||||||
|
// maybe only the first part already refers to a subroutine
|
||||||
|
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
|
||||||
|
if (node2 is Subroutine) {
|
||||||
|
calls[scope] = calls.getValue(scope).plus(node2)
|
||||||
|
calledBy[node2] = calledBy.getValue(node2).plus(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val matches2 = asmRefRx.matchEntire(line)
|
||||||
|
if (matches2 != null) {
|
||||||
|
val target = matches2.groups[2]?.value
|
||||||
|
if (target != null && (target[0].isLetter() || target[0] == '_')) {
|
||||||
|
if (target.contains('.')) {
|
||||||
|
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||||
|
if (node is Subroutine) {
|
||||||
|
calls[scope] = calls.getValue(scope).plus(node)
|
||||||
|
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user