mirror of
https://github.com/irmen/prog8.git
synced 2025-06-15 02:23:36 +00:00
Compare commits
830 Commits
Author | SHA1 | Date | |
---|---|---|---|
e4bca5fe47 | |||
a1729b65ab | |||
2950d26c8e | |||
4f8d4a9585 | |||
d787795759 | |||
cf74e73e27 | |||
2770254fd9 | |||
de04bd8cfa | |||
076a547f91 | |||
dffd0a2706 | |||
6c66f86103 | |||
26502c949a | |||
8dfe510883 | |||
96ba9f5902 | |||
3a6ba0ab71 | |||
32d894d6b6 | |||
543efa4299 | |||
eba0708099 | |||
51e6bf0d45 | |||
07b5c44a54 | |||
9fe32c1c34 | |||
0e0278c84a | |||
dea775a9cd | |||
7e3e18a5c7 | |||
8e3ebc84f0 | |||
e6079dfd71 | |||
2b435fe6a5 | |||
4e640b11fd | |||
8b1e1e68fa | |||
fd11927708 | |||
cd500fee8c | |||
1bd32c0f19 | |||
7aefca3de0 | |||
f275ed96ea | |||
d14dac3872 | |||
b0213b0565 | |||
c677f0a875 | |||
6e65cb2c0a | |||
e65c5402d7 | |||
334f86480a | |||
0e62f5b759 | |||
edf9a500d3 | |||
001d01fdaf | |||
a95677564e | |||
4aca8bb8df | |||
5540482888 | |||
00d735249b | |||
b5289511ba | |||
b6ded8501f | |||
781915d2cf | |||
f4cef3eaf2 | |||
d23c2eed86 | |||
15695a304e | |||
6319269976 | |||
0ed3d951a7 | |||
2aa39757b4 | |||
39d32a3600 | |||
219d17de34 | |||
9bb5b454e4 | |||
2412f8c531 | |||
8701d684e6 | |||
b543cc34cd | |||
791dbbab9b | |||
ac0b1da3fc | |||
2f97aedc3c | |||
ab544ee965 | |||
fa527f8624 | |||
92ee0aefee | |||
99759ae853 | |||
81930312ff | |||
194fbcdd91 | |||
1e3930aae2 | |||
62dda4d891 | |||
2b870fb9f7 | |||
53f0318187 | |||
5e6e711f33 | |||
78af2cd4dc | |||
02cb237623 | |||
cc0f19653e | |||
4fff150c7b | |||
f6136891cc | |||
1e22170302 | |||
bdda6f502a | |||
1bbd77fddb | |||
321fdd10d1 | |||
9867dfcdeb | |||
7c09ac632c | |||
3502f65332 | |||
628390c3b5 | |||
bc37097df2 | |||
7d98275763 | |||
d6ffb549f6 | |||
bcd0db984d | |||
d9244f22c2 | |||
c97d76dbf2 | |||
9e05e97d7f | |||
1070dedd7c | |||
ccd1516637 | |||
f1f51a01c6 | |||
be75b8dbe5 | |||
02fae0e722 | |||
e35b739579 | |||
34aa6cc8a2 | |||
d7a6b20028 | |||
eb2d5bb1f8 | |||
cefef3d1be | |||
cc96ab7a9b | |||
49ea31c0a4 | |||
f1478d776b | |||
40e4cfb686 | |||
76f459ee95 | |||
c478718019 | |||
c27248a58b | |||
51bc539468 | |||
2395863e7e | |||
69c459c8ac | |||
c8855b2b10 | |||
a910c0fddb | |||
fd55611cac | |||
52f6be2bb0 | |||
857f930dc2 | |||
dd2c436dc6 | |||
9f047ba752 | |||
9d4ec4a9b2 | |||
cdc6d9aa65 | |||
997bc21feb | |||
975af4764d | |||
bf69219f98 | |||
f34f9329f1 | |||
90271d0dcd | |||
195cd7597d | |||
4a81406262 | |||
f9fd426843 | |||
e612056ecd | |||
6f0103398b | |||
afb60db382 | |||
5731b876ff | |||
055f917a2e | |||
4ed7fb771c | |||
c328e9018c | |||
b270f6f713 | |||
5c13918f11 | |||
40cc216557 | |||
1481f92cb0 | |||
76d54fbe5c | |||
9f72779cdc | |||
3dcef89a74 | |||
46373717b6 | |||
7277c08fa6 | |||
04e75455c4 | |||
8ac17ae14e | |||
cb5d6ddf80 | |||
e0794db33a | |||
b128b79132 | |||
79e6d4b8dd | |||
b9ddde0f12 | |||
a0ec37b35b | |||
506ac8014c | |||
72b4198301 | |||
24eee0cb34 | |||
9fc0c3f849 | |||
db314ed903 | |||
1ef9b8be61 | |||
79782ad547 | |||
4b6d045df1 | |||
b4d1d545a8 | |||
f61682cdc7 | |||
d61420f1c6 | |||
3d09d605e1 | |||
025dde264a | |||
87cee7a0fd | |||
61784a03bb | |||
9d9ca0f08d | |||
58f37513e7 | |||
ee7f9d457d | |||
bec2224c3d | |||
4305984168 | |||
07dd64958f | |||
76101d7f8d | |||
7d6a0ab256 | |||
4309a0dc68 | |||
dde6919446 | |||
54fc9c91ac | |||
41658c97a3 | |||
45c9cc97d9 | |||
6fa7debee5 | |||
ee9f662016 | |||
3550e1214c | |||
8dcb43ad1c | |||
e6a1442296 | |||
cb65480c6c | |||
3e7c7ab497 | |||
f0930d8a18 | |||
5a846bdeb5 | |||
baf9dfb46c | |||
edd3a22848 | |||
583428b19c | |||
08d44ae553 | |||
b3b2541c1e | |||
8e927e0b73 | |||
8e3e996f4a | |||
b6fa361bcc | |||
ca83092aed | |||
3cda92331e | |||
c989abe265 | |||
89230ade7a | |||
b4931c9a1f | |||
ddfcf45d40 | |||
ee12236d53 | |||
df6698c98f | |||
c3b82f2cfa | |||
64c89f1c8f | |||
e09b65ea94 | |||
c81952c356 | |||
f80e462d25 | |||
51f32677b7 | |||
4b366358c4 | |||
3378586098 | |||
6777d952c1 | |||
6c8b18ddbd | |||
69780ecde9 | |||
9e2c52e1ec | |||
6cb0e6a936 | |||
dd82e550d5 | |||
cdcda27d07 | |||
ffffcdd50a | |||
d37d62574c | |||
f2380457d6 | |||
efa42d5d96 | |||
e17c18b653 | |||
7607d3d64a | |||
d7d7147d43 | |||
b40e1eabb9 | |||
3b8e18004c | |||
4c03950c28 | |||
170a0183f8 | |||
c62ff16f8b | |||
ab495fe6e1 | |||
c2a8dc23d0 | |||
6734ae3c88 | |||
4c1c595f14 | |||
9002c67639 | |||
b91aabd3c0 | |||
3307f673f6 | |||
07b00bec61 | |||
e0d2b60d8b | |||
45bfecee73 | |||
80e3a11268 | |||
38a6c6a866 | |||
8f224afed9 | |||
48a4c46a6c | |||
7d08380c7f | |||
b3b3cf3807 | |||
f0f6150e18 | |||
dc600cc3ed | |||
ae648b8a0a | |||
583af3bd4f | |||
d65cfbf093 | |||
118aed2e31 | |||
85abf4d123 | |||
44b8291540 | |||
d6444bba66 | |||
5a2f8fdfe1 | |||
bba4f84503 | |||
684e081399 | |||
96c700ee46 | |||
5f15794c3b | |||
a40b3134f4 | |||
c70b4daf87 | |||
928611eb20 | |||
f1d55c688a | |||
d22df22f7d | |||
061e1be0a4 | |||
950bc4b937 | |||
dcb81e6bea | |||
daaa83ee7d | |||
b7c1450121 | |||
787f52d1f8 | |||
50213f146a | |||
7f2aea60c9 | |||
168621f7c2 | |||
8b630798d8 | |||
52e8a44517 | |||
59f33658ad | |||
e0315bffdc | |||
cd28d0c0e0 | |||
0baa2c8b23 | |||
4977d1fbd5 | |||
3b7a92f1b4 | |||
f6920172dd | |||
93bfc8f5f4 | |||
39b7655264 | |||
8b75ceb412 | |||
c39fc4010d | |||
8df778a515 | |||
5134ea76bf | |||
3ba37df29d | |||
e221d674d9 | |||
251f947293 | |||
41e1e1cbb0 | |||
da1bc351d2 | |||
43c0afdea0 | |||
add5bfa2ec | |||
34babfb5de | |||
4f6c45c86c | |||
e6220a464c | |||
8dcd49934a | |||
bedc3bdb56 | |||
83ceb0fde9 | |||
1d299c56e0 | |||
0d735c2ccc | |||
4094f89d4a | |||
cf1e8b194a | |||
74e5644f55 | |||
b5dc5fc615 | |||
7a7270d769 | |||
7549ddcd2b | |||
08f0303178 | |||
0d7a291b81 | |||
2265ae9600 | |||
cba502e87a | |||
ac94236614 | |||
ddf1be2a13 | |||
b7694686c2 | |||
63332c0530 | |||
8a504f8eee | |||
106fc5daa4 | |||
7accb73993 | |||
e9aa6a0956 | |||
df20467e03 | |||
ecbd9d739e | |||
8af17c295a | |||
329b28cad1 | |||
452c29574d | |||
5bedc1b333 | |||
0bf6d2f72c | |||
c09b8af491 | |||
260bcd3a55 | |||
6b5211ad12 | |||
a92ec14989 | |||
b3348eb22b | |||
bec5a261e5 | |||
4b53641e1d | |||
00071d53d5 | |||
6902834568 | |||
fa2d87f3dd | |||
44019d1a61 | |||
6f74fb49bd | |||
a303b39cf0 | |||
3e63a29c59 | |||
261c0fc9b6 | |||
895b30f7e5 | |||
b985604e22 | |||
f7953e4ef3 | |||
63483d1f0e | |||
8b981f03bf | |||
d0d0910bf2 | |||
57ac820767 | |||
b8bda867b6 | |||
05d3a2450c | |||
d40788adfa | |||
83fbf86b1c | |||
e876008427 | |||
2b43353eb4 | |||
a74403c347 | |||
2f4c6c8697 | |||
238d8197f5 | |||
53a600d87b | |||
2a0ffaf45d | |||
936b046ed9 | |||
378dcfe351 | |||
0a330b9288 | |||
a88b40d6c1 | |||
09f25ffbd9 | |||
ab1232d742 | |||
a7f56fe0fc | |||
58a9452c36 | |||
6d8c4f403f | |||
88b80fed90 | |||
acdbd0c391 | |||
d9a8cfed8c | |||
122796fbba | |||
510ca042c9 | |||
125f6205f2 | |||
8136f3df5c | |||
38d06a7e94 | |||
49db10539a | |||
8efe4c6267 | |||
04d8bb8fbf | |||
08aa13c90c | |||
d1febc0208 | |||
5980e58ac6 | |||
e1dc283d4b | |||
8be234973c | |||
7def8ff2cd | |||
340b1c2e42 | |||
7e0f7ba438 | |||
fefd9b52a8 | |||
afd155ac4f | |||
ee724eb4f1 | |||
2f1f20ea11 | |||
063bcf17d8 | |||
72509eef44 | |||
2da28864e9 | |||
4278f64682 | |||
59ae3c3fcd | |||
7fa21fbdff | |||
e95af7498e | |||
79c75adac1 | |||
d212f69d89 | |||
edf5e69d39 | |||
574eb0d174 | |||
8bd4914e2f | |||
5ebaaff64b | |||
5c9e0c9f51 | |||
8132edbb08 | |||
d29ce78c86 | |||
94bc9d7a69 | |||
e8faec0932 | |||
69ca4fe304 | |||
cd99fe46fd | |||
4825b4dc68 | |||
8d0607ef58 | |||
225295a7d8 | |||
4cd74daf53 | |||
6eb9118197 | |||
d0bd2f522c | |||
661c757236 | |||
aaa20093ef | |||
1eecdd6fa3 | |||
800b5b2a43 | |||
9d17421c66 | |||
0edd50e956 | |||
288d4f08b3 | |||
526e4b8bdc | |||
e0c5ccc16b | |||
ebc2c614d7 | |||
29f5a85158 | |||
8af2380a47 | |||
431f2a2088 | |||
e05ea887f6 | |||
95c0425151 | |||
47cbc7b1f9 | |||
e7b75d591c | |||
99f7d469f4 | |||
8a6ef17fbf | |||
5f337a0bd9 | |||
87862f772a | |||
3ab641aa21 | |||
3efa8da8e0 | |||
3e28ed4fe4 | |||
44949460ed | |||
83cc19ad6f | |||
66bb98c479 | |||
ff3f985658 | |||
2ba6c9ccbe | |||
3eaf111e7d | |||
30da26b9a9 | |||
e35ad0cc8f | |||
1a36302cf1 | |||
82a28bb555 | |||
c1ce0be451 | |||
c0a5f8fef0 | |||
702cf304d0 | |||
4dee8b6048 | |||
ec665e0cc1 | |||
aec3b82476 | |||
e83796b5b9 | |||
8eb69d6eda | |||
74b5124a42 | |||
b9706a180b | |||
8aeb8a9bb7 | |||
8f2e166a22 | |||
fdd91170dc | |||
c40ddb061b | |||
353d6cfc55 | |||
f37564c49c | |||
157484d94b | |||
7626c9fff7 | |||
1f55f9fc49 | |||
2554bc7ef8 | |||
7cb4100419 | |||
2d3b7eb878 | |||
4d01a78731 | |||
a03e36828a | |||
260fb65b06 | |||
9fb8526136 | |||
26fc5ff5e2 | |||
5060f0bb19 | |||
beaf6d449b | |||
4d68b508a2 | |||
cd825e386d | |||
095c8b2309 | |||
8b6eb74c58 | |||
aba437e5a2 | |||
efe3ed499b | |||
5595564a1f | |||
439761cb67 | |||
bee6c65293 | |||
10145b946b | |||
ebf4b50059 | |||
07cce3b3fc | |||
f2c19afd95 | |||
d159e70e1c | |||
ac693a2541 | |||
1e988116ce | |||
ec9e722927 | |||
4cd5e8c378 | |||
b759d5e06a | |||
1469033c1e | |||
c15fd75df7 | |||
73524e01a6 | |||
9e54e11113 | |||
01ac5f29db | |||
67a2241e32 | |||
72b6dc3de7 | |||
6f5b645995 | |||
458ad1de57 | |||
216f48b7c1 | |||
b2d1757e5a | |||
6e53eb9d5c | |||
e5ee5be9c5 | |||
bd237b2b95 | |||
d31cf766eb | |||
56d530ff04 | |||
0bbb2240f2 | |||
1c8e4dba73 | |||
4a9956c4a4 | |||
59c0e6ae32 | |||
94c30fc21e | |||
8bb3b3be20 | |||
85e3c2c5a2 | |||
4be381c597 | |||
6ff5470cf1 | |||
151dcfdef9 | |||
c282b4cb9f | |||
c426f4626c | |||
0e3c92626e | |||
5099525e24 | |||
e22b4cbb67 | |||
2b48828179 | |||
3e181362dd | |||
71fd98e39e | |||
71cd8b6d51 | |||
ad75fcbf7e | |||
f8b04a6357 | |||
d8fcbb78d3 | |||
8408bf3789 | |||
3e1185658e | |||
d778cdcd61 | |||
90b303fc03 | |||
eb86b1270d | |||
a1f0cc878b | |||
f2e2720b15 | |||
ec8cfe1591 | |||
22eac159e5 | |||
956b0c3fa7 | |||
a6427e0949 | |||
22031f39b0 | |||
c4673d3a67 | |||
e83e021541 | |||
c1f2ecd413 | |||
46fbe01df9 | |||
8647a8290e | |||
bac51f4b31 | |||
582aab180a | |||
5fb714fcb2 | |||
3994de77d0 | |||
24c8d1f1f4 | |||
110f877dcc | |||
9cd3a9f8e8 | |||
1464050bf5 | |||
95e9e1b550 | |||
bda1c1c1eb | |||
d020a7974a | |||
a51fad3aab | |||
3cd32778bb | |||
8d67056f84 | |||
e986973b5e | |||
448c934cba | |||
96ef7ba55d | |||
4372de1e7e | |||
af0fb88adf | |||
066233eee8 | |||
b6f85d10b0 | |||
6f75413c09 | |||
d45fe4ce74 | |||
e828c013e6 | |||
988459f744 | |||
7c701bdf3f | |||
446fc35d5c | |||
bec9cc7047 | |||
961380acb6 | |||
84c0685a60 | |||
629222f103 | |||
8c448e5bc2 | |||
b5fa6c2d0a | |||
680b2df08a | |||
09bd47f98b | |||
7f69f9ce4f | |||
4179b4e543 | |||
66364554c4 | |||
43f2448789 | |||
130cee1e70 | |||
b976360248 | |||
225bfc4164 | |||
d7ceda4d82 | |||
14d091e60a | |||
2809668ef4 | |||
bafb86e00b | |||
f5db31b8ff | |||
e1d0dbed0c | |||
1d1fe364d0 | |||
2b9316c4ff | |||
c50cbbb526 | |||
b93d9ecd7e | |||
96243db88b | |||
4daf75a8cc | |||
8c63d7cf5b | |||
6f78a32e64 | |||
af6731c9c8 | |||
25cf0d2b94 | |||
9389791d91 | |||
aa8191d0a1 | |||
0d5c78e875 | |||
e8679ae03b | |||
d1d224b7fc | |||
df995f7bc9 | |||
af39502450 | |||
ffa38955d6 | |||
8d82fb6d8f | |||
306770331a | |||
d3f433c8cf | |||
cf49cbd1f8 | |||
8a99e75299 | |||
2dbf849c82 | |||
ba3dce0b4c | |||
ca9588380a | |||
ae2619602d | |||
de06353194 | |||
3ff3f5e1cc | |||
4b747859b3 | |||
2201765366 | |||
dfa1d5e398 | |||
ce9a90f626 | |||
2deb18beb2 | |||
0f7454059c | |||
f9ba09ac4d | |||
4e74873eae | |||
f0cd03d14f | |||
f2b069c562 | |||
bc89306dc1 | |||
bf4da1655b | |||
d819aa270f | |||
e6d945f835 | |||
4fe408f1fd | |||
c376e42092 | |||
63a653cdf0 | |||
5d900800f2 | |||
def06dbc0b | |||
9b66a597bb | |||
f1ee3b4e60 | |||
6395e39d63 | |||
2a6d9d7e31 | |||
32a7cd31da | |||
dd4a56cb5f | |||
d110d1cb5f | |||
48858019b7 | |||
aff6b1fca5 | |||
d260182ef3 | |||
e39a38b0d9 | |||
82d7179c92 | |||
f42746ba06 | |||
1f69deaccd | |||
ea8b7ab193 | |||
9938959026 | |||
d5e5485d2e | |||
97b9c8f320 | |||
35aebbc209 | |||
81f7419f70 | |||
2f951bd54d | |||
18f5963b09 | |||
836509c1d1 | |||
949d536e42 | |||
f69b17e165 | |||
49a0584c54 | |||
e21aa2c8f0 | |||
40071b1431 | |||
02e29e6990 | |||
e19de0901e | |||
137d506e42 | |||
90c4a26d52 | |||
f378a8997b | |||
1377bed988 | |||
8f9f947c42 | |||
37f6c2858f | |||
13d7f239ab | |||
a6f3c84e28 | |||
fe4e0e9835 | |||
809917f13b | |||
2b35498370 | |||
f45eabdd9e | |||
438f3ee8d2 | |||
4bea31f051 | |||
5eae7a2b93 | |||
364ef3e55c | |||
e61818f194 | |||
0f9ce319d4 | |||
5d90871789 | |||
88a9e09918 | |||
c50ecf6055 | |||
a18de75da9 | |||
e112dfd910 | |||
9154d8bd37 | |||
0b55372b3b | |||
3ad7fb010f | |||
3f64d1bb5a | |||
a6f564ad88 | |||
d97da3bb7b | |||
a77d3c92ad | |||
6d17e5307c | |||
c2205e473a | |||
4ffb194847 | |||
744cd6ec42 | |||
f08fc18ab5 | |||
462af76770 | |||
9cec554f7c | |||
08b25e610d | |||
e896d5a1a6 | |||
b939562062 | |||
256781bba5 | |||
19705196d6 | |||
3ce692bb10 | |||
78bdbde3ae | |||
8d8c066447 | |||
5da9379c37 | |||
032d20ff37 | |||
d19b17cbfe | |||
4a4f8ff5db | |||
60a9209a14 | |||
0f9e167df3 | |||
2e2b8c498e | |||
144199730f | |||
4bb4eab3b2 | |||
cf9151f669 | |||
aef4598cec | |||
3ada0fdf84 | |||
a5d97b326e | |||
2640015fb1 | |||
6cd42ddafe | |||
1f17c22132 | |||
5c62f612cc | |||
b9ca1c2e2c | |||
93b2ff2e52 | |||
3991d23a69 | |||
1be139759c | |||
d0674ad688 | |||
ffb47458ff | |||
84ec1be8a4 | |||
f4dafec645 | |||
97ce72521d | |||
d2f0e74879 | |||
d9e3895c45 | |||
5075901830 | |||
f1193bb5a0 | |||
d3dc279105 | |||
acc942f690 | |||
e947067dcf | |||
bd9ebf4603 | |||
f41192a52a | |||
ff54d6abd7 | |||
f40bcc219f | |||
679965410a | |||
c6e13ae2a3 | |||
20cdcc673b | |||
89f46222d9 | |||
b27cbfac5e | |||
31c946aeeb | |||
bfc8a26381 | |||
9d98746501 | |||
63b03ba70c | |||
70bab76b36 | |||
15d24d4308 | |||
9ec62eb045 | |||
12f841e30d | |||
335599ed22 | |||
0b717f9e76 | |||
e941f6ecca | |||
ef7744dbda | |||
c83a61c460 | |||
335684caf7 | |||
8d6220ce51 | |||
39ea5c5f99 | |||
b03597ac13 | |||
58f323c087 | |||
513a68584c | |||
88d5c68b32 | |||
14f9382cf9 | |||
cffb582568 | |||
e1812ce16c | |||
7a3163f59a | |||
6f3b2749b0 | |||
c144d4e501 | |||
edfd9d55ba | |||
774897260e | |||
65ba91411d | |||
9cbb8e1a64 | |||
53e9ad5088 | |||
cf6ea63fa6 | |||
1de0ebb7bc | |||
77c1376d6d | |||
353f1954a5 | |||
8bf3406cf8 | |||
936bf9a05c | |||
4487499663 | |||
3976cc26a2 | |||
e6ff87ecd0 | |||
c0887b5f08 | |||
f14dda4eca | |||
bd7f75c130 | |||
fbe3ce008b | |||
7ac6c8f2d1 | |||
fdfbb7bdf0 | |||
1c16bbb742 | |||
9735527062 | |||
402827497e | |||
f81aa0d867 | |||
d32a970101 | |||
cd651aa416 | |||
8a3189123a |
2
.gitignore
vendored
2
.gitignore
vendored
@ -29,3 +29,5 @@ parsetab.py
|
|||||||
|
|
||||||
.gradle
|
.gradle
|
||||||
/prog8compiler.jar
|
/prog8compiler.jar
|
||||||
|
sd*.img
|
||||||
|
|
||||||
|
7
.idea/inspectionProfiles/Project_Default.xml
generated
7
.idea/inspectionProfiles/Project_Default.xml
generated
@ -3,14 +3,11 @@
|
|||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
<Languages>
|
<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 minSize="70" name="Kotlin" />
|
||||||
<language isEnabled="false" name="TypeScript" />
|
<language isEnabled="false" name="Groovy" />
|
||||||
<language isEnabled="false" name="ActionScript" />
|
|
||||||
</Languages>
|
</Languages>
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
|
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
|
||||||
<option name="processCode" value="false" />
|
<option name="processCode" value="false" />
|
||||||
<option name="processLiterals" value="true" />
|
<option name="processLiterals" value="true" />
|
||||||
|
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="Kotlin2JvmCompilerArguments">
|
<component name="Kotlin2JvmCompilerArguments">
|
||||||
<option name="jvmTarget" value="1.8" />
|
<option name="jvmTarget" value="11" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
9
.idea/libraries/antlr_4_7_2_complete.xml
generated
9
.idea/libraries/antlr_4_7_2_complete.xml
generated
@ -1,9 +0,0 @@
|
|||||||
<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>
|
|
@ -1,7 +1,7 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="antlr-4.8-complete">
|
<library name="antlr-4.9-complete">
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.8-complete.jar!/" />
|
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.9-complete.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
9
.idea/libraries/antlr_runtime_4_7_2.xml
generated
9
.idea/libraries/antlr_runtime_4_7_2.xml
generated
@ -1,9 +0,0 @@
|
|||||||
<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>
|
|
@ -1,7 +1,7 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="antlr-runtime-4.8">
|
<library name="antlr-runtime-4.9">
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.8.jar!/" />
|
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.9.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
9
.idea/libraries/dbus_java_3_2_4.xml
generated
Normal file
9
.idea/libraries/dbus_java_3_2_4.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="dbus-java-3.2.4">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/dbusCompilerService/lib/dbus-java-3.2.4.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
10
.idea/libraries/javax_json_api_1_1_4.xml
generated
Normal file
10
.idea/libraries/javax_json_api_1_1_4.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="javax.json-api-1.1.4">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-api-1.1.4.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-1.1.4.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
@ -1,7 +1,7 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="kotlinx-cli-jvm-0.1.0-dev-5">
|
<library name="kotlinx-cli-jvm">
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar!/" />
|
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.3.1.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
10
.idea/libraries/slf4j_api_1_7_30.xml
generated
Normal file
10
.idea/libraries/slf4j_api_1_7_30.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="slf4j-api-1.7.30">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-api-1.7.30.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-simple-1.7.30.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
12
.idea/libraries/takes_http.xml
generated
Normal file
12
.idea/libraries/takes_http.xml
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="takes-http">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/cactoos-0.42.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-lang3-3.7.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-text-1.4.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/takes-1.19.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -16,7 +16,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
3
.idea/modules.xml
generated
3
.idea/modules.xml
generated
@ -3,8 +3,11 @@
|
|||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
|
@ -4,8 +4,8 @@ sudo: false
|
|||||||
# dist: xenial
|
# dist: xenial
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- chmod +x gradlew
|
- chmod +x ./gradlew
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- gradle test
|
- ./gradlew test
|
||||||
|
|
||||||
|
112
README.md
112
README.md
@ -2,61 +2,77 @@
|
|||||||
[](https://travis-ci.org/irmen/prog8)
|
[](https://travis-ci.org/irmen/prog8)
|
||||||
[](https://prog8.readthedocs.io/)
|
[](https://prog8.readthedocs.io/)
|
||||||
|
|
||||||
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
|
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
||||||
===========================================================================
|
============================================================================
|
||||||
|
|
||||||
*Written by Irmen de Jong (irmen@razorvine.net)*
|
*Written by Irmen de Jong (irmen@razorvine.net)*
|
||||||
|
|
||||||
*Software license: GNU GPL 3.0, see file LICENSE*
|
*Software license: GNU GPL 3.0, see file LICENSE*
|
||||||
|
|
||||||
|
|
||||||
This is a structured programming language for the 8-bit 6502/6510 microprocessor from the late 1970's and 1980's
|
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
|
||||||
as used in many home computers from that era. It is a medium to low level programming language,
|
as used in many home computers from that era. It is a medium to low level programming language,
|
||||||
which aims to provide many conveniences over raw assembly code (even when using a macro assembler):
|
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
|
||||||
|
|
||||||
- reduction of source code length
|
Documentation
|
||||||
|
-------------
|
||||||
|
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
|
||||||
|
https://prog8.readthedocs.io/
|
||||||
|
|
||||||
|
|
||||||
|
What does Prog8 provide?
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
- reduction of source code length over raw assembly
|
||||||
- modularity, symbol scoping, subroutines
|
- modularity, symbol scoping, subroutines
|
||||||
- various data types other than just bytes (16-bit words, floats, strings)
|
- various data types other than just bytes (16-bit words, floats, strings)
|
||||||
- automatic variable allocations, automatic string and array variables and string sharing
|
- automatic variable allocations, automatic string and array variables and string sharing
|
||||||
- subroutines with an input- and output parameter signature
|
- subroutines with input parameters and result values
|
||||||
- constant folding in expressions
|
- high-level program optimizations
|
||||||
|
- small program boilerplate/compilersupport overhead
|
||||||
|
- sane variable initialization, programs can be restarted again just fine after exiting to basic
|
||||||
- conditional branches
|
- conditional branches
|
||||||
|
- floating point operations (requires the C64 Basic ROM routines for this)
|
||||||
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||||
- structs to group together sets of variables and manipulate them at once
|
- structs to group together sets of variables and manipulate them at once
|
||||||
- floating point operations (requires the C64 Basic ROM routines for this)
|
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
|
||||||
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
- various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||||
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
|
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||||
|
- fast execution speed due to compilation to native assembly code
|
||||||
|
- variables are allocated statically
|
||||||
- inline assembly allows you to have full control when every cycle or byte matters
|
- inline assembly allows you to have full control when every cycle or byte matters
|
||||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
- supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, and provides them also on the C64.
|
||||||
|
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
|
||||||
|
|
||||||
Rapid edit-compile-run-debug cycle:
|
*Rapid edit-compile-run-debug cycle:*
|
||||||
|
|
||||||
- use a modern PC to do the work on
|
- use a modern PC to do the work on, use nice editors and enjoy quick compilation times
|
||||||
- very quick compilation times
|
|
||||||
- can automatically run the program in the Vice emulator after succesful compilation
|
- 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
|
||||||
|
|
||||||
Prog8 is mainly targeted at the Commodore-64 machine at this time.
|
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
|
||||||
Contributions to add support for other 8-bit (or other?!) machines are welcome.
|
|
||||||
|
|
||||||
Documentation/manual
|
- "c64": Commodore-64 (6510 CPU = almost a 6502)
|
||||||
--------------------
|
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
|
||||||
https://prog8.readthedocs.io/
|
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||||
|
|
||||||
Required tools
|
|
||||||
--------------
|
|
||||||
|
Additional required tools
|
||||||
|
-------------------------
|
||||||
|
|
||||||
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
||||||
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
||||||
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 a prepackaged version of the compiler.
|
A **Java runtime (jre or jdk), version 11 or newer** is required to run a prepackaged version of the compiler.
|
||||||
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
|
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
|
||||||
IntelliJ IDEA with the Kotlin plugin).
|
IntelliJ IDEA with the Kotlin plugin).
|
||||||
|
|
||||||
It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence
|
It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence
|
||||||
of the [Vice emulator](http://vice-emu.sourceforge.net/)
|
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
|
||||||
|
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
|
||||||
|
|
||||||
|
|
||||||
Example code
|
Example code
|
||||||
@ -64,44 +80,43 @@ Example code
|
|||||||
|
|
||||||
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||||
|
|
||||||
%import c64utils
|
%import textio
|
||||||
%zeropage basicsafe
|
%zeropage basicsafe
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
|
||||||
ubyte[256] sieve
|
ubyte[256] sieve
|
||||||
ubyte candidate_prime = 2
|
ubyte candidate_prime = 2 ; is increased in the loop
|
||||||
|
|
||||||
sub start() {
|
sub start() {
|
||||||
memset(sieve, 256, false)
|
sys.memset(sieve, 256, false) ; clear the sieve
|
||||||
|
txt.print("prime numbers up to 255:\n\n")
|
||||||
c64scr.print("prime numbers up to 255:\n\n")
|
|
||||||
ubyte amount=0
|
ubyte amount=0
|
||||||
while true {
|
repeat {
|
||||||
ubyte prime = find_next_prime()
|
ubyte prime = find_next_prime()
|
||||||
if prime==0
|
if prime==0
|
||||||
break
|
break
|
||||||
c64scr.print_ub(prime)
|
txt.print_ub(prime)
|
||||||
c64scr.print(", ")
|
txt.print(", ")
|
||||||
amount++
|
amount++
|
||||||
}
|
}
|
||||||
c64.CHROUT('\n')
|
txt.nl()
|
||||||
c64scr.print("number of primes (expected 54): ")
|
txt.print("number of primes (expected 54): ")
|
||||||
c64scr.print_ub(amount)
|
txt.print_ub(amount)
|
||||||
c64.CHROUT('\n')
|
txt.nl()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub find_next_prime() -> ubyte {
|
sub find_next_prime() -> ubyte {
|
||||||
|
|
||||||
while sieve[candidate_prime] {
|
while sieve[candidate_prime] {
|
||||||
candidate_prime++
|
candidate_prime++
|
||||||
if candidate_prime==0
|
if candidate_prime==0
|
||||||
return 0
|
return 0 ; we wrapped; no more primes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
; found next one, mark the multiples and return it.
|
||||||
sieve[candidate_prime] = true
|
sieve[candidate_prime] = true
|
||||||
uword multiple = candidate_prime
|
uword multiple = candidate_prime
|
||||||
|
|
||||||
while multiple < len(sieve) {
|
while multiple < len(sieve) {
|
||||||
sieve[lsb(multiple)] = true
|
sieve[lsb(multiple)] = true
|
||||||
multiple += candidate_prime
|
multiple += candidate_prime
|
||||||
@ -111,11 +126,12 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
when compiled an ran on a C-64 you'll get:
|
when compiled an ran on a C-64 you'll get:
|
||||||
|
|
||||||

|

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

|

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

|

|
||||||
|
|
||||||
|
There are a couple of examples specially made for the CommanderX16 compiler target.
|
||||||
|
For instance here's a well known space ship animated in 3D with hidden line removal,
|
||||||
|
in the CommanderX16 emulator:
|
||||||
|
|
||||||
|

|
||||||
|
@ -1,41 +1,29 @@
|
|||||||
buildscript {
|
|
||||||
dependencies {
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// id "org.jetbrains.kotlin.jvm" version "1.3.72"
|
|
||||||
id 'application'
|
|
||||||
id 'org.jetbrains.dokka' version "0.9.18"
|
|
||||||
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
|
||||||
id 'java'
|
id 'java'
|
||||||
|
id 'application'
|
||||||
|
id "org.jetbrains.kotlin.jvm" version "1.4.30"
|
||||||
|
id 'org.jetbrains.dokka' version "0.9.18"
|
||||||
|
id 'com.github.johnrengelman.shadow' version '6.1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: "kotlin"
|
targetCompatibility = 11
|
||||||
apply plugin: "java"
|
sourceCompatibility = 11
|
||||||
|
|
||||||
targetCompatibility = 1.8
|
|
||||||
sourceCompatibility = 1.8
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||||
maven { url "https://dl.bintray.com/orangy/maven/" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':parser')
|
implementation project(':compilerAst')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
implementation 'org.antlr:antlr4-runtime:4.8'
|
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
|
// implementation 'net.razorvine:ksim65:1.8'
|
||||||
// implementation 'net.razorvine:ksim65:1.6'
|
// implementation "com.github.hypfvieh:dbus-java:3.2.4"
|
||||||
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
|
|
||||||
implementation project(':parser')
|
|
||||||
|
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
|
||||||
@ -45,7 +33,8 @@ dependencies {
|
|||||||
|
|
||||||
compileKotlin {
|
compileKotlin {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "11"
|
||||||
|
useIR = true
|
||||||
// verbose = true
|
// verbose = true
|
||||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||||
}
|
}
|
||||||
@ -53,7 +42,8 @@ compileKotlin {
|
|||||||
|
|
||||||
compileTestKotlin {
|
compileTestKotlin {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "11"
|
||||||
|
useIR = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +66,8 @@ sourceSets {
|
|||||||
startScripts.enabled = true
|
startScripts.enabled = true
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClassName = 'prog8.CompilerMainKt'
|
mainClass = 'prog8.CompilerMainKt'
|
||||||
|
mainClassName = 'prog8.CompilerMainKt' // deprecated
|
||||||
applicationName = 'p8compile'
|
applicationName = 'p8compile'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,3 +101,7 @@ dokka {
|
|||||||
outputFormat = 'html'
|
outputFormat = 'html'
|
||||||
outputDirectory = "$buildDir/kdoc"
|
outputDirectory = "$buildDir/kdoc"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task wrapper(type: Wrapper) {
|
||||||
|
gradleVersion = '6.7'
|
||||||
|
}
|
||||||
|
@ -8,12 +8,11 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="module" module-name="parser" />
|
|
||||||
<orderEntry type="library" name="unittest-libs" level="project" />
|
<orderEntry type="library" name="unittest-libs" level="project" />
|
||||||
<orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" />
|
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
|
||||||
<orderEntry type="library" name="antlr-runtime-4.8" level="project" />
|
<orderEntry type="module" module-name="compilerAst" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
Binary file not shown.
Binary file not shown.
BIN
compiler/lib/kotlinx-cli-jvm-0.3.1.jar
Normal file
BIN
compiler/lib/kotlinx-cli-jvm-0.3.1.jar
Normal file
Binary file not shown.
20
compiler/res/.editorconfig
Normal file
20
compiler/res/.editorconfig
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 120
|
||||||
|
tab_width = 8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
ij_smart_tabs = true
|
||||||
|
|
||||||
|
[*.p8]
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[*.asm]
|
||||||
|
indent_size = 8
|
||||||
|
indent_style = tab
|
678
compiler/res/prog8lib/c64/floats.asm
Normal file
678
compiler/res/prog8lib/c64/floats.asm
Normal file
@ -0,0 +1,678 @@
|
|||||||
|
; --- low level floating point assembly routines for the C64
|
||||||
|
|
||||||
|
FL_ONE_const .byte 129 ; 1.0
|
||||||
|
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
|
||||||
|
FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
|
||||||
|
|
||||||
|
|
||||||
|
floats_store_reg .byte 0 ; temp storage
|
||||||
|
|
||||||
|
|
||||||
|
ub2float .proc
|
||||||
|
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
||||||
|
; clobbers A, Y
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
lda #0
|
||||||
|
jsr GIVAYF
|
||||||
|
_fac_to_mem ldx P8ZP_SCRATCH_W2
|
||||||
|
ldy P8ZP_SCRATCH_W2+1
|
||||||
|
jsr MOVMF
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
b2float .proc
|
||||||
|
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
||||||
|
; clobbers A, Y
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
lda P8ZP_SCRATCH_B1
|
||||||
|
jsr FREADSA
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
uw2float .proc
|
||||||
|
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr GIVUAYFAY
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
w2float .proc
|
||||||
|
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy P8ZP_SCRATCH_W1
|
||||||
|
lda P8ZP_SCRATCH_W1+1
|
||||||
|
jsr GIVAYF
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
cast_from_uw .proc
|
||||||
|
; -- uword in A/Y into float var at (P8ZP_SCRATCH_W2)
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr GIVUAYFAY
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
cast_from_w .proc
|
||||||
|
; -- word in A/Y into float var at (P8ZP_SCRATCH_W2)
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr GIVAYFAY
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
cast_from_ub .proc
|
||||||
|
; -- ubyte in Y into float var at (P8ZP_SCRATCH_W2)
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FREADUY
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
cast_from_b .proc
|
||||||
|
; -- byte in A into float var at (P8ZP_SCRATCH_W2)
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FREADSA
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
cast_as_uw_into_ya .proc ; also used for float 2 ub
|
||||||
|
; -- cast float at A/Y to uword into Y/A
|
||||||
|
jsr MOVFM
|
||||||
|
jmp cast_FAC1_as_uw_into_ya
|
||||||
|
.pend
|
||||||
|
|
||||||
|
cast_as_w_into_ay .proc ; also used for float 2 b
|
||||||
|
; -- cast float at A/Y to word into A/Y
|
||||||
|
jsr MOVFM
|
||||||
|
jmp cast_FAC1_as_w_into_ay
|
||||||
|
.pend
|
||||||
|
|
||||||
|
cast_FAC1_as_uw_into_ya .proc ; also used for float 2 ub
|
||||||
|
; -- cast fac1 to uword into Y/A
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr GETADR ; into Y/A
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
|
||||||
|
; -- cast fac1 to word into A/Y
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr AYINT
|
||||||
|
ldy $64
|
||||||
|
lda $65
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
stack_b2float .proc
|
||||||
|
; -- b2float operating on the stack
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FREADSA
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_w2float .proc
|
||||||
|
; -- w2float operating on the stack
|
||||||
|
inx
|
||||||
|
ldy P8ESTACK_LO,x
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr GIVAYF
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_ub2float .proc
|
||||||
|
; -- ub2float operating on the stack
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
tay
|
||||||
|
lda #0
|
||||||
|
jsr GIVAYF
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_uw2float .proc
|
||||||
|
; -- uw2float operating on the stack
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
ldy P8ESTACK_HI,x
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr GIVUAYFAY
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_float2w .proc ; also used for float2b
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr AYINT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
lda $64
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
lda $65
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_float2uw .proc ; also used for float2ub
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr GETADR
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
tya
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
push_float .proc
|
||||||
|
; ---- push mflpt5 in A/Y onto stack
|
||||||
|
; (taking 3 stack positions = 6 bytes of which 1 is padding)
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
pop_float .proc
|
||||||
|
; ---- pops mflpt5 from stack to memory A/Y
|
||||||
|
; (frees 3 stack positions = 6 bytes of which 1 is padding)
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #4
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
pop_float_fac1 .proc
|
||||||
|
; -- pops float from stack into FAC1
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jmp MOVFM
|
||||||
|
.pend
|
||||||
|
|
||||||
|
copy_float .proc
|
||||||
|
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||||
|
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||||
|
sta _target+1
|
||||||
|
sty _target+2
|
||||||
|
ldy #4
|
||||||
|
_loop lda (P8ZP_SCRATCH_W1),y
|
||||||
|
_target sta $ffff,y ; modified
|
||||||
|
dey
|
||||||
|
bpl _loop
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
inc_var_f .proc
|
||||||
|
; -- add 1 to float pointed to by A/Y
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr MOVFM
|
||||||
|
lda #<FL_ONE_const
|
||||||
|
ldy #>FL_ONE_const
|
||||||
|
jsr FADD
|
||||||
|
ldx P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr MOVMF
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
dec_var_f .proc
|
||||||
|
; -- subtract 1 from float pointed to by A/Y
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<FL_ONE_const
|
||||||
|
ldy #>FL_ONE_const
|
||||||
|
jsr MOVFM
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr FSUB
|
||||||
|
ldx P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr MOVMF
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
pop_2_floats_f2_in_fac1 .proc
|
||||||
|
; -- pop 2 floats from stack, load the second one in FAC1 as well
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jmp MOVFM
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
||||||
|
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
||||||
|
|
||||||
|
|
||||||
|
push_fac1 .proc
|
||||||
|
; -- push the float in FAC1 onto the stack
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
_internal ldx #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr MOVMF
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
jmp push_float
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
pow_f .proc
|
||||||
|
; -- push f1 ** f2 on stack
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr CONUPK ; fac2 = float1
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr FPWR
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
div_f .proc
|
||||||
|
; -- push f1/f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FDIV
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
add_f .proc
|
||||||
|
; -- push f1+f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FADD
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
sub_f .proc
|
||||||
|
; -- push f1-f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FSUB
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
mul_f .proc
|
||||||
|
; -- push f1*f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FMULT
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
neg_f .proc
|
||||||
|
; -- toggle the sign bit on the stack
|
||||||
|
lda P8ESTACK_HI+3,x
|
||||||
|
eor #$80
|
||||||
|
sta P8ESTACK_HI+3,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
var_fac1_less_f .proc
|
||||||
|
; -- is the float in FAC1 < the variable AY?
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FCOMP
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
cmp #255
|
||||||
|
beq +
|
||||||
|
lda #0
|
||||||
|
rts
|
||||||
|
+ lda #1
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
var_fac1_lesseq_f .proc
|
||||||
|
; -- is the float in FAC1 <= the variable AY?
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FCOMP
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
cmp #0
|
||||||
|
beq +
|
||||||
|
cmp #255
|
||||||
|
beq +
|
||||||
|
lda #0
|
||||||
|
rts
|
||||||
|
+ lda #1
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
var_fac1_greater_f .proc
|
||||||
|
; -- is the float in FAC1 > the variable AY?
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FCOMP
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
cmp #1
|
||||||
|
beq +
|
||||||
|
lda #0
|
||||||
|
+ rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
var_fac1_greatereq_f .proc
|
||||||
|
; -- is the float in FAC1 >= the variable AY?
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FCOMP
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
cmp #0
|
||||||
|
beq +
|
||||||
|
cmp #1
|
||||||
|
beq +
|
||||||
|
lda #0
|
||||||
|
rts
|
||||||
|
+ lda #1
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
var_fac1_notequal_f .proc
|
||||||
|
; -- are the floats numbers in FAC1 and the variable AY *not* identical?
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FCOMP
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
and #1
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
vars_equal_f .proc
|
||||||
|
; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical?
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy #0
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
cmp (P8ZP_SCRATCH_W2),y
|
||||||
|
bne _false
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
cmp (P8ZP_SCRATCH_W2),y
|
||||||
|
bne _false
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
cmp (P8ZP_SCRATCH_W2),y
|
||||||
|
bne _false
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
cmp (P8ZP_SCRATCH_W2),y
|
||||||
|
bne _false
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
cmp (P8ZP_SCRATCH_W2),y
|
||||||
|
bne _false
|
||||||
|
lda #1
|
||||||
|
rts
|
||||||
|
_false lda #0
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
equal_f .proc
|
||||||
|
; -- are the two mflpt5 numbers on the stack identical?
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO-3,x
|
||||||
|
cmp P8ESTACK_LO,x
|
||||||
|
bne _equals_false
|
||||||
|
lda P8ESTACK_LO-2,x
|
||||||
|
cmp P8ESTACK_LO+1,x
|
||||||
|
bne _equals_false
|
||||||
|
lda P8ESTACK_LO-1,x
|
||||||
|
cmp P8ESTACK_LO+2,x
|
||||||
|
bne _equals_false
|
||||||
|
lda P8ESTACK_HI-2,x
|
||||||
|
cmp P8ESTACK_HI+1,x
|
||||||
|
bne _equals_false
|
||||||
|
lda P8ESTACK_HI-1,x
|
||||||
|
cmp P8ESTACK_HI+2,x
|
||||||
|
bne _equals_false
|
||||||
|
_equals_true lda #1
|
||||||
|
_equals_store inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
_equals_false lda #0
|
||||||
|
beq _equals_store
|
||||||
|
.pend
|
||||||
|
|
||||||
|
notequal_f .proc
|
||||||
|
; -- are the two mflpt5 numbers on the stack different?
|
||||||
|
jsr equal_f
|
||||||
|
eor #1 ; invert the result
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
vars_less_f .proc
|
||||||
|
; -- is float in AY < float in P8ZP_SCRATCH_W2 ?
|
||||||
|
jsr MOVFM
|
||||||
|
lda P8ZP_SCRATCH_W2
|
||||||
|
ldy P8ZP_SCRATCH_W2+1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FCOMP
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
cmp #255
|
||||||
|
bne +
|
||||||
|
lda #1
|
||||||
|
rts
|
||||||
|
+ lda #0
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
vars_lesseq_f .proc
|
||||||
|
; -- is float in AY <= float in P8ZP_SCRATCH_W2 ?
|
||||||
|
jsr MOVFM
|
||||||
|
lda P8ZP_SCRATCH_W2
|
||||||
|
ldy P8ZP_SCRATCH_W2+1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FCOMP
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
cmp #255
|
||||||
|
bne +
|
||||||
|
- lda #1
|
||||||
|
rts
|
||||||
|
+ cmp #0
|
||||||
|
beq -
|
||||||
|
lda #0
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
less_f .proc
|
||||||
|
; -- is f1 < f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #255
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
lesseq_f .proc
|
||||||
|
; -- is f1 <= f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #255
|
||||||
|
beq compare_floats._return_true
|
||||||
|
cmp #0
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greater_f .proc
|
||||||
|
; -- is f1 > f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #1
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greatereq_f .proc
|
||||||
|
; -- is f1 >= f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #1
|
||||||
|
beq compare_floats._return_true
|
||||||
|
cmp #0
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
compare_floats .proc
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr MOVFM ; fac1 = flt1
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
_return_false lda #0
|
||||||
|
_return_result sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
_return_true lda #1
|
||||||
|
bne _return_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
set_array_float_from_fac1 .proc
|
||||||
|
; -- set the float in FAC1 in the array (index in A, array in P8ZP_SCRATCH_W1)
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_W1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ stx floats_store_reg
|
||||||
|
tax
|
||||||
|
jsr MOVMF
|
||||||
|
ldx floats_store_reg
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
set_0_array_float .proc
|
||||||
|
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
tay
|
||||||
|
lda #0
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
set_array_float .proc
|
||||||
|
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
adc P8ZP_SCRATCH_W2
|
||||||
|
ldy P8ZP_SCRATCH_W2+1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jmp copy_float
|
||||||
|
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||||
|
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
@ -4,14 +4,14 @@
|
|||||||
;
|
;
|
||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target c64
|
||||||
%option enable_floats
|
%option enable_floats
|
||||||
|
|
||||||
|
floats {
|
||||||
c64flt {
|
|
||||||
; ---- this block contains C-64 floating point related functions ----
|
; ---- this block contains C-64 floating point related functions ----
|
||||||
|
|
||||||
const float PI = 3.141592653589793
|
const float PI = 3.141592653589793
|
||||||
const float TWOPI = 6.283185307179586
|
const float TWOPI = 6.283185307179586
|
||||||
|
|
||||||
|
|
||||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||||
@ -19,29 +19,10 @@ c64flt {
|
|||||||
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
||||||
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||||
|
|
||||||
; constants in five-byte "mflpt" format in the BASIC ROM
|
|
||||||
&float FL_PIVAL = $aea8 ; 3.1415926...
|
|
||||||
&float FL_N32768 = $b1a5 ; -32768
|
|
||||||
&float FL_FONE = $b9bc ; 1
|
|
||||||
&float FL_SQRHLF = $b9d6 ; SQR(2) / 2
|
|
||||||
&float FL_SQRTWO = $b9db ; SQR(2)
|
|
||||||
&float FL_NEGHLF = $b9e0 ; -.5
|
|
||||||
&float FL_LOG2 = $b9e5 ; LOG(2)
|
|
||||||
&float FL_TENC = $baf9 ; 10
|
|
||||||
&float FL_NZMIL = $bdbd ; 1e9 (1 billion)
|
|
||||||
&float FL_FHALF = $bf11 ; .5
|
|
||||||
&float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
|
|
||||||
&float FL_PIHALF = $e2e0 ; PI / 2
|
|
||||||
&float FL_TWOPI = $e2e5 ; 2 * PI
|
|
||||||
&float FL_FR4 = $e2ea ; .25
|
|
||||||
; oddly enough, 0.0 isn't available in the kernel.
|
|
||||||
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
|
|
||||||
|
|
||||||
|
|
||||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||||
|
|
||||||
; checked functions below:
|
|
||||||
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||||
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
|
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
|
||||||
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||||
@ -52,22 +33,22 @@ romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
|||||||
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||||
|
|
||||||
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
||||||
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
|
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
|
||||||
|
|
||||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||||
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||||
|
|
||||||
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
|
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
|
||||||
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||||
|
|
||||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||||
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||||
; there is also c64flt.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||||
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||||
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||||
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
; there is also floats.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
||||||
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||||
|
|
||||||
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
|
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
|
||||||
@ -91,6 +72,7 @@ romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1
|
|||||||
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||||
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||||
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||||
|
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||||
|
|
||||||
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
|
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
|
||||||
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||||
@ -163,9 +145,9 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
|||||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||||
%asm {{
|
%asm {{
|
||||||
sta c64.SCRATCH_ZPREG
|
sta P8ZP_SCRATCH_REG
|
||||||
tya
|
tya
|
||||||
ldy c64.SCRATCH_ZPREG
|
ldy P8ZP_SCRATCH_REG
|
||||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -174,9 +156,9 @@ asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
|||||||
; ---- fac1 to signed word in A/Y
|
; ---- fac1 to signed word in A/Y
|
||||||
%asm {{
|
%asm {{
|
||||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||||
sta c64.SCRATCH_ZPREG
|
sta P8ZP_SCRATCH_REG
|
||||||
tya
|
tya
|
||||||
ldy c64.SCRATCH_ZPREG
|
ldy P8ZP_SCRATCH_REG
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -185,41 +167,35 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
|||||||
; ---- fac1 to unsigned word in A/Y
|
; ---- fac1 to unsigned word in A/Y
|
||||||
%asm {{
|
%asm {{
|
||||||
jsr GETADR ; this uses the inverse order, Y/A
|
jsr GETADR ; this uses the inverse order, Y/A
|
||||||
sta c64.SCRATCH_ZPB1
|
sta P8ZP_SCRATCH_B1
|
||||||
tya
|
tya
|
||||||
ldy c64.SCRATCH_ZPB1
|
ldy P8ZP_SCRATCH_B1
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub print_f (float value) {
|
sub print_f (float value) {
|
||||||
; ---- prints the floating point value (without a newline) using basic rom routines.
|
; ---- prints the floating point value (without a newline).
|
||||||
%asm {{
|
%asm {{
|
||||||
stx c64.SCRATCH_ZPREGX
|
stx floats_store_reg
|
||||||
lda #<value
|
lda #<value
|
||||||
ldy #>value
|
ldy #>value
|
||||||
jsr MOVFM ; load float into fac1
|
jsr MOVFM ; load float into fac1
|
||||||
jsr FOUT ; fac1 to string in A/Y
|
jsr FOUT ; fac1 to string in A/Y
|
||||||
jsr c64.STROUT ; print string in A/Y
|
sta P8ZP_SCRATCH_W1
|
||||||
ldx c64.SCRATCH_ZPREGX
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ ldx floats_store_reg
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub print_fln (float value) {
|
%asminclude "library:c64/floats.asm", ""
|
||||||
; ---- prints the floating point value (with a newline at the end) using basic rom routines
|
%asminclude "library:c64/floats_funcs.asm", ""
|
||||||
%asm {{
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<value
|
|
||||||
ldy #>value
|
|
||||||
jsr MOVFM ; load float into fac1
|
|
||||||
jsr FPRINTLN ; print fac1 with newline
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
%asminclude "library:c64floats.asm", ""
|
|
||||||
|
|
||||||
} ; ------ end of block c64flt
|
|
437
compiler/res/prog8lib/c64/floats_funcs.asm
Normal file
437
compiler/res/prog8lib/c64/floats_funcs.asm
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
; --- floating point builtin functions
|
||||||
|
|
||||||
|
|
||||||
|
abs_f_stack .proc
|
||||||
|
; -- push abs(AY) on stack
|
||||||
|
jsr floats.MOVFM
|
||||||
|
jsr floats.ABS
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
abs_f_fac1 .proc
|
||||||
|
; -- FAC1 = abs(AY)
|
||||||
|
jsr floats.MOVFM
|
||||||
|
jmp floats.ABS
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_atan_stack .proc
|
||||||
|
jsr func_atan_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_atan_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr ATN
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_ceil_stack .proc
|
||||||
|
jsr func_ceil_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_ceil_fac1 .proc
|
||||||
|
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
ldx #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr MOVMF
|
||||||
|
jsr INT
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FCOMP
|
||||||
|
cmp #0
|
||||||
|
beq +
|
||||||
|
lda #<FL_ONE_const
|
||||||
|
ldy #>FL_ONE_const
|
||||||
|
jsr FADD
|
||||||
|
+ ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_floor_stack .proc
|
||||||
|
jsr func_floor_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_floor_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr INT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_round_stack .proc
|
||||||
|
jsr func_round_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_round_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FADDH
|
||||||
|
jsr INT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sin_stack .proc
|
||||||
|
jsr func_sin_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sin_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr SIN
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_cos_stack .proc
|
||||||
|
jsr func_cos_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_cos_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr COS
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_tan_stack .proc
|
||||||
|
jsr func_tan_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_tan_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr TAN
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rad_stack .proc
|
||||||
|
jsr func_rad_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rad_fac1 .proc
|
||||||
|
; -- convert degrees to radians (d * pi / 180)
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<_pi_div_180
|
||||||
|
ldy #>_pi_div_180
|
||||||
|
jsr FMULT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_deg_stack .proc
|
||||||
|
jsr func_deg_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_deg_fac1 .proc
|
||||||
|
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<_one_over_pi_div_180
|
||||||
|
ldy #>_one_over_pi_div_180
|
||||||
|
jsr FMULT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_ln_stack .proc
|
||||||
|
jsr func_ln_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_ln_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr LOG
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_log2_stack .proc
|
||||||
|
jsr func_log2_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_log2_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr LOG
|
||||||
|
jsr MOVEF
|
||||||
|
lda #<FL_LOG2_const
|
||||||
|
ldy #>FL_LOG2_const
|
||||||
|
jsr MOVFM
|
||||||
|
jsr FDIVT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sign_f_stack .proc
|
||||||
|
jsr func_sign_f_into_A
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sign_f_into_A .proc
|
||||||
|
jsr MOVFM
|
||||||
|
jmp SIGN
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sqrt_stack .proc
|
||||||
|
jsr func_sqrt_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sqrt_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr SQR
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rndf_stack .proc
|
||||||
|
jsr func_rndf_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rndf_fac1 .proc
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #1
|
||||||
|
jsr FREADSA
|
||||||
|
jsr RND ; rng into fac1
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_swap_f .proc
|
||||||
|
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
|
||||||
|
ldy #4
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
dey
|
||||||
|
bpl -
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_reverse_f .proc
|
||||||
|
; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||||
|
_left_index = P8ZP_SCRATCH_W2
|
||||||
|
_right_index = P8ZP_SCRATCH_W2+1
|
||||||
|
_loop_count = P8ZP_SCRATCH_REG
|
||||||
|
pha
|
||||||
|
jsr a_times_5
|
||||||
|
sec
|
||||||
|
sbc #5
|
||||||
|
sta _right_index
|
||||||
|
lda #0
|
||||||
|
sta _left_index
|
||||||
|
pla
|
||||||
|
lsr a
|
||||||
|
sta _loop_count
|
||||||
|
_loop ; push the left indexed float on the stack
|
||||||
|
ldy _left_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
; copy right index float to left index float
|
||||||
|
ldy _right_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
inc _left_index
|
||||||
|
inc _right_index
|
||||||
|
ldy _right_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
inc _left_index
|
||||||
|
inc _right_index
|
||||||
|
ldy _right_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
inc _left_index
|
||||||
|
inc _right_index
|
||||||
|
ldy _right_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
inc _left_index
|
||||||
|
inc _right_index
|
||||||
|
ldy _right_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
; pop the float off the stack into the right index float
|
||||||
|
ldy _right_index
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
inc _left_index
|
||||||
|
lda _right_index
|
||||||
|
sec
|
||||||
|
sbc #9
|
||||||
|
sta _right_index
|
||||||
|
dec _loop_count
|
||||||
|
bne _loop
|
||||||
|
rts
|
||||||
|
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
a_times_5 .proc
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_any_f_into_A .proc
|
||||||
|
jsr a_times_5
|
||||||
|
jmp prog8_lib.func_any_b_into_A
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_all_f_into_A .proc
|
||||||
|
jsr a_times_5
|
||||||
|
jmp prog8_lib.func_all_b_into_A
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_any_f_stack .proc
|
||||||
|
jsr a_times_5
|
||||||
|
jmp prog8_lib.func_any_b_stack
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_all_f_stack .proc
|
||||||
|
jsr a_times_5
|
||||||
|
jmp prog8_lib.func_all_b_stack
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_max_f_stack .proc
|
||||||
|
jsr func_max_f_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_max_f_fac1 .proc
|
||||||
|
; -- max(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||||
|
_loop_count = P8ZP_SCRATCH_REG
|
||||||
|
stx floats_store_reg
|
||||||
|
sta _loop_count
|
||||||
|
lda #255
|
||||||
|
sta _minmax_cmp+1 ; modifying
|
||||||
|
lda #<_largest_neg_float
|
||||||
|
ldy #>_largest_neg_float
|
||||||
|
_minmax_entry jsr MOVFM
|
||||||
|
- lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr FCOMP
|
||||||
|
_minmax_cmp cmp #255 ; modified
|
||||||
|
bne +
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr MOVFM
|
||||||
|
+ lda P8ZP_SCRATCH_W1
|
||||||
|
clc
|
||||||
|
adc #5
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
bcc +
|
||||||
|
inc P8ZP_SCRATCH_W1+1
|
||||||
|
+ dec _loop_count
|
||||||
|
bne -
|
||||||
|
ldx floats_store_reg
|
||||||
|
rts
|
||||||
|
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_min_f_stack .proc
|
||||||
|
jsr func_min_f_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_min_f_fac1 .proc
|
||||||
|
; -- min(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||||
|
sta func_max_f_fac1._loop_count
|
||||||
|
lda #1
|
||||||
|
sta func_max_f_fac1._minmax_cmp+1
|
||||||
|
lda #<_largest_pos_float
|
||||||
|
ldy #>_largest_pos_float
|
||||||
|
jmp func_max_f_fac1._minmax_entry
|
||||||
|
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
func_sum_f_stack .proc
|
||||||
|
jsr func_sum_f_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sum_f_fac1 .proc
|
||||||
|
; -- sum(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||||
|
_loop_count = P8ZP_SCRATCH_REG
|
||||||
|
stx floats_store_reg
|
||||||
|
sta _loop_count
|
||||||
|
lda #<FL_ZERO_const
|
||||||
|
ldy #>FL_ZERO_const
|
||||||
|
jsr MOVFM
|
||||||
|
- lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr FADD
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
clc
|
||||||
|
adc #5
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
bcc +
|
||||||
|
inc P8ZP_SCRATCH_W1+1
|
||||||
|
+ dec _loop_count
|
||||||
|
bne -
|
||||||
|
ldx floats_store_reg
|
||||||
|
rts
|
||||||
|
.pend
|
373
compiler/res/prog8lib/c64/graphics.p8
Normal file
373
compiler/res/prog8lib/c64/graphics.p8
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
%target c64
|
||||||
|
%import textio
|
||||||
|
|
||||||
|
; bitmap pixel graphics module for the C64
|
||||||
|
; only black/white monochrome 320x200 for now
|
||||||
|
; assumes bitmap screen memory is $2000-$3fff
|
||||||
|
|
||||||
|
graphics {
|
||||||
|
const uword BITMAP_ADDRESS = $2000
|
||||||
|
const uword WIDTH = 320
|
||||||
|
const ubyte HEIGHT = 200
|
||||||
|
|
||||||
|
sub enable_bitmap_mode() {
|
||||||
|
; enable bitmap screen, erase it and set colors to black/white.
|
||||||
|
c64.SCROLY = %00111011
|
||||||
|
c64.SCROLX = %00001000
|
||||||
|
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
|
||||||
|
clear_screen(1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub disable_bitmap_mode() {
|
||||||
|
; enables text mode, erase the text screen, color white
|
||||||
|
c64.SCROLY = %00011011
|
||||||
|
c64.SCROLX = %00001000
|
||||||
|
c64.VMCSB = (c64.VMCSB & %11110000) | %00000100 ; $1000-$2fff
|
||||||
|
txt.fill_screen(' ', 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||||
|
sys.memset(BITMAP_ADDRESS, 320*200/8, 0)
|
||||||
|
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||||
|
; Bresenham algorithm.
|
||||||
|
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||||
|
; TODO there are some slight errors at the first/last pixels in certain slopes...??
|
||||||
|
if y1>y2 {
|
||||||
|
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||||
|
swap(x1, x2)
|
||||||
|
swap(y1, y2)
|
||||||
|
}
|
||||||
|
word @zp dx = x2-x1 as word
|
||||||
|
word @zp dy = y2-y1
|
||||||
|
|
||||||
|
if dx==0 {
|
||||||
|
vertical_line(x1, y1, abs(dy)+1 as ubyte)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if dy==0 {
|
||||||
|
if x1>x2
|
||||||
|
x1=x2
|
||||||
|
horizontal_line(x1, y1, abs(dx)+1 as uword)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
; TODO rewrite the rest in optimized assembly
|
||||||
|
|
||||||
|
word @zp d = 0
|
||||||
|
ubyte positive_ix = true
|
||||||
|
if dx < 0 {
|
||||||
|
dx = -dx
|
||||||
|
positive_ix = false
|
||||||
|
}
|
||||||
|
dx *= 2
|
||||||
|
dy *= 2
|
||||||
|
internal_plotx = x1
|
||||||
|
|
||||||
|
if dx >= dy {
|
||||||
|
if positive_ix {
|
||||||
|
repeat {
|
||||||
|
internal_plot(y1)
|
||||||
|
if internal_plotx==x2
|
||||||
|
return
|
||||||
|
internal_plotx++
|
||||||
|
d += dy
|
||||||
|
if d > dx {
|
||||||
|
y1++
|
||||||
|
d -= dx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat {
|
||||||
|
internal_plot(y1)
|
||||||
|
if internal_plotx==x2
|
||||||
|
return
|
||||||
|
internal_plotx--
|
||||||
|
d += dy
|
||||||
|
if d > dx {
|
||||||
|
y1++
|
||||||
|
d -= dx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if positive_ix {
|
||||||
|
repeat {
|
||||||
|
internal_plot(y1)
|
||||||
|
if y1 == y2
|
||||||
|
return
|
||||||
|
y1++
|
||||||
|
d += dx
|
||||||
|
if d > dy {
|
||||||
|
internal_plotx++
|
||||||
|
d -= dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat {
|
||||||
|
internal_plot(y1)
|
||||||
|
if y1 == y2
|
||||||
|
return
|
||||||
|
y1++
|
||||||
|
d += dx
|
||||||
|
if d > dy {
|
||||||
|
internal_plotx--
|
||||||
|
d -= dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rect(uword x, ubyte y, uword width, ubyte height) {
|
||||||
|
if width==0 or height==0
|
||||||
|
return
|
||||||
|
horizontal_line(x, y, width)
|
||||||
|
if height==1
|
||||||
|
return
|
||||||
|
horizontal_line(x, y+height-1, width)
|
||||||
|
vertical_line(x, y+1, height-2)
|
||||||
|
if width==1
|
||||||
|
return
|
||||||
|
vertical_line(x+width-1, y+1, height-2)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fillrect(uword x, ubyte y, uword width, ubyte height) {
|
||||||
|
if width==0
|
||||||
|
return
|
||||||
|
repeat height {
|
||||||
|
horizontal_line(x, y, width)
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub horizontal_line(uword x, ubyte y, uword length) {
|
||||||
|
if length<8 {
|
||||||
|
internal_plotx=x
|
||||||
|
repeat lsb(length) {
|
||||||
|
internal_plot(y)
|
||||||
|
internal_plotx++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte separate_pixels = lsb(x) & 7
|
||||||
|
uword addr = get_y_lookup(y) + (x&$fff8)
|
||||||
|
|
||||||
|
if separate_pixels {
|
||||||
|
%asm {{
|
||||||
|
lda addr
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda addr+1
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
ldy separate_pixels
|
||||||
|
lda _filled_right,y
|
||||||
|
eor #255
|
||||||
|
ldy #0
|
||||||
|
ora (P8ZP_SCRATCH_W1),y
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
}}
|
||||||
|
addr += 8
|
||||||
|
length += separate_pixels
|
||||||
|
length -= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
if length {
|
||||||
|
%asm {{
|
||||||
|
lda length
|
||||||
|
and #7
|
||||||
|
sta separate_pixels
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lsr length+1
|
||||||
|
ror length
|
||||||
|
lsr length+1
|
||||||
|
ror length
|
||||||
|
lsr length+1
|
||||||
|
ror length
|
||||||
|
lda addr
|
||||||
|
sta _modified+1
|
||||||
|
lda addr+1
|
||||||
|
sta _modified+2
|
||||||
|
lda length
|
||||||
|
ora length+1
|
||||||
|
beq _zero
|
||||||
|
ldy length
|
||||||
|
ldx #$ff
|
||||||
|
_modified stx $ffff ; modified
|
||||||
|
lda _modified+1
|
||||||
|
clc
|
||||||
|
adc #8
|
||||||
|
sta _modified+1
|
||||||
|
bcc +
|
||||||
|
inc _modified+2
|
||||||
|
+ dey
|
||||||
|
bne _modified
|
||||||
|
_zero ldx P8ZP_SCRATCH_REG
|
||||||
|
|
||||||
|
ldy separate_pixels
|
||||||
|
beq _zero2
|
||||||
|
lda _modified+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda _modified+2
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
lda _filled_right,y
|
||||||
|
ldy #0
|
||||||
|
ora (P8ZP_SCRATCH_W1),y
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
jmp _zero2
|
||||||
|
_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110
|
||||||
|
_zero2
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub vertical_line(uword x, ubyte y, ubyte height) {
|
||||||
|
internal_plotx = x
|
||||||
|
repeat height {
|
||||||
|
internal_plot(y)
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||||
|
; Midpoint algorithm
|
||||||
|
if radius==0
|
||||||
|
return
|
||||||
|
ubyte @zp ploty
|
||||||
|
ubyte @zp yy = 0
|
||||||
|
word @zp decisionOver2 = (1 as word)-radius
|
||||||
|
|
||||||
|
while radius>=yy {
|
||||||
|
internal_plotx = xcenter + radius
|
||||||
|
ploty = ycenter + yy
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter - radius
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter + radius
|
||||||
|
ploty = ycenter - yy
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter - radius
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter + yy
|
||||||
|
ploty = ycenter + radius
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter - yy
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter + yy
|
||||||
|
ploty = ycenter - radius
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter - yy
|
||||||
|
internal_plot(ploty)
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0
|
||||||
|
decisionOver2 += (yy as word)*2+1
|
||||||
|
else {
|
||||||
|
radius--
|
||||||
|
decisionOver2 += (yy as word -radius)*2+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||||
|
; Midpoint algorithm, filled
|
||||||
|
if radius==0
|
||||||
|
return
|
||||||
|
ubyte @zp yy = 0
|
||||||
|
word decisionOver2 = (1 as word)-radius
|
||||||
|
|
||||||
|
while radius>=yy {
|
||||||
|
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
|
||||||
|
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
|
||||||
|
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
|
||||||
|
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0
|
||||||
|
decisionOver2 += (yy as word)*2+1
|
||||||
|
else {
|
||||||
|
radius--
|
||||||
|
decisionOver2 += (yy as word -radius)*2+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
; here is the non-asm code for the plot routine below:
|
||||||
|
; sub plot_nonasm(uword px, ubyte py) {
|
||||||
|
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||||
|
; uword addr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
|
||||||
|
; @(addr) |= ormask[lsb(px) & 7]
|
||||||
|
; }
|
||||||
|
|
||||||
|
inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
|
||||||
|
%asm {{
|
||||||
|
stx graphics.internal_plotx
|
||||||
|
sty graphics.internal_plotx+1
|
||||||
|
jsr graphics.internal_plot
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
; for efficiency of internal algorithms here is the internal plot routine
|
||||||
|
; that takes the plotx coordinate in a separate variable instead of the XY register pair:
|
||||||
|
|
||||||
|
uword internal_plotx ; 0..319 ; separate 'parameter' for internal_plot()
|
||||||
|
|
||||||
|
asmsub internal_plot(ubyte ploty @A) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
|
||||||
|
%asm {{
|
||||||
|
tay
|
||||||
|
lda internal_plotx+1
|
||||||
|
sta P8ZP_SCRATCH_W2+1
|
||||||
|
lsr a ; 0
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
lda internal_plotx
|
||||||
|
pha
|
||||||
|
and #7
|
||||||
|
tax
|
||||||
|
|
||||||
|
lda _y_lookup_lo,y
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_W2
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
lda _y_lookup_hi,y
|
||||||
|
adc P8ZP_SCRATCH_W2+1
|
||||||
|
sta P8ZP_SCRATCH_W2+1
|
||||||
|
|
||||||
|
pla ; internal_plotx
|
||||||
|
and #%11111000
|
||||||
|
tay
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
ora _ormask,x
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
rts
|
||||||
|
|
||||||
|
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
|
||||||
|
|
||||||
|
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
|
||||||
|
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
|
||||||
|
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||||
|
; We use the 64tass syntax for range expressions to calculate this table on assembly time.
|
||||||
|
|
||||||
|
_plot_y_values := $2000 + 320*(range(200)>>3) + (range(200) & 7)
|
||||||
|
|
||||||
|
_y_lookup_lo .byte <_plot_y_values
|
||||||
|
_y_lookup_hi .byte >_plot_y_values
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub get_y_lookup(ubyte y @Y) -> uword @AY {
|
||||||
|
%asm {{
|
||||||
|
lda internal_plot._y_lookup_lo,y
|
||||||
|
pha
|
||||||
|
lda internal_plot._y_lookup_hi,y
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -5,20 +5,13 @@
|
|||||||
;
|
;
|
||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target c64
|
||||||
|
|
||||||
c64 {
|
c64 {
|
||||||
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
|
|
||||||
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
|
|
||||||
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
|
|
||||||
&ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
|
|
||||||
&ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
|
|
||||||
&uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
|
||||||
&uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
|
||||||
|
|
||||||
|
|
||||||
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||||
&ubyte TIME_MID = $a1 ; .. mid byte
|
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||||
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||||
|
&ubyte STATUS = $90 ; kernal status variable for I/O
|
||||||
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||||
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||||
|
|
||||||
@ -183,19 +176,11 @@ c64 {
|
|||||||
; ---- 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 txt.print instead)
|
||||||
|
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
|
||||||
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
|
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||||
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
|
||||||
|
|
||||||
|
|
||||||
; ---- end of C64 basic routines ----
|
|
||||||
|
|
||||||
|
|
||||||
; ---- C64 kernal routines ----
|
|
||||||
|
|
||||||
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
|
|
||||||
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||||
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
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 $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||||
@ -219,25 +204,479 @@ romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial
|
|||||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||||
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
|
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
|
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
romsub $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 $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 $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, 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 $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||||
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
|
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
|
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
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 $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 $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||||
|
|
||||||
; ---- end of C64 kernal routines ----
|
; ---- end of C64 ROM kernal routines ----
|
||||||
|
|
||||||
|
; ---- utilities -----
|
||||||
|
|
||||||
|
asmsub STOP2() -> ubyte @A {
|
||||||
|
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||||
|
%asm {{
|
||||||
|
txa
|
||||||
|
pha
|
||||||
|
jsr c64.STOP
|
||||||
|
beq +
|
||||||
|
pla
|
||||||
|
tax
|
||||||
|
lda #0
|
||||||
|
rts
|
||||||
|
+ pla
|
||||||
|
tax
|
||||||
|
lda #1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub RDTIM16() -> uword @AY {
|
||||||
|
; -- like RDTIM() but only returning the lower 16 bits for convenience
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr c64.RDTIM
|
||||||
|
pha
|
||||||
|
txa
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; ---- C64 specific system utility routines: ----
|
||||||
|
|
||||||
|
asmsub init_system() {
|
||||||
|
; Initializes the machine to a sane starting state.
|
||||||
|
; Called automatically by the loader program logic.
|
||||||
|
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
|
||||||
|
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
|
||||||
|
; Also a different color scheme is chosen to identify ourselves a little.
|
||||||
|
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
cld
|
||||||
|
lda #%00101111
|
||||||
|
sta $00
|
||||||
|
lda #%00100111
|
||||||
|
sta $01
|
||||||
|
jsr c64.IOINIT
|
||||||
|
jsr c64.RESTOR
|
||||||
|
jsr c64.CINT
|
||||||
|
lda #6
|
||||||
|
sta c64.EXTCOL
|
||||||
|
lda #7
|
||||||
|
sta c64.COLOR
|
||||||
|
lda #0
|
||||||
|
sta c64.BGCOL0
|
||||||
|
jsr disable_runstop_and_charsetswitch
|
||||||
|
clc
|
||||||
|
clv
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub init_system_phase2() {
|
||||||
|
%asm {{
|
||||||
|
rts ; no phase 2 steps on the C64
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||||
|
%asm {{
|
||||||
|
lda #$80
|
||||||
|
sta 657 ; disable charset switching
|
||||||
|
lda #239
|
||||||
|
sta 808 ; disable run/stop key
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
||||||
|
%asm {{
|
||||||
|
sta _modified+1
|
||||||
|
sty _modified+2
|
||||||
|
lda #0
|
||||||
|
adc #0
|
||||||
|
sta _use_kernal
|
||||||
|
sei
|
||||||
|
lda #<_irq_handler
|
||||||
|
sta c64.CINV
|
||||||
|
lda #>_irq_handler
|
||||||
|
sta c64.CINV+1
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
_irq_handler jsr _irq_handler_init
|
||||||
|
_modified jsr $ffff ; modified
|
||||||
|
jsr _irq_handler_end
|
||||||
|
lda _use_kernal
|
||||||
|
bne +
|
||||||
|
lda #$ff
|
||||||
|
sta c64.VICIRQ ; acknowledge raster irq
|
||||||
|
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
|
||||||
|
; end irq processing - don't use kernal's irq handling
|
||||||
|
pla
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
tax
|
||||||
|
pla
|
||||||
|
rti
|
||||||
|
+ jmp c64.IRQDFRT ; continue with normal kernal irq routine
|
||||||
|
|
||||||
|
_use_kernal .byte 0
|
||||||
|
|
||||||
|
_irq_handler_init
|
||||||
|
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
|
||||||
|
stx IRQ_X_REG
|
||||||
|
lda P8ZP_SCRATCH_B1
|
||||||
|
sta IRQ_SCRATCH_ZPB1
|
||||||
|
lda P8ZP_SCRATCH_REG
|
||||||
|
sta IRQ_SCRATCH_ZPREG
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
sta IRQ_SCRATCH_ZPWORD1
|
||||||
|
lda P8ZP_SCRATCH_W1+1
|
||||||
|
sta IRQ_SCRATCH_ZPWORD1+1
|
||||||
|
lda P8ZP_SCRATCH_W2
|
||||||
|
sta IRQ_SCRATCH_ZPWORD2
|
||||||
|
lda P8ZP_SCRATCH_W2+1
|
||||||
|
sta IRQ_SCRATCH_ZPWORD2+1
|
||||||
|
; stack protector; make sure we don't clobber the top of the evaluation stack
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
cld
|
||||||
|
rts
|
||||||
|
|
||||||
|
_irq_handler_end
|
||||||
|
; restore all zp scratch registers and the X register
|
||||||
|
lda IRQ_SCRATCH_ZPB1
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
lda IRQ_SCRATCH_ZPREG
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
lda IRQ_SCRATCH_ZPWORD1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda IRQ_SCRATCH_ZPWORD1+1
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
lda IRQ_SCRATCH_ZPWORD2
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
lda IRQ_SCRATCH_ZPWORD2+1
|
||||||
|
sta P8ZP_SCRATCH_W2+1
|
||||||
|
ldx IRQ_X_REG
|
||||||
|
rts
|
||||||
|
|
||||||
|
IRQ_X_REG .byte 0
|
||||||
|
IRQ_SCRATCH_ZPB1 .byte 0
|
||||||
|
IRQ_SCRATCH_ZPREG .byte 0
|
||||||
|
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||||
|
IRQ_SCRATCH_ZPWORD2 .word 0
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub restore_irq() clobbers(A) {
|
||||||
|
%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 handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
|
||||||
|
%asm {{
|
||||||
|
sta _modified+1
|
||||||
|
sty _modified+2
|
||||||
|
lda #0
|
||||||
|
adc #0
|
||||||
|
sta set_irq._use_kernal
|
||||||
|
lda cx16.r0
|
||||||
|
ldy cx16.r0+1
|
||||||
|
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_irq._irq_handler_init
|
||||||
|
_modified jsr $ffff ; modified
|
||||||
|
jsr set_irq._irq_handler_end
|
||||||
|
lda #$ff
|
||||||
|
sta c64.VICIRQ ; acknowledge raster irq
|
||||||
|
lda set_irq._use_kernal
|
||||||
|
bne +
|
||||||
|
; end irq processing - don't use kernal's irq handling
|
||||||
|
pla
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
tax
|
||||||
|
pla
|
||||||
|
rti
|
||||||
|
+ jmp c64.IRQDFRT ; continue with kernal irq routine
|
||||||
|
|
||||||
|
_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
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
; ---- end of C64 specific system utility routines ----
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sys {
|
||||||
|
; ------- lowlevel system routines --------
|
||||||
|
|
||||||
|
const ubyte target = 64 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
|
||||||
|
|
||||||
|
|
||||||
|
asmsub reset_system() {
|
||||||
|
; Soft-reset the system back to Basic prompt.
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
lda #14
|
||||||
|
sta $01 ; bank the kernal in
|
||||||
|
jmp (c64.RESET_VEC)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub wait(uword jiffies) {
|
||||||
|
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||||
|
repeat jiffies {
|
||||||
|
ubyte jiff = lsb(c64.RDTIM16())
|
||||||
|
while jiff==lsb(c64.RDTIM16()) {
|
||||||
|
; wait until 1 jiffy has passed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||||
|
%asm {{
|
||||||
|
ldx cx16.r0
|
||||||
|
stx P8ZP_SCRATCH_W1 ; source in ZP
|
||||||
|
ldx cx16.r0+1
|
||||||
|
stx P8ZP_SCRATCH_W1+1
|
||||||
|
ldx cx16.r1
|
||||||
|
stx P8ZP_SCRATCH_W2 ; target in ZP
|
||||||
|
ldx cx16.r1+1
|
||||||
|
stx P8ZP_SCRATCH_W2+1
|
||||||
|
cpy #0
|
||||||
|
bne _longcopy
|
||||||
|
|
||||||
|
; copy <= 255 bytes
|
||||||
|
tay
|
||||||
|
bne _copyshort
|
||||||
|
rts ; nothing to copy
|
||||||
|
|
||||||
|
_copyshort
|
||||||
|
; decrease source and target pointers so we can simply index by Y
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
bne +
|
||||||
|
dec P8ZP_SCRATCH_W1+1
|
||||||
|
+ dec P8ZP_SCRATCH_W1
|
||||||
|
lda P8ZP_SCRATCH_W2
|
||||||
|
bne +
|
||||||
|
dec P8ZP_SCRATCH_W2+1
|
||||||
|
+ dec P8ZP_SCRATCH_W2
|
||||||
|
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
rts
|
||||||
|
|
||||||
|
_longcopy
|
||||||
|
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
|
||||||
|
tya
|
||||||
|
tax ; x = num pages (1+)
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
inc P8ZP_SCRATCH_W1+1
|
||||||
|
inc P8ZP_SCRATCH_W2+1
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
bne _copyshort
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
|
||||||
|
%asm {{
|
||||||
|
ldy cx16.r0
|
||||||
|
sty P8ZP_SCRATCH_W1
|
||||||
|
ldy cx16.r0+1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldx cx16.r1
|
||||||
|
ldy cx16.r1+1
|
||||||
|
jmp prog8_lib.memset
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
|
||||||
|
%asm {{
|
||||||
|
ldx cx16.r0
|
||||||
|
stx P8ZP_SCRATCH_W1
|
||||||
|
ldx cx16.r0+1
|
||||||
|
stx P8ZP_SCRATCH_W1+1
|
||||||
|
ldx cx16.r1
|
||||||
|
stx P8ZP_SCRATCH_W2
|
||||||
|
ldx cx16.r1+1
|
||||||
|
stx P8ZP_SCRATCH_W2+1
|
||||||
|
jmp prog8_lib.memsetw
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline asmsub rsave() {
|
||||||
|
; save cpu status flag and all registers A, X, Y.
|
||||||
|
; see http://6502.org/tutorials/register_preservation.html
|
||||||
|
%asm {{
|
||||||
|
php
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
pha
|
||||||
|
txa
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
pha
|
||||||
|
lda P8ZP_SCRATCH_REG
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub rrestore() {
|
||||||
|
; restore all registers and cpu status flag
|
||||||
|
%asm {{
|
||||||
|
pla
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
tax
|
||||||
|
pla
|
||||||
|
plp
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub read_flags() -> ubyte @A {
|
||||||
|
%asm {{
|
||||||
|
php
|
||||||
|
pla
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub clear_carry() {
|
||||||
|
%asm {{
|
||||||
|
clc
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub set_carry() {
|
||||||
|
%asm {{
|
||||||
|
sec
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub clear_irqd() {
|
||||||
|
%asm {{
|
||||||
|
cli
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub set_irqd() {
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub exit(ubyte returnvalue @A) {
|
||||||
|
; -- immediately exit the program with a return code in the A register
|
||||||
|
%asm {{
|
||||||
|
jsr c64.CLRCHN ; reset i/o channels
|
||||||
|
ldx prog8_lib.orig_stackpointer
|
||||||
|
txs
|
||||||
|
rts ; return to original caller
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub progend() -> uword @AY {
|
||||||
|
%asm {{
|
||||||
|
lda #<prog8_program_end
|
||||||
|
ldy #>prog8_program_end
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cx16 {
|
||||||
|
|
||||||
|
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
|
||||||
|
; they are simulated on the C64 as well but their location in memory is different
|
||||||
|
; (because there's no room for them in the zeropage)
|
||||||
|
; they are allocated at the bottom of the eval-stack (should be ample space unless
|
||||||
|
; you're doing insane nesting of expressions...)
|
||||||
|
&uword r0 = $cf00
|
||||||
|
&uword r1 = $cf02
|
||||||
|
&uword r2 = $cf04
|
||||||
|
&uword r3 = $cf06
|
||||||
|
&uword r4 = $cf08
|
||||||
|
&uword r5 = $cf0a
|
||||||
|
&uword r6 = $cf0c
|
||||||
|
&uword r7 = $cf0e
|
||||||
|
&uword r8 = $cf10
|
||||||
|
&uword r9 = $cf12
|
||||||
|
&uword r10 = $cf14
|
||||||
|
&uword r11 = $cf16
|
||||||
|
&uword r12 = $cf18
|
||||||
|
&uword r13 = $cf1a
|
||||||
|
&uword r14 = $cf1c
|
||||||
|
&uword r15 = $cf1e
|
||||||
|
|
||||||
}
|
}
|
618
compiler/res/prog8lib/c64/textio.p8
Normal file
618
compiler/res/prog8lib/c64/textio.p8
Normal file
@ -0,0 +1,618 @@
|
|||||||
|
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target c64
|
||||||
|
%import syslib
|
||||||
|
%import conv
|
||||||
|
|
||||||
|
|
||||||
|
txt {
|
||||||
|
|
||||||
|
const ubyte DEFAULT_WIDTH = 40
|
||||||
|
const ubyte DEFAULT_HEIGHT = 25
|
||||||
|
|
||||||
|
|
||||||
|
sub clear_screen() {
|
||||||
|
txt.chrout(147)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub home() {
|
||||||
|
txt.chrout(19)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub nl() {
|
||||||
|
txt.chrout('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub spc() {
|
||||||
|
txt.chrout(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||||
|
; ---- set the cursor on the given column (starting with 0) on the current line
|
||||||
|
%asm {{
|
||||||
|
sec
|
||||||
|
jsr c64.PLOT
|
||||||
|
tay
|
||||||
|
clc
|
||||||
|
jmp c64.PLOT
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||||
|
; ---- fill the character screen with the given fill character and character color.
|
||||||
|
; (assumes screen and color matrix are at their default addresses)
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr clear_screencolors
|
||||||
|
pla
|
||||||
|
jsr clear_screenchars
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||||
|
; ---- clear the character screen with the given fill character (leaves colors)
|
||||||
|
; (assumes screen matrix is at the default address)
|
||||||
|
%asm {{
|
||||||
|
ldy #250
|
||||||
|
- sta c64.Screen+250*0-1,y
|
||||||
|
sta c64.Screen+250*1-1,y
|
||||||
|
sta c64.Screen+250*2-1,y
|
||||||
|
sta c64.Screen+250*3-1,y
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
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 #250
|
||||||
|
- sta c64.Colors+250*0-1,y
|
||||||
|
sta c64.Colors+250*1-1,y
|
||||||
|
sta c64.Colors+250*2-1,y
|
||||||
|
sta c64.Colors+250*3-1,y
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub color (ubyte txtcol) {
|
||||||
|
c64.COLOR = txtcol
|
||||||
|
}
|
||||||
|
|
||||||
|
sub lowercase() {
|
||||||
|
c64.VMCSB |= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
sub uppercase() {
|
||||||
|
c64.VMCSB &= ~2
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||||
|
; ---- scroll the whole screen 1 character to the left
|
||||||
|
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||||
|
; Carry flag determines if screen color data must be scrolled too
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
bcc _scroll_screen
|
||||||
|
|
||||||
|
+ ; scroll the screen and the color memory
|
||||||
|
ldx #0
|
||||||
|
ldy #38
|
||||||
|
-
|
||||||
|
.for row=0, row<=24, row+=1
|
||||||
|
lda c64.Screen + 40*row + 1,x
|
||||||
|
sta c64.Screen + 40*row + 0,x
|
||||||
|
lda c64.Colors + 40*row + 1,x
|
||||||
|
sta c64.Colors + 40*row + 0,x
|
||||||
|
.next
|
||||||
|
inx
|
||||||
|
dey
|
||||||
|
bpl -
|
||||||
|
rts
|
||||||
|
|
||||||
|
_scroll_screen ; scroll only 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 + 0,x
|
||||||
|
.next
|
||||||
|
inx
|
||||||
|
dey
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
|
||||||
|
; ---- scroll the whole screen 1 character to the right
|
||||||
|
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||||
|
; Carry flag determines if screen color data must be scrolled too
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
bcc _scroll_screen
|
||||||
|
|
||||||
|
+ ; scroll the screen and the color memory
|
||||||
|
ldx #38
|
||||||
|
-
|
||||||
|
.for row=0, row<=24, row+=1
|
||||||
|
lda c64.Screen + 40*row + 0,x
|
||||||
|
sta c64.Screen + 40*row + 1,x
|
||||||
|
lda c64.Colors + 40*row + 0,x
|
||||||
|
sta c64.Colors + 40*row + 1,x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
rts
|
||||||
|
|
||||||
|
_scroll_screen ; scroll only the screen memory
|
||||||
|
ldx #38
|
||||||
|
-
|
||||||
|
.for row=0, row<=24, row+=1
|
||||||
|
lda c64.Screen + 40*row + 0,x
|
||||||
|
sta c64.Screen + 40*row + 1,x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
|
||||||
|
; ---- scroll the whole screen 1 character up
|
||||||
|
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||||
|
; Carry flag determines if screen color data must be scrolled too
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
bcc _scroll_screen
|
||||||
|
|
||||||
|
+ ; scroll the screen and the color memory
|
||||||
|
ldx #39
|
||||||
|
-
|
||||||
|
.for row=1, row<=24, row+=1
|
||||||
|
lda c64.Screen + 40*row,x
|
||||||
|
sta c64.Screen + 40*(row-1),x
|
||||||
|
lda c64.Colors + 40*row,x
|
||||||
|
sta c64.Colors + 40*(row-1),x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
rts
|
||||||
|
|
||||||
|
_scroll_screen ; scroll only the screen memory
|
||||||
|
ldx #39
|
||||||
|
-
|
||||||
|
.for row=1, row<=24, row+=1
|
||||||
|
lda c64.Screen + 40*row,x
|
||||||
|
sta c64.Screen + 40*(row-1),x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
|
||||||
|
; ---- scroll the whole screen 1 character down
|
||||||
|
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||||
|
; Carry flag determines if screen color data must be scrolled too
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
bcc _scroll_screen
|
||||||
|
|
||||||
|
+ ; scroll the screen and 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
|
||||||
|
lda c64.Screen + 40*row,x
|
||||||
|
sta c64.Screen + 40*(row+1),x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
rts
|
||||||
|
|
||||||
|
_scroll_screen ; scroll only the screen memory
|
||||||
|
ldx #39
|
||||||
|
-
|
||||||
|
.for row=23, row>=0, row-=1
|
||||||
|
lda c64.Screen + 40*row,x
|
||||||
|
sta c64.Screen + 40*(row+1),x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
|
||||||
|
|
||||||
|
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print null terminated string from A/Y
|
||||||
|
; note: the compiler contains an optimization that will replace
|
||||||
|
; a call to this subroutine with a string argument of just one char,
|
||||||
|
; by just one call to c64.CHROUT of that single char.
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
sty P8ZP_SCRATCH_REG
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_B1),y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
jsr c64.CHROUT
|
||||||
|
txa
|
||||||
|
jsr c64.CHROUT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
_print_byte_digits
|
||||||
|
pha
|
||||||
|
cpy #'0'
|
||||||
|
beq +
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
jsr c64.CHROUT
|
||||||
|
jmp _ones
|
||||||
|
+ pla
|
||||||
|
cmp #'0'
|
||||||
|
beq _ones
|
||||||
|
jsr c64.CHROUT
|
||||||
|
_ones txa
|
||||||
|
jsr c64.CHROUT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the byte in A in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
pha
|
||||||
|
cmp #0
|
||||||
|
bpl +
|
||||||
|
lda #'-'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
+ pla
|
||||||
|
jsr conv.byte2decimal
|
||||||
|
jmp print_ub._print_byte_digits
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
bcc +
|
||||||
|
pha
|
||||||
|
lda #'$'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
+ jsr conv.ubyte2hex
|
||||||
|
jsr c64.CHROUT
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
bcc +
|
||||||
|
lda #'%'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
+ ldy #8
|
||||||
|
- lda #'0'
|
||||||
|
asl P8ZP_SCRATCH_B1
|
||||||
|
bcc +
|
||||||
|
lda #'1'
|
||||||
|
+ jsr c64.CHROUT
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr print_ubbin
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
jmp print_ubbin
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||||
|
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr print_ubhex
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
jmp print_ubhex
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
beq _allzero
|
||||||
|
cmp #'0'
|
||||||
|
bne _gotdigit
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
|
||||||
|
_gotdigit
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
lda conv.uword2decimal.decTenThousands,y
|
||||||
|
bne _gotdigit
|
||||||
|
rts
|
||||||
|
_allzero
|
||||||
|
lda #'0'
|
||||||
|
jmp c64.CHROUT
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||||
|
%asm {{
|
||||||
|
cpy #0
|
||||||
|
bpl +
|
||||||
|
pha
|
||||||
|
lda #'-'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
tya
|
||||||
|
eor #255
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
eor #255
|
||||||
|
clc
|
||||||
|
adc #1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jmp print_uw
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||||
|
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||||
|
; It assumes the keyboard is selected as I/O channel!
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0 ; char counter = 0
|
||||||
|
- jsr c64.CHRIN
|
||||||
|
cmp #$0d ; return (ascii 13) pressed?
|
||||||
|
beq + ; yes, end.
|
||||||
|
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ lda #0
|
||||||
|
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
|
||||||
|
rts
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
|
||||||
|
; ---- sets the character in the screen matrix at the given position
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda _screenrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
txa
|
||||||
|
clc
|
||||||
|
adc _screenrows,y
|
||||||
|
sta _mod+1
|
||||||
|
bcc +
|
||||||
|
inc _mod+2
|
||||||
|
+ pla
|
||||||
|
_mod sta $ffff ; modified
|
||||||
|
rts
|
||||||
|
|
||||||
|
_screenrows .word $0400 + range(0, 1000, 40)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||||
|
; ---- get the character in the screen matrix at the given location
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda setchr._screenrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
adc setchr._screenrows,y
|
||||||
|
sta _mod+1
|
||||||
|
bcc _mod
|
||||||
|
inc _mod+2
|
||||||
|
_mod lda $ffff ; modified
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
|
||||||
|
; ---- set the color in A on the screen matrix at the given position
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda _colorrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
txa
|
||||||
|
clc
|
||||||
|
adc _colorrows,y
|
||||||
|
sta _mod+1
|
||||||
|
bcc +
|
||||||
|
inc _mod+2
|
||||||
|
+ pla
|
||||||
|
_mod sta $ffff ; modified
|
||||||
|
rts
|
||||||
|
|
||||||
|
_colorrows .word $d800 + range(0, 1000, 40)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||||
|
; ---- get the color in the screen color matrix at the given location
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda setclr._colorrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
adc setclr._colorrows,y
|
||||||
|
sta _mod+1
|
||||||
|
bcc _mod
|
||||||
|
inc _mod+2
|
||||||
|
_mod lda $ffff ; modified
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||||
|
; ---- set char+color at the given position on the screen
|
||||||
|
%asm {{
|
||||||
|
lda row
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda setchr._screenrows+1,y
|
||||||
|
sta _charmod+2
|
||||||
|
adc #$d4
|
||||||
|
sta _colormod+2
|
||||||
|
lda setchr._screenrows,y
|
||||||
|
clc
|
||||||
|
adc column
|
||||||
|
sta _charmod+1
|
||||||
|
sta _colormod+1
|
||||||
|
bcc +
|
||||||
|
inc _charmod+2
|
||||||
|
inc _colormod+2
|
||||||
|
+ lda char
|
||||||
|
_charmod sta $ffff ; modified
|
||||||
|
lda charcolor
|
||||||
|
_colormod sta $ffff ; modified
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||||
|
; ---- safe wrapper around PLOT kernal routine, to save the X register.
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
tax
|
||||||
|
clc
|
||||||
|
jsr c64.PLOT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||||
|
; -- returns the text screen width (number of columns)
|
||||||
|
%asm {{
|
||||||
|
jsr c64.SCREEN
|
||||||
|
txa
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||||
|
; -- returns the text screen height (number of rows)
|
||||||
|
%asm {{
|
||||||
|
jsr c64.SCREEN
|
||||||
|
tya
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,798 +0,0 @@
|
|||||||
; --- 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 c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
ldy c64.SCRATCH_ZPB1
|
|
||||||
jsr FREADUY
|
|
||||||
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
|
|
||||||
ldy c64.SCRATCH_ZPWORD2+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
b2float .proc
|
|
||||||
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
|
||||||
; clobbers A, Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
lda c64.SCRATCH_ZPB1
|
|
||||||
jsr FREADSA
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
uw2float .proc
|
|
||||||
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr GIVUAYFAY
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
w2float .proc
|
|
||||||
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1
|
|
||||||
lda c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr GIVAYF
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_b2float .proc
|
|
||||||
; -- b2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr FREADSA
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_w2float .proc
|
|
||||||
; -- w2float operating on the stack
|
|
||||||
inx
|
|
||||||
ldy c64.ESTACK_LO,x
|
|
||||||
lda c64.ESTACK_HI,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr GIVAYF
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_ub2float .proc
|
|
||||||
; -- ub2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
tay
|
|
||||||
jsr FREADUY
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_uw2float .proc
|
|
||||||
; -- uw2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
ldy c64.ESTACK_HI,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr GIVUAYFAY
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_float2w .proc ; also used for float2b
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr AYINT
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
lda $64
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
lda $65
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_float2uw .proc ; also used for float2ub
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr GETADR
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
tya
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
push_float .proc
|
|
||||||
; ---- push mflpt5 in A/Y onto stack
|
|
||||||
; (taking 3 stack positions = 6 bytes of which 1 is padding)
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
ldy #0
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_rndf .proc
|
|
||||||
; -- put a random floating point value on the stack
|
|
||||||
stx c64.SCRATCH_ZPREG
|
|
||||||
lda #1
|
|
||||||
jsr FREADSA
|
|
||||||
jsr RND ; rng into fac1
|
|
||||||
ldx #<_rndf_rnum5
|
|
||||||
ldy #>_rndf_rnum5
|
|
||||||
jsr MOVMF ; fac1 to mem X/Y
|
|
||||||
ldx c64.SCRATCH_ZPREG
|
|
||||||
lda #<_rndf_rnum5
|
|
||||||
ldy #>_rndf_rnum5
|
|
||||||
jmp push_float
|
|
||||||
_rndf_rnum5 .byte 0,0,0,0,0
|
|
||||||
.pend
|
|
||||||
|
|
||||||
push_float_from_indexed_var .proc
|
|
||||||
; -- push the float from the array at A/Y with index on stack, onto the stack.
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr prog8_lib.pop_index_times_5
|
|
||||||
jsr prog8_lib.add_a_to_zpword
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jmp push_float
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pop_float .proc
|
|
||||||
; ---- pops mflpt5 from stack to memory A/Y
|
|
||||||
; (frees 3 stack positions = 6 bytes of which 1 is padding)
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
ldy #4
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_HI,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_HI,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pop_float_fac1 .proc
|
|
||||||
; -- pops float from stack into FAC1
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jmp MOVFM
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pop_float_to_indexed_var .proc
|
|
||||||
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr prog8_lib.pop_index_times_5
|
|
||||||
jsr prog8_lib.add_a_to_zpword
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jmp pop_float
|
|
||||||
.pend
|
|
||||||
|
|
||||||
copy_float .proc
|
|
||||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
|
||||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
ldy #0
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
inc_var_f .proc
|
|
||||||
; -- add 1 to float pointed to by A/Y
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr MOVFM
|
|
||||||
lda #<FL_FONE
|
|
||||||
ldy #>FL_FONE
|
|
||||||
jsr FADD
|
|
||||||
ldx c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
dec_var_f .proc
|
|
||||||
; -- subtract 1 from float pointed to by A/Y
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<FL_FONE
|
|
||||||
ldy #>FL_FONE
|
|
||||||
jsr MOVFM
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr FSUB
|
|
||||||
ldx c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
inc_indexed_var_f .proc
|
|
||||||
; -- add 1 to float in array pointed to by A/Y, at index X
|
|
||||||
pha
|
|
||||||
txa
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
pla
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
bcc +
|
|
||||||
iny
|
|
||||||
+ jmp inc_var_f
|
|
||||||
.pend
|
|
||||||
|
|
||||||
dec_indexed_var_f .proc
|
|
||||||
; -- subtract 1 to float in array pointed to by A/Y, at index X
|
|
||||||
pha
|
|
||||||
txa
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
pla
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
bcc +
|
|
||||||
iny
|
|
||||||
+ jmp dec_var_f
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
pop_2_floats_f2_in_fac1 .proc
|
|
||||||
; -- pop 2 floats from stack, load the second one in FAC1 as well
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jmp MOVFM
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
|
||||||
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
|
||||||
|
|
||||||
push_fac1_as_result .proc
|
|
||||||
; -- push the float in FAC1 onto the stack, and return from calculation
|
|
||||||
ldx #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr MOVMF
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
jmp push_float
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pow_f .proc
|
|
||||||
; -- push f1 ** f2 on stack
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr CONUPK ; fac2 = float1
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr FPWR
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
div_f .proc
|
|
||||||
; -- push f1/f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FDIV
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
add_f .proc
|
|
||||||
; -- push f1+f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FADD
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
sub_f .proc
|
|
||||||
; -- push f1-f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FSUB
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
mul_f .proc
|
|
||||||
; -- push f1*f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FMULT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
neg_f .proc
|
|
||||||
; -- push -flt back on stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr NEGOP
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
abs_f .proc
|
|
||||||
; -- push abs(float) on stack (as float)
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr ABS
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
equal_f .proc
|
|
||||||
; -- are the two mflpt5 numbers on the stack identical?
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO-3,x
|
|
||||||
cmp c64.ESTACK_LO,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_LO-2,x
|
|
||||||
cmp c64.ESTACK_LO+1,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_LO-1,x
|
|
||||||
cmp c64.ESTACK_LO+2,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_HI-2,x
|
|
||||||
cmp c64.ESTACK_HI+1,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_HI-1,x
|
|
||||||
cmp c64.ESTACK_HI+2,x
|
|
||||||
bne _equals_false
|
|
||||||
_equals_true lda #1
|
|
||||||
_equals_store inx
|
|
||||||
sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
_equals_false lda #0
|
|
||||||
beq _equals_store
|
|
||||||
.pend
|
|
||||||
|
|
||||||
notequal_f .proc
|
|
||||||
; -- are the two mflpt5 numbers on the stack different?
|
|
||||||
jsr equal_f
|
|
||||||
eor #1 ; invert the result
|
|
||||||
sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
less_f .proc
|
|
||||||
; -- is f1 < f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #255
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
lesseq_f .proc
|
|
||||||
; -- is f1 <= f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #255
|
|
||||||
beq compare_floats._return_true
|
|
||||||
cmp #0
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
greater_f .proc
|
|
||||||
; -- is f1 > f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #1
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
greatereq_f .proc
|
|
||||||
; -- is f1 >= f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #1
|
|
||||||
beq compare_floats._return_true
|
|
||||||
cmp #0
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
compare_floats .proc
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr MOVFM ; fac1 = flt1
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
stx c64.SCRATCH_ZPREG
|
|
||||||
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
|
|
||||||
ldx c64.SCRATCH_ZPREG
|
|
||||||
rts
|
|
||||||
_return_false lda #0
|
|
||||||
_return_result sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
_return_true lda #1
|
|
||||||
bne _return_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_sin .proc
|
|
||||||
; -- push sin(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr SIN
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_cos .proc
|
|
||||||
; -- push cos(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr COS
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_tan .proc
|
|
||||||
; -- push tan(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr TAN
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_atan .proc
|
|
||||||
; -- push atan(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr ATN
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_ln .proc
|
|
||||||
; -- push ln(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr LOG
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_log2 .proc
|
|
||||||
; -- push log base 2, ln(f)/ln(2), back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr LOG
|
|
||||||
jsr MOVEF
|
|
||||||
lda #<c64.FL_LOG2
|
|
||||||
ldy #>c64.FL_LOG2
|
|
||||||
jsr MOVFM
|
|
||||||
jsr FDIVT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_sqrt .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr SQR
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_rad .proc
|
|
||||||
; -- convert degrees to radians (d * pi / 180)
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<_pi_div_180
|
|
||||||
ldy #>_pi_div_180
|
|
||||||
jsr FMULT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_deg .proc
|
|
||||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<_one_over_pi_div_180
|
|
||||||
ldy #>_one_over_pi_div_180
|
|
||||||
jsr FMULT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_round .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr FADDH
|
|
||||||
jsr INT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_floor .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr INT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_ceil .proc
|
|
||||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
ldx #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr MOVMF
|
|
||||||
jsr INT
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FCOMP
|
|
||||||
cmp #0
|
|
||||||
beq +
|
|
||||||
lda #<FL_FONE
|
|
||||||
ldy #>FL_FONE
|
|
||||||
jsr FADD
|
|
||||||
+ jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_any_f .proc
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x ; array size
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
|
||||||
jmp prog8_lib.func_any_b._entry
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_all_f .proc
|
|
||||||
inx
|
|
||||||
jsr prog8_lib.peek_address
|
|
||||||
lda c64.ESTACK_LO,x ; array size
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
|
||||||
tay
|
|
||||||
dey
|
|
||||||
- lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
clc
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
cmp #0
|
|
||||||
beq +
|
|
||||||
cpy #255
|
|
||||||
bne -
|
|
||||||
lda #1
|
|
||||||
sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
+ sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_max_f .proc
|
|
||||||
lda #255
|
|
||||||
sta _minmax_cmp+1
|
|
||||||
lda #<_largest_neg_float
|
|
||||||
ldy #>_largest_neg_float
|
|
||||||
_minmax_entry jsr MOVFM
|
|
||||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
- sty c64.SCRATCH_ZPREG
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr FCOMP
|
|
||||||
_minmax_cmp cmp #255 ; modified
|
|
||||||
bne +
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr MOVFM
|
|
||||||
+ lda c64.SCRATCH_ZPWORD1
|
|
||||||
clc
|
|
||||||
adc #5
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
bcc +
|
|
||||||
inc c64.SCRATCH_ZPWORD1+1
|
|
||||||
+ ldy c64.SCRATCH_ZPREG
|
|
||||||
dey
|
|
||||||
cpy #255
|
|
||||||
bne -
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_min_f .proc
|
|
||||||
lda #1
|
|
||||||
sta func_max_f._minmax_cmp+1
|
|
||||||
lda #<_largest_pos_float
|
|
||||||
ldy #>_largest_pos_float
|
|
||||||
jmp func_max_f._minmax_entry
|
|
||||||
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_sum_f .proc
|
|
||||||
lda #<FL_ZERO
|
|
||||||
ldy #>FL_ZERO
|
|
||||||
jsr MOVFM
|
|
||||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
- sty c64.SCRATCH_ZPREG
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr FADD
|
|
||||||
ldy c64.SCRATCH_ZPREG
|
|
||||||
dey
|
|
||||||
cpy #255
|
|
||||||
beq +
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
clc
|
|
||||||
adc #5
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
bcc -
|
|
||||||
inc c64.SCRATCH_ZPWORD1+1
|
|
||||||
bne -
|
|
||||||
+ jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
sign_f .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
jsr SIGN
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
set_0_array_float .proc
|
|
||||||
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.ESTACK_LO,x
|
|
||||||
tay
|
|
||||||
lda #0
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
iny
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
iny
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
iny
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
iny
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),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 c64.ESTACK_LO,x
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.ESTACK_LO,x
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPWORD2
|
|
||||||
ldy c64.SCRATCH_ZPWORD2+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 (c64.SCRATCH_ZPWORD1),y
|
|
||||||
pha
|
|
||||||
lda (c64.SCRATCH_ZPWORD2),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
pla
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
dey
|
|
||||||
bpl -
|
|
||||||
rts
|
|
||||||
.pend
|
|
File diff suppressed because it is too large
Load Diff
736
compiler/res/prog8lib/conv.p8
Normal file
736
compiler/res/prog8lib/conv.p8
Normal file
@ -0,0 +1,736 @@
|
|||||||
|
; Number conversions routines.
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
|
||||||
|
|
||||||
|
conv {
|
||||||
|
|
||||||
|
; ----- number conversions to decimal strings ----
|
||||||
|
|
||||||
|
str string_out = "????????????????" ; result buffer for the string conversion routines
|
||||||
|
|
||||||
|
asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
sty string_out
|
||||||
|
sta string_out+1
|
||||||
|
stx string_out+2
|
||||||
|
lda #0
|
||||||
|
sta string_out+3
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str_ub (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- convert the ubyte in A in decimal string form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
ldy #0
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
_output_byte_digits
|
||||||
|
; hundreds?
|
||||||
|
cpy #'0'
|
||||||
|
beq +
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
sta string_out,y
|
||||||
|
pla
|
||||||
|
inc P8ZP_SCRATCH_B1
|
||||||
|
; tens?
|
||||||
|
+ ldy P8ZP_SCRATCH_B1
|
||||||
|
cmp #'0'
|
||||||
|
beq +
|
||||||
|
sta string_out,y
|
||||||
|
iny
|
||||||
|
+ ; ones.
|
||||||
|
txa
|
||||||
|
sta string_out,y
|
||||||
|
iny
|
||||||
|
lda #0
|
||||||
|
sta string_out,y
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str_b (byte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- convert the byte in A in decimal string form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
ldy #0
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
cmp #0
|
||||||
|
bpl +
|
||||||
|
pha
|
||||||
|
lda #'-'
|
||||||
|
sta string_out
|
||||||
|
inc P8ZP_SCRATCH_B1
|
||||||
|
pla
|
||||||
|
+ jsr conv.byte2decimal
|
||||||
|
bra str_ub._output_byte_digits
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str_ubhex (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- convert the ubyte in A in hex string form
|
||||||
|
%asm {{
|
||||||
|
jsr conv.ubyte2hex
|
||||||
|
sta string_out
|
||||||
|
sty string_out+1
|
||||||
|
lda #0
|
||||||
|
sta string_out+2
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str_ubbin (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- convert the ubyte in A in binary string form
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
ldy #0
|
||||||
|
sty string_out+8
|
||||||
|
ldy #7
|
||||||
|
- lsr P8ZP_SCRATCH_B1
|
||||||
|
bcc +
|
||||||
|
lda #'1'
|
||||||
|
bne _digit
|
||||||
|
+ lda #'0'
|
||||||
|
_digit sta string_out,y
|
||||||
|
dey
|
||||||
|
bpl -
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str_uwbin (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- convert the uword in A/Y in binary string form
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
tya
|
||||||
|
jsr str_ubbin
|
||||||
|
ldy #0
|
||||||
|
sty string_out+16
|
||||||
|
ldy #7
|
||||||
|
- lsr P8ZP_SCRATCH_REG
|
||||||
|
bcc +
|
||||||
|
lda #'1'
|
||||||
|
bne _digit
|
||||||
|
+ lda #'0'
|
||||||
|
_digit sta string_out+8,y
|
||||||
|
dey
|
||||||
|
bpl -
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- convert the uword in A/Y in hexadecimal string form (4 digits)
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr conv.ubyte2hex
|
||||||
|
sta string_out
|
||||||
|
sty string_out+1
|
||||||
|
pla
|
||||||
|
jsr conv.ubyte2hex
|
||||||
|
sta string_out+2
|
||||||
|
sty string_out+3
|
||||||
|
lda #0
|
||||||
|
sta string_out+4
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
sta string_out,y
|
||||||
|
beq +
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str_uw (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldx #0
|
||||||
|
_output_digits
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
beq _allzero
|
||||||
|
cmp #'0'
|
||||||
|
bne _gotdigit
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
_gotdigit sta string_out,x
|
||||||
|
inx
|
||||||
|
iny
|
||||||
|
lda conv.uword2decimal.decTenThousands,y
|
||||||
|
bne _gotdigit
|
||||||
|
_end lda #0
|
||||||
|
sta string_out,x
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
|
||||||
|
_allzero lda #'0'
|
||||||
|
sta string_out,x
|
||||||
|
inx
|
||||||
|
bne _end
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str_w (word value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
|
||||||
|
%asm {{
|
||||||
|
cpy #0
|
||||||
|
bpl str_uw
|
||||||
|
phx
|
||||||
|
pha
|
||||||
|
lda #'-'
|
||||||
|
sta string_out
|
||||||
|
tya
|
||||||
|
eor #255
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
eor #255
|
||||||
|
clc
|
||||||
|
adc #1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jsr conv.uword2decimal
|
||||||
|
ldx #1
|
||||||
|
bne str_uw._output_digits
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
; ---- string conversion to numbers -----
|
||||||
|
|
||||||
|
asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
|
||||||
|
; -- parses a string into a 16 bit unsigned number. String may be in decimal, hex or binary format.
|
||||||
|
; (the latter two require a $ or % prefix to be recognised)
|
||||||
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
|
; returns amount of processed characters in A, and the parsed number will be in cx16.r15.
|
||||||
|
; if the string was invalid, 0 will be returned in A.
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
cmp #'$'
|
||||||
|
beq _hex
|
||||||
|
cmp #'%'
|
||||||
|
beq _bin
|
||||||
|
pla
|
||||||
|
jsr str2uword
|
||||||
|
jmp _result
|
||||||
|
_hex pla
|
||||||
|
jsr hex2uword
|
||||||
|
jmp _result
|
||||||
|
_bin pla
|
||||||
|
jsr bin2uword
|
||||||
|
_result
|
||||||
|
pha
|
||||||
|
lda cx16.r15
|
||||||
|
sta P8ZP_SCRATCH_B1 ; result value
|
||||||
|
pla
|
||||||
|
sta cx16.r15
|
||||||
|
sty cx16.r15+1
|
||||||
|
lda P8ZP_SCRATCH_B1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub str2ubyte(str string @AY) clobbers(Y) -> ubyte @A {
|
||||||
|
; -- returns in A the unsigned byte value of the string number argument in AY
|
||||||
|
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||||
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
|
; result in A, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||||
|
%asm {{
|
||||||
|
jsr conv.str2uword
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub str2byte(str string @AY) clobbers(Y) -> ubyte @A {
|
||||||
|
; -- returns in A the signed byte value of the string number argument in AY
|
||||||
|
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||||
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
|
; result in A, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||||
|
%asm {{
|
||||||
|
jsr conv.str2word
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str2uword(str string @AY) -> uword @AY {
|
||||||
|
; -- returns the unsigned word value of the string number argument in AY
|
||||||
|
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||||
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
|
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||||
|
%asm {{
|
||||||
|
_result = P8ZP_SCRATCH_W1
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy #0
|
||||||
|
sty _result
|
||||||
|
sty _result+1
|
||||||
|
sty cx16.r15+1
|
||||||
|
_loop
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
sec
|
||||||
|
sbc #48
|
||||||
|
bpl _digit
|
||||||
|
_done
|
||||||
|
sty cx16.r15
|
||||||
|
lda _result
|
||||||
|
ldy _result+1
|
||||||
|
rts
|
||||||
|
_digit
|
||||||
|
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 _loop
|
||||||
|
; 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)
|
||||||
|
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||||
|
%asm {{
|
||||||
|
_result = P8ZP_SCRATCH_W1
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy #0
|
||||||
|
sty _result
|
||||||
|
sty _result+1
|
||||||
|
sty _negative
|
||||||
|
sty cx16.r15+1
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
cmp #'+'
|
||||||
|
bne +
|
||||||
|
iny
|
||||||
|
+ cmp #'-'
|
||||||
|
bne _parse
|
||||||
|
inc _negative
|
||||||
|
iny
|
||||||
|
_parse lda (P8ZP_SCRATCH_W2),y
|
||||||
|
sec
|
||||||
|
sbc #48
|
||||||
|
bpl _digit
|
||||||
|
_done
|
||||||
|
sty cx16.r15
|
||||||
|
lda _negative
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
lda #0
|
||||||
|
sbc _result
|
||||||
|
sta _result
|
||||||
|
lda #0
|
||||||
|
sbc _result+1
|
||||||
|
sta _result+1
|
||||||
|
+ lda _result
|
||||||
|
ldy _result+1
|
||||||
|
rts
|
||||||
|
_digit
|
||||||
|
cmp #10
|
||||||
|
bcs _done
|
||||||
|
; add digit to result
|
||||||
|
pha
|
||||||
|
jsr str2uword._result_times_10
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
adc _result
|
||||||
|
sta _result
|
||||||
|
bcc +
|
||||||
|
inc _result+1
|
||||||
|
+ iny
|
||||||
|
bne _parse
|
||||||
|
; never reached
|
||||||
|
_negative .byte 0
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub hex2uword(str string @AY) -> uword @AY {
|
||||||
|
; -- hexadecimal string (with or without '$') to uword.
|
||||||
|
; string may be in petscii or c64-screencode encoding.
|
||||||
|
; stops parsing at the first character that's not a hex digit (except leading $)
|
||||||
|
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy #0
|
||||||
|
sty P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
sty cx16.r15+1
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
beq _stop
|
||||||
|
cmp #'$'
|
||||||
|
bne _loop
|
||||||
|
iny
|
||||||
|
_loop
|
||||||
|
lda #0
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
beq _stop
|
||||||
|
cmp #7 ; screencode letters A-F are 1-6
|
||||||
|
bcc _add_letter
|
||||||
|
cmp #'g'
|
||||||
|
bcs _stop
|
||||||
|
cmp #'a'
|
||||||
|
bcs _add_letter
|
||||||
|
cmp #'0'
|
||||||
|
bcc _stop
|
||||||
|
cmp #'9'+1
|
||||||
|
bcs _stop
|
||||||
|
_calc
|
||||||
|
asl P8ZP_SCRATCH_W1
|
||||||
|
rol P8ZP_SCRATCH_W1+1
|
||||||
|
asl P8ZP_SCRATCH_W1
|
||||||
|
rol P8ZP_SCRATCH_W1+1
|
||||||
|
asl P8ZP_SCRATCH_W1
|
||||||
|
rol P8ZP_SCRATCH_W1+1
|
||||||
|
asl P8ZP_SCRATCH_W1
|
||||||
|
rol P8ZP_SCRATCH_W1+1
|
||||||
|
and #$0f
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
ora P8ZP_SCRATCH_W1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
iny
|
||||||
|
bne _loop
|
||||||
|
_stop
|
||||||
|
sty cx16.r15
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
rts
|
||||||
|
_add_letter
|
||||||
|
pha
|
||||||
|
lda #9
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
pla
|
||||||
|
jmp _calc
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub bin2uword(str string @AY) -> uword @AY {
|
||||||
|
; -- binary string (with or without '%') to uword.
|
||||||
|
; stops parsing at the first character that's not a 0 or 1. (except leading %)
|
||||||
|
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy #0
|
||||||
|
sty P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
sty cx16.r15+1
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
beq _stop
|
||||||
|
cmp #'%'
|
||||||
|
bne _loop
|
||||||
|
iny
|
||||||
|
_loop
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
cmp #'0'
|
||||||
|
bcc _stop
|
||||||
|
cmp #'2'
|
||||||
|
bcs _stop
|
||||||
|
_first asl P8ZP_SCRATCH_W1
|
||||||
|
rol P8ZP_SCRATCH_W1+1
|
||||||
|
and #1
|
||||||
|
ora P8ZP_SCRATCH_W1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
iny
|
||||||
|
bne _loop
|
||||||
|
_stop
|
||||||
|
sty cx16.r15
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
; ----- low level 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
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||||
|
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||||
|
; note: if the number is negative, you have to deal with the '-' yourself!
|
||||||
|
%asm {{
|
||||||
|
cmp #0
|
||||||
|
bpl +
|
||||||
|
eor #255
|
||||||
|
clc
|
||||||
|
adc #1
|
||||||
|
+ jmp ubyte2decimal
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub ubyte2hex (ubyte value @A) -> ubyte @A, ubyte @Y {
|
||||||
|
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
pha
|
||||||
|
and #$0f
|
||||||
|
tax
|
||||||
|
ldy _hex_digits,x
|
||||||
|
pla
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
tax
|
||||||
|
lda _hex_digits,x
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
|
||||||
|
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub uword2hex (uword value @AY) clobbers(A,Y) {
|
||||||
|
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
tya
|
||||||
|
jsr ubyte2hex
|
||||||
|
sta output
|
||||||
|
sty output+1
|
||||||
|
lda P8ZP_SCRATCH_REG
|
||||||
|
jsr ubyte2hex
|
||||||
|
sta output+2
|
||||||
|
sty output+3
|
||||||
|
rts
|
||||||
|
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
155
compiler/res/prog8lib/cx16/floats.p8
Normal file
155
compiler/res/prog8lib/cx16/floats.p8
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
; Prog8 definitions for floating point handling on the CommanderX16
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target cx16
|
||||||
|
%option enable_floats
|
||||||
|
|
||||||
|
floats {
|
||||||
|
; ---- this block contains C-64 floating point related functions ----
|
||||||
|
|
||||||
|
const float PI = 3.141592653589793
|
||||||
|
const float TWOPI = 6.283185307179586
|
||||||
|
|
||||||
|
|
||||||
|
; ---- ROM float functions ----
|
||||||
|
|
||||||
|
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
||||||
|
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||||
|
|
||||||
|
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||||
|
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||||
|
|
||||||
|
romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||||
|
|
||||||
|
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||||
|
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||||
|
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||||
|
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||||
|
|
||||||
|
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||||
|
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
|
romsub $fe06 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||||
|
|
||||||
|
romsub $fe09 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||||
|
romsub $fe0c = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||||
|
romsub $fe0f = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||||
|
romsub $fe12 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||||
|
romsub $fe15 = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||||
|
romsub $fe1b = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
||||||
|
romsub $fe1e = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
||||||
|
romsub $fe24 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||||
|
romsub $fe27 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||||
|
romsub $fe2a = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||||
|
romsub $fe33 = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||||
|
romsub $fe36 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||||
|
romsub $fe3c = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||||
|
romsub $fe3f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||||
|
romsub $fe42 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
||||||
|
|
||||||
|
romsub $fe48 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||||
|
romsub $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||||
|
romsub $fe4e = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||||
|
romsub $fe51 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||||
|
romsub $fe54 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||||
|
romsub $fe5a = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||||
|
romsub $fe5d = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||||
|
romsub $fe60 = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||||
|
romsub $fe6c = ABS() ; fac1 = ABS(fac1)
|
||||||
|
romsub $fe6f = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||||
|
romsub $fe78 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||||
|
romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||||
|
romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||||
|
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||||
|
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||||
|
; note: there is no FPWR() on the Cx16
|
||||||
|
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
|
||||||
|
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||||
|
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
|
||||||
|
romsub $fea2 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||||
|
romsub $fea5 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||||
|
romsub $fea8 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||||
|
romsub $feab = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||||
|
romsub $feae = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||||
|
|
||||||
|
|
||||||
|
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||||
|
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_W2
|
||||||
|
jsr GIVAYF ; load it as signed... correct afterwards
|
||||||
|
lda P8ZP_SCRATCH_B1
|
||||||
|
bpl +
|
||||||
|
lda #<_flt65536
|
||||||
|
ldy #>_flt65536
|
||||||
|
jsr FADD
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
_flt65536 .byte 145,0,0,0,0 ; 65536.0
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||||
|
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
||||||
|
; ---- fac1 to signed word in A/Y
|
||||||
|
%asm {{
|
||||||
|
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||||
|
; ---- fac1 to unsigned word in A/Y
|
||||||
|
%asm {{
|
||||||
|
jsr GETADR ; this uses the inverse order, Y/A
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub print_f (float value) {
|
||||||
|
; ---- prints the floating point value (without a newline).
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
lda #<value
|
||||||
|
ldy #>value
|
||||||
|
jsr MOVFM ; load float into fac1
|
||||||
|
jsr FOUT ; fac1 to string in A/Y
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
%asminclude "library:c64/floats.asm", ""
|
||||||
|
%asminclude "library:c64/floats_funcs.asm", ""
|
||||||
|
|
||||||
|
}
|
976
compiler/res/prog8lib/cx16/gfx2.p8
Normal file
976
compiler/res/prog8lib/cx16/gfx2.p8
Normal file
@ -0,0 +1,976 @@
|
|||||||
|
%target cx16
|
||||||
|
|
||||||
|
; Bitmap pixel graphics routines for the CommanderX16
|
||||||
|
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
|
||||||
|
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
|
||||||
|
;
|
||||||
|
; No text layer is currently shown, text can be drawn as part of the bitmap itself.
|
||||||
|
; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead.
|
||||||
|
; Note: for color palette manipulation, use the "palette" module or write Vera registers yourself.
|
||||||
|
; Note: this library implements code for various resolutions and color depths. This takes up memory.
|
||||||
|
; If you're memory constrained you should probably not use this built-in library,
|
||||||
|
; but make a copy in your project only containing the code for the required resolution.
|
||||||
|
;
|
||||||
|
;
|
||||||
|
; SCREEN MODE LIST:
|
||||||
|
; mode 0 = reset back to default text mode
|
||||||
|
; mode 1 = bitmap 320 x 240 monochrome
|
||||||
|
; mode 2 = bitmap 320 x 240 x 4c (TODO not yet implemented)
|
||||||
|
; mode 3 = bitmap 320 x 240 x 16c (TODO not yet implemented)
|
||||||
|
; mode 4 = bitmap 320 x 240 x 256c
|
||||||
|
; mode 5 = bitmap 640 x 480 monochrome
|
||||||
|
; mode 6 = bitmap 640 x 480 x 4c
|
||||||
|
; higher color dephts in highres are not supported due to lack of VRAM
|
||||||
|
|
||||||
|
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
|
||||||
|
|
||||||
|
gfx2 {
|
||||||
|
|
||||||
|
; read-only control variables:
|
||||||
|
ubyte active_mode = 0
|
||||||
|
uword width = 0
|
||||||
|
uword height = 0
|
||||||
|
ubyte bpp = 0
|
||||||
|
ubyte monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes
|
||||||
|
|
||||||
|
sub screen_mode(ubyte mode) {
|
||||||
|
when mode {
|
||||||
|
1 -> {
|
||||||
|
; lores monochrome
|
||||||
|
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||||
|
cx16.VERA_DC_HSCALE = 64
|
||||||
|
cx16.VERA_DC_VSCALE = 64
|
||||||
|
cx16.VERA_L1_CONFIG = %00000100
|
||||||
|
cx16.VERA_L1_MAPBASE = 0
|
||||||
|
cx16.VERA_L1_TILEBASE = 0
|
||||||
|
width = 320
|
||||||
|
height = 240
|
||||||
|
bpp = 1
|
||||||
|
}
|
||||||
|
; TODO modes 2, 3 not yet implemented
|
||||||
|
4 -> {
|
||||||
|
; lores 256c
|
||||||
|
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||||
|
cx16.VERA_DC_HSCALE = 64
|
||||||
|
cx16.VERA_DC_VSCALE = 64
|
||||||
|
cx16.VERA_L1_CONFIG = %00000111
|
||||||
|
cx16.VERA_L1_MAPBASE = 0
|
||||||
|
cx16.VERA_L1_TILEBASE = 0
|
||||||
|
width = 320
|
||||||
|
height = 240
|
||||||
|
bpp = 8
|
||||||
|
}
|
||||||
|
5 -> {
|
||||||
|
; highres monochrome
|
||||||
|
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||||
|
cx16.VERA_DC_HSCALE = 128
|
||||||
|
cx16.VERA_DC_VSCALE = 128
|
||||||
|
cx16.VERA_L1_CONFIG = %00000100
|
||||||
|
cx16.VERA_L1_MAPBASE = 0
|
||||||
|
cx16.VERA_L1_TILEBASE = %00000001
|
||||||
|
width = 640
|
||||||
|
height = 480
|
||||||
|
bpp = 1
|
||||||
|
}
|
||||||
|
6 -> {
|
||||||
|
; highres 4c
|
||||||
|
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||||
|
cx16.VERA_DC_HSCALE = 128
|
||||||
|
cx16.VERA_DC_VSCALE = 128
|
||||||
|
cx16.VERA_L1_CONFIG = %00000101
|
||||||
|
cx16.VERA_L1_MAPBASE = 0
|
||||||
|
cx16.VERA_L1_TILEBASE = %00000001
|
||||||
|
width = 640
|
||||||
|
height = 480
|
||||||
|
bpp = 2
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
; back to default text mode and colors
|
||||||
|
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
|
||||||
|
c64.CINT() ; back to text mode
|
||||||
|
width = 0
|
||||||
|
height = 0
|
||||||
|
bpp = 0
|
||||||
|
mode = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
active_mode = mode
|
||||||
|
if bpp
|
||||||
|
clear_screen()
|
||||||
|
}
|
||||||
|
|
||||||
|
sub clear_screen() {
|
||||||
|
monochrome_stipple(false)
|
||||||
|
position(0, 0)
|
||||||
|
when active_mode {
|
||||||
|
1 -> {
|
||||||
|
; lores monochrome
|
||||||
|
repeat 240/2/8
|
||||||
|
cs_innerloop640()
|
||||||
|
}
|
||||||
|
; TODO mode 2, 3
|
||||||
|
4 -> {
|
||||||
|
; lores 256c
|
||||||
|
repeat 240/2
|
||||||
|
cs_innerloop640()
|
||||||
|
}
|
||||||
|
5 -> {
|
||||||
|
; highres monochrome
|
||||||
|
repeat 480/8
|
||||||
|
cs_innerloop640()
|
||||||
|
}
|
||||||
|
6 -> {
|
||||||
|
; highres 4c
|
||||||
|
repeat 480/4
|
||||||
|
cs_innerloop640()
|
||||||
|
}
|
||||||
|
; modes 7 and 8 not supported due to lack of VRAM
|
||||||
|
}
|
||||||
|
position(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub monochrome_stipple(ubyte enable) {
|
||||||
|
monochrome_dont_stipple_flag = not enable
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rect(uword x, uword y, uword width, uword height, ubyte color) {
|
||||||
|
if width==0 or height==0
|
||||||
|
return
|
||||||
|
horizontal_line(x, y, width, color)
|
||||||
|
if height==1
|
||||||
|
return
|
||||||
|
horizontal_line(x, y+height-1, width, color)
|
||||||
|
vertical_line(x, y+1, height-2, color)
|
||||||
|
if width==1
|
||||||
|
return
|
||||||
|
vertical_line(x+width-1, y+1, height-2, color)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fillrect(uword x, uword y, uword width, uword height, ubyte color) {
|
||||||
|
if width==0
|
||||||
|
return
|
||||||
|
repeat height {
|
||||||
|
horizontal_line(x, y, width, color)
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub horizontal_line(uword x, uword y, uword length, ubyte color) {
|
||||||
|
if length==0
|
||||||
|
return
|
||||||
|
when active_mode {
|
||||||
|
1, 5 -> {
|
||||||
|
; monochrome modes, either resolution
|
||||||
|
ubyte separate_pixels = (8-lsb(x)) & 7
|
||||||
|
if separate_pixels as uword > length
|
||||||
|
separate_pixels = lsb(length)
|
||||||
|
repeat separate_pixels {
|
||||||
|
; TODO optimize this by writing a masked byte in 1 go
|
||||||
|
plot(x, y, color)
|
||||||
|
x++
|
||||||
|
}
|
||||||
|
length -= separate_pixels
|
||||||
|
if length {
|
||||||
|
position(x, y)
|
||||||
|
separate_pixels = lsb(length) & 7
|
||||||
|
x += length & $fff8
|
||||||
|
%asm {{
|
||||||
|
lsr length+1
|
||||||
|
ror length
|
||||||
|
lsr length+1
|
||||||
|
ror length
|
||||||
|
lsr length+1
|
||||||
|
ror length
|
||||||
|
lda color
|
||||||
|
bne +
|
||||||
|
ldy #0 ; black
|
||||||
|
bra _loop
|
||||||
|
+ lda monochrome_dont_stipple_flag
|
||||||
|
beq _stipple
|
||||||
|
ldy #255 ; don't stipple
|
||||||
|
bra _loop
|
||||||
|
_stipple lda y
|
||||||
|
and #1 ; determine stipple pattern to use
|
||||||
|
bne +
|
||||||
|
ldy #%01010101
|
||||||
|
bra _loop
|
||||||
|
+ ldy #%10101010
|
||||||
|
_loop lda length
|
||||||
|
ora length+1
|
||||||
|
beq _done
|
||||||
|
sty cx16.VERA_DATA0
|
||||||
|
lda length
|
||||||
|
bne +
|
||||||
|
dec length+1
|
||||||
|
+ dec length
|
||||||
|
bra _loop
|
||||||
|
_done
|
||||||
|
}}
|
||||||
|
repeat separate_pixels {
|
||||||
|
; TODO optimize this by writing a masked byte in 1 go
|
||||||
|
plot(x, y, color)
|
||||||
|
x++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
|
||||||
|
}
|
||||||
|
4 -> {
|
||||||
|
; lores 256c
|
||||||
|
position(x, y)
|
||||||
|
%asm {{
|
||||||
|
lda color
|
||||||
|
phx
|
||||||
|
ldx length+1
|
||||||
|
beq +
|
||||||
|
ldy #0
|
||||||
|
- sta cx16.VERA_DATA0
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
+ ldy length ; remaining
|
||||||
|
beq +
|
||||||
|
- sta cx16.VERA_DATA0
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
+ plx
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
6 -> {
|
||||||
|
; highres 4c
|
||||||
|
; TODO also mostly usable for lores 4c?
|
||||||
|
color &= 3
|
||||||
|
ubyte[4] colorbits
|
||||||
|
ubyte ii
|
||||||
|
for ii in 3 downto 0 {
|
||||||
|
colorbits[ii] = color
|
||||||
|
color <<= 2
|
||||||
|
}
|
||||||
|
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
%asm {{
|
||||||
|
lda cx16.VERA_ADDR_H
|
||||||
|
and #%00000111 ; no auto advance
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
stz cx16.VERA_CTRL ; setup vera addr 0
|
||||||
|
lda cx16.r1
|
||||||
|
and #1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda cx16.r0
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
phx
|
||||||
|
ldx x
|
||||||
|
}}
|
||||||
|
|
||||||
|
repeat length {
|
||||||
|
%asm {{
|
||||||
|
txa
|
||||||
|
and #3
|
||||||
|
tay
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
and gfx2.plot.mask4c,y
|
||||||
|
ora colorbits,y
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
cpy #%00000011 ; next vera byte?
|
||||||
|
bne +
|
||||||
|
inc cx16.VERA_ADDR_L
|
||||||
|
bne +
|
||||||
|
inc cx16.VERA_ADDR_M
|
||||||
|
+ bne +
|
||||||
|
inc cx16.VERA_ADDR_H
|
||||||
|
+ inx ; next pixel
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
plx
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub vertical_line(uword x, uword y, uword height, ubyte color) {
|
||||||
|
position(x,y)
|
||||||
|
when active_mode {
|
||||||
|
1, 5 -> {
|
||||||
|
; monochrome, either resolution
|
||||||
|
; note for the 1 bpp modes we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
|
||||||
|
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
|
||||||
|
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
|
||||||
|
cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask
|
||||||
|
if active_mode>=5
|
||||||
|
cx16.r14 = 640/8
|
||||||
|
else
|
||||||
|
cx16.r14 = 320/8
|
||||||
|
if color {
|
||||||
|
if monochrome_dont_stipple_flag {
|
||||||
|
repeat height {
|
||||||
|
%asm {{
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
ora cx16.r15
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
lda cx16.VERA_ADDR_L
|
||||||
|
clc
|
||||||
|
adc cx16.r14 ; advance vera ptr to go to the next line
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.VERA_ADDR_M
|
||||||
|
adc #0
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
|
||||||
|
; adc #0
|
||||||
|
; sta cx16.VERA_ADDR_H
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
; stippling.
|
||||||
|
height = (height+1)/2 ; TODO is the line sometimes 1 pixel too long now because of rounding?
|
||||||
|
%asm {{
|
||||||
|
lda x
|
||||||
|
eor y
|
||||||
|
and #1
|
||||||
|
bne +
|
||||||
|
lda cx16.VERA_ADDR_L
|
||||||
|
clc
|
||||||
|
adc cx16.r14 ; advance vera ptr to go to the next line for correct stipple pattern
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.VERA_ADDR_M
|
||||||
|
adc #0
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
+
|
||||||
|
asl cx16.r14
|
||||||
|
ldy height
|
||||||
|
beq +
|
||||||
|
- lda cx16.VERA_DATA0
|
||||||
|
ora cx16.r15
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
lda cx16.VERA_ADDR_L
|
||||||
|
clc
|
||||||
|
adc cx16.r14 ; advance vera data ptr to go to the next-next line
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.VERA_ADDR_M
|
||||||
|
adc #0
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
|
||||||
|
; adc #0
|
||||||
|
; sta cx16.VERA_ADDR_H
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
+
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cx16.r15 = ~cx16.r15
|
||||||
|
repeat height {
|
||||||
|
%asm {{
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
and cx16.r15
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
lda cx16.VERA_ADDR_L
|
||||||
|
clc
|
||||||
|
adc cx16.r14 ; advance vera data ptr to go to the next line
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.VERA_ADDR_M
|
||||||
|
adc #0
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
; the bitmap size is small enough to not have to deal with the _H part:
|
||||||
|
; lda cx16.VERA_ADDR_H
|
||||||
|
; adc #0
|
||||||
|
; sta cx16.VERA_ADDR_H
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
4 -> {
|
||||||
|
; lores 256c
|
||||||
|
; set vera auto-increment to 320 pixel increment (=next line)
|
||||||
|
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
|
||||||
|
%asm {{
|
||||||
|
ldy height
|
||||||
|
beq +
|
||||||
|
lda color
|
||||||
|
- sta cx16.VERA_DATA0
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
+
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
6 -> {
|
||||||
|
; highres 4c
|
||||||
|
; note for this mode we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
|
||||||
|
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
|
||||||
|
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
|
||||||
|
; TODO also mostly usable for lores 4c?
|
||||||
|
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
|
||||||
|
; TODO optimize the loop in pure assembly
|
||||||
|
color &= 3
|
||||||
|
color <<= gfx2.plot.shift4c[lsb(x) & 3]
|
||||||
|
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
|
||||||
|
repeat height {
|
||||||
|
ubyte value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask | color
|
||||||
|
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
|
||||||
|
%asm {{
|
||||||
|
; 24 bits add 160 (640/4)
|
||||||
|
clc
|
||||||
|
lda cx16.r0
|
||||||
|
adc #640/4
|
||||||
|
sta cx16.r0
|
||||||
|
lda cx16.r0+1
|
||||||
|
adc #0
|
||||||
|
sta cx16.r0+1
|
||||||
|
bcc +
|
||||||
|
inc cx16.r1
|
||||||
|
+
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
|
||||||
|
; Bresenham algorithm.
|
||||||
|
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||||
|
; TODO there are some slight errors at the first/last pixels in certain slopes...
|
||||||
|
if y1>y2 {
|
||||||
|
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||||
|
swap(x1, x2)
|
||||||
|
swap(y1, y2)
|
||||||
|
}
|
||||||
|
word @zp dx = x2-x1 as word
|
||||||
|
word @zp dy = y2-y1 as word
|
||||||
|
|
||||||
|
if dx==0 {
|
||||||
|
vertical_line(x1, y1, abs(dy)+1 as uword, color)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if dy==0 {
|
||||||
|
if x1>x2
|
||||||
|
x1=x2
|
||||||
|
horizontal_line(x1, y1, abs(dx)+1 as uword, color)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
; TODO rewrite the rest in optimized assembly (or reuse GRAPH_draw_line if we can get the FB replacement vector layer working)
|
||||||
|
word @zp d = 0
|
||||||
|
ubyte positive_ix = true
|
||||||
|
if dx < 0 {
|
||||||
|
dx = -dx
|
||||||
|
positive_ix = false
|
||||||
|
}
|
||||||
|
dx *= 2
|
||||||
|
dy *= 2
|
||||||
|
cx16.r14 = x1 ; internal plot X
|
||||||
|
|
||||||
|
if dx >= dy {
|
||||||
|
if positive_ix {
|
||||||
|
repeat {
|
||||||
|
plot(cx16.r14, y1, color)
|
||||||
|
if cx16.r14==x2
|
||||||
|
return
|
||||||
|
cx16.r14++
|
||||||
|
d += dy
|
||||||
|
if d > dx {
|
||||||
|
y1++
|
||||||
|
d -= dx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat {
|
||||||
|
plot(cx16.r14, y1, color)
|
||||||
|
if cx16.r14==x2
|
||||||
|
return
|
||||||
|
cx16.r14--
|
||||||
|
d += dy
|
||||||
|
if d > dx {
|
||||||
|
y1++
|
||||||
|
d -= dx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if positive_ix {
|
||||||
|
repeat {
|
||||||
|
plot(cx16.r14, y1, color)
|
||||||
|
if y1 == y2
|
||||||
|
return
|
||||||
|
y1++
|
||||||
|
d += dx
|
||||||
|
if d > dy {
|
||||||
|
cx16.r14++
|
||||||
|
d -= dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat {
|
||||||
|
plot(cx16.r14, y1, color)
|
||||||
|
if y1 == y2
|
||||||
|
return
|
||||||
|
y1++
|
||||||
|
d += dx
|
||||||
|
if d > dy {
|
||||||
|
cx16.r14--
|
||||||
|
d -= dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
|
||||||
|
; Midpoint algorithm.
|
||||||
|
if radius==0
|
||||||
|
return
|
||||||
|
|
||||||
|
ubyte @zp xx = radius
|
||||||
|
ubyte @zp yy = 0
|
||||||
|
word @zp decisionOver2 = (1 as word)-xx
|
||||||
|
; R14 = internal plot X
|
||||||
|
; R15 = internal plot Y
|
||||||
|
|
||||||
|
while xx>=yy {
|
||||||
|
cx16.r14 = xcenter + xx
|
||||||
|
cx16.r15 = ycenter + yy
|
||||||
|
plot(cx16.r14, cx16.r15, color)
|
||||||
|
cx16.r14 = xcenter - xx
|
||||||
|
plot(cx16.r14, cx16.r15, color)
|
||||||
|
cx16.r14 = xcenter + xx
|
||||||
|
cx16.r15 = ycenter - yy
|
||||||
|
plot(cx16.r14, cx16.r15, color)
|
||||||
|
cx16.r14 = xcenter - xx
|
||||||
|
plot(cx16.r14, cx16.r15, color)
|
||||||
|
cx16.r14 = xcenter + yy
|
||||||
|
cx16.r15 = ycenter + xx
|
||||||
|
plot(cx16.r14, cx16.r15, color)
|
||||||
|
cx16.r14 = xcenter - yy
|
||||||
|
plot(cx16.r14, cx16.r15, color)
|
||||||
|
cx16.r14 = xcenter + yy
|
||||||
|
cx16.r15 = ycenter - xx
|
||||||
|
plot(cx16.r14, cx16.r15, color)
|
||||||
|
cx16.r14 = xcenter - yy
|
||||||
|
plot(cx16.r14, cx16.r15, color)
|
||||||
|
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0
|
||||||
|
decisionOver2 += (yy as word)*2+1
|
||||||
|
else {
|
||||||
|
xx--
|
||||||
|
decisionOver2 += (yy as word -xx)*2+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||||
|
; Midpoint algorithm, filled
|
||||||
|
if radius==0
|
||||||
|
return
|
||||||
|
ubyte @zp yy = 0
|
||||||
|
word @zp decisionOver2 = (1 as word)-radius
|
||||||
|
|
||||||
|
while radius>=yy {
|
||||||
|
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
|
||||||
|
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
|
||||||
|
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
|
||||||
|
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0
|
||||||
|
decisionOver2 += (yy as word)*2+1
|
||||||
|
else {
|
||||||
|
radius--
|
||||||
|
decisionOver2 += (yy as word -radius)*2+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub plot(uword @zp x, uword y, ubyte color) {
|
||||||
|
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||||
|
ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100]
|
||||||
|
ubyte[4] shift4c = [6,4,2,0]
|
||||||
|
uword addr
|
||||||
|
ubyte value
|
||||||
|
|
||||||
|
when active_mode {
|
||||||
|
1 -> {
|
||||||
|
; lores monochrome
|
||||||
|
%asm {{
|
||||||
|
lda x
|
||||||
|
eor y
|
||||||
|
ora monochrome_dont_stipple_flag
|
||||||
|
and #1
|
||||||
|
}}
|
||||||
|
if_nz {
|
||||||
|
addr = x/8 + y*(320/8)
|
||||||
|
value = bits[lsb(x)&7]
|
||||||
|
if color
|
||||||
|
cx16.vpoke_or(0, addr, value)
|
||||||
|
else {
|
||||||
|
value = ~value
|
||||||
|
cx16.vpoke_and(0, addr, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
4 -> {
|
||||||
|
; lores 256c
|
||||||
|
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
cx16.vpoke(lsb(cx16.r1), cx16.r0, color)
|
||||||
|
; activate vera auto-increment mode so next_pixel() can be used after this
|
||||||
|
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | %00010000
|
||||||
|
color = cx16.VERA_DATA0
|
||||||
|
}
|
||||||
|
5 -> {
|
||||||
|
; highres monochrome
|
||||||
|
%asm {{
|
||||||
|
lda x
|
||||||
|
eor y
|
||||||
|
ora monochrome_dont_stipple_flag
|
||||||
|
and #1
|
||||||
|
}}
|
||||||
|
if_nz {
|
||||||
|
addr = x/8 + y*(640/8)
|
||||||
|
value = bits[lsb(x)&7]
|
||||||
|
if color
|
||||||
|
cx16.vpoke_or(0, addr, value)
|
||||||
|
else {
|
||||||
|
value = ~value
|
||||||
|
cx16.vpoke_and(0, addr, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
6 -> {
|
||||||
|
; highres 4c
|
||||||
|
; TODO also mostly usable for lores 4c?
|
||||||
|
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
color &= 3
|
||||||
|
color <<= shift4c[lsb(x) & 3]
|
||||||
|
; TODO optimize the vera memory manipulation in pure assembly
|
||||||
|
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
|
||||||
|
value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask4c[lsb(x) & 3] | color
|
||||||
|
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub position(uword @zp x, uword y) {
|
||||||
|
ubyte bank
|
||||||
|
when active_mode {
|
||||||
|
1 -> {
|
||||||
|
; lores monochrome
|
||||||
|
cx16.r0 = y*(320/8) + x/8
|
||||||
|
cx16.vaddr(0, cx16.r0, 0, 1)
|
||||||
|
}
|
||||||
|
; TODO modes 2,3
|
||||||
|
4 -> {
|
||||||
|
; lores 256c
|
||||||
|
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
bank = lsb(cx16.r1)
|
||||||
|
cx16.vaddr(bank, cx16.r0, 0, 1)
|
||||||
|
}
|
||||||
|
5 -> {
|
||||||
|
; highres monochrome
|
||||||
|
cx16.r0 = y*(640/8) + x/8
|
||||||
|
cx16.vaddr(0, cx16.r0, 0, 1)
|
||||||
|
}
|
||||||
|
6 -> {
|
||||||
|
; highres 4c
|
||||||
|
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
bank = lsb(cx16.r1)
|
||||||
|
cx16.vaddr(bank, cx16.r0, 0, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub next_pixel(ubyte color @A) {
|
||||||
|
; -- sets the next pixel byte to the graphics chip.
|
||||||
|
; for 8 bpp screens this will plot 1 pixel.
|
||||||
|
; for 1 bpp screens it will plot 8 pixels at once (color = bit pattern).
|
||||||
|
; for 2 bpp screens it will plot 4 pixels at once (color = bit pattern).
|
||||||
|
%asm {{
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub next_pixels(uword pixels @AY, uword amount @R0) clobbers(A, Y) {
|
||||||
|
; -- sets the next bunch of pixels from a prepared array of bytes.
|
||||||
|
; for 8 bpp screens this will plot 1 pixel per byte.
|
||||||
|
; for 1 bpp screens it will plot 8 pixels at once (colors are the bit patterns per byte).
|
||||||
|
; for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte).
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldx cx16.r0+1
|
||||||
|
beq +
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
inc P8ZP_SCRATCH_W1+1 ; next page of 256 pixels
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
|
||||||
|
+ ldx cx16.r0 ; remaining pixels
|
||||||
|
beq +
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
iny
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
+ plx
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub set_8_pixels_from_bits(ubyte bits @R0, ubyte oncolor @A, ubyte offcolor @Y) {
|
||||||
|
; this is only useful in 256 color mode where one pixel equals one byte value.
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
ldx #8
|
||||||
|
- asl cx16.r0
|
||||||
|
bcc +
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
bra ++
|
||||||
|
+ sty cx16.VERA_DATA0
|
||||||
|
+ dex
|
||||||
|
bne -
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ubyte charset_orig_bank = $0
|
||||||
|
const uword charset_orig_addr = $f800 ; in bank 0, so $0f800
|
||||||
|
const ubyte charset_bank = $1
|
||||||
|
const uword charset_addr = $f000 ; in bank 1, so $1f000
|
||||||
|
|
||||||
|
sub text_charset(ubyte charset) {
|
||||||
|
; -- make a copy of the selected character set to use with text()
|
||||||
|
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
|
||||||
|
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase.
|
||||||
|
cx16.screen_set_charset(charset, 0)
|
||||||
|
cx16.vaddr(charset_orig_bank, charset_orig_addr, 0, 1)
|
||||||
|
cx16.vaddr(charset_bank, charset_addr, 1, 1)
|
||||||
|
repeat 256*8 {
|
||||||
|
cx16.VERA_DATA1 = cx16.VERA_DATA0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub text(uword @zp x, uword y, ubyte color, uword sctextptr) {
|
||||||
|
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
|
||||||
|
; You must also have called text_charset() first to select and prepare the character set to use.
|
||||||
|
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to mulitples of 8 ! TODO allow per-pixel horizontal positioning
|
||||||
|
uword chardataptr
|
||||||
|
when active_mode {
|
||||||
|
1, 5 -> {
|
||||||
|
; monochrome mode, either resolution
|
||||||
|
cx16.r2 = 40
|
||||||
|
if active_mode>=5
|
||||||
|
cx16.r2 = 80
|
||||||
|
while @(sctextptr) {
|
||||||
|
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||||
|
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
||||||
|
position(x,y)
|
||||||
|
%asm {{
|
||||||
|
lda cx16.VERA_ADDR_H
|
||||||
|
and #%111 ; don't auto-increment, we have to do that manually because of the ora
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda color
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
ldy #8
|
||||||
|
- lda P8ZP_SCRATCH_B1
|
||||||
|
bne + ; white color, plot normally
|
||||||
|
lda cx16.VERA_DATA1
|
||||||
|
eor #255 ; black color, keep only the other pixels
|
||||||
|
and cx16.VERA_DATA0
|
||||||
|
bra ++
|
||||||
|
+ lda cx16.VERA_DATA0
|
||||||
|
ora cx16.VERA_DATA1
|
||||||
|
+ sta cx16.VERA_DATA0
|
||||||
|
lda cx16.VERA_ADDR_L
|
||||||
|
clc
|
||||||
|
adc cx16.r2
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
bcc +
|
||||||
|
inc cx16.VERA_ADDR_M
|
||||||
|
+ lda x
|
||||||
|
clc
|
||||||
|
adc #1
|
||||||
|
sta x
|
||||||
|
bcc +
|
||||||
|
inc x+1
|
||||||
|
+ dey
|
||||||
|
bne -
|
||||||
|
}}
|
||||||
|
sctextptr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
4 -> {
|
||||||
|
; lores 256c
|
||||||
|
while @(sctextptr) {
|
||||||
|
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||||
|
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
||||||
|
repeat 8 {
|
||||||
|
; TODO rewrite this inner loop fully in assembly
|
||||||
|
position(x,y)
|
||||||
|
y++
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
ldx #1
|
||||||
|
lda cx16.VERA_DATA1
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
ldy #8
|
||||||
|
- asl P8ZP_SCRATCH_B1
|
||||||
|
bcc +
|
||||||
|
stx cx16.VERA_DATA0 ; write a pixel
|
||||||
|
bra ++
|
||||||
|
+ lda cx16.VERA_DATA0 ; don't write a pixel, but do advance to the next address
|
||||||
|
+ dey
|
||||||
|
bne -
|
||||||
|
plx
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
x+=8
|
||||||
|
y-=8
|
||||||
|
sctextptr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
6 -> {
|
||||||
|
; hires 4c
|
||||||
|
while @(sctextptr) {
|
||||||
|
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||||
|
repeat 8 {
|
||||||
|
; TODO rewrite this inner loop fully in assembly
|
||||||
|
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
|
||||||
|
repeat 8 {
|
||||||
|
charbits <<= 1
|
||||||
|
if_cs
|
||||||
|
plot(x, y, color)
|
||||||
|
x++
|
||||||
|
}
|
||||||
|
x-=8
|
||||||
|
chardataptr++
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
x+=8
|
||||||
|
y-=8
|
||||||
|
sctextptr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub cs_innerloop640() clobbers(Y) {
|
||||||
|
%asm {{
|
||||||
|
ldy #80
|
||||||
|
- stz cx16.VERA_DATA0
|
||||||
|
stz cx16.VERA_DATA0
|
||||||
|
stz cx16.VERA_DATA0
|
||||||
|
stz cx16.VERA_DATA0
|
||||||
|
stz cx16.VERA_DATA0
|
||||||
|
stz cx16.VERA_DATA0
|
||||||
|
stz cx16.VERA_DATA0
|
||||||
|
stz cx16.VERA_DATA0
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub addr_mul_24_for_highres_4c(uword yy @R2, uword xx @R3) clobbers(A, Y) -> uword @R0, uword @R1 {
|
||||||
|
; yy * 160 + xx/4 (24 bits calculation)
|
||||||
|
; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
%asm {{
|
||||||
|
ldy #5
|
||||||
|
- asl cx16.r2
|
||||||
|
rol cx16.r2+1
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
lda cx16.r2
|
||||||
|
sta cx16.r0
|
||||||
|
lda cx16.r2+1
|
||||||
|
sta cx16.r0+1
|
||||||
|
asl cx16.r0
|
||||||
|
rol cx16.r0+1
|
||||||
|
asl cx16.r0
|
||||||
|
rol cx16.r0+1
|
||||||
|
|
||||||
|
; xx >>= 2 (xx=R3)
|
||||||
|
lsr cx16.r3+1
|
||||||
|
ror cx16.r3
|
||||||
|
lsr cx16.r3+1
|
||||||
|
ror cx16.r3
|
||||||
|
|
||||||
|
; add r2 and xx (r3) to r0 (24-bits)
|
||||||
|
stz cx16.r1
|
||||||
|
clc
|
||||||
|
lda cx16.r0
|
||||||
|
adc cx16.r2
|
||||||
|
sta cx16.r0
|
||||||
|
lda cx16.r0+1
|
||||||
|
adc cx16.r2+1
|
||||||
|
sta cx16.r0+1
|
||||||
|
bcc +
|
||||||
|
inc cx16.r1
|
||||||
|
+ clc
|
||||||
|
lda cx16.r0
|
||||||
|
adc cx16.r3
|
||||||
|
sta cx16.r0
|
||||||
|
lda cx16.r0+1
|
||||||
|
adc cx16.r3+1
|
||||||
|
sta cx16.r0+1
|
||||||
|
bcc +
|
||||||
|
inc cx16.r1
|
||||||
|
+
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub addr_mul_24_for_lores_256c(uword yy @R0, uword xx @AY) clobbers(A) -> uword @R0, ubyte @R1 {
|
||||||
|
; yy * 320 + xx (24 bits calculation)
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda cx16.r0
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta cx16.r1
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
lda cx16.r0
|
||||||
|
asl a
|
||||||
|
rol P8ZP_SCRATCH_REG
|
||||||
|
asl a
|
||||||
|
rol P8ZP_SCRATCH_REG
|
||||||
|
asl a
|
||||||
|
rol P8ZP_SCRATCH_REG
|
||||||
|
asl a
|
||||||
|
rol P8ZP_SCRATCH_REG
|
||||||
|
asl a
|
||||||
|
rol P8ZP_SCRATCH_REG
|
||||||
|
asl a
|
||||||
|
rol P8ZP_SCRATCH_REG
|
||||||
|
sta cx16.r0
|
||||||
|
lda P8ZP_SCRATCH_B1
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_REG
|
||||||
|
sta cx16.r0+1
|
||||||
|
bcc +
|
||||||
|
inc cx16.r1
|
||||||
|
+ ; now add the value to this 24-bits number
|
||||||
|
lda cx16.r0
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_W1
|
||||||
|
sta cx16.r0
|
||||||
|
lda cx16.r0+1
|
||||||
|
adc P8ZP_SCRATCH_W1+1
|
||||||
|
sta cx16.r0+1
|
||||||
|
bcc +
|
||||||
|
inc cx16.r1
|
||||||
|
+ lda cx16.r1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
138
compiler/res/prog8lib/cx16/graphics.p8
Normal file
138
compiler/res/prog8lib/cx16/graphics.p8
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
%target cx16
|
||||||
|
%import syslib
|
||||||
|
%import textio
|
||||||
|
|
||||||
|
; Bitmap pixel graphics module for the CommanderX16
|
||||||
|
; wraps the graphics functions that are in ROM.
|
||||||
|
; only black/white monochrome 320x200 for now. (i.e. truncated at the bottom)
|
||||||
|
; For full-screen 640x480 or 320x240 graphics, use the "gfx2" module instead. (but that is Cx16-specific)
|
||||||
|
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
|
||||||
|
|
||||||
|
|
||||||
|
graphics {
|
||||||
|
const uword WIDTH = 320
|
||||||
|
const ubyte HEIGHT = 200
|
||||||
|
|
||||||
|
sub enable_bitmap_mode() {
|
||||||
|
; enable bitmap screen, erase it and set colors to black/white.
|
||||||
|
void cx16.screen_set_mode($80)
|
||||||
|
cx16.GRAPH_init(0)
|
||||||
|
clear_screen(1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub disable_bitmap_mode() {
|
||||||
|
; enables text mode, erase the text screen, color white
|
||||||
|
void cx16.screen_set_mode(2)
|
||||||
|
txt.fill_screen(' ', 1) ; doesn't seem to fully clear the text screen after returning from gfx mode
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||||
|
cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor)
|
||||||
|
cx16.GRAPH_clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||||
|
cx16.GRAPH_draw_line(x1, y1, x2, y2)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fillrect(uword x, uword y, uword width, uword height) {
|
||||||
|
cx16.GRAPH_draw_rect(x, y, width, height, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rect(uword x, uword y, uword width, uword height) {
|
||||||
|
cx16.GRAPH_draw_rect(x, y, width, height, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub horizontal_line(uword x, uword y, uword length) {
|
||||||
|
if length
|
||||||
|
cx16.GRAPH_draw_line(x, y, x+length-1, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub vertical_line(uword x, uword y, uword height) {
|
||||||
|
if height
|
||||||
|
cx16.GRAPH_draw_line(x, y, x, y+height-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||||
|
;cx16.r0 = xcenter - radius/2
|
||||||
|
;cx16.r1 = ycenter - radius/2
|
||||||
|
;cx16.r2 = radius*2
|
||||||
|
;cx16.r3 = radius*2
|
||||||
|
;cx16.GRAPH_draw_oval(false) ; currently this call is not implemented on cx16, does a BRK
|
||||||
|
|
||||||
|
; Midpoint algorithm
|
||||||
|
if radius==0
|
||||||
|
return
|
||||||
|
ubyte @zp xx = radius
|
||||||
|
ubyte @zp yy = 0
|
||||||
|
word @zp decisionOver2 = (1 as word)-xx
|
||||||
|
|
||||||
|
while xx>=yy {
|
||||||
|
cx16.r0 = xcenter + xx
|
||||||
|
cx16.r1 = ycenter + yy
|
||||||
|
cx16.FB_cursor_position2()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter - xx
|
||||||
|
cx16.FB_cursor_position2()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter + xx
|
||||||
|
cx16.r1 = ycenter - yy
|
||||||
|
cx16.FB_cursor_position2()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter - xx
|
||||||
|
cx16.FB_cursor_position2()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter + yy
|
||||||
|
cx16.r1 = ycenter + xx
|
||||||
|
cx16.FB_cursor_position2()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter - yy
|
||||||
|
cx16.FB_cursor_position2()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter + yy
|
||||||
|
cx16.r1 = ycenter - xx
|
||||||
|
cx16.FB_cursor_position2()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter - yy
|
||||||
|
cx16.FB_cursor_position2()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0 {
|
||||||
|
decisionOver2 += (yy as word)*2+1
|
||||||
|
} else {
|
||||||
|
xx--
|
||||||
|
decisionOver2 += (yy as word -xx)*2+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||||
|
if radius==0
|
||||||
|
return
|
||||||
|
ubyte @zp yy = 0
|
||||||
|
word decisionOver2 = (1 as word)-radius
|
||||||
|
|
||||||
|
while radius>=yy {
|
||||||
|
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
|
||||||
|
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
|
||||||
|
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
|
||||||
|
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0
|
||||||
|
decisionOver2 += (yy as word)*2+1
|
||||||
|
else {
|
||||||
|
radius--
|
||||||
|
decisionOver2 += (yy as word -radius)*2+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
|
||||||
|
%asm {{
|
||||||
|
jsr cx16.FB_cursor_position
|
||||||
|
lda #1
|
||||||
|
jsr cx16.FB_set_pixel
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
183
compiler/res/prog8lib/cx16/palette.p8
Normal file
183
compiler/res/prog8lib/cx16/palette.p8
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
%target cx16
|
||||||
|
|
||||||
|
; Manipulate the Commander X16's display color palette.
|
||||||
|
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
|
||||||
|
|
||||||
|
palette {
|
||||||
|
|
||||||
|
uword vera_palette_ptr
|
||||||
|
ubyte c
|
||||||
|
|
||||||
|
sub set_color(ubyte index, uword color) {
|
||||||
|
vera_palette_ptr = $fa00+index*2
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, lsb(color))
|
||||||
|
vera_palette_ptr++
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, msb(color))
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_rgb4(uword palette_bytes_ptr, uword num_colors) {
|
||||||
|
; 2 bytes per color entry, the Vera uses this, but the R/GB bytes order is swapped
|
||||||
|
vera_palette_ptr = $fa00
|
||||||
|
repeat num_colors {
|
||||||
|
cx16.vpoke(1, vera_palette_ptr+1, @(palette_bytes_ptr))
|
||||||
|
palette_bytes_ptr++
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, @(palette_bytes_ptr))
|
||||||
|
palette_bytes_ptr++
|
||||||
|
vera_palette_ptr+=2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_rgb(uword palette_words_ptr, uword num_colors) {
|
||||||
|
; 1 word per color entry (in little endian format so $gb0r)
|
||||||
|
vera_palette_ptr = $fa00
|
||||||
|
repeat num_colors*2 {
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))
|
||||||
|
palette_words_ptr++
|
||||||
|
vera_palette_ptr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_rgb8(uword palette_bytes_ptr, uword num_colors) {
|
||||||
|
; 3 bytes per color entry, adjust color depth from 8 to 4 bits per channel.
|
||||||
|
vera_palette_ptr = $fa00
|
||||||
|
ubyte red
|
||||||
|
ubyte greenblue
|
||||||
|
repeat num_colors {
|
||||||
|
red = @(palette_bytes_ptr) >> 4
|
||||||
|
palette_bytes_ptr++
|
||||||
|
greenblue = @(palette_bytes_ptr) & %11110000
|
||||||
|
palette_bytes_ptr++
|
||||||
|
greenblue |= @(palette_bytes_ptr) >> 4 ; add Blue
|
||||||
|
palette_bytes_ptr++
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, greenblue)
|
||||||
|
vera_palette_ptr++
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, red)
|
||||||
|
vera_palette_ptr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_monochrome(uword screencolorRGB, uword drawcolorRGB) {
|
||||||
|
vera_palette_ptr = $fa00
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, lsb(screencolorRGB)) ; G,B
|
||||||
|
vera_palette_ptr++
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, msb(screencolorRGB)) ; R
|
||||||
|
vera_palette_ptr++
|
||||||
|
repeat 255 {
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, lsb(drawcolorRGB)) ; G,B
|
||||||
|
vera_palette_ptr++
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, msb(drawcolorRGB)) ; R
|
||||||
|
vera_palette_ptr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_grayscale() {
|
||||||
|
vera_palette_ptr = $fa00
|
||||||
|
repeat 16 {
|
||||||
|
c=0
|
||||||
|
repeat 16 {
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, c)
|
||||||
|
vera_palette_ptr++
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, c)
|
||||||
|
vera_palette_ptr++
|
||||||
|
c += $11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uword[] C64_colorpalette_dark = [ ; this is a darker palette with more contrast
|
||||||
|
$000, ; 0 = black
|
||||||
|
$FFF, ; 1 = white
|
||||||
|
$632, ; 2 = red
|
||||||
|
$7AB, ; 3 = cyan
|
||||||
|
$638, ; 4 = purple
|
||||||
|
$584, ; 5 = green
|
||||||
|
$327, ; 6 = blue
|
||||||
|
$BC6, ; 7 = yellow
|
||||||
|
$642, ; 8 = orange
|
||||||
|
$430, ; 9 = brown
|
||||||
|
$965, ; 10 = light red
|
||||||
|
$444, ; 11 = dark grey
|
||||||
|
$666, ; 12 = medium grey
|
||||||
|
$9D8, ; 13 = light green
|
||||||
|
$65B, ; 14 = light blue
|
||||||
|
$999 ; 15 = light grey
|
||||||
|
]
|
||||||
|
|
||||||
|
uword[] C64_colorpalette_pepto = [ ; # this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
||||||
|
$000, ; 0 = black
|
||||||
|
$FFF, ; 1 = white
|
||||||
|
$833, ; 2 = red
|
||||||
|
$7cc, ; 3 = cyan
|
||||||
|
$839, ; 4 = purple
|
||||||
|
$5a4, ; 5 = green
|
||||||
|
$229, ; 6 = blue
|
||||||
|
$ef7, ; 7 = yellow
|
||||||
|
$852, ; 8 = orange
|
||||||
|
$530, ; 9 = brown
|
||||||
|
$c67, ; 10 = light red
|
||||||
|
$444, ; 11 = dark grey
|
||||||
|
$777, ; 12 = medium grey
|
||||||
|
$af9, ; 13 = light green
|
||||||
|
$76e, ; 14 = light blue
|
||||||
|
$bbb ; 15 = light grey
|
||||||
|
]
|
||||||
|
|
||||||
|
uword[] C64_colorpalette_light = [ ; this is a lighter palette
|
||||||
|
$000, ; 0 = black
|
||||||
|
$FFF, ; 1 = white
|
||||||
|
$944, ; 2 = red
|
||||||
|
$7CC, ; 3 = cyan
|
||||||
|
$95A, ; 4 = purple
|
||||||
|
$6A5, ; 5 = green
|
||||||
|
$549, ; 6 = blue
|
||||||
|
$CD8, ; 7 = yellow
|
||||||
|
$963, ; 8 = orange
|
||||||
|
$650, ; 9 = brown
|
||||||
|
$C77, ; 10 = light red
|
||||||
|
$666, ; 11 = dark grey
|
||||||
|
$888, ; 12 = medium grey
|
||||||
|
$AE9, ; 13 = light green
|
||||||
|
$87C, ; 14 = light blue
|
||||||
|
$AAA ; 15 = light grey
|
||||||
|
]
|
||||||
|
|
||||||
|
sub set_c64pepto() {
|
||||||
|
vera_palette_ptr = $fa00
|
||||||
|
repeat 16 {
|
||||||
|
for c in 0 to 15 {
|
||||||
|
uword cc = C64_colorpalette_pepto[c]
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
|
||||||
|
vera_palette_ptr++
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
|
||||||
|
vera_palette_ptr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_c64light() {
|
||||||
|
vera_palette_ptr = $fa00
|
||||||
|
repeat 16 {
|
||||||
|
for c in 0 to 15 {
|
||||||
|
uword cc = C64_colorpalette_light[c]
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
|
||||||
|
vera_palette_ptr++
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
|
||||||
|
vera_palette_ptr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_c64dark() {
|
||||||
|
vera_palette_ptr = $fa00
|
||||||
|
repeat 16 {
|
||||||
|
for c in 0 to 15 {
|
||||||
|
uword cc = C64_colorpalette_dark[c]
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
|
||||||
|
vera_palette_ptr++
|
||||||
|
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
|
||||||
|
vera_palette_ptr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
782
compiler/res/prog8lib/cx16/syslib.p8
Normal file
782
compiler/res/prog8lib/cx16/syslib.p8
Normal file
@ -0,0 +1,782 @@
|
|||||||
|
; Prog8 definitions for the CommanderX16
|
||||||
|
; Including memory registers, I/O registers, Basic and Kernal subroutines.
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target cx16
|
||||||
|
|
||||||
|
|
||||||
|
c64 {
|
||||||
|
|
||||||
|
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||||
|
|
||||||
|
; STROUT --> use txt.print
|
||||||
|
; CLEARSCR -> use txt.clear_screen
|
||||||
|
; HOMECRSR -> use txt.plot
|
||||||
|
|
||||||
|
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||||
|
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||||
|
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||||
|
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||||
|
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||||
|
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||||
|
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||||
|
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||||
|
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See MEMTOP2
|
||||||
|
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||||
|
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||||
|
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||||
|
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||||
|
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||||
|
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||||
|
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||||
|
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||||
|
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||||
|
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||||
|
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||||
|
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||||
|
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||||
|
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||||
|
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||||
|
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||||
|
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||||
|
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||||
|
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||||
|
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
||||||
|
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||||
|
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||||
|
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||||
|
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||||
|
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||||
|
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||||
|
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||||
|
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||||
|
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||||
|
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||||
|
|
||||||
|
; ---- utility
|
||||||
|
|
||||||
|
asmsub STOP2() -> ubyte @A {
|
||||||
|
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr c64.STOP
|
||||||
|
beq +
|
||||||
|
plx
|
||||||
|
lda #0
|
||||||
|
rts
|
||||||
|
+ plx
|
||||||
|
lda #1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub RDTIM16() -> uword @AY {
|
||||||
|
; -- like RDTIM() but only returning the lower 16 bits for convenience
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr c64.RDTIM
|
||||||
|
pha
|
||||||
|
txa
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub MEMTOP2() -> ubyte @A {
|
||||||
|
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks.
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
sec
|
||||||
|
jsr c64.MEMTOP
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cx16 {
|
||||||
|
|
||||||
|
; irq and hardware vectors:
|
||||||
|
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||||
|
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
|
||||||
|
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
|
||||||
|
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
|
||||||
|
|
||||||
|
|
||||||
|
; the sixteen virtual 16-bit registers
|
||||||
|
&uword r0 = $0002
|
||||||
|
&uword r1 = $0004
|
||||||
|
&uword r2 = $0006
|
||||||
|
&uword r3 = $0008
|
||||||
|
&uword r4 = $000a
|
||||||
|
&uword r5 = $000c
|
||||||
|
&uword r6 = $000e
|
||||||
|
&uword r7 = $0010
|
||||||
|
&uword r8 = $0012
|
||||||
|
&uword r9 = $0014
|
||||||
|
&uword r10 = $0016
|
||||||
|
&uword r11 = $0018
|
||||||
|
&uword r12 = $001a
|
||||||
|
&uword r13 = $001c
|
||||||
|
&uword r14 = $001e
|
||||||
|
&uword r15 = $0020
|
||||||
|
|
||||||
|
; VERA registers
|
||||||
|
|
||||||
|
const uword VERA_BASE = $9F20
|
||||||
|
&ubyte VERA_ADDR_L = VERA_BASE + $0000
|
||||||
|
&ubyte VERA_ADDR_M = VERA_BASE + $0001
|
||||||
|
&ubyte VERA_ADDR_H = VERA_BASE + $0002
|
||||||
|
&ubyte VERA_DATA0 = VERA_BASE + $0003
|
||||||
|
&ubyte VERA_DATA1 = VERA_BASE + $0004
|
||||||
|
&ubyte VERA_CTRL = VERA_BASE + $0005
|
||||||
|
&ubyte VERA_IEN = VERA_BASE + $0006
|
||||||
|
&ubyte VERA_ISR = VERA_BASE + $0007
|
||||||
|
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
|
||||||
|
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009
|
||||||
|
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A
|
||||||
|
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B
|
||||||
|
&ubyte VERA_DC_BORDER = VERA_BASE + $000C
|
||||||
|
&ubyte VERA_DC_HSTART = VERA_BASE + $0009
|
||||||
|
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A
|
||||||
|
&ubyte VERA_DC_VSTART = VERA_BASE + $000B
|
||||||
|
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C
|
||||||
|
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
|
||||||
|
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
|
||||||
|
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
|
||||||
|
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010
|
||||||
|
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011
|
||||||
|
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012
|
||||||
|
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013
|
||||||
|
&ubyte VERA_L1_CONFIG = VERA_BASE + $0014
|
||||||
|
&ubyte VERA_L1_MAPBASE = VERA_BASE + $0015
|
||||||
|
&ubyte VERA_L1_TILEBASE = VERA_BASE + $0016
|
||||||
|
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017
|
||||||
|
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018
|
||||||
|
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019
|
||||||
|
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A
|
||||||
|
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B
|
||||||
|
&ubyte VERA_AUDIO_RATE = VERA_BASE + $001C
|
||||||
|
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
|
||||||
|
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
|
||||||
|
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
|
||||||
|
; VERA_PSG_BASE = $1F9C0
|
||||||
|
; VERA_PALETTE_BASE = $1FA00
|
||||||
|
; VERA_SPRITES_BASE = $1FC00
|
||||||
|
|
||||||
|
; I/O
|
||||||
|
|
||||||
|
const uword via1 = $9f60 ;VIA 6522 #1
|
||||||
|
&ubyte d1prb = via1+0
|
||||||
|
&ubyte d1pra = via1+1
|
||||||
|
&ubyte d1ddrb = via1+2
|
||||||
|
&ubyte d1ddra = via1+3
|
||||||
|
&ubyte d1t1l = via1+4
|
||||||
|
&ubyte d1t1h = via1+5
|
||||||
|
&ubyte d1t1ll = via1+6
|
||||||
|
&ubyte d1t1lh = via1+7
|
||||||
|
&ubyte d1t2l = via1+8
|
||||||
|
&ubyte d1t2h = via1+9
|
||||||
|
&ubyte d1sr = via1+10
|
||||||
|
&ubyte d1acr = via1+11
|
||||||
|
&ubyte d1pcr = via1+12
|
||||||
|
&ubyte d1ifr = via1+13
|
||||||
|
&ubyte d1ier = via1+14
|
||||||
|
&ubyte d1ora = via1+15
|
||||||
|
|
||||||
|
const uword via2 = $9f70 ;VIA 6522 #2
|
||||||
|
&ubyte d2prb = via2+0
|
||||||
|
&ubyte d2pra = via2+1
|
||||||
|
&ubyte d2ddrb = via2+2
|
||||||
|
&ubyte d2ddra = via2+3
|
||||||
|
&ubyte d2t1l = via2+4
|
||||||
|
&ubyte d2t1h = via2+5
|
||||||
|
&ubyte d2t1ll = via2+6
|
||||||
|
&ubyte d2t1lh = via2+7
|
||||||
|
&ubyte d2t2l = via2+8
|
||||||
|
&ubyte d2t2h = via2+9
|
||||||
|
&ubyte d2sr = via2+10
|
||||||
|
&ubyte d2acr = via2+11
|
||||||
|
&ubyte d2pcr = via2+12
|
||||||
|
&ubyte d2ifr = via2+13
|
||||||
|
&ubyte d2ier = via2+14
|
||||||
|
&ubyte d2ora = via2+15
|
||||||
|
|
||||||
|
|
||||||
|
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
||||||
|
; spelling of the names is taken from the Commander X-16 rom sources
|
||||||
|
|
||||||
|
; supported C128 additions
|
||||||
|
romsub $ff4a = close_all(ubyte device @A) clobbers(A,X,Y)
|
||||||
|
romsub $ff59 = lkupla(ubyte la @A) clobbers(A,X,Y)
|
||||||
|
romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
|
||||||
|
romsub $ff5f = screen_set_mode(ubyte mode @A) clobbers(A, X, Y) -> ubyte @Pc
|
||||||
|
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
|
||||||
|
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
|
||||||
|
romsub $ff6e = jsrfar()
|
||||||
|
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||||
|
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||||
|
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||||
|
romsub $ff7d = primm()
|
||||||
|
|
||||||
|
; X16 additions
|
||||||
|
romsub $ff44 = macptr() clobbers(A,X,Y)
|
||||||
|
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
|
||||||
|
romsub $ff68 = mouse_config(ubyte shape @A, ubyte scale @X) clobbers (A, X, Y)
|
||||||
|
romsub $ff6b = mouse_get(ubyte zpdataptr @X) clobbers(A)
|
||||||
|
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||||
|
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||||
|
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||||
|
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||||
|
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
||||||
|
|
||||||
|
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
|
||||||
|
|
||||||
|
; high level graphics & fonts
|
||||||
|
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
||||||
|
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
|
||||||
|
romsub $ff26 = GRAPH_set_window(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
|
||||||
|
romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y)
|
||||||
|
romsub $ff2c = GRAPH_draw_line(uword x1 @R0, uword y1 @R1, uword x2 @R2, uword y2 @R3) clobbers(A,X,Y)
|
||||||
|
romsub $ff2f = GRAPH_draw_rect(uword x @R0, uword y @R1, uword width @R2, uword height @R3, uword cornerradius @R4, ubyte fill @Pc) clobbers(A,X,Y)
|
||||||
|
romsub $ff32 = GRAPH_move_rect(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword width @R4, uword height @R5) clobbers(A,X,Y)
|
||||||
|
romsub $ff35 = GRAPH_draw_oval(uword x @R0, uword y @R1, uword width @R2, uword height @R3, ubyte fill @Pc) clobbers(A,X,Y)
|
||||||
|
romsub $ff38 = GRAPH_draw_image(uword x @R0, uword y @R1, uword ptr @R2, uword width @R3, uword height @R4) clobbers(A,X,Y)
|
||||||
|
romsub $ff3b = GRAPH_set_font(uword fontptr @R0) clobbers(A,X,Y)
|
||||||
|
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc) clobbers(A,X,Y)
|
||||||
|
romsub $ff41 = GRAPH_put_char(uword x @R0, uword y @R1, ubyte char @A) clobbers(A,X,Y)
|
||||||
|
romsub $ff41 = GRAPH_put_next_char(ubyte char @A) clobbers(A,X,Y) ; alias for the routine above that doesn't reset the position of the initial character
|
||||||
|
|
||||||
|
; framebuffer
|
||||||
|
romsub $fef6 = FB_init() clobbers(A,X,Y)
|
||||||
|
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
|
||||||
|
romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y)
|
||||||
|
romsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
|
||||||
|
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the previous routine, but avoiding having to respecify both x and y every time
|
||||||
|
romsub $ff02 = FB_cursor_next_line(uword x @R0) clobbers(A,X,Y)
|
||||||
|
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
|
||||||
|
romsub $ff08 = FB_get_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
|
||||||
|
romsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y)
|
||||||
|
romsub $ff0e = FB_set_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
|
||||||
|
romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y)
|
||||||
|
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @R0, ubyte mask @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y)
|
||||||
|
romsub $ff17 = FB_fill_pixels(uword count @R0, uword pstep @R1, ubyte color @A) clobbers(A,X,Y)
|
||||||
|
romsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1) clobbers(A,X,Y)
|
||||||
|
romsub $ff1d = FB_move_pixels(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword count @R4) clobbers(A,X,Y)
|
||||||
|
|
||||||
|
; misc
|
||||||
|
romsub $fef0 = sprite_set_image(uword pixels @R0, uword mask @R1, ubyte bpp @R2, ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc
|
||||||
|
romsub $fef3 = sprite_set_position(uword x @R0, uword y @R1, ubyte number @A) clobbers(A,X,Y)
|
||||||
|
romsub $fee4 = memory_fill(uword address @R0, uword num_bytes @R1, ubyte value @A) clobbers(A,X,Y)
|
||||||
|
romsub $fee7 = memory_copy(uword source @R0, uword target @R1, uword num_bytes @R2) clobbers(A,X,Y)
|
||||||
|
romsub $feea = memory_crc(uword address @R0, uword num_bytes @R1) clobbers(A,X,Y) -> uword @R2
|
||||||
|
romsub $feed = memory_decompress(uword input @R0, uword output @R1) clobbers(A,X,Y) -> uword @R1 ; last address +1 is result in R1
|
||||||
|
romsub $fedb = console_init(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
|
||||||
|
romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc) clobbers(A,X,Y)
|
||||||
|
romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
|
||||||
|
romsub $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2) clobbers(A,X,Y)
|
||||||
|
romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
|
||||||
|
romsub $fed2 = kbdbuf_put(ubyte key @A) clobbers(A,X,Y)
|
||||||
|
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||||
|
romsub $fecc = monitor() clobbers(A,X,Y)
|
||||||
|
|
||||||
|
; ---- end of kernal routines ----
|
||||||
|
|
||||||
|
|
||||||
|
; ---- utilities -----
|
||||||
|
|
||||||
|
inline asmsub rombank(ubyte rombank @A) {
|
||||||
|
; -- set the rom banks
|
||||||
|
%asm {{
|
||||||
|
sta $01 ; rom bank register (new)
|
||||||
|
sta cx16.d1prb ; rom bank register (old)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub rambank(ubyte rambank @A) {
|
||||||
|
; -- set the ram bank
|
||||||
|
%asm {{
|
||||||
|
sta $00 ; ram bank register (new)
|
||||||
|
sta cx16.d1pra ; ram bank register (old)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
|
||||||
|
; -- get a byte from VERA's video memory
|
||||||
|
; note: inefficient when reading multiple sequential bytes!
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL
|
||||||
|
pla
|
||||||
|
and #1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
stx cx16.VERA_ADDR_L
|
||||||
|
lda cx16.VERA_DATA1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrOrDecrByOne @Y) clobbers(A) {
|
||||||
|
; -- setup the VERA's data address register 0 or 1
|
||||||
|
%asm {{
|
||||||
|
and #1
|
||||||
|
pha
|
||||||
|
lda cx16.r1
|
||||||
|
and #1
|
||||||
|
sta cx16.VERA_CTRL
|
||||||
|
lda cx16.r0
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
pla
|
||||||
|
cpy #0
|
||||||
|
bmi ++
|
||||||
|
beq +
|
||||||
|
ora #%00010000
|
||||||
|
+ sta cx16.VERA_ADDR_H
|
||||||
|
rts
|
||||||
|
+ ora #%00011000
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
|
||||||
|
; -- write a single byte to VERA's video memory
|
||||||
|
; note: inefficient when writing multiple sequential bytes!
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
and #1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda cx16.r0
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
sty cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub vpoke_or(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
|
||||||
|
; -- or a single byte to the value already in the VERA's video memory at that location
|
||||||
|
; note: inefficient when writing multiple sequential bytes!
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
and #1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda cx16.r0
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
tya
|
||||||
|
ora cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub vpoke_and(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
|
||||||
|
; -- and a single byte to the value already in the VERA's video memory at that location
|
||||||
|
; note: inefficient when writing multiple sequential bytes!
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
and #1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda cx16.r0
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
tya
|
||||||
|
and cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
|
||||||
|
; -- xor a single byte to the value already in the VERA's video memory at that location
|
||||||
|
; note: inefficient when writing multiple sequential bytes!
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
and #1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda cx16.r0
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
tya
|
||||||
|
eor cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub FB_set_pixels_from_buf(uword buffer, uword count) {
|
||||||
|
%asm {{
|
||||||
|
; -- This is replacement code for the normal FB_set_pixels subroutine in ROM
|
||||||
|
; However that routine contains a bug in the current v38 ROM that makes it crash when count > 255.
|
||||||
|
; So the code below replaces that. Once the ROM is patched this routine is no longer necessary.
|
||||||
|
; See https://github.com/commanderx16/x16-rom/issues/179
|
||||||
|
phx
|
||||||
|
lda buffer
|
||||||
|
ldy buffer+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
jsr _pixels
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
|
||||||
|
_pixels lda count+1
|
||||||
|
beq +
|
||||||
|
ldx #0
|
||||||
|
- jsr _loop
|
||||||
|
inc P8ZP_SCRATCH_W1+1
|
||||||
|
dec count+1
|
||||||
|
bne -
|
||||||
|
|
||||||
|
+ ldx count
|
||||||
|
_loop ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
iny
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
; ---- system stuff -----
|
||||||
|
asmsub init_system() {
|
||||||
|
; Initializes the machine to a sane starting state.
|
||||||
|
; Called automatically by the loader program logic.
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
cld
|
||||||
|
;stz $00
|
||||||
|
;stz $01
|
||||||
|
;stz d1prb ; select rom bank 0 (enable kernal)
|
||||||
|
lda #$80
|
||||||
|
sta VERA_CTRL
|
||||||
|
jsr c64.IOINIT
|
||||||
|
jsr c64.RESTOR
|
||||||
|
jsr c64.CINT
|
||||||
|
lda #$90 ; black
|
||||||
|
jsr c64.CHROUT
|
||||||
|
lda #1 ; swap fg/bg
|
||||||
|
jsr c64.CHROUT
|
||||||
|
lda #$9e ; yellow
|
||||||
|
jsr c64.CHROUT
|
||||||
|
lda #147 ; clear screen
|
||||||
|
jsr c64.CHROUT
|
||||||
|
lda #0
|
||||||
|
tax
|
||||||
|
tay
|
||||||
|
clc
|
||||||
|
clv
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub init_system_phase2() {
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
lda cx16.CINV
|
||||||
|
sta restore_irq._orig_irqvec
|
||||||
|
lda cx16.CINV+1
|
||||||
|
sta restore_irq._orig_irqvec+1
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
||||||
|
%asm {{
|
||||||
|
sta _modified+1
|
||||||
|
sty _modified+2
|
||||||
|
lda #0
|
||||||
|
adc #0
|
||||||
|
sta _use_kernal
|
||||||
|
sei
|
||||||
|
lda #<_irq_handler
|
||||||
|
sta cx16.CINV
|
||||||
|
lda #>_irq_handler
|
||||||
|
sta cx16.CINV+1
|
||||||
|
lda cx16.VERA_IEN
|
||||||
|
ora #%00000001 ; enable the vsync irq
|
||||||
|
sta cx16.VERA_IEN
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
|
||||||
|
_irq_handler jsr _irq_handler_init
|
||||||
|
_modified jsr $ffff ; modified
|
||||||
|
jsr _irq_handler_end
|
||||||
|
lda _use_kernal
|
||||||
|
bne +
|
||||||
|
; end irq processing - don't use kernal's irq handling
|
||||||
|
lda cx16.VERA_ISR
|
||||||
|
ora #1
|
||||||
|
sta cx16.VERA_ISR ; clear Vera Vsync irq status
|
||||||
|
ply
|
||||||
|
plx
|
||||||
|
pla
|
||||||
|
rti
|
||||||
|
+ jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
|
||||||
|
|
||||||
|
_use_kernal .byte 0
|
||||||
|
|
||||||
|
_irq_handler_init
|
||||||
|
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
|
||||||
|
stx IRQ_X_REG
|
||||||
|
lda P8ZP_SCRATCH_B1
|
||||||
|
sta IRQ_SCRATCH_ZPB1
|
||||||
|
lda P8ZP_SCRATCH_REG
|
||||||
|
sta IRQ_SCRATCH_ZPREG
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
sta IRQ_SCRATCH_ZPWORD1
|
||||||
|
lda P8ZP_SCRATCH_W1+1
|
||||||
|
sta IRQ_SCRATCH_ZPWORD1+1
|
||||||
|
lda P8ZP_SCRATCH_W2
|
||||||
|
sta IRQ_SCRATCH_ZPWORD2
|
||||||
|
lda P8ZP_SCRATCH_W2+1
|
||||||
|
sta IRQ_SCRATCH_ZPWORD2+1
|
||||||
|
; stack protector; make sure we don't clobber the top of the evaluation stack
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
cld
|
||||||
|
rts
|
||||||
|
|
||||||
|
_irq_handler_end
|
||||||
|
; restore all zp scratch registers and the X register
|
||||||
|
lda IRQ_SCRATCH_ZPB1
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
lda IRQ_SCRATCH_ZPREG
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
lda IRQ_SCRATCH_ZPWORD1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda IRQ_SCRATCH_ZPWORD1+1
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
lda IRQ_SCRATCH_ZPWORD2
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
lda IRQ_SCRATCH_ZPWORD2+1
|
||||||
|
sta P8ZP_SCRATCH_W2+1
|
||||||
|
ldx IRQ_X_REG
|
||||||
|
rts
|
||||||
|
|
||||||
|
IRQ_X_REG .byte 0
|
||||||
|
IRQ_SCRATCH_ZPB1 .byte 0
|
||||||
|
IRQ_SCRATCH_ZPREG .byte 0
|
||||||
|
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||||
|
IRQ_SCRATCH_ZPWORD2 .word 0
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
asmsub restore_irq() clobbers(A) {
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
lda _orig_irqvec
|
||||||
|
sta cx16.CINV
|
||||||
|
lda _orig_irqvec+1
|
||||||
|
sta cx16.CINV+1
|
||||||
|
lda cx16.VERA_IEN
|
||||||
|
and #%11110000 ; disable all Vera IRQs
|
||||||
|
ora #%00000001 ; enable only the vsync Irq
|
||||||
|
sta cx16.VERA_IEN
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
_orig_irqvec .word 0
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||||
|
%asm {{
|
||||||
|
sta _modified+1
|
||||||
|
sty _modified+2
|
||||||
|
lda cx16.r0
|
||||||
|
ldy cx16.r0+1
|
||||||
|
sei
|
||||||
|
lda cx16.VERA_IEN
|
||||||
|
and #%11110000 ; clear other IRQs
|
||||||
|
ora #%00000010 ; enable the line (raster) irq
|
||||||
|
sta cx16.VERA_IEN
|
||||||
|
lda cx16.r0
|
||||||
|
ldy cx16.r0+1
|
||||||
|
jsr set_rasterline
|
||||||
|
lda #<_raster_irq_handler
|
||||||
|
sta cx16.CINV
|
||||||
|
lda #>_raster_irq_handler
|
||||||
|
sta cx16.CINV+1
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
|
||||||
|
_raster_irq_handler
|
||||||
|
jsr set_irq._irq_handler_init
|
||||||
|
_modified jsr $ffff ; modified
|
||||||
|
jsr set_irq._irq_handler_end
|
||||||
|
; end irq processing - don't use kernal's irq handling
|
||||||
|
lda cx16.VERA_ISR
|
||||||
|
ora #%00000010
|
||||||
|
sta cx16.VERA_ISR ; clear Vera line irq status
|
||||||
|
ply
|
||||||
|
plx
|
||||||
|
pla
|
||||||
|
rti
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub set_rasterline(uword line @AY) {
|
||||||
|
%asm {{
|
||||||
|
sta cx16.VERA_IRQ_LINE_L
|
||||||
|
lda cx16.VERA_IEN
|
||||||
|
and #%01111111
|
||||||
|
sta cx16.VERA_IEN
|
||||||
|
tya
|
||||||
|
lsr a
|
||||||
|
ror a
|
||||||
|
and #%10000000
|
||||||
|
ora cx16.VERA_IEN
|
||||||
|
sta cx16.VERA_IEN
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sys {
|
||||||
|
; ------- lowlevel system routines --------
|
||||||
|
|
||||||
|
const ubyte target = 16 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
|
||||||
|
|
||||||
|
|
||||||
|
asmsub reset_system() {
|
||||||
|
; Soft-reset the system back to Basic prompt.
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
stz $01 ; bank the kernal in (new rom bank register)
|
||||||
|
stz cx16.d1prb ; bank the kernal in (old rom bank register)
|
||||||
|
jmp (cx16.RESET_VEC)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub wait(uword jiffies) {
|
||||||
|
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||||
|
repeat jiffies {
|
||||||
|
ubyte jiff = lsb(c64.RDTIM16())
|
||||||
|
while jiff==lsb(c64.RDTIM16()) {
|
||||||
|
; wait until 1 jiffy has passed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||||
|
%asm {{
|
||||||
|
sta cx16.r2
|
||||||
|
sty cx16.r2+1
|
||||||
|
jsr cx16.memory_copy
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
|
||||||
|
%asm {{
|
||||||
|
jsr cx16.memory_fill
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers (A,X,Y) {
|
||||||
|
%asm {{
|
||||||
|
ldx cx16.r0
|
||||||
|
stx P8ZP_SCRATCH_W1
|
||||||
|
ldx cx16.r0+1
|
||||||
|
stx P8ZP_SCRATCH_W1+1
|
||||||
|
ldx cx16.r1
|
||||||
|
stx P8ZP_SCRATCH_W2
|
||||||
|
ldx cx16.r1+1
|
||||||
|
stx P8ZP_SCRATCH_W2+1
|
||||||
|
jmp prog8_lib.memsetw
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub rsave() {
|
||||||
|
; save cpu status flag and all registers A, X, Y.
|
||||||
|
; see http://6502.org/tutorials/register_preservation.html
|
||||||
|
%asm {{
|
||||||
|
php
|
||||||
|
pha
|
||||||
|
phy
|
||||||
|
phx
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub rrestore() {
|
||||||
|
; restore all registers and cpu status flag
|
||||||
|
%asm {{
|
||||||
|
plx
|
||||||
|
ply
|
||||||
|
pla
|
||||||
|
plp
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub read_flags() -> ubyte @A {
|
||||||
|
%asm {{
|
||||||
|
php
|
||||||
|
pla
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub clear_carry() {
|
||||||
|
%asm {{
|
||||||
|
clc
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub set_carry() {
|
||||||
|
%asm {{
|
||||||
|
sec
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub clear_irqd() {
|
||||||
|
%asm {{
|
||||||
|
cli
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub set_irqd() {
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub exit(ubyte returnvalue @A) {
|
||||||
|
; -- immediately exit the program with a return code in the A register
|
||||||
|
%asm {{
|
||||||
|
jsr c64.CLRCHN ; reset i/o channels
|
||||||
|
ldx prog8_lib.orig_stackpointer
|
||||||
|
txs
|
||||||
|
rts ; return to original caller
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub progend() -> uword @AY {
|
||||||
|
%asm {{
|
||||||
|
lda #<prog8_program_end
|
||||||
|
ldy #>prog8_program_end
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
749
compiler/res/prog8lib/cx16/textio.p8
Normal file
749
compiler/res/prog8lib/cx16/textio.p8
Normal file
@ -0,0 +1,749 @@
|
|||||||
|
; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target cx16
|
||||||
|
%import syslib
|
||||||
|
%import conv
|
||||||
|
|
||||||
|
|
||||||
|
txt {
|
||||||
|
|
||||||
|
const ubyte DEFAULT_WIDTH = 80
|
||||||
|
const ubyte DEFAULT_HEIGHT = 60
|
||||||
|
|
||||||
|
|
||||||
|
sub clear_screen() {
|
||||||
|
txt.chrout(147)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub home() {
|
||||||
|
txt.chrout(19)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub nl() {
|
||||||
|
txt.chrout('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub spc() {
|
||||||
|
txt.chrout(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||||
|
; ---- set the cursor on the given column (starting with 0) on the current line
|
||||||
|
%asm {{
|
||||||
|
sec
|
||||||
|
jsr c64.PLOT
|
||||||
|
tay
|
||||||
|
clc
|
||||||
|
jmp c64.PLOT
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||||
|
; ---- fill the character screen with the given fill character and character color.
|
||||||
|
%asm {{
|
||||||
|
sty _ly+1
|
||||||
|
phx
|
||||||
|
pha
|
||||||
|
jsr c64.SCREEN ; get dimensions in X/Y
|
||||||
|
txa
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
sta _lx+1
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
lda #%00010000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||||
|
stz cx16.VERA_ADDR_L ; start at (0,0)
|
||||||
|
stz cx16.VERA_ADDR_M
|
||||||
|
pla
|
||||||
|
_lx ldx #0 ; modified
|
||||||
|
phy
|
||||||
|
_ly ldy #1 ; modified
|
||||||
|
- sta cx16.VERA_DATA0
|
||||||
|
sty cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sty cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sty cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sty cx16.VERA_DATA0
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
ply
|
||||||
|
dey
|
||||||
|
beq +
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
inc cx16.VERA_ADDR_M ; next line
|
||||||
|
bra _lx
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||||
|
; ---- clear the character screen with the given fill character (leaves colors)
|
||||||
|
; (assumes screen matrix is at the default address)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
pha
|
||||||
|
jsr c64.SCREEN ; get dimensions in X/Y
|
||||||
|
txa
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
sta _lx+1
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
lda #%00100000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 2, bank 0.
|
||||||
|
stz cx16.VERA_ADDR_L ; start at (0,0)
|
||||||
|
stz cx16.VERA_ADDR_M
|
||||||
|
pla
|
||||||
|
_lx ldx #0 ; modified
|
||||||
|
- sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dey
|
||||||
|
beq +
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
inc cx16.VERA_ADDR_M ; next line
|
||||||
|
bra _lx
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||||
|
; ---- clear the character screen colors with the given color (leaves characters).
|
||||||
|
; (assumes color matrix is at the default address)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
sta _la+1
|
||||||
|
jsr c64.SCREEN ; get dimensions in X/Y
|
||||||
|
txa
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
sta _lx+1
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
lda #%00100000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 2, bank 0.
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_ADDR_L ; start at (1,0)
|
||||||
|
stz cx16.VERA_ADDR_M
|
||||||
|
_lx ldx #0 ; modified
|
||||||
|
_la lda #0 ; modified
|
||||||
|
- sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dey
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
inc cx16.VERA_ADDR_M ; next line
|
||||||
|
bra _lx
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b]
|
||||||
|
|
||||||
|
sub color (ubyte txtcol) {
|
||||||
|
txtcol &= 15
|
||||||
|
c64.CHROUT(color_to_charcode[txtcol])
|
||||||
|
}
|
||||||
|
|
||||||
|
sub color2 (ubyte txtcol, ubyte bgcol) {
|
||||||
|
txtcol &= 15
|
||||||
|
bgcol &= 15
|
||||||
|
c64.CHROUT(color_to_charcode[bgcol])
|
||||||
|
c64.CHROUT(1) ; switch fg and bg colors
|
||||||
|
c64.CHROUT(color_to_charcode[txtcol])
|
||||||
|
}
|
||||||
|
|
||||||
|
sub lowercase() {
|
||||||
|
cx16.screen_set_charset(3, 0) ; lowercase charset
|
||||||
|
}
|
||||||
|
|
||||||
|
sub uppercase() {
|
||||||
|
cx16.screen_set_charset(2, 0) ; uppercase charset
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_left() 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
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr c64.SCREEN
|
||||||
|
dex
|
||||||
|
stx _lx+1
|
||||||
|
dey
|
||||||
|
sty P8ZP_SCRATCH_B1 ; number of rows to scroll
|
||||||
|
|
||||||
|
_nextline
|
||||||
|
stz cx16.VERA_CTRL ; data port 0: source column
|
||||||
|
lda #%00010000 ; auto increment 1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda #2
|
||||||
|
sta cx16.VERA_ADDR_L ; begin in column 1
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1: destination column
|
||||||
|
lda #%00010000 ; auto increment 1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
|
||||||
|
_lx ldx #0 ; modified
|
||||||
|
- lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy char
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy color
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dec P8ZP_SCRATCH_B1
|
||||||
|
bpl _nextline
|
||||||
|
|
||||||
|
lda #0
|
||||||
|
sta cx16.VERA_CTRL
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_right() clobbers(A) {
|
||||||
|
; ---- scroll the whole screen 1 character to the right
|
||||||
|
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr c64.SCREEN
|
||||||
|
dex
|
||||||
|
stx _lx+1
|
||||||
|
txa
|
||||||
|
asl a
|
||||||
|
dea
|
||||||
|
sta _rcol+1
|
||||||
|
ina
|
||||||
|
ina
|
||||||
|
sta _rcol2+1
|
||||||
|
dey
|
||||||
|
sty P8ZP_SCRATCH_B1 ; number of rows to scroll
|
||||||
|
|
||||||
|
_nextline
|
||||||
|
stz cx16.VERA_CTRL ; data port 0: source column
|
||||||
|
lda #%00011000 ; auto decrement 1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
_rcol lda #79*2-1 ; modified
|
||||||
|
sta cx16.VERA_ADDR_L ; begin in rightmost column minus one
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1: destination column
|
||||||
|
lda #%00011000 ; auto decrement 1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
_rcol2 lda #79*2+1 ; modified
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
|
||||||
|
_lx ldx #0 ; modified
|
||||||
|
- lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy char
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy color
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dec P8ZP_SCRATCH_B1
|
||||||
|
bpl _nextline
|
||||||
|
|
||||||
|
lda #0
|
||||||
|
sta cx16.VERA_CTRL
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_up() clobbers(A, Y) {
|
||||||
|
; ---- scroll the whole screen 1 character up
|
||||||
|
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr c64.SCREEN
|
||||||
|
stx _nextline+1
|
||||||
|
dey
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
stz cx16.VERA_CTRL ; data port 0 is source
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_ADDR_M ; start at second line
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
lda #%00010000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||||
|
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1 is destination
|
||||||
|
stz cx16.VERA_ADDR_M ; start at top line
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
lda #%00010000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||||
|
|
||||||
|
_nextline
|
||||||
|
ldx #80 ; modified
|
||||||
|
- lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy char
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy color
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dec P8ZP_SCRATCH_B1
|
||||||
|
beq +
|
||||||
|
stz cx16.VERA_CTRL ; data port 0
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
inc cx16.VERA_ADDR_M
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
inc cx16.VERA_ADDR_M
|
||||||
|
bra _nextline
|
||||||
|
|
||||||
|
+ lda #0
|
||||||
|
sta cx16.VERA_CTRL
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_down() clobbers(A, Y) {
|
||||||
|
; ---- scroll the whole screen 1 character down
|
||||||
|
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr c64.SCREEN
|
||||||
|
stx _nextline+1
|
||||||
|
dey
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
stz cx16.VERA_CTRL ; data port 0 is source
|
||||||
|
dey
|
||||||
|
sty cx16.VERA_ADDR_M ; start at line before bottom line
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
lda #%00010000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||||
|
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1 is destination
|
||||||
|
iny
|
||||||
|
sty cx16.VERA_ADDR_M ; start at bottom line
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
lda #%00010000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||||
|
|
||||||
|
_nextline
|
||||||
|
ldx #80 ; modified
|
||||||
|
- lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy char
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy color
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dec P8ZP_SCRATCH_B1
|
||||||
|
beq +
|
||||||
|
stz cx16.VERA_CTRL ; data port 0
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
dec cx16.VERA_ADDR_M
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
dec cx16.VERA_ADDR_M
|
||||||
|
bra _nextline
|
||||||
|
|
||||||
|
+ lda #0
|
||||||
|
sta cx16.VERA_CTRL
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
|
||||||
|
|
||||||
|
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print null terminated string from A/Y
|
||||||
|
; note: the compiler contains an optimization that will replace
|
||||||
|
; a call to this subroutine with a string argument of just one char,
|
||||||
|
; by just one call to c64.CHROUT of that single char.
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
sty P8ZP_SCRATCH_REG
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_B1),y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
jsr c64.CHROUT
|
||||||
|
txa
|
||||||
|
jsr c64.CHROUT
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
_print_byte_digits
|
||||||
|
pha
|
||||||
|
cpy #'0'
|
||||||
|
beq +
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
jsr c64.CHROUT
|
||||||
|
bra _ones
|
||||||
|
+ pla
|
||||||
|
cmp #'0'
|
||||||
|
beq _ones
|
||||||
|
jsr c64.CHROUT
|
||||||
|
_ones txa
|
||||||
|
jsr c64.CHROUT
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the byte in A in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
pha
|
||||||
|
cmp #0
|
||||||
|
bpl +
|
||||||
|
lda #'-'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
+ pla
|
||||||
|
jsr conv.byte2decimal
|
||||||
|
bra print_ub._print_byte_digits
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
bcc +
|
||||||
|
pha
|
||||||
|
lda #'$'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
+ jsr conv.ubyte2hex
|
||||||
|
jsr c64.CHROUT
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
bcc +
|
||||||
|
lda #'%'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
+ ldy #8
|
||||||
|
- lda #'0'
|
||||||
|
asl P8ZP_SCRATCH_B1
|
||||||
|
bcc +
|
||||||
|
lda #'1'
|
||||||
|
+ jsr c64.CHROUT
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr print_ubbin
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
bra 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
|
||||||
|
bra print_ubhex
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
plx
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
beq _allzero
|
||||||
|
cmp #'0'
|
||||||
|
bne _gotdigit
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
|
||||||
|
_gotdigit
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
lda conv.uword2decimal.decTenThousands,y
|
||||||
|
bne _gotdigit
|
||||||
|
rts
|
||||||
|
_allzero
|
||||||
|
lda #'0'
|
||||||
|
jmp c64.CHROUT
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||||
|
%asm {{
|
||||||
|
cpy #0
|
||||||
|
bpl +
|
||||||
|
pha
|
||||||
|
lda #'-'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
tya
|
||||||
|
eor #255
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
eor #255
|
||||||
|
clc
|
||||||
|
adc #1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ bra print_uw
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||||
|
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||||
|
; It assumes the keyboard is selected as I/O channel!
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0 ; char counter = 0
|
||||||
|
- jsr c64.CHRIN
|
||||||
|
cmp #$0d ; return (ascii 13) pressed?
|
||||||
|
beq + ; yes, end.
|
||||||
|
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ lda #0
|
||||||
|
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
|
||||||
|
rts
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
|
||||||
|
; ---- sets the character in the screen matrix at the given position
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
txa
|
||||||
|
asl a
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
pla
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
|
||||||
|
; ---- get the character in the screen matrix at the given location
|
||||||
|
%asm {{
|
||||||
|
asl a
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) {
|
||||||
|
; ---- set the color in A on the screen matrix at the given position
|
||||||
|
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
|
||||||
|
; use the high nybble in A to set the Bg color!
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
txa
|
||||||
|
asl a
|
||||||
|
ina
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
pla
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
|
||||||
|
; ---- get the color in the screen color matrix at the given location
|
||||||
|
%asm {{
|
||||||
|
asl a
|
||||||
|
ina
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||||
|
; ---- set char+color at the given position on the screen
|
||||||
|
; note: color handling is the same as on the C64: it only sets the foreground color.
|
||||||
|
; use setcc2 if you want Cx-16 specific feature of setting both Bg+Fg colors.
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
lda column
|
||||||
|
asl a
|
||||||
|
tax
|
||||||
|
ldy row
|
||||||
|
lda charcolor
|
||||||
|
and #$0f
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
stx cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda char
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
inx
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
stx cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
and #$f0
|
||||||
|
ora P8ZP_SCRATCH_B1
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setcc2 (ubyte column, ubyte row, ubyte char, ubyte colors) {
|
||||||
|
; ---- set char+color at the given position on the screen
|
||||||
|
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
|
||||||
|
; use the high nybble in A to set the Bg color!
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
lda column
|
||||||
|
asl a
|
||||||
|
tax
|
||||||
|
ldy row
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
stx cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda char
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
inx
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
stx cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda colors
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||||
|
; ---- safe wrapper around PLOT kernal routine, to save the X register.
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
tax
|
||||||
|
clc
|
||||||
|
jsr c64.PLOT
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||||
|
; -- returns the text screen width (number of columns)
|
||||||
|
%asm {{
|
||||||
|
jsr c64.SCREEN
|
||||||
|
txa
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||||
|
; -- returns the text screen height (number of rows)
|
||||||
|
%asm {{
|
||||||
|
jsr c64.SCREEN
|
||||||
|
tya
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
compiler/res/prog8lib/cx16logo.p8
Normal file
31
compiler/res/prog8lib/cx16logo.p8
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
; routine to draw the Commander X16's log in petscii.
|
||||||
|
|
||||||
|
%import textio
|
||||||
|
|
||||||
|
cx16logo {
|
||||||
|
sub logo_at(ubyte column, ubyte row) {
|
||||||
|
uword strptr
|
||||||
|
for strptr in logo_lines {
|
||||||
|
txt.plot(column, row)
|
||||||
|
txt.print(strptr)
|
||||||
|
row++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub logo() {
|
||||||
|
uword strptr
|
||||||
|
for strptr in logo_lines
|
||||||
|
txt.print(strptr)
|
||||||
|
txt.nl()
|
||||||
|
}
|
||||||
|
|
||||||
|
str[] logo_lines = [
|
||||||
|
"\uf10d\uf11a\uf139\uf11b \uf11a\uf13a\uf11b\n",
|
||||||
|
"\uf10b\uf11a▎\uf139\uf11b \uf11a\uf13a\uf130\uf11b\n",
|
||||||
|
"\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌\n",
|
||||||
|
"\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132\n",
|
||||||
|
"\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂\n",
|
||||||
|
"\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b\n",
|
||||||
|
"\uf101\uf130\uf13a \uf139▎\uf100"
|
||||||
|
]
|
||||||
|
}
|
450
compiler/res/prog8lib/diskio.p8
Normal file
450
compiler/res/prog8lib/diskio.p8
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
; C64 and Cx16 disk drive I/O routines.
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
|
||||||
|
%import textio
|
||||||
|
%import string
|
||||||
|
%import syslib
|
||||||
|
|
||||||
|
diskio {
|
||||||
|
|
||||||
|
sub directory(ubyte drivenumber) -> ubyte {
|
||||||
|
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
|
||||||
|
|
||||||
|
c64.SETNAM(1, "$")
|
||||||
|
c64.SETLFS(13, drivenumber, 0)
|
||||||
|
void c64.OPEN() ; open 13,8,0,"$"
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
void c64.CHKIN(13) ; use #13 as input channel
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
|
||||||
|
repeat 4 {
|
||||||
|
void c64.CHRIN() ; skip the 4 prologue bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
; while not key pressed / EOF encountered, read data.
|
||||||
|
ubyte status = c64.READST()
|
||||||
|
while not status {
|
||||||
|
ubyte low = c64.CHRIN()
|
||||||
|
ubyte high = c64.CHRIN()
|
||||||
|
txt.print_uw(mkword(high, low))
|
||||||
|
txt.spc()
|
||||||
|
ubyte @zp char
|
||||||
|
repeat {
|
||||||
|
char = c64.CHRIN()
|
||||||
|
if char==0
|
||||||
|
break
|
||||||
|
txt.chrout(char)
|
||||||
|
}
|
||||||
|
txt.nl()
|
||||||
|
void c64.CHRIN() ; skip 2 bytes
|
||||||
|
void c64.CHRIN()
|
||||||
|
status = c64.READST()
|
||||||
|
if c64.STOP2()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
io_error:
|
||||||
|
status = c64.READST()
|
||||||
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
|
c64.CLOSE(13)
|
||||||
|
|
||||||
|
if status and status & $40 == 0 { ; bit 6=end of file
|
||||||
|
txt.print("\ni/o error, status: ")
|
||||||
|
txt.print_ub(status)
|
||||||
|
txt.nl()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
; internal variables for the iterative file lister / loader
|
||||||
|
ubyte list_skip_disk_name
|
||||||
|
uword list_pattern
|
||||||
|
uword list_blocks
|
||||||
|
ubyte iteration_in_progress = false
|
||||||
|
ubyte @zp first_byte
|
||||||
|
ubyte have_first_byte
|
||||||
|
str list_filename = "?" * 32
|
||||||
|
|
||||||
|
|
||||||
|
; ----- get a list of files (uses iteration functions internally) -----
|
||||||
|
|
||||||
|
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
|
||||||
|
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested.
|
||||||
|
uword names_buffer = memory("filenames", 512)
|
||||||
|
uword buffer_start = names_buffer
|
||||||
|
ubyte files_found = 0
|
||||||
|
if lf_start_list(drivenumber, pattern_ptr) {
|
||||||
|
while lf_next_entry() {
|
||||||
|
@(name_ptrs) = lsb(names_buffer)
|
||||||
|
name_ptrs++
|
||||||
|
@(name_ptrs) = msb(names_buffer)
|
||||||
|
name_ptrs++
|
||||||
|
names_buffer += string.copy(diskio.list_filename, names_buffer) + 1
|
||||||
|
files_found++
|
||||||
|
if names_buffer - buffer_start > 512-18
|
||||||
|
break
|
||||||
|
if files_found == max_names
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lf_end_list()
|
||||||
|
}
|
||||||
|
return files_found
|
||||||
|
}
|
||||||
|
|
||||||
|
; ----- iterative file lister functions (uses io channel 12) -----
|
||||||
|
|
||||||
|
sub lf_start_list(ubyte drivenumber, uword pattern_ptr) -> ubyte {
|
||||||
|
; -- start an iterative file listing with optional pattern matching.
|
||||||
|
; note: only a single iteration loop can be active at a time!
|
||||||
|
lf_end_list()
|
||||||
|
list_pattern = pattern_ptr
|
||||||
|
list_skip_disk_name = true
|
||||||
|
iteration_in_progress = true
|
||||||
|
|
||||||
|
c64.SETNAM(1, "$")
|
||||||
|
c64.SETLFS(12, drivenumber, 0)
|
||||||
|
void c64.OPEN() ; open 12,8,0,"$"
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
void c64.CHKIN(12) ; use #12 as input channel
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
|
||||||
|
repeat 4 {
|
||||||
|
void c64.CHRIN() ; skip the 4 prologue bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
if c64.READST()==0
|
||||||
|
return true
|
||||||
|
|
||||||
|
io_error:
|
||||||
|
lf_end_list()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sub lf_next_entry() -> ubyte {
|
||||||
|
; -- retrieve the next entry from an iterative file listing session.
|
||||||
|
; results will be found in list_blocks and list_filename.
|
||||||
|
; if it returns false though, there are no more entries (or an error occurred).
|
||||||
|
|
||||||
|
if not iteration_in_progress
|
||||||
|
return false
|
||||||
|
|
||||||
|
repeat {
|
||||||
|
void c64.CHKIN(12) ; use #12 as input channel again
|
||||||
|
|
||||||
|
uword nameptr = &list_filename
|
||||||
|
ubyte blocks_lsb = c64.CHRIN()
|
||||||
|
ubyte blocks_msb = c64.CHRIN()
|
||||||
|
|
||||||
|
if c64.READST()
|
||||||
|
goto close_end
|
||||||
|
|
||||||
|
list_blocks = mkword(blocks_msb, blocks_lsb)
|
||||||
|
|
||||||
|
; read until the filename starts after the first "
|
||||||
|
while c64.CHRIN()!='\"' {
|
||||||
|
if c64.READST()
|
||||||
|
goto close_end
|
||||||
|
}
|
||||||
|
|
||||||
|
; read the filename
|
||||||
|
repeat {
|
||||||
|
ubyte char = c64.CHRIN()
|
||||||
|
if char==0
|
||||||
|
break
|
||||||
|
if char=='\"'
|
||||||
|
break
|
||||||
|
@(nameptr) = char
|
||||||
|
nameptr++
|
||||||
|
}
|
||||||
|
|
||||||
|
@(nameptr) = 0
|
||||||
|
|
||||||
|
while c64.CHRIN() {
|
||||||
|
; read the rest of the entry until the end
|
||||||
|
}
|
||||||
|
|
||||||
|
void c64.CHRIN() ; skip 2 bytes
|
||||||
|
void c64.CHRIN()
|
||||||
|
|
||||||
|
if not list_skip_disk_name {
|
||||||
|
if not list_pattern
|
||||||
|
return true
|
||||||
|
if prog8_lib.pattern_match(list_filename, list_pattern)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
list_skip_disk_name = false
|
||||||
|
}
|
||||||
|
|
||||||
|
close_end:
|
||||||
|
lf_end_list()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sub lf_end_list() {
|
||||||
|
; -- end an iterative file listing session (close channels).
|
||||||
|
if iteration_in_progress {
|
||||||
|
c64.CLRCHN()
|
||||||
|
c64.CLOSE(12)
|
||||||
|
iteration_in_progress = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
; ----- iterative file loader functions (uses io channel 11) -----
|
||||||
|
|
||||||
|
sub f_open(ubyte drivenumber, uword filenameptr) -> ubyte {
|
||||||
|
; -- open a file for iterative reading with f_read
|
||||||
|
; note: only a single iteration loop can be active at a time!
|
||||||
|
f_close()
|
||||||
|
|
||||||
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
|
c64.SETLFS(11, drivenumber, 0)
|
||||||
|
void c64.OPEN() ; open 11,8,0,"filename"
|
||||||
|
if_cc {
|
||||||
|
iteration_in_progress = true
|
||||||
|
have_first_byte = false
|
||||||
|
void c64.CHKIN(11) ; use #11 as input channel
|
||||||
|
if_cc {
|
||||||
|
first_byte = c64.CHRIN() ; read first byte to test for file not found
|
||||||
|
if not c64.READST() {
|
||||||
|
have_first_byte = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f_close()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
|
||||||
|
; -- read from the currently open file, up to the given number of bytes.
|
||||||
|
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
||||||
|
if not iteration_in_progress or not num_bytes
|
||||||
|
return 0
|
||||||
|
|
||||||
|
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||||
|
if have_first_byte {
|
||||||
|
have_first_byte=false
|
||||||
|
@(bufferpointer) = first_byte
|
||||||
|
bufferpointer++
|
||||||
|
list_blocks++
|
||||||
|
num_bytes--
|
||||||
|
}
|
||||||
|
|
||||||
|
void c64.CHKIN(11) ; use #11 as input channel again
|
||||||
|
%asm {{
|
||||||
|
lda bufferpointer
|
||||||
|
sta _in_buffer+1
|
||||||
|
lda bufferpointer+1
|
||||||
|
sta _in_buffer+2
|
||||||
|
}}
|
||||||
|
repeat num_bytes {
|
||||||
|
%asm {{
|
||||||
|
jsr c64.CHRIN
|
||||||
|
sta cx16.r5
|
||||||
|
_in_buffer sta $ffff
|
||||||
|
inc _in_buffer+1
|
||||||
|
bne +
|
||||||
|
inc _in_buffer+2
|
||||||
|
+ inc list_blocks
|
||||||
|
bne +
|
||||||
|
inc list_blocks+1
|
||||||
|
+
|
||||||
|
}}
|
||||||
|
|
||||||
|
if cx16.r5==$0d { ; chance on I/o error status?
|
||||||
|
first_byte = c64.READST()
|
||||||
|
if first_byte & $40
|
||||||
|
f_close() ; end of file, close it
|
||||||
|
if first_byte
|
||||||
|
return list_blocks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list_blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
sub f_read_all(uword bufferpointer) -> uword {
|
||||||
|
; -- read the full contents of the file, returns number of bytes read.
|
||||||
|
if not iteration_in_progress
|
||||||
|
return 0
|
||||||
|
|
||||||
|
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||||
|
if have_first_byte {
|
||||||
|
have_first_byte=false
|
||||||
|
@(bufferpointer) = first_byte
|
||||||
|
bufferpointer++
|
||||||
|
list_blocks++
|
||||||
|
}
|
||||||
|
|
||||||
|
while not c64.READST() {
|
||||||
|
list_blocks += f_read(bufferpointer, 256)
|
||||||
|
bufferpointer += 256
|
||||||
|
}
|
||||||
|
return list_blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y {
|
||||||
|
; Routine to read text lines from a text file. Lines must be less than 255 characters.
|
||||||
|
; Reads characters from the input file UNTIL a newline or return character (or EOF).
|
||||||
|
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
|
||||||
|
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
|
||||||
|
; I/O error status should be checked by the caller itself via READST() routine.
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldx #11
|
||||||
|
jsr c64.CHKIN ; use channel 11 again for input
|
||||||
|
ldy #0
|
||||||
|
lda have_first_byte
|
||||||
|
beq _loop
|
||||||
|
lda #0
|
||||||
|
sta have_first_byte
|
||||||
|
lda first_byte
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
_loop jsr c64.CHRIN
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
beq _end
|
||||||
|
iny
|
||||||
|
cmp #$0a
|
||||||
|
beq _line_end
|
||||||
|
cmp #$0d
|
||||||
|
bne _loop
|
||||||
|
_line_end dey ; get rid of the trailing end-of-line char
|
||||||
|
lda #0
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
_end rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub f_close() {
|
||||||
|
; -- end an iterative file loading session (close channels).
|
||||||
|
if iteration_in_progress {
|
||||||
|
c64.CLRCHN()
|
||||||
|
c64.CLOSE(11)
|
||||||
|
iteration_in_progress = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub status(ubyte drivenumber) -> uword {
|
||||||
|
; -- retrieve the disk drive's current status message
|
||||||
|
uword messageptr = &filename
|
||||||
|
c64.SETNAM(0, filename)
|
||||||
|
c64.SETLFS(15, drivenumber, 15)
|
||||||
|
void c64.OPEN() ; open 15,8,15
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
void c64.CHKIN(15) ; use #15 as input channel
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
|
||||||
|
while not c64.READST() {
|
||||||
|
@(messageptr) = c64.CHRIN()
|
||||||
|
messageptr++
|
||||||
|
}
|
||||||
|
|
||||||
|
io_error:
|
||||||
|
@(messageptr) = 0
|
||||||
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
|
c64.CLOSE(15)
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte {
|
||||||
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
|
c64.SETLFS(1, drivenumber, 0)
|
||||||
|
uword end_address = address + size
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
lda address
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda address+1
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<P8ZP_SCRATCH_W1
|
||||||
|
ldx end_address
|
||||||
|
ldy end_address+1
|
||||||
|
jsr c64.SAVE
|
||||||
|
php
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
plp
|
||||||
|
}}
|
||||||
|
|
||||||
|
first_byte = 0 ; result var reuse
|
||||||
|
if_cc
|
||||||
|
first_byte = c64.READST()==0
|
||||||
|
|
||||||
|
c64.CLRCHN()
|
||||||
|
c64.CLOSE(1)
|
||||||
|
|
||||||
|
return first_byte
|
||||||
|
}
|
||||||
|
|
||||||
|
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
||||||
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
|
ubyte secondary = 1
|
||||||
|
uword end_of_load = 0
|
||||||
|
if address_override
|
||||||
|
secondary = 0
|
||||||
|
c64.SETLFS(1, drivenumber, secondary)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #0
|
||||||
|
ldx address_override
|
||||||
|
ldy address_override+1
|
||||||
|
jsr c64.LOAD
|
||||||
|
bcs +
|
||||||
|
stx end_of_load
|
||||||
|
sty end_of_load+1
|
||||||
|
+ ldx P8ZP_SCRATCH_REG
|
||||||
|
}}
|
||||||
|
|
||||||
|
c64.CLRCHN()
|
||||||
|
c64.CLOSE(1)
|
||||||
|
|
||||||
|
if end_of_load
|
||||||
|
return end_of_load - address_override
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
str filename = "0:??????????????????????????????????????"
|
||||||
|
|
||||||
|
sub delete(ubyte drivenumber, uword filenameptr) {
|
||||||
|
; -- delete a file on the drive
|
||||||
|
filename[0] = 's'
|
||||||
|
filename[1] = ':'
|
||||||
|
ubyte flen = string.copy(filenameptr, &filename+2)
|
||||||
|
c64.SETNAM(flen+2, filename)
|
||||||
|
c64.SETLFS(1, drivenumber, 15)
|
||||||
|
void c64.OPEN()
|
||||||
|
c64.CLRCHN()
|
||||||
|
c64.CLOSE(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
|
||||||
|
; -- rename a file on the drive
|
||||||
|
filename[0] = 'r'
|
||||||
|
filename[1] = ':'
|
||||||
|
ubyte flen_new = string.copy(newfileptr, &filename+2)
|
||||||
|
filename[flen_new+2] = '='
|
||||||
|
ubyte flen_old = string.copy(oldfileptr, &filename+3+flen_new)
|
||||||
|
c64.SETNAM(3+flen_new+flen_old, filename)
|
||||||
|
c64.SETLFS(1, drivenumber, 15)
|
||||||
|
void c64.OPEN()
|
||||||
|
c64.CLRCHN()
|
||||||
|
c64.CLOSE(1)
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,6 @@
|
|||||||
; Prog8 internal Math library routines - always included by the compiler
|
; Internal Math library routines - always included by the compiler
|
||||||
;
|
;
|
||||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
;
|
|
||||||
; indent format: TABS, size=8
|
|
||||||
|
|
||||||
%import c64lib
|
|
||||||
|
|
||||||
math {
|
math {
|
||||||
%asminclude "library:math.asm", ""
|
%asminclude "library:math.asm", ""
|
||||||
|
1115
compiler/res/prog8lib/prog8_funcs.asm
Normal file
1115
compiler/res/prog8lib/prog8_funcs.asm
Normal file
File diff suppressed because it is too large
Load Diff
1074
compiler/res/prog8lib/prog8_lib.asm
Normal file
1074
compiler/res/prog8lib/prog8_lib.asm
Normal file
File diff suppressed because it is too large
Load Diff
84
compiler/res/prog8lib/prog8_lib.p8
Normal file
84
compiler/res/prog8lib/prog8_lib.p8
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
; Internal library routines - always included by the compiler
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
|
||||||
|
prog8_lib {
|
||||||
|
%asminclude "library:prog8_lib.asm", ""
|
||||||
|
%asminclude "library:prog8_funcs.asm", ""
|
||||||
|
|
||||||
|
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||||
|
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||||
|
ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||||
|
byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||||
|
|
||||||
|
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
|
||||||
|
%asm {{
|
||||||
|
; pattern matching of a string.
|
||||||
|
; Input: cx16.r0: A NUL-terminated, <255-length pattern
|
||||||
|
; AY: A NUL-terminated, <255-length string
|
||||||
|
;
|
||||||
|
; Output: A = 1 if the string matches the pattern, A = 0 if not.
|
||||||
|
;
|
||||||
|
; Notes: Clobbers A, X, Y. Each * in the pattern uses 4 bytes of stack.
|
||||||
|
;
|
||||||
|
; see http://6502.org/source/strings/patmatch.htm
|
||||||
|
|
||||||
|
str = P8ZP_SCRATCH_W1
|
||||||
|
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sta str
|
||||||
|
sty str+1
|
||||||
|
lda cx16.r0
|
||||||
|
sta modify_pattern1+1
|
||||||
|
sta modify_pattern2+1
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta modify_pattern1+2
|
||||||
|
sta modify_pattern2+2
|
||||||
|
jsr _match
|
||||||
|
lda #0
|
||||||
|
adc #0
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
|
||||||
|
|
||||||
|
_match
|
||||||
|
ldx #$00 ; x is an index in the pattern
|
||||||
|
ldy #$ff ; y is an index in the string
|
||||||
|
modify_pattern1
|
||||||
|
next lda $ffff,x ; look at next pattern character MODIFIED
|
||||||
|
cmp #'*' ; is it a star?
|
||||||
|
beq star ; yes, do the complicated stuff
|
||||||
|
iny ; no, let's look at the string
|
||||||
|
cmp #'?' ; is the pattern caracter a ques?
|
||||||
|
bne reg ; no, it's a regular character
|
||||||
|
lda (str),y ; yes, so it will match anything
|
||||||
|
beq fail ; except the end of string
|
||||||
|
reg cmp (str),y ; are both characters the same?
|
||||||
|
bne fail ; no, so no match
|
||||||
|
inx ; yes, keep checking
|
||||||
|
cmp #0 ; are we at end of string?
|
||||||
|
bne next ; not yet, loop
|
||||||
|
found rts ; success, return with c=1
|
||||||
|
|
||||||
|
star inx ; skip star in pattern
|
||||||
|
modify_pattern2
|
||||||
|
cmp $ffff,x ; string of stars equals one star MODIFIED
|
||||||
|
beq star ; so skip them also
|
||||||
|
stloop txa ; we first try to match with * = ""
|
||||||
|
pha ; and grow it by 1 character every
|
||||||
|
tya ; time we loop
|
||||||
|
pha ; save x and y on stack
|
||||||
|
jsr next ; recursive call
|
||||||
|
pla ; restore x and y
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
tax
|
||||||
|
bcs found ; we found a match, return with c=1
|
||||||
|
iny ; no match yet, try to grow * string
|
||||||
|
lda (str),y ; are we at the end of string?
|
||||||
|
bne stloop ; not yet, add a character
|
||||||
|
fail clc ; yes, no match found, return with c=0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +0,0 @@
|
|||||||
; Prog8 internal library routines - always included by the compiler
|
|
||||||
;
|
|
||||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
|
||||||
;
|
|
||||||
; indent format: TABS, size=8
|
|
||||||
|
|
||||||
%import c64lib
|
|
||||||
|
|
||||||
prog8_lib {
|
|
||||||
%asminclude "library:prog8lib.asm", ""
|
|
||||||
}
|
|
235
compiler/res/prog8lib/string.p8
Normal file
235
compiler/res/prog8lib/string.p8
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
; 0-terminated string manipulation routines.
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
|
||||||
|
|
||||||
|
string {
|
||||||
|
|
||||||
|
asmsub length(uword string @AY) clobbers(A) -> ubyte @Y {
|
||||||
|
; Returns the number of bytes in the string.
|
||||||
|
; This value is determined during runtime and counts upto the first terminating 0 byte in the string,
|
||||||
|
; regardless of the size of the string during compilation time. Don’t confuse this with len and sizeof!
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
beq +
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub left(uword source @R0, ubyte length @A, uword target @R1) clobbers(A, Y) {
|
||||||
|
; Copies the left side of the source string of the given length to target string.
|
||||||
|
; It is assumed the target string buffer is large enough to contain the result.
|
||||||
|
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||||
|
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||||
|
%asm {{
|
||||||
|
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||||
|
ldy cx16.r0
|
||||||
|
sty P8ZP_SCRATCH_W1
|
||||||
|
ldy cx16.r0+1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy cx16.r1
|
||||||
|
sty P8ZP_SCRATCH_W2
|
||||||
|
ldy cx16.r1+1
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
tay
|
||||||
|
lda #0
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
cpy #0
|
||||||
|
bne _loop
|
||||||
|
rts
|
||||||
|
_loop dey
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
cpy #0
|
||||||
|
bne _loop
|
||||||
|
+ rts
|
||||||
|
}}
|
||||||
|
; asmgen.out(" jsr prog8_lib.func_leftstr")
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub right(uword source @R0, ubyte length @A, uword target @R1) clobbers(A,Y) {
|
||||||
|
; Copies the right side of the source string of the given length to target string.
|
||||||
|
; It is assumed the target string buffer is large enough to contain the result.
|
||||||
|
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||||
|
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||||
|
%asm {{
|
||||||
|
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
lda cx16.r0
|
||||||
|
ldy cx16.r0+1
|
||||||
|
jsr string.length
|
||||||
|
tya
|
||||||
|
sec
|
||||||
|
sbc P8ZP_SCRATCH_B1
|
||||||
|
clc
|
||||||
|
adc cx16.r0
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda cx16.r0+1
|
||||||
|
adc #0
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
ldy cx16.r1
|
||||||
|
sty P8ZP_SCRATCH_W2
|
||||||
|
ldy cx16.r1+1
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
lda #0
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
cpy #0
|
||||||
|
bne _loop
|
||||||
|
rts
|
||||||
|
_loop dey
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
cpy #0
|
||||||
|
bne _loop
|
||||||
|
+ rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub slice(uword source @R0, ubyte start @A, ubyte length @Y, uword target @R1) clobbers(A, Y) {
|
||||||
|
; Copies a segment from the source string, starting at the given index,
|
||||||
|
; and of the given length to target string.
|
||||||
|
; It is assumed the target string buffer is large enough to contain the result.
|
||||||
|
; Also, you have to make sure yourself that start and length are within bounds of the strings.
|
||||||
|
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||||
|
%asm {{
|
||||||
|
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||||
|
; substr(source, target, start, length)
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
lda cx16.r0
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
lda cx16.r1
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
lda cx16.r1+1
|
||||||
|
sta P8ZP_SCRATCH_W2+1
|
||||||
|
|
||||||
|
; adjust src location
|
||||||
|
clc
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
bcc +
|
||||||
|
inc P8ZP_SCRATCH_W1+1
|
||||||
|
+ lda #0
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
beq _startloop
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
_startloop dey
|
||||||
|
cpy #$ff
|
||||||
|
bne -
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub find(uword string @R0, ubyte character @A) -> uword @AY {
|
||||||
|
; Locates the first position of the given character in the string,
|
||||||
|
; returns the string starting with this character or $0000 if the character is not found.
|
||||||
|
%asm {{
|
||||||
|
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
lda cx16.r0
|
||||||
|
ldy cx16.r0+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
beq _notfound
|
||||||
|
cmp P8ZP_SCRATCH_B1
|
||||||
|
beq _found
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
_notfound lda #0
|
||||||
|
ldy #0
|
||||||
|
rts
|
||||||
|
_found sty P8ZP_SCRATCH_B1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ rts
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub copy(uword source @R0, uword target @AY) clobbers(A) -> ubyte @Y {
|
||||||
|
; Copy a string to another, overwriting that one.
|
||||||
|
; Returns the length of the string that was copied.
|
||||||
|
; Often you don’t have to call this explicitly and can just write string1 = string2
|
||||||
|
; but this function is useful if you’re dealing with addresses for instance.
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda cx16.r0
|
||||||
|
ldy cx16.r0+1
|
||||||
|
jmp prog8_lib.strcpy
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
|
||||||
|
; Compares two strings for sorting.
|
||||||
|
; Returns -1 (255), 0 or 1 depeding on wether string1 sorts before, equal or after string2.
|
||||||
|
; Note that you can also directly compare strings and string values with eachother using
|
||||||
|
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
lda cx16.r0
|
||||||
|
ldy cx16.r0+1
|
||||||
|
jmp prog8_lib.strcmp_mem
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub lower(uword st @AY) {
|
||||||
|
; Lowercases the petscii string in-place.
|
||||||
|
; (for efficiency, non-letter characters > 128 will also not be left intact,
|
||||||
|
; but regular text doesn't usually contain those characters anyway.)
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
beq _done
|
||||||
|
and #$7f
|
||||||
|
cmp #97
|
||||||
|
bcc +
|
||||||
|
cmp #123
|
||||||
|
bcs +
|
||||||
|
and #%11011111
|
||||||
|
+ sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
_done rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub upper(uword st @AY) {
|
||||||
|
; Uppercases the petscii string in-place.
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
beq _done
|
||||||
|
cmp #65
|
||||||
|
bcc +
|
||||||
|
cmp #91
|
||||||
|
bcs +
|
||||||
|
ora #%00100000
|
||||||
|
+ sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
_done rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
50
compiler/res/prog8lib/test_stack.p8
Normal file
50
compiler/res/prog8lib/test_stack.p8
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
; utility debug code to print the X (evalstack) and SP (cpu stack) registers.
|
||||||
|
|
||||||
|
%import textio
|
||||||
|
|
||||||
|
test_stack {
|
||||||
|
|
||||||
|
asmsub test() {
|
||||||
|
%asm {{
|
||||||
|
stx _saveX
|
||||||
|
lda #13
|
||||||
|
jsr txt.chrout
|
||||||
|
lda #'-'
|
||||||
|
ldy #12
|
||||||
|
- jsr txt.chrout
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
lda #13
|
||||||
|
jsr txt.chrout
|
||||||
|
lda #'x'
|
||||||
|
jsr txt.chrout
|
||||||
|
lda #'='
|
||||||
|
jsr txt.chrout
|
||||||
|
lda _saveX
|
||||||
|
jsr txt.print_ub
|
||||||
|
lda #' '
|
||||||
|
jsr txt.chrout
|
||||||
|
lda #'s'
|
||||||
|
jsr txt.chrout
|
||||||
|
lda #'p'
|
||||||
|
jsr txt.chrout
|
||||||
|
lda #'='
|
||||||
|
jsr txt.chrout
|
||||||
|
tsx
|
||||||
|
txa
|
||||||
|
jsr txt.print_ub
|
||||||
|
lda #13
|
||||||
|
jsr txt.chrout
|
||||||
|
lda #'-'
|
||||||
|
ldy #12
|
||||||
|
- jsr txt.chrout
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
lda #13
|
||||||
|
jsr txt.chrout
|
||||||
|
ldx _saveX
|
||||||
|
rts
|
||||||
|
_saveX .byte 0
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
3.0
|
6.3
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package prog8
|
package prog8
|
||||||
|
|
||||||
import kotlinx.cli.*
|
import kotlinx.cli.ArgParser
|
||||||
|
import kotlinx.cli.ArgType
|
||||||
|
import kotlinx.cli.default
|
||||||
|
import kotlinx.cli.multiple
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.AstException
|
||||||
import prog8.compiler.CompilationResult
|
import prog8.compiler.CompilationResult
|
||||||
import prog8.compiler.compileProgram
|
import prog8.compiler.compileProgram
|
||||||
import prog8.compiler.target.CompilationTarget
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8.compiler.target.c64.Petscii
|
|
||||||
import prog8.compiler.target.c64.codegen.AsmGen
|
|
||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
import java.io.IOException
|
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.StandardWatchEventKinds
|
import java.nio.file.StandardWatchEventKinds
|
||||||
@ -34,73 +34,68 @@ fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDe
|
|||||||
|
|
||||||
|
|
||||||
private fun compileMain(args: Array<String>) {
|
private fun compileMain(args: Array<String>) {
|
||||||
val cli = CommandLineInterface("prog8compiler")
|
val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
|
||||||
val startEmulator by cli.flagArgument("-emu", "auto-start the Vice C-64 emulator after successful compilation")
|
val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
|
||||||
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
|
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
||||||
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
|
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||||
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
|
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
||||||
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||||
val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently only 'c64' (C64 6502 assembly) available", "c64")
|
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
||||||
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
|
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
|
||||||
|
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cli.parse(args)
|
cli.parse(args)
|
||||||
} catch (e: Exception) {
|
} catch (e: IllegalStateException) {
|
||||||
|
System.err.println(e.message)
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
when(compilationTarget) {
|
|
||||||
"c64" -> {
|
|
||||||
with(CompilationTarget) {
|
|
||||||
name = "c64"
|
|
||||||
machine = C64MachineDefinition
|
|
||||||
encodeString = { str, altEncoding ->
|
|
||||||
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
|
||||||
}
|
|
||||||
decodeString = { bytes, altEncoding ->
|
|
||||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
|
||||||
}
|
|
||||||
asmGenerator = ::AsmGen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
System.err.println("invalid compilation target")
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val outputPath = pathFrom(outputDir)
|
val outputPath = pathFrom(outputDir)
|
||||||
if(!outputPath.toFile().isDirectory) {
|
if(!outputPath.toFile().isDirectory) {
|
||||||
System.err.println("Output path doesn't exist")
|
System.err.println("Output path doesn't exist")
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(watchMode && moduleFiles.size<=1) {
|
if(watchMode==true) {
|
||||||
val watchservice = FileSystems.getDefault().newWatchService()
|
val watchservice = FileSystems.getDefault().newWatchService()
|
||||||
|
val allImportedFiles = mutableSetOf<Path>()
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
val filepath = pathFrom(moduleFiles.single()).normalize()
|
println("Continuous watch mode active. Modules: $moduleFiles")
|
||||||
println("Continuous watch mode active. Main module: $filepath")
|
val results = mutableListOf<CompilationResult>()
|
||||||
|
for(filepathRaw in moduleFiles) {
|
||||||
|
val filepath = pathFrom(filepathRaw).normalize()
|
||||||
|
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
|
||||||
|
results.add(compilationResult)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
val allNewlyImportedFiles = results.flatMap { it.importedFiles }
|
||||||
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
|
allImportedFiles.addAll(allNewlyImportedFiles)
|
||||||
println("Imported files (now watching:)")
|
|
||||||
for (importedFile in compilationResult.importedFiles) {
|
println("Imported files (now watching:)")
|
||||||
print(" ")
|
for (importedFile in allImportedFiles) {
|
||||||
println(importedFile)
|
print(" ")
|
||||||
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
println(importedFile)
|
||||||
}
|
val watchDir = importedFile.parent ?: Path.of(".")
|
||||||
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||||
|
}
|
||||||
|
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||||
|
|
||||||
|
var recompile=false
|
||||||
|
while(!recompile) {
|
||||||
val event = watchservice.take()
|
val event = watchservice.take()
|
||||||
for(changed in event.pollEvents()) {
|
for (changed in event.pollEvents()) {
|
||||||
val changedPath = changed.context() as Path
|
val changedPath = changed.context() as Path
|
||||||
println(" change detected: $changedPath")
|
if(allImportedFiles.any { it.fileName == changedPath.fileName }) {
|
||||||
|
println(" change detected: $changedPath")
|
||||||
|
recompile = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
event.reset()
|
event.reset()
|
||||||
println("\u001b[H\u001b[2J") // clear the screen
|
|
||||||
} catch (x: Exception) {
|
|
||||||
throw x
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println("\u001b[H\u001b[2J") // clear the screen
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -108,7 +103,7 @@ private fun compileMain(args: Array<String>) {
|
|||||||
val filepath = pathFrom(filepathRaw).normalize()
|
val filepath = pathFrom(filepathRaw).normalize()
|
||||||
val compilationResult: CompilationResult
|
val compilationResult: CompilationResult
|
||||||
try {
|
try {
|
||||||
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
|
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
|
||||||
if(!compilationResult.success)
|
if(!compilationResult.success)
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
} catch (x: ParsingFailedError) {
|
} catch (x: ParsingFailedError) {
|
||||||
@ -117,24 +112,11 @@ private fun compileMain(args: Array<String>) {
|
|||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startEmulator) {
|
if (startEmulator==true) {
|
||||||
if (compilationResult.programName.isEmpty())
|
if (compilationResult.programName.isEmpty())
|
||||||
println("\nCan't start emulator because no program was assembled.")
|
println("\nCan't start emulator because no program was assembled.")
|
||||||
else if(startEmulator) {
|
else {
|
||||||
for(emulator in listOf("x64sc", "x64")) {
|
compilationResult.compTarget.machine.launchEmulator(compilationResult.programName)
|
||||||
println("\nStarting C-64 emulator $emulator...")
|
|
||||||
val cmdline = listOf(emulator, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
|
|
||||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
package prog8.ast.base
|
|
||||||
|
|
||||||
import prog8.ast.Module
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.processing.*
|
|
||||||
import prog8.compiler.CompilationOptions
|
|
||||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
|
||||||
import prog8.optimizer.AssignmentTransformer
|
|
||||||
|
|
||||||
|
|
||||||
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 Program.transformAssignments(errors: ErrorReporter) {
|
|
||||||
val transform = AssignmentTransformer(this, errors)
|
|
||||||
transform.visit(this)
|
|
||||||
while(transform.optimizationsDone>0 && errors.isEmpty()) {
|
|
||||||
transform.applyModifications()
|
|
||||||
transform.optimizationsDone = 0
|
|
||||||
transform.visit(this)
|
|
||||||
}
|
|
||||||
transform.applyModifications()
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
@ -1,118 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,176 +0,0 @@
|
|||||||
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),
|
|
||||||
null,
|
|
||||||
first,
|
|
||||||
first.position
|
|
||||||
)
|
|
||||||
val assignFirst = Assignment(
|
|
||||||
AssignTarget.fromExpr(first),
|
|
||||||
null,
|
|
||||||
second,
|
|
||||||
first.position
|
|
||||||
)
|
|
||||||
val assignSecond = Assignment(
|
|
||||||
AssignTarget.fromExpr(second),
|
|
||||||
null,
|
|
||||||
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(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
|
||||||
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.args.single(), DataType.UBYTE, false, functionCall.position)
|
|
||||||
return listOf(IAstModification.ReplaceNode(
|
|
||||||
functionCall, typecast, 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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,184 +0,0 @@
|
|||||||
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) augmented assignment is turned into regular assignment.
|
|
||||||
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
|
|
||||||
// - 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, null, 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> {
|
|
||||||
if(assignment.aug_op!=null) {
|
|
||||||
return listOf(IAstModification.ReplaceNode(assignment, assignment.asDesugaredNonaugmented(), parent))
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
null, 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),
|
|
||||||
null, 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
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)
|
|
||||||
return listOf(IAstModification.ReplaceNode(typecast, value, parent))
|
|
||||||
}
|
|
||||||
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
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)
|
|
||||||
= checkTypes(functionCall as IFunctionCall, functionCall.definingScope())
|
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement)
|
|
||||||
= checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope())
|
|
||||||
|
|
||||||
private fun checkTypes(call: IFunctionCall, scope: INameScope) {
|
|
||||||
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
|
|
||||||
val target = call.target.targetStatement(scope)
|
|
||||||
when(target) {
|
|
||||||
is Subroutine -> {
|
|
||||||
val paramtypes = target.parameters.map { it.type }
|
|
||||||
if(argtypes!=paramtypes)
|
|
||||||
throw CompilerException("parameter type mismatch $call")
|
|
||||||
}
|
|
||||||
is BuiltinFunctionStatementPlaceholder -> {
|
|
||||||
val func = BuiltinFunctions.getValue(target.name)
|
|
||||||
val paramtypes = func.parameters.map { it.possibleDatatypes }
|
|
||||||
for(x in argtypes.zip(paramtypes)) {
|
|
||||||
if(x.first !in x.second)
|
|
||||||
throw CompilerException("parameter type mismatch $call")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +1,118 @@
|
|||||||
package prog8.compiler
|
package prog8.compiler
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.processing.AstWalker
|
|
||||||
import prog8.ast.processing.IAstModification
|
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
|
import prog8.ast.walk.AstWalker
|
||||||
|
import prog8.ast.walk.IAstModification
|
||||||
|
import prog8.compiler.target.ICompilationTarget
|
||||||
|
|
||||||
|
|
||||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||||
|
|
||||||
private val noModifications = emptyList<IAstModification>()
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
if (decl.value == null && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
subroutineVariables.add(decl.name to decl)
|
||||||
// a numeric vardecl without an initial value is initialized with zero.
|
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||||
decl.value = decl.zeroElementValue()
|
// a numeric vardecl without an initial value is initialized with zero,
|
||||||
|
// unless there's already an assignment below, that initializes the value
|
||||||
|
if(decl.allowInitializeWithZero)
|
||||||
|
{
|
||||||
|
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
|
||||||
|
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
|
||||||
|
decl.value = null
|
||||||
|
else
|
||||||
|
decl.value = decl.zeroElementValue()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
// Try to replace A = B <operator> Something by A= B, A = A <operator> Something
|
||||||
|
// this triggers the more efficent augmented assignment code generation more often.
|
||||||
|
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||||
|
if(!assignment.isAugmentable
|
||||||
|
&& assignment.target.identifier != null
|
||||||
|
&& compTarget.isInRegularRAM(assignment.target, program)) {
|
||||||
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
|
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
||||||
|
if (binExpr.left !is BinaryExpression) {
|
||||||
|
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
|
||||||
|
// the right part of the expression contains the target variable itself.
|
||||||
|
// we can't 'split' it trivially because the variable will be changed halfway through.
|
||||||
|
if(binExpr.operator in associativeOperators) {
|
||||||
|
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
|
||||||
|
// use the other part of the expression to split.
|
||||||
|
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()),
|
||||||
|
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||||
|
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
|
||||||
|
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
|
||||||
|
|
||||||
|
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
|
subroutineVariables.clear()
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
val decls = scope.statements.filterIsInstance<VarDecl>()
|
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||||
|
subroutineVariables.addAll(decls.map { it.name to it })
|
||||||
|
|
||||||
val sub = scope.definingSubroutine()
|
val sub = scope.definingSubroutine()
|
||||||
if (sub != null) {
|
if (sub != null) {
|
||||||
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
|
||||||
var conflicts = false
|
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
|
||||||
decls.forEach {
|
val replaceVardecls =numericVarsWithValue.map {
|
||||||
val existing = existingVariables[it.name]
|
val initValue = it.value!! // assume here that value has always been set by now
|
||||||
if (existing != null) {
|
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
||||||
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
|
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||||
conflicts = true
|
val assign = Assignment(target, initValue, it.position)
|
||||||
}
|
initValue.parent = assign
|
||||||
}
|
IAstModification.ReplaceNode(it, assign, scope)
|
||||||
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, null, 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
|
|
||||||
}
|
}
|
||||||
|
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
|
||||||
|
return replaceVardecls + moveVardeclsUp
|
||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
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.
|
val firstDeclarations = mutableMapOf<String, VarDecl>()
|
||||||
|
for(decl in subroutineVariables) {
|
||||||
|
val existing = firstDeclarations[decl.first]
|
||||||
|
if(existing!=null && existing !== decl.second) {
|
||||||
|
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
|
||||||
|
} else {
|
||||||
|
firstDeclarations[decl.first] = decl.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
|
||||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||||
val mods = mutableListOf<IAstModification>()
|
val mods = mutableListOf<IAstModification>()
|
||||||
val returnStmt = Return(null, subroutine.position)
|
val returnStmt = Return(null, subroutine.position)
|
||||||
if (subroutine.asmAddress == null
|
if (subroutine.asmAddress == null
|
||||||
|
&& !subroutine.inline
|
||||||
&& subroutine.statements.isNotEmpty()
|
&& subroutine.statements.isNotEmpty()
|
||||||
&& subroutine.amountOfRtsInAsm() == 0
|
&& subroutine.amountOfRtsInAsm() == 0
|
||||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||||
@ -72,9 +129,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
|||||||
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
|
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
|
||||||
&& outerStatements[subroutineStmtIdx - 1] !is Return
|
&& outerStatements[subroutineStmtIdx - 1] !is Return
|
||||||
&& outerScope !is Block) {
|
&& outerScope !is Block) {
|
||||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
|
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
return mods
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,13 +145,40 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
|||||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(sourceDt in PassByReferenceDatatypes) {
|
|
||||||
|
|
||||||
|
// Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires
|
||||||
|
// that the types of assignment values and their target are the same,
|
||||||
|
// and that the types of both operands of a binaryexpression node are the same.
|
||||||
|
// So, it is not easily possible to remove the typecasts that are there to make these conditions true.
|
||||||
|
// The only place for now where we can do this is for:
|
||||||
|
// asmsub register pair parameter.
|
||||||
|
|
||||||
|
if(typecast.type in WordDatatypes) {
|
||||||
|
val fcall = typecast.parent as? IFunctionCall
|
||||||
|
if (fcall != null) {
|
||||||
|
val sub = fcall.target.targetStatement(program) as? Subroutine
|
||||||
|
if (sub != null && sub.isAsmSubroutine) {
|
||||||
|
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sourceDt in PassByReferenceDatatypes) {
|
||||||
if(typecast.type==DataType.UWORD) {
|
if(typecast.type==DataType.UWORD) {
|
||||||
return listOf(IAstModification.ReplaceNode(
|
if(typecast.expression is IdentifierReference) {
|
||||||
typecast,
|
return listOf(IAstModification.ReplaceNode(
|
||||||
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
typecast,
|
||||||
parent
|
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
||||||
))
|
parent
|
||||||
|
))
|
||||||
|
} else if(typecast.expression is IFunctionCall) {
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
typecast,
|
||||||
|
typecast.expression,
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||||
}
|
}
|
||||||
@ -103,4 +186,34 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
|||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
val binExpr = ifStatement.condition as? BinaryExpression
|
||||||
|
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||||
|
// if x -> if x!=0, if x+5 -> if x+5 != 0
|
||||||
|
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
val binExpr = untilLoop.condition as? BinaryExpression
|
||||||
|
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||||
|
// until x -> until x!=0, until x+5 -> until x+5 != 0
|
||||||
|
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
val binExpr = whileLoop.condition as? BinaryExpression
|
||||||
|
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||||
|
// while x -> while x!=0, while x+5 -> while x+5 != 0
|
||||||
|
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,30 @@
|
|||||||
package prog8.compiler
|
package prog8.compiler
|
||||||
|
|
||||||
|
import prog8.ast.AstToSourceCode
|
||||||
|
import prog8.ast.IBuiltinFunctions
|
||||||
|
import prog8.ast.IMemSizer
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.AstException
|
||||||
|
import prog8.ast.base.Position
|
||||||
|
import prog8.ast.expressions.Expression
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.statements.Directive
|
||||||
|
import prog8.compiler.astprocessing.*
|
||||||
|
import prog8.compiler.functions.*
|
||||||
|
import prog8.compiler.target.C64Target
|
||||||
|
import prog8.compiler.target.Cx16Target
|
||||||
|
import prog8.compiler.target.ICompilationTarget
|
||||||
|
import prog8.compiler.target.asmGeneratorFor
|
||||||
|
import prog8.optimizer.*
|
||||||
|
import prog8.parser.ModuleImporter
|
||||||
|
import prog8.parser.ParsingFailedError
|
||||||
|
import prog8.parser.moduleName
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.math.abs
|
import kotlin.system.exitProcess
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
|
|
||||||
enum class OutputType {
|
enum class OutputType {
|
||||||
RAW,
|
RAW,
|
||||||
@ -27,31 +48,284 @@ data class CompilationOptions(val output: OutputType,
|
|||||||
val launcher: LauncherType,
|
val launcher: LauncherType,
|
||||||
val zeropage: ZeropageType,
|
val zeropage: ZeropageType,
|
||||||
val zpReserved: List<IntRange>,
|
val zpReserved: List<IntRange>,
|
||||||
val floats: Boolean)
|
val floats: Boolean,
|
||||||
|
val noSysInit: Boolean,
|
||||||
|
val compTarget: ICompilationTarget) {
|
||||||
|
var slowCodegenWarnings = false
|
||||||
|
var optimize = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class CompilerException(message: String?) : Exception(message)
|
class CompilerException(message: String?) : Exception(message)
|
||||||
|
|
||||||
fun Number.toHex(): String {
|
class CompilationResult(val success: Boolean,
|
||||||
// 0..15 -> "0".."15"
|
val programAst: Program,
|
||||||
// 16..255 -> "$10".."$ff"
|
val programName: String,
|
||||||
// 256..65536 -> "$0100".."$ffff"
|
val compTarget: ICompilationTarget,
|
||||||
// negative values are prefixed with '-'.
|
val importedFiles: List<Path>)
|
||||||
val integer = this.toInt()
|
|
||||||
if(integer<0)
|
|
||||||
return '-' + abs(integer).toHex()
|
fun compileProgram(filepath: Path,
|
||||||
return when (integer) {
|
optimize: Boolean,
|
||||||
in 0 until 16 -> integer.toString()
|
writeAssembly: Boolean,
|
||||||
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
|
slowCodegenWarnings: Boolean,
|
||||||
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
|
compilationTarget: String,
|
||||||
else -> throw CompilerException("number too large for 16 bits $this")
|
outputDir: Path): CompilationResult {
|
||||||
|
var programName = ""
|
||||||
|
lateinit var programAst: Program
|
||||||
|
lateinit var importedFiles: List<Path>
|
||||||
|
val errors = ErrorReporter()
|
||||||
|
|
||||||
|
val compTarget =
|
||||||
|
when(compilationTarget) {
|
||||||
|
C64Target.name -> C64Target
|
||||||
|
Cx16Target.name -> Cx16Target
|
||||||
|
else -> {
|
||||||
|
System.err.println("invalid compilation target")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val totalTime = measureTimeMillis {
|
||||||
|
// import main module and everything it needs
|
||||||
|
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget)
|
||||||
|
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||||
|
compilationOptions.optimize = optimize
|
||||||
|
programAst = ast
|
||||||
|
importedFiles = imported
|
||||||
|
processAst(programAst, errors, compilationOptions)
|
||||||
|
if (compilationOptions.optimize)
|
||||||
|
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
||||||
|
postprocessAst(programAst, errors, compilationOptions)
|
||||||
|
|
||||||
|
// printAst(programAst)
|
||||||
|
|
||||||
|
if(writeAssembly)
|
||||||
|
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
|
||||||
|
}
|
||||||
|
System.out.flush()
|
||||||
|
System.err.flush()
|
||||||
|
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||||
|
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
|
||||||
|
|
||||||
|
} catch (px: ParsingFailedError) {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
||||||
|
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions {
|
||||||
|
lateinit var program: Program
|
||||||
|
|
||||||
|
override val names = functions.keys
|
||||||
|
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
|
||||||
|
|
||||||
|
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? {
|
||||||
|
val func = BuiltinFunctions[name]
|
||||||
|
if(func!=null) {
|
||||||
|
val exprfunc = func.constExpressionFunc
|
||||||
|
if(exprfunc!=null) {
|
||||||
|
return try {
|
||||||
|
exprfunc(args, position, program, memsizer)
|
||||||
|
} catch(x: NotConstArgumentException) {
|
||||||
|
// const-evaluating the builtin function call failed.
|
||||||
|
null
|
||||||
|
} catch(x: CannotEvaluateException) {
|
||||||
|
// const-evaluating the builtin function call failed.
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(func.known_returntype==null)
|
||||||
|
throw IllegalArgumentException("builtin function $name can't be used here because it doesn't return a value")
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
override fun returnType(name: String, args: MutableList<Expression>) =
|
||||||
|
builtinFunctionReturnType(name, args, program)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICompilationTarget): Triple<Program, CompilationOptions, List<Path>> {
|
||||||
|
val compilationTargetName = compTarget.name
|
||||||
|
println("Compiler target: $compilationTargetName. Parsing...")
|
||||||
|
val importer = ModuleImporter()
|
||||||
|
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||||
|
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget)
|
||||||
|
bf.program = programAst
|
||||||
|
importer.importModule(programAst, filepath, compTarget, compilationTargetName)
|
||||||
|
errors.report()
|
||||||
|
|
||||||
|
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
|
||||||
|
val compilerOptions = determineCompilationOptions(programAst, compTarget)
|
||||||
|
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||||
|
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||||
|
|
||||||
|
// depending on the machine and compiler options we may have to include some libraries
|
||||||
|
for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName))
|
||||||
|
importer.importLibraryModule(programAst, lib, compTarget, compilationTargetName)
|
||||||
|
|
||||||
|
// always import prog8_lib and math
|
||||||
|
importer.importLibraryModule(programAst, "math", compTarget, compilationTargetName)
|
||||||
|
importer.importLibraryModule(programAst, "prog8_lib", compTarget, compilationTargetName)
|
||||||
|
errors.report()
|
||||||
|
return Triple(programAst, compilerOptions, importedFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
|
||||||
|
val mainModule = program.mainModule
|
||||||
|
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
|
||||||
|
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||||
|
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
|
||||||
|
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||||
|
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 noSysInit = allOptions.any { it.name == "no_sysinit" }
|
||||||
|
var 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zpType==ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
|
||||||
|
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
|
||||||
|
zpType = ZeropageType.BASICSAFE
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
|
||||||
|
System.err.println("invalid output type $outputType")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
|
||||||
|
System.err.println("invalid launcher type $launcherType")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompilationOptions(
|
||||||
|
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
||||||
|
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
||||||
|
zpType, zpReserved, floatsEnabled, noSysInit,
|
||||||
|
compTarget
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
|
// perform initial syntax checks and processings
|
||||||
|
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||||
|
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
|
||||||
|
errors.report()
|
||||||
|
programAst.constantFold(errors, compilerOptions.compTarget)
|
||||||
|
errors.report()
|
||||||
|
programAst.reorderStatements(errors)
|
||||||
|
errors.report()
|
||||||
|
programAst.addTypecasts(errors)
|
||||||
|
errors.report()
|
||||||
|
programAst.variousCleanups()
|
||||||
|
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
||||||
|
errors.report()
|
||||||
|
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
|
||||||
|
errors.report()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
|
||||||
|
// optimize the parse tree
|
||||||
|
println("Optimizing...")
|
||||||
|
while (true) {
|
||||||
|
// keep optimizing expressions and statements until no more steps remain
|
||||||
|
val optsDone1 = programAst.simplifyExpressions()
|
||||||
|
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
|
||||||
|
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget, ::loadAsmIncludeFile)
|
||||||
|
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
|
||||||
|
errors.report()
|
||||||
|
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
val remover = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile)
|
||||||
|
remover.visit(programAst)
|
||||||
|
remover.applyModifications()
|
||||||
|
errors.report()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
|
programAst.addTypecasts(errors)
|
||||||
|
errors.report()
|
||||||
|
programAst.variousCleanups()
|
||||||
|
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
|
||||||
|
errors.report()
|
||||||
|
val callGraph = CallGraph(programAst, ::loadAsmIncludeFile)
|
||||||
|
callGraph.checkRecursiveCalls(errors)
|
||||||
|
errors.report()
|
||||||
|
programAst.verifyFunctionArgTypes()
|
||||||
|
programAst.moveMainAndStartToFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeAssembly(programAst: Program,
|
||||||
|
errors: IErrorReporter,
|
||||||
|
outputDir: Path,
|
||||||
|
compilerOptions: CompilationOptions): String {
|
||||||
|
// asm generation directly from the Ast,
|
||||||
|
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
|
||||||
|
errors.report()
|
||||||
|
|
||||||
|
// printAst(programAst)
|
||||||
|
|
||||||
|
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
||||||
|
val assembly = asmGeneratorFor(compilerOptions.compTarget,
|
||||||
|
programAst,
|
||||||
|
errors,
|
||||||
|
compilerOptions.compTarget.machine.zeropage,
|
||||||
|
compilerOptions,
|
||||||
|
outputDir).compileToAssembly()
|
||||||
|
assembly.assemble(compilerOptions)
|
||||||
|
errors.report()
|
||||||
|
return assembly.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun printAst(programAst: Program) {
|
||||||
|
println()
|
||||||
|
val printer = AstToSourceCode(::print, programAst)
|
||||||
|
printer.visit(programAst)
|
||||||
|
println()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadAsmIncludeFile(filename: String, source: Path): String {
|
fun loadAsmIncludeFile(filename: String, source: Path): String {
|
||||||
return if (filename.startsWith("library:")) {
|
return if (filename.startsWith("library:")) {
|
||||||
val resource = tryGetEmbeddedResource(filename.substring(8))
|
val resource = tryGetEmbeddedResource(filename.substring(8))
|
||||||
?: throw IllegalArgumentException("library file '$filename' not found")
|
?: throw IllegalArgumentException("library file '$filename' not found")
|
||||||
resource.bufferedReader().use { it.readText() }
|
resource.bufferedReader().use { it.readText() }
|
||||||
} else {
|
} else {
|
||||||
// first try in the isSameAs folder as where the containing file was imported from
|
// first try in the isSameAs folder as where the containing file was imported from
|
||||||
|
@ -1,9 +1,18 @@
|
|||||||
package prog8.ast.base
|
package prog8.compiler
|
||||||
|
|
||||||
|
import prog8.ast.base.Position
|
||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
|
|
||||||
|
|
||||||
class ErrorReporter {
|
interface IErrorReporter {
|
||||||
|
fun err(msg: String, position: Position)
|
||||||
|
fun warn(msg: String, position: Position)
|
||||||
|
fun isEmpty(): Boolean
|
||||||
|
fun report()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class ErrorReporter: IErrorReporter {
|
||||||
private enum class MessageSeverity {
|
private enum class MessageSeverity {
|
||||||
WARNING,
|
WARNING,
|
||||||
ERROR
|
ERROR
|
||||||
@ -13,10 +22,14 @@ class ErrorReporter {
|
|||||||
private val messages = mutableListOf<CompilerMessage>()
|
private val messages = mutableListOf<CompilerMessage>()
|
||||||
private val alreadyReportedMessages = mutableSetOf<String>()
|
private val alreadyReportedMessages = mutableSetOf<String>()
|
||||||
|
|
||||||
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
|
override fun err(msg: String, position: Position) {
|
||||||
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
|
messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
|
||||||
|
}
|
||||||
|
override fun warn(msg: String, position: Position) {
|
||||||
|
messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
|
||||||
|
}
|
||||||
|
|
||||||
fun handle() {
|
override fun report() {
|
||||||
var numErrors = 0
|
var numErrors = 0
|
||||||
var numWarnings = 0
|
var numWarnings = 0
|
||||||
messages.forEach {
|
messages.forEach {
|
||||||
@ -24,7 +37,7 @@ class ErrorReporter {
|
|||||||
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
|
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
|
||||||
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
|
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
|
||||||
}
|
}
|
||||||
val msg = "${it.position} ${it.severity} ${it.message}".trim()
|
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
|
||||||
if(msg !in alreadyReportedMessages) {
|
if(msg !in alreadyReportedMessages) {
|
||||||
System.err.println(msg)
|
System.err.println(msg)
|
||||||
alreadyReportedMessages.add(msg)
|
alreadyReportedMessages.add(msg)
|
||||||
@ -40,5 +53,5 @@ class ErrorReporter {
|
|||||||
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isEmpty() = messages.isEmpty()
|
override fun isEmpty() = messages.isEmpty()
|
||||||
}
|
}
|
@ -1,216 +0,0 @@
|
|||||||
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(errors)
|
|
||||||
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.")
|
|
||||||
|
|
||||||
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
|
|
||||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
|
|
||||||
importer.importLibraryModule(programAst, "c64lib")
|
|
||||||
importer.importLibraryModule(programAst, "c64utils")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
remover.visit(programAst)
|
|
||||||
remover.applyModifications()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
|
||||||
programAst.transformAssignments(errors)
|
|
||||||
errors.handle()
|
|
||||||
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,
|
|
||||||
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
|
|
||||||
programAst.processAstBeforeAsmGeneration(errors)
|
|
||||||
errors.handle()
|
|
||||||
|
|
||||||
// printAst(programAst)
|
|
||||||
|
|
||||||
val assembly = CompilationTarget.asmGenerator(
|
|
||||||
programAst,
|
|
||||||
errors,
|
|
||||||
zeropage,
|
|
||||||
compilerOptions,
|
|
||||||
outputDir).compileToAssembly(optimize)
|
|
||||||
assembly.assemble(compilerOptions)
|
|
||||||
errors.handle()
|
|
||||||
return assembly.name
|
|
||||||
}
|
|
||||||
|
|
||||||
fun printAst(programAst: Program) {
|
|
||||||
println()
|
|
||||||
val printer = AstToSourceCode(::print, programAst)
|
|
||||||
printer.visit(programAst)
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
|
|
@ -8,6 +8,12 @@ class ZeropageDepletedError(message: String) : Exception(message)
|
|||||||
|
|
||||||
abstract class Zeropage(protected val options: CompilationOptions) {
|
abstract class Zeropage(protected val options: CompilationOptions) {
|
||||||
|
|
||||||
|
abstract val SCRATCH_B1 : Int // temp storage for a single byte
|
||||||
|
abstract val SCRATCH_REG : Int // temp storage for a register
|
||||||
|
abstract val SCRATCH_W1 : Int // temp storage 1 for a word $fb+$fc
|
||||||
|
abstract val SCRATCH_W2 : Int // temp storage 2 for a word $fb+$fc
|
||||||
|
|
||||||
|
|
||||||
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
|
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
|
||||||
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
|
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
|
||||||
|
|
||||||
@ -15,8 +21,8 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
|
|
||||||
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
||||||
|
|
||||||
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
|
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
|
||||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
|
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
|
||||||
|
|
||||||
if(options.zeropage==ZeropageType.DONTUSE)
|
if(options.zeropage==ZeropageType.DONTUSE)
|
||||||
throw CompilerException("zero page usage has been disabled")
|
throw CompilerException("zero page usage has been disabled")
|
||||||
@ -39,13 +45,13 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
|
|
||||||
if(free.size > 0) {
|
if(free.size > 0) {
|
||||||
if(size==1) {
|
if(size==1) {
|
||||||
for(candidate in free.min()!! .. free.max()!!+1) {
|
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
|
||||||
if(loneByte(candidate))
|
if(loneByte(candidate))
|
||||||
return makeAllocation(candidate, 1, datatype, scopedname)
|
return makeAllocation(candidate, 1, datatype, scopedname)
|
||||||
}
|
}
|
||||||
return makeAllocation(free[0], 1, datatype, scopedname)
|
return makeAllocation(free[0], 1, datatype, scopedname)
|
||||||
}
|
}
|
||||||
for(candidate in free.min()!! .. free.max()!!+1) {
|
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
|
||||||
if (sequentialFree(candidate, size))
|
if (sequentialFree(candidate, size))
|
||||||
return makeAllocation(candidate, size, datatype, scopedname)
|
return makeAllocation(candidate, size, datatype, scopedname)
|
||||||
}
|
}
|
||||||
@ -58,18 +64,10 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
|
|
||||||
private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int {
|
private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int {
|
||||||
free.removeAll(address until address+size)
|
free.removeAll(address until address+size)
|
||||||
allocations[address] = Pair(name ?: "<unnamed>", datatype)
|
allocations[address] = (name ?: "<unnamed>") to datatype
|
||||||
return address
|
return address
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free
|
private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free
|
||||||
private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
|
private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
|
||||||
|
|
||||||
enum class ExitProgramStrategy {
|
|
||||||
CLEAN_EXIT,
|
|
||||||
SYSTEM_RESET
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract val exitProgramStrategy: ExitProgramStrategy
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
97
compiler/src/prog8/compiler/astprocessing/AstExtensions.kt
Normal file
97
compiler/src/prog8/compiler/astprocessing/AstExtensions.kt
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.FatalAstException
|
||||||
|
import prog8.ast.statements.Directive
|
||||||
|
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||||
|
import prog8.compiler.CompilationOptions
|
||||||
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.target.ICompilationTarget
|
||||||
|
|
||||||
|
|
||||||
|
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||||
|
val checker = AstChecker(this, compilerOptions, errors, compTarget)
|
||||||
|
checker.visit(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||||
|
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
|
||||||
|
fixer.visit(this)
|
||||||
|
fixer.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.reorderStatements(errors: IErrorReporter) {
|
||||||
|
val reorder = StatementReorderer(this, errors)
|
||||||
|
reorder.visit(this)
|
||||||
|
reorder.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.addTypecasts(errors: IErrorReporter) {
|
||||||
|
val caster = TypecastsAdder(this, errors)
|
||||||
|
caster.visit(this)
|
||||||
|
caster.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.verifyFunctionArgTypes() {
|
||||||
|
val fixer = VerifyFunctionArgTypes(this)
|
||||||
|
fixer.visit(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||||
|
|
||||||
|
val checker2 = AstIdentifiersChecker(this, errors, compTarget)
|
||||||
|
checker2.visit(this)
|
||||||
|
|
||||||
|
if(errors.isEmpty()) {
|
||||||
|
val transforms = AstVariousTransforms(this)
|
||||||
|
transforms.visit(this)
|
||||||
|
transforms.applyModifications()
|
||||||
|
val lit2decl = LiteralsToAutoVars(this)
|
||||||
|
lit2decl.visit(this)
|
||||||
|
lit2decl.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modules.map { it.name }.toSet().size != modules.size) {
|
||||||
|
throw FatalAstException("modules should all be unique")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.variousCleanups() {
|
||||||
|
val process = VariousCleanups()
|
||||||
|
process.visit(this)
|
||||||
|
process.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.moveMainAndStartToFirst() {
|
||||||
|
// the module containing the program entrypoint is moved to the first in the sequence.
|
||||||
|
// the "main" block containing the entrypoint is moved to the top in there,
|
||||||
|
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
|
||||||
|
|
||||||
|
val directives = modules[0].statements.filterIsInstance<Directive>()
|
||||||
|
val start = this.entrypoint()
|
||||||
|
if(start!=null) {
|
||||||
|
val mod = start.definingModule()
|
||||||
|
val block = start.definingBlock()
|
||||||
|
if(!modules.remove(mod))
|
||||||
|
throw FatalAstException("module wrong")
|
||||||
|
modules.add(0, mod)
|
||||||
|
mod.remove(block)
|
||||||
|
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
||||||
|
if(afterDirective<0)
|
||||||
|
mod.statements.add(block)
|
||||||
|
else
|
||||||
|
mod.statements.add(afterDirective, block)
|
||||||
|
block.remove(start)
|
||||||
|
afterDirective = block.statements.indexOfFirst { it !is Directive }
|
||||||
|
if(afterDirective<0)
|
||||||
|
block.statements.add(start)
|
||||||
|
else
|
||||||
|
block.statements.add(afterDirective, start)
|
||||||
|
|
||||||
|
// overwrite the directives in the module containing the entrypoint
|
||||||
|
for(directive in directives) {
|
||||||
|
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
|
||||||
|
modules[0].statements.add(0, directive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,20 @@
|
|||||||
package prog8.ast.processing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
import prog8.ast.Module
|
import prog8.ast.Module
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.base.NumericDatatypes
|
||||||
|
import prog8.ast.base.Position
|
||||||
|
import prog8.ast.expressions.ArrayLiteralValue
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.target.CompilationTarget
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.functions.BuiltinFunctions
|
||||||
|
import prog8.compiler.target.ICompilationTarget
|
||||||
|
|
||||||
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
|
internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
|
||||||
private var blocks = mutableMapOf<String, Block>()
|
private var blocks = mutableMapOf<String, Block>()
|
||||||
|
|
||||||
private fun nameError(name: String, position: Position, existing: Statement) {
|
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||||
@ -22,22 +28,42 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(block: Block) {
|
override fun visit(block: Block) {
|
||||||
|
if(block.name in compTarget.machine.opcodeNames)
|
||||||
|
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
|
||||||
|
|
||||||
val existing = blocks[block.name]
|
val existing = blocks[block.name]
|
||||||
if(existing!=null)
|
if(existing!=null)
|
||||||
nameError(block.name, block.position, existing)
|
nameError(block.name, block.position, existing)
|
||||||
else
|
else
|
||||||
blocks[block.name] = block
|
blocks[block.name] = block
|
||||||
|
|
||||||
|
if(!block.isInLibrary) {
|
||||||
|
val libraries = program.modules.filter { it.isLibraryModule }
|
||||||
|
val libraryBlockNames = libraries.flatMap { it.statements.filterIsInstance<Block>().map { b -> b.name } }
|
||||||
|
if(block.name in libraryBlockNames)
|
||||||
|
errors.err("block is already defined in an included library module", block.position)
|
||||||
|
}
|
||||||
|
|
||||||
super.visit(block)
|
super.visit(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visit(directive: Directive) {
|
||||||
|
if(directive.directive=="%target") {
|
||||||
|
val compatibleTarget = directive.args.single().name
|
||||||
|
if (compatibleTarget != compTarget.name)
|
||||||
|
errors.err("module's compilation target ($compatibleTarget) differs from active target (${compTarget.name})", directive.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.visit(directive)
|
||||||
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl) {
|
override fun visit(decl: VarDecl) {
|
||||||
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||||
|
|
||||||
if(decl.name in BuiltinFunctions)
|
if(decl.name in BuiltinFunctions)
|
||||||
errors.err("builtin function cannot be redefined", decl.position)
|
errors.err("builtin function cannot be redefined", decl.position)
|
||||||
|
|
||||||
if(decl.name in CompilationTarget.machine.opcodeNames)
|
if(decl.name in compTarget.machine.opcodeNames)
|
||||||
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
||||||
|
|
||||||
if(decl.datatype==DataType.STRUCT) {
|
if(decl.datatype==DataType.STRUCT) {
|
||||||
@ -67,11 +93,16 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
if (existing != null && existing !== decl)
|
if (existing != null && existing !== decl)
|
||||||
nameError(decl.name, decl.position, existing)
|
nameError(decl.name, decl.position, existing)
|
||||||
|
|
||||||
|
if(decl.definingBlock().name==decl.name)
|
||||||
|
nameError(decl.name, decl.position, decl.definingBlock())
|
||||||
|
if(decl.definingSubroutine()?.name==decl.name)
|
||||||
|
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
|
||||||
|
|
||||||
super.visit(decl)
|
super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine) {
|
override fun visit(subroutine: Subroutine) {
|
||||||
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
|
if(subroutine.name in compTarget.machine.opcodeNames) {
|
||||||
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
||||||
} else if(subroutine.name in BuiltinFunctions) {
|
} else if(subroutine.name in BuiltinFunctions) {
|
||||||
// the builtin functions can't be redefined
|
// the builtin functions can't be redefined
|
||||||
@ -85,14 +116,6 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
if (existing != null && existing !== subroutine)
|
if (existing != null && existing !== subroutine)
|
||||||
nameError(subroutine.name, subroutine.position, existing)
|
nameError(subroutine.name, subroutine.position, existing)
|
||||||
|
|
||||||
// does the parameter redefine a variable declared elsewhere?
|
|
||||||
for(param in subroutine.parameters) {
|
|
||||||
val existingVar = subroutine.lookup(listOf(param.name), subroutine)
|
|
||||||
if (existingVar != null && existingVar.parent !== subroutine) {
|
|
||||||
nameError(param.name, param.position, existingVar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
|
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
|
||||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||||
@ -102,9 +125,12 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
val labelOrVar = subroutine.getLabelOrVariable(name)
|
val labelOrVar = subroutine.getLabelOrVariable(name)
|
||||||
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
|
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
|
||||||
nameError(name, labelOrVar.position, subroutine)
|
nameError(name, labelOrVar.position, subroutine)
|
||||||
val sub = subroutine.statements.singleOrNull { it is Subroutine && it.name==name}
|
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
|
||||||
if(sub!=null)
|
if(sub!=null)
|
||||||
nameError(name, sub.position, subroutine)
|
nameError(name, subroutine.position, sub)
|
||||||
|
val block = program.allBlocks().firstOrNull { it.name==name }
|
||||||
|
if(block!=null)
|
||||||
|
nameError(name, subroutine.position, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||||
@ -116,7 +142,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(label: Label) {
|
override fun visit(label: Label) {
|
||||||
if(label.name in CompilationTarget.machine.opcodeNames)
|
if(label.name in compTarget.machine.opcodeNames)
|
||||||
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
||||||
|
|
||||||
if(label.name in BuiltinFunctions) {
|
if(label.name in BuiltinFunctions) {
|
||||||
@ -138,8 +164,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(string: StringLiteralValue) {
|
override fun visit(string: StringLiteralValue) {
|
||||||
if (string.value.length !in 1..255)
|
if (string.value.length > 255)
|
||||||
errors.err("string literal length must be between 1 and 255", string.position)
|
errors.err("string literal length max is 255", string.position)
|
||||||
|
|
||||||
super.visit(string)
|
super.visit(string)
|
||||||
}
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.expressions.BinaryExpression
|
||||||
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
|
import prog8.ast.statements.AnonymousScope
|
||||||
|
import prog8.ast.statements.ParameterVarDecl
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
|
import prog8.ast.statements.VarDecl
|
||||||
|
import prog8.ast.walk.AstWalker
|
||||||
|
import prog8.ast.walk.IAstModification
|
||||||
|
|
||||||
|
|
||||||
|
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
|
// is it a struct variable? then define all its struct members as mangled names,
|
||||||
|
// and include the original decl as well.
|
||||||
|
if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) {
|
||||||
|
val decls = decl.flattenStructMembers()
|
||||||
|
decls.add(decl)
|
||||||
|
val result = AnonymousScope(decls, decl.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
decl, result, parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
|
// For non-kernal subroutines and non-asm parameters:
|
||||||
|
// inject subroutine params as local variables (if they're not there yet).
|
||||||
|
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||||
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||||
|
if(subroutine.asmAddress==null) {
|
||||||
|
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
||||||
|
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||||
|
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
||||||
|
return subroutine.parameters
|
||||||
|
.filter { it.name !in namesInSub }
|
||||||
|
.map {
|
||||||
|
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||||
|
IAstModification.InsertFirst(vardecl, subroutine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
val leftStr = expr.left as? StringLiteralValue
|
||||||
|
val rightStr = expr.right as? StringLiteralValue
|
||||||
|
if(expr.operator == "+") {
|
||||||
|
val concatenatedString = concatString(expr)
|
||||||
|
if(concatenatedString!=null)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, concatenatedString, parent))
|
||||||
|
}
|
||||||
|
else if(expr.operator == "*") {
|
||||||
|
if (leftStr!=null) {
|
||||||
|
val amount = expr.right.constValue(program)
|
||||||
|
if(amount!=null) {
|
||||||
|
val string = leftStr.value.repeat(amount.number.toInt())
|
||||||
|
val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (rightStr!=null) {
|
||||||
|
val amount = expr.right.constValue(program)
|
||||||
|
if(amount!=null) {
|
||||||
|
val string = rightStr.value.repeat(amount.number.toInt())
|
||||||
|
val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
|
||||||
|
val rightStrval = expr.right as? StringLiteralValue
|
||||||
|
val leftStrval = expr.left as? StringLiteralValue
|
||||||
|
return when {
|
||||||
|
expr.operator!="+" -> null
|
||||||
|
expr.left is BinaryExpression && rightStrval!=null -> {
|
||||||
|
val subStrVal = concatString(expr.left as BinaryExpression)
|
||||||
|
if(subStrVal==null)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position)
|
||||||
|
}
|
||||||
|
expr.right is BinaryExpression && leftStrval!=null -> {
|
||||||
|
val subStrVal = concatString(expr.right as BinaryExpression)
|
||||||
|
if(subStrVal==null)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position)
|
||||||
|
}
|
||||||
|
leftStrval!=null && rightStrval!=null -> {
|
||||||
|
StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.expressions.ArrayLiteralValue
|
||||||
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
|
import prog8.ast.statements.VarDecl
|
||||||
|
import prog8.ast.statements.WhenChoice
|
||||||
|
import prog8.ast.walk.AstWalker
|
||||||
|
import prog8.ast.walk.IAstModification
|
||||||
|
|
||||||
|
|
||||||
|
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
||||||
|
// replace the literal string by a identifier reference to the interned string
|
||||||
|
val scopedName = program.internString(string)
|
||||||
|
val identifier = IdentifierReference(scopedName, string.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||||
|
val vardecl = array.parent as? VarDecl
|
||||||
|
if(vardecl!=null) {
|
||||||
|
// adjust the datatype of the array (to an educated guess)
|
||||||
|
val arrayDt = array.type
|
||||||
|
if(!arrayDt.istype(vardecl.datatype)) {
|
||||||
|
val cast = array.cast(vardecl.datatype)
|
||||||
|
if (cast != null && cast !== array)
|
||||||
|
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val arrayDt = array.guessDatatype(program)
|
||||||
|
if(arrayDt.isKnown) {
|
||||||
|
// this array literal is part of an expression, turn it into an identifier reference
|
||||||
|
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||||
|
if(litval2!=null) {
|
||||||
|
val vardecl2 = VarDecl.createAuto(litval2)
|
||||||
|
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(array, identifier, parent),
|
||||||
|
IAstModification.InsertFirst(vardecl2, array.definingScope())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package prog8.ast.processing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
441
compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt
Normal file
441
compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
|
import prog8.ast.*
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.ast.walk.AstWalker
|
||||||
|
import prog8.ast.walk.IAstModification
|
||||||
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
|
||||||
|
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : 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 block and module, most directives and vardecls are moved to the top. (not in subroutines!)
|
||||||
|
// - 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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val subs = subroutine.statements.filterIsInstance<Subroutine>()
|
||||||
|
if(subs.isNotEmpty()) {
|
||||||
|
// all subroutines defined within this subroutine are moved to the end
|
||||||
|
return subs.map { IAstModification.Remove(it, subroutine) } +
|
||||||
|
subs.map { IAstModification.InsertLast(it, subroutine) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
|
||||||
|
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
||||||
|
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
||||||
|
// rewrite pointervar[index] into @(pointervar+index)
|
||||||
|
val indexer = arrayIndexedExpression.indexer
|
||||||
|
val index = (indexer.indexNum ?: indexer.indexVar)!!
|
||||||
|
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", index, arrayIndexedExpression.position)
|
||||||
|
return if(parent is AssignTarget) {
|
||||||
|
// we're part of the target of an assignment, we have to actually change the assign target itself
|
||||||
|
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
|
||||||
|
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
|
||||||
|
} else {
|
||||||
|
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
arrayIndexedExpression.indexer.indexNum = expr2
|
||||||
|
arrayIndexedExpression.indexer.origExpression = null
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
arrayIndexedExpression.indexer.indexVar = expr2
|
||||||
|
arrayIndexedExpression.indexer.origExpression = null
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
is Expression -> {
|
||||||
|
// replace complex indexing with a temp variable
|
||||||
|
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||||
|
}
|
||||||
|
else -> return noModifications
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
// when using a simple bit shift and assigning it to a variable of a different type,
|
||||||
|
// try to make the bit shifting 'wide enough' to fall into the variable's type.
|
||||||
|
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
|
||||||
|
if(expr.operator=="<<" || expr.operator==">>") {
|
||||||
|
val leftDt = expr.left.inferType(program)
|
||||||
|
when (parent) {
|
||||||
|
is Assignment -> {
|
||||||
|
val targetDt = parent.target.inferType(program)
|
||||||
|
if(leftDt != targetDt) {
|
||||||
|
val cast = TypecastExpression(expr.left, targetDt.typeOrElse(DataType.STRUCT), true, parent.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is VarDecl -> {
|
||||||
|
if(!leftDt.istype(parent.datatype)) {
|
||||||
|
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IFunctionCall -> {
|
||||||
|
val argnum = parent.args.indexOf(expr)
|
||||||
|
when (val callee = parent.target.targetStatement(program)) {
|
||||||
|
is Subroutine -> {
|
||||||
|
val paramType = callee.parameters[argnum].type
|
||||||
|
if(leftDt isAssignableTo paramType) {
|
||||||
|
val cast = TypecastExpression(expr.left, paramType, true, parent.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is BuiltinFunctionStatementPlaceholder -> {
|
||||||
|
val func = BuiltinFunctions.getValue(callee.name)
|
||||||
|
val paramTypes = func.parameters[argnum].possibleDatatypes
|
||||||
|
for(type in paramTypes) {
|
||||||
|
if(leftDt isAssignableTo type) {
|
||||||
|
val cast = TypecastExpression(expr.left, type, true, parent.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw FatalAstException("weird callee")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> return noModifications
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(expr.operator in logicalOperators) {
|
||||||
|
// make sure that logical expressions like "var and other-logical-expression
|
||||||
|
// is rewritten as "var!=0 and other-logical-expression", to avoid bitwise boolean and
|
||||||
|
// generating the wrong results later
|
||||||
|
|
||||||
|
fun wrapped(expr: Expression): Expression =
|
||||||
|
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0, expr.position), expr.position)
|
||||||
|
|
||||||
|
fun isLogicalExpr(expr: Expression?): Boolean {
|
||||||
|
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
|
||||||
|
return true
|
||||||
|
if(expr is PrefixExpression && expr.operator in logicalOperators)
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return if(isLogicalExpr(expr.left)) {
|
||||||
|
if(isLogicalExpr(expr.right))
|
||||||
|
noModifications
|
||||||
|
else
|
||||||
|
listOf(IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr))
|
||||||
|
} else {
|
||||||
|
if(isLogicalExpr(expr.right))
|
||||||
|
listOf(IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr))
|
||||||
|
else {
|
||||||
|
listOf(
|
||||||
|
IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr),
|
||||||
|
IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
|
||||||
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
val subroutine = expr.definingSubroutine()!!
|
||||||
|
val statement = expr.containingStatement()
|
||||||
|
val indexerVarPrefix = "prog8_autovar_index_"
|
||||||
|
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
|
||||||
|
|
||||||
|
// TODO make this a bit smarter so it can reuse indexer variables. BUT BEWARE of scoping+initialization problems then
|
||||||
|
// add another loop index var to be used for this expression
|
||||||
|
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
|
||||||
|
val indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer)
|
||||||
|
repo.add(indexerVar)
|
||||||
|
// create the indexer var at block level scope
|
||||||
|
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
|
||||||
|
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
|
||||||
|
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
|
||||||
|
|
||||||
|
// replace the indexer with just the variable
|
||||||
|
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||||
|
val indexerExpression = expr.indexer.origExpression!!
|
||||||
|
val target = AssignTarget(IdentifierReference(listOf(indexerVar.name), indexerExpression.position), null, null, indexerExpression.position)
|
||||||
|
val assign = Assignment(target, indexerExpression, indexerExpression.position)
|
||||||
|
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
|
||||||
|
modifications.add(IAstModification.SetExpression( {
|
||||||
|
expr.indexer.indexVar = it as IdentifierReference
|
||||||
|
expr.indexer.indexNum = null
|
||||||
|
expr.indexer.origExpression = null
|
||||||
|
}, target.identifier!!.copy(), expr.indexer))
|
||||||
|
|
||||||
|
return modifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||||
|
it.first?.first() ?: Int.MAX_VALUE
|
||||||
|
}
|
||||||
|
whenStatement.choices.clear()
|
||||||
|
choices.mapTo(whenStatement.choices) { it.second }
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
|
val declValue = decl.value
|
||||||
|
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||||
|
val declConstValue = declValue.constValue(program)
|
||||||
|
if(declConstValue==null) {
|
||||||
|
// move the vardecl (without value) to the scope and replace this with a regular assignment
|
||||||
|
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
|
||||||
|
if(decl.datatype!=DataType.FLOAT) {
|
||||||
|
decl.value = null
|
||||||
|
decl.allowInitializeWithZero = false
|
||||||
|
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||||
|
val assign = Assignment(target, declValue, decl.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(decl, assign, parent),
|
||||||
|
IAstModification.InsertFirst(decl, decl.definingScope())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
val valueType = assignment.value.inferType(program)
|
||||||
|
val targetType = assignment.target.inferType(program)
|
||||||
|
|
||||||
|
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
|
||||||
|
if (assignment.value is ArrayLiteralValue) {
|
||||||
|
errors.err("cannot assign non-const array value, use separate assignment per field", assignment.position)
|
||||||
|
} else {
|
||||||
|
return copyStructValue(assignment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
|
||||||
|
if (assignment.value is ArrayLiteralValue) {
|
||||||
|
errors.err("cannot assign non-const array value, use separate assignment per element", assignment.position)
|
||||||
|
} else {
|
||||||
|
return copyArrayValue(assignment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 copyArrayValue(assign: Assignment): List<IAstModification> {
|
||||||
|
val identifier = assign.target.identifier!!
|
||||||
|
val targetVar = identifier.targetVarDecl(program)!!
|
||||||
|
|
||||||
|
if(targetVar.arraysize==null)
|
||||||
|
errors.err("array has no defined size", assign.position)
|
||||||
|
|
||||||
|
val sourceIdent = assign.value as IdentifierReference
|
||||||
|
val sourceVar = sourceIdent.targetVarDecl(program)!!
|
||||||
|
if(!sourceVar.isArray) {
|
||||||
|
errors.err("value must be an array", sourceIdent.position)
|
||||||
|
} else {
|
||||||
|
if (sourceVar.arraysize!!.constIndex() != targetVar.arraysize!!.constIndex())
|
||||||
|
errors.err("element count mismatch", assign.position)
|
||||||
|
if (sourceVar.datatype != targetVar.datatype)
|
||||||
|
errors.err("element type mismatch", assign.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!errors.isEmpty())
|
||||||
|
return emptyList()
|
||||||
|
|
||||||
|
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
|
||||||
|
mutableListOf(
|
||||||
|
AddressOf(sourceIdent, assign.position),
|
||||||
|
AddressOf(identifier, assign.position),
|
||||||
|
NumericLiteralValue.optimalInteger(targetVar.arraysize!!.constIndex()!!, assign.position)
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
assign.position
|
||||||
|
)
|
||||||
|
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyStructValue(structAssignment: Assignment): List<IAstModification> {
|
||||||
|
val identifier = structAssignment.target.identifier!!
|
||||||
|
val targetVar = identifier.targetVarDecl(program)!!
|
||||||
|
val struct = targetVar.struct!!
|
||||||
|
when (structAssignment.value) {
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program)!!
|
||||||
|
val memsize = struct.memsize(program.memsizer)
|
||||||
|
when {
|
||||||
|
sourceVar.struct!=null -> {
|
||||||
|
// struct memberwise copy
|
||||||
|
val sourceStruct = sourceVar.struct!!
|
||||||
|
if(sourceStruct!==targetVar.struct) {
|
||||||
|
errors.err("struct type mismatch", structAssignment.position)
|
||||||
|
return listOf()
|
||||||
|
}
|
||||||
|
if(struct.statements.size!=sourceStruct.statements.size) {
|
||||||
|
errors.err("struct element count mismatch", structAssignment.position)
|
||||||
|
return listOf()
|
||||||
|
}
|
||||||
|
if(memsize!=sourceStruct.memsize(program.memsizer)) {
|
||||||
|
errors.err("memory size mismatch", structAssignment.position)
|
||||||
|
return listOf()
|
||||||
|
}
|
||||||
|
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
|
||||||
|
mutableListOf(
|
||||||
|
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
|
||||||
|
AddressOf(identifier, structAssignment.position),
|
||||||
|
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
structAssignment.position
|
||||||
|
)
|
||||||
|
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
|
||||||
|
}
|
||||||
|
sourceVar.isArray -> {
|
||||||
|
val array = sourceVar.value as ArrayLiteralValue
|
||||||
|
if(struct.statements.size!=array.value.size) {
|
||||||
|
errors.err("struct element count mismatch", structAssignment.position)
|
||||||
|
return listOf()
|
||||||
|
}
|
||||||
|
if(memsize!=array.memsize(program.memsizer)) {
|
||||||
|
errors.err("memory size mismatch", structAssignment.position)
|
||||||
|
return listOf()
|
||||||
|
}
|
||||||
|
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
|
||||||
|
mutableListOf(
|
||||||
|
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
|
||||||
|
AddressOf(identifier, structAssignment.position),
|
||||||
|
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
structAssignment.position
|
||||||
|
)
|
||||||
|
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw FatalAstException("can only assign arrays or structs to structs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ArrayLiteralValue -> {
|
||||||
|
throw IllegalArgumentException("not going to do a structLv assignment here")
|
||||||
|
}
|
||||||
|
else -> throw FatalAstException("strange struct value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,16 +1,18 @@
|
|||||||
package prog8.ast.processing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
import prog8.ast.INameScope
|
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.ast.walk.AstWalker
|
||||||
|
import prog8.ast.walk.IAstModification
|
||||||
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
|
||||||
class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
||||||
/*
|
/*
|
||||||
* Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
|
* 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)
|
* (this includes function call arguments)
|
||||||
@ -18,6 +20,26 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
|||||||
|
|
||||||
private val noModifications = emptyList<IAstModification>()
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
|
val declValue = decl.value
|
||||||
|
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
|
||||||
|
val valueDt = declValue.inferType(program)
|
||||||
|
if(!valueDt.istype(decl.datatype)) {
|
||||||
|
|
||||||
|
// don't add a typecast on an array initializer value
|
||||||
|
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
|
||||||
|
return noModifications
|
||||||
|
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
declValue,
|
||||||
|
TypecastExpression(declValue, decl.datatype, true, declValue.position),
|
||||||
|
decl
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
val leftDt = expr.left.inferType(program)
|
val leftDt = expr.left.inferType(program)
|
||||||
val rightDt = expr.right.inferType(program)
|
val rightDt = expr.right.inferType(program)
|
||||||
@ -40,19 +62,27 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
|||||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
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
|
// see if a typecast is needed to convert the value's type into the proper target type
|
||||||
val valueItype = assignment.value.inferType(program)
|
val valueItype = assignment.value.inferType(program)
|
||||||
val targetItype = assignment.target.inferType(program, assignment)
|
val targetItype = assignment.target.inferType(program)
|
||||||
if(targetItype.isKnown && valueItype.isKnown) {
|
if(targetItype.isKnown && valueItype.isKnown) {
|
||||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||||
if (valuetype != targettype) {
|
if (valuetype != targettype) {
|
||||||
if (valuetype isAssignableTo targettype) {
|
if (valuetype isAssignableTo targettype) {
|
||||||
|
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
|
||||||
|
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
|
||||||
|
return noModifications
|
||||||
return listOf(IAstModification.ReplaceNode(
|
return listOf(IAstModification.ReplaceNode(
|
||||||
assignment.value,
|
assignment.value,
|
||||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||||
assignment))
|
assignment))
|
||||||
} else {
|
} else {
|
||||||
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> =
|
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> {
|
||||||
listOf(IAstModification.ReplaceNode(cvalue, cvalue.cast(targettype), cvalue.parent))
|
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)
|
val cvalue = assignment.value.constValue(program)
|
||||||
if(cvalue!=null) {
|
if(cvalue!=null) {
|
||||||
val number = cvalue.number.toDouble()
|
val number = cvalue.number.toDouble()
|
||||||
@ -78,46 +108,45 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope())
|
return afterFunctionCallArgs(functionCallStatement)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||||
return afterFunctionCallArgs(functionCall, functionCall.definingScope())
|
return afterFunctionCallArgs(functionCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
|
private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
|
||||||
// see if a typecast is needed to convert the arguments into the required parameter's type
|
// see if a typecast is needed to convert the arguments into the required parameter's type
|
||||||
val modifications = mutableListOf<IAstModification>()
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
|
||||||
when(val sub = call.target.targetStatement(scope)) {
|
when(val sub = call.target.targetStatement(program)) {
|
||||||
is Subroutine -> {
|
is Subroutine -> {
|
||||||
for(arg in sub.parameters.zip(call.args.withIndex())) {
|
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||||
val argItype = arg.second.value.inferType(program)
|
val argItype = pair.second.inferType(program)
|
||||||
if(argItype.isKnown) {
|
if(argItype.isKnown) {
|
||||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||||
val requiredType = arg.first.type
|
val requiredType = pair.first.type
|
||||||
if (requiredType != argtype) {
|
if (requiredType != argtype) {
|
||||||
if (argtype isAssignableTo requiredType) {
|
if (argtype isAssignableTo requiredType) {
|
||||||
modifications += IAstModification.ReplaceNode(
|
modifications += IAstModification.ReplaceNode(
|
||||||
call.args[arg.second.index],
|
call.args[index],
|
||||||
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
|
TypecastExpression(pair.second, requiredType, true, pair.second.position),
|
||||||
call as Node)
|
call as Node)
|
||||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||||
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
|
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
|
||||||
modifications += IAstModification.ReplaceNode(
|
if(pair.second is IdentifierReference) {
|
||||||
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) {
|
|
||||||
try {
|
|
||||||
val castedValue = (arg.second.value as NumericLiteralValue).cast(requiredType)
|
|
||||||
modifications += IAstModification.ReplaceNode(
|
modifications += IAstModification.ReplaceNode(
|
||||||
call.args[arg.second.index],
|
call.args[index],
|
||||||
castedValue,
|
AddressOf(pair.second as IdentifierReference, pair.second.position),
|
||||||
call as Node)
|
call as Node)
|
||||||
} catch (x: ExpressionError) {
|
|
||||||
// no cast possible
|
|
||||||
}
|
}
|
||||||
|
} else if(pair.second is NumericLiteralValue) {
|
||||||
|
val cast = (pair.second as NumericLiteralValue).cast(requiredType)
|
||||||
|
if(cast.isValid)
|
||||||
|
modifications += IAstModification.ReplaceNode(
|
||||||
|
call.args[index],
|
||||||
|
cast.valueOrZero(),
|
||||||
|
call as Node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,25 +154,25 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
|||||||
}
|
}
|
||||||
is BuiltinFunctionStatementPlaceholder -> {
|
is BuiltinFunctionStatementPlaceholder -> {
|
||||||
val func = BuiltinFunctions.getValue(sub.name)
|
val func = BuiltinFunctions.getValue(sub.name)
|
||||||
for (arg in func.parameters.zip(call.args.withIndex())) {
|
func.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||||
val argItype = arg.second.value.inferType(program)
|
val argItype = pair.second.inferType(program)
|
||||||
if (argItype.isKnown) {
|
if (argItype.isKnown) {
|
||||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||||
if (arg.first.possibleDatatypes.any { argtype == it })
|
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
||||||
continue
|
for (possibleType in pair.first.possibleDatatypes) {
|
||||||
for (possibleType in arg.first.possibleDatatypes) {
|
if (argtype isAssignableTo possibleType) {
|
||||||
if (argtype isAssignableTo possibleType) {
|
modifications += IAstModification.ReplaceNode(
|
||||||
modifications += IAstModification.ReplaceNode(
|
call.args[index],
|
||||||
call.args[arg.second.index],
|
TypecastExpression(pair.second, possibleType, true, pair.second.position),
|
||||||
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
|
call as Node)
|
||||||
call as Node)
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
null -> { }
|
else -> { }
|
||||||
else -> throw FatalAstException("call to something weird $sub ${call.target}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return modifications
|
return modifications
|
||||||
@ -152,7 +181,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
|||||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
// warn about any implicit type casts to Float, because that may not be intended
|
// warn about any implicit type casts to Float, because that may not be intended
|
||||||
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||||
errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
|
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
|
||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
@ -161,7 +190,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
|||||||
// make sure the memory address is an uword
|
// make sure the memory address is an uword
|
||||||
val dt = memread.addressExpression.inferType(program)
|
val dt = memread.addressExpression.inferType(program)
|
||||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||||
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
|
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||||
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||||
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||||
}
|
}
|
||||||
@ -172,7 +201,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
|||||||
// make sure the memory address is an uword
|
// make sure the memory address is an uword
|
||||||
val dt = memwrite.addressExpression.inferType(program)
|
val dt = memwrite.addressExpression.inferType(program)
|
||||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||||
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
|
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||||
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||||
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||||
}
|
}
|
||||||
@ -189,7 +218,9 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
|||||||
if (returnValue.inferType(program).istype(subReturnType))
|
if (returnValue.inferType(program).istype(subReturnType))
|
||||||
return noModifications
|
return noModifications
|
||||||
if (returnValue is NumericLiteralValue) {
|
if (returnValue is NumericLiteralValue) {
|
||||||
returnStmt.value = returnValue.cast(subroutine.returntypes.single())
|
val cast = returnValue.cast(subroutine.returntypes.single())
|
||||||
|
if(cast.isValid)
|
||||||
|
returnStmt.value = cast.valueOrZero()
|
||||||
} else {
|
} else {
|
||||||
return listOf(IAstModification.ReplaceNode(
|
return listOf(IAstModification.ReplaceNode(
|
||||||
returnValue,
|
returnValue,
|
73
compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt
Normal file
73
compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.base.Position
|
||||||
|
import prog8.ast.expressions.DirectMemoryRead
|
||||||
|
import prog8.ast.expressions.FunctionCall
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.expressions.TypecastExpression
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.ast.walk.AstWalker
|
||||||
|
import prog8.ast.walk.IAstModification
|
||||||
|
|
||||||
|
|
||||||
|
internal class VariousCleanups: AstWalker() {
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
|
return if(parent is INameScope)
|
||||||
|
listOf(ScopeFlatten(scope, parent as INameScope))
|
||||||
|
else
|
||||||
|
noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
val idx = into.statements.indexOf(scope)
|
||||||
|
if(idx>=0) {
|
||||||
|
into.statements.addAll(idx+1, scope.statements)
|
||||||
|
into.statements.remove(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(typecast.expression is NumericLiteralValue) {
|
||||||
|
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
|
||||||
|
if(value.isValid)
|
||||||
|
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
return before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||||
|
return before(functionCall as IFunctionCall, parent, functionCall.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
|
||||||
|
if(functionCall.target.nameInSource==listOf("peek")) {
|
||||||
|
// peek(a) is synonymous with @(a)
|
||||||
|
val memread = DirectMemoryRead(functionCall.args.single(), position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(functionCall as Node, memread, parent))
|
||||||
|
}
|
||||||
|
if(functionCall.target.nameInSource==listOf("poke")) {
|
||||||
|
// poke(a, v) is synonymous with @(a) = v
|
||||||
|
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
|
||||||
|
val assign = Assignment(tgt, functionCall.args[1], position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.expressions.Expression
|
||||||
|
import prog8.ast.expressions.FunctionCall
|
||||||
|
import prog8.ast.expressions.TypecastExpression
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.ast.walk.IAstVisitor
|
||||||
|
import prog8.compiler.CompilerException
|
||||||
|
import prog8.compiler.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||||
|
|
||||||
|
override fun visit(functionCall: FunctionCall) {
|
||||||
|
val error = checkTypes(functionCall as IFunctionCall, program)
|
||||||
|
if(error!=null)
|
||||||
|
throw CompilerException(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
|
val error = checkTypes(functionCallStatement as IFunctionCall, program)
|
||||||
|
if (error!=null)
|
||||||
|
throw CompilerException(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private fun argTypeCompatible(argDt: DataType, paramDt: DataType): Boolean {
|
||||||
|
if(argDt==paramDt)
|
||||||
|
return true
|
||||||
|
|
||||||
|
// there are some exceptions that are considered compatible, such as STR <> UWORD
|
||||||
|
if(argDt==DataType.STR && paramDt==DataType.UWORD ||
|
||||||
|
argDt==DataType.UWORD && paramDt==DataType.STR)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkTypes(call: IFunctionCall, program: Program): String? {
|
||||||
|
val argITypes = call.args.map { it.inferType(program) }
|
||||||
|
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
|
||||||
|
if(firstUnknownDt>=0)
|
||||||
|
return "argument ${firstUnknownDt+1} invalid argument type"
|
||||||
|
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
|
||||||
|
val target = call.target.targetStatement(program)
|
||||||
|
if (target is Subroutine) {
|
||||||
|
if(call.args.size != target.parameters.size)
|
||||||
|
return "invalid number of arguments"
|
||||||
|
val paramtypes = target.parameters.map { it.type }
|
||||||
|
val mismatch = argtypes.zip(paramtypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
|
||||||
|
if(mismatch>=0) {
|
||||||
|
val actual = argtypes[mismatch].toString()
|
||||||
|
val expected = paramtypes[mismatch].toString()
|
||||||
|
return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected"
|
||||||
|
}
|
||||||
|
if(target.isAsmSubroutine) {
|
||||||
|
if(target.asmReturnvaluesRegisters.size>1) {
|
||||||
|
// multiple return values will NOT work inside an expression.
|
||||||
|
// they MIGHT work in a regular assignment or just a function call statement.
|
||||||
|
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
|
||||||
|
if (call !is FunctionCallStatement) {
|
||||||
|
val checkParent =
|
||||||
|
if(parent is TypecastExpression)
|
||||||
|
parent.parent
|
||||||
|
else
|
||||||
|
parent
|
||||||
|
if (checkParent !is Assignment && checkParent !is VarDecl) {
|
||||||
|
return "can't use subroutine call that returns multiple return values here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (target is BuiltinFunctionStatementPlaceholder) {
|
||||||
|
val func = BuiltinFunctions.getValue(target.name)
|
||||||
|
if(call.args.size != func.parameters.size)
|
||||||
|
return "invalid number of arguments"
|
||||||
|
val paramtypes = func.parameters.map { it.possibleDatatypes }
|
||||||
|
argtypes.zip(paramtypes).forEachIndexed { index, pair ->
|
||||||
|
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
|
||||||
|
if (!anyCompatible) {
|
||||||
|
val actual = pair.first.toString()
|
||||||
|
val expected = pair.second.toString()
|
||||||
|
return "argument ${index + 1} type mismatch, was: $actual expected: $expected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
488
compiler/src/prog8/compiler/functions/BuiltinFunctions.kt
Normal file
488
compiler/src/prog8/compiler/functions/BuiltinFunctions.kt
Normal file
@ -0,0 +1,488 @@
|
|||||||
|
package prog8.compiler.functions
|
||||||
|
|
||||||
|
import prog8.ast.IMemSizer
|
||||||
|
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 kotlin.math.*
|
||||||
|
|
||||||
|
|
||||||
|
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
|
||||||
|
|
||||||
|
|
||||||
|
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue
|
||||||
|
|
||||||
|
|
||||||
|
class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean)
|
||||||
|
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
|
||||||
|
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
|
||||||
|
override fun toString(): String {
|
||||||
|
val paramConvs = params.mapIndexed { index, it ->
|
||||||
|
when {
|
||||||
|
it.reg!=null -> "$index:${it.reg}"
|
||||||
|
it.variable -> "$index:variable"
|
||||||
|
else -> "$index:???"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val returnConv =
|
||||||
|
when {
|
||||||
|
returns.reg!=null -> returns.reg.toString()
|
||||||
|
returns.floatFac1 -> "floatFAC1"
|
||||||
|
else -> "<no returnvalue>"
|
||||||
|
}
|
||||||
|
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FSignature(val name: String,
|
||||||
|
val pure: Boolean, // does it have side effects?
|
||||||
|
val parameters: List<FParam>,
|
||||||
|
val known_returntype: DataType?, // specify return type if fixed, otherwise null if it depends on the arguments
|
||||||
|
val constExpressionFunc: ConstExpressionCaller? = null) {
|
||||||
|
|
||||||
|
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
|
||||||
|
val returns = when(known_returntype) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> ReturnConvention(known_returntype, RegisterOrPair.A, false)
|
||||||
|
DataType.UWORD, DataType.WORD -> ReturnConvention(known_returntype, RegisterOrPair.AY, false)
|
||||||
|
DataType.FLOAT -> ReturnConvention(known_returntype, null, true)
|
||||||
|
in PassByReferenceDatatypes -> ReturnConvention(known_returntype!!, RegisterOrPair.AY, false)
|
||||||
|
else -> {
|
||||||
|
val paramType = actualParamTypes.first()
|
||||||
|
if(pure)
|
||||||
|
// return type depends on arg type
|
||||||
|
when(paramType) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false)
|
||||||
|
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
DataType.FLOAT -> ReturnConvention(paramType, null, true)
|
||||||
|
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
else -> ReturnConvention(paramType, null, false)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ReturnConvention(paramType, null, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return when {
|
||||||
|
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
|
||||||
|
actualParamTypes.size==1 -> {
|
||||||
|
// one parameter? via register/registerpair
|
||||||
|
val paramConv = when(val paramType = actualParamTypes[0]) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
|
||||||
|
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
else -> ParamConvention(paramType, null, false)
|
||||||
|
}
|
||||||
|
CallConvention(listOf(paramConv), returns)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// multiple parameters? via variables
|
||||||
|
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }
|
||||||
|
CallConvention(paramConvs, returns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
|
||||||
|
private val functionSignatures: List<FSignature> = listOf(
|
||||||
|
// this set of function have no return value and operate in-place:
|
||||||
|
FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
FSignature("ror" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
FSignature("rol2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
FSignature("ror2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||||
|
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||||
|
// these few have a return value depending on the argument(s):
|
||||||
|
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
||||||
|
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
||||||
|
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||||
|
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
||||||
|
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||||
|
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
|
||||||
|
FSignature("offsetof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinOffsetof),
|
||||||
|
// normal functions follow:
|
||||||
|
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||||
|
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||||
|
FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||||
|
FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||||
|
FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||||
|
FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||||
|
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||||
|
FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||||
|
FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||||
|
FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||||
|
FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||||
|
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||||
|
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||||
|
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
|
||||||
|
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
|
||||||
|
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||||
|
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||||
|
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||||
|
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||||
|
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||||
|
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||||
|
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||||
|
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAny) },
|
||||||
|
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAll) },
|
||||||
|
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
|
||||||
|
FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
|
||||||
|
FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||||
|
FSignature("peek" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UBYTE),
|
||||||
|
FSignature("peekw" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
FSignature("poke" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UBYTE))), null),
|
||||||
|
FSignature("pokew" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UWORD))), null),
|
||||||
|
FSignature("fastrnd8" , false, emptyList(), DataType.UBYTE),
|
||||||
|
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
|
||||||
|
FSignature("rndw" , false, emptyList(), DataType.UWORD),
|
||||||
|
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
|
||||||
|
FSignature("memory" , true, listOf(FParam("name", setOf(DataType.STR)), FParam("size", setOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
val BuiltinFunctions = functionSignatures.associateBy { it.name }
|
||||||
|
|
||||||
|
|
||||||
|
fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
|
||||||
|
|
||||||
|
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
|
||||||
|
|
||||||
|
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
|
||||||
|
|
||||||
|
fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0
|
||||||
|
|
||||||
|
fun builtinAll(array: List<Number>): Number = if(array.all { it.toDouble()!=0.0 }) 1 else 0
|
||||||
|
|
||||||
|
|
||||||
|
fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): InferredTypes.InferredType {
|
||||||
|
|
||||||
|
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) {
|
||||||
|
val idt = arglist.inferType(program)
|
||||||
|
if(!idt.isKnown)
|
||||||
|
throw FatalAstException("couldn't determine type of iterable $arglist")
|
||||||
|
return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.STR, in NumericDatatypes -> dt
|
||||||
|
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
|
||||||
|
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw FatalAstException("function '$function' requires one argument which is an iterable")
|
||||||
|
}
|
||||||
|
|
||||||
|
val func = BuiltinFunctions.getValue(function)
|
||||||
|
if(func.known_returntype!=null)
|
||||||
|
return InferredTypes.knownFor(func.known_returntype)
|
||||||
|
|
||||||
|
// function has return values, but the return type depends on the arguments
|
||||||
|
return when (function) {
|
||||||
|
"abs" -> {
|
||||||
|
val dt = args.single().inferType(program)
|
||||||
|
return if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
|
||||||
|
dt
|
||||||
|
else
|
||||||
|
InferredTypes.InferredType.unknown()
|
||||||
|
}
|
||||||
|
"max", "min" -> {
|
||||||
|
when(val dt = datatypeFromIterableArg(args.single())) {
|
||||||
|
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
||||||
|
in NumericDatatypes -> InferredTypes.knownFor(dt)
|
||||||
|
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
|
||||||
|
else -> InferredTypes.unknown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"sum" -> {
|
||||||
|
when(datatypeFromIterableArg(args.single())) {
|
||||||
|
DataType.UBYTE, DataType.UWORD -> InferredTypes.knownFor(DataType.UWORD)
|
||||||
|
DataType.BYTE, DataType.WORD -> InferredTypes.knownFor(DataType.WORD)
|
||||||
|
DataType.FLOAT -> InferredTypes.knownFor(DataType.FLOAT)
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_UW -> InferredTypes.knownFor(DataType.UWORD)
|
||||||
|
DataType.ARRAY_B, DataType.ARRAY_W -> InferredTypes.knownFor(DataType.WORD)
|
||||||
|
DataType.ARRAY_F -> InferredTypes.knownFor(DataType.FLOAT)
|
||||||
|
DataType.STR -> InferredTypes.knownFor(DataType.UWORD)
|
||||||
|
else -> InferredTypes.unknown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"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 CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
|
||||||
|
|
||||||
|
|
||||||
|
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
||||||
|
if(args.size!=1)
|
||||||
|
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val float = constval.number.toDouble()
|
||||||
|
return numericLiteral(function(float), args[0].position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun oneDoubleArgOutputWord(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
||||||
|
if(args.size!=1)
|
||||||
|
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val float = constval.number.toDouble()
|
||||||
|
return NumericLiteralValue(DataType.WORD, function(float).toInt(), args[0].position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Number): NumericLiteralValue {
|
||||||
|
if(args.size!=1)
|
||||||
|
throw SyntaxError("built-in function requires one integer argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
if(constval.type != DataType.UBYTE && constval.type!= DataType.UWORD)
|
||||||
|
throw SyntaxError("built-in function requires one integer argument", position)
|
||||||
|
|
||||||
|
val integer = constval.number.toInt()
|
||||||
|
return numericLiteral(function(integer).toInt(), args[0].position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Number>)->Number): NumericLiteralValue {
|
||||||
|
if(args.size!=1)
|
||||||
|
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
||||||
|
|
||||||
|
val array= args[0] as? ArrayLiteralValue ?: throw NotConstArgumentException()
|
||||||
|
val constElements = array.value.map{it.constValue(program)?.number}
|
||||||
|
if(constElements.contains(null))
|
||||||
|
throw NotConstArgumentException()
|
||||||
|
|
||||||
|
return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinAbs(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
// 1 arg, type = float or int, result type= isSameAs as argument type
|
||||||
|
if(args.size!=1)
|
||||||
|
throw SyntaxError("abs requires one numeric argument", position)
|
||||||
|
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
return when (constval.type) {
|
||||||
|
in IntegerDatatypes -> numericLiteral(abs(constval.number.toInt()), args[0].position)
|
||||||
|
DataType.FLOAT -> numericLiteral(abs(constval.number.toDouble()), args[0].position)
|
||||||
|
else -> throw SyntaxError("abs requires one numeric argument", position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
// 1 arg, type = anything, result type = ubyte
|
||||||
|
if(args.size!=1)
|
||||||
|
throw SyntaxError("offsetof requires one argument", position)
|
||||||
|
val idref = args[0] as? IdentifierReference
|
||||||
|
?: throw SyntaxError("offsetof argument should be an identifier", position)
|
||||||
|
|
||||||
|
val vardecl = idref.targetVarDecl(program)!!
|
||||||
|
val struct = vardecl.struct
|
||||||
|
if (struct == null || vardecl.datatype == DataType.STRUCT)
|
||||||
|
throw SyntaxError("offsetof can only be used on struct members", position)
|
||||||
|
|
||||||
|
val membername = idref.nameInSource.last()
|
||||||
|
var offset = 0
|
||||||
|
for(member in struct.statements) {
|
||||||
|
if((member as VarDecl).name == membername)
|
||||||
|
return NumericLiteralValue(DataType.UBYTE, offset, position)
|
||||||
|
offset += memsizer.memorySize(member.datatype)
|
||||||
|
}
|
||||||
|
throw SyntaxError("undefined struct member", position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
// 1 arg, type = anything, result type = ubyte
|
||||||
|
if(args.size!=1)
|
||||||
|
throw SyntaxError("sizeof requires one argument", position)
|
||||||
|
if(args[0] !is IdentifierReference)
|
||||||
|
throw SyntaxError("sizeof argument should be an identifier", position)
|
||||||
|
|
||||||
|
val dt = args[0].inferType(program)
|
||||||
|
if(dt.isKnown) {
|
||||||
|
val target = (args[0] as IdentifierReference).targetStatement(program)
|
||||||
|
?: throw CannotEvaluateException("sizeof", "no target")
|
||||||
|
|
||||||
|
fun structSize(target: StructDecl) = NumericLiteralValue(DataType.UBYTE, target.memsize(memsizer), 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(memsizer.memorySize(elementDt) * 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, memsizer.memorySize(dt.typeOrElse(DataType.STRUCT)), position)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw SyntaxError("sizeof invalid argument type", position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinLen(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
|
||||||
|
if(args.size!=1)
|
||||||
|
throw SyntaxError("len requires one argument", position)
|
||||||
|
|
||||||
|
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
|
||||||
|
var arraySize = directMemVar?.arraysize?.constIndex()
|
||||||
|
if(arraySize != null)
|
||||||
|
return NumericLiteralValue.optimalInteger(arraySize, position)
|
||||||
|
if(args[0] is ArrayLiteralValue)
|
||||||
|
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
|
||||||
|
if(args[0] !is IdentifierReference)
|
||||||
|
throw SyntaxError("len argument should be an identifier", position)
|
||||||
|
val target = (args[0] as IdentifierReference).targetVarDecl(program)
|
||||||
|
?: throw CannotEvaluateException("len", "no target vardecl")
|
||||||
|
|
||||||
|
return when(target.datatype) {
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F -> {
|
||||||
|
arraySize = target.arraysize?.constIndex()
|
||||||
|
if(arraySize==null)
|
||||||
|
throw CannotEvaluateException("len", "arraysize unknown")
|
||||||
|
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||||
|
}
|
||||||
|
DataType.STR -> {
|
||||||
|
val refLv = target.value as? StringLiteralValue ?: throw CannotEvaluateException("len", "stringsize unknown")
|
||||||
|
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
|
||||||
|
}
|
||||||
|
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
|
||||||
|
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
|
||||||
|
else -> throw CompilerException("weird datatype")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinMkword(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
if (args.size != 2)
|
||||||
|
throw SyntaxError("mkword requires msb and lsb arguments", position)
|
||||||
|
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val constLsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
|
||||||
|
return NumericLiteralValue(DataType.UWORD, result, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinSin8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
if (args.size != 1)
|
||||||
|
throw SyntaxError("sin8 requires one argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
|
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
if (args.size != 1)
|
||||||
|
throw SyntaxError("sin8u requires one argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
|
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinCos8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
if (args.size != 1)
|
||||||
|
throw SyntaxError("cos8 requires one argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
|
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
if (args.size != 1)
|
||||||
|
throw SyntaxError("cos8u requires one argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
|
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinSin16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
if (args.size != 1)
|
||||||
|
throw SyntaxError("sin16 requires one argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
|
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
if (args.size != 1)
|
||||||
|
throw SyntaxError("sin16u requires one argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
|
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinCos16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
if (args.size != 1)
|
||||||
|
throw SyntaxError("cos16 requires one argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
|
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
|
if (args.size != 1)
|
||||||
|
throw SyntaxError("cos16u requires one argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
|
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun builtinSgn(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): 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 tweakedValue: Number =
|
||||||
|
if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
|
||||||
|
floatNum.toInt() // we have an integer disguised as a float.
|
||||||
|
else
|
||||||
|
floatNum
|
||||||
|
|
||||||
|
return when(tweakedValue) {
|
||||||
|
is Int -> NumericLiteralValue.optimalInteger(value.toInt(), position)
|
||||||
|
is Short -> NumericLiteralValue.optimalInteger(value.toInt(), position)
|
||||||
|
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
|
||||||
|
is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
||||||
|
is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
||||||
|
else -> throw FatalAstException("invalid number type ${value::class}")
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,10 +3,12 @@ package prog8.compiler.target
|
|||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
|
|
||||||
internal interface IAssemblyGenerator {
|
internal interface IAssemblyGenerator {
|
||||||
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
|
fun compileToAssembly(): IAssemblyProgram
|
||||||
}
|
}
|
||||||
|
|
||||||
internal const val generatedLabelPrefix = "_prog8_label_"
|
internal const val generatedLabelPrefix = "_prog8_label_"
|
||||||
|
internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
|
||||||
|
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
|
||||||
|
|
||||||
internal interface IAssemblyProgram {
|
internal interface IAssemblyProgram {
|
||||||
val name: String
|
val name: String
|
||||||
|
119
compiler/src/prog8/compiler/target/ICompilationTarget.kt
Normal file
119
compiler/src/prog8/compiler/target/ICompilationTarget.kt
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package prog8.compiler.target
|
||||||
|
|
||||||
|
import prog8.ast.IMemSizer
|
||||||
|
import prog8.ast.IStringEncoding
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.statements.AssignTarget
|
||||||
|
import prog8.compiler.CompilationOptions
|
||||||
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.Zeropage
|
||||||
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
|
import prog8.compiler.target.c64.Petscii
|
||||||
|
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||||
|
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
|
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||||
|
val name: String
|
||||||
|
val machine: IMachineDefinition
|
||||||
|
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||||
|
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
||||||
|
|
||||||
|
fun isInRegularRAM(target: AssignTarget, program: Program): Boolean {
|
||||||
|
val memAddr = target.memoryAddress
|
||||||
|
val arrayIdx = target.arrayindexed
|
||||||
|
val ident = target.identifier
|
||||||
|
when {
|
||||||
|
memAddr != null -> {
|
||||||
|
return when (memAddr.addressExpression) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
|
||||||
|
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
|
||||||
|
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
||||||
|
else
|
||||||
|
false
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arrayIdx != null -> {
|
||||||
|
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
|
||||||
|
return if (targetStmt?.type == VarDeclType.MEMORY) {
|
||||||
|
val addr = targetStmt.value as? NumericLiteralValue
|
||||||
|
if (addr != null)
|
||||||
|
machine.isRegularRAMaddress(addr.number.toInt())
|
||||||
|
else
|
||||||
|
false
|
||||||
|
} else true
|
||||||
|
}
|
||||||
|
ident != null -> {
|
||||||
|
val decl = ident.targetVarDecl(program)!!
|
||||||
|
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
|
||||||
|
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
||||||
|
else
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal object C64Target: ICompilationTarget {
|
||||||
|
override val name = "c64"
|
||||||
|
override val machine = C64MachineDefinition
|
||||||
|
override fun encodeString(str: String, altEncoding: Boolean) =
|
||||||
|
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
|
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||||
|
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||||
|
|
||||||
|
override fun memorySize(dt: DataType): Int {
|
||||||
|
return when(dt) {
|
||||||
|
in ByteDatatypes -> 1
|
||||||
|
in WordDatatypes -> 2
|
||||||
|
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||||
|
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||||
|
else -> -9999999
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object Cx16Target: ICompilationTarget {
|
||||||
|
override val name = "cx16"
|
||||||
|
override val machine = CX16MachineDefinition
|
||||||
|
override fun encodeString(str: String, altEncoding: Boolean) =
|
||||||
|
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
|
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||||
|
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||||
|
|
||||||
|
override fun memorySize(dt: DataType): Int {
|
||||||
|
return when(dt) {
|
||||||
|
in ByteDatatypes -> 1
|
||||||
|
in WordDatatypes -> 2
|
||||||
|
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||||
|
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||||
|
else -> -9999999
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun asmGeneratorFor(
|
||||||
|
compTarget: ICompilationTarget,
|
||||||
|
program: Program,
|
||||||
|
errors: IErrorReporter,
|
||||||
|
zp: Zeropage,
|
||||||
|
options: CompilationOptions,
|
||||||
|
outputDir: Path
|
||||||
|
): IAssemblyGenerator
|
||||||
|
{
|
||||||
|
// at the moment we only have one code generation backend (for 6502 and 65c02)
|
||||||
|
return AsmGen(program, errors, zp, options, compTarget, outputDir)
|
||||||
|
}
|
@ -4,12 +4,34 @@ import prog8.compiler.CompilationOptions
|
|||||||
import prog8.compiler.Zeropage
|
import prog8.compiler.Zeropage
|
||||||
|
|
||||||
|
|
||||||
|
interface IMachineFloat {
|
||||||
|
fun toDouble(): Double
|
||||||
|
fun makeFloatFillAsm(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class CpuType {
|
||||||
|
CPU6502,
|
||||||
|
CPU65c02
|
||||||
|
}
|
||||||
|
|
||||||
interface IMachineDefinition {
|
interface IMachineDefinition {
|
||||||
val FLOAT_MAX_NEGATIVE: Double
|
val FLOAT_MAX_NEGATIVE: Double
|
||||||
val FLOAT_MAX_POSITIVE: Double
|
val FLOAT_MAX_POSITIVE: Double
|
||||||
val FLOAT_MEM_SIZE: Int
|
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>
|
val opcodeNames: Set<String>
|
||||||
|
var zeropage: Zeropage
|
||||||
|
val cpu: CpuType
|
||||||
|
|
||||||
fun getZeropage(compilerOptions: CompilationOptions): Zeropage
|
fun initializeZeropage(compilerOptions: CompilationOptions)
|
||||||
|
fun getFloat(num: Number): IMachineFloat
|
||||||
|
|
||||||
|
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||||
|
fun launchEmulator(programName: String)
|
||||||
|
fun isRegularRAMaddress(address: Int): Boolean
|
||||||
}
|
}
|
||||||
|
@ -7,27 +7,27 @@ import prog8.compiler.target.generatedLabelPrefix
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
|
class AssemblyProgram(override val name: String, outputDir: Path, private val compTarget: String) : IAssemblyProgram {
|
||||||
private val assemblyFile = outputDir.resolve("$name.asm")
|
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||||
private val prgFile = outputDir.resolve("$name.prg")
|
private val prgFile = outputDir.resolve("$name.prg")
|
||||||
private val binFile = outputDir.resolve("$name.bin")
|
private val binFile = outputDir.resolve("$name.bin")
|
||||||
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
|
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
|
||||||
|
|
||||||
override 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",
|
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
|
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||||
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
||||||
|
|
||||||
val outFile = when (options.output) {
|
val outFile = when (options.output) {
|
||||||
OutputType.PRG -> {
|
OutputType.PRG -> {
|
||||||
command.add("--cbm-prg")
|
command.add("--cbm-prg")
|
||||||
println("\nCreating C-64 prg.")
|
println("\nCreating prg for target $compTarget.")
|
||||||
prgFile
|
prgFile
|
||||||
}
|
}
|
||||||
OutputType.RAW -> {
|
OutputType.RAW -> {
|
||||||
command.add("--nostart")
|
command.add("--nostart")
|
||||||
println("\nCreating raw binary.")
|
println("\nCreating raw binary for target $compTarget.")
|
||||||
binFile
|
binFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,62 @@
|
|||||||
package prog8.compiler.target.c64
|
package prog8.compiler.target.c64
|
||||||
|
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.*
|
||||||
import prog8.compiler.CompilerException
|
import prog8.compiler.target.CpuType
|
||||||
import prog8.compiler.Zeropage
|
|
||||||
import prog8.compiler.ZeropageType
|
|
||||||
import prog8.compiler.target.IMachineDefinition
|
import prog8.compiler.target.IMachineDefinition
|
||||||
|
import prog8.compiler.target.IMachineFloat
|
||||||
|
import java.io.IOException
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
object C64MachineDefinition: IMachineDefinition {
|
internal object C64MachineDefinition: IMachineDefinition {
|
||||||
|
|
||||||
|
override val cpu = CpuType.CPU6502
|
||||||
|
|
||||||
// 5-byte cbm MFLPT format limitations:
|
// 5-byte cbm MFLPT format limitations:
|
||||||
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
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_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||||
override val FLOAT_MEM_SIZE = 5
|
override val FLOAT_MEM_SIZE = 5
|
||||||
const val BASIC_LOAD_ADDRESS = 0x0801
|
override val POINTER_MEM_SIZE = 2
|
||||||
const val RAW_LOAD_ADDRESS = 0xc000
|
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)
|
// 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
|
||||||
const val ESTACK_LO_VALUE = 0xce00 // $ce00-$ceff inclusive
|
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
|
||||||
const val ESTACK_HI_VALUE = 0xcf00 // $cf00-$cfff inclusive
|
|
||||||
const val ESTACK_LO_HEX = "\$ce00"
|
|
||||||
const val ESTACK_LO_PLUS1_HEX = "\$ce01"
|
|
||||||
const val ESTACK_LO_PLUS2_HEX = "\$ce02"
|
|
||||||
const val ESTACK_HI_HEX = "\$cf00"
|
|
||||||
const val ESTACK_HI_PLUS1_HEX = "\$cf01"
|
|
||||||
const val ESTACK_HI_PLUS2_HEX = "\$cf02"
|
|
||||||
|
|
||||||
override fun getZeropage(compilerOptions: CompilationOptions) = C64Zeropage(compilerOptions)
|
override lateinit var zeropage: Zeropage
|
||||||
|
|
||||||
|
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||||
|
|
||||||
|
override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> {
|
||||||
|
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||||
|
listOf("syslib")
|
||||||
|
else
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun launchEmulator(programName: String) {
|
||||||
|
for(emulator in listOf("x64sc", "x64")) {
|
||||||
|
println("\nStarting C-64 emulator $emulator...")
|
||||||
|
val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list",
|
||||||
|
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
|
||||||
|
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
|
val process: Process
|
||||||
|
try {
|
||||||
|
process=processb.start()
|
||||||
|
} catch(x: IOException) {
|
||||||
|
continue // try the next emulator executable
|
||||||
|
}
|
||||||
|
process.waitFor()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
|
||||||
|
|
||||||
|
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||||
|
zeropage = C64Zeropage(compilerOptions)
|
||||||
|
}
|
||||||
|
|
||||||
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
// 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",
|
override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
|
||||||
@ -42,20 +70,12 @@ object C64MachineDefinition: IMachineDefinition {
|
|||||||
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
||||||
|
|
||||||
|
|
||||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
|
|
||||||
companion object {
|
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
|
||||||
const val SCRATCH_B1 = 0x02
|
override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
|
||||||
const val SCRATCH_REG = 0x03 // temp storage for a register
|
override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc
|
||||||
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
|
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
|
||||||
const val SCRATCH_W1 = 0xfb // $fb+$fc
|
|
||||||
const val SCRATCH_W2 = 0xfd // $fd+$fe
|
|
||||||
}
|
|
||||||
|
|
||||||
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
|
|
||||||
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
|
|
||||||
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -65,12 +85,14 @@ object C64MachineDefinition: IMachineDefinition {
|
|||||||
if (options.zeropage == ZeropageType.FULL) {
|
if (options.zeropage == ZeropageType.FULL) {
|
||||||
free.addAll(0x04..0xf9)
|
free.addAll(0x04..0xf9)
|
||||||
free.add(0xff)
|
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(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||||
} else {
|
} else {
|
||||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||||
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
free.addAll(listOf(
|
||||||
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||||
|
0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||||
|
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||||
0x22, 0x23, 0x24, 0x25,
|
0x22, 0x23, 0x24, 0x25,
|
||||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||||
@ -80,45 +102,43 @@ object C64MachineDefinition: IMachineDefinition {
|
|||||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
||||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||||
// 0x90-0xfa is 'kernel work storage area'
|
// 0x90-0xfa is 'kernal work storage area'
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||||
// remove the zero page locations used for floating point operations from the free list
|
// remove the zero page locations used for floating point operations from the free list
|
||||||
free.removeAll(listOf(
|
free.removeAll(listOf(
|
||||||
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
|
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.zeropage!=ZeropageType.DONTUSE) {
|
if(options.zeropage!=ZeropageType.DONTUSE) {
|
||||||
// add the other free Zp addresses,
|
// add the free Zp addresses
|
||||||
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
|
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||||
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||||
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
|
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
|
||||||
} else {
|
} else {
|
||||||
// don't use the zeropage at all
|
// don't use the zeropage at all
|
||||||
free.clear()
|
free.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(SCRATCH_B1 !in free)
|
require(SCRATCH_B1 !in free)
|
||||||
assert(SCRATCH_REG !in free)
|
require(SCRATCH_REG !in free)
|
||||||
assert(SCRATCH_REG_X !in free)
|
require(SCRATCH_W1 !in free)
|
||||||
assert(SCRATCH_W1 !in free)
|
require(SCRATCH_W2 !in free)
|
||||||
assert(SCRATCH_W2 !in free)
|
|
||||||
|
|
||||||
for (reserved in options.zpReserved)
|
for (reserved in options.zpReserved)
|
||||||
reserve(reserved)
|
reserve(reserved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short): IMachineFloat {
|
||||||
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val zero = Mflpt5(0, 0, 0, 0, 0)
|
val zero = Mflpt5(0, 0, 0, 0, 0)
|
||||||
@ -165,7 +185,7 @@ object C64MachineDefinition: IMachineDefinition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toDouble(): Double {
|
override fun toDouble(): Double {
|
||||||
if (this == zero) return 0.0
|
if (this == zero) return 0.0
|
||||||
val exp = b0 - 128
|
val exp = b0 - 128
|
||||||
val sign = (b1.toInt() and 0x80) > 0
|
val sign = (b1.toInt() and 0x80) > 0
|
||||||
@ -173,5 +193,14 @@ object C64MachineDefinition: IMachineDefinition {
|
|||||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||||
return if (sign) -result else result
|
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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@ object Petscii {
|
|||||||
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
|
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
|
||||||
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
|
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
|
||||||
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
|
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
|
||||||
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
|
'_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
|
||||||
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
|
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
|
||||||
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
|
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
|
||||||
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
|
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
|
||||||
@ -236,7 +236,7 @@ object Petscii {
|
|||||||
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
|
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
|
||||||
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
|
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
|
||||||
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
|
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
|
||||||
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
|
'_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
|
||||||
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
|
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
|
||||||
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
|
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
|
||||||
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
|
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
|
||||||
@ -431,7 +431,7 @@ object Petscii {
|
|||||||
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
|
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
|
||||||
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
|
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
|
||||||
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
|
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
|
||||||
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
|
'_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
|
||||||
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
|
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
|
||||||
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
|
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
|
||||||
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
|
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
|
||||||
@ -495,7 +495,7 @@ object Petscii {
|
|||||||
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
|
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
|
||||||
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
|
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
|
||||||
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
|
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
|
||||||
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
|
'_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
|
||||||
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
|
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
|
||||||
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
|
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
|
||||||
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
|
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
|
||||||
@ -626,7 +626,7 @@ object Petscii {
|
|||||||
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
|
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
|
||||||
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
|
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
|
||||||
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
|
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
|
||||||
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
|
'_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
|
||||||
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
|
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
|
||||||
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
|
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
|
||||||
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
|
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
|
||||||
@ -885,7 +885,7 @@ object Petscii {
|
|||||||
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
|
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
|
||||||
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
|
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
|
||||||
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
|
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
|
||||||
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
|
'_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
|
||||||
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
|
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
|
||||||
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
|
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
|
||||||
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
|
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
|
||||||
@ -1054,11 +1054,16 @@ object Petscii {
|
|||||||
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
|
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
|
||||||
return text.map {
|
return text.map {
|
||||||
val petscii = lookup[it]
|
val petscii = lookup[it]
|
||||||
petscii?.toShort() ?: if(it=='\u0000')
|
petscii?.toShort() ?: when (it) {
|
||||||
0.toShort()
|
'\u0000' -> 0.toShort()
|
||||||
else {
|
in '\u8000'..'\u80ff' -> {
|
||||||
val case = if (lowercase) "lower" else "upper"
|
// special case: take the lower 8 bit hex value directly
|
||||||
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
|
(it.toInt() - 0x8000).toShort()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val case = if (lowercase) "lower" else "upper"
|
||||||
|
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1072,11 +1077,16 @@ object Petscii {
|
|||||||
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
|
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
|
||||||
return text.map{
|
return text.map{
|
||||||
val screencode = lookup[it]
|
val screencode = lookup[it]
|
||||||
screencode?.toShort() ?: if(it=='\u0000')
|
screencode?.toShort() ?: when (it) {
|
||||||
0.toShort()
|
'\u0000' -> 0.toShort()
|
||||||
else {
|
in '\u8000'..'\u80ff' -> {
|
||||||
val case = if (lowercase) "lower" else "upper"
|
// special case: take the lower 8 bit hex value directly
|
||||||
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
|
(it.toInt() - 0x8000).toShort()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val case = if (lowercase) "lower" else "upper"
|
||||||
|
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,630 +0,0 @@
|
|||||||
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.target.c64.C64MachineDefinition.C64Zeropage
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
|
||||||
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)
|
|
||||||
"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)
|
|
||||||
"lsl" -> funcLsl(fcall)
|
|
||||||
"lsr" -> funcLsr(fcall)
|
|
||||||
"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 ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.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.asmIdentifierName(variable)
|
|
||||||
val numElements = decl.arraysize!!.size()
|
|
||||||
when (decl.datatype) {
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$varName
|
|
||||||
ldy #>$varName
|
|
||||||
sta ${C64Zeropage.SCRATCH_W1}
|
|
||||||
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
|
||||||
lda #$numElements
|
|
||||||
jsr prog8_lib.reverse_b
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$varName
|
|
||||||
ldy #>$varName
|
|
||||||
sta ${C64Zeropage.SCRATCH_W1}
|
|
||||||
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
|
||||||
lda #$numElements
|
|
||||||
jsr prog8_lib.reverse_w
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> {
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$varName
|
|
||||||
ldy #>$varName
|
|
||||||
sta ${C64Zeropage.SCRATCH_W1}
|
|
||||||
sty ${C64Zeropage.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.asmIdentifierName(variable)
|
|
||||||
val numElements = decl.arraysize!!.size()
|
|
||||||
when (decl.datatype) {
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$varName
|
|
||||||
ldy #>$varName
|
|
||||||
sta ${C64Zeropage.SCRATCH_W1}
|
|
||||||
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
|
||||||
lda #$numElements
|
|
||||||
sta ${C64Zeropage.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 ${C64Zeropage.SCRATCH_W1}
|
|
||||||
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
|
||||||
lda #$numElements
|
|
||||||
sta ${C64Zeropage.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.asmIdentifierName(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.asmIdentifierName(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 $ESTACK_LO_HEX,x
|
|
||||||
sta (+) + 1
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
sta (+) + 2
|
|
||||||
+ ror ${'$'}ffff ; modified
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(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.asmIdentifierName(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.asmIdentifierName(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.asmIdentifierName(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 $ESTACK_LO_HEX,x
|
|
||||||
sta (+) + 1
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
sta (+) + 2
|
|
||||||
+ rol ${'$'}ffff ; modified
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(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.asmIdentifierName(what)
|
|
||||||
asmgen.out(" rol $variable | rol $variable+1")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcLsr(fcall: IFunctionCall) {
|
|
||||||
val what = fcall.args.single()
|
|
||||||
val dt = what.inferType(program)
|
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
when (what) {
|
|
||||||
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
|
|
||||||
is DirectMemoryRead -> {
|
|
||||||
if (what.addressExpression is NumericLiteralValue) {
|
|
||||||
val number = (what.addressExpression as NumericLiteralValue).number
|
|
||||||
asmgen.out(" lsr ${number.toHex()}")
|
|
||||||
} else {
|
|
||||||
asmgen.translateExpression(what.addressExpression)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
sta (+) + 1
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
sta (+) + 2
|
|
||||||
+ lsr ${'$'}ffff ; modified
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
asmgen.translateExpression(what.identifier)
|
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
|
||||||
asmgen.out(" jsr prog8_lib.lsr_array_ub")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
when (what) {
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
asmgen.translateExpression(what.identifier)
|
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
|
||||||
asmgen.out(" jsr prog8_lib.lsr_array_b")
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
|
||||||
asmgen.out(" lda $variable | asl a | 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.lsr_array_uw")
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
|
||||||
asmgen.out(" lsr $variable+1 | ror $variable")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
when (what) {
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
asmgen.translateExpression(what.identifier)
|
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
|
||||||
asmgen.out(" jsr prog8_lib.lsr_array_w")
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
|
||||||
asmgen.out(" lda $variable+1 | asl a | ror $variable+1 | ror $variable")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcLsl(fcall: IFunctionCall) {
|
|
||||||
val what = fcall.args.single()
|
|
||||||
val dt = what.inferType(program)
|
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
|
||||||
in ByteDatatypes -> {
|
|
||||||
when (what) {
|
|
||||||
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
|
|
||||||
is DirectMemoryRead -> {
|
|
||||||
if (what.addressExpression is NumericLiteralValue) {
|
|
||||||
val number = (what.addressExpression as NumericLiteralValue).number
|
|
||||||
asmgen.out(" asl ${number.toHex()}")
|
|
||||||
} else {
|
|
||||||
asmgen.translateExpression(what.addressExpression)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
sta (+) + 1
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
sta (+) + 2
|
|
||||||
+ asl ${'$'}ffff ; modified
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
asmgen.translateExpression(what.identifier)
|
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
|
||||||
asmgen.out(" jsr prog8_lib.lsl_array_b")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in WordDatatypes -> {
|
|
||||||
when (what) {
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
asmgen.translateExpression(what.identifier)
|
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
|
||||||
asmgen.out(" jsr prog8_lib.lsl_array_w")
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
|
||||||
asmgen.out(" asl $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) {
|
|
||||||
outputPushAddressOfIdentifier(fcall.args[0])
|
|
||||||
asmgen.out(" jsr prog8_lib.func_strlen")
|
|
||||||
}
|
|
||||||
|
|
||||||
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.asmIdentifierName(first)
|
|
||||||
val secondName = asmgen.asmIdentifierName(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 ${C64Zeropage.SCRATCH_W1}
|
|
||||||
lda #>$firstName
|
|
||||||
sta ${C64Zeropage.SCRATCH_W1+1}
|
|
||||||
lda #<$secondName
|
|
||||||
sta ${C64Zeropage.SCRATCH_W2}
|
|
||||||
lda #>$secondName
|
|
||||||
sta ${C64Zeropage.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) {
|
|
||||||
translateFunctionArguments(fcall.args, func)
|
|
||||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,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("should have been const-folded")
|
|
||||||
if (arg is IdentifierReference) {
|
|
||||||
val sourceName = asmgen.asmIdentifierName(arg)
|
|
||||||
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
|
|
||||||
} else {
|
|
||||||
asmgen.translateExpression(arg)
|
|
||||||
asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
|
|
||||||
arg as IdentifierReference
|
|
||||||
val identifierName = asmgen.asmIdentifierName(arg)
|
|
||||||
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.size()!!
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$identifierName
|
|
||||||
sta $ESTACK_LO_HEX,x
|
|
||||||
lda #>$identifierName
|
|
||||||
sta $ESTACK_HI_HEX,x
|
|
||||||
dex
|
|
||||||
lda #$size
|
|
||||||
sta $ESTACK_LO_HEX,x
|
|
||||||
dex
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun outputPushAddressOfIdentifier(arg: Expression) {
|
|
||||||
val identifierName = asmgen.asmIdentifierName(arg as IdentifierReference)
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$identifierName
|
|
||||||
sta $ESTACK_LO_HEX,x
|
|
||||||
lda #>$identifierName
|
|
||||||
sta $ESTACK_HI_HEX,x
|
|
||||||
dex
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FSignature) {
|
|
||||||
args.forEach {
|
|
||||||
asmgen.translateExpression(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,500 +0,0 @@
|
|||||||
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.target.c64.C64MachineDefinition
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS2_HEX
|
|
||||||
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 -> translatePushFromArray(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 $ESTACK_LO_HEX,x | dex")
|
|
||||||
RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
|
|
||||||
RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
|
|
||||||
RegisterOrPair.X -> {
|
|
||||||
// return value in X register has been discarded, just push a zero
|
|
||||||
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | dex")
|
|
||||||
}
|
|
||||||
RegisterOrPair.AX -> {
|
|
||||||
// return value in X register has been discarded, just push a zero in this place
|
|
||||||
asmgen.out(" sta $ESTACK_LO_HEX,x | lda #0 | sta $ESTACK_HI_HEX,x | dex")
|
|
||||||
}
|
|
||||||
RegisterOrPair.XY -> {
|
|
||||||
// return value in X register has been discarded, just push a zero in this place
|
|
||||||
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,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 $ESTACK_HI_PLUS1_HEX,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 -> asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | ${asmgen.signExtendAtoMsb("$ESTACK_HI_PLUS1_HEX,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.asmIdentifierName(expr.identifier)
|
|
||||||
asmgen.out(" lda #<$name | sta $ESTACK_LO_HEX,x | lda #>$name | sta $ESTACK_HI_HEX,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 $ESTACK_LO_HEX,x | dex")
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
// the identifier is a pointer variable, so read the value from the address in it
|
|
||||||
val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference)
|
|
||||||
asmgen.out("""
|
|
||||||
lda $sourceName
|
|
||||||
sta (+) +1
|
|
||||||
lda $sourceName+1
|
|
||||||
sta (+) +2
|
|
||||||
+ lda ${'$'}ffff ; modified
|
|
||||||
sta $ESTACK_LO_HEX,x
|
|
||||||
dex""")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
translateExpression(expr.addressExpression)
|
|
||||||
asmgen.out(" jsr prog8_lib.read_byte_from_address")
|
|
||||||
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateExpression(expr: NumericLiteralValue) {
|
|
||||||
when(expr.type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta $ESTACK_LO_HEX,x | dex")
|
|
||||||
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
|
||||||
lda #<${expr.number.toHex()}
|
|
||||||
sta $ESTACK_LO_HEX,x
|
|
||||||
lda #>${expr.number.toHex()}
|
|
||||||
sta $ESTACK_HI_HEX,x
|
|
||||||
dex
|
|
||||||
""")
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
val floatConst = asmgen.getFloatConst(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.asmIdentifierName(expr)
|
|
||||||
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
|
||||||
asmgen.out(" lda $varname | sta $ESTACK_LO_HEX,x | dex")
|
|
||||||
}
|
|
||||||
DataType.UWORD, DataType.WORD -> {
|
|
||||||
asmgen.out(" lda $varname | sta $ESTACK_LO_HEX,x | lda $varname+1 | sta $ESTACK_HI_HEX,x | dex")
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
asmgen.out(" lda #<$varname | ldy #>$varname| jsr c64flt.push_float")
|
|
||||||
}
|
|
||||||
in IterableDatatypes -> {
|
|
||||||
asmgen.out(" lda #<$varname | sta $ESTACK_LO_HEX,x | lda #>$varname | sta $ESTACK_HI_HEX,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 val powersOfTwo = setOf(0,1,2,4,8,16,32,64,128,256)
|
|
||||||
|
|
||||||
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) {
|
|
||||||
">>" -> {
|
|
||||||
// bit-shifts are always by a constant number (for now)
|
|
||||||
translateExpression(expr.left)
|
|
||||||
val amount = expr.right.constValue(program)!!.number.toInt()
|
|
||||||
when (leftDt) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
if(amount<=2)
|
|
||||||
repeat(amount) { asmgen.out(" lsr $ESTACK_LO_PLUS1_HEX,x") }
|
|
||||||
else {
|
|
||||||
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x")
|
|
||||||
repeat(amount) { asmgen.out(" lsr a") }
|
|
||||||
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
if(amount<=2)
|
|
||||||
repeat(amount) { asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x") }
|
|
||||||
else {
|
|
||||||
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | sta ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
|
||||||
repeat(amount) { asmgen.out(" asl a | ror ${C64MachineDefinition.C64Zeropage.SCRATCH_B1} | lda ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}") }
|
|
||||||
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,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 $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,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 $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
|
||||||
else
|
|
||||||
asmgen.out(" jsr math.shift_right_w_$left")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
"<<" -> {
|
|
||||||
// bit-shifts are always by a constant number (for now)
|
|
||||||
translateExpression(expr.left)
|
|
||||||
val amount = expr.right.constValue(program)!!.number.toInt()
|
|
||||||
if (leftDt in ByteDatatypes) {
|
|
||||||
if(amount<=2)
|
|
||||||
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x") }
|
|
||||||
else {
|
|
||||||
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x")
|
|
||||||
repeat(amount) { asmgen.out(" asl a") }
|
|
||||||
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,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 $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,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 $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
eor #255
|
|
||||||
sta $ESTACK_LO_PLUS1_HEX,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 translatePushFromArray(arrayExpr: ArrayIndexedExpression) {
|
|
||||||
// assume *reading* from an array
|
|
||||||
val index = arrayExpr.arrayspec.index
|
|
||||||
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
|
|
||||||
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
|
|
||||||
if(index is NumericLiteralValue) {
|
|
||||||
val elementDt = ArrayElementTypes.getValue(arrayDt)
|
|
||||||
val indexValue = index.number.toInt() * elementDt.memorySize()
|
|
||||||
when(elementDt) {
|
|
||||||
in ByteDatatypes -> {
|
|
||||||
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | dex")
|
|
||||||
}
|
|
||||||
in WordDatatypes -> {
|
|
||||||
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | lda $arrayVarName+$indexValue+1 | sta $ESTACK_HI_HEX,x | dex")
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
asmgen.translateArrayIndexIntoA(arrayExpr)
|
|
||||||
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 $ESTACK_LO_PLUS2_HEX,x
|
|
||||||
clc
|
|
||||||
adc $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
inx
|
|
||||||
sta $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
""")
|
|
||||||
"-" -> asmgen.out("""
|
|
||||||
lda $ESTACK_LO_PLUS2_HEX,x
|
|
||||||
sec
|
|
||||||
sbc $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
inx
|
|
||||||
sta $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
""")
|
|
||||||
"<<", ">>" -> throw AssemblyError("bit-shifts not via stack")
|
|
||||||
"<" -> 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,554 +0,0 @@
|
|||||||
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.AssignTarget
|
|
||||||
import prog8.ast.statements.Assignment
|
|
||||||
import prog8.ast.statements.ForLoop
|
|
||||||
import prog8.compiler.AssemblyError
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
|
||||||
import prog8.compiler.toHex
|
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
|
|
||||||
// todo choose more efficient comparisons to avoid needless lda's
|
|
||||||
// todo optimize common case when step == 2 or -2
|
|
||||||
|
|
||||||
|
|
||||||
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 continueLabel = asmgen.makeLabel("for_continue")
|
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
|
||||||
asmgen.loopContinueLabels.push(continueLabel)
|
|
||||||
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.asmIdentifierName(stmt.loopVar)
|
|
||||||
asmgen.translateExpression(range.to)
|
|
||||||
asmgen.translateExpression(range.from)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda ${ESTACK_LO_HEX},x
|
|
||||||
sta $varname
|
|
||||||
$loopLabel""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel lda $varname
|
|
||||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
beq $endLabel
|
|
||||||
$incdec $varname
|
|
||||||
jmp $loopLabel
|
|
||||||
$endLabel inx""")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
// bytes, step >= 2 or <= -2
|
|
||||||
|
|
||||||
// loop over byte range via loopvar
|
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
|
||||||
asmgen.translateExpression(range.to)
|
|
||||||
asmgen.translateExpression(range.from)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda ${ESTACK_LO_HEX},x
|
|
||||||
sta $varname
|
|
||||||
$loopLabel""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel lda $varname""")
|
|
||||||
if(stepsize>0) {
|
|
||||||
asmgen.out("""
|
|
||||||
clc
|
|
||||||
adc #$stepsize
|
|
||||||
sta $varname
|
|
||||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
bcc $loopLabel
|
|
||||||
beq $loopLabel""")
|
|
||||||
} else {
|
|
||||||
asmgen.out("""
|
|
||||||
sec
|
|
||||||
sbc #${stepsize.absoluteValue}
|
|
||||||
sta $varname
|
|
||||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
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)
|
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
|
||||||
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
|
|
||||||
null, range.from, range.position)
|
|
||||||
assignLoopvar.linkParents(stmt)
|
|
||||||
asmgen.translate(assignLoopvar)
|
|
||||||
asmgen.out(loopLabel)
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
lda $varname+1
|
|
||||||
cmp $ESTACK_HI_PLUS1_HEX,x
|
|
||||||
bne +
|
|
||||||
lda $varname
|
|
||||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
beq $endLabel""")
|
|
||||||
if(stepsize==1) {
|
|
||||||
asmgen.out("""
|
|
||||||
+ inc $varname
|
|
||||||
bne +
|
|
||||||
inc $varname+1
|
|
||||||
""")
|
|
||||||
} else {
|
|
||||||
asmgen.out("""
|
|
||||||
+ lda $varname
|
|
||||||
bne +
|
|
||||||
dec $varname+1
|
|
||||||
+ dec $varname""")
|
|
||||||
}
|
|
||||||
asmgen.out("""
|
|
||||||
+ jmp $loopLabel
|
|
||||||
$endLabel inx""")
|
|
||||||
}
|
|
||||||
stepsize > 0 -> {
|
|
||||||
|
|
||||||
// (u)words, step >= 2
|
|
||||||
|
|
||||||
asmgen.translateExpression(range.to)
|
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
|
||||||
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
|
|
||||||
null, range.from, range.position)
|
|
||||||
assignLoopvar.linkParents(stmt)
|
|
||||||
asmgen.translate(assignLoopvar)
|
|
||||||
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
|
|
||||||
lda $ESTACK_HI_PLUS1_HEX,x
|
|
||||||
cmp $varname+1
|
|
||||||
bcc $endLabel
|
|
||||||
bne $loopLabel
|
|
||||||
lda $varname
|
|
||||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
bcc $endLabel
|
|
||||||
bcs $loopLabel
|
|
||||||
$endLabel inx""")
|
|
||||||
} else {
|
|
||||||
asmgen.out("""
|
|
||||||
lda $varname
|
|
||||||
clc
|
|
||||||
adc #<$stepsize
|
|
||||||
sta $varname
|
|
||||||
lda $varname+1
|
|
||||||
adc #>$stepsize
|
|
||||||
sta $varname+1
|
|
||||||
lda $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
cmp $varname
|
|
||||||
lda $ESTACK_HI_PLUS1_HEX,x
|
|
||||||
sbc $varname+1
|
|
||||||
bvc +
|
|
||||||
eor #$80
|
|
||||||
+ bpl $loopLabel
|
|
||||||
$endLabel inx""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
|
|
||||||
// (u)words, step <= -2
|
|
||||||
asmgen.translateExpression(range.to)
|
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
|
||||||
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
|
|
||||||
null, range.from, range.position)
|
|
||||||
assignLoopvar.linkParents(stmt)
|
|
||||||
asmgen.translate(assignLoopvar)
|
|
||||||
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
|
|
||||||
cmp $ESTACK_HI_PLUS1_HEX,x
|
|
||||||
bcc $endLabel
|
|
||||||
bne $loopLabel
|
|
||||||
lda $varname
|
|
||||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
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
|
|
||||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
lda $varname+1
|
|
||||||
sbc $ESTACK_HI_PLUS1_HEX,x
|
|
||||||
bvc +
|
|
||||||
eor #$80
|
|
||||||
+ bpl $loopLabel
|
|
||||||
$endLabel inx""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("range expression can only be byte or word")
|
|
||||||
}
|
|
||||||
|
|
||||||
asmgen.loopEndLabels.pop()
|
|
||||||
asmgen.loopContinueLabels.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
|
|
||||||
val loopLabel = asmgen.makeLabel("for_loop")
|
|
||||||
val endLabel = asmgen.makeLabel("for_end")
|
|
||||||
val continueLabel = asmgen.makeLabel("for_continue")
|
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
|
||||||
asmgen.loopContinueLabels.push(continueLabel)
|
|
||||||
val iterableName = asmgen.asmIdentifierName(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""")
|
|
||||||
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar)}")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel inc $loopLabel+1
|
|
||||||
bne $loopLabel
|
|
||||||
inc $loopLabel+2
|
|
||||||
bne $loopLabel
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
|
||||||
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases
|
|
||||||
val length = decl.arraysize!!.size()!!
|
|
||||||
val counterLabel = asmgen.makeLabel("for_counter")
|
|
||||||
val modifiedLabel = asmgen.makeLabel("for_modified")
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$iterableName
|
|
||||||
ldy #>$iterableName
|
|
||||||
sta $modifiedLabel+1
|
|
||||||
sty $modifiedLabel+2
|
|
||||||
ldy #0
|
|
||||||
$loopLabel sty $counterLabel
|
|
||||||
$modifiedLabel lda ${65535.toHex()},y ; modified""")
|
|
||||||
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar)}")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel ldy $counterLabel
|
|
||||||
iny
|
|
||||||
cpy #${length and 255}
|
|
||||||
beq $endLabel
|
|
||||||
jmp $loopLabel
|
|
||||||
$counterLabel .byte 0
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
|
||||||
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases
|
|
||||||
val length = decl.arraysize!!.size()!! * 2
|
|
||||||
val counterLabel = asmgen.makeLabel("for_counter")
|
|
||||||
val modifiedLabel = asmgen.makeLabel("for_modified")
|
|
||||||
val modifiedLabel2 = asmgen.makeLabel("for_modified2")
|
|
||||||
val loopvarName = asmgen.asmIdentifierName(stmt.loopVar)
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$iterableName
|
|
||||||
ldy #>$iterableName
|
|
||||||
sta $modifiedLabel+1
|
|
||||||
sty $modifiedLabel+2
|
|
||||||
lda #<$iterableName+1
|
|
||||||
ldy #>$iterableName+1
|
|
||||||
sta $modifiedLabel2+1
|
|
||||||
sty $modifiedLabel2+2
|
|
||||||
ldy #0
|
|
||||||
$loopLabel sty $counterLabel
|
|
||||||
$modifiedLabel lda ${65535.toHex()},y ; modified
|
|
||||||
sta $loopvarName
|
|
||||||
$modifiedLabel2 lda ${65535.toHex()},y ; modified
|
|
||||||
sta $loopvarName+1""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel ldy $counterLabel
|
|
||||||
iny
|
|
||||||
iny
|
|
||||||
cpy #${length and 255}
|
|
||||||
beq $endLabel
|
|
||||||
jmp $loopLabel
|
|
||||||
$counterLabel .byte 0
|
|
||||||
$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()
|
|
||||||
asmgen.loopContinueLabels.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
|
|
||||||
// TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter var in such cases
|
|
||||||
if (range.isEmpty())
|
|
||||||
throw AssemblyError("empty range")
|
|
||||||
val loopLabel = asmgen.makeLabel("for_loop")
|
|
||||||
val endLabel = asmgen.makeLabel("for_end")
|
|
||||||
val continueLabel = asmgen.makeLabel("for_continue")
|
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
|
||||||
asmgen.loopContinueLabels.push(continueLabel)
|
|
||||||
when(iterableDt) {
|
|
||||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
|
||||||
val counterLabel = asmgen.makeLabel("for_counter")
|
|
||||||
// loop over byte range via loopvar
|
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
|
||||||
when {
|
|
||||||
range.step==1 -> {
|
|
||||||
// step = 1
|
|
||||||
asmgen.out("""
|
|
||||||
lda #${range.first}
|
|
||||||
sta $varname
|
|
||||||
lda #${range.last-range.first+1 and 255}
|
|
||||||
sta $counterLabel
|
|
||||||
$loopLabel""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel dec $counterLabel
|
|
||||||
beq $endLabel
|
|
||||||
inc $varname
|
|
||||||
jmp $loopLabel
|
|
||||||
$counterLabel .byte 0
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
range.step==-1 -> {
|
|
||||||
// step = -1
|
|
||||||
asmgen.out("""
|
|
||||||
lda #${range.first}
|
|
||||||
sta $varname
|
|
||||||
lda #${range.first-range.last+1 and 255}
|
|
||||||
sta $counterLabel
|
|
||||||
$loopLabel""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel dec $counterLabel
|
|
||||||
beq $endLabel
|
|
||||||
dec $varname
|
|
||||||
jmp $loopLabel
|
|
||||||
$counterLabel .byte 0
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
range.step >= 2 -> {
|
|
||||||
// step >= 2
|
|
||||||
asmgen.out("""
|
|
||||||
lda #${(range.last-range.first) / range.step + 1}
|
|
||||||
sta $counterLabel
|
|
||||||
lda #${range.first}
|
|
||||||
sta $varname
|
|
||||||
$loopLabel""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel dec $counterLabel
|
|
||||||
beq $endLabel
|
|
||||||
lda $varname
|
|
||||||
clc
|
|
||||||
adc #${range.step}
|
|
||||||
sta $varname
|
|
||||||
jmp $loopLabel
|
|
||||||
$counterLabel .byte 0
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// step <= -2
|
|
||||||
asmgen.out("""
|
|
||||||
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
|
|
||||||
sta $counterLabel
|
|
||||||
lda #${range.first}
|
|
||||||
sta $varname
|
|
||||||
$loopLabel""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel dec $counterLabel
|
|
||||||
beq $endLabel
|
|
||||||
lda $varname
|
|
||||||
sec
|
|
||||||
sbc #${range.step.absoluteValue}
|
|
||||||
sta $varname
|
|
||||||
jmp $loopLabel
|
|
||||||
$counterLabel .byte 0
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
|
||||||
// loop over word range via loopvar
|
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
|
||||||
when {
|
|
||||||
range.step == 1 -> {
|
|
||||||
// word, step = 1
|
|
||||||
val lastValue = range.last+1
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<${range.first}
|
|
||||||
ldy #>${range.first}
|
|
||||||
sta $varname
|
|
||||||
sty $varname+1
|
|
||||||
$loopLabel""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel inc $varname
|
|
||||||
bne +
|
|
||||||
inc $varname+1
|
|
||||||
+ lda $varname
|
|
||||||
cmp #<$lastValue
|
|
||||||
bne +
|
|
||||||
lda $varname+1
|
|
||||||
cmp #>$lastValue
|
|
||||||
beq $endLabel
|
|
||||||
+ jmp $loopLabel
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
range.step == -1 -> {
|
|
||||||
// word, step = 1
|
|
||||||
val lastValue = range.last-1
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<${range.first}
|
|
||||||
ldy #>${range.first}
|
|
||||||
sta $varname
|
|
||||||
sty $varname+1
|
|
||||||
$loopLabel""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel lda $varname
|
|
||||||
bne +
|
|
||||||
dec $varname+1
|
|
||||||
+ dec $varname
|
|
||||||
lda $varname
|
|
||||||
cmp #<$lastValue
|
|
||||||
bne +
|
|
||||||
lda $varname+1
|
|
||||||
cmp #>$lastValue
|
|
||||||
beq $endLabel
|
|
||||||
+ jmp $loopLabel
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
range.step >= 2 -> {
|
|
||||||
// word, step >= 2
|
|
||||||
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
|
|
||||||
val lastValue = range.last+range.step
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<${range.first}
|
|
||||||
ldy #>${range.first}
|
|
||||||
sta $varname
|
|
||||||
sty $varname+1
|
|
||||||
$loopLabel""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel clc
|
|
||||||
lda $varname
|
|
||||||
adc #<${range.step}
|
|
||||||
sta $varname
|
|
||||||
lda $varname+1
|
|
||||||
adc #>${range.step}
|
|
||||||
sta $varname+1
|
|
||||||
lda $varname
|
|
||||||
cmp #<$lastValue
|
|
||||||
bne +
|
|
||||||
lda $varname+1
|
|
||||||
cmp #>$lastValue
|
|
||||||
beq $endLabel
|
|
||||||
+ jmp $loopLabel
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// step <= -2
|
|
||||||
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
|
|
||||||
val lastValue = range.last+range.step
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<${range.first}
|
|
||||||
ldy #>${range.first}
|
|
||||||
sta $varname
|
|
||||||
sty $varname+1
|
|
||||||
$loopLabel""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel sec
|
|
||||||
lda $varname
|
|
||||||
sbc #<${range.step.absoluteValue}
|
|
||||||
sta $varname
|
|
||||||
lda $varname+1
|
|
||||||
sbc #>${range.step.absoluteValue}
|
|
||||||
sta $varname+1
|
|
||||||
lda $varname
|
|
||||||
cmp #<$lastValue
|
|
||||||
bne +
|
|
||||||
lda $varname+1
|
|
||||||
cmp #>$lastValue
|
|
||||||
beq $endLabel
|
|
||||||
+ jmp $loopLabel
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("range expression can only be byte or word")
|
|
||||||
}
|
|
||||||
asmgen.loopEndLabels.pop()
|
|
||||||
asmgen.loopContinueLabels.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,300 +0,0 @@
|
|||||||
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.AssignTarget
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.ast.statements.SubroutineParameter
|
|
||||||
import prog8.compiler.AssemblyError
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
|
||||||
import prog8.compiler.toHex
|
|
||||||
|
|
||||||
|
|
||||||
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 c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
|
||||||
|
|
||||||
val subName = asmgen.asmIdentifierName(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.
|
|
||||||
argsViaStackEvaluation(stmt, sub)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
asmgen.out(" jsr $subName")
|
|
||||||
|
|
||||||
if(saveX)
|
|
||||||
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun argsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
|
||||||
for (arg in stmt.args.reversed())
|
|
||||||
asmgen.translateExpression(arg)
|
|
||||||
for (regparam in sub.asmParameterRegisters) {
|
|
||||||
when (regparam.registerOrPair) {
|
|
||||||
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
|
||||||
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
|
|
||||||
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
|
|
||||||
RegisterOrPair.AX -> throw AssemblyError("can't pop into X register - use a variable instead")
|
|
||||||
RegisterOrPair.AY -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
|
|
||||||
RegisterOrPair.XY -> throw AssemblyError("can't pop into X register - use a variable instead")
|
|
||||||
null -> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
when (regparam.statusflag) {
|
|
||||||
Statusflag.Pc -> asmgen.out("""
|
|
||||||
inx
|
|
||||||
pha
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
beq +
|
|
||||||
sec
|
|
||||||
bcs ++
|
|
||||||
+ clc
|
|
||||||
+ pla
|
|
||||||
""")
|
|
||||||
null -> {
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("can only use Carry as status flag parameter")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(!argumentTypeCompatible(valueDt, parameter.value.type))
|
|
||||||
throw AssemblyError("argument type incompatible")
|
|
||||||
|
|
||||||
val paramVar = parameter.value
|
|
||||||
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
|
||||||
val target = AssignTarget(IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
|
||||||
target.linkParents(value.parent)
|
|
||||||
when (value) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
// optimize when the argument is a constant literal
|
|
||||||
when(parameter.value.type) {
|
|
||||||
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
|
|
||||||
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
|
|
||||||
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
|
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
|
|
||||||
else -> throw AssemblyError("weird parameter datatype")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
// optimize when the argument is a variable
|
|
||||||
when (parameter.value.type) {
|
|
||||||
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
|
|
||||||
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
|
|
||||||
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
|
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
|
|
||||||
else -> throw AssemblyError("weird parameter datatype")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is DirectMemoryRead -> {
|
|
||||||
when(value.addressExpression) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
|
||||||
asmgen.assignFromMemoryByte(target, address, null)
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateExpression(value.addressExpression)
|
|
||||||
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
|
|
||||||
asmgen.assignFromRegister(target, CpuRegister.A)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateExpression(value)
|
|
||||||
asmgen.assignFromEvalResult(target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(!argumentTypeCompatible(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.asmIdentifierName(value)
|
|
||||||
asmgen.out("""
|
|
||||||
lda $sourceName
|
|
||||||
beq +
|
|
||||||
sec
|
|
||||||
bcs ++
|
|
||||||
+ clc
|
|
||||||
+
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateExpression(value)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
pha
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
beq +
|
|
||||||
sec
|
|
||||||
bcs ++
|
|
||||||
+ clc
|
|
||||||
+ pla
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else throw AssemblyError("can only use Carry as status flag parameter")
|
|
||||||
}
|
|
||||||
register!=null && register.name.length==1 -> {
|
|
||||||
when (value) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
asmgen.assignToRegister(CpuRegister.valueOf(register.name), value.number.toShort(), null)
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
asmgen.assignToRegister(CpuRegister.valueOf(register.name), null, value)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateExpression(value)
|
|
||||||
when(register) {
|
|
||||||
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
|
||||||
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
|
|
||||||
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
|
|
||||||
else -> throw AssemblyError("cannot assign to register pair")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
register!=null && register.name.length==2 -> {
|
|
||||||
// register pair as a 16-bit value (only possible for subroutine parameters)
|
|
||||||
when (value) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
// optimize when the argument is a constant literal
|
|
||||||
val hex = value.number.toHex()
|
|
||||||
when (register) {
|
|
||||||
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
|
|
||||||
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
|
|
||||||
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is AddressOf -> {
|
|
||||||
// optimize when the argument is an address of something
|
|
||||||
val sourceName = asmgen.asmIdentifierName(value.identifier)
|
|
||||||
when (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 -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val sourceName = asmgen.asmIdentifierName(value)
|
|
||||||
if(valueDt in PassByReferenceDatatypes) {
|
|
||||||
when (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 -> {}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
when (register) {
|
|
||||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
|
||||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
|
||||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateExpression(value)
|
|
||||||
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
|
|
||||||
throw AssemblyError("can't use X register here - use a variable")
|
|
||||||
else if (register == RegisterOrPair.AY)
|
|
||||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun argumentTypeCompatible(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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,128 +0,0 @@
|
|||||||
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.target.c64.C64MachineDefinition.C64Zeropage
|
|
||||||
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.asmIdentifierName(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.asmIdentifierName(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 -> throw AssemblyError("weird target type $targetMemory")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val what = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
|
||||||
val elementDt = ArrayElementTypes.getValue(arrayDt)
|
|
||||||
when(index) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
val indexValue = index.number.toInt() * elementDt.memorySize()
|
|
||||||
when(elementDt) {
|
|
||||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what+$indexValue" else " dec $what+$indexValue")
|
|
||||||
in WordDatatypes -> {
|
|
||||||
if(incr)
|
|
||||||
asmgen.out(" inc $what+$indexValue | bne + | inc $what+$indexValue+1 |+")
|
|
||||||
else
|
|
||||||
asmgen.out("""
|
|
||||||
lda $what+$indexValue
|
|
||||||
bne +
|
|
||||||
dec $what+$indexValue+1
|
|
||||||
+ dec $what+$indexValue
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
asmgen.out(" lda #<$what+$indexValue | ldy #>$what+$indexValue")
|
|
||||||
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("need numeric type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
|
||||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
|
||||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird target type ${stmt.target}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) {
|
|
||||||
asmgen.out(" stx ${C64Zeropage.SCRATCH_REG_X} | tax")
|
|
||||||
when(arrayDt) {
|
|
||||||
DataType.STR,
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
|
||||||
asmgen.out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x")
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
||||||
if(incr)
|
|
||||||
asmgen.out(" inc $arrayVarName,x | bne + | inc $arrayVarName+1,x |+")
|
|
||||||
else
|
|
||||||
asmgen.out("""
|
|
||||||
lda $arrayVarName,x
|
|
||||||
bne +
|
|
||||||
dec $arrayVarName+1,x
|
|
||||||
+ dec $arrayVarName
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> {
|
|
||||||
asmgen.out(" lda #<$arrayVarName | ldy #>$arrayVarName")
|
|
||||||
asmgen.out(if(incr) " jsr c64flt.inc_indexed_var_f" else " jsr c64flt.dec_indexed_var_f")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird array dt")
|
|
||||||
}
|
|
||||||
asmgen.out(" ldx ${C64Zeropage.SCRATCH_REG_X}")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
1441
compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt
Normal file
1441
compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,4 @@
|
|||||||
package prog8.compiler.target.c64.codegen
|
package prog8.compiler.target.cpu6502.codegen
|
||||||
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
|
||||||
|
|
||||||
|
|
||||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
||||||
@ -78,19 +75,19 @@ private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
|||||||
|
|
||||||
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
// the when statement (on bytes) generates a sequence of:
|
// the when statement (on bytes) generates a sequence of:
|
||||||
// lda $ce01,x
|
// lda $ce01,x
|
||||||
// cmp #$20
|
// cmp #$20
|
||||||
// beq check_prog8_s72choice_32
|
// beq check_prog8_s72choice_32
|
||||||
// lda $ce01,x
|
// lda $ce01,x
|
||||||
// cmp #$21
|
// cmp #$21
|
||||||
// beq check_prog8_s73choice_33
|
// beq check_prog8_s73choice_33
|
||||||
// the repeated lda can be removed
|
// the repeated lda can be removed
|
||||||
val mods = mutableListOf<Modification>()
|
val mods = mutableListOf<Modification>()
|
||||||
for(lines in linesByFour) {
|
for(lines in linesByFour) {
|
||||||
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
|
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
|
||||||
lines[1].value.trim().startsWith("cmp ") &&
|
lines[1].value.trim().startsWith("cmp ") &&
|
||||||
lines[2].value.trim().startsWith("beq ") &&
|
lines[2].value.trim().startsWith("beq ") &&
|
||||||
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") {
|
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
|
||||||
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,10 +99,10 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
|
|||||||
// this is a lot harder for word values because the instruction sequence varies.
|
// this is a lot harder for word values because the instruction sequence varies.
|
||||||
val mods = mutableListOf<Modification>()
|
val mods = mutableListOf<Modification>()
|
||||||
for(lines in linesByFour) {
|
for(lines in linesByFour) {
|
||||||
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
|
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
|
||||||
lines[1].value.trim()=="dex" &&
|
lines[1].value.trim()=="dex" &&
|
||||||
lines[2].value.trim()=="inx" &&
|
lines[2].value.trim()=="inx" &&
|
||||||
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
|
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
|
||||||
mods.add(Modification(lines[1].index, true, null))
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
mods.add(Modification(lines[2].index, true, null))
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
mods.add(Modification(lines[3].index, true, null))
|
mods.add(Modification(lines[3].index, true, null))
|
||||||
@ -154,7 +151,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
||||||
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("jsr c64flt.copy_float")) {
|
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
|
||||||
|
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
|
||||||
|
|
||||||
val nineth = pair[8].value.trimStart()
|
val nineth = pair[8].value.trimStart()
|
||||||
val tenth = pair[9].value.trimStart()
|
val tenth = pair[9].value.trimStart()
|
||||||
@ -164,7 +162,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
val fourteenth = pair[13].value.trimStart()
|
val fourteenth = pair[13].value.trimStart()
|
||||||
|
|
||||||
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
|
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
|
||||||
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") && fourteenth.startsWith("jsr c64flt.copy_float")) {
|
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
|
||||||
|
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
|
||||||
|
|
||||||
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
||||||
// identical float init
|
// identical float init
|
||||||
@ -180,7 +179,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
|
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||||
|
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM)
|
||||||
val mods = mutableListOf<Modification>()
|
val mods = mutableListOf<Modification>()
|
||||||
for (pair in linesByFour) {
|
for (pair in linesByFour) {
|
||||||
val first = pair[0].value.trimStart()
|
val first = pair[0].value.trimStart()
|
||||||
@ -196,10 +196,14 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
|
|||||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||||
(first.startsWith("stx ") && second.startsWith("ldx "))
|
(first.startsWith("stx ") && second.startsWith("ldx "))
|
||||||
) {
|
) {
|
||||||
val firstLoc = first.substring(4)
|
val third = pair[2].value.trimStart()
|
||||||
val secondLoc = second.substring(4)
|
if(!third.startsWith("b")) {
|
||||||
if (firstLoc == secondLoc) {
|
// no branch instruction follows, we can potentiall remove the load instruction
|
||||||
mods.add(Modification(pair[1].index, true, null))
|
val firstLoc = first.substring(4).trimStart()
|
||||||
|
val secondLoc = second.substring(4).trimStart()
|
||||||
|
if (firstLoc == secondLoc) {
|
||||||
|
mods.add(Modification(pair[1].index, true, null))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,585 @@
|
|||||||
|
package prog8.compiler.target.cpu6502.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.ArrayElementTypes
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.RegisterOrPair
|
||||||
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.expressions.RangeExpr
|
||||||
|
import prog8.ast.statements.ForLoop
|
||||||
|
import prog8.ast.toHex
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
|
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("unknown 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 array, 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.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
|
||||||
|
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
|
||||||
|
asmgen.out(loopLabel)
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
$modifiedLabel cmp #0 ; modified
|
||||||
|
beq $endLabel
|
||||||
|
$incdec $varname""")
|
||||||
|
asmgen.jmp(loopLabel)
|
||||||
|
asmgen.out(endLabel)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// bytes, step >= 2 or <= -2
|
||||||
|
|
||||||
|
// loop over byte range via loopvar
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
|
||||||
|
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
|
||||||
|
asmgen.out(loopLabel)
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
if(stepsize>0) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
clc
|
||||||
|
adc #$stepsize
|
||||||
|
sta $varname
|
||||||
|
$modifiedLabel cmp #0 ; modified
|
||||||
|
bmi $loopLabel
|
||||||
|
beq $loopLabel""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
sec
|
||||||
|
sbc #${stepsize.absoluteValue}
|
||||||
|
sta $varname
|
||||||
|
$modifiedLabel cmp #0 ; modified
|
||||||
|
bpl $loopLabel""")
|
||||||
|
}
|
||||||
|
asmgen.out(endLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
|
when {
|
||||||
|
|
||||||
|
// words, step 1 or -1
|
||||||
|
|
||||||
|
stepsize == 1 || stepsize == -1 -> {
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
assignLoopvar(stmt, range)
|
||||||
|
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||||
|
asmgen.out("""
|
||||||
|
sty $modifiedLabel+1
|
||||||
|
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""")
|
||||||
|
asmgen.jmp(loopLabel)
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
+ lda $varname
|
||||||
|
bne +
|
||||||
|
dec $varname+1
|
||||||
|
+ dec $varname""")
|
||||||
|
asmgen.jmp(loopLabel)
|
||||||
|
}
|
||||||
|
asmgen.out(endLabel)
|
||||||
|
}
|
||||||
|
stepsize > 0 -> {
|
||||||
|
|
||||||
|
// (u)words, step >= 2
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
assignLoopvar(stmt, range)
|
||||||
|
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||||
|
asmgen.out("""
|
||||||
|
sty $modifiedLabel+1
|
||||||
|
sta $modifiedLabel2+1
|
||||||
|
$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""")
|
||||||
|
} 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""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
|
||||||
|
// (u)words, step <= -2
|
||||||
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
|
assignLoopvar(stmt, range)
|
||||||
|
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||||
|
asmgen.out("""
|
||||||
|
sty $modifiedLabel+1
|
||||||
|
sta $modifiedLabel2+1
|
||||||
|
$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""")
|
||||||
|
} 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""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("range expression can only be byte or word")
|
||||||
|
}
|
||||||
|
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
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)!!
|
||||||
|
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 || range.last==254) {
|
||||||
|
asmgen.out("""
|
||||||
|
inc $varname
|
||||||
|
beq $endLabel
|
||||||
|
inc $varname
|
||||||
|
bne $loopLabel""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
inc $varname
|
||||||
|
inc $varname
|
||||||
|
lda $varname
|
||||||
|
cmp #${range.last+2}
|
||||||
|
bne $loopLabel""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-2 -> {
|
||||||
|
when (range.last) {
|
||||||
|
0 -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
beq $endLabel
|
||||||
|
dec $varname
|
||||||
|
dec $varname""")
|
||||||
|
asmgen.jmp(loopLabel)
|
||||||
|
}
|
||||||
|
1 -> asmgen.out("""
|
||||||
|
dec $varname
|
||||||
|
beq $endLabel
|
||||||
|
dec $varname
|
||||||
|
bne $loopLabel""")
|
||||||
|
else -> asmgen.out("""
|
||||||
|
dec $varname
|
||||||
|
dec $varname
|
||||||
|
lda $varname
|
||||||
|
cmp #${range.last-2}
|
||||||
|
bne $loopLabel""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// step <= -3 or >= 3
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
cmp #${range.last}
|
||||||
|
beq $endLabel
|
||||||
|
clc
|
||||||
|
adc #${range.step}
|
||||||
|
sta $varname""")
|
||||||
|
asmgen.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""")
|
||||||
|
asmgen.jmp(loopLabel)
|
||||||
|
asmgen.out(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("""
|
||||||
|
inc $varname
|
||||||
|
lda $varname
|
||||||
|
cmp #${range.last+1}
|
||||||
|
bne $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""")
|
||||||
|
asmgen.jmp(loopLabel)
|
||||||
|
asmgen.out(endLabel)
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
asmgen.out("""
|
||||||
|
dec $varname
|
||||||
|
bne $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.out("""
|
||||||
|
dec $varname
|
||||||
|
lda $varname
|
||||||
|
cmp #${range.last-1}
|
||||||
|
bne $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}
|
||||||
|
beq $endLabel
|
||||||
|
+ inc $varname
|
||||||
|
bne $loopLabel
|
||||||
|
inc $varname+1""")
|
||||||
|
asmgen.jmp(loopLabel)
|
||||||
|
asmgen.out(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}
|
||||||
|
beq $endLabel
|
||||||
|
+ lda $varname
|
||||||
|
bne +
|
||||||
|
dec $varname+1
|
||||||
|
+ dec $varname""")
|
||||||
|
asmgen.jmp(loopLabel)
|
||||||
|
asmgen.out(endLabel)
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
|
||||||
|
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), stmt.definingSubroutine())
|
||||||
|
}
|
@ -0,0 +1,347 @@
|
|||||||
|
package prog8.compiler.target.cpu6502.codegen
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.target.CpuType
|
||||||
|
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||||
|
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
|
||||||
|
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
||||||
|
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
|
||||||
|
|
||||||
|
|
||||||
|
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
|
||||||
|
saveXbeforeCall(stmt)
|
||||||
|
translateFunctionCall(stmt)
|
||||||
|
restoreXafterCall(stmt)
|
||||||
|
// just ignore any result values from the function call.
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun saveXbeforeCall(stmt: IFunctionCall) {
|
||||||
|
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||||
|
if(sub.shouldSaveX()) {
|
||||||
|
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||||
|
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
|
||||||
|
if(regSaveOnStack)
|
||||||
|
asmgen.saveRegisterStack(CpuRegister.X, keepAonEntry)
|
||||||
|
else
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine()!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun restoreXafterCall(stmt: IFunctionCall) {
|
||||||
|
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||||
|
if(sub.shouldSaveX()) {
|
||||||
|
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||||
|
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
|
||||||
|
|
||||||
|
if(regSaveOnStack)
|
||||||
|
asmgen.restoreRegisterStack(CpuRegister.X, keepAonReturn)
|
||||||
|
else
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
||||||
|
// Output only the code to setup the parameters and perform the actual call
|
||||||
|
// NOTE: does NOT output the code to deal with the result values!
|
||||||
|
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||||
|
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||||
|
|
||||||
|
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||||
|
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, IndexedValue(0, sub.parameters.single()), stmt.args[0])
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fun isNoClobberRisk(expr: Expression): Boolean {
|
||||||
|
if(expr is AddressOf ||
|
||||||
|
expr is NumericLiteralValue ||
|
||||||
|
expr is StringLiteralValue ||
|
||||||
|
expr is ArrayLiteralValue ||
|
||||||
|
expr is IdentifierReference)
|
||||||
|
return true
|
||||||
|
|
||||||
|
if(expr is FunctionCall) {
|
||||||
|
if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb"))
|
||||||
|
return isNoClobberRisk(expr.args[0])
|
||||||
|
if(expr.target.nameInSource==listOf("mkword"))
|
||||||
|
return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
stmt.args.all {isNoClobberRisk(it)} -> {
|
||||||
|
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
|
||||||
|
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
|
||||||
|
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
|
||||||
|
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
|
||||||
|
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
|
||||||
|
for(arg in cx16virtualRegs)
|
||||||
|
argumentViaRegister(sub, arg.first.first, arg.first.second)
|
||||||
|
for(arg in cpuRegs)
|
||||||
|
argumentViaRegister(sub, arg.first.first, arg.first.second)
|
||||||
|
for(arg in statusRegs)
|
||||||
|
argumentViaRegister(sub, arg.first.first, arg.first.second)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// Risk of clobbering due to complex expression args. Evaluate first, then assign registers.
|
||||||
|
registerArgsViaStackEvaluation(stmt, sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sub.inline && asmgen.options.optimize) {
|
||||||
|
if(sub.containsDefinedVariables())
|
||||||
|
throw AssemblyError("can't inline sub with vars")
|
||||||
|
if(!sub.isAsmSubroutine && sub.parameters.isNotEmpty())
|
||||||
|
throw AssemblyError("can't inline a non-asm subroutine with parameters")
|
||||||
|
asmgen.out(" \t; inlined routine follows: ${sub.name} from ${sub.position}")
|
||||||
|
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
|
||||||
|
statements.forEach { asmgen.translate(it) }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
asmgen.out(" jsr $subName")
|
||||||
|
}
|
||||||
|
|
||||||
|
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
if(sub.parameters.isEmpty())
|
||||||
|
return
|
||||||
|
|
||||||
|
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
|
||||||
|
for (arg in stmt.args.reversed())
|
||||||
|
asmgen.translateExpression(arg)
|
||||||
|
|
||||||
|
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||||
|
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||||
|
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||||
|
|
||||||
|
asmgen.out(" inx") // align estack pointer
|
||||||
|
|
||||||
|
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
|
||||||
|
val plusIdxStr = if(argi.index==0) "" else "+${argi.index}"
|
||||||
|
when {
|
||||||
|
argi.value.second.statusflag == Statusflag.Pc -> {
|
||||||
|
require(argForCarry == null)
|
||||||
|
argForCarry = argi
|
||||||
|
}
|
||||||
|
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
|
||||||
|
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
|
||||||
|
require(argForXregister==null)
|
||||||
|
argForXregister = argi
|
||||||
|
}
|
||||||
|
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
|
||||||
|
require(argForAregister == null)
|
||||||
|
argForAregister = argi
|
||||||
|
}
|
||||||
|
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
|
||||||
|
asmgen.out(" ldy P8ESTACK_LO$plusIdxStr,x")
|
||||||
|
}
|
||||||
|
argi.value.second.registerOrPair in Cx16VirtualRegisters -> {
|
||||||
|
// immediately output code to load the virtual register, to avoid clobbering the A register later
|
||||||
|
when (sub.parameters[argi.index].type) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
// only load the lsb of the virtual register
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_LO$plusIdxStr,x
|
||||||
|
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
|
||||||
|
""")
|
||||||
|
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
|
asmgen.out(" stz cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
|
||||||
|
else
|
||||||
|
asmgen.out(" lda #0 | sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
|
||||||
|
}
|
||||||
|
in WordDatatypes ->
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_LO$plusIdxStr,x
|
||||||
|
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
|
||||||
|
lda P8ESTACK_HI$plusIdxStr,x
|
||||||
|
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1
|
||||||
|
""")
|
||||||
|
else -> throw AssemblyError("weird dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird argument")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argForCarry!=null) {
|
||||||
|
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_LO$plusIdxStr,x
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
bcs ++
|
||||||
|
+ clc
|
||||||
|
+ php""") // push the status flags
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argForAregister!=null) {
|
||||||
|
val plusIdxStr = if(argForAregister.index==0) "" else "+${argForAregister.index}"
|
||||||
|
when(argForAregister.value.second.registerOrPair) {
|
||||||
|
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x | ldy P8ESTACK_HI$plusIdxStr,x")
|
||||||
|
else -> throw AssemblyError("weird arg")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argForXregister!=null) {
|
||||||
|
val plusIdxStr = if(argForXregister.index==0) "" else "+${argForXregister.index}"
|
||||||
|
|
||||||
|
if(argForAregister!=null)
|
||||||
|
asmgen.out(" pha")
|
||||||
|
when(argForXregister.value.second.registerOrPair) {
|
||||||
|
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x | tax")
|
||||||
|
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO$plusIdxStr,x | lda P8ESTACK_HI$plusIdxStr,x | tax | tya")
|
||||||
|
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI$plusIdxStr,x | lda P8ESTACK_LO$plusIdxStr,x | tax")
|
||||||
|
else -> throw AssemblyError("weird arg")
|
||||||
|
}
|
||||||
|
if(argForAregister!=null)
|
||||||
|
asmgen.out(" pla")
|
||||||
|
} else {
|
||||||
|
repeat(sub.parameters.size - 1) { asmgen.out(" inx") } // unwind stack
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argForCarry!=null)
|
||||||
|
asmgen.out(" plp") // set the carry flag back to correct value
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||||
|
// pass parameter via a regular variable (not via registers)
|
||||||
|
val valueIDt = value.inferType(program)
|
||||||
|
if(!valueIDt.isKnown)
|
||||||
|
throw AssemblyError("unknown dt")
|
||||||
|
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||||
|
throw AssemblyError("argument type incompatible")
|
||||||
|
|
||||||
|
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
|
||||||
|
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
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("unknown dt")
|
||||||
|
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 requiredDt = parameter.value.type
|
||||||
|
if(requiredDt!=valueDt) {
|
||||||
|
if(valueDt largerThan requiredDt)
|
||||||
|
throw AssemblyError("can only convert byte values to word param types")
|
||||||
|
}
|
||||||
|
if (statusflag!=null) {
|
||||||
|
if(requiredDt!=valueDt)
|
||||||
|
throw AssemblyError("for statusflag, byte value is required")
|
||||||
|
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.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||||
|
asmgen.out("""
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
bcs ++
|
||||||
|
+ clc
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else throw AssemblyError("can only use Carry as status flag parameter")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// via register or register pair
|
||||||
|
register!!
|
||||||
|
if(requiredDt largerThan valueDt) {
|
||||||
|
// we need to sign extend the source, do this via temporary word variable
|
||||||
|
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
|
||||||
|
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
|
||||||
|
asmgen.signExtendVariableLsb(scratchVar, valueDt)
|
||||||
|
asmgen.assignVariableToRegister(scratchVar, register)
|
||||||
|
} else {
|
||||||
|
val target: AsmAssignTarget =
|
||||||
|
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||||
|
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
|
||||||
|
else
|
||||||
|
AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
|
||||||
|
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||||
|
if(value is IdentifierReference) {
|
||||||
|
val addr = AddressOf(value, Position.DUMMY)
|
||||||
|
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||||
|
} else {
|
||||||
|
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||||
|
}
|
||||||
|
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, 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,127 @@
|
|||||||
|
package prog8.compiler.target.cpu6502.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.ast.toHex
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
val scope = stmt.definingSubroutine()
|
||||||
|
when {
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val what = asmgen.asmVariableName(targetIdent)
|
||||||
|
when (stmt.target.inferType(program).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 floats.inc_var_f" else " jsr floats.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.assignExpressionToRegister(addressExpr, RegisterOrPair.AY)
|
||||||
|
asmgen.out(" sta (+) + 1 | sty (+) + 2")
|
||||||
|
if(incr)
|
||||||
|
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||||
|
else
|
||||||
|
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
||||||
|
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
if(targetArrayIdx.indexer.indexNum!=null) {
|
||||||
|
val indexValue = targetArrayIdx.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
|
||||||
|
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 floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("need numeric type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, scope!!)
|
||||||
|
asmgen.out(" 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,x
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #>$asmArrayvarname
|
||||||
|
clc
|
||||||
|
adc #<$asmArrayvarname
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jsr floats.inc_var_f""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird array elt dt")
|
||||||
|
}
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,220 @@
|
|||||||
|
package prog8.compiler.target.cpu6502.codegen.assignment
|
||||||
|
|
||||||
|
import prog8.ast.IMemSizer
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.target.cpu6502.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,
|
||||||
|
private val program: Program,
|
||||||
|
private val asmgen: AsmGen,
|
||||||
|
val datatype: DataType,
|
||||||
|
val scope: Subroutine?,
|
||||||
|
private val variableAsmName: String? = 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?.indexer?.constIndex() }
|
||||||
|
val asmVarname: String
|
||||||
|
get() = if(array==null)
|
||||||
|
variableAsmName!!
|
||||||
|
else
|
||||||
|
asmgen.asmVariableName(array.arrayvar)
|
||||||
|
|
||||||
|
lateinit var origAssign: AsmAssignment
|
||||||
|
|
||||||
|
init {
|
||||||
|
if(register!=null && datatype !in NumericDatatypes)
|
||||||
|
throw AssemblyError("register must be integer or float type")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
|
||||||
|
val idt = inferType(program)
|
||||||
|
if(!idt.isKnown)
|
||||||
|
throw AssemblyError("unknown dt")
|
||||||
|
val dt = idt.typeOrElse(DataType.STRUCT)
|
||||||
|
when {
|
||||||
|
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||||
|
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
|
||||||
|
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine(), memory = memoryAddress, origAstTarget = this)
|
||||||
|
else -> throw AssemblyError("weird target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
||||||
|
when(registers) {
|
||||||
|
RegisterOrPair.A,
|
||||||
|
RegisterOrPair.X,
|
||||||
|
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers)
|
||||||
|
RegisterOrPair.AX,
|
||||||
|
RegisterOrPair.AY,
|
||||||
|
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
|
||||||
|
RegisterOrPair.FAC1,
|
||||||
|
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
|
||||||
|
RegisterOrPair.R0,
|
||||||
|
RegisterOrPair.R1,
|
||||||
|
RegisterOrPair.R2,
|
||||||
|
RegisterOrPair.R3,
|
||||||
|
RegisterOrPair.R4,
|
||||||
|
RegisterOrPair.R5,
|
||||||
|
RegisterOrPair.R6,
|
||||||
|
RegisterOrPair.R7,
|
||||||
|
RegisterOrPair.R8,
|
||||||
|
RegisterOrPair.R9,
|
||||||
|
RegisterOrPair.R10,
|
||||||
|
RegisterOrPair.R11,
|
||||||
|
RegisterOrPair.R12,
|
||||||
|
RegisterOrPair.R13,
|
||||||
|
RegisterOrPair.R14,
|
||||||
|
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||||
|
private val program: Program,
|
||||||
|
private val asmgen: AsmGen,
|
||||||
|
val datatype: DataType,
|
||||||
|
private val variableAsmName: String? = null,
|
||||||
|
val array: ArrayIndexedExpression? = null,
|
||||||
|
val memory: DirectMemoryRead? = null,
|
||||||
|
val register: RegisterOrPair? = 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?.indexer?.constIndex() }
|
||||||
|
|
||||||
|
val asmVarname: String
|
||||||
|
get() = if(array==null)
|
||||||
|
variableAsmName!!
|
||||||
|
else
|
||||||
|
asmgen.asmVariableName(array.arrayvar)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource {
|
||||||
|
return when {
|
||||||
|
indexer.indexNum!=null -> fromAstSource(indexer.indexNum!!, program, asmgen)
|
||||||
|
indexer.indexVar!=null -> fromAstSource(indexer.indexVar!!, program, asmgen)
|
||||||
|
else -> throw AssemblyError("weird indexer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
|
||||||
|
val cv = value.constValue(program)
|
||||||
|
if(cv!=null)
|
||||||
|
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
||||||
|
|
||||||
|
return when(value) {
|
||||||
|
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, 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)
|
||||||
|
val varName=asmgen.asmVariableName(value)
|
||||||
|
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
||||||
|
if(dt==DataType.UWORD && varName.toLowerCase().startsWith("cx16.r")) {
|
||||||
|
val regStr = varName.toLowerCase().substring(5)
|
||||||
|
val reg = RegisterOrPair.valueOf(regStr.toUpperCase())
|
||||||
|
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
|
||||||
|
} else {
|
||||||
|
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
||||||
|
}
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||||
|
}
|
||||||
|
is FunctionCall -> {
|
||||||
|
when (val sub = value.target.targetStatement(program)) {
|
||||||
|
is Subroutine -> {
|
||||||
|
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
|
||||||
|
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||||
|
|
||||||
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||||
|
}
|
||||||
|
is BuiltinFunctionStatementPlaceholder -> {
|
||||||
|
val returnType = value.inferType(program)
|
||||||
|
if(!returnType.isKnown)
|
||||||
|
throw AssemblyError("unknown dt")
|
||||||
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.STRUCT), expression = value)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw AssemblyError("weird call")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val dt = value.inferType(program)
|
||||||
|
if(!dt.isKnown)
|
||||||
|
throw AssemblyError("unknown dt")
|
||||||
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.STRUCT), expression = value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun adjustSignedUnsigned(target: AsmAssignTarget): AsmAssignSource {
|
||||||
|
// allow some signed/unsigned relaxations
|
||||||
|
|
||||||
|
fun withAdjustedDt(newType: DataType) =
|
||||||
|
AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression)
|
||||||
|
|
||||||
|
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,
|
||||||
|
memsizer: IMemSizer,
|
||||||
|
val position: Position) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||||
|
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
|
||||||
|
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||||
|
"source storage size must be less or equal to target datatype storage size"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
114
compiler/src/prog8/compiler/target/cx16/CX16MachineDefinition.kt
Normal file
114
compiler/src/prog8/compiler/target/cx16/CX16MachineDefinition.kt
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package prog8.compiler.target.cx16
|
||||||
|
|
||||||
|
import prog8.compiler.*
|
||||||
|
import prog8.compiler.target.CpuType
|
||||||
|
import prog8.compiler.target.IMachineDefinition
|
||||||
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
internal object CX16MachineDefinition: IMachineDefinition {
|
||||||
|
|
||||||
|
override val cpu = CpuType.CPU65c02
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive
|
||||||
|
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
|
||||||
|
|
||||||
|
override lateinit var zeropage: Zeropage
|
||||||
|
|
||||||
|
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
|
||||||
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
|
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||||
|
listOf("syslib")
|
||||||
|
else
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun launchEmulator(programName: String) {
|
||||||
|
for(emulator in listOf("x16emu")) {
|
||||||
|
println("\nStarting Commander X16 emulator $emulator...")
|
||||||
|
val cmdline = listOf(emulator, "-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 isRegularRAMaddress(address: Int): Boolean = address < 0x9f00 || address in 0xa000..0xbfff
|
||||||
|
|
||||||
|
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||||
|
zeropage = CX16Zeropage(compilerOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
||||||
|
override val opcodeNames = setOf("adc", "and", "asl", "bcc", "bcs",
|
||||||
|
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
||||||
|
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey",
|
||||||
|
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
||||||
|
"inc", "inx", "iny", "jmp", "jsr",
|
||||||
|
"lda", "ldx", "ldy", "lsr", "nop", "ora", "pha", "php",
|
||||||
|
"pla", "plp", "rol", "ror", "rti", "rts", "sbc",
|
||||||
|
"sec", "sed", "sei",
|
||||||
|
"sta", "stx", "sty", "tax", "tay", "tsx", "txa", "txs", "tya",
|
||||||
|
"bra", "phx", "phy", "plx", "ply", "stz", "trb", "tsb", "bbr", "bbs",
|
||||||
|
"rmb", "smb", "stp", "wai")
|
||||||
|
|
||||||
|
|
||||||
|
internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
|
|
||||||
|
override val SCRATCH_B1 = 0x7a // temp storage for a single byte
|
||||||
|
override val SCRATCH_REG = 0x7b // temp storage for a register, must be B1+1
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||||
|
}
|
||||||
|
ZeropageType.KERNALSAFE -> {
|
||||||
|
free.addAll(0x22..0x7f)
|
||||||
|
free.addAll(0xa9..0xff)
|
||||||
|
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||||
|
}
|
||||||
|
ZeropageType.BASICSAFE -> {
|
||||||
|
free.addAll(0x22..0x7f)
|
||||||
|
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||||
|
}
|
||||||
|
ZeropageType.DONTUSE -> {
|
||||||
|
free.clear() // don't use zeropage at all
|
||||||
|
}
|
||||||
|
else -> throw CompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||||
|
}
|
||||||
|
|
||||||
|
require(SCRATCH_B1 !in free)
|
||||||
|
require(SCRATCH_REG !in free)
|
||||||
|
require(SCRATCH_W1 !in free)
|
||||||
|
require(SCRATCH_W2 !in free)
|
||||||
|
|
||||||
|
for (reserved in options.zpReserved)
|
||||||
|
reserve(reserved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,397 +0,0 @@
|
|||||||
package prog8.functions
|
|
||||||
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.compiler.CompilerException
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
|
|
||||||
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
|
|
||||||
|
|
||||||
|
|
||||||
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
|
|
||||||
|
|
||||||
|
|
||||||
class FSignature(val pure: Boolean, // does it have side effects?
|
|
||||||
val parameters: List<FParam>,
|
|
||||||
val returntype: DataType?,
|
|
||||||
val constExpressionFunc: ConstExpressionCaller? = null)
|
|
||||||
|
|
||||||
|
|
||||||
val BuiltinFunctions = mapOf(
|
|
||||||
// this set of function have no return value and operate in-place:
|
|
||||||
"rol" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
|
||||||
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
|
||||||
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
|
||||||
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
|
||||||
"lsl" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
|
|
||||||
"lsr" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
|
|
||||||
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
|
||||||
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
|
||||||
// these few have a return value depending on the argument(s):
|
|
||||||
"max" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // 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 FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
|
||||||
"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
|
|
||||||
// normal functions follow:
|
|
||||||
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
|
||||||
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
|
|
||||||
"sin8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
|
||||||
"sin8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
|
||||||
"sin16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
|
||||||
"sin16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
|
||||||
"cos" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
|
|
||||||
"cos8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
|
||||||
"cos8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
|
||||||
"cos16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
|
||||||
"cos16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
|
||||||
"tan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
|
|
||||||
"atan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
|
|
||||||
"ln" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
|
|
||||||
"log2" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
|
|
||||||
"sqrt16" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
|
||||||
"sqrt" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
|
||||||
"rad" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
|
||||||
"deg" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
|
||||||
"round" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
|
||||||
"floor" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
|
||||||
"ceil" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
|
||||||
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
|
||||||
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
|
||||||
"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 }},
|
|
||||||
"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}},
|
|
||||||
"mkword" to FSignature(true, listOf(FParam("lsb", setOf(DataType.UBYTE)), FParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
|
||||||
"rnd" to FSignature(true, emptyList(), DataType.UBYTE),
|
|
||||||
"rndw" to FSignature(true, emptyList(), DataType.UWORD),
|
|
||||||
"rndf" to FSignature(true, emptyList(), DataType.FLOAT),
|
|
||||||
"exit" to FSignature(false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
|
|
||||||
"rsave" to FSignature(false, emptyList(), null),
|
|
||||||
"rrestore" to FSignature(false, emptyList(), null),
|
|
||||||
"set_carry" to FSignature(false, emptyList(), null),
|
|
||||||
"clear_carry" to FSignature(false, emptyList(), null),
|
|
||||||
"set_irqd" to FSignature(false, emptyList(), null),
|
|
||||||
"clear_irqd" to FSignature(false, emptyList(), null),
|
|
||||||
"read_flags" to FSignature(false, emptyList(), DataType.UBYTE),
|
|
||||||
"swap" to FSignature(false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
|
||||||
"memcopy" to FSignature(false, listOf(
|
|
||||||
FParam("from", IterableDatatypes + DataType.UWORD),
|
|
||||||
FParam("to", IterableDatatypes + DataType.UWORD),
|
|
||||||
FParam("numbytes", setOf(DataType.UBYTE))), null),
|
|
||||||
"memset" to FSignature(false, listOf(
|
|
||||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
|
||||||
FParam("numbytes", setOf(DataType.UWORD)),
|
|
||||||
FParam("bytevalue", ByteDatatypes)), null),
|
|
||||||
"memsetw" to FSignature(false, listOf(
|
|
||||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
|
||||||
FParam("numwords", setOf(DataType.UWORD)),
|
|
||||||
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
|
||||||
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
|
|
||||||
"substr" to FSignature(false, listOf(
|
|
||||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
|
||||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
|
||||||
FParam("start", setOf(DataType.UBYTE)),
|
|
||||||
FParam("length", setOf(DataType.UBYTE))), null),
|
|
||||||
"leftstr" to FSignature(false, listOf(
|
|
||||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
|
||||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
|
||||||
FParam("length", setOf(DataType.UBYTE))), null),
|
|
||||||
"rightstr" to FSignature(false, listOf(
|
|
||||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
|
||||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
|
||||||
FParam("length", setOf(DataType.UBYTE))), null)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
|
|
||||||
|
|
||||||
fun builtinMin(array: List<Number>): Number = array.minBy { it.toDouble() }!!
|
|
||||||
|
|
||||||
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
|
|
||||||
|
|
||||||
fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0
|
|
||||||
|
|
||||||
fun builtinAll(array: List<Number>): Number = if(array.all { it.toDouble()!=0.0 }) 1 else 0
|
|
||||||
|
|
||||||
|
|
||||||
fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): InferredTypes.InferredType {
|
|
||||||
|
|
||||||
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) {
|
|
||||||
val idt = arglist.inferType(program)
|
|
||||||
if(!idt.isKnown)
|
|
||||||
throw FatalAstException("couldn't determine type of iterable $arglist")
|
|
||||||
return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
|
|
||||||
DataType.STR, in NumericDatatypes -> dt
|
|
||||||
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
|
|
||||||
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw FatalAstException("function '$function' requires one argument which is an iterable")
|
|
||||||
}
|
|
||||||
|
|
||||||
val func = BuiltinFunctions.getValue(function)
|
|
||||||
if(func.returntype!=null)
|
|
||||||
return InferredTypes.knownFor(func.returntype)
|
|
||||||
// function has return values, but the return type depends on the arguments
|
|
||||||
|
|
||||||
return when (function) {
|
|
||||||
"abs" -> {
|
|
||||||
val dt = args.single().inferType(program)
|
|
||||||
if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
|
|
||||||
return dt
|
|
||||||
else
|
|
||||||
throw FatalAstException("weird datatype passed to abs $dt")
|
|
||||||
}
|
|
||||||
"max", "min" -> {
|
|
||||||
when(val dt = datatypeFromIterableArg(args.single())) {
|
|
||||||
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
|
||||||
in NumericDatatypes -> InferredTypes.knownFor(dt)
|
|
||||||
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
|
|
||||||
else -> InferredTypes.unknown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"sum" -> {
|
|
||||||
when(datatypeFromIterableArg(args.single())) {
|
|
||||||
DataType.UBYTE, DataType.UWORD -> InferredTypes.knownFor(DataType.UWORD)
|
|
||||||
DataType.BYTE, DataType.WORD -> InferredTypes.knownFor(DataType.WORD)
|
|
||||||
DataType.FLOAT -> InferredTypes.knownFor(DataType.FLOAT)
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_UW -> InferredTypes.knownFor(DataType.UWORD)
|
|
||||||
DataType.ARRAY_B, DataType.ARRAY_W -> InferredTypes.knownFor(DataType.WORD)
|
|
||||||
DataType.ARRAY_F -> InferredTypes.knownFor(DataType.FLOAT)
|
|
||||||
DataType.STR -> InferredTypes.knownFor(DataType.UWORD)
|
|
||||||
else -> InferredTypes.unknown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"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 CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
|
|
||||||
|
|
||||||
|
|
||||||
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
|
||||||
if(args.size!=1)
|
|
||||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val float = constval.number.toDouble()
|
|
||||||
return numericLiteral(function(float), args[0].position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun oneDoubleArgOutputWord(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
|
||||||
if(args.size!=1)
|
|
||||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val float = constval.number.toDouble()
|
|
||||||
return NumericLiteralValue(DataType.WORD, function(float).toInt(), args[0].position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Number): NumericLiteralValue {
|
|
||||||
if(args.size!=1)
|
|
||||||
throw SyntaxError("built-in function requires one integer argument", position)
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
if(constval.type != DataType.UBYTE && constval.type!= DataType.UWORD)
|
|
||||||
throw SyntaxError("built-in function requires one integer argument", position)
|
|
||||||
|
|
||||||
val integer = constval.number.toInt()
|
|
||||||
return numericLiteral(function(integer).toInt(), args[0].position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Number>)->Number): NumericLiteralValue {
|
|
||||||
if(args.size!=1)
|
|
||||||
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
|
||||||
|
|
||||||
val array= args[0] as? ArrayLiteralValue ?: throw NotConstArgumentException()
|
|
||||||
val constElements = array.value.map{it.constValue(program)?.number}
|
|
||||||
if(constElements.contains(null))
|
|
||||||
throw NotConstArgumentException()
|
|
||||||
|
|
||||||
return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
|
||||||
// 1 arg, type = float or int, result type= isSameAs as argument type
|
|
||||||
if(args.size!=1)
|
|
||||||
throw SyntaxError("abs requires one numeric argument", position)
|
|
||||||
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
return when (constval.type) {
|
|
||||||
in IntegerDatatypes -> numericLiteral(abs(constval.number.toInt()), args[0].position)
|
|
||||||
DataType.FLOAT -> numericLiteral(abs(constval.number.toDouble()), args[0].position)
|
|
||||||
else -> throw SyntaxError("abs requires one numeric argument", position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
|
||||||
if (args.size != 1)
|
|
||||||
throw SyntaxError("strlen requires one argument", position)
|
|
||||||
val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
if(argument.type != DataType.STR)
|
|
||||||
throw SyntaxError("strlen must have string argument", position)
|
|
||||||
|
|
||||||
throw NotConstArgumentException() // this function is not considering the string argument a constant
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
throw SyntaxError("len requires one argument", position)
|
|
||||||
val constArg = args[0].constValue(program)
|
|
||||||
if(constArg!=null)
|
|
||||||
throw SyntaxError("len of weird argument ${args[0]}", position)
|
|
||||||
|
|
||||||
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
|
|
||||||
var arraySize = directMemVar?.arraysize?.size()
|
|
||||||
if(arraySize != null)
|
|
||||||
return NumericLiteralValue.optimalInteger(arraySize, position)
|
|
||||||
if(args[0] is ArrayLiteralValue)
|
|
||||||
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
|
|
||||||
if(args[0] !is IdentifierReference)
|
|
||||||
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
|
|
||||||
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
|
|
||||||
?: throw CannotEvaluateException("len", "no target vardecl")
|
|
||||||
|
|
||||||
return when(target.datatype) {
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
||||||
arraySize = target.arraysize?.size()
|
|
||||||
if(arraySize==null)
|
|
||||||
throw CannotEvaluateException("len", "arraysize unknown")
|
|
||||||
if(arraySize>256)
|
|
||||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
|
||||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> {
|
|
||||||
arraySize = target.arraysize?.size()
|
|
||||||
if(arraySize==null)
|
|
||||||
throw CannotEvaluateException("len", "arraysize unknown")
|
|
||||||
if(arraySize>256)
|
|
||||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
|
||||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
|
||||||
}
|
|
||||||
DataType.STR -> {
|
|
||||||
val refLv = target.value as StringLiteralValue
|
|
||||||
if(refLv.value.length>255)
|
|
||||||
throw CompilerException("string length exceeds byte limit ${refLv.position}")
|
|
||||||
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
|
|
||||||
}
|
|
||||||
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
|
|
||||||
else -> throw CompilerException("weird datatype")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
|
||||||
if (args.size != 2)
|
|
||||||
throw SyntaxError("mkword requires lsb and msb arguments", position)
|
|
||||||
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
|
|
||||||
return NumericLiteralValue(DataType.UWORD, result, position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
|
||||||
if (args.size != 1)
|
|
||||||
throw SyntaxError("sin8 requires one argument", position)
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
|
||||||
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toShort(), position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
|
||||||
if (args.size != 1)
|
|
||||||
throw SyntaxError("sin8u requires one argument", position)
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
|
||||||
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort(), position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
|
||||||
if (args.size != 1)
|
|
||||||
throw SyntaxError("cos8 requires one argument", position)
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
|
||||||
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toShort(), position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
|
||||||
if (args.size != 1)
|
|
||||||
throw SyntaxError("cos8u requires one argument", position)
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
|
||||||
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort(), position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
|
||||||
if (args.size != 1)
|
|
||||||
throw SyntaxError("sin16 requires one argument", position)
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
|
||||||
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
|
||||||
if (args.size != 1)
|
|
||||||
throw SyntaxError("sin16u requires one argument", position)
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
|
||||||
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
|
||||||
if (args.size != 1)
|
|
||||||
throw SyntaxError("cos16 requires one argument", position)
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
|
||||||
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
|
||||||
if (args.size != 1)
|
|
||||||
throw SyntaxError("cos16u requires one argument", position)
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
|
||||||
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
|
|
||||||
}
|
|
||||||
|
|
||||||
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.toShort(), position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
|
|
||||||
val floatNum=value.toDouble()
|
|
||||||
val tweakedValue: Number =
|
|
||||||
if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
|
|
||||||
floatNum.toInt() // we have an integer disguised as a float.
|
|
||||||
else
|
|
||||||
floatNum
|
|
||||||
|
|
||||||
return when(tweakedValue) {
|
|
||||||
is Int -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
|
|
||||||
is Short -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
|
|
||||||
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
|
|
||||||
is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
|
||||||
is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
|
||||||
else -> throw FatalAstException("invalid number type ${value::class}")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,157 +0,0 @@
|
|||||||
package prog8.optimizer
|
|
||||||
|
|
||||||
import prog8.ast.Node
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.ErrorReporter
|
|
||||||
import prog8.ast.expressions.BinaryExpression
|
|
||||||
import prog8.ast.processing.AstWalker
|
|
||||||
import prog8.ast.processing.IAstModification
|
|
||||||
import prog8.ast.statements.Assignment
|
|
||||||
import prog8.ast.statements.PostIncrDecr
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal class AssignmentTransformer(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
|
||||||
|
|
||||||
var optimizationsDone: Int = 0
|
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
|
|
||||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
|
||||||
// modify A = A + 5 back into augmented form A += 5 for easier code generation for optimized in-place assignments
|
|
||||||
// also to put code generation stuff together, single value assignment (A = 5) is converted to a special
|
|
||||||
// augmented form as wel (with the operator "setvalue")
|
|
||||||
if (assignment.aug_op == null) {
|
|
||||||
val binExpr = assignment.value as? BinaryExpression
|
|
||||||
if (binExpr != null) {
|
|
||||||
if (assignment.target.isSameAs(binExpr.left)) {
|
|
||||||
assignment.value = binExpr.right
|
|
||||||
assignment.aug_op = binExpr.operator + "="
|
|
||||||
assignment.value.parent = assignment
|
|
||||||
optimizationsDone++
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assignment.aug_op = "setvalue"
|
|
||||||
optimizationsDone++
|
|
||||||
} else if(assignment.aug_op == "+=") {
|
|
||||||
val binExpr = assignment.value as? BinaryExpression
|
|
||||||
if (binExpr != null) {
|
|
||||||
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
|
|
||||||
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
|
|
||||||
if(binExpr.operator == "+") {
|
|
||||||
when {
|
|
||||||
leftnum == 1.0 -> {
|
|
||||||
optimizationsDone++
|
|
||||||
return listOf(IAstModification.SwapOperands(binExpr))
|
|
||||||
}
|
|
||||||
leftnum == 2.0 -> {
|
|
||||||
optimizationsDone++
|
|
||||||
return listOf(IAstModification.SwapOperands(binExpr))
|
|
||||||
}
|
|
||||||
rightnum == 1.0 -> {
|
|
||||||
// x += y + 1 -> x += y , x++
|
|
||||||
return listOf(
|
|
||||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
rightnum == 2.0 -> {
|
|
||||||
// x += y + 2 -> x += y , x++, x++
|
|
||||||
return listOf(
|
|
||||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(binExpr.operator == "-") {
|
|
||||||
when {
|
|
||||||
leftnum == 1.0 -> {
|
|
||||||
optimizationsDone++
|
|
||||||
return listOf(IAstModification.SwapOperands(binExpr))
|
|
||||||
}
|
|
||||||
leftnum == 2.0 -> {
|
|
||||||
optimizationsDone++
|
|
||||||
return listOf(IAstModification.SwapOperands(binExpr))
|
|
||||||
}
|
|
||||||
rightnum == 1.0 -> {
|
|
||||||
// x += y - 1 -> x += y , x--
|
|
||||||
return listOf(
|
|
||||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
rightnum == 2.0 -> {
|
|
||||||
// x += y - 2 -> x += y , x--, x--
|
|
||||||
return listOf(
|
|
||||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(assignment.aug_op == "-=") {
|
|
||||||
val binExpr = assignment.value as? BinaryExpression
|
|
||||||
if (binExpr != null) {
|
|
||||||
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
|
|
||||||
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
|
|
||||||
if(binExpr.operator == "+") {
|
|
||||||
when {
|
|
||||||
leftnum == 1.0 -> {
|
|
||||||
optimizationsDone++
|
|
||||||
return listOf(IAstModification.SwapOperands(binExpr))
|
|
||||||
}
|
|
||||||
leftnum == 2.0 -> {
|
|
||||||
optimizationsDone++
|
|
||||||
return listOf(IAstModification.SwapOperands(binExpr))
|
|
||||||
}
|
|
||||||
rightnum == 1.0 -> {
|
|
||||||
// x -= y + 1 -> x -= y , x--
|
|
||||||
return listOf(
|
|
||||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
rightnum == 2.0 -> {
|
|
||||||
// x -= y + 2 -> x -= y , x--, x--
|
|
||||||
return listOf(
|
|
||||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(binExpr.operator == "-") {
|
|
||||||
when {
|
|
||||||
leftnum == 1.0 -> {
|
|
||||||
optimizationsDone++
|
|
||||||
return listOf(IAstModification.SwapOperands(binExpr))
|
|
||||||
}
|
|
||||||
leftnum == 2.0 -> {
|
|
||||||
optimizationsDone++
|
|
||||||
return listOf(IAstModification.SwapOperands(binExpr))
|
|
||||||
}
|
|
||||||
rightnum == 1.0 -> {
|
|
||||||
// x -= y - 1 -> x -= y , x++
|
|
||||||
return listOf(
|
|
||||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
rightnum == 2.0 -> {
|
|
||||||
// x -= y - 2 -> x -= y , x++, x++
|
|
||||||
return listOf(
|
|
||||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
|
|
||||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user