mirror of
https://github.com/irmen/prog8.git
synced 2025-06-14 11:23:37 +00:00
Compare commits
773 Commits
v5.2
...
version7.1
Author | SHA1 | Date | |
---|---|---|---|
7f8fe75ab2 | |||
44143f481a | |||
440abf4998 | |||
3c10427e04 | |||
85956b5828 | |||
41e40cad03 | |||
df2d5c6585 | |||
f696fce187 | |||
82d3d81bb2 | |||
f0cff661df | |||
82d20dea39 | |||
804bb06859 | |||
5afa7e53f8 | |||
7f15b7b716 | |||
552e0c2248 | |||
e5b9e1f5e7 | |||
502bf90007 | |||
40bf117497 | |||
4011dce31b | |||
9e881e32f8 | |||
637a8899c5 | |||
cf0e395921 | |||
6ef438ce50 | |||
46e4b977a4 | |||
9626c5dead | |||
aea364e43d | |||
06defd0cb0 | |||
0f80897c50 | |||
57bb1c2c0d | |||
7b35b414e8 | |||
761aac7a23 | |||
15a02d7664 | |||
16ed68c1ec | |||
e63cf660c6 | |||
aaff484306 | |||
3281d9a215 | |||
0fcd61e00f | |||
c4523ea470 | |||
0447b3e4cc | |||
4d27c2901b | |||
855e18b31c | |||
d790878af6 | |||
85b244df2f | |||
6070afa6b6 | |||
975594703d | |||
6b8c3ef614 | |||
9b22f05381 | |||
ca3a990f9e | |||
557f4f689f | |||
66574d058a | |||
1c7c67060d | |||
9827ee97ad | |||
71a9a84211 | |||
367a2a4cee | |||
4f7465ba44 | |||
f891fc698c | |||
36bec62c9a | |||
dd5a2c8315 | |||
56bff46481 | |||
b83a0adb19 | |||
92ffefe656 | |||
51b2e41879 | |||
ef43bc9208 | |||
33733a4001 | |||
e5a1b37981 | |||
30aa72dc8e | |||
2c2d474059 | |||
c55ac0450f | |||
2d26b9c994 | |||
f38fe092ee | |||
7a33eb163b | |||
5db0408b9f | |||
3557d38ce0 | |||
7de4e9e66a | |||
73838ccb8b | |||
0509de76d5 | |||
f4b3d19059 | |||
f37fb82d53 | |||
dbe98f3fa5 | |||
371d4768e6 | |||
562d8386ec | |||
1625e4eb85 | |||
2365a076ac | |||
9898791771 | |||
a1c658274d | |||
be9998b48b | |||
e8f308f654 | |||
07132a2c42 | |||
9c4582e283 | |||
0204002d9b | |||
b3107cfad0 | |||
91ae68c07e | |||
06b3bf27b5 | |||
fbef63e150 | |||
bb8ee9bb3e | |||
25677a4126 | |||
3aeca0a770 | |||
4bd4733e52 | |||
9acec4d952 | |||
8388adcd1d | |||
5988ba76b5 | |||
1a06e7a16e | |||
7241cef7a5 | |||
5145296486 | |||
2cbf2d2226 | |||
754664aefa | |||
af99173cd7 | |||
fd1f30f92b | |||
d9ab2f8b90 | |||
bd6c60cf8a | |||
f0c150d93b | |||
c2986eaf47 | |||
ef0c4797bb | |||
ac02a99934 | |||
fb67d1155f | |||
eb46852bb9 | |||
007d8d2811 | |||
ebe04fc114 | |||
d7dd7f70c0 | |||
f2cb89a128 | |||
b8fade23de | |||
3b97a17648 | |||
0d06e3ff22 | |||
c914f7bbcf | |||
1b451180c1 | |||
ed061b362b | |||
e1026584c8 | |||
4c615e4fac | |||
7c9d48833b | |||
b60b195aec | |||
db76c8d7f4 | |||
de92740e87 | |||
522bf91c30 | |||
48d3abc1fe | |||
3f6f25e06f | |||
34ba07ee3b | |||
ac37319d20 | |||
b2c6274f74 | |||
402884b5ce | |||
23c99002c0 | |||
ee115b3337 | |||
82f5a141ed | |||
0567168ea9 | |||
c80a15846d | |||
5e194536a8 | |||
43c5ab8ecc | |||
cd295228ef | |||
6c42221620 | |||
0d73a7cd07 | |||
39d5b7edb0 | |||
6fa50a699f | |||
ddaef3e5d5 | |||
c3e9d4a9f8 | |||
7530fb67c8 | |||
19bb56df47 | |||
b0073ac933 | |||
137a89da15 | |||
44da7a302f | |||
4096aae8d4 | |||
d3e026d82a | |||
fa5ecd6495 | |||
af209ad50e | |||
7b89228fa7 | |||
d31a88206c | |||
cd4ed8765b | |||
b6f780d70d | |||
b071a58ca7 | |||
ce554f7718 | |||
99b1cec2e1 | |||
46911a8905 | |||
4eb61529f6 | |||
81abf29bec | |||
85897ef8cd | |||
b824c0b125 | |||
6367c6d116 | |||
a7736d88a9 | |||
049dbf5a78 | |||
4ac92caeb5 | |||
7af3da2a97 | |||
95a62fcdd1 | |||
7872d20554 | |||
a598eb7e98 | |||
c786acc39b | |||
07d8052a57 | |||
db9edb477e | |||
9bd3a6758a | |||
2cb1560bbd | |||
006059438f | |||
84ea3b9788 | |||
b667abde10 | |||
aa10997171 | |||
7880ac1909 | |||
f53848b4b9 | |||
82f2f38680 | |||
dcc2549574 | |||
cfe4753913 | |||
fcb1a7e4d4 | |||
ce76a7dfa5 | |||
7c1de81861 | |||
eddad20acc | |||
7daad57862 | |||
1468049fe9 | |||
3b91e59a79 | |||
3496a30528 | |||
32bad5df15 | |||
3f58eca1be | |||
2350843d1d | |||
a2588a178c | |||
e5292696c4 | |||
34b2a65ccb | |||
3aa3659bc7 | |||
b8117394c0 | |||
fd2bbd2b59 | |||
127c470746 | |||
f2844bdf1a | |||
c5bfef4264 | |||
73863acc12 | |||
19e99204b9 | |||
13f5b94c3e | |||
3a2498401d | |||
53b20ba625 | |||
e7f6f0950f | |||
9fbe1b38a5 | |||
078485134d | |||
67b1766e32 | |||
d4b69ac79c | |||
e61a2d7083 | |||
c03f6604af | |||
572bb38ddb | |||
42c5c0cb9f | |||
e145d2255e | |||
442fa07dd4 | |||
31ae9e1243 | |||
d7f83f8df2 | |||
29e2d4e0c8 | |||
2732d2c844 | |||
c4a037b277 | |||
0e614ad6fc | |||
ca1a8cd617 | |||
ba96a637be | |||
c2cac772e3 | |||
6b7216f4ec | |||
e4fb5946dd | |||
ca61248861 | |||
68d7b4649e | |||
0416aacbbd | |||
bc731e6f8e | |||
ae5d7705bb | |||
b9bd541532 | |||
83639c2535 | |||
25d80f4df1 | |||
74f918d911 | |||
a20efa56eb | |||
f4d83075be | |||
254592c383 | |||
ee23ac0537 | |||
a48cf0bb24 | |||
dae59238cd | |||
8736da1a21 | |||
09a1de69e7 | |||
63d67bc6cb | |||
7099245204 | |||
4d097d2139 | |||
6485bf9ad9 | |||
b7c5b1bfc7 | |||
2b7546e827 | |||
3549ccf4b3 | |||
e2f5752d9a | |||
1a59019fc8 | |||
7bac7bdc3e | |||
19fe58dbac | |||
0a5b30e21c | |||
664818fd29 | |||
d5214e2505 | |||
d906fcea0e | |||
29c8e8b740 | |||
71fec4c555 | |||
5ee36c897d | |||
4aba0c7405 | |||
ed7479c854 | |||
8d3d5f726a | |||
a9a7068818 | |||
1bde7c7718 | |||
17068130bb | |||
81a91d62cb | |||
2575263438 | |||
7f0e25cb50 | |||
a1e4e9c50f | |||
98eff2701b | |||
8b84f87217 | |||
306a1b7bc2 | |||
481214c46e | |||
a5961cbeab | |||
3bf335e0a0 | |||
68f696d165 | |||
1170aed026 | |||
bf1b2066b6 | |||
4c080afb76 | |||
ee1c43ca91 | |||
1c2e6f9e4c | |||
dd379430d9 | |||
42033ebd35 | |||
a086d6e009 | |||
c70bbdab26 | |||
3d956ef554 | |||
329f491c30 | |||
e93701f50e | |||
e680de05ea | |||
56fec674c5 | |||
54d92a027a | |||
319ac3a641 | |||
0a03c46351 | |||
ae1b62e147 | |||
8d567f6b06 | |||
b1ef09675b | |||
2b7b925090 | |||
e0454e95db | |||
91e421d961 | |||
c853afe769 | |||
1a64cb38d5 | |||
ccebd22856 | |||
a1f3b82333 | |||
3dda29781e | |||
a9d297ee31 | |||
e5ff61f201 | |||
d116eb7655 | |||
bc726c6334 | |||
123473dfc8 | |||
d9eccd4fba | |||
5b890847e5 | |||
64c85b9617 | |||
3e3b0bcd8b | |||
4c1eb1b12a | |||
530d03d284 | |||
619fa9b65e | |||
0032235933 | |||
61d1f1ea87 | |||
238d27acdc | |||
2f62271453 | |||
75d5117a2d | |||
b4700af2f5 | |||
374e2b311d | |||
49036abbaf | |||
38ccbac97c | |||
6b4896b8f5 | |||
d582d1cc42 | |||
9e2b8a2aa9 | |||
6fdc733941 | |||
422b390c48 | |||
67a9d1285c | |||
8e26e38ecc | |||
02e12d8575 | |||
fe2954ce08 | |||
1fe4439395 | |||
2ff04d2abd | |||
3f30d3aa89 | |||
129e17b33a | |||
bf2d8c3f4b | |||
b29f04ce01 | |||
d185ebad48 | |||
605df7c91c | |||
ec60cad8bb | |||
6aa0f5a392 | |||
4cae2c56ec | |||
d840975054 | |||
1b14da6c03 | |||
292640b17a | |||
112a7b09f2 | |||
863ec9ce8a | |||
2eb346a205 | |||
8092355acb | |||
e7ef2ed31b | |||
af4de6d2fc | |||
69f73dd779 | |||
9706b46012 | |||
6d75dd3bb8 | |||
bd295ffc99 | |||
07ce3e3c9d | |||
cbc3e37a89 | |||
3626828ceb | |||
24b77fb5a5 | |||
1505fe686a | |||
0991131fa8 | |||
2e928bd3c2 | |||
ca868ae19e | |||
3e286dd14c | |||
11247d52b1 | |||
1dbc902513 | |||
330e691b78 | |||
6780d4f562 | |||
b30b8b7368 | |||
3df182b8c3 | |||
7f21d89fea | |||
2b267b4ba1 | |||
ef64881528 | |||
9a6bd760bd | |||
00b9766aea | |||
6381d2b6ac | |||
d2ab5f230d | |||
824b41d457 | |||
b5523c7077 | |||
eb3594b18c | |||
852d85d010 | |||
5e0aef04fe | |||
a00c693f93 | |||
c943da1448 | |||
b630fae580 | |||
38e40084f1 | |||
bf23ad78e6 | |||
ded1d19737 | |||
496a3b0d2c | |||
6922333755 | |||
a00c39e9cf | |||
1c1da8e38e | |||
50a306f492 | |||
6995ee2d17 | |||
6c60ea9cac | |||
2431ed811a | |||
6bd205c02a | |||
62ec77e148 | |||
9120e1de88 | |||
60e169bd87 | |||
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 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -26,6 +26,9 @@ parser.out
|
||||
parsetab.py
|
||||
.pytest_cache/
|
||||
.attach_pid*
|
||||
compiler/lib/
|
||||
|
||||
.gradle
|
||||
/prog8compiler.jar
|
||||
sd*.img
|
||||
*.d64
|
||||
|
8
.idea/codeInsightSettings.xml
generated
Normal file
8
.idea/codeInsightSettings.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaProjectCodeInsightSettings">
|
||||
<excluded-names>
|
||||
<name>kotlin.Result</name>
|
||||
</excluded-names>
|
||||
</component>
|
||||
</project>
|
7
.idea/compiler.xml
generated
Normal file
7
.idea/compiler.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<option name="BUILD_PROCESS_HEAP_SIZE" value="1200" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
7
.idea/inspectionProfiles/Project_Default.xml
generated
7
.idea/inspectionProfiles/Project_Default.xml
generated
@ -3,14 +3,11 @@
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<Languages>
|
||||
<language minSize="100" isEnabled="false" name="JavaScript" />
|
||||
<language isEnabled="false" name="Groovy" />
|
||||
<language isEnabled="false" name="Style Sheets" />
|
||||
<language minSize="70" name="Kotlin" />
|
||||
<language isEnabled="false" name="TypeScript" />
|
||||
<language isEnabled="false" name="ActionScript" />
|
||||
<language isEnabled="false" name="Groovy" />
|
||||
</Languages>
|
||||
</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">
|
||||
<option name="processCode" value="false" />
|
||||
<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"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="1.8" />
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
</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>
|
9
.idea/libraries/antlr_4_8_complete.xml
generated
9
.idea/libraries/antlr_4_8_complete.xml
generated
@ -1,9 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr-4.8-complete">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.8-complete.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
19
.idea/libraries/antlr_antlr4.xml
generated
Normal file
19
.idea/libraries/antlr_antlr4.xml
generated
Normal file
@ -0,0 +1,19 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.9.2">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.9.2/antlr4-4.9.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.9.2/antlr4-runtime-4.9.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.2/antlr-runtime-3.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3/ST4-4.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
9
.idea/libraries/antlr_runtime_4_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>
|
9
.idea/libraries/antlr_runtime_4_8.xml
generated
9
.idea/libraries/antlr_runtime_4_8.xml
generated
@ -1,9 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr-runtime-4.8">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.8.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
25
.idea/libraries/github_hypfvieh_dbus_java.xml
generated
Normal file
25
.idea/libraries/github_hypfvieh_dbus_java.xml
generated
Normal file
@ -0,0 +1,25 @@
|
||||
<component name="libraryTable">
|
||||
<library name="github.hypfvieh.dbus.java" type="repository">
|
||||
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.0/dbus-java-3.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.5/jnr-unixsocket-0.38.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.1/jnr-ffi-2.2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1-native.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.0/asm-9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.0/asm-commons-9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.0/asm-analysis-9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.0/asm-tree-9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.0/asm-util-9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.3/jnr-enxio-0.32.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.4/jnr-posix-3.1.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
10
.idea/libraries/glassfish_javax_json.xml
generated
Normal file
10
.idea/libraries/glassfish_javax_json.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="glassfish.javax.json" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.glassfish:javax.json:1.1.4" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
10
.idea/libraries/hamcrest.xml
generated
Normal file
10
.idea/libraries/hamcrest.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="hamcrest" type="repository">
|
||||
<properties maven-id="org.hamcrest:hamcrest:2.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
10
.idea/libraries/jetbrains_kotlinx_cli_jvm.xml
generated
Normal file
10
.idea/libraries/jetbrains_kotlinx_cli_jvm.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.3" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.3/kotlinx-cli-jvm-0.3.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
17
.idea/libraries/junit_jupiter.xml
generated
Normal file
17
.idea/libraries/junit_jupiter.xml
generated
Normal file
@ -0,0 +1,17 @@
|
||||
<component name="libraryTable">
|
||||
<library name="junit.jupiter" type="repository">
|
||||
<properties maven-id="org.junit.jupiter:junit-jupiter:5.7.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.7.2/junit-jupiter-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.7.2/junit-jupiter-params-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.7.2/junit-jupiter-engine-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
9
.idea/libraries/kotlinx_cli_jvm_0_1_0_dev_5.xml
generated
9
.idea/libraries/kotlinx_cli_jvm_0_1_0_dev_5.xml
generated
@ -1,9 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="kotlinx-cli-jvm-0.1.0-dev-5">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
15
.idea/libraries/michael_bull_kotlin_result_jvm.xml
generated
Normal file
15
.idea/libraries/michael_bull_kotlin_result_jvm.xml
generated
Normal file
@ -0,0 +1,15 @@
|
||||
<component name="libraryTable">
|
||||
<library name="michael.bull.kotlin.result.jvm" type="repository">
|
||||
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.12/kotlin-result-jvm-1.1.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.10/kotlin-stdlib-jdk8-1.5.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.10/kotlin-stdlib-1.5.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.10/kotlin-stdlib-jdk7-1.5.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.10/kotlin-stdlib-common-1.5.10.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
11
.idea/libraries/slf4j_simple.xml
generated
Normal file
11
.idea/libraries/slf4j_simple.xml
generated
Normal file
@ -0,0 +1,11 @@
|
||||
<component name="libraryTable">
|
||||
<library name="slf4j.simple" type="repository">
|
||||
<properties maven-id="org.slf4j:slf4j-simple:1.7.30" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/1.7.30/slf4j-simple-1.7.30.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
13
.idea/libraries/takes.xml
generated
Normal file
13
.idea/libraries/takes.xml
generated
Normal file
@ -0,0 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="takes" type="repository">
|
||||
<properties maven-id="org.takes:takes:1.19" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/takes/takes/1.19/takes-1.19.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/cactoos/cactoos/0.42/cactoos-0.42.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-text/1.4/commons-text-1.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-lang3/3.7/commons-lang3-3.7.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
10
.idea/libraries/unittest_libs.xml
generated
10
.idea/libraries/unittest_libs.xml
generated
@ -1,10 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="unittest-libs">
|
||||
<CLASSES>
|
||||
<root url="file://$PROJECT_DIR$/compiler/lib" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
<jarDirectory url="file://$PROJECT_DIR$/compiler/lib" recursive="false" />
|
||||
</library>
|
||||
</component>
|
6
.idea/misc.xml
generated
6
.idea/misc.xml
generated
@ -4,7 +4,7 @@
|
||||
<option name="perGrammarGenerationSettings">
|
||||
<list>
|
||||
<PerGrammarGenerationSettings>
|
||||
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/prog8.g4" />
|
||||
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/Prog8ANTLR.g4" />
|
||||
<option name="autoGen" value="true" />
|
||||
<option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" />
|
||||
<option name="libDir" value="" />
|
||||
@ -16,7 +16,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
|
3
.idea/modules.xml
generated
3
.idea/modules.xml
generated
@ -3,8 +3,11 @@
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<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$/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" />
|
||||
</modules>
|
||||
</component>
|
||||
|
11
.travis.yml
11
.travis.yml
@ -1,11 +0,0 @@
|
||||
language: java
|
||||
sudo: false
|
||||
# jdk: openjdk8
|
||||
# dist: xenial
|
||||
|
||||
before_install:
|
||||
- chmod +x ./gradlew
|
||||
|
||||
script:
|
||||
- ./gradlew test
|
||||
|
32
CompilerDevelopment.md
Normal file
32
CompilerDevelopment.md
Normal file
@ -0,0 +1,32 @@
|
||||
#### Just a few remarks upfront:
|
||||
* There is the (gradle/IDEA) module `parser`: that's the parser generated by ANTLR4, in Java. The only file to be edited here is the grammar, `prog8.g4`.
|
||||
* Then we have the module `compilerAst` - in Kotlin - which uses `parser` and adds AST nodes. Here we put our additions to the generated thing, *including any tests of the parsing stage*.
|
||||
- the name is a bit misleading, as this module isn't (or, resp. shouldn't be; see below) about *compiling*, only the parsing stage
|
||||
- also, the tree that comes out isn't much of an *abstraction*, but rather still more or less a parse tree (this might very well change).
|
||||
- **However, let's not *yet* rename the module.** We'll find a good name during refactoring.
|
||||
|
||||
#### Problems with `compilerAst`:
|
||||
* `ModuleImporter.kt`, doing (Prog8-) module resolution. That's not the parser's job.
|
||||
* `ParsingFailedError` (in `ModuleParsing.kt`): this exception (it is actually *not* a `java.lang.Error`...) is thrown in a number of places, where other exceptions would make more sense. For example: not finding a file should just yield a `NoSuchFileException`, not this one. The other problem with it is that it does not provide any additional information about the source of parsing error, in particular a `Position`.
|
||||
* During parsing, character literals are turned into UBYTEs (since there is no basic type e.g. CHAR). That's bad because it depends on a specific character encoding (`IStringEncoding` in `compilerAst/src/prog8/ast/AstToplevel.kt`) of/for some target platform. Note that *strings* are indeed encoded later, in the `compiler` module.
|
||||
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
|
||||
|
||||
#### Steps to take, in conceptual (!) order:
|
||||
1. introduce an abstraction `SourceCode` that encapsulates the origin and actual loading of Prog8 source code
|
||||
- from the local file system (use case: user programs)
|
||||
- from resources (prog8lib)
|
||||
- from plain strings (for testing)
|
||||
2. add subclass `ParseError : ParsingFailedError` which adds information about the *source of parsing error* (`SourceCode` and `Position`). We cannot just replace `ParsingFailedError` right away because it is so widely used (even in the `compiler` module). Therefore we'll just subclass for the time being, add more and more tests requiring the new one to be thrown (or, resp., NOT to be thrown), and gradually transition.
|
||||
3. introduce a minimal interface to the outside, input: `SourceCode`, output: a tree with a `Module` node as the root
|
||||
- this will be the Kotlin singleton `Prog8Parser` with the main method `parseModule`
|
||||
- plus, optionally, method's for registering/unregistering a listener with the parser
|
||||
- the *only* exception ever thrown / reported to listeners (TBD) will be `ParseError`
|
||||
- anything related to the lexer, error strategies, character/token streams is hidden from the outside
|
||||
- to make a clear distinction between the *generated* parser (and lexer) vs. `Prog8Parser`, and to discourage directly using the generated stuff, we'll rename the existing `prog8Parser`/`prog8Lexer` to `Prog8ANTLRParser` and `Prog8ANTLRLexer` and move them to package `prog8.parser.generated`
|
||||
4. introduce AST node `CharLiteral` and keep them until after identifier resolution and type checking; insert there an AST transformation step that turns them in UBYTE constants (literals)
|
||||
5. remove uses of `IStringEncoding` from module `compilerAst` - none should be necessary anymore
|
||||
6. move `IStringEncoding` to module `compiler`
|
||||
7. same with `ModuleImporter`, then rewrite that (addressing #46)
|
||||
8. refactor AST nodes and grammar: less generated parse tree nodes (`XyzContext`), less intermediary stuff (private classes in `Antrl2Kotlin.kt`), more compact code. Also: nicer names such as simply `StringLiteral` instead of `StringLiteralValue`
|
||||
9. re-think `IStringEncoding` to address #38
|
||||
|
7
LICENSE
7
LICENSE
@ -1,3 +1,10 @@
|
||||
|
||||
This sofware license is for Prog8 the compiler + associated libraries.
|
||||
The software generated by running the compiler is excluded from this.
|
||||
|
||||
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
|
56
README.md
56
README.md
@ -1,5 +1,3 @@
|
||||
[](https://saythanks.io/to/irmen)
|
||||
[](https://travis-ci.org/irmen/prog8)
|
||||
[](https://prog8.readthedocs.io/)
|
||||
|
||||
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
||||
@ -7,9 +5,6 @@ Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
||||
|
||||
*Written by Irmen de Jong (irmen@razorvine.net)*
|
||||
|
||||
*Software license: GNU GPL 3.0, see file LICENSE*
|
||||
|
||||
|
||||
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
|
||||
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).
|
||||
@ -19,25 +14,36 @@ Documentation
|
||||
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
|
||||
https://prog8.readthedocs.io/
|
||||
|
||||
Software license
|
||||
----------------
|
||||
GNU GPL 3.0, see file LICENSE
|
||||
|
||||
- prog8 (the compiler + libraries) is licensed under GNU GPL 3.0
|
||||
- *exception:* the resulting files created by running the compiler are free to use in whatever way desired.
|
||||
|
||||
|
||||
What does Prog8 provide?
|
||||
------------------------
|
||||
|
||||
- big reduction of source code length over raw assembly
|
||||
- reduction of source code length over raw assembly
|
||||
- fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8.
|
||||
- modularity, symbol scoping, subroutines
|
||||
- various data types other than just bytes (16-bit words, floats, strings)
|
||||
- automatic variable allocations, automatic string and array variables and string sharing
|
||||
- subroutines with an input- and output parameter signature
|
||||
- no stack frame allocations because parameters and local variables are automatically allocated statically
|
||||
- constant folding in expressions and other high-level program optimizations
|
||||
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
|
||||
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
|
||||
- automatic static variable allocations, automatic string and array variables and string sharing
|
||||
- subroutines with input parameters and result values
|
||||
- high-level program optimizations
|
||||
- small program boilerplate/compilersupport overhead
|
||||
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||
- 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
|
||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||
- structs to group together sets of variables and manipulate them at once
|
||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
|
||||
- various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- fast execution speed due to compilation to native assembly code
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64.
|
||||
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
|
||||
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
@ -48,9 +54,9 @@ What does Prog8 provide?
|
||||
|
||||
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
|
||||
|
||||
- "c64": Commodore-64 (6510 CPU = almost a 6502), the main target.
|
||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) .
|
||||
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
- "c64": Commodore-64 (6510 CPU = almost a 6502)
|
||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
|
||||
- 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)!
|
||||
|
||||
|
||||
|
||||
@ -84,9 +90,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
ubyte candidate_prime = 2 ; is increased in the loop
|
||||
|
||||
sub start() {
|
||||
; clear the sieve, to reset starting situation on subsequent runs
|
||||
memset(sieve, 256, false)
|
||||
; calculate primes
|
||||
sys.memset(sieve, 256, false) ; clear the sieve
|
||||
txt.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
repeat {
|
||||
@ -97,17 +101,17 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
txt.print(", ")
|
||||
amount++
|
||||
}
|
||||
txt.chrout('\n')
|
||||
txt.nl()
|
||||
txt.print("number of primes (expected 54): ")
|
||||
txt.print_ub(amount)
|
||||
txt.chrout('\n')
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub find_next_prime() -> ubyte {
|
||||
while sieve[candidate_prime] {
|
||||
candidate_prime++
|
||||
if candidate_prime==0
|
||||
return 0 ; we wrapped; no more primes available in the sieve
|
||||
return 0 ; we wrapped; no more primes
|
||||
}
|
||||
|
||||
; found next one, mark the multiples and return it.
|
||||
@ -124,6 +128,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
|
||||
|
||||
|
||||
when compiled an ran on a C-64 you'll get:
|
||||
|
||||

|
||||
@ -140,7 +145,8 @@ If you want to play a video game, a fully working Tetris clone is included in th
|
||||
|
||||

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

|
||||
|
3
build.gradle
Normal file
3
build.gradle
Normal file
@ -0,0 +1,3 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm" version "1.5.30" apply false
|
||||
}
|
@ -1,51 +1,53 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.20"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.4.20"
|
||||
id 'application'
|
||||
id 'org.jetbrains.dokka' version "0.9.18"
|
||||
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.0'
|
||||
}
|
||||
|
||||
apply plugin: "kotlin"
|
||||
apply plugin: "java"
|
||||
|
||||
targetCompatibility = 11
|
||||
sourceCompatibility = 11
|
||||
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven { url "https://dl.bintray.com/orangy/maven/" }
|
||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||
}
|
||||
|
||||
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||
|
||||
dependencies {
|
||||
implementation project(':parser')
|
||||
implementation project(':compilerAst')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.antlr:antlr4-runtime:4.8'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
|
||||
// implementation 'net.razorvine:ksim65:1.6'
|
||||
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
|
||||
implementation project(':parser')
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
|
||||
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude group: 'com.ibm.icu', module: 'icu4j'
|
||||
exclude group: "org.antlr", module: "antlr4"
|
||||
}
|
||||
|
||||
configurations {
|
||||
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
|
||||
// this avoids linking in the complete antlr binary jar
|
||||
compile {
|
||||
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
useIR = true
|
||||
// verbose = true
|
||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||
}
|
||||
@ -54,6 +56,7 @@ compileKotlin {
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
useIR = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,7 +71,8 @@ sourceSets {
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
srcDir "${project.projectDir}/test"
|
||||
srcDir "${project(':compilerAst').projectDir}/test/helpers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -76,7 +80,7 @@ sourceSets {
|
||||
startScripts.enabled = true
|
||||
|
||||
application {
|
||||
mainClassName = 'prog8.CompilerMainKt'
|
||||
mainClass = 'prog8.CompilerMainKt'
|
||||
applicationName = 'p8compile'
|
||||
}
|
||||
|
||||
@ -104,13 +108,3 @@ test {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dokka {
|
||||
outputFormat = 'html'
|
||||
outputDirectory = "$buildDir/kdoc"
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '6.1.1'
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="Python" name="Python">
|
||||
<configuration sdkName="Python 3.9" />
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
@ -8,12 +13,14 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
|
||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="parser" />
|
||||
<orderEntry type="library" name="unittest-libs" level="project" />
|
||||
<orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" />
|
||||
<orderEntry type="library" name="antlr-runtime-4.8" level="project" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
|
||||
<orderEntry type="library" name="hamcrest" level="project" />
|
||||
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
|
||||
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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
|
@ -2,6 +2,8 @@
|
||||
|
||||
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
|
||||
|
||||
@ -426,7 +428,9 @@ var_fac1_greater_f .proc
|
||||
cmp #1
|
||||
beq +
|
||||
lda #0
|
||||
+ rts
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_greatereq_f .proc
|
||||
|
@ -4,7 +4,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target c64
|
||||
%option enable_floats
|
||||
|
||||
floats {
|
||||
@ -19,23 +18,6 @@ floats {
|
||||
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
||||
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||
|
||||
; constants in five-byte "mflpt" format in the BASIC ROM
|
||||
&float FL_PIVAL = $aea8 ; 3.1415926...
|
||||
&float FL_N32768 = $b1a5 ; -32768
|
||||
&float FL_FONE = $b9bc ; 1
|
||||
&float FL_SQRHLF = $b9d6 ; SQR(2) / 2
|
||||
&float FL_SQRTWO = $b9db ; SQR(2)
|
||||
&float FL_NEGHLF = $b9e0 ; -.5
|
||||
&float FL_LOG2 = $b9e5 ; LOG(2)
|
||||
&float FL_TENC = $baf9 ; 10
|
||||
&float FL_NZMIL = $bdbd ; 1e9 (1 billion)
|
||||
&float FL_FHALF = $bf11 ; .5
|
||||
&float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
|
||||
&float FL_PIHALF = $e2e0 ; PI / 2
|
||||
&float FL_TWOPI = $e2e5 ; 2 * PI
|
||||
&float FL_FR4 = $e2ea ; .25
|
||||
; oddly enough, 0.0 isn't available in the kernel.
|
||||
|
||||
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||
@ -100,7 +82,7 @@ romsub $bc58 = ABS() ; fac1 = ABS(fac1)
|
||||
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
|
||||
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1
|
||||
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
@ -212,7 +194,7 @@ sub print_f (float value) {
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm", ""
|
||||
%asminclude "library:c64/floats_funcs.asm", ""
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
}
|
||||
|
@ -175,8 +175,8 @@ func_log2_fac1 .proc
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
jsr MOVEF
|
||||
lda #<FL_LOG2
|
||||
ldy #>FL_LOG2
|
||||
lda #<FL_LOG2_const
|
||||
ldy #>FL_LOG2_const
|
||||
jsr MOVFM
|
||||
jsr FDIVT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
|
@ -1,8 +1,7 @@
|
||||
%target c64
|
||||
%import textio
|
||||
|
||||
; bitmap pixel graphics module for the C64
|
||||
; only black/white monchrome 320x200 for now
|
||||
; only black/white monochrome 320x200 for now
|
||||
; assumes bitmap screen memory is $2000-$3fff
|
||||
|
||||
graphics {
|
||||
@ -27,29 +26,40 @@ graphics {
|
||||
}
|
||||
|
||||
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||
memset(BITMAP_ADDRESS, 320*200/8, 0)
|
||||
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 rewrite this in optimized assembly
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to avoid 8 instead of just 4 special cases
|
||||
; 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 as word)-x1
|
||||
word @zp dy = (y2 as word)-y1
|
||||
|
||||
if dx==0 {
|
||||
vertical_line(x1, y1, abs(dy) as ubyte +1)
|
||||
return
|
||||
}
|
||||
if dy==0 {
|
||||
if x1>x2
|
||||
x1=x2
|
||||
horizontal_line(x1, y1, abs(dx) as uword +1)
|
||||
return
|
||||
}
|
||||
|
||||
word @zp d = 0
|
||||
ubyte positive_ix = true
|
||||
word @zp dx = x2-x1 as word
|
||||
word @zp dy = y2-y1
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
positive_ix = false
|
||||
}
|
||||
dx *= 2
|
||||
dy *= 2
|
||||
word @zp dx2 = dx*2
|
||||
word @zp dy2 = dy*2
|
||||
internal_plotx = x1
|
||||
|
||||
if dx >= dy {
|
||||
@ -59,10 +69,10 @@ graphics {
|
||||
if internal_plotx==x2
|
||||
return
|
||||
internal_plotx++
|
||||
d += dy
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -71,10 +81,10 @@ graphics {
|
||||
if internal_plotx==x2
|
||||
return
|
||||
internal_plotx--
|
||||
d += dy
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,10 +96,10 @@ graphics {
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
d += dx2
|
||||
if d > dy {
|
||||
internal_plotx++
|
||||
d -= dy
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -98,88 +108,183 @@ graphics {
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
d += dx2
|
||||
if d > dy {
|
||||
internal_plotx--
|
||||
d -= dy
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 xx = radius
|
||||
ubyte @zp yy = 0
|
||||
byte @zp decisionOver2 = 1-xx as byte
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while xx>=yy {
|
||||
internal_plotx = xcenter + xx
|
||||
while radius>=yy {
|
||||
internal_plotx = xcenter + radius
|
||||
ploty = ycenter + yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - xx
|
||||
internal_plotx = xcenter - radius
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + xx
|
||||
internal_plotx = xcenter + radius
|
||||
ploty = ycenter - yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - xx
|
||||
internal_plotx = xcenter - radius
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + yy
|
||||
ploty = ycenter + xx
|
||||
ploty = ycenter + radius
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + yy
|
||||
ploty = ycenter - xx
|
||||
ploty = ycenter - radius
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - yy
|
||||
internal_plot(ploty)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Midpoint algorithm, filled
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word decisionOver2 = (1 as word)-radius
|
||||
|
||||
while xx>=yy {
|
||||
ubyte ycenter_plus_yy = ycenter + yy
|
||||
ubyte ycenter_min_yy = ycenter - yy
|
||||
ubyte ycenter_plus_xx = ycenter + xx
|
||||
ubyte ycenter_min_xx = ycenter - xx
|
||||
|
||||
for internal_plotx in xcenter to xcenter+xx {
|
||||
internal_plot(ycenter_plus_yy)
|
||||
internal_plot(ycenter_min_yy)
|
||||
}
|
||||
for internal_plotx in xcenter-xx to xcenter-1 {
|
||||
internal_plot(ycenter_plus_yy)
|
||||
internal_plot(ycenter_min_yy)
|
||||
}
|
||||
for internal_plotx in xcenter to xcenter+yy {
|
||||
internal_plot(ycenter_plus_xx)
|
||||
internal_plot(ycenter_min_xx)
|
||||
}
|
||||
for internal_plotx in xcenter-yy to xcenter {
|
||||
internal_plot(ycenter_plus_xx)
|
||||
internal_plot(ycenter_min_xx)
|
||||
}
|
||||
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 += 2*yy+1
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -192,11 +297,11 @@ graphics {
|
||||
; @(addr) |= ormask[lsb(px) & 7]
|
||||
; }
|
||||
|
||||
asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
|
||||
inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
|
||||
%asm {{
|
||||
stx internal_plotx
|
||||
sty internal_plotx+1
|
||||
jmp internal_plot
|
||||
stx graphics.internal_plotx
|
||||
sty graphics.internal_plotx+1
|
||||
jsr graphics.internal_plot
|
||||
}}
|
||||
}
|
||||
|
||||
@ -248,6 +353,17 @@ _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,13 +5,11 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target c64
|
||||
|
||||
c64 {
|
||||
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||
&ubyte STATUS = $90 ; kernel status variable for I/O
|
||||
&ubyte STATUS = $90 ; kernal status variable for I/O
|
||||
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||
|
||||
@ -178,7 +176,7 @@ c64 {
|
||||
|
||||
; ---- C64 ROM kernal routines ----
|
||||
|
||||
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
|
||||
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 $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
@ -202,7 +200,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $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 $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $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
|
||||
@ -211,10 +209,10 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $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 $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $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
|
||||
@ -225,6 +223,41 @@ romsub $FFF3 = IOBASE() -> uword @ XY ; read base addr
|
||||
|
||||
; ---- 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 in AY for convenience
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr c64.RDTIM
|
||||
pha
|
||||
txa
|
||||
tay
|
||||
pla
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
|
||||
; ---- C64 specific system utility routines: ----
|
||||
|
||||
@ -259,17 +292,13 @@ asmsub init_system() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to Basic prompt.
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
sei
|
||||
lda #14
|
||||
sta $01 ; bank the kernal in
|
||||
jmp (c64.RESET_VEC)
|
||||
rts ; no phase 2 steps on the C64
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_runstop_and_charsetswitch() {
|
||||
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 657 ; disable charset switching
|
||||
@ -279,27 +308,13 @@ asmsub disable_runstop_and_charsetswitch() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irqvec_excl() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
lda #<_irq_handler
|
||||
sta c64.CINV
|
||||
lda #>_irq_handler
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
|
||||
jmp c64.IRQDFEND ; end irq processing - don't call kernel
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irqvec() clobbers(A) {
|
||||
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
|
||||
@ -308,9 +323,23 @@ asmsub set_irqvec() clobbers(A) {
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr _irq_handler_init
|
||||
jsr irq.irq
|
||||
_modified jsr $ffff ; modified
|
||||
jsr _irq_handler_end
|
||||
jmp c64.IRQDFRT ; continue with normal kernel irq routine
|
||||
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
|
||||
@ -363,7 +392,7 @@ IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_irqvec() {
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
lda #<c64.IRQDFRT
|
||||
@ -379,8 +408,15 @@ asmsub restore_irqvec() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
|
||||
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
|
||||
@ -391,12 +427,21 @@ asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
jmp c64.IRQDFRT
|
||||
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
|
||||
@ -420,29 +465,271 @@ _setup_raster_irq
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq_excl(uword rasterpos @ AY) clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
jsr set_rasterirq._setup_raster_irq
|
||||
lda #<_raster_irq_handler
|
||||
sta c64.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
jmp c64.IRQDFEND ; end irq processing - don't call kernel
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- end of C64 specific system utility routines ----
|
||||
|
||||
}
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 64 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on 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)
|
||||
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
||||
repeat jiffies {
|
||||
ubyte jiff = lsb(c64.RDTIM16())
|
||||
while jiff==lsb(c64.RDTIM16()) {
|
||||
; wait until 1 jiffy has passed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asmsub waitvsync() clobbers(A) {
|
||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
|
||||
%asm {{
|
||||
- bit c64.SCROLY
|
||||
bpl -
|
||||
- bit c64.SCROLY
|
||||
bmi -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub waitrastborder() {
|
||||
; --- busy wait till the raster position has reached the bottom screen border (approximately)
|
||||
; note: a more accurate way to do this is by using a raster irq handler instead.
|
||||
%asm {{
|
||||
- bit c64.SCROLY
|
||||
bpl -
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
%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
|
||||
|
||||
&ubyte r0L = $cf00
|
||||
&ubyte r1L = $cf02
|
||||
&ubyte r2L = $cf04
|
||||
&ubyte r3L = $cf06
|
||||
&ubyte r4L = $cf08
|
||||
&ubyte r5L = $cf0a
|
||||
&ubyte r6L = $cf0c
|
||||
&ubyte r7L = $cf0e
|
||||
&ubyte r8L = $cf10
|
||||
&ubyte r9L = $cf12
|
||||
&ubyte r10L = $cf14
|
||||
&ubyte r11L = $cf16
|
||||
&ubyte r12L = $cf18
|
||||
&ubyte r13L = $cf1a
|
||||
&ubyte r14L = $cf1c
|
||||
&ubyte r15L = $cf1e
|
||||
|
||||
&ubyte r0H = $cf01
|
||||
&ubyte r1H = $cf03
|
||||
&ubyte r2H = $cf05
|
||||
&ubyte r3H = $cf07
|
||||
&ubyte r4H = $cf09
|
||||
&ubyte r5H = $cf0b
|
||||
&ubyte r6H = $cf0d
|
||||
&ubyte r7H = $cf0f
|
||||
&ubyte r8H = $cf11
|
||||
&ubyte r9H = $cf13
|
||||
&ubyte r10H = $cf15
|
||||
&ubyte r11H = $cf17
|
||||
&ubyte r12H = $cf19
|
||||
&ubyte r13H = $cf1b
|
||||
&ubyte r14H = $cf1d
|
||||
&ubyte r15H = $cf1f
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target c64
|
||||
%import syslib
|
||||
%import conv
|
||||
|
||||
@ -16,7 +15,30 @@ const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
clear_screenchars(' ')
|
||||
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) {
|
||||
@ -38,13 +60,13 @@ asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||
; ---- clear the character screen with the given fill character (leaves colors)
|
||||
; (assumes screen matrix is at the default address)
|
||||
%asm {{
|
||||
ldy #0
|
||||
_loop sta c64.Screen,y
|
||||
sta c64.Screen+$0100,y
|
||||
sta c64.Screen+$0200,y
|
||||
sta c64.Screen+$02e8,y
|
||||
iny
|
||||
bne _loop
|
||||
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
|
||||
}}
|
||||
}
|
||||
@ -53,13 +75,13 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||
; ---- clear the character screen colors with the given color (leaves characters).
|
||||
; (assumes color matrix is at the default address)
|
||||
%asm {{
|
||||
ldy #0
|
||||
_loop sta c64.Colors,y
|
||||
sta c64.Colors+$0100,y
|
||||
sta c64.Colors+$0200,y
|
||||
sta c64.Colors+$02e8,y
|
||||
iny
|
||||
bne _loop
|
||||
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
|
||||
}}
|
||||
}
|
||||
@ -83,29 +105,31 @@ asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #0
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Colors + 40*row + 1,x
|
||||
sta c64.Colors + 40*row,x
|
||||
.next
|
||||
.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 the screen memory
|
||||
_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,x
|
||||
.next
|
||||
.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 -
|
||||
@ -121,26 +145,28 @@ asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Colors + 40*row + 0,x
|
||||
sta c64.Colors + 40*row + 1,x
|
||||
.next
|
||||
.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 the screen memory
|
||||
_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
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
@ -155,26 +181,28 @@ asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row-1),x
|
||||
.next
|
||||
.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 the screen memory
|
||||
_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
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
@ -189,26 +217,28 @@ asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
+ ; 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
|
||||
.next
|
||||
.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 the screen memory
|
||||
_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
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
@ -555,7 +585,7 @@ _colormod sta $ffff ; modified
|
||||
}
|
||||
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||
; ---- safe wrapper around PLOT kernel routine, to save the X register.
|
||||
; ---- safe wrapper around PLOT kernal routine, to save the X register.
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
tax
|
||||
|
@ -1,30 +1,518 @@
|
||||
; Prog8 definitions for number conversions routines.
|
||||
; Number conversions routines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
conv {
|
||||
|
||||
; ----- number conversions to decimal strings
|
||||
; ----- 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
|
||||
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
|
||||
}}
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
}}
|
||||
}
|
||||
|
||||
%asm {{
|
||||
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
|
||||
@ -193,11 +681,7 @@ decOnes .byte 0
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
; ----- utility functions ----
|
||||
|
||||
|
||||
asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
|
||||
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 {{
|
||||
@ -210,7 +694,7 @@ asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y {
|
||||
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
|
||||
@ -232,7 +716,7 @@ _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
|
||||
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
|
||||
@ -245,209 +729,8 @@ asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
|
||||
sta output+2
|
||||
sty output+3
|
||||
rts
|
||||
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
|
||||
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
|
||||
; -- returns the unsigned byte value of the string number argument in AY
|
||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
jmp str2uword
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
|
||||
; -- returns the signed byte value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
jmp str2word
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2uword(str string @ AY) -> uword @ AY {
|
||||
; -- returns the unsigned word value of the string number argument in AY
|
||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
_result = P8ZP_SCRATCH_W2
|
||||
sta _mod+1
|
||||
sty _mod+2
|
||||
ldy #0
|
||||
sty _result
|
||||
sty _result+1
|
||||
_mod lda $ffff,y ; modified
|
||||
sec
|
||||
sbc #48
|
||||
bpl +
|
||||
_done ; return result
|
||||
lda _result
|
||||
ldy _result+1
|
||||
rts
|
||||
+ cmp #10
|
||||
bcs _done
|
||||
; add digit to result
|
||||
pha
|
||||
jsr _result_times_10
|
||||
pla
|
||||
clc
|
||||
adc _result
|
||||
sta _result
|
||||
bcc +
|
||||
inc _result+1
|
||||
+ iny
|
||||
bne _mod
|
||||
; never reached
|
||||
|
||||
_result_times_10 ; (W*4 + W)*2
|
||||
lda _result+1
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda _result
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
clc
|
||||
adc _result
|
||||
sta _result
|
||||
lda P8ZP_SCRATCH_REG
|
||||
adc _result+1
|
||||
asl _result
|
||||
rol a
|
||||
sta _result+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2word(str string @ AY) -> word @ AY {
|
||||
; -- returns the signed word value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
_result = P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
sty _result
|
||||
sty _result+1
|
||||
sty _negative
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp #'+'
|
||||
bne +
|
||||
iny
|
||||
+ cmp #'-'
|
||||
bne _parse
|
||||
inc _negative
|
||||
iny
|
||||
_parse lda (P8ZP_SCRATCH_W1),y
|
||||
sec
|
||||
sbc #48
|
||||
bpl _digit
|
||||
_done ; return result
|
||||
lda _negative
|
||||
beq +
|
||||
sec
|
||||
lda #0
|
||||
sbc _result
|
||||
sta _result
|
||||
lda #0
|
||||
sbc _result+1
|
||||
sta _result+1
|
||||
+ lda _result
|
||||
ldy _result+1
|
||||
rts
|
||||
_digit cmp #10
|
||||
bcs _done
|
||||
; add digit to result
|
||||
pha
|
||||
jsr str2uword._result_times_10
|
||||
pla
|
||||
clc
|
||||
adc _result
|
||||
sta _result
|
||||
bcc +
|
||||
inc _result+1
|
||||
+ iny
|
||||
bne _parse
|
||||
; never reached
|
||||
_negative .byte 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub hex2uword(str string @ AY) -> uword @AY {
|
||||
; -- hexadecimal string with or without '$' to uword.
|
||||
; string may be in petscii or c64-screencode encoding.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #'$'
|
||||
beq _skip
|
||||
cmp #7
|
||||
bcc _add_nine
|
||||
cmp #'9'
|
||||
beq _calc
|
||||
bcs _add_nine
|
||||
_calc asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
and #$0f
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
ora P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
_skip inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
_stop lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
_add_nine ldy #9
|
||||
sty P8ZP_SCRATCH_B1
|
||||
bne _calc
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub bin2uword(str string @ AY) -> uword @AY {
|
||||
; -- binary string with or without '%' to uword.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #'%'
|
||||
beq +
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
and #1
|
||||
ora P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
+ inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
_stop lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,11 +4,12 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target cx16
|
||||
%option enable_floats
|
||||
|
||||
floats {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
; ---- this block contains C-64 compatible floating point related functions ----
|
||||
; the addresses are from cx16 V39 emulator and roms! they won't work on older versions.
|
||||
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
@ -43,45 +44,44 @@ 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 $fe30 = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
romsub $fe33 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $fe36 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||
romsub $fe39 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||
romsub $fe3c = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
||||
|
||||
romsub $fe48 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
romsub $fe4e = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $fe51 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||
romsub $fe54 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $fe5a = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $fe5d = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $fe60 = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||
romsub $fe6c = ABS() ; fac1 = ABS(fac1)
|
||||
romsub $fe6f = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
romsub $fe78 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||
romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
|
||||
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
|
||||
romsub $fea2 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $fea5 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $fea8 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
romsub $feab = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
romsub $feae = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
romsub $fe42 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $fe45 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
romsub $fe48 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $fe4b = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||
romsub $fe4e = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $fe54 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $fe57 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $fe5a = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||
romsub $fe66 = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||
romsub $fe69 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
romsub $fe72 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||
romsub $fe78 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
romsub $fe7b = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
romsub $fe81 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $fe84 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
romsub $fe8a = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
romsub $fe8d = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $fe96 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $fe99 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $fe9c = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
romsub $fe9f = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
romsub $fea2 = 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
|
||||
sta _tmp
|
||||
sty P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_W2
|
||||
ldy _tmp
|
||||
jsr GIVAYF ; load it as signed... correct afterwards
|
||||
lda P8ZP_SCRATCH_B1
|
||||
bpl +
|
||||
@ -90,6 +90,7 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
jsr FADD
|
||||
+ plx
|
||||
rts
|
||||
_tmp .byte 0
|
||||
_flt65536 .byte 145,0,0,0,0 ; 65536.0
|
||||
}}
|
||||
}
|
||||
@ -98,9 +99,9 @@ _flt65536 .byte 145,0,0,0,0 ; 65536.0
|
||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||
}}
|
||||
}
|
||||
@ -109,9 +110,9 @@ asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to signed word in A/Y
|
||||
%asm {{
|
||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||
sta P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -127,6 +128,14 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub FREADUY (ubyte value @Y) {
|
||||
; -- 8 bit unsigned Y -> float in fac1
|
||||
%asm {{
|
||||
lda #0
|
||||
jmp GIVAYF
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_f (float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
@ -148,7 +157,7 @@ sub print_f (float value) {
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm", ""
|
||||
%asminclude "library:c64/floats_funcs.asm", ""
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
}
|
||||
|
989
compiler/res/prog8lib/cx16/gfx2.p8
Normal file
989
compiler/res/prog8lib/cx16/gfx2.p8
Normal file
@ -0,0 +1,989 @@
|
||||
; 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) {
|
||||
when active_mode {
|
||||
1, 5 -> {
|
||||
; monochrome, lo-res
|
||||
cx16.r15L = gfx2.plot.bits[x as ubyte & 7] ; bitmask
|
||||
if color {
|
||||
if monochrome_dont_stipple_flag {
|
||||
; draw continuous line.
|
||||
position2(x,y,true)
|
||||
if active_mode==1
|
||||
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||
repeat height {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
ora cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
} else {
|
||||
; draw stippled line.
|
||||
if x&1 {
|
||||
y++
|
||||
height--
|
||||
}
|
||||
position2(x,y,true)
|
||||
if active_mode==1
|
||||
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
|
||||
repeat height/2 {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
ora cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
position2(x,y,true)
|
||||
cx16.r15 = ~cx16.r15 ; erase pixels
|
||||
if active_mode==1
|
||||
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||
repeat height {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
and cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
4 -> {
|
||||
; lores 256c
|
||||
; set vera auto-increment to 320 pixel increment (=next line)
|
||||
position(x,y)
|
||||
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
|
||||
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
|
||||
if height==0
|
||||
return
|
||||
position2(x,y,true)
|
||||
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
|
||||
color &= 3
|
||||
color <<= gfx2.plot.shift4c[lsb(x) & 3]
|
||||
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
|
||||
repeat height {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
and mask
|
||||
ora color
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub set_both_strides(ubyte stride) {
|
||||
stride <<= 4
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
|
||||
; Bresenham algorithm.
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
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 as word)-x1
|
||||
word @zp dy = (y2 as word)-y1
|
||||
|
||||
if dx==0 {
|
||||
vertical_line(x1, y1, abs(dy) as uword +1, color)
|
||||
return
|
||||
}
|
||||
if dy==0 {
|
||||
if x1>x2
|
||||
x1=x2
|
||||
horizontal_line(x1, y1, abs(dx) as uword +1, color)
|
||||
return
|
||||
}
|
||||
|
||||
word @zp d = 0
|
||||
cx16.r13 = true ; 'positive_ix'
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
cx16.r13 = false
|
||||
}
|
||||
word @zp dx2 = dx*2
|
||||
word @zp dy2 = dy*2
|
||||
cx16.r14 = x1 ; internal plot X
|
||||
|
||||
if dx >= dy {
|
||||
if cx16.r13 {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, color)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14++
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, color)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14--
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if cx16.r13 {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, color)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14++
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, color)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14--
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores monochrome
|
||||
%asm {{
|
||||
lda x
|
||||
eor y
|
||||
ora monochrome_dont_stipple_flag
|
||||
and #1
|
||||
}}
|
||||
if_nz {
|
||||
cx16.r0L = lsb(x) & 7 ; xbits
|
||||
x /= 8
|
||||
x += y*(320/8)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda x+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda x
|
||||
sta cx16.VERA_ADDR_L
|
||||
ldy cx16.r0L ; xbits
|
||||
lda bits,y
|
||||
ldy color
|
||||
beq +
|
||||
tsb cx16.VERA_DATA0
|
||||
bra ++
|
||||
+ trb cx16.VERA_DATA0
|
||||
+
|
||||
}}
|
||||
}
|
||||
}
|
||||
; TODO mode 2,3
|
||||
4 -> {
|
||||
; lores 256c
|
||||
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda cx16.r1
|
||||
ora #%00010000 ; enable auto-increment so next_pixel() can be used after this
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda color
|
||||
sta cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
5 -> {
|
||||
; highres monochrome
|
||||
%asm {{
|
||||
lda x
|
||||
eor y
|
||||
ora monochrome_dont_stipple_flag
|
||||
and #1
|
||||
}}
|
||||
if_nz {
|
||||
cx16.r0L = lsb(x) & 7 ; xbits
|
||||
x /= 8
|
||||
x += y*(640/8)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda x+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda x
|
||||
sta cx16.VERA_ADDR_L
|
||||
ldy cx16.r0L ; xbits
|
||||
lda bits,y
|
||||
ldy color
|
||||
beq +
|
||||
tsb cx16.VERA_DATA0
|
||||
bra ++
|
||||
+ trb cx16.VERA_DATA0
|
||||
+
|
||||
}}
|
||||
}
|
||||
}
|
||||
6 -> {
|
||||
; 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)
|
||||
cx16.r2L = lsb(x) & 3 ; xbits
|
||||
color &= 3
|
||||
color <<= shift4c[cx16.r2L]
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda cx16.r1L
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0H
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.r0L
|
||||
sta cx16.VERA_ADDR_L
|
||||
ldy cx16.r2L ; xbits
|
||||
lda mask4c,y
|
||||
and cx16.VERA_DATA0
|
||||
ora color
|
||||
sta cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub position2(uword @zp x, uword y, ubyte also_port_1) {
|
||||
position(x, y)
|
||||
if also_port_1 {
|
||||
when active_mode {
|
||||
1, 5 -> cx16.vaddr(0, cx16.r0, 1, 1)
|
||||
; TODO modes 2, 3
|
||||
4, 6 -> {
|
||||
ubyte bank = lsb(cx16.r1)
|
||||
cx16.vaddr(bank, cx16.r0, 1, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline asmsub next_pixel(ubyte color @A) {
|
||||
; -- 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 multiples 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
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
%target cx16
|
||||
%import syslib
|
||||
%import textio
|
||||
|
||||
; bitmap pixel graphics module for the CommanderX16
|
||||
; Bitmap pixel graphics module for the CommanderX16
|
||||
; wraps the graphics functions that are in ROM.
|
||||
; only black/white monchrome 320x200 for now.
|
||||
; 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
|
||||
@ -13,15 +15,14 @@ graphics {
|
||||
sub enable_bitmap_mode() {
|
||||
; enable bitmap screen, erase it and set colors to black/white.
|
||||
void cx16.screen_set_mode($80)
|
||||
cx16.r0 = 0
|
||||
cx16.GRAPH_init()
|
||||
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) ; TODO doesn't seem to fully clear the text screen after returning from gfx mode
|
||||
txt.fill_screen(' ', 1) ; doesn't seem to fully clear the text screen after returning from gfx mode
|
||||
}
|
||||
|
||||
|
||||
@ -31,11 +32,25 @@ graphics {
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
cx16.r0 = x1
|
||||
cx16.r1 = y1
|
||||
cx16.r2 = x2
|
||||
cx16.r3 = y2
|
||||
cx16.GRAPH_draw_line()
|
||||
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) {
|
||||
@ -43,122 +58,80 @@ graphics {
|
||||
;cx16.r1 = ycenter - radius/2
|
||||
;cx16.r2 = radius*2
|
||||
;cx16.r3 = radius*2
|
||||
;cx16.GRAPH_draw_oval(false) ; TODO currently is not implemented on cx16, does a BRK
|
||||
;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
|
||||
byte @zp decisionOver2 = 1-xx as byte
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter + yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter + xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
yy++
|
||||
if decisionOver2<=0 {
|
||||
decisionOver2 += 2*yy+1
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
} else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
decisionOver2 += (yy as word -xx)*2+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; cx16.r0 = xcenter - radius/2
|
||||
; cx16.r1 = ycenter - radius/2
|
||||
; cx16.r2 = radius*2
|
||||
; cx16.r3 = radius*2
|
||||
; cx16.GRAPH_draw_oval(true) ; TODO currently is not implemented on cx16, does a BRK
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word decisionOver2 = (1 as word)-radius
|
||||
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
ubyte ycenter_plus_yy = ycenter + yy
|
||||
ubyte ycenter_min_yy = ycenter - yy
|
||||
ubyte ycenter_plus_xx = ycenter + xx
|
||||
ubyte ycenter_min_xx = ycenter - xx
|
||||
uword @zp plotx
|
||||
|
||||
for plotx in xcenter to xcenter+xx {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
for plotx in xcenter-xx to xcenter-1 {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
for plotx in xcenter to xcenter+yy {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
for plotx in xcenter-yy to xcenter {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
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 += 2*yy+1
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword plotx, ubyte ploty) {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ploty
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(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
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
189
compiler/res/prog8lib/cx16/palette.p8
Normal file
189
compiler/res/prog8lib/cx16/palette.p8
Normal file
@ -0,0 +1,189 @@
|
||||
; 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 as uword * 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_all_black() {
|
||||
set_monochrome($000, $000)
|
||||
}
|
||||
|
||||
sub set_all_white() {
|
||||
set_monochrome($fff, $fff)
|
||||
}
|
||||
|
||||
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++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -5,9 +5,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target cx16
|
||||
|
||||
|
||||
c64 {
|
||||
|
||||
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||
@ -24,7 +21,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; re
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See cx16.numbanks()
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
@ -35,7 +32,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $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 $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $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
|
||||
@ -44,10 +41,10 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $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 $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $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
|
||||
@ -56,14 +53,46 @@ romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of
|
||||
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 in AY for convenience
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.RDTIM
|
||||
pha
|
||||
txa
|
||||
tay
|
||||
pla
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
|
||||
; 65c02 hardware vectors:
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
; 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
|
||||
@ -84,52 +113,87 @@ cx16 {
|
||||
&uword r14 = $001e
|
||||
&uword r15 = $0020
|
||||
|
||||
&ubyte r0L = $0002
|
||||
&ubyte r1L = $0004
|
||||
&ubyte r2L = $0006
|
||||
&ubyte r3L = $0008
|
||||
&ubyte r4L = $000a
|
||||
&ubyte r5L = $000c
|
||||
&ubyte r6L = $000e
|
||||
&ubyte r7L = $0010
|
||||
&ubyte r8L = $0012
|
||||
&ubyte r9L = $0014
|
||||
&ubyte r10L = $0016
|
||||
&ubyte r11L = $0018
|
||||
&ubyte r12L = $001a
|
||||
&ubyte r13L = $001c
|
||||
&ubyte r14L = $001e
|
||||
&ubyte r15L = $0020
|
||||
|
||||
&ubyte r0H = $0003
|
||||
&ubyte r1H = $0005
|
||||
&ubyte r2H = $0007
|
||||
&ubyte r3H = $0009
|
||||
&ubyte r4H = $000b
|
||||
&ubyte r5H = $000d
|
||||
&ubyte r6H = $000f
|
||||
&ubyte r7H = $0011
|
||||
&ubyte r8H = $0013
|
||||
&ubyte r9H = $0015
|
||||
&ubyte r10H = $0017
|
||||
&ubyte r11H = $0019
|
||||
&ubyte r12H = $001b
|
||||
&ubyte r13H = $001d
|
||||
&ubyte r14H = $001f
|
||||
&ubyte r15H = $0021
|
||||
|
||||
|
||||
; VERA registers
|
||||
|
||||
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
|
||||
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
|
||||
const uword via1 = $9f00 ;VIA 6522 #1
|
||||
&ubyte d1prb = via1+0
|
||||
&ubyte d1pra = via1+1
|
||||
&ubyte d1ddrb = via1+2
|
||||
@ -147,23 +211,28 @@ cx16 {
|
||||
&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
|
||||
const uword via2 = $9f10 ;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
|
||||
|
||||
&ubyte ym2151adr = $9f40
|
||||
&ubyte ym2151dat = $9f41
|
||||
|
||||
const uword extdev = $9f60
|
||||
|
||||
|
||||
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
||||
@ -190,72 +259,296 @@ romsub $ff6b = mouse_get(ubyte zpdataptr @X) clobbers(A)
|
||||
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $ff4d = clock_set_date_time() clobbers(A, X, Y) ; args: r0, r1, r2, r3L
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) ; outout args: r0, r1, r2, r3L
|
||||
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() clobbers(A,X,Y) ; uses vectors=r0
|
||||
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
||||
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
|
||||
romsub $ff26 = GRAPH_set_window() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
|
||||
romsub $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() clobbers(A,X,Y) ; uses x1=r0, y1=r1, x2=r2, y2=r3
|
||||
romsub $ff2f = GRAPH_draw_rect(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3, cornerradius=r4
|
||||
romsub $ff32 = GRAPH_move_rect() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, width=r4, height=r5
|
||||
romsub $ff35 = GRAPH_draw_oval(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
|
||||
romsub $ff38 = GRAPH_draw_image() clobbers(A,X,Y) ; uses x=r0, y=r1, ptr=r2, width=r3, height=r4
|
||||
romsub $ff3b = GRAPH_set_font() clobbers(A,X,Y) ; uses ptr=r0
|
||||
romsub $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(ubyte char @A) clobbers(A,X,Y) ; uses x=r0, y=r1
|
||||
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 ; also outputs width=r0, height=r1
|
||||
romsub $fefc = FB_set_palette(ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y) ; also uses pointer=r0
|
||||
romsub $feff = FB_cursor_position() clobbers(A,X,Y) ; uses x=r0, y=r1
|
||||
romsub $ff02 = FB_cursor_next_line() clobbers(A,X,Y) ; uses x=r0
|
||||
romsub $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() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||
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() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||
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 @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y) ; also uses mask=r0L
|
||||
romsub $ff17 = FB_fill_pixels(ubyte color @A) clobbers(A,X,Y) ; also uses count=r0, step=r1
|
||||
romsub $ff1a = FB_filter_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||
romsub $ff1d = FB_move_pixels() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, count=r4
|
||||
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(ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc ; also uses pixels=r0, mask=r1, bpp=r2L
|
||||
romsub $fef3 = sprite_set_position(ubyte number @A) clobbers(A,X,Y) ; also uses x=r0 and y=r1
|
||||
romsub $fee4 = memory_fill(ubyte value @A) clobbers(A,X,Y) ; uses address=r0, num_bytes=r1
|
||||
romsub $fee7 = memory_copy() clobbers(A,X,Y) ; uses source=r0, target=r1, num_bytes=r2
|
||||
romsub $feea = memory_crc() clobbers(A,X,Y) ; uses address=r0, num_bytes=r1 result->r2
|
||||
romsub $feed = memory_decompress() clobbers(A,X,Y) ; uses input=r0, output=r1 result->r1
|
||||
romsub $fedb = console_init() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
|
||||
romsub $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() clobbers(A,X,Y) ; uses ptr=r0, width=r1, height=r2
|
||||
romsub $fed5 = console_set_paging_message() clobbers(A,X,Y) ; uses messageptr=r0
|
||||
romsub $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 ----
|
||||
|
||||
asmsub init_system() {
|
||||
|
||||
; ---- utilities -----
|
||||
|
||||
inline asmsub rombank(ubyte rombank @A) {
|
||||
; -- set the rom banks
|
||||
%asm {{
|
||||
sta $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rambank(ubyte rambank @A) {
|
||||
; -- set the ram bank
|
||||
%asm {{
|
||||
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub numbanks() -> ubyte @A {
|
||||
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
|
||||
%asm {{
|
||||
phx
|
||||
sec
|
||||
jsr c64.MEMTOP
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
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
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command VLOAD "filename",device,bank,address
|
||||
; loads a file into video memory in the given bank:address, returns success in A
|
||||
; !! NOTE !! the V38 ROMs contain a bug in the LOAD code that makes the load address not work correctly,
|
||||
; it works fine when loading from local filesystem
|
||||
%asm {{
|
||||
; -- load a file into video ram
|
||||
phx
|
||||
pha
|
||||
tya
|
||||
tax
|
||||
lda #1
|
||||
ldy #0
|
||||
jsr c64.SETLFS
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr prog8_lib.strlen
|
||||
tya
|
||||
ldx cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr c64.SETNAM
|
||||
pla
|
||||
clc
|
||||
adc #2
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
stz P8ZP_SCRATCH_B1
|
||||
jsr c64.LOAD
|
||||
bcs +
|
||||
inc P8ZP_SCRATCH_B1
|
||||
+ jsr c64.CLRCHN
|
||||
lda #1
|
||||
jsr c64.CLOSE
|
||||
plx
|
||||
lda P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX {
|
||||
; convenience routine to get the joystick state without requiring inline assembly that deals with the multiple return values.
|
||||
; Also disables interrupts to avoid the IRQ race condition mentioned here: https://github.com/commanderx16/x16-rom/issues/203
|
||||
; TODO once that issue is resolved, this routine can be redefined as: romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX
|
||||
%asm {{
|
||||
sei
|
||||
jsr cx16.joystick_get
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub FB_set_pixels_from_buf(uword buffer, uword count) {
|
||||
%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
|
||||
lda #$80
|
||||
sta VERA_CTRL
|
||||
stz $01 ; select rom bank 0 (enable kernal)
|
||||
jsr c64.IOINIT
|
||||
jsr c64.RESTOR
|
||||
jsr c64.CINT
|
||||
@ -277,15 +570,311 @@ asmsub init_system() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to Basic prompt.
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
sei
|
||||
lda #14
|
||||
sta $01
|
||||
stz cx16.d1prb ; bank the kernal in
|
||||
jmp (cx16.RESET_VEC)
|
||||
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 initial power-on Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
stz $01 ; bank the kernal in
|
||||
jmp (cx16.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub wait(uword jiffies @AY) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
|
||||
; note: regular system vsync irq handler must be running, and no nother irqs
|
||||
%asm {{
|
||||
- wai ; wait for irq (assume it was vsync)
|
||||
cmp #0
|
||||
bne +
|
||||
dey
|
||||
+ dec a
|
||||
bne -
|
||||
cpy #0
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub waitvsync() {
|
||||
; --- suspend execution until the next vsync has occurred, without depending on custom irq handling.
|
||||
; note: system vsync irq handler has to be active for this routine to work (and no other irqs-- which is the default).
|
||||
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
|
||||
%asm {{
|
||||
wai
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
%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
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target cx16
|
||||
%import syslib
|
||||
%import conv
|
||||
|
||||
@ -15,8 +14,31 @@ const ubyte DEFAULT_WIDTH = 80
|
||||
const ubyte DEFAULT_HEIGHT = 60
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
clear_screenchars(' ')
|
||||
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) {
|
||||
@ -154,10 +176,9 @@ sub uppercase() {
|
||||
cx16.screen_set_charset(2, 0) ; uppercase charset
|
||||
}
|
||||
|
||||
asmsub scroll_left (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||
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
|
||||
; Carry flag is a dummy on the cx16
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.SCREEN
|
||||
@ -198,10 +219,9 @@ _lx ldx #0 ; modified
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_right (ubyte dummy @ Pc) clobbers(A) {
|
||||
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
|
||||
; Carry flag is a dummy on the cx16
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.SCREEN
|
||||
@ -250,10 +270,9 @@ _lx ldx #0 ; modified
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_up (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||
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
|
||||
; Carry flag is a dummy on the cx16
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.SCREEN
|
||||
@ -300,10 +319,9 @@ _nextline
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_down (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||
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
|
||||
; Carry flag is a dummy on the cx16
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.SCREEN
|
||||
@ -401,7 +419,7 @@ _print_byte_digits
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
jmp _ones
|
||||
bra _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq _ones
|
||||
@ -424,7 +442,7 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||
jsr c64.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
bra print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
@ -475,7 +493,7 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
jsr print_ubbin
|
||||
pla
|
||||
clc
|
||||
jmp print_ubbin
|
||||
bra print_ubbin
|
||||
}}
|
||||
}
|
||||
|
||||
@ -488,7 +506,7 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
jsr print_ubhex
|
||||
pla
|
||||
clc
|
||||
jmp print_ubhex
|
||||
bra print_ubhex
|
||||
}}
|
||||
}
|
||||
|
||||
@ -551,7 +569,7 @@ asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||
adc #1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp print_uw
|
||||
+ bra print_uw
|
||||
}}
|
||||
}
|
||||
|
||||
@ -579,95 +597,126 @@ asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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 kernel routine, to save the X register.
|
||||
; ---- safe wrapper around PLOT kernal routine, to save the X register.
|
||||
%asm {{
|
||||
phx
|
||||
tax
|
||||
|
@ -1,3 +1,5 @@
|
||||
; routine to draw the Commander X16's log in petscii.
|
||||
|
||||
%import textio
|
||||
|
||||
cx16logo {
|
||||
@ -14,7 +16,7 @@ cx16logo {
|
||||
uword strptr
|
||||
for strptr in logo_lines
|
||||
txt.print(strptr)
|
||||
txt.chrout('\n')
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
str[] logo_lines = [
|
||||
|
@ -1,20 +1,22 @@
|
||||
%import textio
|
||||
%import syslib
|
||||
; C64 and Cx16 disk drive I/O routines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
; Note: this code is compatible with C64 and CX16.
|
||||
%import textio
|
||||
%import string
|
||||
%import syslib
|
||||
|
||||
diskio {
|
||||
|
||||
|
||||
sub directory(ubyte drivenumber) -> byte {
|
||||
; -- Shows the directory contents of disk drive 8-11 (provide as argument).
|
||||
sub directory(ubyte drivenumber) -> ubyte {
|
||||
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
|
||||
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
void c64.OPEN() ; open 1,8,0,"$"
|
||||
c64.SETLFS(13, drivenumber, 0)
|
||||
void c64.OPEN() ; open 13,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void c64.CHKIN(1) ; use #1 as input channel
|
||||
void c64.CHKIN(13) ; use #13 as input channel
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
@ -25,31 +27,34 @@ diskio {
|
||||
; while not key pressed / EOF encountered, read data.
|
||||
ubyte status = c64.READST()
|
||||
while not status {
|
||||
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
|
||||
txt.chrout(' ')
|
||||
ubyte low = c64.CHRIN()
|
||||
ubyte high = c64.CHRIN()
|
||||
txt.print_uw(mkword(high, low))
|
||||
txt.spc()
|
||||
ubyte @zp char
|
||||
do {
|
||||
repeat {
|
||||
char = c64.CHRIN()
|
||||
if char==0
|
||||
break
|
||||
txt.chrout(char)
|
||||
} until char==0
|
||||
txt.chrout('\n')
|
||||
}
|
||||
txt.nl()
|
||||
void c64.CHRIN() ; skip 2 bytes
|
||||
void c64.CHRIN()
|
||||
status = c64.READST()
|
||||
void c64.STOP()
|
||||
if_nz
|
||||
if c64.STOP2()
|
||||
break
|
||||
}
|
||||
|
||||
io_error:
|
||||
status = c64.READST()
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(1)
|
||||
c64.CLOSE(13)
|
||||
|
||||
if status and status != 64 { ; 64=end of file
|
||||
if status and status & $40 == 0 { ; bit 6=end of file
|
||||
txt.print("\ni/o error, status: ")
|
||||
txt.print_ub(status)
|
||||
txt.chrout('\n')
|
||||
txt.nl()
|
||||
return false
|
||||
}
|
||||
|
||||
@ -57,9 +62,323 @@ io_error:
|
||||
}
|
||||
|
||||
|
||||
sub status(ubyte drivenumber) {
|
||||
; -- display the disk drive's current status message
|
||||
c64.SETNAM(0, $0000)
|
||||
; 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; ----- iterative file saver functions (uses io channel 14) -----
|
||||
|
||||
sub f_open_w(ubyte drivenumber, uword filenameptr) -> ubyte {
|
||||
; -- open a file for iterative writing with f_write
|
||||
f_close_w()
|
||||
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
c64.SETLFS(14, drivenumber, 1)
|
||||
void c64.OPEN() ; open 14,8,1,"filename"
|
||||
if_cc {
|
||||
void c64.CHKOUT(14) ; use #14 as input channel
|
||||
return not c64.READST()
|
||||
}
|
||||
f_close_w()
|
||||
return false
|
||||
}
|
||||
|
||||
sub f_write(uword bufferpointer, uword num_bytes) -> ubyte {
|
||||
; -- write the given umber of bytes to the currently open file
|
||||
if num_bytes!=0 {
|
||||
void c64.CHKOUT(14) ; use #14 as input channel again
|
||||
repeat num_bytes {
|
||||
c64.CHROUT(@(bufferpointer))
|
||||
bufferpointer++
|
||||
}
|
||||
return not c64.READST()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
sub f_close_w() {
|
||||
; -- end an iterative file writing session (close channels).
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(14)
|
||||
}
|
||||
|
||||
|
||||
; ---- other functions ----
|
||||
|
||||
sub status(ubyte drivenumber) -> uword {
|
||||
; -- 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
|
||||
@ -68,19 +387,27 @@ io_error:
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
while not c64.READST()
|
||||
txt.chrout(c64.CHRIN())
|
||||
while not c64.READST() {
|
||||
@(messageptr) = c64.CHRIN()
|
||||
messageptr++
|
||||
}
|
||||
|
||||
io_error:
|
||||
@(messageptr) = 0
|
||||
done:
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(15)
|
||||
return filename
|
||||
|
||||
io_error:
|
||||
filename = "?disk error"
|
||||
goto done
|
||||
}
|
||||
|
||||
|
||||
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> byte {
|
||||
c64.SETNAM(strlen(filenameptr), filenameptr)
|
||||
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
|
||||
first_byte = 0 ; result var reuse
|
||||
|
||||
%asm {{
|
||||
lda address
|
||||
@ -98,13 +425,16 @@ io_error:
|
||||
}}
|
||||
|
||||
if_cc
|
||||
return c64.READST()==0
|
||||
first_byte = c64.READST()==0
|
||||
|
||||
return false
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
|
||||
return first_byte
|
||||
}
|
||||
|
||||
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
||||
c64.SETNAM(strlen(filenameptr), filenameptr)
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
ubyte secondary = 1
|
||||
uword end_of_load = 0
|
||||
if address_override
|
||||
@ -122,6 +452,9 @@ io_error:
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
}}
|
||||
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
|
||||
if end_of_load
|
||||
return end_of_load - address_override
|
||||
|
||||
@ -133,10 +466,9 @@ io_error:
|
||||
|
||||
sub delete(ubyte drivenumber, uword filenameptr) {
|
||||
; -- delete a file on the drive
|
||||
ubyte flen = strlen(filenameptr)
|
||||
filename[0] = 's'
|
||||
filename[1] = ':'
|
||||
memcopy(filenameptr, &filename+2, flen+1)
|
||||
ubyte flen = string.copy(filenameptr, &filename+2)
|
||||
c64.SETNAM(flen+2, filename)
|
||||
c64.SETLFS(1, drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
@ -146,13 +478,11 @@ io_error:
|
||||
|
||||
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
|
||||
; -- rename a file on the drive
|
||||
ubyte flen_old = strlen(oldfileptr)
|
||||
ubyte flen_new = strlen(newfileptr)
|
||||
filename[0] = 'r'
|
||||
filename[1] = ':'
|
||||
memcopy(newfileptr, &filename+2, flen_new)
|
||||
ubyte flen_new = string.copy(newfileptr, &filename+2)
|
||||
filename[flen_new+2] = '='
|
||||
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
|
||||
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()
|
||||
|
@ -1,11 +1,8 @@
|
||||
; Prog8 internal Math library routines - always included by the compiler
|
||||
; Internal Math library routines - always included by the compiler
|
||||
; Generic machine independent 6502 code.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
; some more interesting routines can be found here:
|
||||
; http://6502org.wikidot.com/software-math
|
||||
; http://codebase64.org/doku.php?id=base:6502_6510_maths
|
||||
@ -247,68 +244,45 @@ randseed .proc
|
||||
.pend
|
||||
|
||||
|
||||
randbyte .proc
|
||||
; -- 8-bit pseudo random number generator into A
|
||||
lda _seed
|
||||
beq _eor
|
||||
asl a
|
||||
beq _done ; if the input was $80, skip the EOR
|
||||
bcc _done
|
||||
_eor eor #$1d ; xor with magic value see below for possible values
|
||||
_done sta _seed
|
||||
rts
|
||||
|
||||
_seed .byte $3a
|
||||
|
||||
; possible 'magic' eor bytes are:
|
||||
; $1d, $2b, $2d, $4d, $5f, $63, $65, $69
|
||||
; $71, $87, $8d, $a9, $c3, $cf, $e7, $f5
|
||||
|
||||
randbyte .proc
|
||||
; -- 8 bit pseudo random number generator into A (by just reusing randword)
|
||||
jmp randword
|
||||
.pend
|
||||
|
||||
|
||||
randword .proc
|
||||
; -- 16 bit pseudo random number generator into AY
|
||||
|
||||
magic_eor = $3f1d
|
||||
; possible magic eor words are:
|
||||
; $3f1d, $3f81, $3fa5, $3fc5, $4075, $409d, $40cd, $4109
|
||||
; $413f, $414b, $4153, $4159, $4193, $4199, $41af, $41bb
|
||||
; rand64k ;Factors of 65535: 3 5 17 257
|
||||
lda sr1+1
|
||||
asl a
|
||||
asl a
|
||||
eor sr1+1
|
||||
asl a
|
||||
eor sr1+1
|
||||
asl a
|
||||
asl a
|
||||
eor sr1+1
|
||||
asl a
|
||||
rol sr1 ;shift this left, "random" bit comes from low
|
||||
rol sr1+1
|
||||
; rand32k ;Factors of 32767: 7 31 151 are independent and can be combined
|
||||
lda sr2+1
|
||||
asl a
|
||||
eor sr2+1
|
||||
asl a
|
||||
asl a
|
||||
ror sr2 ;shift this right, random bit comes from high - nicer when eor with sr1
|
||||
rol sr2+1
|
||||
lda sr1+1 ;can be left out
|
||||
eor sr2+1 ;if you dont use
|
||||
tay ;y as suggested
|
||||
lda sr1 ;mix up lowbytes of SR1
|
||||
eor sr2 ;and SR2 to combine both
|
||||
rts
|
||||
|
||||
lda _seed
|
||||
beq _lowZero ; $0000 and $8000 are special values to test for
|
||||
sr1 .word $a55a
|
||||
sr2 .word $7653
|
||||
|
||||
; Do a normal shift
|
||||
asl _seed
|
||||
lda _seed+1
|
||||
rol a
|
||||
bcc _noEor
|
||||
|
||||
_doEor ; high byte is in A
|
||||
eor #>magic_eor
|
||||
sta _seed+1
|
||||
lda _seed
|
||||
eor #<magic_eor
|
||||
sta _seed
|
||||
ldy _seed+1
|
||||
rts
|
||||
|
||||
_lowZero lda _seed+1
|
||||
beq _doEor ; High byte is also zero, so apply the EOR
|
||||
; For speed, you could store 'magic' into 'seed' directly
|
||||
; instead of running the EORs
|
||||
|
||||
; wasn't zero, check for $8000
|
||||
asl a
|
||||
beq _noEor ; if $00 is left after the shift, then it was $80
|
||||
bcs _doEor ; else, do the EOR based on the carry bit as usual
|
||||
|
||||
_noEor sta _seed+1
|
||||
tay
|
||||
lda _seed
|
||||
rts
|
||||
|
||||
_seed .word $2c9e
|
||||
.pend
|
||||
|
||||
|
||||
@ -774,6 +748,38 @@ stack_mul_word_100 .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
stack_mul_word_320 .proc
|
||||
; stackW = stackLo * 256 + stackLo * 64 (stackHi doesn't matter)
|
||||
ldy P8ESTACK_LO+1,x
|
||||
lda #0
|
||||
sta P8ESTACK_HI+1,x
|
||||
tya
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
sta P8ESTACK_LO+1,x
|
||||
tya
|
||||
clc
|
||||
adc P8ESTACK_HI+1,x
|
||||
sta P8ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
stack_mul_word_640 .proc
|
||||
; stackW = (stackLo * 2 * 320) (stackHi doesn't matter)
|
||||
asl P8ESTACK_LO+1,x
|
||||
jmp stack_mul_word_320
|
||||
.pend
|
||||
|
||||
|
||||
; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : ---------
|
||||
mul_byte_3 .proc
|
||||
@ -1178,8 +1184,6 @@ mul_word_40 .proc
|
||||
rol a
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol a
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol a
|
||||
tay
|
||||
lda P8ZP_SCRATCH_W1
|
||||
rts
|
||||
@ -1241,6 +1245,39 @@ mul_word_100 .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_word_320 .proc
|
||||
; AY = A * 256 + A * 64 (msb in Y doesn't matter)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #0
|
||||
sty 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
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
pha
|
||||
clc
|
||||
lda P8ZP_SCRATCH_B1
|
||||
adc P8ZP_SCRATCH_REG
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_word_640 .proc
|
||||
; AY = (A * 2 * 320) (msb in Y doesn't matter)
|
||||
asl a
|
||||
jmp mul_word_320
|
||||
.pend
|
||||
|
||||
|
||||
; ----------- end optimized multiplications -----------
|
||||
|
||||
|
||||
@ -1301,6 +1338,33 @@ shift_left_w_3 .proc
|
||||
jmp shift_left_w_7._shift3
|
||||
.pend
|
||||
|
||||
|
||||
shift_left_w .proc
|
||||
; -- variable number of shifts left
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
bne _shift
|
||||
rts
|
||||
_shift asl P8ESTACK_LO+1,x
|
||||
rol P8ESTACK_HI+1,x
|
||||
dey
|
||||
bne _shift
|
||||
rts
|
||||
.pend
|
||||
|
||||
shift_right_uw .proc
|
||||
; -- uword variable number of shifts right
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
bne _shift
|
||||
rts
|
||||
_shift lsr P8ESTACK_HI+1,x
|
||||
ror P8ESTACK_LO+1,x
|
||||
dey
|
||||
bne _shift
|
||||
rts
|
||||
.pend
|
||||
|
||||
shift_right_uw_7 .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
sta P8ZP_SCRATCH_B1
|
||||
@ -1431,6 +1495,21 @@ shift_right_w_3 .proc
|
||||
.pend
|
||||
|
||||
|
||||
shift_right_w .proc
|
||||
; -- signed word variable number of shifts right
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
bne _shift
|
||||
rts
|
||||
_shift lda P8ESTACK_HI+1,x
|
||||
asl a
|
||||
ror P8ESTACK_HI+1,x
|
||||
ror P8ESTACK_LO+1,x
|
||||
dey
|
||||
bne _shift
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
; support for bit shifting that is too large to be unrolled:
|
||||
|
||||
@ -1449,3 +1528,71 @@ _negative lsr a
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
square .proc
|
||||
; -- calculate square root of signed word in AY, result in AY
|
||||
; routine by Lee Davsion, source: http://6502.org/source/integers/square.htm
|
||||
; using this routine is about twice as fast as doing a regular multiplication.
|
||||
;
|
||||
; Calculates the 16 bit unsigned integer square of the signed 16 bit integer in
|
||||
; Numberl/Numberh. The result is always in the range 0 to 65025 and is held in
|
||||
; Squarel/Squareh
|
||||
;
|
||||
; The maximum input range is only +/-255 and no checking is done to ensure that
|
||||
; this is so.
|
||||
;
|
||||
; This routine is useful if you are trying to draw circles as for any circle
|
||||
;
|
||||
; x^2+y^2=r^2 where x and y are the co-ordinates of any point on the circle and
|
||||
; r is the circle radius
|
||||
|
||||
numberl = P8ZP_SCRATCH_W1 ; number to square low byte
|
||||
numberh = P8ZP_SCRATCH_W1+1 ; number to square high byte
|
||||
squarel = P8ZP_SCRATCH_W2 ; square low byte
|
||||
squareh = P8ZP_SCRATCH_W2+1 ; square high byte
|
||||
tempsq = P8ZP_SCRATCH_B1 ; temp byte for intermediate result
|
||||
|
||||
sta numberl
|
||||
sty numberh
|
||||
stx P8ZP_SCRATCH_REG
|
||||
|
||||
lda #$00 ; clear a
|
||||
sta squarel ; clear square low byte
|
||||
; (no need to clear the high byte, it gets shifted out)
|
||||
lda numberl ; get number low byte
|
||||
ldx numberh ; get number high byte
|
||||
bpl _nonneg ; if +ve don't negate it
|
||||
; else do a two's complement
|
||||
eor #$ff ; invert
|
||||
sec ; +1
|
||||
adc #$00 ; and add it
|
||||
|
||||
_nonneg:
|
||||
sta tempsq ; save abs(number)
|
||||
ldx #$08 ; set bit count
|
||||
|
||||
_nextr2bit:
|
||||
asl squarel ; low byte *2
|
||||
rol squareh ; high byte *2+carry from low
|
||||
asl a ; shift number byte
|
||||
bcc _nosqadd ; don't do add if c = 0
|
||||
tay ; save a
|
||||
clc ; clear carry for add
|
||||
lda tempsq ; get number
|
||||
adc squarel ; add number^2 low byte
|
||||
sta squarel ; save number^2 low byte
|
||||
lda #$00 ; clear a
|
||||
adc squareh ; add number^2 high byte
|
||||
sta squareh ; save number^2 high byte
|
||||
tya ; get a back
|
||||
|
||||
_nosqadd:
|
||||
dex ; decrement bit count
|
||||
bne _nextr2bit ; go do next bit
|
||||
|
||||
lda squarel
|
||||
ldy squareh
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
|
||||
.pend
|
||||
|
@ -1,9 +1,7 @@
|
||||
; 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
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
math {
|
||||
%asminclude "library:math.asm", ""
|
||||
%asminclude "library:math.asm"
|
||||
}
|
||||
|
@ -432,6 +432,7 @@ func_min_ub_stack .proc
|
||||
func_min_b_into_A .proc
|
||||
; -- min(barray) -> A. (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
tay
|
||||
dey
|
||||
lda #127
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
@ -548,6 +549,7 @@ func_min_w_stack .proc
|
||||
func_max_ub_into_A .proc
|
||||
; -- max(ubarray) -> A (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
tay
|
||||
dey
|
||||
lda #0
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
@ -1078,271 +1080,28 @@ _loop_hi ldy _index_first
|
||||
.pend
|
||||
|
||||
|
||||
func_exit .proc
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
ldx orig_stackpointer
|
||||
txs
|
||||
rts ; return to original caller
|
||||
.pend
|
||||
func_peekw .proc
|
||||
; -- read the word value on the address in AY
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_read_flags_stack .proc
|
||||
; -- put the processor status register on the stack
|
||||
php
|
||||
pla
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_memset .proc
|
||||
; note: clobbers A,Y
|
||||
txa
|
||||
pha
|
||||
lda _arg_address
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _arg_address+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
ldx _arg_numbytes
|
||||
ldy _arg_numbytes+1
|
||||
lda _arg_bytevalue
|
||||
jsr memset
|
||||
pla
|
||||
tax
|
||||
rts
|
||||
_arg_address .word 0
|
||||
_arg_numbytes .word 0
|
||||
_arg_bytevalue .byte 0
|
||||
.pend
|
||||
|
||||
|
||||
func_memsetw .proc
|
||||
; note: clobbers A,Y
|
||||
txa
|
||||
pha
|
||||
lda _arg_address
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _arg_address+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda _arg_numwords
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda _arg_numwords+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda _arg_wordvalue
|
||||
ldy _arg_wordvalue+1
|
||||
jsr memsetw
|
||||
pla
|
||||
tax
|
||||
rts
|
||||
_arg_address .word 0
|
||||
_arg_numwords .word 0
|
||||
_arg_wordvalue .word 0
|
||||
.pend
|
||||
|
||||
|
||||
func_memcopy .proc
|
||||
; memcopy of any number of bytes, note: clobbers A,Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda _arg_from
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _arg_from+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda _arg_to
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda _arg_to+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
|
||||
ldy #0
|
||||
ldx _arg_numbytes+1
|
||||
beq _remain
|
||||
- lda (P8ZP_SCRATCH_W1),y ; move a page at a time
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dex
|
||||
bne -
|
||||
_remain ldx _arg_numbytes
|
||||
beq _done
|
||||
- lda (P8ZP_SCRATCH_W1),y ; move the remaining bytes
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
dex
|
||||
bne -
|
||||
|
||||
_done ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
|
||||
_arg_from .word 0
|
||||
_arg_to .word 0
|
||||
_arg_numbytes .word 0
|
||||
.pend
|
||||
|
||||
|
||||
func_memcopy255 .proc
|
||||
; fast memcopy of up to 255 bytes, note: clobbers A,Y
|
||||
; note: also uses the _arg variables from regular func_memcopy
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda func_memcopy._arg_from
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda func_memcopy._arg_from+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda func_memcopy._arg_to
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda func_memcopy._arg_to+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldx func_memcopy._arg_numbytes
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1), y
|
||||
sta (P8ZP_SCRATCH_W2), y
|
||||
iny
|
||||
dex
|
||||
bne -
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_leftstr .proc
|
||||
; leftstr(source, target, length)
|
||||
lda _arg_source
|
||||
ldy _arg_source+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda _arg_target
|
||||
ldy _arg_target+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy _arg_length
|
||||
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
|
||||
|
||||
_arg_source .word 0
|
||||
_arg_target .word 0
|
||||
_arg_length .byte 0
|
||||
.pend
|
||||
|
||||
|
||||
func_rightstr .proc
|
||||
; rightstr(source, target, length)
|
||||
lda _arg_source
|
||||
ldy _arg_source+1
|
||||
jsr func_strlen_into_A
|
||||
sec
|
||||
sbc _arg_length
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda _arg_source
|
||||
ldy _arg_source+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda _arg_target
|
||||
ldy _arg_target+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_REG
|
||||
- ldy P8ZP_SCRATCH_B1
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
inc P8ZP_SCRATCH_B1
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
inc P8ZP_SCRATCH_REG
|
||||
cmp #0
|
||||
bne -
|
||||
rts
|
||||
|
||||
_arg_source .word 0
|
||||
_arg_target .word 0
|
||||
_arg_length .byte 0
|
||||
.pend
|
||||
|
||||
|
||||
func_strlen_into_A .proc
|
||||
; -- put length of 0-terminated string in A/Y into A
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
iny
|
||||
bne -
|
||||
+ tya
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_strlen_stack .proc
|
||||
jsr func_strlen_into_A
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_substr .proc
|
||||
; substr(source, target, start, length)
|
||||
lda _arg_source
|
||||
ldy _arg_source+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda _arg_target
|
||||
ldy _arg_target+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy _arg_length
|
||||
lda _arg_start
|
||||
sta P8ZP_SCRATCH_B1
|
||||
|
||||
; 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
|
||||
jmp _startloop
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
_startloop dey
|
||||
cpy #$ff
|
||||
bne -
|
||||
rts
|
||||
|
||||
_arg_source .word 0
|
||||
_arg_target .word 0
|
||||
_arg_start .byte 0
|
||||
_arg_length .byte 0
|
||||
.pend
|
||||
|
||||
|
||||
func_strcmp .proc
|
||||
; -- compare 2 strings -> A
|
||||
lda _arg_s2
|
||||
ldy _arg_s2+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda _arg_s1
|
||||
ldy _arg_s1+1
|
||||
jmp strcmp_mem
|
||||
_arg_s1 .word 0
|
||||
_arg_s2 .word 0
|
||||
.pend
|
||||
|
||||
func_strcmp_stack .proc
|
||||
jsr func_strcmp
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
func_pokew .proc
|
||||
; -- store the word value in AY in the address in P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
.pend
|
||||
|
@ -1,9 +1,7 @@
|
||||
; Prog8 internal library routines - always included by the compiler
|
||||
; Internal library routines - always included by the compiler
|
||||
; Generic machine independent 6502 code.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
read_byte_from_address_on_stack .proc
|
||||
@ -993,6 +991,7 @@ _arg_index .byte 0
|
||||
strcpy .proc
|
||||
; copy a string (must be 0-terminated) from A/Y to (P8ZP_SCRATCH_W1)
|
||||
; it is assumed the target string is large enough.
|
||||
; returns the length of the string that was copied in Y.
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #$ff
|
||||
@ -1020,33 +1019,33 @@ _arg_s2 .word 0
|
||||
strcmp_mem .proc
|
||||
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
|
||||
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
bne +
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
bne _return_minusone
|
||||
beq _return
|
||||
+ lda (P8ZP_SCRATCH_W2),y
|
||||
sec
|
||||
sbc (P8ZP_SCRATCH_W1),y
|
||||
bmi _return_one
|
||||
bne _return_minusone
|
||||
inc P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
bne +
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
bne _return_minusone
|
||||
beq _return
|
||||
+ lda (P8ZP_SCRATCH_W2),y
|
||||
sec
|
||||
sbc (P8ZP_SCRATCH_W1),y
|
||||
bmi _return_one
|
||||
bne _return_minusone
|
||||
inc P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
_return_one
|
||||
lda #1
|
||||
_return rts
|
||||
lda #1
|
||||
_return rts
|
||||
_return_minusone
|
||||
lda #-1
|
||||
rts
|
||||
.pend
|
||||
lda #-1
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
sign_extend_stack_byte .proc
|
||||
@ -1072,3 +1071,15 @@ sign_extend_AY_byte .proc
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
strlen .proc
|
||||
; -- returns the number of bytes in the string in AY, in Y.
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
.pend
|
||||
|
@ -1,10 +1,84 @@
|
||||
; Prog8 internal library routines - always included by the compiler
|
||||
; 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
|
||||
|
||||
prog8_lib {
|
||||
%asminclude "library:prog8_lib.asm", ""
|
||||
%asminclude "library:prog8_funcs.asm", ""
|
||||
%asminclude "library:prog8_lib.asm"
|
||||
%asminclude "library:prog8_funcs.asm"
|
||||
|
||||
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
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
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
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 depending 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) -> ubyte @Y {
|
||||
; Lowercases the petscii string in-place. Returns length of the string.
|
||||
; (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) -> ubyte @Y {
|
||||
; Uppercases the petscii string in-place. Returns length of the string.
|
||||
%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
|
||||
}}
|
||||
}
|
||||
}
|
@ -1,48 +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
|
||||
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 @@
|
||||
5.2
|
||||
7.1
|
||||
|
@ -6,8 +6,8 @@ import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.parser.ParsingFailedError
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardWatchEventKinds
|
||||
@ -16,63 +16,76 @@ import kotlin.system.exitProcess
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
printSoftwareHeader("compiler")
|
||||
|
||||
compileMain(args)
|
||||
}
|
||||
|
||||
internal fun printSoftwareHeader(what: String) {
|
||||
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
|
||||
println("\nProg8 $what v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
|
||||
println("\nProg8 compiler v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
|
||||
println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
|
||||
|
||||
val succes = compileMain(args)
|
||||
if(!succes)
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
|
||||
fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
|
||||
|
||||
|
||||
private fun compileMain(args: Array<String>) {
|
||||
val cli = CommandLineInterface("prog8compiler")
|
||||
val startEmulator by cli.flagArgument("-emu", "auto-start emulator after successful compilation")
|
||||
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
|
||||
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
|
||||
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
|
||||
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||
val slowCodegenWarnings by cli.flagArgument("-slowwarn", "show debug warnings about slow/problematic assembly code generation")
|
||||
val compilationTarget by cli.flagValueArgument("-target", "compilertarget",
|
||||
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name)
|
||||
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
|
||||
private fun compileMain(args: Array<String>): Boolean {
|
||||
val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
|
||||
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
|
||||
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
|
||||
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
||||
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
|
||||
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||
|
||||
try {
|
||||
cli.parse(args)
|
||||
} catch (e: Exception) {
|
||||
exitProcess(1)
|
||||
} catch (e: IllegalStateException) {
|
||||
System.err.println(e.message)
|
||||
return false
|
||||
}
|
||||
|
||||
val outputPath = pathFrom(outputDir)
|
||||
if(!outputPath.toFile().isDirectory) {
|
||||
System.err.println("Output path doesn't exist")
|
||||
exitProcess(1)
|
||||
return false
|
||||
}
|
||||
|
||||
if(watchMode) {
|
||||
val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') }
|
||||
if(faultyOption!=null) {
|
||||
System.err.println("Unknown command line option given: $faultyOption")
|
||||
return false
|
||||
}
|
||||
|
||||
val srcdirs = sourceDirs.toMutableList()
|
||||
if(srcdirs.firstOrNull()!=".")
|
||||
srcdirs.add(0, ".")
|
||||
|
||||
if(watchMode==true) {
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
val allImportedFiles = mutableSetOf<Path>()
|
||||
|
||||
while(true) {
|
||||
println("Continuous watch mode active. Modules: $moduleFiles")
|
||||
val results = mutableListOf<CompilationResult>()
|
||||
for(filepathRaw in moduleFiles) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
|
||||
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
|
||||
results.add(compilationResult)
|
||||
}
|
||||
|
||||
val allImportedFiles = results.flatMap { it.importedFiles }
|
||||
val allNewlyImportedFiles = results.flatMap { it.importedFiles }
|
||||
allImportedFiles.addAll(allNewlyImportedFiles)
|
||||
|
||||
println("Imported files (now watching:)")
|
||||
for (importedFile in allImportedFiles) {
|
||||
print(" ")
|
||||
println(importedFile)
|
||||
val watchDir = importedFile.parent ?: Path.of(".")
|
||||
val watchDir = importedFile.parent ?: Path.of("")
|
||||
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||
}
|
||||
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||
@ -98,22 +111,30 @@ private fun compileMain(args: Array<String>) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult: CompilationResult
|
||||
try {
|
||||
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
|
||||
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
|
||||
if(!compilationResult.success)
|
||||
exitProcess(1)
|
||||
return false
|
||||
} catch (x: ParsingFailedError) {
|
||||
exitProcess(1)
|
||||
return false
|
||||
} catch (x: AstException) {
|
||||
exitProcess(1)
|
||||
return false
|
||||
}
|
||||
|
||||
if (startEmulator) {
|
||||
if (compilationResult.programName.isEmpty())
|
||||
if(startEmulator1==true || startEmulator2==true) {
|
||||
if (compilationResult.programName.isEmpty()) {
|
||||
println("\nCan't start emulator because no program was assembled.")
|
||||
else if(startEmulator) {
|
||||
CompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val programNameInPath = outputPath.resolve(compilationResult.programName)
|
||||
|
||||
if (startEmulator1==true)
|
||||
compilationResult.compTarget.machine.launchEmulator(1, programNameInPath)
|
||||
else if (startEmulator2==true)
|
||||
compilationResult.compTarget.machine.launchEmulator(2, programNameInPath)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -1,102 +0,0 @@
|
||||
package prog8.ast.base
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.*
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
|
||||
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
|
||||
val checker = AstChecker(this, compilerOptions, errors)
|
||||
checker.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
|
||||
val fixer = BeforeAsmGenerationAstChanger(this, errors)
|
||||
fixer.visit(this)
|
||||
fixer.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements(errors: ErrorReporter) {
|
||||
val reorder = StatementReorderer(this, errors)
|
||||
reorder.visit(this)
|
||||
reorder.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
val caster = TypecastsAdder(this, errors)
|
||||
caster.visit(this)
|
||||
caster.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.verifyFunctionArgTypes() {
|
||||
val fixer = VerifyFunctionArgTypes(this)
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Module.checkImportedValid() {
|
||||
val imr = ImportedModuleDirectiveRemover()
|
||||
imr.visit(this, this.parent)
|
||||
imr.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||
|
||||
val checker2 = AstIdentifiersChecker(this, errors)
|
||||
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,22 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
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 as INameScope))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -1,364 +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, val errors: ErrorReporter) : 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
arrayIndexedExpression.indexer.indexNum = expr2
|
||||
arrayIndexedExpression.indexer.origExpression = null
|
||||
return noModifications
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
arrayIndexedExpression.indexer.indexVar = expr2
|
||||
arrayIndexedExpression.indexer.origExpression = null
|
||||
return noModifications
|
||||
}
|
||||
is Expression -> {
|
||||
// replace complex indexing with a temp variable
|
||||
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||
}
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
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 even smarter so that an indexerVar can be reused for a different following statement... requires updating the partOfStatement?
|
||||
var indexerVar = repo.firstOrNull { it.replaces isSameAs expr.indexer }
|
||||
if(indexerVar==null) {
|
||||
// add another loop index var to be used for this expression
|
||||
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
|
||||
indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer, statement)
|
||||
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))
|
||||
}
|
||||
indexerVar.used++ // keep track of how many times it it used, to avoid assigning it multiple times
|
||||
|
||||
// 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)
|
||||
if(indexerVar.used==1) {
|
||||
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)
|
||||
var assignments = emptyList<Assignment>()
|
||||
|
||||
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
|
||||
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
|
||||
} else {
|
||||
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2'
|
||||
}
|
||||
}
|
||||
|
||||
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
|
||||
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
|
||||
} else {
|
||||
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
|
||||
}
|
||||
}
|
||||
|
||||
if(assignments.isNotEmpty()) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val scope = assignment.definingScope()
|
||||
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
|
||||
modifications.add(IAstModification.Remove(assignment, scope))
|
||||
return modifications
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// rewrite in-place assignment expressions a bit so that the assignment target usually is the leftmost operand
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null) {
|
||||
if(binExpr.left isSameAs assignment.target) {
|
||||
// A = A <operator> 5, unchanged
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
if (binExpr.right isSameAs assignment.target) {
|
||||
// A = v <associative-operator> A ==> A = A <associative-operator> v
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
|
||||
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||
if(leftBinExpr?.operator == binExpr.operator) {
|
||||
return if(leftBinExpr.left isSameAs assignment.target) {
|
||||
// A = (A <associative-operator> x) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
|
||||
val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position)
|
||||
val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position)
|
||||
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||
} else {
|
||||
// A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
|
||||
val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position)
|
||||
val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position)
|
||||
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||
}
|
||||
}
|
||||
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||
if(rightBinExpr?.operator == binExpr.operator) {
|
||||
return if(rightBinExpr.left isSameAs assignment.target) {
|
||||
// A = x <associative-operator> (A <same-operator> y) ==> A = A <associative-operator> (x <same-operator> y)
|
||||
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.right, binExpr.position)
|
||||
val newValue = BinaryExpression(rightBinExpr.left, binExpr.operator, newRight, binExpr.position)
|
||||
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||
} else {
|
||||
// A = x <associative-operator> (y <same-operator> A) ==> A = A <associative-operator> (x <same-operator> y)
|
||||
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.left, binExpr.position)
|
||||
val newValue = BinaryExpression(rightBinExpr.right, binExpr.operator, newRight, binExpr.position)
|
||||
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
|
||||
val identifier = assign.target.identifier!!
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val alv = assign.value as? ArrayLiteralValue
|
||||
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||
}
|
||||
|
||||
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
|
||||
val identifier = assign.target.identifier!!
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val sourceIdent = assign.value as IdentifierReference
|
||||
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!!
|
||||
if(!sourceVar.isArray) {
|
||||
errors.err("value must be an array", sourceIdent.position)
|
||||
return emptyList()
|
||||
}
|
||||
val alv = sourceVar.value as? ArrayLiteralValue
|
||||
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||
}
|
||||
|
||||
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
|
||||
if(targetVar.arraysize==null) {
|
||||
errors.err("array has no defined size", identifier.position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
|
||||
errors.err("element count mismatch", position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// TODO use a pointer loop instead of individual assignments
|
||||
return alv.value.mapIndexed { index, value ->
|
||||
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
|
||||
Assignment(AssignTarget(null, idx, null, position), value, value.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val struct = targetVar.struct!!
|
||||
|
||||
val slv = structAssignment.value as? ArrayLiteralValue
|
||||
if(slv==null || slv.value.size != struct.numberOfElements) {
|
||||
errors.err("element count mismatch", structAssignment.position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
|
||||
targetDecl as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
|
||||
sourceValue, sourceValue.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
|
||||
// TODO use memcopy beyond a certain number of elements
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val struct = targetVar.struct!!
|
||||
when (structAssignment.value) {
|
||||
is IdentifierReference -> {
|
||||
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
when {
|
||||
sourceVar.struct!=null -> {
|
||||
// struct memberwise copy
|
||||
val sourceStruct = sourceVar.struct!!
|
||||
if(sourceStruct!==targetVar.struct) {
|
||||
// structs are not the same in assignment
|
||||
return listOf() // error will be printed elsewhere
|
||||
}
|
||||
if(struct.statements.size!=sourceStruct.statements.size)
|
||||
return listOf() // error will be printed elsewhere
|
||||
return struct.statements.zip(sourceStruct.statements).map { member ->
|
||||
val targetDecl = member.first as VarDecl
|
||||
val sourceDecl = member.second as VarDecl
|
||||
if(targetDecl.name != sourceDecl.name)
|
||||
throw FatalAstException("struct member mismatch")
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
sourceVar.isArray -> {
|
||||
val array = (sourceVar.value as ArrayLiteralValue).value
|
||||
if(struct.statements.size!=array.size)
|
||||
return listOf() // error will be printed elsewhere
|
||||
return struct.statements.zip(array).map {
|
||||
val decl = it.first as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, decl.name)
|
||||
val targetName = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val target = AssignTarget(targetName, null, null, structAssignment.position)
|
||||
val assign = Assignment(target, it.second, structAssignment.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("can only assign arrays or structs to structs")
|
||||
}
|
||||
}
|
||||
}
|
||||
is ArrayLiteralValue -> {
|
||||
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
||||
}
|
||||
else -> throw FatalAstException("strange struct value")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,44 +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 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
|
||||
}
|
||||
}
|
@ -5,27 +5,30 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.astprocessing.isInRegularRAMof
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
subroutineVariables.add(decl.name to decl)
|
||||
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
// a numeric vardecl without an initial value is initialized with zero,
|
||||
// unless there's already an assignment below, that initializes the value
|
||||
// A numeric vardecl without an initial value is initialized with zero,
|
||||
// unless there's already an assignment below, that initializes the value.
|
||||
// This allows you to restart the program and have the same starting values of the variables
|
||||
if(decl.allowInitializeWithZero)
|
||||
{
|
||||
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
|
||||
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
|
||||
val nextAssign = decl.definingScope.nextSibling(decl) as? Assignment
|
||||
if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY))
|
||||
decl.value = null
|
||||
else
|
||||
else {
|
||||
decl.value = decl.zeroElementValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -37,7 +40,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||
if(!assignment.isAugmentable
|
||||
&& assignment.target.identifier != null
|
||||
&& assignment.target.isInRegularRAM(program.namespace)) {
|
||||
&& assignment.target.isInRegularRAMof(compTarget.machine)) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
||||
if (binExpr.left !is BinaryExpression) {
|
||||
@ -49,14 +52,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
// use the other part of the expression to split.
|
||||
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()),
|
||||
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
} else {
|
||||
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
|
||||
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
}
|
||||
@ -66,30 +69,37 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
}
|
||||
|
||||
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
|
||||
private val addedIfConditionVars = mutableSetOf<Pair<Subroutine, String>>()
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
subroutineVariables.clear()
|
||||
addedIfConditionVars.clear()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
|
||||
subroutineVariables.addAll(decls.map { it.name to it })
|
||||
|
||||
val sub = scope.definingSubroutine()
|
||||
val sub = scope.definingSubroutine
|
||||
if (sub != null) {
|
||||
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
|
||||
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
|
||||
val replaceVardecls =numericVarsWithValue.map {
|
||||
val initValue = it.value!! // assume here that value has always been set by now
|
||||
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
||||
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||
val assign = Assignment(target, initValue, it.position)
|
||||
initValue.parent = assign
|
||||
IAstModification.ReplaceNode(it, assign, scope)
|
||||
// move any remaining vardecls of the scope into the upper scope. Make sure the position remains the same!
|
||||
val replacements = mutableListOf<IAstModification>()
|
||||
val movements = mutableListOf<IAstModification.InsertFirst>()
|
||||
|
||||
for(decl in decls) {
|
||||
if(decl.value!=null && decl.datatype in NumericDatatypes) {
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, decl.value!!, decl.position)
|
||||
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
} else {
|
||||
replacements.add(IAstModification.Remove(decl, scope))
|
||||
}
|
||||
movements.add(IAstModification.InsertFirst(decl, sub))
|
||||
}
|
||||
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
|
||||
return replaceVardecls + moveVardeclsUp
|
||||
return replacements + movements
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
@ -106,20 +116,21 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
}
|
||||
|
||||
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
|
||||
// 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.
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
if (subroutine.asmAddress == null
|
||||
&& subroutine.statements.isNotEmpty()
|
||||
&& subroutine.amountOfRtsInAsm() == 0
|
||||
if (subroutine.asmAddress == null && !subroutine.inline) {
|
||||
if(subroutine.statements.isEmpty() ||
|
||||
(subroutine.amountOfRtsInAsm() == 0
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine) {
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
&& subroutine.statements.last() !is Subroutine)) {
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
}
|
||||
}
|
||||
|
||||
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
|
||||
val outerScope = subroutine.definingScope()
|
||||
val outerScope = subroutine.definingScope
|
||||
val outerStatements = outerScope.statements
|
||||
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||
if (subroutineStmtIdx > 0
|
||||
@ -136,7 +147,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
// see if we can remove superfluous typecasts (outside of expressions)
|
||||
// such as casting byte<->ubyte, word<->uword
|
||||
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
|
||||
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
||||
if(typecast.parent !is Expression) {
|
||||
@ -152,16 +163,6 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
// The only place for now where we can do this is for:
|
||||
// asmsub register pair parameter.
|
||||
|
||||
if(typecast.type in WordDatatypes) {
|
||||
val fcall = typecast.parent as? IFunctionCall
|
||||
if (fcall != null) {
|
||||
val sub = fcall.target.targetStatement(program.namespace) as? Subroutine
|
||||
if (sub != null && sub.isAsmSubroutine) {
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(typecast.type==DataType.UWORD) {
|
||||
if(typecast.expression is IdentifierReference) {
|
||||
@ -192,9 +193,55 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||
}
|
||||
|
||||
if((binExpr.operator=="==" || binExpr.operator=="!=") &&
|
||||
(binExpr.left as? NumericLiteralValue)?.number==0 &&
|
||||
(binExpr.right as? NumericLiteralValue)?.number!=0)
|
||||
throw CompilerException("if 0==X should have been swapped to if X==0")
|
||||
|
||||
// split the conditional expression into separate variables if the operand(s) is not simple.
|
||||
// DISABLED FOR NOW AS IT GENEREATES LARGER CODE IN THE SIMPLE CASES LIKE IF X {...} or IF NOT X {...}
|
||||
// val modifications = mutableListOf<IAstModification>()
|
||||
// if(!binExpr.left.isSimple) {
|
||||
// val sub = binExpr.definingSubroutine()!!
|
||||
// val (variable, isNew, assignment) = addIfOperandVar(sub, "left", binExpr.left)
|
||||
// if(isNew)
|
||||
// modifications.add(IAstModification.InsertFirst(variable, sub))
|
||||
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
|
||||
// modifications.add(IAstModification.ReplaceNode(binExpr.left, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
|
||||
// addedIfConditionVars.add(Pair(sub, variable.name))
|
||||
// }
|
||||
// if(!binExpr.right.isSimple) {
|
||||
// val sub = binExpr.definingSubroutine()!!
|
||||
// val (variable, isNew, assignment) = addIfOperandVar(sub, "right", binExpr.right)
|
||||
// if(isNew)
|
||||
// modifications.add(IAstModification.InsertFirst(variable, sub))
|
||||
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
|
||||
// modifications.add(IAstModification.ReplaceNode(binExpr.right, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
|
||||
// addedIfConditionVars.add(Pair(sub, variable.name))
|
||||
// }
|
||||
// return modifications
|
||||
return noModifications
|
||||
}
|
||||
|
||||
// private fun addIfOperandVar(sub: Subroutine, side: String, operand: Expression): Triple<VarDecl, Boolean, Assignment> {
|
||||
// val dt = operand.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
// val varname = "prog8_ifvar_${side}_${dt.name.toLowerCase()}"
|
||||
// val tgt = AssignTarget(IdentifierReference(listOf(varname), operand.position), null, null, operand.position)
|
||||
// val assign = Assignment(tgt, operand, operand.position)
|
||||
// if(Pair(sub, varname) in addedIfConditionVars) {
|
||||
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
|
||||
// return Triple(vardecl, false, assign)
|
||||
// }
|
||||
// val existing = sub.statements.firstOrNull { it is VarDecl && it.name == varname} as VarDecl?
|
||||
// return if (existing == null) {
|
||||
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
|
||||
// Triple(vardecl, true, assign)
|
||||
// } else {
|
||||
// Triple(existing, false, assign)
|
||||
// }
|
||||
// }
|
||||
|
||||
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val binExpr = untilLoop.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
@ -214,4 +261,95 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource==listOf("cmp")) {
|
||||
// if the datatype of the arguments of cmp() are different, cast the byte one to word.
|
||||
val arg1 = functionCallStatement.args[0]
|
||||
val arg2 = functionCallStatement.args[1]
|
||||
val dt1 = arg1.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes)
|
||||
return noModifications
|
||||
val cast1 = TypecastExpression(arg1, if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(arg1, cast1, functionCallStatement))
|
||||
} else {
|
||||
if(dt2 in WordDatatypes)
|
||||
return noModifications
|
||||
val cast2 = TypecastExpression(arg2, if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(arg2, cast2, functionCallStatement))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
val containingStatement = getContainingStatement(arrayIndexedExpression)
|
||||
if(getComplexArrayIndexedExpressions(containingStatement).size > 1) {
|
||||
errors.err("it's not possible to use more than one complex array indexing expression in a single statement; break it up via a temporary variable for instance", containingStatement.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
val index = arrayIndexedExpression.indexer.indexExpr
|
||||
if(index !is NumericLiteralValue && index !is IdentifierReference) {
|
||||
// replace complex indexing expression with a temp variable to hold the computed index first
|
||||
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun getComplexArrayIndexedExpressions(stmt: Statement): List<ArrayIndexedExpression> {
|
||||
|
||||
class Searcher : IAstVisitor {
|
||||
val complexArrayIndexedExpressions = mutableListOf<ArrayIndexedExpression>()
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
val ix = arrayIndexedExpression.indexer.indexExpr
|
||||
if(ix !is NumericLiteralValue && ix !is IdentifierReference)
|
||||
complexArrayIndexedExpressions.add(arrayIndexedExpression)
|
||||
}
|
||||
|
||||
override fun visit(branchStatement: BranchStatement) {}
|
||||
|
||||
override fun visit(forLoop: ForLoop) {}
|
||||
|
||||
override fun visit(ifStatement: IfStatement) {
|
||||
ifStatement.condition.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
untilLoop.condition.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
val searcher = Searcher()
|
||||
stmt.accept(searcher)
|
||||
return searcher.complexArrayIndexedExpressions
|
||||
}
|
||||
|
||||
private fun getContainingStatement(expression: Expression): Statement {
|
||||
var node: Node = expression
|
||||
while(node !is Statement)
|
||||
node = node.parent
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val statement = expr.containingStatement
|
||||
val dt = expr.indexer.indexExpr.inferType(program)
|
||||
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "r9L" else "r9"
|
||||
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
|
||||
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||
val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position)
|
||||
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope))
|
||||
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
|
||||
return modifications
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,32 @@
|
||||
package prog8.compiler
|
||||
|
||||
import com.github.michaelbull.result.*
|
||||
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.ParseError
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.parser.SourceCode
|
||||
import prog8.parser.SourceCode.Companion.libraryFilePrefix
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Path
|
||||
import kotlin.math.abs
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.nameWithoutExtension
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
||||
enum class OutputType {
|
||||
RAW,
|
||||
@ -28,44 +51,345 @@ data class CompilationOptions(val output: OutputType,
|
||||
val zeropage: ZeropageType,
|
||||
val zpReserved: List<IntRange>,
|
||||
val floats: Boolean,
|
||||
val noSysInit: Boolean) {
|
||||
val noSysInit: Boolean,
|
||||
val compTarget: ICompilationTarget) {
|
||||
var slowCodegenWarnings = false
|
||||
var optimize = false
|
||||
}
|
||||
|
||||
|
||||
class CompilerException(message: String?) : Exception(message)
|
||||
|
||||
fun Number.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
// 16..255 -> "$10".."$ff"
|
||||
// 256..65536 -> "$0100".."$ffff"
|
||||
// negative values are prefixed with '-'.
|
||||
val integer = this.toInt()
|
||||
if(integer<0)
|
||||
return '-' + abs(integer).toHex()
|
||||
return when (integer) {
|
||||
in 0 until 16 -> integer.toString()
|
||||
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
|
||||
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
|
||||
else -> throw CompilerException("number too large for 16 bits $this")
|
||||
class CompilationResult(val success: Boolean,
|
||||
val programAst: Program,
|
||||
val programName: String,
|
||||
val compTarget: ICompilationTarget,
|
||||
val importedFiles: List<Path>)
|
||||
|
||||
|
||||
fun compileProgram(filepath: Path,
|
||||
optimize: Boolean,
|
||||
writeAssembly: Boolean,
|
||||
slowCodegenWarnings: Boolean,
|
||||
compilationTarget: String,
|
||||
sourceDirs: List<String>,
|
||||
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 -> throw IllegalArgumentException("invalid compilation target")
|
||||
}
|
||||
|
||||
try {
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
|
||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||
compilationOptions.optimize = optimize
|
||||
programAst = ast
|
||||
importedFiles = imported
|
||||
processAst(programAst, errors, compilationOptions)
|
||||
if (compilationOptions.optimize)
|
||||
optimizeAst(
|
||||
programAst,
|
||||
errors,
|
||||
BuiltinFunctionsFacade(BuiltinFunctions),
|
||||
compTarget,
|
||||
compilationOptions
|
||||
)
|
||||
postprocessAst(programAst, errors, compilationOptions)
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
if (writeAssembly) {
|
||||
val result = writeAssembly(programAst, errors, outputDir, compilationOptions)
|
||||
when (result) {
|
||||
is WriteAssemblyResult.Ok -> programName = result.filename
|
||||
is WriteAssemblyResult.Fail -> {
|
||||
System.err.println(result.error)
|
||||
return CompilationResult(false, programAst, programName, compTarget, importedFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
|
||||
} catch (px: ParseError) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
|
||||
System.err.print("\u001b[0m") // reset
|
||||
} catch (pfx: ParsingFailedError) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
System.err.println(pfx.message)
|
||||
System.err.print("\u001b[0m") // reset
|
||||
} catch (ax: AstException) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
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", 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)
|
||||
return null // 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)
|
||||
}
|
||||
|
||||
fun parseImports(filepath: Path,
|
||||
errors: IErrorReporter,
|
||||
compTarget: ICompilationTarget,
|
||||
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||
println("Compiler target: ${compTarget.name}. Parsing...")
|
||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||
val programAst = Program(filepath.nameWithoutExtension, bf, compTarget)
|
||||
bf.program = programAst
|
||||
|
||||
val importer = ModuleImporter(programAst, compTarget.name, errors, sourceDirs)
|
||||
val importedModuleResult = importer.importModule(filepath)
|
||||
importedModuleResult.onFailure { throw it }
|
||||
errors.report()
|
||||
|
||||
val importedFiles = programAst.modules.map { it.source }
|
||||
.filter { it.isFromFilesystem }
|
||||
.map { Path(it.origin) }
|
||||
val compilerOptions = determineCompilationOptions(programAst, compTarget)
|
||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||
|
||||
// depending on the machine and compiler options we may have to include some libraries
|
||||
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
|
||||
importer.importLibraryModule(lib)
|
||||
|
||||
// always import prog8_lib and math
|
||||
importer.importLibraryModule("math")
|
||||
importer.importLibraryModule("prog8_lib")
|
||||
errors.report()
|
||||
return Triple(programAst, compilerOptions, importedFiles)
|
||||
}
|
||||
|
||||
fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
|
||||
val toplevelModule = program.toplevelModule
|
||||
val outputDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
|
||||
val launcherDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } as? Directive)
|
||||
val outputTypeStr = outputDirective?.args?.single()?.name?.uppercase()
|
||||
val launcherTypeStr = launcherDirective?.args?.single()?.name?.uppercase()
|
||||
val zpoption: String? = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
|
||||
as? Directive)?.args?.single()?.name?.uppercase()
|
||||
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }
|
||||
.flatMap { (it as Directive).args }.toSet()
|
||||
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: zp option floatsafe changed to basicsafe for cx16 target")
|
||||
zpType = ZeropageType.BASICSAFE
|
||||
}
|
||||
|
||||
val zpReserved = toplevelModule.statements
|
||||
.asSequence()
|
||||
.filter { it is Directive && it.directive == "%zpreserved" }
|
||||
.map { (it as Directive).args }
|
||||
.map { it[0].int!!..it[1].int!! }
|
||||
.toList()
|
||||
|
||||
val outputType = if (outputTypeStr == null) OutputType.PRG else {
|
||||
try {
|
||||
OutputType.valueOf(outputTypeStr)
|
||||
} catch (x: IllegalArgumentException) {
|
||||
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
|
||||
OutputType.PRG
|
||||
}
|
||||
}
|
||||
val launcherType = if (launcherTypeStr == null) LauncherType.BASIC else {
|
||||
try {
|
||||
LauncherType.valueOf(launcherTypeStr)
|
||||
} catch (x: IllegalArgumentException) {
|
||||
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
|
||||
LauncherType.BASIC
|
||||
}
|
||||
}
|
||||
|
||||
return CompilationOptions(
|
||||
outputType,
|
||||
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)
|
||||
errors.report()
|
||||
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
|
||||
// NOTE: we will then lose the opportunity to do constant-folding on any expression containing a char literal, but how often will those occur?
|
||||
// Also they might be optimized away eventually in codegen or by the assembler even
|
||||
programAst.charLiteralsToUByteLiterals(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
programAst.constantFold(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
programAst.reorderStatements(errors)
|
||||
errors.report()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.report()
|
||||
programAst.variousCleanups(programAst, errors)
|
||||
errors.report()
|
||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
programAst.checkIdentifiers(errors, compilerOptions)
|
||||
errors.report()
|
||||
}
|
||||
|
||||
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) {
|
||||
// optimize the parse tree
|
||||
println("Optimizing...")
|
||||
|
||||
val remover = UnusedCodeRemover(programAst, errors, compTarget)
|
||||
remover.visit(programAst)
|
||||
remover.applyModifications()
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
errors.report()
|
||||
}
|
||||
|
||||
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
programAst.addTypecasts(errors)
|
||||
errors.report()
|
||||
programAst.variousCleanups(programAst, errors)
|
||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
|
||||
errors.report()
|
||||
val callGraph = CallGraph(programAst)
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
errors.report()
|
||||
programAst.verifyFunctionArgTypes()
|
||||
programAst.moveMainAndStartToFirst()
|
||||
}
|
||||
|
||||
private sealed class WriteAssemblyResult {
|
||||
class Ok(val filename: String): WriteAssemblyResult()
|
||||
class Fail(val error: String): WriteAssemblyResult()
|
||||
}
|
||||
|
||||
private fun writeAssembly(programAst: Program,
|
||||
errors: IErrorReporter,
|
||||
outputDir: Path,
|
||||
compilerOptions: CompilationOptions): WriteAssemblyResult {
|
||||
// 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()
|
||||
|
||||
return if(assembly.valid && errors.noErrors()) {
|
||||
val assemblerReturnStatus = assembly.assemble(compilerOptions)
|
||||
if(assemblerReturnStatus!=0)
|
||||
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
|
||||
else {
|
||||
errors.report()
|
||||
WriteAssemblyResult.Ok(assembly.name)
|
||||
}
|
||||
} else {
|
||||
errors.report()
|
||||
WriteAssemblyResult.Fail("compiler failed with errors")
|
||||
}
|
||||
}
|
||||
|
||||
fun loadAsmIncludeFile(filename: String, source: Path): String {
|
||||
return if (filename.startsWith("library:")) {
|
||||
val resource = tryGetEmbeddedResource(filename.substring(8))
|
||||
?: throw IllegalArgumentException("library file '$filename' not found")
|
||||
resource.bufferedReader().use { it.readText() }
|
||||
fun printAst(programAst: Program) {
|
||||
println()
|
||||
val printer = AstToSourceCode(::print, programAst)
|
||||
printer.visit(programAst)
|
||||
println()
|
||||
}
|
||||
|
||||
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
|
||||
return if (filename.startsWith(libraryFilePrefix)) {
|
||||
return runCatching {
|
||||
val stream = object {}.javaClass.getResourceAsStream("/prog8lib/${filename.substring(libraryFilePrefix.length)}") // TODO handle via SourceCode
|
||||
stream!!.bufferedReader().use { r -> r.readText() }
|
||||
}.mapError { NoSuchFileException(File(filename)) }
|
||||
} else {
|
||||
// first try in the isSameAs folder as where the containing file was imported from
|
||||
val sib = source.resolveSibling(filename)
|
||||
val sib = Path(source.origin).resolveSibling(filename)
|
||||
|
||||
if (sib.toFile().isFile)
|
||||
sib.toFile().readText()
|
||||
Ok(sib.toFile().readText())
|
||||
else
|
||||
File(filename).readText()
|
||||
Ok(File(filename).readText())
|
||||
}
|
||||
}
|
||||
|
||||
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||
}
|
||||
|
@ -1,9 +1,18 @@
|
||||
package prog8.ast.base
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.base.Position
|
||||
import prog8.parser.ParsingFailedError
|
||||
|
||||
|
||||
class ErrorReporter {
|
||||
interface IErrorReporter {
|
||||
fun err(msg: String, position: Position)
|
||||
fun warn(msg: String, position: Position)
|
||||
fun noErrors(): Boolean
|
||||
fun report()
|
||||
}
|
||||
|
||||
|
||||
internal class ErrorReporter: IErrorReporter {
|
||||
private enum class MessageSeverity {
|
||||
WARNING,
|
||||
ERROR
|
||||
@ -13,10 +22,14 @@ class ErrorReporter {
|
||||
private val messages = mutableListOf<CompilerMessage>()
|
||||
private val alreadyReportedMessages = mutableSetOf<String>()
|
||||
|
||||
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
|
||||
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
|
||||
override fun err(msg: String, position: 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 numWarnings = 0
|
||||
messages.forEach {
|
||||
@ -40,5 +53,5 @@ class ErrorReporter {
|
||||
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
||||
}
|
||||
|
||||
fun isEmpty() = messages.isEmpty()
|
||||
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
|
||||
}
|
6
compiler/src/prog8/compiler/IStringEncoding.kt
Normal file
6
compiler/src/prog8/compiler/IStringEncoding.kt
Normal file
@ -0,0 +1,6 @@
|
||||
package prog8.compiler
|
||||
|
||||
interface IStringEncoding {
|
||||
fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
||||
}
|
@ -1,246 +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.C64Target
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.optimizer.*
|
||||
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.exitProcess
|
||||
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,
|
||||
slowCodegenWarnings: Boolean,
|
||||
compilationTarget: String,
|
||||
outputDir: Path): CompilationResult {
|
||||
var programName = ""
|
||||
lateinit var programAst: Program
|
||||
lateinit var importedFiles: List<Path>
|
||||
val errors = ErrorReporter()
|
||||
|
||||
when(compilationTarget) {
|
||||
C64Target.name -> CompilationTarget.instance = C64Target
|
||||
Cx16Target.name -> CompilationTarget.instance = 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)
|
||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||
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("Compiler target: ${CompilationTarget.instance.name}. Parsing...")
|
||||
val importer = ModuleImporter()
|
||||
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||
importer.importModule(programAst, filepath)
|
||||
errors.handle()
|
||||
|
||||
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
|
||||
|
||||
val compilerOptions = determineCompilationOptions(programAst)
|
||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||
|
||||
// depending on the machine and compiler options we may have to include some libraries
|
||||
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst)
|
||||
|
||||
// always import prog8_lib and math
|
||||
importer.importLibraryModule(programAst, "math")
|
||||
importer.importLibraryModule(programAst, "prog8_lib")
|
||||
errors.handle()
|
||||
return Triple(programAst, compilerOptions, importedFiles)
|
||||
}
|
||||
|
||||
private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
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()
|
||||
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 && CompilationTarget.instance.name == Cx16Target.name) {
|
||||
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
|
||||
zpType = ZeropageType.BASICSAFE
|
||||
}
|
||||
|
||||
val zpReserved = mainModule.statements
|
||||
.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
|
||||
)
|
||||
}
|
||||
|
||||
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||
// perform initial syntax checks and processings
|
||||
println("Processing for target ${CompilationTarget.instance.name}...")
|
||||
programAst.checkIdentifiers(errors)
|
||||
errors.handle()
|
||||
programAst.constantFold(errors)
|
||||
errors.handle()
|
||||
programAst.reorderStatements(errors)
|
||||
errors.handle()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors)
|
||||
errors.handle()
|
||||
programAst.checkIdentifiers(errors)
|
||||
errors.handle()
|
||||
}
|
||||
|
||||
private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
|
||||
// optimize the parse tree
|
||||
println("Optimizing...")
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = programAst.simplifyExpressions()
|
||||
val optsDone2 = programAst.splitBinaryExpressions()
|
||||
val optsDone3 = programAst.optimizeStatements(errors)
|
||||
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
errors.handle()
|
||||
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
||||
break
|
||||
}
|
||||
|
||||
val remover = UnusedCodeRemover(programAst, errors)
|
||||
remover.visit(programAst)
|
||||
remover.applyModifications()
|
||||
errors.handle()
|
||||
}
|
||||
|
||||
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
|
||||
errors.handle()
|
||||
val callGraph = CallGraph(programAst)
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
errors.handle()
|
||||
programAst.verifyFunctionArgTypes()
|
||||
programAst.moveMainAndStartToFirst()
|
||||
}
|
||||
|
||||
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
|
||||
optimize: Boolean, compilerOptions: CompilationOptions): String {
|
||||
// asm generation directly from the Ast,
|
||||
programAst.processAstBeforeAsmGeneration(errors)
|
||||
errors.handle()
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
CompilationTarget.instance.machine.initializeZeropage(compilerOptions)
|
||||
val assembly = CompilationTarget.instance.asmGenerator(
|
||||
programAst,
|
||||
errors,
|
||||
CompilationTarget.instance.machine.zeropage,
|
||||
compilerOptions,
|
||||
outputDir).compileToAssembly(optimize)
|
||||
assembly.assemble(compilerOptions)
|
||||
errors.handle()
|
||||
return assembly.name
|
||||
}
|
||||
|
||||
fun printAst(programAst: Program) {
|
||||
println()
|
||||
val printer = AstToSourceCode(::print, programAst)
|
||||
printer.visit(programAst)
|
||||
println()
|
||||
}
|
||||
|
159
compiler/src/prog8/compiler/ModuleImporter.kt
Normal file
159
compiler/src/prog8/compiler/ModuleImporter.kt
Normal file
@ -0,0 +1,159 @@
|
||||
package prog8.compiler
|
||||
|
||||
import com.github.michaelbull.result.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.DirectiveArg
|
||||
import prog8.parser.Prog8Parser
|
||||
import prog8.parser.SourceCode
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
|
||||
|
||||
class ModuleImporter(private val program: Program,
|
||||
private val compilationTargetName: String,
|
||||
val errors: IErrorReporter,
|
||||
sourceDirs: List<String>) {
|
||||
|
||||
private val sourcePaths: List<Path> = sourceDirs.map { Path(it) }
|
||||
|
||||
fun importModule(filePath: Path): Result<Module, NoSuchFileException> {
|
||||
val currentDir = Path("").absolute()
|
||||
val searchIn = listOf(currentDir) + sourcePaths
|
||||
val candidates = searchIn
|
||||
.map { it.absolute().div(filePath).normalize().absolute() }
|
||||
.filter { it.exists() }
|
||||
.map { currentDir.relativize(it) }
|
||||
.map { if (it.isAbsolute) it else Path(".", "$it") }
|
||||
|
||||
val srcPath = when (candidates.size) {
|
||||
0 -> return Err(NoSuchFileException(
|
||||
file = filePath.normalize().toFile(),
|
||||
reason = "searched in $searchIn"))
|
||||
1 -> candidates.first()
|
||||
else -> candidates.first() // when more candiates, pick the one from the first location
|
||||
}
|
||||
|
||||
val logMsg = "importing '${filePath.nameWithoutExtension}' (from file $srcPath)"
|
||||
println(logMsg)
|
||||
|
||||
val module = importModule(SourceCode.File(srcPath))
|
||||
return if(module==null)
|
||||
Err(NoSuchFileException(srcPath.toFile()))
|
||||
else
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
fun importLibraryModule(name: String): Module? {
|
||||
val import = Directive("%import", listOf(
|
||||
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
), Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
return executeImportDirective(import, null)
|
||||
}
|
||||
|
||||
private fun importModule(src: SourceCode) : Module? {
|
||||
val moduleAst = Prog8Parser.parseModule(src)
|
||||
program.addModule(moduleAst)
|
||||
|
||||
// accept additional imports
|
||||
try {
|
||||
val lines = moduleAst.statements.toMutableList()
|
||||
lines.asSequence()
|
||||
.mapIndexed { i, it -> i to it }
|
||||
.filter { (it.second as? Directive)?.directive == "%import" }
|
||||
.forEach { executeImportDirective(it.second as Directive, moduleAst) }
|
||||
moduleAst.statements = lines
|
||||
return moduleAst
|
||||
} catch (x: Exception) {
|
||||
// in case of error, make sure the module we're importing is no longer in the Ast
|
||||
program.removeModule(moduleAst)
|
||||
throw x
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeImportDirective(import: Directive, importingModule: Module?): Module? {
|
||||
if(import.directive!="%import" || import.args.size!=1)
|
||||
throw SyntaxError("invalid import directive", import.position)
|
||||
if(!import.args[0].str.isNullOrEmpty() || import.args[0].name==null)
|
||||
throw SyntaxError("%import requires unquoted module name", import.position)
|
||||
val moduleName = import.args[0].name!!
|
||||
if("$moduleName.p8" == import.position.file)
|
||||
throw SyntaxError("cannot import self", import.position)
|
||||
|
||||
val existing = program.modules.singleOrNull { it.name == moduleName }
|
||||
if (existing!=null)
|
||||
return existing
|
||||
|
||||
// try internal library first
|
||||
val moduleResourceSrc = getModuleFromResource("$moduleName.p8", compilationTargetName)
|
||||
val importedModule =
|
||||
moduleResourceSrc.fold(
|
||||
success = {
|
||||
println("importing '$moduleName' (from internal ${it.origin})")
|
||||
importModule(it)
|
||||
},
|
||||
failure = {
|
||||
// try filesystem next
|
||||
val moduleSrc = getModuleFromFile(moduleName, importingModule)
|
||||
moduleSrc.fold(
|
||||
success = {
|
||||
println("importing '$moduleName' (from file ${it.origin})")
|
||||
importModule(it)
|
||||
},
|
||||
failure = {
|
||||
errors.err("no module found with name $moduleName", import.position)
|
||||
return null
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if(importedModule!=null)
|
||||
removeDirectivesFromImportedModule(importedModule)
|
||||
return importedModule
|
||||
}
|
||||
|
||||
private fun removeDirectivesFromImportedModule(importedModule: Module) {
|
||||
// Most global directives don't apply for imported modules, so remove them
|
||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||
var directives = importedModule.statements.filterIsInstance<Directive>()
|
||||
importedModule.statements.removeAll(directives)
|
||||
directives = directives.filter{ it.directive !in moduleLevelDirectives }
|
||||
importedModule.statements.addAll(0, directives)
|
||||
}
|
||||
|
||||
private fun getModuleFromResource(name: String, compilationTargetName: String): Result<SourceCode, NoSuchFileException> {
|
||||
val result =
|
||||
runCatching { SourceCode.Resource("/prog8lib/$compilationTargetName/$name") }
|
||||
.orElse { runCatching { SourceCode.Resource("/prog8lib/$name") } }
|
||||
|
||||
return result.mapError { NoSuchFileException(File(name)) }
|
||||
}
|
||||
|
||||
private fun getModuleFromFile(name: String, importingModule: Module?): Result<SourceCode, NoSuchFileException> {
|
||||
val fileName = "$name.p8"
|
||||
val locations =
|
||||
if (importingModule == null) { // <=> imported from library module
|
||||
sourcePaths
|
||||
} else {
|
||||
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
|
||||
sourcePaths.drop(dropCurDir) +
|
||||
// TODO: won't work until Prog8Parser is fixed s.t. it fully initializes the modules it returns. // hm, what won't work?)
|
||||
listOf(Path(importingModule.position.file).parent ?: Path("")) +
|
||||
listOf(Path(".", "prog8lib"))
|
||||
}
|
||||
|
||||
locations.forEach {
|
||||
try {
|
||||
return Ok(SourceCode.File(it.resolve(fileName)))
|
||||
} catch (e: NoSuchFileException) {
|
||||
}
|
||||
}
|
||||
|
||||
return Err(NoSuchFileException(File("name")))
|
||||
}
|
||||
}
|
@ -19,9 +19,31 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
|
||||
val allowedDatatypes = NumericDatatypes
|
||||
|
||||
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
||||
fun availableBytes() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
||||
fun hasByteAvailable() = if(options.zeropage==ZeropageType.DONTUSE) false else free.isNotEmpty()
|
||||
fun availableWords(): Int {
|
||||
if(options.zeropage==ZeropageType.DONTUSE)
|
||||
return 0
|
||||
|
||||
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
|
||||
val words = free.windowed(2).filter { it[0] == it[1]-1 }
|
||||
var nonOverlappingWordsCount = 0
|
||||
var prevMsbLoc = -1
|
||||
for(w in words) {
|
||||
if(w[0]!=prevMsbLoc) {
|
||||
nonOverlappingWordsCount++
|
||||
prevMsbLoc = w[1]
|
||||
}
|
||||
}
|
||||
return nonOverlappingWordsCount
|
||||
}
|
||||
fun hasWordAvailable(): Boolean {
|
||||
if(options.zeropage==ZeropageType.DONTUSE)
|
||||
return false
|
||||
|
||||
return free.windowed(2).any { it[0] == it[1] - 1 }
|
||||
}
|
||||
|
||||
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
|
||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
|
||||
|
||||
if(options.zeropage==ZeropageType.DONTUSE)
|
||||
|
File diff suppressed because it is too large
Load Diff
211
compiler/src/prog8/compiler/astprocessing/AstExtensions.kt
Normal file
211
compiler/src/prog8/compiler/astprocessing/AstExtensions.kt
Normal file
@ -0,0 +1,211 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.IStringEncoding
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compiler.target.IMachineDefinition
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
fun RangeExpr.size(encoding: IStringEncoding): Int? {
|
||||
val fromLv = (from as? NumericLiteralValue)
|
||||
val toLv = (to as? NumericLiteralValue)
|
||||
if(fromLv==null || toLv==null)
|
||||
return null
|
||||
return toConstantIntegerRange(encoding)?.count()
|
||||
}
|
||||
|
||||
fun RangeExpr.toConstantIntegerRange(encoding: IStringEncoding): IntProgression? {
|
||||
val fromVal: Int
|
||||
val toVal: Int
|
||||
val fromString = from as? StringLiteralValue
|
||||
val toString = to as? StringLiteralValue
|
||||
if(fromString!=null && toString!=null ) {
|
||||
// string range -> int range over character values
|
||||
fromVal = encoding.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
|
||||
toVal = encoding.encodeString(toString.value, fromString.altEncoding)[0].toInt()
|
||||
} else {
|
||||
val fromLv = from as? NumericLiteralValue
|
||||
val toLv = to as? NumericLiteralValue
|
||||
if(fromLv==null || toLv==null)
|
||||
return null // non-constant range
|
||||
// integer range
|
||||
fromVal = fromLv.number.toInt()
|
||||
toVal = toLv.number.toInt()
|
||||
}
|
||||
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
|
||||
return makeRange(fromVal, toVal, stepVal)
|
||||
}
|
||||
|
||||
private fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||
return when {
|
||||
fromVal <= toVal -> when {
|
||||
stepVal <= 0 -> IntRange.EMPTY
|
||||
stepVal == 1 -> fromVal..toVal
|
||||
else -> fromVal..toVal step stepVal
|
||||
}
|
||||
else -> when {
|
||||
stepVal >= 0 -> IntRange.EMPTY
|
||||
stepVal == -1 -> fromVal downTo toVal
|
||||
else -> fromVal downTo toVal step abs(stepVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||
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)
|
||||
while(errors.noErrors() && fixer.applyModifications()>0) {
|
||||
fixer.visit(this)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements(errors: IErrorReporter) {
|
||||
val reorder = StatementReorderer(this, errors)
|
||||
reorder.visit(this)
|
||||
if(errors.noErrors()) {
|
||||
reorder.applyModifications()
|
||||
reorder.visit(this)
|
||||
if(errors.noErrors())
|
||||
reorder.applyModifications()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.charLiteralsToUByteLiterals(errors: IErrorReporter, enc: IStringEncoding) {
|
||||
val walker = object : AstWalker() {
|
||||
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
char,
|
||||
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toInt(), char.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
}
|
||||
walker.visit(this)
|
||||
walker.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.addTypecasts(errors: IErrorReporter) {
|
||||
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, options: CompilationOptions) {
|
||||
|
||||
val checker2 = AstIdentifiersChecker(this, errors, options.compTarget)
|
||||
checker2.visit(this)
|
||||
|
||||
if(errors.noErrors()) {
|
||||
val transforms = AstVariousTransforms(this)
|
||||
transforms.visit(this)
|
||||
transforms.applyModifications()
|
||||
val lit2decl = LiteralsToAutoVars(this)
|
||||
lit2decl.visit(this)
|
||||
lit2decl.applyModifications()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
|
||||
val process = VariousCleanups(program, errors)
|
||||
process.visit(this)
|
||||
if(errors.noErrors())
|
||||
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
|
||||
val mod = start.definingModule
|
||||
val block = start.definingBlock
|
||||
moveModuleToFront(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)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun AssignTarget.isInRegularRAMof(machine: IMachineDefinition): Boolean {
|
||||
val memAddr = memoryAddress
|
||||
val arrayIdx = arrayindexed
|
||||
val ident = identifier
|
||||
when {
|
||||
memAddr != null -> {
|
||||
return when (memAddr.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val program = definingModule.program
|
||||
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
|
||||
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
|
||||
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
||||
else
|
||||
false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
arrayIdx != null -> {
|
||||
val program = definingModule.program
|
||||
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
|
||||
return if (targetStmt?.type == VarDeclType.MEMORY) {
|
||||
val addr = targetStmt.value as? NumericLiteralValue
|
||||
if (addr != null)
|
||||
machine.isRegularRAMaddress(addr.number.toInt())
|
||||
else
|
||||
false
|
||||
} else true
|
||||
}
|
||||
ident != null -> {
|
||||
val program = definingModule.program
|
||||
val decl = ident.targetVarDecl(program)!!
|
||||
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
|
||||
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
||||
else
|
||||
true
|
||||
}
|
||||
else -> return true
|
||||
}
|
||||
}
|
@ -1,95 +1,61 @@
|
||||
package prog8.ast.processing
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
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 fun nameError(name: String, position: Position, existing: Statement) {
|
||||
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
|
||||
}
|
||||
|
||||
override fun visit(module: Module) {
|
||||
blocks.clear() // blocks may be redefined within a different module
|
||||
|
||||
super.visit(module)
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
if(block.name in CompilationTarget.instance.machine.opcodeNames)
|
||||
if(block.name in compTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
|
||||
|
||||
val existing = blocks[block.name]
|
||||
if(existing!=null)
|
||||
nameError(block.name, block.position, existing)
|
||||
if(existing!=null) {
|
||||
if(block.isInLibrary)
|
||||
nameError(existing.name, existing.position, block)
|
||||
else
|
||||
nameError(block.name, block.position, existing)
|
||||
}
|
||||
else
|
||||
blocks[block.name] = block
|
||||
|
||||
super.visit(block)
|
||||
}
|
||||
|
||||
override fun visit(directive: Directive) {
|
||||
if(directive.directive=="%target") {
|
||||
val compatibleTarget = directive.args.single().name
|
||||
if (compatibleTarget != CompilationTarget.instance.name)
|
||||
errors.err("module's compilation target ($compatibleTarget) differs from active target (${CompilationTarget.instance.name})", directive.position)
|
||||
}
|
||||
|
||||
super.visit(directive)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||
|
||||
if(decl.name in BuiltinFunctions)
|
||||
errors.err("builtin function cannot be redefined", decl.position)
|
||||
|
||||
if(decl.name in CompilationTarget.instance.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)
|
||||
|
||||
if(decl.datatype==DataType.STRUCT) {
|
||||
if (decl.structHasBeenFlattened)
|
||||
return super.visit(decl) // don't do this multiple times
|
||||
|
||||
if (decl.struct == null) {
|
||||
errors.err("undefined struct type", decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
if (decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes })
|
||||
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
|
||||
|
||||
if (decl.value is NumericLiteralValue) {
|
||||
errors.err("you cannot initialize a struct using a single value", decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
if (decl.value != null && decl.value !is ArrayLiteralValue) {
|
||||
errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
}
|
||||
|
||||
val existing = program.namespace.lookup(listOf(decl.name), decl)
|
||||
if (existing != null && existing !== decl)
|
||||
nameError(decl.name, decl.position, existing)
|
||||
|
||||
if(decl.definingBlock().name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingBlock())
|
||||
if(decl.definingSubroutine()?.name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
|
||||
if(decl.definingBlock.name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingBlock)
|
||||
if(decl.definingSubroutine?.name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingSubroutine!!)
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if(subroutine.name in CompilationTarget.instance.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)
|
||||
} else if(subroutine.name in BuiltinFunctions) {
|
||||
// the builtin functions can't be redefined
|
||||
@ -103,8 +69,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
if (existing != null && existing !== subroutine)
|
||||
nameError(subroutine.name, subroutine.position, existing)
|
||||
|
||||
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters. Blocks are okay.
|
||||
val symbolsInSub = subroutine.allDefinedSymbols
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
||||
val paramsToCheck = paramNames.intersect(namesInSub)
|
||||
@ -112,28 +78,34 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
val labelOrVar = subroutine.getLabelOrVariable(name)
|
||||
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
|
||||
nameError(name, labelOrVar.position, subroutine)
|
||||
val sub = subroutine.statements.singleOrNull { it is Subroutine && it.name==name}
|
||||
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
|
||||
if(sub!=null)
|
||||
nameError(name, sub.position, subroutine)
|
||||
nameError(name, subroutine.position, sub)
|
||||
}
|
||||
|
||||
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||
}
|
||||
|
||||
if(subroutine.name == subroutine.definingBlock.name) {
|
||||
// subroutines cannot have the same name as their enclosing block,
|
||||
// because this causes symbol scoping issues in the resulting assembly source
|
||||
nameError(subroutine.name, subroutine.position, subroutine.definingBlock)
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(subroutine)
|
||||
}
|
||||
|
||||
override fun visit(label: Label) {
|
||||
if(label.name in CompilationTarget.instance.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)
|
||||
|
||||
if(label.name in BuiltinFunctions) {
|
||||
// the builtin functions can't be redefined
|
||||
errors.err("builtin function cannot be redefined", label.position)
|
||||
} else {
|
||||
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
|
||||
val existing = label.definingSubroutine?.getAllLabels(label.name) ?: emptyList()
|
||||
for(el in existing) {
|
||||
if(el === label || el.name != label.name)
|
||||
continue
|
||||
@ -153,14 +125,4 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
override fun visit(structDecl: StructDecl) {
|
||||
for(member in structDecl.statements){
|
||||
val decl = member as? VarDecl
|
||||
if(decl!=null && decl.datatype !in NumericDatatypes)
|
||||
errors.err("structs can only contain numerical types", decl.position)
|
||||
}
|
||||
|
||||
super.visit(structDecl)
|
||||
}
|
||||
}
|
@ -1,34 +1,23 @@
|
||||
package prog8.ast.processing
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.ArrayIndexedExpression
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.DirectMemoryRead
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
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-kernel subroutines and non-asm parameters:
|
||||
// For non-kernal subroutines and non-asm parameters:
|
||||
// inject subroutine params as local variables (if they're not there yet).
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
val symbolsInSub = subroutine.allDefinedSymbols
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
||||
@ -77,6 +66,10 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
|
||||
}
|
||||
|
||||
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
|
||||
val rightStrval = expr.right as? StringLiteralValue
|
||||
val leftStrval = expr.left as? StringLiteralValue
|
||||
@ -103,3 +96,25 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal fun replacePointerVarIndexWithMemread(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
||||
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
||||
// rewrite pointervar[index] into @(pointervar+index)
|
||||
val indexer = arrayIndexedExpression.indexer
|
||||
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", indexer.indexExpr, arrayIndexedExpression.position)
|
||||
return if(parent is AssignTarget) {
|
||||
// we're part of the target of an assignment, we have to actually change the assign target itself
|
||||
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
|
||||
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
|
||||
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
|
||||
} else {
|
||||
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
@ -1,24 +1,25 @@
|
||||
package prog8.ast.processing
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
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 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())
|
||||
)
|
||||
// replace the literal string by an identifier reference to the interned string
|
||||
val scopedName = program.internString(string)
|
||||
val identifier = IdentifierReference(scopedName, string.position)
|
||||
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
@ -28,7 +29,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
if(arrayDt isnot vardecl.datatype) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null && cast !== array)
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
@ -37,13 +38,13 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED))
|
||||
if(litval2!=null) {
|
||||
val vardecl2 = VarDecl.createAuto(litval2)
|
||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(array, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl2, array.definingScope())
|
||||
IAstModification.InsertFirst(vardecl2, array.definingScope)
|
||||
)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package prog8.ast.processing
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
|
||||
/*
|
289
compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt
Normal file
289
compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt
Normal file
@ -0,0 +1,289 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.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.
|
||||
// - 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 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> {
|
||||
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||
// (this should be done by the ExpressionSimplifier when optimizing is enabled,
|
||||
// but the current assembly code generator for IF statements now also depends on it, so we do it here regardless of optimization.)
|
||||
if (expr.left.constValue(program) != null && expr.operator in associativeOperators && expr.right.constValue(program) == null)
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
|
||||
// when using a simple bit shift and assigning it to a variable of a different type,
|
||||
// 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.getOr(DataType.UNDEFINED), true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
is VarDecl -> {
|
||||
if(leftDt isnot 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
|
||||
}
|
||||
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||
it.first?.first() ?: Int.MAX_VALUE
|
||||
}
|
||||
whenStatement.choices.clear()
|
||||
choices.mapTo(whenStatement.choices) { it.second }
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
val valueType = assignment.value.inferType(program)
|
||||
val targetType = assignment.target.inferType(program)
|
||||
|
||||
if(targetType.isArray && valueType.isArray) {
|
||||
if (assignment.value is ArrayLiteralValue) {
|
||||
errors.err("cannot assign array literal here, use separate assignment per element", assignment.position)
|
||||
} else {
|
||||
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)
|
||||
|
||||
if(assign.value !is IdentifierReference) {
|
||||
errors.err("invalid array value to assign to other array", assign.value.position)
|
||||
return noModifications
|
||||
}
|
||||
val sourceIdent = assign.value as IdentifierReference
|
||||
val 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.noErrors())
|
||||
return noModifications
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
@ -1,31 +1,35 @@
|
||||
package prog8.ast.processing
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
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.
|
||||
* (this includes function call arguments)
|
||||
*/
|
||||
|
||||
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) {
|
||||
if(decl.type==VarDeclType.VAR && declValue!=null) {
|
||||
val valueDt = declValue.inferType(program)
|
||||
if(!valueDt.istype(decl.datatype)) {
|
||||
if(valueDt isnot decl.datatype) {
|
||||
|
||||
// don't add a typecast on an array initializer value
|
||||
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
|
||||
if(valueDt.isInteger && decl.datatype in ArrayDatatypes)
|
||||
return noModifications
|
||||
|
||||
// don't add a typecast if the initializer value is inherently not assignable
|
||||
if(valueDt isNotAssignableTo decl.datatype)
|
||||
return noModifications
|
||||
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
@ -43,7 +47,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
val rightDt = expr.right.inferType(program)
|
||||
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
||||
// determine common datatype and add typecast as required to make left and right equal types
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr.left, expr.right)
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
|
||||
if(toFix!=null) {
|
||||
return when {
|
||||
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||
@ -62,8 +66,8 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
val valueItype = assignment.value.inferType(program)
|
||||
val targetItype = assignment.target.inferType(program)
|
||||
if(targetItype.isKnown && valueItype.isKnown) {
|
||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||
val targettype = targetItype.getOr(DataType.UNDEFINED)
|
||||
val valuetype = valueItype.getOr(DataType.UNDEFINED)
|
||||
if (valuetype != targettype) {
|
||||
if (valuetype isAssignableTo targettype) {
|
||||
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
|
||||
@ -106,23 +110,23 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
}
|
||||
|
||||
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> {
|
||||
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
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
when(val sub = call.target.targetStatement(scope)) {
|
||||
when(val sub = call.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||
val argItype = pair.second.inferType(program)
|
||||
if(argItype.isKnown) {
|
||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||
val argtype = argItype.getOr(DataType.UNDEFINED)
|
||||
val requiredType = pair.first.type
|
||||
if (requiredType != argtype) {
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
@ -155,7 +159,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
func.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||
val argItype = pair.second.inferType(program)
|
||||
if (argItype.isKnown) {
|
||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||
val argtype = argItype.getOr(DataType.UNDEFINED)
|
||||
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
||||
for (possibleType in pair.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
@ -178,7 +182,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// warn about any implicit type casts to Float, because that may not be intended
|
||||
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
if(typecast.implicit && typecast.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
|
||||
}
|
||||
return noModifications
|
||||
@ -187,7 +191,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// make sure the memory address is an uword
|
||||
val dt = memread.addressExpression.inferType(program)
|
||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
|
||||
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||
@ -198,7 +202,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
||||
// make sure the memory address is an uword
|
||||
val dt = memwrite.addressExpression.inferType(program)
|
||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
|
||||
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||
@ -210,10 +214,10 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
// add a typecast to the return type if it doesn't match the subroutine's signature
|
||||
val returnValue = returnStmt.value
|
||||
if(returnValue!=null) {
|
||||
val subroutine = returnStmt.definingSubroutine()!!
|
||||
val subroutine = returnStmt.definingSubroutine!!
|
||||
if(subroutine.returntypes.size==1) {
|
||||
val subReturnType = subroutine.returntypes.first()
|
||||
if (returnValue.inferType(program).istype(subReturnType))
|
||||
if (returnValue.inferType(program) istype subReturnType)
|
||||
return noModifications
|
||||
if (returnValue is NumericLiteralValue) {
|
||||
val cast = returnValue.cast(subroutine.returntypes.single())
|
121
compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt
Normal file
121
compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt
Normal file
@ -0,0 +1,121 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.IErrorReporter
|
||||
|
||||
|
||||
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
|
||||
|
||||
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)
|
||||
scope.statements.forEach { it.parent = into as Node }
|
||||
into.statements.remove(scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(typecast.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $typecast")
|
||||
|
||||
if(typecast.expression is NumericLiteralValue) {
|
||||
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
|
||||
if(value.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
|
||||
}
|
||||
|
||||
val sourceDt = typecast.expression.inferType(program)
|
||||
if(sourceDt istype typecast.type)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
if(subroutine.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $subroutine")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $assignment")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> {
|
||||
if(assignTarget.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $assignTarget")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $decl")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
if(scope.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $scope")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
if(returnStmt.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $returnStmt")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
if(identifier.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $identifier")
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -1,26 +1,26 @@
|
||||
package prog8.ast.processing
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
|
||||
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program)
|
||||
val error = checkTypes(functionCall as IFunctionCall, program)
|
||||
if(error!=null)
|
||||
throw CompilerException(error)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program)
|
||||
val error = checkTypes(functionCallStatement as IFunctionCall, program)
|
||||
if (error!=null)
|
||||
throw CompilerException(error)
|
||||
}
|
||||
@ -39,12 +39,13 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
return false
|
||||
}
|
||||
|
||||
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
|
||||
fun checkTypes(call: IFunctionCall, program: Program): String? {
|
||||
val argITypes = call.args.map { it.inferType(program) }
|
||||
if(argITypes.any { !it.isKnown })
|
||||
throw FatalAstException("unknown dt")
|
||||
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
|
||||
val target = call.target.targetStatement(scope)
|
||||
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
|
||||
if(firstUnknownDt>=0)
|
||||
return "argument ${firstUnknownDt+1} invalid argument type"
|
||||
val argtypes = argITypes.map { it.getOr(DataType.UNDEFINED) }
|
||||
val target = call.target.targetStatement(program)
|
||||
if (target is Subroutine) {
|
||||
if(call.args.size != target.parameters.size)
|
||||
return "invalid number of arguments"
|
||||
@ -60,8 +61,15 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
// multiple return values will NOT work inside an expression.
|
||||
// they MIGHT work in a regular assignment or just a function call statement.
|
||||
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
|
||||
if(call !is FunctionCallStatement && parent !is Assignment && parent !is VarDecl) {
|
||||
return "can't use subroutine call that returns multiple return values here (try moving it into a separate assignment)"
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,18 @@
|
||||
package prog8.functions
|
||||
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>)
|
||||
class FParam(val name: String, val possibleDatatypes: Array<DataType>)
|
||||
|
||||
|
||||
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
|
||||
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue
|
||||
|
||||
|
||||
class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean)
|
||||
@ -87,88 +87,63 @@ class FSignature(val name: String,
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
|
||||
private val functionSignatures: List<FSignature> = listOf(
|
||||
// this set of function have no return value and operate in-place:
|
||||
FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("rol2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("rol" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("rol2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
FSignature("cmp" , false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
|
||||
// these few have a return value depending on the argument(s):
|
||||
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
||||
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
||||
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
||||
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
||||
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
||||
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
|
||||
FSignature("sizeof" , true, listOf(FParam("object", DataType.values())), DataType.UBYTE, ::builtinSizeof),
|
||||
// normal functions follow:
|
||||
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> 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 -> 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 -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
|
||||
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> 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 -> 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("sin" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||
FSignature("sin8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||
FSignature("sin8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||
FSignature("sin16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||
FSignature("sin16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||
FSignature("cos" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||
FSignature("cos8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||
FSignature("cos8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||
FSignature("cos16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||
FSignature("cos16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||
FSignature("tan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||
FSignature("atan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
FSignature("ln" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
|
||||
FSignature("log2" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
FSignature("sqrt16" , true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||
FSignature("sqrt" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
FSignature("rad" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||
FSignature("deg" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
FSignature("round" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||
FSignature("floor" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
FSignature("ceil" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
FSignature("lsb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
|
||||
FSignature("msb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
|
||||
FSignature("mkword" , true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
FSignature("pokew" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
||||
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
|
||||
FSignature("rndw" , false, emptyList(), DataType.UWORD),
|
||||
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
|
||||
FSignature("exit" , false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
|
||||
FSignature("rsave" , false, emptyList(), null),
|
||||
FSignature("rrestore" , false, emptyList(), null),
|
||||
FSignature("set_carry" , false, emptyList(), null),
|
||||
FSignature("clear_carry" , false, emptyList(), null),
|
||||
FSignature("set_irqd" , false, emptyList(), null),
|
||||
FSignature("clear_irqd" , false, emptyList(), null),
|
||||
FSignature("read_flags" , false, emptyList(), DataType.UBYTE),
|
||||
FSignature("memory" , true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
||||
FSignature("memcopy" , false, listOf(
|
||||
FParam("from", IterableDatatypes + DataType.UWORD),
|
||||
FParam("to", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numbytes", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("memset" , false, listOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numbytes", setOf(DataType.UWORD)),
|
||||
FParam("bytevalue", ByteDatatypes)), null),
|
||||
FSignature("memsetw" , false, listOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numwords", setOf(DataType.UWORD)),
|
||||
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
||||
FSignature("strlen" , true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
|
||||
FSignature("substr" , false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("start", setOf(DataType.UBYTE)),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
FSignature("leftstr" , false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
FSignature("rightstr" , false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
FSignature("strcmp" , false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, null)
|
||||
FSignature("callfar" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
|
||||
FSignature("callrom" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
|
||||
|
||||
)
|
||||
|
||||
val BuiltinFunctions = functionSignatures.associateBy { it.name }
|
||||
@ -178,7 +153,7 @@ 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 builtinSum(array: List<Number>): Number = array.sumOf { it.toDouble() }
|
||||
|
||||
fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0
|
||||
|
||||
@ -189,7 +164,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
|
||||
fun datatypeFromIterableArg(arglist: Expression): DataType {
|
||||
if(arglist is ArrayLiteralValue) {
|
||||
val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
|
||||
val dt = arglist.value.map {it.inferType(program).getOr(DataType.UNDEFINED)}.toSet()
|
||||
if(dt.any { it !in NumericDatatypes }) {
|
||||
throw FatalAstException("fuction $function only accepts array of numeric values")
|
||||
}
|
||||
@ -203,9 +178,9 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
val idt = arglist.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("couldn't determine type of iterable $arglist")
|
||||
return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
|
||||
return when(val dt = idt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.STR, in NumericDatatypes -> dt
|
||||
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
|
||||
in ArrayDatatypes -> ArrayToElementTypes.getValue(dt)
|
||||
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
|
||||
}
|
||||
}
|
||||
@ -220,7 +195,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
return when (function) {
|
||||
"abs" -> {
|
||||
val dt = args.single().inferType(program)
|
||||
return if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
|
||||
return if(dt.isNumeric)
|
||||
dt
|
||||
else
|
||||
InferredTypes.InferredType.unknown()
|
||||
@ -229,7 +204,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
when(val dt = datatypeFromIterableArg(args.single())) {
|
||||
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
||||
in NumericDatatypes -> InferredTypes.knownFor(dt)
|
||||
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
|
||||
in ArrayDatatypes -> InferredTypes.knownFor(ArrayToElementTypes.getValue(dt))
|
||||
else -> InferredTypes.unknown()
|
||||
}
|
||||
}
|
||||
@ -298,7 +273,8 @@ private fun collectionArg(args: List<Expression>, position: Position, program: P
|
||||
return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
|
||||
}
|
||||
|
||||
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@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)
|
||||
@ -311,7 +287,7 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
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)
|
||||
@ -320,56 +296,30 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
|
||||
|
||||
val dt = args[0].inferType(program)
|
||||
if(dt.isKnown) {
|
||||
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
|
||||
val target = (args[0] as IdentifierReference).targetStatement(program)
|
||||
?: throw CannotEvaluateException("sizeof", "no target")
|
||||
|
||||
fun structSize(target: StructDecl) =
|
||||
NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position)
|
||||
|
||||
return when {
|
||||
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
|
||||
dt.isArray -> {
|
||||
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
||||
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
|
||||
numericLiteral(elementDt.memorySize() * length, position)
|
||||
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
|
||||
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, dt.typeOrElse(DataType.STRUCT).memorySize(), position)
|
||||
dt istype DataType.STR -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
||||
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.getOr(DataType.UNDEFINED)), position)
|
||||
}
|
||||
} else {
|
||||
throw SyntaxError("sizeof invalid argument type", position)
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
if(argument is StringLiteralValue)
|
||||
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
|
||||
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
|
||||
if(vardecl!=null) {
|
||||
if(vardecl.datatype!=DataType.STR && vardecl.datatype!=DataType.UWORD)
|
||||
throw SyntaxError("strlen must have string argument", position)
|
||||
if(vardecl.autogeneratedDontRemove && vardecl.value!=null) {
|
||||
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
|
||||
}
|
||||
}
|
||||
throw NotConstArgumentException()
|
||||
}
|
||||
|
||||
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
|
||||
@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.namespace)
|
||||
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
|
||||
var arraySize = directMemVar?.arraysize?.constIndex()
|
||||
if(arraySize != null)
|
||||
return NumericLiteralValue.optimalInteger(arraySize, position)
|
||||
@ -377,7 +327,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
||||
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.namespace)
|
||||
val target = (args[0] as IdentifierReference).targetVarDecl(program)
|
||||
?: throw CannotEvaluateException("len", "no target vardecl")
|
||||
|
||||
return when(target.datatype) {
|
||||
@ -388,17 +338,17 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||
}
|
||||
DataType.STR -> {
|
||||
val refLv = target.value as StringLiteralValue
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@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()
|
||||
@ -407,7 +357,8 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
|
||||
return NumericLiteralValue(DataType.UWORD, result, position)
|
||||
}
|
||||
|
||||
private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@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()
|
||||
@ -415,7 +366,8 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
|
||||
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@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()
|
||||
@ -423,7 +375,8 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
|
||||
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@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()
|
||||
@ -431,7 +384,8 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
|
||||
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@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()
|
||||
@ -439,7 +393,8 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
|
||||
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@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()
|
||||
@ -447,7 +402,8 @@ private fun builtinSin16(args: List<Expression>, position: Position, program: Pr
|
||||
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
|
||||
}
|
||||
|
||||
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@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()
|
||||
@ -455,7 +411,8 @@ private fun builtinSin16u(args: List<Expression>, position: Position, program: P
|
||||
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
|
||||
}
|
||||
|
||||
private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@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()
|
||||
@ -463,7 +420,8 @@ private fun builtinCos16(args: List<Expression>, position: Position, program: Pr
|
||||
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
|
||||
}
|
||||
|
||||
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@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()
|
||||
@ -471,7 +429,8 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
|
||||
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
|
||||
}
|
||||
|
||||
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@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()
|
@ -1,47 +0,0 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
internal interface CompilationTarget {
|
||||
val name: String
|
||||
val machine: IMachineDefinition
|
||||
fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
||||
fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path): IAssemblyGenerator
|
||||
|
||||
companion object {
|
||||
lateinit var instance: CompilationTarget
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal object C64Target: CompilationTarget {
|
||||
override val name = "c64"
|
||||
override val machine = C64MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean) =
|
||||
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
|
||||
AsmGen(program, errors, zp, options, path)
|
||||
}
|
||||
|
||||
internal object Cx16Target: CompilationTarget {
|
||||
override val name = "cx16"
|
||||
override val machine = CX16MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean) =
|
||||
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
|
||||
AsmGen(program, errors, zp, options, path)
|
||||
}
|
@ -3,7 +3,7 @@ package prog8.compiler.target
|
||||
import prog8.compiler.CompilationOptions
|
||||
|
||||
internal interface IAssemblyGenerator {
|
||||
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
|
||||
fun compileToAssembly(): IAssemblyProgram
|
||||
}
|
||||
|
||||
internal const val generatedLabelPrefix = "_prog8_label_"
|
||||
@ -11,6 +11,7 @@ internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
|
||||
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
|
||||
|
||||
internal interface IAssemblyProgram {
|
||||
val valid: Boolean
|
||||
val name: String
|
||||
fun assemble(options: CompilationOptions)
|
||||
fun assemble(options: CompilationOptions): Int
|
||||
}
|
||||
|
98
compiler/src/prog8/compiler/target/ICompilationTarget.kt
Normal file
98
compiler/src/prog8/compiler/target/ICompilationTarget.kt
Normal file
@ -0,0 +1,98 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.IMemSizer
|
||||
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.IStringEncoding
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||
import java.io.CharConversionException
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
internal object C64Target: ICompilationTarget {
|
||||
override val name = "c64"
|
||||
override val machine = C64MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
try {
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
} catch (x: CharConversionException) {
|
||||
throw CharConversionException("can't decode string: ${x.message}")
|
||||
}
|
||||
|
||||
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): List<Short> {
|
||||
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
try {
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
} catch (x: CharConversionException) {
|
||||
throw CharConversionException("can't decode string: ${x.message}")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
@ -1,22 +1,21 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.parser.ModuleImporter
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
internal interface IMachineFloat {
|
||||
interface IMachineFloat {
|
||||
fun toDouble(): Double
|
||||
fun makeFloatFillAsm(): String
|
||||
}
|
||||
|
||||
internal enum class CpuType {
|
||||
enum class CpuType {
|
||||
CPU6502,
|
||||
CPU65c02
|
||||
}
|
||||
|
||||
internal interface IMachineDefinition {
|
||||
interface IMachineDefinition {
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: Int
|
||||
@ -32,8 +31,8 @@ internal interface IMachineDefinition {
|
||||
|
||||
fun initializeZeropage(compilerOptions: CompilationOptions)
|
||||
fun getFloat(num: Number): IMachineFloat
|
||||
fun getFloatRomConst(number: Double): String?
|
||||
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
|
||||
fun launchEmulator(programName: String)
|
||||
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||
fun isRegularRAMaddress(address: Int): Boolean
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
package prog8.compiler.target.c64
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.IMachineDefinition
|
||||
import prog8.compiler.target.IMachineFloat
|
||||
import prog8.parser.ModuleImporter
|
||||
import prog8.compiler.target.cbm.viceMonListPostfix
|
||||
import java.io.IOException
|
||||
import java.math.RoundingMode
|
||||
import java.nio.file.Path
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
@ -24,7 +23,6 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
override val RAW_LOAD_ADDRESS = 0xc000
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
// and some heavily used string constants derived from the two values above
|
||||
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
|
||||
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
|
||||
|
||||
@ -32,51 +30,23 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
|
||||
override fun getFloatRomConst(number: Double): String? {
|
||||
// try to match the ROM float constants to save memory
|
||||
val mflpt5 = Mflpt5.fromNumber(number)
|
||||
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
|
||||
when {
|
||||
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ZERO_const" // not a ROM const
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ONE_const" // not a ROM const
|
||||
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "floats.FL_PIVAL"
|
||||
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_N32768"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FONE"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRHLF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRTWO"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_NEGHLF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "floats.FL_LOG2"
|
||||
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "floats.FL_TENC"
|
||||
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "floats.FL_NZMIL"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FHALF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "floats.FL_LOGEB2"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_PIHALF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_TWOPI"
|
||||
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FR4"
|
||||
else -> {
|
||||
// attempt to correct for a few rounding issues
|
||||
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
|
||||
3.1415926536 -> return "floats.FL_PIVAL"
|
||||
1.4142135624 -> return "floats.FL_SQRTWO"
|
||||
0.7071067812 -> return "floats.FL_SQRHLF"
|
||||
0.6931471806 -> return "floats.FL_LOG2"
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
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(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The c64 target only supports the main emulator (Vice).")
|
||||
return
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
|
||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
importer.importLibraryModule(program, "syslib")
|
||||
}
|
||||
|
||||
override fun launchEmulator(programName: String) {
|
||||
for(emulator in listOf("x64sc", "x64")) {
|
||||
println("\nStarting C-64 emulator $emulator...")
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list",
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", "${programNameWithPath}.$viceMonListPostfix",
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
@ -116,7 +86,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
|
||||
if (options.zeropage == ZeropageType.FULL) {
|
||||
@ -139,13 +109,14 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
// 0x90-0xfa is 'kernel work storage area'
|
||||
// 0x90-0xfa is 'kernal work storage area'
|
||||
))
|
||||
}
|
||||
|
||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
// remove the zero page locations used for floating point operations from the free list
|
||||
// remove the zeropage locations used for floating point operations from the free list
|
||||
free.removeAll(listOf(
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,270 +0,0 @@
|
||||
package prog8.compiler.target.c64.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.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.codegen.assignment.*
|
||||
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
|
||||
val sub = stmt.target.targetSubroutine(program.namespace)!!
|
||||
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
|
||||
translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
|
||||
// functioncalls no longer return results on the stack, so simply ignore the results in the registers
|
||||
if(preserveStatusRegisterAfterCall)
|
||||
asmgen.out(" plp\t; restore status flags from call")
|
||||
}
|
||||
|
||||
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
|
||||
// output the code to setup the parameters and perform the actual call
|
||||
// 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() || sub.regXasParam()
|
||||
if(saveX)
|
||||
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine()!!)
|
||||
|
||||
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 {
|
||||
// multiple register arguments, risk of register clobbering.
|
||||
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
|
||||
when {
|
||||
stmt.args.all {it is AddressOf ||
|
||||
it is NumericLiteralValue ||
|
||||
it is StringLiteralValue ||
|
||||
it is ArrayLiteralValue ||
|
||||
it is IdentifierReference} -> {
|
||||
// no risk of clobbering for these simple argument types. Optimize the register loading.
|
||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||
argumentViaRegister(sub, arg.first, arg.second)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Risk of clobbering due to complex expression args. Work via the stack.
|
||||
registerArgsViaStackEvaluation(stmt, sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
asmgen.out(" jsr $subName")
|
||||
|
||||
if(preserveStatusRegisterAfterCall) {
|
||||
asmgen.out(" php\t; save status flags from call")
|
||||
// note: the containing statement (such as the FunctionCallStatement or the Assignment or the Expression)
|
||||
// must take care of popping this value again at the end!
|
||||
}
|
||||
|
||||
if(saveX)
|
||||
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
|
||||
}
|
||||
|
||||
private fun 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()) {
|
||||
when {
|
||||
argi.value.second.statusflag == Statusflag.Pc -> {
|
||||
require(argForCarry == null)
|
||||
argForCarry = argi
|
||||
}
|
||||
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
|
||||
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
|
||||
require(argForXregister==null)
|
||||
argForXregister = argi
|
||||
}
|
||||
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
|
||||
require(argForAregister == null)
|
||||
argForAregister = argi
|
||||
}
|
||||
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
|
||||
asmgen.out(" ldy P8ESTACK_LO+${argi.index},x")
|
||||
}
|
||||
else -> throw AssemblyError("weird argument")
|
||||
}
|
||||
}
|
||||
|
||||
if(argForCarry!=null) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+${argForCarry.index},x
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+ php""") // push the status flags
|
||||
}
|
||||
|
||||
if(argForAregister!=null) {
|
||||
when(argForAregister.value.second.registerOrPair) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x | ldy P8ESTACK_HI+${argForAregister.index},x")
|
||||
else -> throw AssemblyError("weird arg")
|
||||
}
|
||||
}
|
||||
|
||||
if(argForXregister!=null) {
|
||||
|
||||
if(argForAregister!=null)
|
||||
asmgen.out(" pha")
|
||||
when(argForXregister.value.second.registerOrPair) {
|
||||
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO+${argForXregister.index},x | tax")
|
||||
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO+${argForXregister.index},x | lda P8ESTACK_HI+${argForXregister.index},x | tax | tya")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI+${argForXregister.index},x | lda P8ESTACK_LO+${argForXregister.index},x | tax")
|
||||
else -> throw AssemblyError("weird arg")
|
||||
}
|
||||
if(argForAregister!=null)
|
||||
asmgen.out(" pla")
|
||||
} else {
|
||||
repeat(sub.parameters.size - 1) { asmgen.out(" inx") } // unwind stack
|
||||
}
|
||||
|
||||
if(argForCarry!=null)
|
||||
asmgen.out(" plp") // set the carry flag back to correct value
|
||||
}
|
||||
|
||||
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
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")
|
||||
}
|
||||
when {
|
||||
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.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, 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
|
||||
}
|
||||
}
|
@ -1,20 +1,26 @@
|
||||
package prog8.compiler.target.c64
|
||||
package prog8.compiler.target.cbm
|
||||
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.OutputType
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.IAssemblyProgram
|
||||
import prog8.compiler.target.generatedLabelPrefix
|
||||
import java.nio.file.Path
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
|
||||
|
||||
internal const val viceMonListPostfix = "vice-mon-list"
|
||||
|
||||
class AssemblyProgram(
|
||||
override val valid: Boolean,
|
||||
override val name: String,
|
||||
outputDir: Path,
|
||||
private val compTarget: String) : IAssemblyProgram {
|
||||
|
||||
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||
private val prgFile = outputDir.resolve("$name.prg")
|
||||
private val binFile = outputDir.resolve("$name.bin")
|
||||
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
|
||||
private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix")
|
||||
|
||||
override fun assemble(options: CompilationOptions) {
|
||||
override fun assemble(options: CompilationOptions): Int {
|
||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||
@ -23,12 +29,12 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro
|
||||
val outFile = when (options.output) {
|
||||
OutputType.PRG -> {
|
||||
command.add("--cbm-prg")
|
||||
println("\nCreating prg for target ${CompilationTarget.instance.name}.")
|
||||
println("\nCreating prg for target $compTarget.")
|
||||
prgFile
|
||||
}
|
||||
OutputType.RAW -> {
|
||||
command.add("--nostart")
|
||||
println("\nCreating raw binary for target ${CompilationTarget.instance.name}.")
|
||||
println("\nCreating raw binary for target $compTarget.")
|
||||
binFile
|
||||
}
|
||||
}
|
||||
@ -36,13 +42,11 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro
|
||||
|
||||
val proc = ProcessBuilder(command).inheritIO().start()
|
||||
val result = proc.waitFor()
|
||||
if (result != 0) {
|
||||
System.err.println("assembler failed with returncode $result")
|
||||
exitProcess(result)
|
||||
if (result == 0) {
|
||||
removeGeneratedLabelsFromMonlist()
|
||||
generateBreakpointList()
|
||||
}
|
||||
|
||||
removeGeneratedLabelsFromMonlist()
|
||||
generateBreakpointList()
|
||||
return result
|
||||
}
|
||||
|
||||
private fun removeGeneratedLabelsFromMonlist() {
|
1184
compiler/src/prog8/compiler/target/cbm/Petscii.kt
Normal file
1184
compiler/src/prog8/compiler/target/cbm/Petscii.kt
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
|
||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
||||
@ -74,12 +74,12 @@ private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
||||
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
||||
|
||||
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// the when statement (on bytes) generates a sequence of:
|
||||
// lda $ce01,x
|
||||
// cmp #$20
|
||||
// when statement (on bytes) generates a sequence of:
|
||||
// lda $ce01,x
|
||||
// cmp #$20
|
||||
// beq check_prog8_s72choice_32
|
||||
// lda $ce01,x
|
||||
// cmp #$21
|
||||
// lda $ce01,x
|
||||
// cmp #$21
|
||||
// beq check_prog8_s73choice_33
|
||||
// the repeated lda can be removed
|
||||
val mods = mutableListOf<Modification>()
|
||||
@ -179,7 +179,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
}
|
||||
|
||||
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>()
|
||||
for (pair in linesByFour) {
|
||||
@ -196,10 +196,14 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
|
||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("stx ") && second.startsWith("ldx "))
|
||||
) {
|
||||
val firstLoc = first.substring(4).trimStart()
|
||||
val secondLoc = second.substring(4).trimStart()
|
||||
if (firstLoc == secondLoc) {
|
||||
mods.add(Modification(pair[1].index, true, null))
|
||||
val third = pair[2].value.trimStart()
|
||||
if(!third.startsWith("b")) {
|
||||
// no branch instruction follows, we can potentiall remove the load instruction
|
||||
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
@ -1,14 +1,15 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ArrayElementTypes
|
||||
import prog8.ast.base.ArrayToElementTypes
|
||||
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 prog8.compiler.toHex
|
||||
import prog8.compiler.astprocessing.toConstantIntegerRange
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@ -19,15 +20,15 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
throw AssemblyError("unknown dt")
|
||||
when(stmt.iterable) {
|
||||
is RangeExpr -> {
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange(asmgen.options.compTarget)
|
||||
if(range==null) {
|
||||
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr)
|
||||
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
|
||||
} else {
|
||||
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range)
|
||||
translateForOverConstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), range)
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
|
||||
translateForOverIterableVar(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as IdentifierReference)
|
||||
}
|
||||
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
||||
}
|
||||
@ -40,6 +41,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val stepsize=range.step.constValue(program)!!.number.toInt()
|
||||
|
||||
if(stepsize < -1) {
|
||||
val limit = range.to.constValue(program)?.number?.toDouble()
|
||||
if(limit==0.0)
|
||||
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
|
||||
}
|
||||
|
||||
when(iterableDt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||
if (stepsize==1 || stepsize==-1) {
|
||||
@ -49,17 +57,17 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
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.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
|
||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
beq $endLabel
|
||||
$incdec $varname
|
||||
jmp $loopLabel
|
||||
$endLabel""")
|
||||
$incdec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
|
||||
} else {
|
||||
|
||||
@ -67,8 +75,8 @@ $endLabel""")
|
||||
|
||||
// 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.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
|
||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.body)
|
||||
if(stepsize>0) {
|
||||
@ -117,16 +125,15 @@ $modifiedLabel2 cmp #0 ; modified
|
||||
asmgen.out("""
|
||||
+ inc $varname
|
||||
bne $loopLabel
|
||||
inc $varname+1
|
||||
jmp $loopLabel
|
||||
""")
|
||||
inc $varname+1""")
|
||||
asmgen.jmp(loopLabel)
|
||||
} else {
|
||||
asmgen.out("""
|
||||
+ lda $varname
|
||||
bne +
|
||||
dec $varname+1
|
||||
+ dec $varname
|
||||
jmp $loopLabel""")
|
||||
+ dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
@ -239,7 +246,7 @@ $endLabel""")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val iterableName = asmgen.asmVariableName(ident)
|
||||
val decl = ident.targetVarDecl(program.namespace)!!
|
||||
val decl = ident.targetVarDecl(program)!!
|
||||
when(iterableDt) {
|
||||
DataType.STR -> {
|
||||
asmgen.out("""
|
||||
@ -282,7 +289,7 @@ $loopLabel sty $indexVar
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(length>=16 && asmgen.zeropage.available() > 0) {
|
||||
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
|
||||
// allocate index var on ZP
|
||||
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||
@ -321,7 +328,7 @@ $loopLabel sty $indexVar
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(length>=16 && asmgen.zeropage.available() > 0) {
|
||||
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
|
||||
// allocate index var on ZP
|
||||
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||
@ -369,7 +376,7 @@ $loopLabel""")
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||
}
|
||||
2 -> {
|
||||
if(range.last==255) {
|
||||
if(range.last==255 || range.last==254) {
|
||||
asmgen.out("""
|
||||
inc $varname
|
||||
beq $endLabel
|
||||
@ -377,34 +384,34 @@ $loopLabel""")
|
||||
bne $loopLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
inc $varname
|
||||
inc $varname
|
||||
lda $varname
|
||||
cmp #${range.last}
|
||||
beq $endLabel
|
||||
inc $varname
|
||||
inc $varname
|
||||
jmp $loopLabel""")
|
||||
cmp #${range.last+2}
|
||||
bne $loopLabel""")
|
||||
}
|
||||
}
|
||||
-2 -> {
|
||||
when (range.last) {
|
||||
0 -> asmgen.out("""
|
||||
lda $varname
|
||||
beq $endLabel
|
||||
dec $varname
|
||||
dec $varname
|
||||
jmp $loopLabel""")
|
||||
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""")
|
||||
dec $varname
|
||||
beq $endLabel
|
||||
dec $varname
|
||||
bne $loopLabel""")
|
||||
else -> asmgen.out("""
|
||||
lda $varname
|
||||
cmp #${range.last}
|
||||
beq $endLabel
|
||||
dec $varname
|
||||
dec $varname
|
||||
jmp $loopLabel""")
|
||||
dec $varname
|
||||
dec $varname
|
||||
lda $varname
|
||||
cmp #${range.last-2}
|
||||
bne $loopLabel""")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -415,8 +422,8 @@ $loopLabel""")
|
||||
beq $endLabel
|
||||
clc
|
||||
adc #${range.step}
|
||||
sta $varname
|
||||
jmp $loopLabel""")
|
||||
sta $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
}
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
@ -452,9 +459,9 @@ $loopLabel""")
|
||||
sta $varname
|
||||
lda $varname+1
|
||||
adc #>${range.step}
|
||||
sta $varname+1
|
||||
jmp $loopLabel
|
||||
$endLabel""")
|
||||
sta $varname+1""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -480,11 +487,10 @@ $loopLabel""")
|
||||
$endLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #${range.last}
|
||||
beq $endLabel
|
||||
inc $varname
|
||||
jmp $loopLabel
|
||||
lda $varname
|
||||
cmp #${range.last+1}
|
||||
bne $loopLabel
|
||||
$endLabel""")
|
||||
}
|
||||
asmgen.loopEndLabels.pop()
|
||||
@ -505,23 +511,22 @@ $loopLabel""")
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
beq $endLabel
|
||||
dec $varname
|
||||
jmp $loopLabel
|
||||
$endLabel""")
|
||||
dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
1 -> {
|
||||
asmgen.out("""
|
||||
dec $varname
|
||||
jmp $loopLabel
|
||||
bne $loopLabel
|
||||
$endLabel""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #${range.last}
|
||||
beq $endLabel
|
||||
dec $varname
|
||||
jmp $loopLabel
|
||||
lda $varname
|
||||
cmp #${range.last-1}
|
||||
bne $loopLabel
|
||||
$endLabel""")
|
||||
}
|
||||
}
|
||||
@ -546,13 +551,12 @@ $loopLabel""")
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #>${range.last}
|
||||
bne +
|
||||
beq $endLabel
|
||||
+ inc $varname
|
||||
bne $loopLabel
|
||||
inc $varname+1
|
||||
jmp $loopLabel
|
||||
$endLabel""")
|
||||
inc $varname+1""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
@ -574,17 +578,16 @@ $loopLabel""")
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #>${range.last}
|
||||
bne +
|
||||
beq $endLabel
|
||||
+ lda $varname
|
||||
bne +
|
||||
dec $varname+1
|
||||
+ dec $varname
|
||||
jmp $loopLabel
|
||||
$endLabel""")
|
||||
+ 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())
|
||||
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).getOr(DataType.UNDEFINED), stmt.definingSubroutine)
|
||||
}
|
@ -0,0 +1,361 @@
|
||||
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.InlineAssembly
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||
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
|
||||
if(regSaveOnStack)
|
||||
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
||||
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
|
||||
if(regSaveOnStack)
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
||||
else
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
||||
// Output only the code to set up the parameters and perform the actual call
|
||||
// NOTE: does NOT output the code to deal with the result values!
|
||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||
|
||||
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) {
|
||||
asmgen.out(" jsr $subName")
|
||||
} else {
|
||||
// inline the subroutine.
|
||||
// we do this by copying the subroutine's statements at the call site.
|
||||
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
||||
// (this condition has been enforced by an ast check earlier)
|
||||
|
||||
// note: for now, this is only reliably supported for asmsubs.
|
||||
if(!sub.isAsmSubroutine)
|
||||
throw AssemblyError("can only reliably inline asmsub routines at this time")
|
||||
|
||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||
val assembly = sub.statements.single() as InlineAssembly
|
||||
asmgen.translate(assembly)
|
||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||
}
|
||||
|
||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||
}
|
||||
|
||||
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 arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
|
||||
require(argForXregister==null)
|
||||
argForXregister = argi
|
||||
}
|
||||
argi.value.second.registerOrPair in arrayOf(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().lowercase()}
|
||||
""")
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(
|
||||
" stz cx16.${
|
||||
argi.value.second.registerOrPair.toString().lowercase()
|
||||
}+1")
|
||||
else
|
||||
asmgen.out(
|
||||
" lda #0 | sta cx16.${
|
||||
argi.value.second.registerOrPair.toString().lowercase()
|
||||
}+1")
|
||||
}
|
||||
in WordDatatypes, in IterableDatatypes ->
|
||||
asmgen.out(
|
||||
"""
|
||||
lda P8ESTACK_LO$plusIdxStr,x
|
||||
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
|
||||
lda P8ESTACK_HI$plusIdxStr,x
|
||||
sta cx16.${
|
||||
argi.value.second.registerOrPair.toString().lowercase()
|
||||
}+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.getOr(DataType.UNDEFINED)
|
||||
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.getOr(DataType.UNDEFINED)
|
||||
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
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
|
||||
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt)
|
||||
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
|
||||
} else {
|
||||
val target: AsmAssignTarget =
|
||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||
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
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
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
|
||||
import prog8.compiler.toHex
|
||||
|
||||
|
||||
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@ -15,11 +15,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
val targetIdent = stmt.target.identifier
|
||||
val targetMemory = stmt.target.memoryAddress
|
||||
val targetArrayIdx = stmt.target.arrayindexed
|
||||
val scope = stmt.definingSubroutine()
|
||||
val scope = stmt.definingSubroutine
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val what = asmgen.asmVariableName(targetIdent)
|
||||
when (stmt.target.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||
when (stmt.target.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
@ -65,9 +65,10 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
||||
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(targetArrayIdx.indexer.indexNum!=null) {
|
||||
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize()
|
||||
val elementDt = targetArrayIdx.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val constIndex = targetArrayIdx.indexer.constIndex()
|
||||
if(constIndex!=null) {
|
||||
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||
in WordDatatypes -> {
|
||||
@ -91,7 +92,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
else
|
||||
{
|
||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||
asmgen.saveRegister(CpuRegister.X, false, scope!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope!!)
|
||||
asmgen.out(" tax")
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
@ -105,7 +106,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
lda $asmArrayvarname,x
|
||||
bne +
|
||||
dec $asmArrayvarname+1,x
|
||||
+ dec $asmArrayvarname
|
||||
+ dec $asmArrayvarname,x
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
@ -119,7 +120,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
}
|
||||
asmgen.restoreRegister(CpuRegister.X, false)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user