mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
868
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ba2ee9857 | ||
|
|
a025d68291 | ||
|
|
81794aded3 | ||
|
|
9621552056 | ||
|
|
5685a1f229 | ||
|
|
b014c78c94 | ||
|
|
ca729d279e | ||
|
|
3b0faa749a | ||
|
|
22a2f8cbc5 | ||
|
|
468850bc47 | ||
|
|
20374f4059 | ||
|
|
ceffa4d83d | ||
|
|
af14562e2b | ||
|
|
dbba6a94fa | ||
|
|
302e4bdf8e | ||
|
|
ccc26a0eb2 | ||
|
|
e017c541bb | ||
|
|
3d5a7795d0 | ||
|
|
af06fdfffc | ||
|
|
e811ecdbde | ||
|
|
4c65049817 | ||
|
|
a7cbc2c5bf | ||
|
|
d51ca62431 | ||
|
|
9ac60eec40 | ||
|
|
c55b16f4cb | ||
|
|
bd0457f262 | ||
|
|
ce2b298d6f | ||
|
|
cf7afaf77a | ||
|
|
54507ad9b3 | ||
|
|
30435b6778 | ||
|
|
619a215a87 | ||
|
|
c12e27dacc | ||
|
|
db32b81d7a | ||
|
|
687d86d36e | ||
|
|
e074fe614f | ||
|
|
bbfb09e467 | ||
|
|
1358a535e3 | ||
|
|
bc62b2de81 | ||
|
|
58f544fe14 | ||
|
|
7dc05688bf | ||
|
|
aac7d409dc | ||
|
|
0771105694 | ||
|
|
9a461628f2 | ||
|
|
c8f309307f | ||
|
|
b702ea50da | ||
|
|
b0f1f655c3 | ||
|
|
59c41ad129 | ||
|
|
60eeaba791 | ||
|
|
30266c1b92 | ||
|
|
05ba1237dc | ||
|
|
9d537ab3c0 | ||
|
|
1ba08ec6ec | ||
|
|
199c3e4efb | ||
|
|
a7c035f4d1 | ||
|
|
05cbe74a60 | ||
|
|
d652d7a924 | ||
|
|
739fb6dc42 | ||
|
|
9c53791ef4 | ||
|
|
420a9b7249 | ||
|
|
9bf8318b1c | ||
|
|
94822b8662 | ||
|
|
b0d50c2a68 | ||
|
|
deae2e6422 | ||
|
|
c8d29e6dc9 | ||
|
|
a7b14c2931 | ||
|
|
a14a8f4e62 | ||
|
|
cf50372515 | ||
|
|
da0df8f133 | ||
|
|
27070f31e8 | ||
|
|
aa3f8e3038 | ||
|
|
277e9c09bb | ||
|
|
70521ce301 | ||
|
|
93b5faa038 | ||
|
|
f33814a594 | ||
|
|
986ae35821 | ||
|
|
937b520838 | ||
|
|
250ddda11b | ||
|
|
bde52cc134 | ||
|
|
8e739febf0 | ||
|
|
3fcb4b4262 | ||
|
|
56643f43df | ||
|
|
a9cad9b51d | ||
|
|
8647b136bd | ||
|
|
4179f2c571 | ||
|
|
02a118ec0e | ||
|
|
561f31329d | ||
|
|
2bbbacc46e | ||
|
|
c3920de251 | ||
|
|
14bf07907b | ||
|
|
c657935f10 | ||
|
|
c08def79ae | ||
|
|
296295011e | ||
|
|
de20c09790 | ||
|
|
4199ab4200 | ||
|
|
fe6850816c | ||
|
|
eff1a9fc53 | ||
|
|
5857382242 | ||
|
|
dfda43e747 | ||
|
|
1029929111 | ||
|
|
ae55109a18 | ||
|
|
2ce7a63c1e | ||
|
|
da6934ea33 | ||
|
|
2c8ccb706e | ||
|
|
0d0d707976 | ||
|
|
4912c96f78 | ||
|
|
79dd8d6b92 | ||
|
|
f750caa833 | ||
|
|
d17c978892 | ||
|
|
45cb50ebb1 | ||
|
|
2cd2bb0121 | ||
|
|
5b890ac5d5 | ||
|
|
894a3e90db | ||
|
|
234468690e | ||
|
|
5f50b254a2 | ||
|
|
c5b17ae880 | ||
|
|
4b320013cf | ||
|
|
f3de26bf6c | ||
|
|
65a1300147 | ||
|
|
ba6179a4d3 | ||
|
|
6b91a464c0 | ||
|
|
08ac5eff3e | ||
|
|
7de39bbd62 | ||
|
|
198cf4d722 | ||
|
|
b0b8513cb8 | ||
|
|
82cc66c0cc | ||
|
|
c81d9b25ac | ||
|
|
3aab457a47 | ||
|
|
c89f870dd0 | ||
|
|
ffb0a4d603 | ||
|
|
df659d285b | ||
|
|
2c2664c91f | ||
|
|
e0147a4a7e | ||
|
|
859223c98b | ||
|
|
7bdb9bc853 | ||
|
|
4e87aa6b85 | ||
|
|
02667e9916 | ||
|
|
9d57cbe263 | ||
|
|
8b25ff2390 | ||
|
|
2cb753caf8 | ||
|
|
34f0bea4e8 | ||
|
|
82828606e6 | ||
|
|
ae751c4af4 | ||
|
|
51b98204e9 | ||
|
|
12f375e070 | ||
|
|
54a1a0704a | ||
|
|
08b4168311 | ||
|
|
4fdd12929a | ||
|
|
cc4f27d121 | ||
|
|
61041f4502 | ||
|
|
673ed91ee3 | ||
|
|
65b6280618 | ||
|
|
e50e175522 | ||
|
|
fa1ceab78f | ||
|
|
7eab0278b3 | ||
|
|
36b12e9897 | ||
|
|
69eaaf29c4 | ||
|
|
69def73f3f | ||
|
|
9c2d8541c3 | ||
|
|
6c93254758 | ||
|
|
a68cdd13a6 | ||
|
|
295dfada61 | ||
|
|
d978be2f9d | ||
|
|
0a4601721a | ||
|
|
d870c39aa4 | ||
|
|
2b93efbb33 | ||
|
|
c563562a6f | ||
|
|
92a0d3cb1b | ||
|
|
21d45a0a90 | ||
|
|
c87afc03ef | ||
|
|
8dc9c4d3c2 | ||
|
|
e264aff607 | ||
|
|
9591aad814 | ||
|
|
3208ef1f1a | ||
|
|
36fcfa6965 | ||
|
|
ba7bf3f4fe | ||
|
|
fcc93b385d | ||
|
|
4c6d9b8f5f | ||
|
|
6a3ee52bd5 | ||
|
|
8dec449a1c | ||
|
|
a09942254d | ||
|
|
d702ace09f | ||
|
|
f011d3f100 | ||
|
|
e404530ba4 | ||
|
|
48da22866c | ||
|
|
2e529f8849 | ||
|
|
79a5ddcdd8 | ||
|
|
7aecb8d6eb | ||
|
|
73768e1876 | ||
|
|
3514bd61ab | ||
|
|
a7f60a571d | ||
|
|
3705064157 | ||
|
|
208205faea | ||
|
|
5af6afef61 | ||
|
|
a5a535806b | ||
|
|
d582c1edf6 | ||
|
|
7275137782 | ||
|
|
f11da0488a | ||
|
|
ed1abe7e6b | ||
|
|
4dedc27e5b | ||
|
|
e70a1ed403 | ||
|
|
c53e72c5b2 | ||
|
|
5ba494c5e5 | ||
|
|
b353904d30 | ||
|
|
a58a643d0d | ||
|
|
823ecfac6f | ||
|
|
22035077af | ||
|
|
da8c3eda92 | ||
|
|
41c2b9c039 | ||
|
|
5c16ebe377 | ||
|
|
c7e25e323a | ||
|
|
1e2fc4d6f9 | ||
|
|
b5dfc0cd97 | ||
|
|
799486eb1b | ||
|
|
ed67a259de | ||
|
|
d276fb9ed5 | ||
|
|
e0329e4457 | ||
|
|
a3161fa117 | ||
|
|
9c8e81b566 | ||
|
|
e2e72a1791 | ||
|
|
ec9567a669 | ||
|
|
b3b983c7ec | ||
|
|
14ae579f00 | ||
|
|
45f48d2e88 | ||
|
|
8d6ed7712d | ||
|
|
eea185bb83 | ||
|
|
99d28ae557 | ||
|
|
528762d961 | ||
|
|
c6e9a45293 | ||
|
|
6880bd2a05 | ||
|
|
7547fb7747 | ||
|
|
ff0e922674 | ||
|
|
71f66171c4 | ||
|
|
c1f76cf073 | ||
|
|
c2d151920d | ||
|
|
c916570abd | ||
|
|
bdab975d14 | ||
|
|
1693e4ff9c | ||
|
|
70d81298c1 | ||
|
|
6bf304b67b | ||
|
|
a5978a913b | ||
|
|
e528f0ccaa | ||
|
|
e5f488dcfb | ||
|
|
1de1c10b41 | ||
|
|
c47a6bde0e | ||
|
|
b8d38eccca | ||
|
|
4c2144eb0d | ||
|
|
68399e8370 | ||
|
|
7b61b066e6 | ||
|
|
bd2a47fb54 | ||
|
|
2521df21e7 | ||
|
|
ddb6b597d6 | ||
|
|
c24fc9e81d | ||
|
|
68f0d2285f | ||
|
|
25f883bb85 | ||
|
|
adc6d6626a | ||
|
|
90ff904b6e | ||
|
|
ba54601eab | ||
|
|
9937990618 | ||
|
|
1c71f702f4 | ||
|
|
3d3c567ea6 | ||
|
|
539c75bbf6 | ||
|
|
2683b6388a | ||
|
|
e007d127f0 | ||
|
|
e5d9609db1 | ||
|
|
051f3809cd | ||
|
|
b5fd07bb91 | ||
|
|
4097d51514 | ||
|
|
577815a1f4 | ||
|
|
c2a942dd45 | ||
|
|
82342268da | ||
|
|
30e576e253 | ||
|
|
a5782c300a | ||
|
|
84f720b63b | ||
|
|
b8cded4373 | ||
|
|
368dce93d9 | ||
|
|
644226f925 | ||
|
|
42e8e91034 | ||
|
|
af80ec6502 | ||
|
|
cf714d8e0c | ||
|
|
86c2f711ed | ||
|
|
86c01e9d7b | ||
|
|
7138ee024b | ||
|
|
b9f65186cb | ||
|
|
27aeafef0d | ||
|
|
16901212d4 | ||
|
|
49f5c30e8c | ||
|
|
bef1678c2a | ||
|
|
3459ffcdb9 | ||
|
|
4cfe9b2729 | ||
|
|
a919ea63e4 | ||
|
|
97e5a6f7bb | ||
|
|
79c05ae396 | ||
|
|
f25ea95bdf | ||
|
|
45bf8ba72a | ||
|
|
07910fc543 | ||
|
|
c64fdcffa8 | ||
|
|
c0b8cf827d | ||
|
|
fc38847af4 | ||
|
|
fea52c6145 | ||
|
|
4b20440efe | ||
|
|
c39410afc2 | ||
|
|
93b953cf60 | ||
|
|
01c82f92eb | ||
|
|
13e032e1a3 | ||
|
|
8405e6f2a3 | ||
|
|
491b28f6b6 | ||
|
|
9286d6b2c8 | ||
|
|
d144235dc9 | ||
|
|
c3b5ee3dfd | ||
|
|
b0e0058310 | ||
|
|
9cbbf85a85 | ||
|
|
2fa82ff3ad | ||
|
|
9936a855ab | ||
|
|
3e03980478 | ||
|
|
e042756453 | ||
|
|
84d5247b78 | ||
|
|
4682462c19 | ||
|
|
b6e503a67b | ||
|
|
dedae29d72 | ||
|
|
bb1d1e2007 | ||
|
|
0aa6c130e8 | ||
|
|
092116de1d | ||
|
|
bf0733f8b3 | ||
|
|
b0cf649c2d | ||
|
|
3499121f9c | ||
|
|
c43cacdc2f | ||
|
|
ead1e5e879 | ||
|
|
4c1f193d53 | ||
|
|
abaaea33d8 | ||
|
|
51257e47da | ||
|
|
fe42040d48 | ||
|
|
67ddd97f3d | ||
|
|
abea7bf329 | ||
|
|
bc3f49dc13 | ||
|
|
7565d04cf5 | ||
|
|
0d76eea1be | ||
|
|
9a2851a9d5 | ||
|
|
5e1612f204 | ||
|
|
316181b971 | ||
|
|
e753eed4d4 | ||
|
|
b52a07c7c4 | ||
|
|
f210c8b0db | ||
|
|
f865a9801f | ||
|
|
6a165e951b | ||
|
|
9322fbb234 | ||
|
|
4b7e2dce9d | ||
|
|
7bda2e987c | ||
|
|
e645b0abe1 | ||
|
|
f10a58ee1b | ||
|
|
f498f598bf | ||
|
|
4dfcb59b6f | ||
|
|
4506a3cb3e | ||
|
|
247559858f | ||
|
|
8a8cf3abc7 | ||
|
|
cf2a5ae4f9 | ||
|
|
0cf488b586 | ||
|
|
a019f22f47 | ||
|
|
9587e1b724 | ||
|
|
e5865a1446 | ||
|
|
7adbdf39ac | ||
|
|
57714efb40 | ||
|
|
dafb0906c3 | ||
|
|
96bb64aebb | ||
|
|
57254233e4 | ||
|
|
9ec4b35de9 | ||
|
|
9adbd77ce4 | ||
|
|
8957e8fff7 | ||
|
|
3aa0344c4a | ||
|
|
54de2c5fbf | ||
|
|
5b5f553d37 | ||
|
|
0e978b250b | ||
|
|
67e6888009 | ||
|
|
b9fe1f2057 | ||
|
|
debb91965b | ||
|
|
d0b3fefd57 | ||
|
|
5cc3623a2a | ||
|
|
d08f532a10 | ||
|
|
35b7e4a4de | ||
|
|
38b77d8817 | ||
|
|
3078f402bb | ||
|
|
f977daafbb | ||
|
|
aabd99e9eb | ||
|
|
e7dbab1822 | ||
|
|
cf6efd76f0 | ||
|
|
8a7922ffcc | ||
|
|
c7a8e84fd0 | ||
|
|
cb57bea862 | ||
|
|
b24ce80945 | ||
|
|
eb18397035 | ||
|
|
cfd1f951f0 | ||
|
|
b0e55eb25b | ||
|
|
37a86f3899 | ||
|
|
5700ae446f | ||
|
|
cb240f6a4b | ||
|
|
b405ffb8bd | ||
|
|
0fa08310e7 | ||
|
|
968d9819db | ||
|
|
54df69a2b3 | ||
|
|
88b3291785 | ||
|
|
c9c68f7d8c | ||
|
|
2d3f0baa51 | ||
|
|
bd4184252f | ||
|
|
f043dba23d | ||
|
|
f85c59b12c | ||
|
|
e6a267bd3f | ||
|
|
a532fb411b | ||
|
|
1f4ffdffc1 | ||
|
|
1053a9a761 | ||
|
|
34eedd1fe7 | ||
|
|
2c10e2b102 | ||
|
|
7f51bbd777 | ||
|
|
5c36e21948 | ||
|
|
a46443417e | ||
|
|
57055b3b55 | ||
|
|
559521c6df | ||
|
|
11e3b39486 | ||
|
|
1c50e397de | ||
|
|
05bea61a69 | ||
|
|
2a8b24074b | ||
|
|
99ead2c74c | ||
|
|
f557d2bee0 | ||
|
|
1f184a14ae | ||
|
|
8be1cd1090 | ||
|
|
5278f9a595 | ||
|
|
dd1a886b66 | ||
|
|
c3ec400ee7 | ||
|
|
7f95377ad7 | ||
|
|
903c553f94 | ||
|
|
0e7237c64a | ||
|
|
122af69cdc | ||
|
|
786d742e76 | ||
|
|
c4c46fd282 | ||
|
|
5a49c97e5d | ||
|
|
2cd195b343 | ||
|
|
36238267d0 | ||
|
|
52fe2f2c0d | ||
|
|
4bf0eba2f8 | ||
|
|
5130a74754 | ||
|
|
9d1bcfc0c6 | ||
|
|
15756a8977 | ||
|
|
0ab36bcbf6 | ||
|
|
36eef51e1c | ||
|
|
309c25a993 | ||
|
|
e61db5ed3f | ||
|
|
cc5ef21fd6 | ||
|
|
f1f925ecee | ||
|
|
ab422c0ebc | ||
|
|
05a13463f4 | ||
|
|
19238cc366 | ||
|
|
e3d4401d99 | ||
|
|
5da4efc2a3 | ||
|
|
c0c44156f1 | ||
|
|
8645833319 | ||
|
|
6a867d98eb | ||
|
|
58363ff4e4 | ||
|
|
2555aa72ca | ||
|
|
3b94fd4c4b | ||
|
|
95f500c32f | ||
|
|
870c2e8827 | ||
|
|
af2360648f | ||
|
|
bb95d40d6d | ||
|
|
55ea599fca | ||
|
|
fd5fdf3d13 | ||
|
|
ca129edce4 | ||
|
|
c1694a4b08 | ||
|
|
a348bbbebe | ||
|
|
e6b0a89d64 | ||
|
|
9a4821d1e3 | ||
|
|
71583a5b93 | ||
|
|
407ba34c72 | ||
|
|
fc4782a35b | ||
|
|
7a83b7db1b | ||
|
|
a827f4c4ce | ||
|
|
01c5794799 | ||
|
|
1c093cca2d | ||
|
|
a85b267872 | ||
|
|
5e0c1c9794 | ||
|
|
887df44359 | ||
|
|
a5fc162c71 | ||
|
|
9d3fc99d71 | ||
|
|
a871713907 | ||
|
|
2a19e5682a | ||
|
|
22d4fa73d6 | ||
|
|
2ed95c52ef | ||
|
|
73fb04aaf5 | ||
|
|
f97a7f426c | ||
|
|
c1189617f3 | ||
|
|
78d43c3cb6 | ||
|
|
86f9b6aa69 | ||
|
|
8df2bb8d2e | ||
|
|
33f30d925e | ||
|
|
c8c51245af | ||
|
|
db227679a8 | ||
|
|
02110de844 | ||
|
|
7d77a0eca6 | ||
|
|
0f3475128a | ||
|
|
e6e4714b02 | ||
|
|
d83ece397d | ||
|
|
a3c768bc6b | ||
|
|
a3d14003cb | ||
|
|
d451953e40 | ||
|
|
d505c42d15 | ||
|
|
8c92663824 | ||
|
|
10d86eb4d8 | ||
|
|
ee5e734f23 | ||
|
|
6b233b7da3 | ||
|
|
a140519374 | ||
|
|
2b118f2e42 | ||
|
|
be9d9fe377 | ||
|
|
d1345aafec | ||
|
|
d5932a7a57 | ||
|
|
5751beb72f | ||
|
|
02194a735a | ||
|
|
b5fc25b2f4 | ||
|
|
5a593b9ef3 | ||
|
|
b411914624 | ||
|
|
0f80b695e7 | ||
|
|
63c425f247 | ||
|
|
276627abcb | ||
|
|
34225d10aa | ||
|
|
69261c51b3 | ||
|
|
63d7ef7105 | ||
|
|
88a3e5b23f | ||
|
|
282c3aa00b | ||
|
|
19f3b03ac3 | ||
|
|
1b5efe1e5d | ||
|
|
2b5a15a178 | ||
|
|
a9f22d9375 | ||
|
|
f9b3514f2a | ||
|
|
be12fb507e | ||
|
|
b2dcd02ee4 | ||
|
|
5c23471254 | ||
|
|
6cb6f37871 | ||
|
|
67e284b680 | ||
|
|
1e5ba48fe0 | ||
|
|
8e6a27d7c1 | ||
|
|
d0edc0b484 | ||
|
|
ccd3d94ec0 | ||
|
|
98355d3850 | ||
|
|
0e53113b85 | ||
|
|
e9a3ed457e | ||
|
|
6001da30d8 | ||
|
|
c55f5e4b50 | ||
|
|
e983c0c2b9 | ||
|
|
532bf13057 | ||
|
|
de706e79de | ||
|
|
a19f337ebe | ||
|
|
041a904e44 | ||
|
|
131134d24b | ||
|
|
97fb5298a8 | ||
|
|
b580a177d4 | ||
|
|
a0b1ca5d9f | ||
|
|
3a291c56b4 | ||
|
|
ed41d5dbc5 | ||
|
|
628bd41740 | ||
|
|
de988f72c6 | ||
|
|
869c5ef544 | ||
|
|
17ee233e8b | ||
|
|
f2d1dffbe0 | ||
|
|
4008280a62 | ||
|
|
2e7235ec02 | ||
|
|
a4330f5048 | ||
|
|
f65aeff231 | ||
|
|
505243aff7 | ||
|
|
d0f2fc13ba | ||
|
|
53d51dcd38 | ||
|
|
c1aa0e49fe | ||
|
|
e1e72d4317 | ||
|
|
feec343cbc | ||
|
|
5cdc4233f3 | ||
|
|
1ae89d18cc | ||
|
|
eb19d00a1e | ||
|
|
e01ee10ef0 | ||
|
|
f4b3cb12e1 | ||
|
|
77ced60ad6 | ||
|
|
6fad90af6e | ||
|
|
3076318d7e | ||
|
|
9c99110738 | ||
|
|
1457d36997 | ||
|
|
43fb9296dd | ||
|
|
8bf744cc4a | ||
|
|
f1f5f070cf | ||
|
|
191a240d0d | ||
|
|
cafb2e355b | ||
|
|
c99c5751f8 | ||
|
|
435b7e56fa | ||
|
|
14f13fac53 | ||
|
|
f8b5037924 | ||
|
|
d2870cb02e | ||
|
|
8e43fc426c | ||
|
|
ba1b6e127a | ||
|
|
4f36a6958c | ||
|
|
df8f08b4ab | ||
|
|
fd5746d3fc | ||
|
|
6ab74fd51d | ||
|
|
245082ca1d | ||
|
|
845e58ea10 | ||
|
|
2a5dbc1ef7 | ||
|
|
02a1deb1e0 | ||
|
|
3ba8e5de10 | ||
|
|
819d1b4af7 | ||
|
|
cdd3d37376 | ||
|
|
d0bc01d618 | ||
|
|
ffab73334a | ||
|
|
83aeb8846c | ||
|
|
3a2087310b | ||
|
|
61b616701e | ||
|
|
bddf872906 | ||
|
|
48214a6fdb | ||
|
|
b7340856df | ||
|
|
2685854113 | ||
|
|
ad92f39469 | ||
|
|
d3fe041e01 | ||
|
|
f32bac5004 | ||
|
|
9a8e4ac105 | ||
|
|
78857e5c22 | ||
|
|
1bb828875d | ||
|
|
82aa67ffda | ||
|
|
7c44835772 | ||
|
|
8b9c63fe90 | ||
|
|
e77fcc8c16 | ||
|
|
08c12131d4 | ||
|
|
5a3ddf705a | ||
|
|
20b29fb9b1 | ||
|
|
fd3d59e159 | ||
|
|
6d1dfd2a09 | ||
|
|
7095b14bcb | ||
|
|
1fe5ff18b1 | ||
|
|
793b2a703b | ||
|
|
53948be68f | ||
|
|
3d0dbb71ce | ||
|
|
3dfa804e52 | ||
|
|
fd93d103f6 | ||
|
|
28977996e2 | ||
|
|
e867cb88a7 | ||
|
|
1ba8dfcc8b | ||
|
|
cd2d30c2c7 | ||
|
|
a47d2fa40d | ||
|
|
6be70af79f | ||
|
|
15a28814d5 | ||
|
|
049acf0e98 | ||
|
|
df0d9e567f | ||
|
|
78920aa115 | ||
|
|
1bb9ad5f46 | ||
|
|
1f30a06429 | ||
|
|
5560c342db | ||
|
|
e073c007ad | ||
|
|
9d1fa856d4 | ||
|
|
7a26bb972f | ||
|
|
e72a4723f9 | ||
|
|
96f0d112d2 | ||
|
|
eac1b64f81 | ||
|
|
0011dd8f4c | ||
|
|
5ce463d133 | ||
|
|
5926473281 | ||
|
|
0a1d06b7f2 | ||
|
|
f98e7d6ad9 | ||
|
|
37896aeed4 | ||
|
|
b175dd0265 | ||
|
|
310d17d8bb | ||
|
|
371bd4b120 | ||
|
|
3965d77813 | ||
|
|
e0ed761661 | ||
|
|
51d5261776 | ||
|
|
b85ac38b3b | ||
|
|
6ff9153d9b | ||
|
|
e0d2e407e4 | ||
|
|
0117ab410b | ||
|
|
b78a0cf076 | ||
|
|
722d52c939 | ||
|
|
162e5ffe3c | ||
|
|
7383ffae46 | ||
|
|
1b4635d27e | ||
|
|
1c376aa9d1 | ||
|
|
91ff17f666 | ||
|
|
5a81021ddc | ||
|
|
9fdd9254f1 | ||
|
|
ae4c4ca160 | ||
|
|
7ab4880c4a | ||
|
|
5c16a40f33 | ||
|
|
6f9f31cd8a | ||
|
|
7cd70acc01 | ||
|
|
2d3c20d347 | ||
|
|
b05bc9ac0e | ||
|
|
2589f953c3 | ||
|
|
a40f9de9e6 | ||
|
|
e7491a56ef | ||
|
|
2bf5d1406d | ||
|
|
c32e7ee57b | ||
|
|
a633b5a4b5 | ||
|
|
a5c078be14 | ||
|
|
547d21a42b | ||
|
|
e2e0992d02 | ||
|
|
10fd321537 | ||
|
|
fb26a7af3b | ||
|
|
8592a3a200 | ||
|
|
930697dad5 | ||
|
|
a60ba27bad | ||
|
|
87480b150a | ||
|
|
0cb9d408ff | ||
|
|
c784fd03fd | ||
|
|
5dcc11d67e | ||
|
|
e36a589e23 | ||
|
|
fe6a8f63a0 | ||
|
|
60e2a38060 | ||
|
|
d808818506 | ||
|
|
0f72bafeae | ||
|
|
c794d2a50b | ||
|
|
db84c375f1 | ||
|
|
03fa6422b3 | ||
|
|
ad34633ca9 | ||
|
|
d5eef79892 | ||
|
|
d5a53c5d21 | ||
|
|
bd51296a33 | ||
|
|
c0103f149e | ||
|
|
86174e49c9 | ||
|
|
0be336c8a8 | ||
|
|
d836f48f38 | ||
|
|
952d7b3162 | ||
|
|
5638eb1c53 | ||
|
|
d3f19bb438 | ||
|
|
be60021edc | ||
|
|
b2ed3b5934 | ||
|
|
59d0f7d49c | ||
|
|
47700918cf | ||
|
|
db7edde2aa | ||
|
|
e2702d3e22 | ||
|
|
74475d023b | ||
|
|
9ae0623ad0 | ||
|
|
33b34b990e | ||
|
|
6dc96bb8f0 | ||
|
|
aeca6350da | ||
|
|
6a340aa59d | ||
|
|
207650fbc2 | ||
|
|
4fb3df977e | ||
|
|
9f742e1806 | ||
|
|
aca6c22098 | ||
|
|
4b45dae68a | ||
|
|
0b68481fda | ||
|
|
bba08f55de | ||
|
|
9e5df3d2d4 | ||
|
|
5c1aa2307d | ||
|
|
3129f5e071 | ||
|
|
04a8181c05 | ||
|
|
27bf9c9a4c | ||
|
|
7658a0ab12 | ||
|
|
42d3340fe4 | ||
|
|
1100ab0d22 | ||
|
|
96eb3f0fb2 | ||
|
|
1ed618c474 | ||
|
|
e6c26587d6 | ||
|
|
4739bd087a | ||
|
|
92751d9285 | ||
|
|
0792571b28 | ||
|
|
511564dd1e | ||
|
|
3da3d63eb4 | ||
|
|
3c859512a5 | ||
|
|
45c05522d5 | ||
|
|
db417ab9ce | ||
|
|
c17a3aff5c | ||
|
|
6184a5004c | ||
|
|
07772bddfd | ||
|
|
15bf4c7692 | ||
|
|
f38c701658 | ||
|
|
10ce992b35 | ||
|
|
331e2b8f2b | ||
|
|
05093e35c8 | ||
|
|
5fd11ca036 | ||
|
|
5330069b53 | ||
|
|
5408a3ca68 | ||
|
|
cecf49b8e3 | ||
|
|
65c17b920a | ||
|
|
7666b72fea | ||
|
|
bcd12a0fc7 | ||
|
|
dd0828a1fc | ||
|
|
770532a039 | ||
|
|
400149d5a3 | ||
|
|
d6ed40af36 | ||
|
|
570ea50a3b | ||
|
|
04b9f8e749 | ||
|
|
2bd7f3e210 | ||
|
|
00bae6e746 | ||
|
|
f6d5e88f31 | ||
|
|
8d44a1b226 | ||
|
|
a47be61812 | ||
|
|
68226a8fcd | ||
|
|
2016883f6c | ||
|
|
d4dd2f02e8 | ||
|
|
bc959435ec | ||
|
|
e5c6e4b854 | ||
|
|
4294658d71 | ||
|
|
0a7352de99 | ||
|
|
bb907aa2af | ||
|
|
e5d74d55b5 | ||
|
|
c291d7d4a8 | ||
|
|
d2633187a6 | ||
|
|
ca56cf26c5 | ||
|
|
f72bec3c4a | ||
|
|
06b4ec600a | ||
|
|
410e330889 | ||
|
|
11f187bda8 | ||
|
|
a53e7c7050 | ||
|
|
e56ccdbc3a | ||
|
|
51264267ad | ||
|
|
8be0cd3c19 | ||
|
|
decfbfc8cc | ||
|
|
652da4c692 | ||
|
|
ce58f0e759 | ||
|
|
8ba8efbc3e | ||
|
|
276121bfb3 | ||
|
|
5c9c6bd023 | ||
|
|
86f331aa39 | ||
|
|
e61328b6d6 | ||
|
|
9502a6f590 | ||
|
|
01748517ae | ||
|
|
3890191981 | ||
|
|
c20e5b788b | ||
|
|
fb3a001a99 | ||
|
|
04910ffbc0 | ||
|
|
caa991e478 | ||
|
|
f9767a5b1b | ||
|
|
3b68741208 | ||
|
|
b8ef64139a | ||
|
|
1ad9e0fc46 | ||
|
|
ce244906ef | ||
|
|
63ab79157f | ||
|
|
9ac0cfd14c | ||
|
|
02c544bb62 | ||
|
|
998d1ad446 | ||
|
|
89e38c6d42 | ||
|
|
a326c6f079 | ||
|
|
acf70d68ca | ||
|
|
17850d3bc1 | ||
|
|
d03e49a92f | ||
|
|
62c5b531c5 | ||
|
|
8911f58bd2 | ||
|
|
da6e47d422 | ||
|
|
10d80d0274 | ||
|
|
23b9a2792d | ||
|
|
0a52cd2cee | ||
|
|
fedf342cee | ||
|
|
2bafc161d6 | ||
|
|
d0c5e35f0a | ||
|
|
859a02fb0a | ||
|
|
2a53dbc1f3 | ||
|
|
63cba4825a | ||
|
|
08e3652924 | ||
|
|
10796c09c7 | ||
|
|
a3cbf4c3db | ||
|
|
bdbdf6e5cd | ||
|
|
1b5dd980df | ||
|
|
f6b59c6832 | ||
|
|
fe8d904792 | ||
|
|
9736ac19d1 | ||
|
|
14abc1f0b7 | ||
|
|
effc7f1b56 | ||
|
|
093be9f2dd | ||
|
|
9aff85f08a | ||
|
|
f2077cb56f | ||
|
|
2a9ac45cad | ||
|
|
e15452b110 | ||
|
|
a64b586a43 | ||
|
|
2613d45cd5 | ||
|
|
c25fbbccfb | ||
|
|
77f0cf8e89 | ||
|
|
74ca190ce0 | ||
|
|
6a0b22bb64 | ||
|
|
07be7f0154 |
No files matched your search
@@ -2,4 +2,4 @@
|
||||
description: Startup workflow to load project context
|
||||
---
|
||||
|
||||
1. Read the file `QWEN.md` in the project root to understand the project structure, module descriptions, and development commands.
|
||||
1. Read the file `AGENTS.md` in the project root as initial system prompt to build context and instructions about the project structure, module descriptions, and development commands.
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
name: asm6502-coder
|
||||
description: Writing or understanding 6502/65C02 assembly in 64tass syntax, standalone or embedded in Prog8
|
||||
---
|
||||
|
||||
# 6502 Assembly Coder
|
||||
|
||||
Use 64tass syntax, not ca65 or another assembler. Keep answers concise and
|
||||
practical. This applies to standalone `.asm` files and Prog8 `%asm {{ }}` or
|
||||
`asmsub` blocks.
|
||||
|
||||
## Syntax
|
||||
|
||||
- Labels begin in column 1. Instructions are indented at least four spaces.
|
||||
- Use lowercase opcodes and operands, two spaces between opcode and operand,
|
||||
and two spaces before end-of-line comments.
|
||||
- Hex is `$1234`, binary is `%1010`, decimal is `123`.
|
||||
- Use `.byte`, `.word`, `.dword`, `.fill`, `.text`, and equates such as
|
||||
`name = value`.
|
||||
- Sections use `.section name` and `.send name`.
|
||||
- Procedures use `.proc` and `.pend`; labels beginning with `_` are local to a
|
||||
procedure.
|
||||
- Conditional assembly uses `.if`, `.elsif`, `.else`, and `.endif`.
|
||||
|
||||
## Anonymous Labels
|
||||
|
||||
Anonymous label definitions are always a single `+` or `-` in column 1.
|
||||
References use repeated signs:
|
||||
|
||||
- `+` is the next upcoming `+` definition, `++` the second, `+++` the third.
|
||||
- `-` is the most recent `-` definition, `--` the preceding one, and so on.
|
||||
- Passing a `+` definition resets the forward-reference count.
|
||||
|
||||
```asm
|
||||
- dex
|
||||
bne - ; most recent '-' above
|
||||
ldx #5
|
||||
+ dex ; first upcoming '+'
|
||||
bne + ; this '+'
|
||||
sta $400
|
||||
+ lda #0 ; second '+' definition
|
||||
bne ++ ; next '+' after this one
|
||||
rts
|
||||
+ inc $d020 ; third '+' definition
|
||||
```
|
||||
|
||||
## CPU Differences
|
||||
|
||||
- `cx16` is the only Prog8 target that supports 65C02 instructions such as
|
||||
`stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `wai`, `stp`, and
|
||||
`bit #imm`. C64, C128, and PET32 use the original 6502.
|
||||
- Rockwell instructions (`rmb`, `smb`, `bbr`, `bbs`) are not available.
|
||||
- Use an explicit `a` for accumulator forms such as `rol a`, `lsr a`, `inc a`,
|
||||
and `dec a`.
|
||||
- Branches are relative. Prog8's assembler invocation enables `--long-branch`
|
||||
for out-of-range branches.
|
||||
- `lda`, `ldx`, and `ldy` do not affect carry. `cmp` sets carry for
|
||||
`register >= operand`.
|
||||
- On NMOS 6502, `jmp ($xxff)` wraps the high-byte fetch within the page.
|
||||
- `BRK` skips the byte after its opcode. The byte is commonly a handler
|
||||
signature.
|
||||
- `bit` memory forms set N and V from the operand and Z from `A AND operand`.
|
||||
65C02 `bit #imm` sets only Z.
|
||||
- Decimal mode can persist into NMOS IRQ handlers. Use `cld` at handler entry
|
||||
and explicitly control D before `adc` or `sbc`.
|
||||
|
||||
## Prog8 Integration
|
||||
|
||||
`asmsub` parameter names are documentation only. Use the actual registers or
|
||||
define assembly aliases, and list every modified register in `clobbers`.
|
||||
|
||||
```prog8
|
||||
asmsub increment(uword value @R0) clobbers (A, X) {
|
||||
%asm {{
|
||||
lda cx16.r0L
|
||||
clc
|
||||
adc #1
|
||||
sta cx16.r0L
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
Annotations include `@A`, `@X`, `@Y`, `@AX`, `@AY`, `@R0`-`@R15`, `@FAC1`,
|
||||
`@FAC2`, `@Pc` (carry), and `@Pz` (zero). Unmapped parameters are accessed as
|
||||
`p8v_name`.
|
||||
|
||||
Prog8 symbols use these prefixes: `p8v_` variables and parameters, `p8s_`
|
||||
subroutines, `p8b_` blocks, `p8c_` constants, `p8l_` labels, and `p8t_` struct
|
||||
types. Fully qualified names may look like
|
||||
`p8b_main.p8s_helper.p8v_local`. `%option no_symbol_prefixing` disables the
|
||||
prefixes. Split word arrays use `_lsb` and `_msb` symbols.
|
||||
|
||||
## Zeropage and Memory
|
||||
|
||||
- Never hardcode zeropage addresses. Use `P8ZP_SCRATCH_B1`,
|
||||
`P8ZP_SCRATCH_REG`, `P8ZP_SCRATCH_W1`, `P8ZP_SCRATCH_W2`, and
|
||||
`P8ZP_SCRATCH_PTR`, or the appropriate `cx16.r0`-`cx16.r15` symbols.
|
||||
- Scratch variables are not necessarily consecutive. CX16 virtual registers
|
||||
are consecutive and have `L` and `H` byte names.
|
||||
- Allocate additional temporary storage in BSS.
|
||||
- The hardware stack occupies `$0100-$01ff` and is limited. Do not overflow
|
||||
it.
|
||||
- Prog8 words are little-endian: load the low byte first, then the high byte.
|
||||
|
||||
```asm
|
||||
clc
|
||||
lda word1
|
||||
adc word2
|
||||
sta result
|
||||
lda word1+1
|
||||
adc word2+1
|
||||
sta result+1
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
```asm
|
||||
ldy #index
|
||||
lda (ptr),y
|
||||
|
||||
jsr p8s_main.p8s_helper
|
||||
```
|
||||
|
||||
Avoid self-modifying code unless it is explicitly required. It cannot run
|
||||
from ROM and complicates debugging and tooling. Prefer lookup tables, RAM
|
||||
vectors, or alternative algorithms.
|
||||
|
||||
## Tools
|
||||
|
||||
Prog8 invokes 64tass with `--ascii --case-sensitive --long-branch -Wall` plus
|
||||
the target output option such as `--cbm-prg`. For manual assembly:
|
||||
|
||||
```bash
|
||||
64tass --ascii --case-sensitive --long-branch -Wall --cbm-prg \
|
||||
-o myprogram.prg myprogram.asm
|
||||
```
|
||||
|
||||
For debugging, add `--vice-labels --labels=labels.txt` and/or
|
||||
`--list=listing.txt`.
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
name: m68k-coder
|
||||
description: Writing or understanding Motorola 68000/68020 assembly in vasm mot syntax, standalone or embedded in Prog8
|
||||
---
|
||||
|
||||
# M68K Assembly Coder
|
||||
|
||||
Use Motorola syntax for `vasm`, not Devpac or other assemblers. Keep answers
|
||||
concise and practical.
|
||||
|
||||
## Syntax
|
||||
|
||||
- Labels begin in column 1. Instructions are indented at least four spaces.
|
||||
- Use lowercase opcodes and operands, two spaces between opcode and operand,
|
||||
and two spaces before end-of-line comments.
|
||||
- Use `.b`, `.w`, and `.l` for byte, word, and long operations. Do not rely on
|
||||
default sizes.
|
||||
- Hex is `$1234`, binary is `%1010`, decimal is `123`, and characters use
|
||||
`#'A'`.
|
||||
- Local labels start with `.`. Equates use `name = value`.
|
||||
- Data directives are `dc.b`, `dc.w`, `dc.l`; allocation directives are
|
||||
`ds.b`, `ds.w`, `ds.l`.
|
||||
- Sections use forms such as `SECTION .text,code` and `SECTION .bss,bss`.
|
||||
|
||||
```asm
|
||||
moveq #0,d0
|
||||
move.w value,d0
|
||||
add.l d1,d0
|
||||
.loop
|
||||
dbra d0,.loop
|
||||
```
|
||||
|
||||
## FPU Size Suffixes
|
||||
|
||||
FPU instructions that access memory **must** include a size suffix: `.s`
|
||||
(single, 4 bytes), `.d` (double, 8 bytes), or `.x` (extended, 12 bytes).
|
||||
Without a suffix, vasm defaults to `.x`, reading 12 bytes and corrupting
|
||||
results when the operand is smaller.
|
||||
|
||||
**Use `.s` with memory operands:**
|
||||
```asm
|
||||
fmove.s value(pc),fp0 ; load float from memory
|
||||
fcmp.s other(pc),fp0 ; compare float in memory
|
||||
ftst.s value(pc) ; test float in memory
|
||||
```
|
||||
|
||||
**Omit the suffix for register-to-register operations:**
|
||||
```asm
|
||||
fadd fp1,fp0 ; fp0 = fp0 + fp1
|
||||
fcmp fp1,fp0 ; compare fp0 vs fp1
|
||||
ftst fp0 ; test fp0
|
||||
fintrz fp0,fp0 ; truncate fp0
|
||||
```
|
||||
|
||||
The FPU registers are always 80-bit internally, so no size suffix is needed
|
||||
when both operands are registers.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Registers `D0-D7` and `A0-A7` are 32-bit; `A7` is the stack pointer.
|
||||
- Pointers are 32-bit and must normally be accessed with `.l`.
|
||||
- Memory is big-endian: `$1234` is stored as `$12,$34` and a long's low byte
|
||||
is at offset `+3`.
|
||||
- Word and long accesses must be even-aligned on 68000. 68020 permits
|
||||
misalignment but it is slower.
|
||||
- The 68000 (`amiga500`) has only word multiply/divide and no scaled indexing,
|
||||
bit-field, or 32-bit multiply/divide instructions. The 68020 (`qemu68k`)
|
||||
adds those features and `extb.l`.
|
||||
- On 68000, sign-extend a byte with `ext.w` followed by `ext.l`.
|
||||
- `move.b` and `move.w` to a data register do not clear its upper bits. Like
|
||||
`move.w` only writes the lower 16 bits, `move.b` only writes the lower 8 bits;
|
||||
both leave all other bits of the register untouched. So zero-extending a byte
|
||||
to long requires `moveq #0,d0` first, then `move.b src,d0`, then `move.l d0,dst`
|
||||
(or mask with `and.l #$ff` / `and.l #$ffff`). Relying on `move.b` to clear the
|
||||
upper bits is a common bug that corrupts values.
|
||||
- `movea` does not set condition codes; `move` does. Keep the stack word
|
||||
aligned and push words or longs, never bytes.
|
||||
|
||||
## Registers and Calls
|
||||
|
||||
- Return values use `D0` (primary), `D1` and `D2` (additional), or `A0` for a
|
||||
pointer.
|
||||
- `D0-D1` and `A0-A1` are scratch registers. Preserve registers required by
|
||||
the surrounding calling convention, especially `D2-D7` and `A2-A6`.
|
||||
- `move.l d0,-(sp)` pushes and `move.l (sp)+,d0` pops.
|
||||
|
||||
## Prog8 Integration
|
||||
|
||||
Assembly can appear in `%asm {{ }}` blocks or `asmsub` routines. `asmsub`
|
||||
parameter names are documentation only: use the actual annotated registers.
|
||||
Declare every modified register in `clobbers`.
|
||||
|
||||
```prog8
|
||||
asmsub add100(uword value @D0) -> uword @D0 clobbers (D0) {
|
||||
%asm {{
|
||||
add.w #100,d0
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
Do not use `%option no_sysinit` on `amiga500` or `qemu68k` targets when the goal
|
||||
is to create normal runnable programs. It skips critical startup logic including
|
||||
library initialization and CLI argument handling, causing programs to crash on
|
||||
start.
|
||||
|
||||
Supported annotations include `@D0`-`@D7`, `@A0`-`@A6`, virtual `@R0`-`@R15`,
|
||||
floating-point `@FP0`-`@FP7`, and flags `@Pc` and `@Pz`.
|
||||
|
||||
Prog8 assembly symbols are prefixed:
|
||||
|
||||
| Prefix | Symbol |
|
||||
| --- | --- |
|
||||
| `p8v_` | variable or parameter |
|
||||
| `p8s_` | subroutine |
|
||||
| `p8b_` | block |
|
||||
| `p8c_` | constant or enum member |
|
||||
| `p8l_` | label |
|
||||
| `p8t_` | struct type |
|
||||
|
||||
Use fully qualified names when needed, such as
|
||||
`p8b_main.p8v_value`. `%option no_symbol_prefixing` disables prefixes.
|
||||
|
||||
```asm
|
||||
move.l p8v_pointer,a0
|
||||
move.w p8v_word,d0
|
||||
jsr p8s_main.p8s_helper
|
||||
```
|
||||
|
||||
## Useful Patterns
|
||||
|
||||
```asm
|
||||
lea source,a0
|
||||
lea dest,a1
|
||||
.copy
|
||||
move.b (a0)+,(a1)+
|
||||
bne.s .copy
|
||||
```
|
||||
|
||||
Use `moveq` for small constants, `addq`/`subq` for constants 1 through 8, and
|
||||
`lea` for address arithmetic. Check divisors for zero and division overflow.
|
||||
|
||||
## Tools
|
||||
|
||||
For QEMU 68k, Prog8 invokes `vasmm68k_mot` with `-m68020 -m68881 -Felf
|
||||
-opt-speed -warnunaligned -ldots -spaces`, then links with `vlink`. For raw
|
||||
binary output use `-Fbin` and `-join=0x<addr>`. Add `-L listing.txt` when a
|
||||
listing is needed.
|
||||
|
||||
Prog8 runs `amiga500` programs with `vamos --cpu 68000` and `qemu68k` programs
|
||||
with `qemu-system-m68k -M virt -cpu m68020 -m 1M -kernel program.elf -nographic`.
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
name: prog8-coder
|
||||
description: Writing or understanding Prog8 programs and Prog8 IR
|
||||
---
|
||||
|
||||
# Prog8 Coder
|
||||
|
||||
You are an expert Prog8 assistant. Keep answers concise and practical. This
|
||||
skill covers `.p8` source and `.p8ir` intermediate representation for the
|
||||
6502, M68000, and virtual targets.
|
||||
|
||||
For full language reference, link users to these project documents rather than
|
||||
repeating them here:
|
||||
|
||||
- Program structure and syntax: `docs/source/programming.rst`
|
||||
- Variables, datatypes, arrays, and tags: `docs/source/variables.rst`
|
||||
- Structs and pointers: `docs/source/structpointers.rst`
|
||||
- Compilation and compiler options: `docs/source/compiling.rst`
|
||||
- Standard library: `docs/source/libraries.rst`
|
||||
- Binary libraries and `extsub`: `docs/source/binlibrary.rst`
|
||||
- Target differences: `docs/source/targetsystem.rst`
|
||||
- Known limitations: `docs/source/todo.rst`
|
||||
- Target API signatures: `docs/source/_static/symboldumps/skeletons-<target>.txt`
|
||||
|
||||
## Essentials
|
||||
|
||||
- A program normally has a `main` block with a parameterless `start` entry
|
||||
subroutine. Other blocks and subroutines may be added.
|
||||
- `%import modulename` imports a module without aliases. Use its defined prefix,
|
||||
such as `%import textio` followed by `txt.print(...)`.
|
||||
- `sys` is always available and needs no import.
|
||||
- `.p8ir` is target-independent IR and can be run with
|
||||
`prog8c -vm program.p8ir`.
|
||||
- Use `%zeropage basicsafe` in test programs when a clean return is needed.
|
||||
- Find library routines with `prog8c -libsearch <regex>` or extract them with
|
||||
`prog8c -libdump <dir>`.
|
||||
|
||||
For CX16 emulator programs, use `%encoding iso`, call `txt.iso()` in `start`,
|
||||
and finish with `sys.poweroff_system()`. CBM targets use PETSCII by default;
|
||||
avoid uppercase in test output unless intentional graphics. The virtual target
|
||||
uses ISO when configured with `%encoding iso` and `txt.iso()`.
|
||||
|
||||
Do not use `%option no_sysinit` on `amiga500` or `qemu68k` targets when the goal
|
||||
is to create normal runnable programs. It skips critical startup logic including
|
||||
library initialization (DOS, graphics, intuition, timer) and CLI argument
|
||||
handling, causing programs to crash on start. It is appropriate for library
|
||||
modules, IRQ handlers, boot stubs, or code that deliberately avoids those
|
||||
libraries.
|
||||
|
||||
## Types and Memory
|
||||
|
||||
- Primitive types include `bool`, `byte`, `ubyte`, `word`, `uword`, `long`,
|
||||
`float`, `str`, and `pointer`.
|
||||
- `pointer` is target-sized: 16-bit on 6502 targets, 32-bit on `amiga500`,
|
||||
`qemu68k`, and `virtual`. Use it for portable addresses, not `uword` or
|
||||
`long` chosen by assumption.
|
||||
- M68000 targets are big-endian and use 32-bit pointers. Load the `m68k-coder`
|
||||
skill for assembly details.
|
||||
- `float` requires `%import floats`.
|
||||
- Variables are statically allocated and zero-initialized. There is no normal
|
||||
call stack for locals, so recursion overwrites locals unless an explicit
|
||||
hardware or software stack is used. Iterative rewrites are preferred.
|
||||
- `memory(name, size)` reserves static memory and returns a `pointer`.
|
||||
- `str` and arrays have compile-time byte budgets. On 6502 targets the usual
|
||||
limit is 256 bytes; M68000 targets allow up to 32768 bytes. Use `memory()`
|
||||
for larger data.
|
||||
- Word arrays are split into LSB/MSB arrays by default. Use `@nosplit` when
|
||||
contiguous storage is required.
|
||||
- Variable tags follow the type and array dimensions, before the name:
|
||||
|
||||
```prog8
|
||||
ubyte[8] @shared vera_storage
|
||||
uword @requirezp address
|
||||
```
|
||||
|
||||
Use `@shared` for values accessed externally by assembly. Use `@zp` and
|
||||
`@requirezp` sparingly because zeropage is limited. Never assume virtual
|
||||
registers survive calls. Long operations may clobber `R12-R15`.
|
||||
|
||||
## Syntax Pitfalls
|
||||
|
||||
- Hex uses `$FF`, binary uses `%1010`, and casts use `expression as type`.
|
||||
- There is no automatic type widening: `byte * byte` remains a byte. Cast
|
||||
explicitly when a wider result is needed.
|
||||
- `&` is an untyped address and `&&` is a typed pointer. Complex pointer field
|
||||
assignments may require `ptr^^.field`.
|
||||
- `and`, `or`, `xor`, and `not` are logical operators. Use `&`, `|`, `^`, and
|
||||
`~` for bitwise operations.
|
||||
- `and` and `or` short-circuit, so the right operand may not be evaluated.
|
||||
- There is no `elif`, bare block, or semicolon statement separator. Semicolon
|
||||
starts a comment. Prefer one statement per line and four-space indentation.
|
||||
- `defer` executes registered statements in reverse registration order.
|
||||
- Array indexing starts at zero. Use `len(array)` rather than hardcoded sizes.
|
||||
- There is no function overloading. Call type-specific library routines such as
|
||||
`txt.print_ub` or `txt.print_w`.
|
||||
|
||||
## Control Flow
|
||||
|
||||
Prog8 supports `if`/`else`, `when`, `for`, `while`, `do`/`until`, `repeat`,
|
||||
`unroll`, `break`, `continue`, `goto`, and labels. `unroll` duplicates a
|
||||
constant-count body at compile time and does not support `break` or `continue`.
|
||||
`repeat` is usually more efficient than `for` when no loop variable is needed.
|
||||
Use `if_cs`, `if_cc`, `if_z`, and `if_nz` for direct CPU-flag branches.
|
||||
|
||||
## Subroutines and Assembly
|
||||
|
||||
- Subroutines can return zero, one, or multiple values:
|
||||
`a, b = routine()` or `void routine()`.
|
||||
- Avoid `private` unless requested. Prog8 symbols are public by default.
|
||||
- `asmsub` bodies contain only one `%asm {{ ... }}` node. Parameters are type
|
||||
checked and documented, but assembly must use the mapped registers. Declare
|
||||
every modified hardware register in `clobbers`.
|
||||
- `extsub` maps a signature to a fixed external address and has no body. Use
|
||||
it for ROM, kernel, drivers, or binary-library routines.
|
||||
- For inline assembly, load `asm6502-coder` for 64tass syntax, target-specific
|
||||
instructions, zero-page rules, and symbol references. Load `m68k-coder` for
|
||||
M68000 assembly.
|
||||
|
||||
## Interrupts and Hardware
|
||||
|
||||
- Keep IRQ handlers short. Set a flag and do substantial work in the main loop.
|
||||
- Virtual registers `R0-R15` are not preserved across IRQ handlers. Avoid them
|
||||
or save and restore them with the library routines.
|
||||
- CX16 IRQ handlers that touch VERA registers must save and restore VERA
|
||||
context.
|
||||
- Use only symbolic scratch names such as `P8ZP_SCRATCH_W1` and
|
||||
`cx16.r0`; never hardcode zeropage addresses.
|
||||
|
||||
## Verification
|
||||
|
||||
- Use the `virtual` target for behavioral tests when possible:
|
||||
`prog8c -target virtual -emu program.p8`.
|
||||
- Use `-check` for syntax and semantic checks without output generation.
|
||||
- Use `-noopt` to determine whether a failure is optimizer-related.
|
||||
- For IR execution, use `-vmtrace` when control flow needs inspection.
|
||||
- Do not modify a correct test program to work around a compiler crash. Reduce
|
||||
the case and fix the compiler instead.
|
||||
- When running `gradle` tests, grep for `SUCCESSFUL` to check if all tests
|
||||
passed, and `FAILED` to check for failures. Gradle does not print individual
|
||||
`PASSED` lines by default.
|
||||
@@ -0,0 +1,45 @@
|
||||
# EditorConfig for Prog8 projects
|
||||
# Ensures consistent formatting across editors and team members
|
||||
# See https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
# All files
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Prog8 source files (.p8)
|
||||
[*.p8]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Assembly files (.asm) - 64tass syntax
|
||||
[*.asm]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Kotlin compiler source files
|
||||
[*.kt]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Documentation files
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.rst]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Gradle build files
|
||||
[*.gradle.kts]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Git ignore files
|
||||
[.gitignore]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -10,32 +10,45 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: build and install recent 64tass
|
||||
- name: install 64tass
|
||||
run: |
|
||||
sudo apt-get install -y make build-essential
|
||||
git clone --depth=1 https://github.com/irmen/64tass
|
||||
cd 64tass
|
||||
make -j4
|
||||
sudo make install
|
||||
sudo apt update
|
||||
sudo apt install 64tass
|
||||
64tass --version
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
|
||||
- name: Build and test with Gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Compile
|
||||
run: ./gradlew compileKotlin compileTestKotlin --console=plain
|
||||
|
||||
- name: Test
|
||||
run: ./gradlew test --continue --max-workers=3 -PtestConcurrency=2 -PtestMaxHeap=8g --console=plain
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
./gradlew build shadowJar --no-daemon
|
||||
./gradlew shadowJar --console=plain
|
||||
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
|
||||
|
||||
|
||||
- name: Create compiler shadowJar artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: prog8-compiler-jar-zipped
|
||||
path: |
|
||||
compiler/build/libs/*-all.jar
|
||||
compiler/build/libs/hash.txt
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: test-results
|
||||
path: compiler/build/test-results/test/
|
||||
+7
-1
@@ -3,6 +3,9 @@
|
||||
.idea/developer-tools.xml
|
||||
.idea/usage.statistics.xml
|
||||
.idea/shelf/
|
||||
.junie/
|
||||
.junie/memory/
|
||||
.serena
|
||||
build/
|
||||
dist/
|
||||
output/
|
||||
@@ -12,8 +15,11 @@ out-old/
|
||||
.*cache/
|
||||
*.directory
|
||||
*.prg
|
||||
*.elf
|
||||
*.o
|
||||
*.bin
|
||||
*.p8ir
|
||||
*.bankedcalls
|
||||
*.labels.txt
|
||||
*.vm.txt
|
||||
*.vice-mon-list
|
||||
@@ -22,6 +28,7 @@ parser/**/*.interp
|
||||
parser/**/*.tokens
|
||||
parser/**/*.java
|
||||
compiler/src/prog8/buildversion/*
|
||||
languageServer/src/prog8/buildversion/*
|
||||
*.py[cod]
|
||||
*.egg
|
||||
*.egg-info
|
||||
@@ -35,7 +42,6 @@ parsetab.py
|
||||
.pytest_cache/
|
||||
.attach_pid*
|
||||
compiler/lib/
|
||||
|
||||
.gradle
|
||||
sd*.img
|
||||
*.d64
|
||||
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JunieProjectTechnologies">
|
||||
<option name="explicitlyEnabledTechs">
|
||||
<set>
|
||||
<option value="junie.python" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+12
-2
@@ -2,6 +2,16 @@
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<option name="BUILD_PROCESS_HEAP_SIZE" value="3000" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
<bytecodeTargetLevel target="11">
|
||||
<module name="prog8.codeGenExperimental" target="17" />
|
||||
<module name="prog8.codeGenExperimental.main" target="17" />
|
||||
<module name="prog8.codeGenExperimental.test" target="17" />
|
||||
<module name="prog8.codeGenM68k" target="17" />
|
||||
<module name="prog8.codeGenM68k.main" target="17" />
|
||||
<module name="prog8.codeGenM68k.test" target="17" />
|
||||
<module name="prog8.codeGenNew6502" target="17" />
|
||||
<module name="prog8.codeGenNew6502.main" target="17" />
|
||||
<module name="prog8.codeGenNew6502.test" target="17" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
Generated
+31
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="LOCAL" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/codeCore" />
|
||||
<option value="$PROJECT_DIR$/codeGenCpu6502" />
|
||||
<option value="$PROJECT_DIR$/codeGenIntermediate" />
|
||||
<option value="$PROJECT_DIR$/codeGenM68k" />
|
||||
<option value="$PROJECT_DIR$/codeGenNew6502" />
|
||||
<option value="$PROJECT_DIR$/codeOptimizers" />
|
||||
<option value="$PROJECT_DIR$/compiler" />
|
||||
<option value="$PROJECT_DIR$/compilerAst" />
|
||||
<option value="$PROJECT_DIR$/intermediate" />
|
||||
<option value="$PROJECT_DIR$/languageServer" />
|
||||
<option value="$PROJECT_DIR$/parser" />
|
||||
<option value="$PROJECT_DIR$/simpleAst" />
|
||||
<option value="$PROJECT_DIR$/virtualmachine" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="myGradleHome" value="/usr/share/java/gradle" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="80ae155e-b222-4ca0-92cf-6041a298e78a" />
|
||||
<option name="name" value="80ae155e-b222-4ca0-92cf-6041a298e78a" />
|
||||
<option name="url" value="https://jitpack.io" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+5
-4
@@ -7,13 +7,14 @@
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
<component name="KotlinCommonCompilerArguments">
|
||||
<option name="apiVersion" value="2.2" />
|
||||
<option name="languageVersion" value="2.2" />
|
||||
<option name="apiVersion" value="2.4" />
|
||||
<option name="languageVersion" value="2.4" />
|
||||
</component>
|
||||
<component name="KotlinCompilerSettings">
|
||||
<option name="additionalArguments" value="-Xwhen-guards -jvm-default=no-compatibility" />
|
||||
<option name="additionalArguments" value="-jvm-default=no-compatibility" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.2.0" />
|
||||
<option name="externalSystemId" value="Gradle" />
|
||||
<option name="version" value="2.4.20" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-23
@@ -1,23 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0.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/2.3.0/kotlin-stdlib-jdk7-2.3.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
-18
@@ -1,18 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.13.2">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.2/antlr4-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-13
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="eclipse.lsp4j" type="repository">
|
||||
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.framework.datatest" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-framework-datatest:5.9.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest/5.9.1/kotest-framework-datatest-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest-jvm/5.9.1/kotest-framework-datatest-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.9.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.1/kotest-runner-junit5-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.1/kotest-framework-engine-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.172/classgraph-4.8.172.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.8.0/kotlinx-coroutines-debug-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.9.1/kotest-framework-discovery-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.1/kotest-extensions-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.1/kotest-framework-concurrency-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.2/junit-platform-commons-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.8.2/junit-platform-suite-api-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.8.2/junit-platform-launcher-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
<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.6" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="michael.bull.kotlin.result.jvm" type="repository">
|
||||
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+1
@@ -20,6 +20,7 @@
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11" />
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
|
||||
Generated
-15
@@ -2,24 +2,9 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/beanshell/beanshell.iml" filepath="$PROJECT_DIR$/beanshell/beanshell.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" filepath="$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/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$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
description: Startup workflow to load project context
|
||||
---
|
||||
|
||||
1. Read the file `AGENTS.md` in the project root as initial system prompt to build context and instructions about the project structure, module descriptions, and development commands.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Agent Instructions
|
||||
|
||||
- The file `AGENTS.md` MUST be loaded as the very first step of every session, if it isn't already loaded by the system.
|
||||
- Follow all project-specific guidelines and information provided in `AGENTS.md`.
|
||||
- Ensure all automated tasks and code generations adhere to the language rules and architectural decisions described in `AGENTS.md`.
|
||||
@@ -1,32 +1,358 @@
|
||||
# Agent Instructions for Prog8 Project
|
||||
# CRITICAL RULES - MUST FOLLOW
|
||||
|
||||
## Required Startup Actions
|
||||
## RESPONSES
|
||||
|
||||
When starting a new session with this project, you **MUST** perform these actions before proceeding with any user requests:
|
||||
- Keep responses concise and to the point - unless the user asks otherwise
|
||||
|
||||
1. **Read QWEN.md** - Execute: `read /home/irmen/Projects/prog8/QWEN.md`
|
||||
- This file contains critical project context including:
|
||||
- Project overview (Prog8 compiler for 8-bit retro systems)
|
||||
- Module descriptions for all 20+ modules
|
||||
- Gradle commands and build instructions
|
||||
- Prog8 compiler usage examples
|
||||
- Development shortcuts
|
||||
## PLANNING MODE
|
||||
|
||||
2. **Check startup workflow** - Execute: `read /home/irmen/Projects/prog8/.agent/workflows/startup.md`
|
||||
- This file may contain additional startup instructions
|
||||
- Always ask clarifying questions
|
||||
- Never assume design, tech stack or features
|
||||
- Use deep-dive sub-agents to assist with research
|
||||
- Use deep-dive sub-agents to review the different aspects of your plan before presenting to the user
|
||||
|
||||
## Why This Matters
|
||||
## CHANGE / EDIT MODE
|
||||
|
||||
The prog8 project is a complex compiler with:
|
||||
- Multiple code generation backends (6502, IR, Virtual Machine)
|
||||
- 20+ interconnected modules
|
||||
- Specific build and development workflows
|
||||
- Proprietary language syntax and conventions
|
||||
- Never implement features yourself when possible - use sub-agents!
|
||||
- Identify changes from the plan that can be implemented in parallel, and use sub-agents to implement the features efficiently
|
||||
- When using sub-agents to implement features, act as a coordinator only
|
||||
- Use the best model for the task - premium models for complex tasks (like coding) and mid-tier models for simpler tasks, like documentation
|
||||
- After completing features (large or small), always run commands like lint, type check and next build to check code quality
|
||||
|
||||
Without reading QWEN.md first, you will lack essential context needed to:
|
||||
- Understand module relationships
|
||||
- Use correct gradle commands
|
||||
- Reference the right files
|
||||
- Follow project conventions
|
||||
## Project Overview
|
||||
- This project is a compiler for the Prog8 programming language.
|
||||
- Prog8 is a programming language primarily targeting 8-bit retro systems with the 6502 CPU, such as the Commodore 64, Commodore 128, and Commander X16.
|
||||
- The compiler has a 6502 code generator backend, and an IR code generator.
|
||||
- The IR code is meant to be used in a new machine specific code generator backend (primarily 6502 but maybe 68000 as well later)
|
||||
- The compiler includes a simple 'virtual machine' that can execute the IR code directly via interpretation.
|
||||
- Prog8 source files have .p8 extension – these are *not* LUA or PICO-8 source files in this case!
|
||||
- Prog8 source files are a "module" that can contain one or more "blocks". They can also import other modules, from internal library files or from source files on the filesystem.
|
||||
- The prog8 compiler is written mostly in Kotlin, those files have the .kt extension.
|
||||
- The standard library is mostly written in Prog8 and assembly code, and can be found in the "compiler" module, in the 'res/prog8lib' directory.
|
||||
- **Library Reference**: For a quick overview of all available modules, routines, and their signatures, **consult the skeleton files (symboldumps)** in `docs/source/_static/symboldumps/`. They are named `skeletons-<target>.txt`.
|
||||
- Kotlin version 2.4 is used for the compiler implementation.
|
||||
- Java 17 is used as Java runtime version.
|
||||
- ANTLR4 version 4.13 is used for the parser implementation.
|
||||
- Dependent library versions can be found in 'build.gradle.kts' and in the IntelliJ IDEA configuration files in .idea/libraries
|
||||
- The compiler main entrypoint is in the "compiler" module, in src/prog8/CompilerMain.kt
|
||||
- **Codebase knowledge graph**: For a structured overview of modules, their dependencies, key entities, the end-to-end compilation pipeline, the standard library layout, and the test structure, consult `docs/CODEBASE-KNOWLEDGE-GRAPH.md`. Read it when you need to understand how the compiler fits together before diving into specific files.
|
||||
|
||||
**DO NOT PROCEED** with any user requests until you've read QWEN.md!
|
||||
## Compilation Flow (High-Level)
|
||||
```
|
||||
Source → parseMainModule() → processAst() → optimizeAst() → postprocessAst()
|
||||
→ Simple AST → Code Generator → Assembly → .prg
|
||||
```
|
||||
|
||||
**Key phases in `compiler/src/prog8/compiler/Compiler.kt`:**
|
||||
- `parseMainModule()` - Import modules, parse to Compiler AST
|
||||
- `processAst()` - Semantic analysis, constant folding, type casting, validation
|
||||
- `optimizeAst()` - Dead code elimination, inlining, statement optimization
|
||||
- `postprocessAst()` - Memory layout, final transformations
|
||||
- Code generation – Simple AST → IR or 6502 assembly
|
||||
|
||||
**Understanding this order is important:** For example, `ConstantIdentifierReplacer` runs during `processAst()`, so by the time `AstChecker` runs at the end of that phase, identifier references have already been replaced with their actual values.
|
||||
|
||||
### Target Differences
|
||||
- **CPU instruction set differences**: Only the CommanderX16 target (cx16) can use 65C02 instructions such as STZ. The other targets (C64, C128, PET32) can only use original 6502 instructions.
|
||||
- When writing or understanding 6502/65C02 assembly code, load the `asm6502-coder` skill for 64tass syntax and conventions.
|
||||
- When writing or understanding M68K assembly code (amiga500, qemu68k targets), load the `m68k-coder` skill for vasm mot syntax and conventions.
|
||||
|
||||
|
||||
## DEBUGGING TIP: Use `-noopt` to isolate problems
|
||||
When investigating a possible code generation problem (both IR and 6502), **FIRST try compiling the program with the `-noopt` switch** to disable most of the compiler optimizations.
|
||||
- **Problem gone with `-noopt`**: Issue is in **optimization phases** (`optimizeAst()`, `UnusedCodeRemover`, `Inliner`, etc.)
|
||||
- **Problem persists with `-noopt`**: Issue is in **parsing, semantic analysis, symboltable, or the regular code generation path**.
|
||||
|
||||
This way you can determine if the problem is caused by a faulty optimization step, or just occurs in the regular code generation path.
|
||||
|
||||
## DEBUGGING TIP: Use `-compareir` to see what changed
|
||||
When investigating optimization-related issues or tracking regressions:
|
||||
```bash
|
||||
# Compile without optimizations (baseline)
|
||||
prog8c -target virtual -noopt -out dir program.p8
|
||||
|
||||
# Compile with optimizations and compare
|
||||
prog8c -target virtual -compareir dir/program_noopt.p8ir program.p8
|
||||
```
|
||||
**Output shows:**
|
||||
- Instruction/chunk/register count changes with percentages
|
||||
- First 10 actual instruction differences
|
||||
- Helps identify which optimization transformed the code
|
||||
|
||||
## DEBUGGING TIP: Use `-vmtrace` to trace execution
|
||||
When debugging VM execution or control flow issues:
|
||||
```bash
|
||||
prog8c -target virtual -vm program.p8ir -vmtrace
|
||||
prog8c -target virtual -emu -vmtrace program.p8
|
||||
```
|
||||
**Output format:** `[chunkName:instructionIndex] instruction`
|
||||
- Shows each executed IR instruction with location
|
||||
- Useful for understanding control flow and finding where execution diverges
|
||||
- **Only works on virtual target**
|
||||
|
||||
## Typical debugging workflow:
|
||||
1. **Quick check:** `-check` for syntax errors
|
||||
2. **Isolate:** `-noopt` to determine if problem is optimizer-related
|
||||
3. **Compare:** `-compareir` to see what instructions changed
|
||||
4. **Trace:** `-vmtrace` to watch actual execution flow
|
||||
5. **Deep dive:** `-printast1` / `-printast2` for compiler internals
|
||||
|
||||
## IMPORTANT: SymbolTable cachedFlat cache
|
||||
The `SymbolTable` class has a **cached `flat` property** for fast symbol lookups.
|
||||
|
||||
**CRITICAL:** If you modify the AST **after** SymbolTable construction, you **MUST** call `symbolTable.resetCachedFlat()` or lookups will fail!
|
||||
|
||||
## IMPORTANT: Virtual Target (IR Codegen) Issues
|
||||
For problems that **ONLY occur with the 'virtual' target**, **ONLY modify these modules**:
|
||||
- `codeGenIntermediate` - IR code generator
|
||||
- `intermediate` - IR representation and file I/O
|
||||
- `virtualmachine` - VM that executes IR code
|
||||
|
||||
**DO NOT modify** `compilerAst`, `simpleAst`, `codeGenCpu6502`, etc. The IR codegen has its own separate handling for symbol tables, AST transformations, and unused code removal.
|
||||
|
||||
## IMPORTANT: Compiler Crashes Must Be Fixed in the Compiler
|
||||
A Kotlin/Java crash in the compiler itself (as opposed to a compilation error in the user's program) indicates a bug in the compiler code. **Such crashes should NEVER be worked around by modifying the Prog8 source file that triggered them.**
|
||||
|
||||
Instead, always fix the bug in the compiler's Kotlin code. The user's program is correct - the compiler needs to handle it properly.
|
||||
|
||||
Steps when encountering a compiler crash:
|
||||
1. Create a minimal reproduction case of the Prog8 source that triggers the crash
|
||||
2. Debug the compiler to find the root cause in the Kotlin code
|
||||
3. Fix the compiler bug, not the user program
|
||||
|
||||
## CRITICAL: NO FORMATTING
|
||||
- DO NOT change indentation and formatting of lines that are not being modified. NEVER run formatters (black, ruff, prettier, etc.) after edits,
|
||||
- .editorconfig handles basic formatting (indentation, line endings, whitespace)
|
||||
- Make ONLY the requested changes, touch nothing else
|
||||
- **NO EMOJI in user documentation**. Do not use emoji or decorative unicode symbols in documentation files. Functional unicode symbols are acceptable when they serve a clear purpose (e.g., → for arrows, ± for plus-minus, × for multiplication, ° for degrees). Avoid decorative emoji like ❌ ✅ ⚠️ 🎉 etc.
|
||||
|
||||
### Code Style Guidelines
|
||||
**Minimal comments when making changes**: When modifying existing code, add only essential comments that explain *why* a change was made or document non-obvious behavior. **Do not add verbose comments** that restate what the code does; let the code speak for itself. Existing extensive comments should be preserved, but new changes should have minimal commentary.
|
||||
|
||||
**Kotlin string `$` handling**: The `$` character is special in Kotlin string literals (string interpolation). If you need a literal `$` character, use Kotlin's `$$"..."` interpolation prefix instead of backslash escaping (`\$`) or `${'$'}`. The `$$"..."` syntax is a fairly recent Kotlin feature that avoids nasty escaping. For example:
|
||||
```kotlin
|
||||
val bill = $$"The value is $3.99"
|
||||
```
|
||||
|
||||
|
||||
## Prog8 language information
|
||||
|
||||
When the task involves writing or understanding `.p8` (Prog8 source) or `.p8ir` (Prog8 intermediate representation) files, **load the `prog8-coder` skill** — it contains the full language reference, syntax rules, and standard library guidance.
|
||||
|
||||
## Project Module Descriptions
|
||||
- `compiler` - Main compiler entrypoint (src/prog8/CompilerMain.kt)
|
||||
- `compilerAst` - Complex AST where most optimizations run
|
||||
- `simpleAst` - Simplified AST used by code generator backends
|
||||
- `parser` - ANTLR4 parser implementation
|
||||
- `codeCore` - Core code generation utilities
|
||||
- `codeGenCpu6502` - 6502 assembly code generator backend
|
||||
- `codeGenIntermediate` - Intermediate representation (IR) code generator
|
||||
- `codeGenVirtual` - Virtual machine code generator backend
|
||||
- `codeOptimizers` - Optimization passes
|
||||
- `intermediate` - IR components
|
||||
- `virtualmachine` - VM that executes IR code
|
||||
- `languageServer` - Language Server Protocol implementation
|
||||
- `docs` - Documentation files
|
||||
- `examples` - Example Prog8 programs
|
||||
|
||||
## Key Information
|
||||
- never read the files and directories that are ignored via the .aiignore and .gitignore files (EXCEPT when it is a folder for agent config such as .junie, .opencode, .qwen or .agent)
|
||||
- never perform any git source control write/update/add/commit/branch operations. Read and status operations are allowed.
|
||||
- **git log/history queries can be useful** for understanding when/why a feature was added or tracking down when a bug was introduced, but for locating code use grep_search or glob instead.
|
||||
- Architecture decisions: separation of frontend/parser, IR intermediate representation, multiple backends
|
||||
|
||||
# Dev environment tips
|
||||
|
||||
## Development Workflows
|
||||
|
||||
**1. Testing your own Prog8 programs** (no compiler changes):
|
||||
- No rebuild needed - just run `prog8c` directly
|
||||
- Edit your `.p8` file → compile/run → check stdout output
|
||||
- Example: `prog8c -target virtual -emu myprogram.p8`
|
||||
|
||||
**2. Testing compiler or standard library changes**:
|
||||
- Rebuild required after every change to `.kt`, `.p8`, or `.asm` files in the compiler project
|
||||
- Workflow: edit source → `gradle installdist installshadowdist` → test with `prog8c`
|
||||
- Example: fix bug in `CodeGen6502.kt` → rebuild → `prog8c -target cx16 test.p8`
|
||||
|
||||
## Building and Installing the Compiler
|
||||
- use the system installed gradle command instead of the gradle wrapper.
|
||||
|
||||
### Quick compile check (NO tests)
|
||||
- **After changing compiler Kotlin source (.kt files)**: Use `gradle :compiler:compileKotlin --console=plain` to quickly check for syntax/compile errors
|
||||
- This compiles the compiler but **skips running tests**, this is much faster than `gradle build`
|
||||
- **Note:** This does NOT install the compiler - use `gradle installdist installshadowdist --console=plain` after to actually use your changes
|
||||
|
||||
### When you MUST rebuild AND reinstall the compiler
|
||||
|
||||
**After ANY change to:**
|
||||
1. **Kotlin compiler source code** (`.kt` files in any module, or the `.g4` grammar file)
|
||||
2. **Standard library Prog8 files** (`compiler/res/prog8lib/**/*.p8`)
|
||||
3. **Standard library assembly files** (`compiler/res/prog8lib/**/*.asm`)
|
||||
|
||||
**Run:** `gradle installdist installshadowdist --console=plain`
|
||||
|
||||
**Why:** The standard library files (.p8 and .asm) are embedded into the compiler JAR during the build. Changes to these files are NOT picked up by simply running `prog8c` - you MUST rebuild and reinstall for your changes to take effect.
|
||||
|
||||
**Without this step, your changes will NOT be reflected when running `prog8c`!**
|
||||
- This compiles AND installs, but still skips running tests (faster than `gradle build`)
|
||||
|
||||
### When to run full build with tests
|
||||
- **Before final verification**: Always run `gradle build` to ensure all tests pass
|
||||
- **After major refactoring**: Run `gradle build` to catch regressions
|
||||
- **When debugging test failures**: Use `gradle test --tests "*TestName*"` for specific tests
|
||||
- **When working on a standalone code gen module** (one that reads `.p8ir` files): Run `gradle :<module>:test --console=plain` instead of full `gradle build`. Compiler unit tests are irrelevant for IR-level changes. Do run full `gradle build` before final verification.
|
||||
|
||||
### Build command summary
|
||||
|
||||
| Command | When to use | Time |
|
||||
|---------|-------------|------|
|
||||
| `gradle :compiler:compileKotlin --console=plain` | Quick syntax check (no tests) | ~10-20s |
|
||||
| `gradle installdist installshadowdist --console=plain` | After compiler changes (to use them) | ~10-20s |
|
||||
| `gradle build --console=plain` | Before commits, after major changes | ~45-60s |
|
||||
| `gradle test --tests "*Name*" --console=plain` | Run specific tests | ~5-30s |
|
||||
|
||||
**Note:** Use system `gradle` command, not wrapper. Run `gradle clean` if you suspect stale artifacts.
|
||||
|
||||
## Using the Compiler (prog8c)
|
||||
- the prog8c compiler executable can be found in the compiler/build/install/prog8c/bin folder (this is already added to the shell's path)
|
||||
- **the `-check` switch performs a quick syntax/semantic check only; it will NOT produce any output files (no .prg, .asm, etc.)**. Use it only for fast error checking during development.
|
||||
- **the `-noopt` switch DISABLES all optimizations** - useful for debugging to determine if a problem is caused by the optimizer. **Optimizations are ENABLED by default** (no flag needed).
|
||||
- **prog8c uses single-dash command line options** (e.g., `-target`, `-noopt`, `-check`), NOT double-dash (`--target` is invalid).
|
||||
- **the `-printast1` switch prints out the internal Compiler AST** after parsing and semantic analysis.
|
||||
- **the `-printast2` switch prints out the optimized Simple AST** just before it goes to the code generator. This is useful for debugging optimizer issues.
|
||||
- **the `-out outdir` switch sets an alternative output directory** for compiled files (.prg, .asm, .list, etc.). **By default, output files are written to the same directory as the source file**.
|
||||
- **Other useful flags**: `-quiet` (suppress messages), `-warnimplicitcasts` (warn on implicit type widening), `-daemon` (keep a background compiler process alive — must be passed on every invocation)
|
||||
|
||||
### Compilation Output Files
|
||||
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
|
||||
- `*.asm` - Generated assembly code from the Prog8 source
|
||||
- `*.list` - Generated full assembly listing file from the Prog8 source
|
||||
- `*.p8ir` - Intermediate representation file, can be executed in the Virtual Machine
|
||||
- `*.vice-mon-list` - Vice emulator monitor list file for debugging
|
||||
|
||||
### Execution Examples
|
||||
- `prog8c -target targetname input.p8` - Compile a Prog8 source file "input.p8" for the given target (cx16, c64, pet32, c128, virtual)
|
||||
- `prog8c -target targetname -emu input.p8` - Compile and execute a prog8 file in the emulator for the given target (cx16, c64, pet32, c128, virtual)
|
||||
- `prog8c -vm input.p8ir` - Execute an existing prog8 program, compiled in IR form, in the Virtual Machine
|
||||
|
||||
### Library Tools
|
||||
- **Library search**: `prog8c -libsearch <regex>` — search for a regex pattern in the embedded stdlib files. Extremely useful to quickly find library routines, variables, or signatures (e.g., `prog8c -libsearch "txt\."` lists all textio routines)
|
||||
- **Library dump**: `prog8c -libdump <dir>` — extract all embedded library source files into a directory for direct inspection
|
||||
|
||||
## Testing and Verification
|
||||
|
||||
- Never assume your changes simply work, always test!
|
||||
- If the project does not have any testing tools, scripts, MCP tools, skills, etc. available for testing, ask the user whether testing should be skipped.
|
||||
- in prog8 code being tested, use '@shared' variables to avoid const-folding
|
||||
|
||||
### Automated Tests (gradle)
|
||||
Always use the `--console=plain` flag with these commands to avoid messing up the agent's text interface.
|
||||
- `gradle build --console=plain` - Full build including all tests (about 50s)
|
||||
- `gradle :compiler:test --console=plain` - Run only compiler tests (faster)
|
||||
- `gradle :compiler:test --tests "prog8tests.compiler.TestOptimization" --console=plain` - Run specific test class
|
||||
|
||||
Note: By default, Gradle only shows failed tests. Passed and skipped tests are silent.
|
||||
|
||||
**⚠️ CRITICAL: Test Filtering Patterns - Read This First!**
|
||||
|
||||
Gradle's `--tests` filter has **strict rules** that are easy to get wrong:
|
||||
|
||||
| Pattern | Example | Works? |
|
||||
|---------|---------|---------------|
|
||||
| **Full class name** | `--tests "prog8tests.compiler.TestOptimization"` | **USE THIS** |
|
||||
| Wildcard at END | `--tests "prog8tests.compiler.Test*"` | Yes |
|
||||
| Wildcard on package | `--tests "prog8tests.compiler.*"` | Yes |
|
||||
| **Wildcard at START** | `--tests "*TestOptimization"` | FAILS |
|
||||
| Test description | `--tests "*Optimization*inline*"` | FAILS |
|
||||
|
||||
**Why the restrictions?** Gradle's `--tests` filter matches **fully qualified class names only**. Wildcards work as **suffixes** (e.g., `Test*`) but NOT as prefixes (e.g., `*Test`). KoTest test names like `"inline multi-value void"` are _descriptions_, not method names, so you cannot filter by them.
|
||||
|
||||
**To find failing tests after a run:**
|
||||
```bash
|
||||
# Check XML results (reliable)
|
||||
grep "<failure" compiler/build/test-results/test/*.xml
|
||||
|
||||
# Or check HTML report (most reliable)
|
||||
cat compiler/build/reports/tests/test/index.html
|
||||
|
||||
# For test compilation errors
|
||||
gradle :compiler:compileTestKotlin --info 2>&1 | grep "^e:"
|
||||
```
|
||||
|
||||
**Additional notes:**
|
||||
- Unit tests use KoTest (FunSpec style)
|
||||
- Tests are in the "compiler" module's `test` directory (and some other modules)
|
||||
- Test config is in root `build.gradle.kts`; tests run in parallel
|
||||
- **When writing test programs**, add at the top: `%zeropage basicsafe` and `%option no_sysinit` - the latter ONLY when NOT writing for the amiga500 or qemu68k targets
|
||||
- When a test fails, the output shows "There were failing tests. See the report at:" - **read that HTML report**
|
||||
|
||||
### Compiler Unit Test Snippets
|
||||
Prog8 code snippets embedded in the compiler's own Kotlin unit tests (e.g., in `TestAstChecks.kt`, `TestOptimization.kt`) follow different rules from standalone programs:
|
||||
- **Keep self-contained** — avoid `%import` directives (test setup may not have library search path configured)
|
||||
- **Use `cx16` target** (not `virtual`, unless specifically testing the VM)
|
||||
- **Set `writeAssembly=false`** if the test only checks generated AST (much faster)
|
||||
- **Set `optimize=false`** by default for these snippets
|
||||
- **Do NOT use** the `%encoding iso` / `txt.iso()` / `sys.poweroff_system()` pattern — that's only for real CX16 emulator runs
|
||||
|
||||
### Testing Assembly Output
|
||||
When testing generated assembly in Kotlin tests:
|
||||
- **NEVER dump raw assembly in string assertions** (e.g., `asm shouldContain "jsr label"`). On failure, this dumps the ENTIRE assembly listing into test logs.
|
||||
- **Instead, parse into lines and check specific properties:**
|
||||
```kotlin
|
||||
val lines = asm.lines().map { it.trim() }
|
||||
lines.any { it == "lda #42" } shouldBe true
|
||||
lines.count { it.startsWith("jsr") } shouldBe 0
|
||||
```
|
||||
- Prefer `lines.any { ... }` for existence checks and `lines.count { ... } shouldBe 0` for absence checks.
|
||||
|
||||
### Manual Verification & Emulators
|
||||
|
||||
**Testing tip**: When writing and testing Prog8 programs, **use the `virtual` target** (e.g., `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir`). This is the preferred way to test because the virtual target can easily write output to stdout, making it simple to verify program behavior and check results.
|
||||
|
||||
**CX16 output verification**: Use `x16emu -echo iso -run -prg input.prg` to echo screen output to stdout **and auto-start the program**. The `-run` flag is **critical**: without it, the program loads but doesn't execute, so you'll see no output. Pipe through `strings` or `grep` to filter: `x16emu -echo iso -run -prg input.prg 2>&1 | grep -E "(PASS|FAIL)"`.
|
||||
**IMPORTANT**: Always add `%encoding iso` at the top of your source file and call `txt.iso()` in `start()`. This prevents PETSCII→ISO charset translation errors that garble uppercase/special characters and make output unreadable:
|
||||
```prog8
|
||||
%encoding iso
|
||||
%import textio
|
||||
main { sub start() { txt.iso(); txt.print("PASS\n") } }
|
||||
```
|
||||
**IMPORTANT: Always use `sys.poweroff_system()` to exit the CX16 emulator cleanly!** Add `sys.poweroff_system()` at the end of your main program block - this exits x16emu automatically in most cases.
|
||||
**Note:** The `sys` module is always available, there is no need to import it ever.
|
||||
|
||||
**Commodore 64 (x64sc)**: `x64sc input.prg` - run an existing compiled program in the Commodore-64 emulator. Ignore any errors and warnings, because the emulator doesn't produce any output on STDOUT.
|
||||
|
||||
### actual 6502 CPU simulation tests
|
||||
For high-fidelity functional verification of generated 6502 code without a full emulator, you can use the `ksim65` simulator in your unit tests. This allows executing the compiled machine code and asserting on memory or register states.
|
||||
- Use the `simulate()` extension function on a `CompilationResult`.
|
||||
- See `prog8tests.codegeneration.TestExecution6502` for examples.
|
||||
- Both 6502 and 65C02 CPUs can be simulated.
|
||||
- The simulator supports capturing serial output and handling hardware reset/poweroff signals.
|
||||
- There are various assertion helper methods to test on the state of CPU registers, memory contents, etc.
|
||||
- Using the simulator is much faster and way more controllable than using a full emulator to run the code.
|
||||
- The simulator DOES NOT represent an actual real world machine and has VERY LIMITED hardware devices so it can generally not be used to run full example prorams on.
|
||||
|
||||
## Git Operations for File Moves/Deletes
|
||||
|
||||
**When renaming or moving git-tracked files, ALWAYS use `git mv`:**
|
||||
```bash
|
||||
# CORRECT - preserves git history
|
||||
git mv old/path/File.kt new/path/File.kt
|
||||
|
||||
# WRONG - git sees this as delete + add (loses history)
|
||||
mv old/path/File.kt new/path/File.kt
|
||||
```
|
||||
|
||||
**When deleting git-tracked files, ALWAYS use `git rm`:**
|
||||
```bash
|
||||
# CORRECT - properly stages the deletion
|
||||
git rm path/to/File.kt
|
||||
|
||||
# WRONG - git sees this as unstaged deletion
|
||||
rm path/to/File.kt
|
||||
```
|
||||
|
||||
**Why this matters:** `git mv` and `git rm` properly stage the changes and preserve file history. Plain `mv`/`rm` requires git to detect renames heuristically, which may not always work correctly.
|
||||
|
||||
## TODO Items
|
||||
The file `docs/source/todo.rst` contains a comprehensive list of things that still have to be fixed, implemented, or optimized. **Use this to understand what features are NOT yet available** in the compiler or Prog8 language: if a user asks for something that's on the TODO list, you'll know it's not implemented yet and can explain the limitation.
|
||||
@@ -1,12 +1,20 @@
|
||||
# super simple Makefile to lauch the main gradle targets to build and/or test the prog8 compiler
|
||||
# super simple Makefile to launch the main gradle targets to build and/or test the prog8 compiler
|
||||
|
||||
.PHONY: all test
|
||||
|
||||
# Use system gradle if available, otherwise fall back to gradlew wrapper
|
||||
# Windows uses 'where' and gradlew.bat, Unix uses 'command -v' and ./gradlew
|
||||
ifeq ($(OS),Windows_NT)
|
||||
GRADLE = $(shell where gradle >nul 2>&1 && echo gradle || echo gradlew.bat)
|
||||
else
|
||||
GRADLE = $(shell command -v gradle >/dev/null 2>&1 && echo gradle || echo ./gradlew)
|
||||
endif
|
||||
|
||||
all:
|
||||
gradle installdist installshadowdist
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
|
||||
$(GRADLE) installdist installshadowdist
|
||||
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
|
||||
|
||||
test:
|
||||
gradle build
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
|
||||
$(GRADLE) build
|
||||
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
# Qwen Assistant Memory for prog8 Project
|
||||
|
||||
This file contains project-specific information that helps the Qwen assistant understand and work effectively with the prog8 codebase.
|
||||
|
||||
## Project Overview
|
||||
- This project is a compiler for the Prog8 programming language.
|
||||
- Prog8 is a programming language primarily targeting 8-bit retro systems with the 6502 CPU, such as the Commodore 64, Commodore 128, and Commander X16.
|
||||
- The compiler has a 6502 code generator backend, and an IR code generator.
|
||||
- The IR code is meant to be used in a new machine specific code generator backend (primarily 6502 but maybe 68000 as well later)
|
||||
- The compiler includes a simple 'virtual machine' that can execute the IR code directly via interpretation.
|
||||
- Prog8 source files have .p8 extension
|
||||
- Prog8 source files are a "module" that can contain 1 ore more "blocks". They can also import other modules, from internal library files or from source files on the filesystem.
|
||||
- The prog8 compiler is written mostly in Kotlin, those files have the .kt extension.
|
||||
|
||||
## Prog8 language feature hints
|
||||
- an overview of the language features can be found in the documentation file docs/source/introduction.rst
|
||||
- the syntax and grammar is specified in an ANTLR4 grammar file found in the parser directory
|
||||
- the syntax for boolean logical operators is 'and', 'or', 'xor', 'not'. Bitwise operators are '&', '|', '^', '~', and '<<','>>' for bit shifting left and right respectively. All operators are documents in docs/source/programming.rst
|
||||
- note that Prog8 does have a 4-byte signed long type and 5-byte "Microsoft" floating point support
|
||||
- module imports are done using "%import modulename"
|
||||
|
||||
## Module Descriptions
|
||||
- `beanshell` - EXPERIMENTAL/UNFINISHED Contains BeanShell integration for scripting capabilities within the compiler
|
||||
- `benchmark-c` - C implementations for performance comparison and benchmarking
|
||||
- `benchmark-program` - Benchmark programs to test compiler output performance
|
||||
- `codeCore` - Core code generation utilities and shared components
|
||||
- `codeGenCpu6502` - 6502 CPU-specific code generator backend (generates assembly for 6502-based systems)
|
||||
- `codeGenExperimental` - Experimental code generators that are under development
|
||||
- `codeGenIntermediate` - Intermediate representation (IR) code generator
|
||||
- `codeGenVirtual` - Virtual machine code generator backend
|
||||
- `codeOptimizers` - Optimization passes for improving generated code efficiency
|
||||
- `compiler` - Main compiler executable and top-level compiler logic
|
||||
- `compilerAst` - complicated Abstract Syntax Tree (AST) where most optimizations also run on, is later transformed into the simpleAst
|
||||
- `docs` - Documentation files for the Prog8 language and compiler
|
||||
- `examples` - Example Prog8 programs demonstrating language features
|
||||
- `intermediate` - Components related to the intermediate representation (IR) of Prog8 programs
|
||||
- `languageServer` - Language Server Protocol implementation for IDE integration
|
||||
- `parser` - ANTLR4 Parser implementation for the Prog8 language syntax
|
||||
- `scripts` - Utility scripts for development, testing, and deployment
|
||||
- `simpleAst` - Simplified AST that is used to run the code generator backends from
|
||||
- `syntax-files` - Syntax definition files for editors and IDEs
|
||||
- `virtualmachine` - Virtual machine implementation that can execute IR code
|
||||
|
||||
## Key Information
|
||||
- never read the files and directories that are ignored via the .aiignore and .gitignore files
|
||||
- Current development focus areas: compiler optimizations, new language features, backend improvements
|
||||
- Important project conventions: Kotlin for compiler implementation, modular architecture, IR-based compilation
|
||||
- Architecture decisions: separation of frontend/parser, IR intermediate representation, multiple backends
|
||||
|
||||
### Gradle Commands
|
||||
- use the system installed gradle command instead of the gradle wrapper.
|
||||
- `gradle build` - Full build of the compiler including running the full test suite
|
||||
- `gradle clean` - Clean build artifacts
|
||||
- `gradle compileKotlin` - Compile only the Kotlin source code
|
||||
- `gradle installdist` - Create the compiler JARs and executable file
|
||||
- `gradle installshadowdist` - Create the single "fat" compiler JAR and executable file
|
||||
|
||||
### Development Shortcuts
|
||||
- `gradle test --tests "*TestName*"` - Run specific test classes
|
||||
- `gradle build --refresh-dependencies` - Refresh dependencies during development
|
||||
|
||||
### Prog8 Compiler Usage
|
||||
- the prog8c compiler executable can be found in the compiler/build/install/prog8c/bin folder (this is already added to the shell's path)
|
||||
- the `-emu` switch can be used to directly execute the resulting program in an emulator after successful compilation.
|
||||
- `prog8c -target cx16 input.p8` - Compile a Prog8 source file "input.p8" for the CommanderX16 target
|
||||
- `prog8c -target cx16 -check input.p8` - Quickly check a Prog8 source file "input.p8" for compiler errors, no output binary is produced
|
||||
- `prog8c -target cx16 -emu input.p8` - Compile and execute a prog8 file in the CommanderX16 emulator
|
||||
- `prog8c -target c64 -emu input.p8` - Compile and execute a prog8 file in the Commodore-64 emulator
|
||||
- `prog8c -target virtual input.p8` - Compile a prog8 file for the IR/Virtual machine target
|
||||
- `prog8c -target virtual -emu input.p8` - Compile and directly execute a prog8 file in the Virtual Machine
|
||||
- `prog8c -vm input.p8ir` - Execute an existing prog8 program, compiled in IR form, in the Virtual Machine
|
||||
- `x16emu -scale 2 -prg input.prg` - Just load an existing compiled program in the CommanderX16 emulator
|
||||
- `x64sc input.prg` - run an existing compiled program in the Commodore-64 emulator (Note: You can safely ignore errors and warnings from this emulator)
|
||||
|
||||
### Compilation Output Files
|
||||
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
|
||||
- `*.asm` - Generated assembly code from the Prog8 source
|
||||
- `*.list` - Generated full assembly listing file from the Prog8 source
|
||||
- `*.p8ir` - Intermediate representation file, can be executed in the Virtual Machine
|
||||
- `*.vice-mon-list` - Vice emulator monitor list file for debugging
|
||||
|
||||
## Working Notes
|
||||
[Any ongoing notes about the project that would be helpful for the assistant to remember]
|
||||
|
||||
## TODO Items
|
||||
The file docs/source/todo.rst contains a comprehensive list of things that still have to be fixed, implemented, or optimized.
|
||||
@@ -4,21 +4,24 @@ PayPal: []
|
||||
|
||||
[](https://prog8.readthedocs.io/)
|
||||
|
||||
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
||||
============================================================================
|
||||
Prog8 - Structured Programming Language for 6502/65c02 (8-bit) and 68000 (32-bit) microprocessors
|
||||
=================================================================================================
|
||||
|
||||
*Written by Irmen de Jong (irmen@razorvine.net)*
|
||||
|
||||
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
|
||||
as used in many home computers from that era. It is a medium to low level programming language,
|
||||
as used in many home computers from that era. It also supports the 32-bit Motorola 68000 CPU, for which the
|
||||
Commodore Amiga is the intended machine. 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).
|
||||
|
||||
**Want to buy me a coffee or a pizza perhaps?**
|
||||
|
||||
This project was created over the last couple of years by dedicating thousands of hours of my free time to it, to make it the best I possibly can.
|
||||
This project was created over the last couple of years by dedicating a huge amount of my free time to it, to make it the best I possibly can.
|
||||
If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza,
|
||||
you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen) or [PayPal](https://paypal.me/irmendejong)
|
||||
|
||||
[](https://jb.gg/OpenSource)
|
||||
I am using JetBrains products to develop the Prog8 compiler.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
@@ -63,8 +66,10 @@ What does Prog8 provide?
|
||||
- programs run very fast because it's compiled to native machine code
|
||||
- compiled code is very small; much smaller than equivalent C code compiled with CC65, and usually runs faster as well
|
||||
- modularity, symbol scoping, subroutines. No need for forward declarations.
|
||||
- Can make vars and routines private, as a simple way to restrict access and hide implementation details
|
||||
- various data types other than just bytes (16-bit words, long integers, floats, strings)
|
||||
- Structs and typed pointers
|
||||
- 2D arrays (`matrix[row][col]` syntax)
|
||||
- floating point math is supported on certain targets
|
||||
- access to most Kernal ROM routines as external subroutine definitions you can call normally
|
||||
- tight control over Zeropage usage
|
||||
@@ -90,7 +95,7 @@ What does Prog8 provide?
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
- use a modern PC to do the work on, use nice editors and enjoy quick compilation times
|
||||
- can automatically run the program in the Vice emulator after succesful compilation
|
||||
- can automatically run the program in the Vice emulator after successful compilation
|
||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||
|
||||
@@ -100,17 +105,22 @@ What does Prog8 provide?
|
||||
- "c64": Commodore-64 (6502 like CPU)
|
||||
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
|
||||
- "pet32": Commodore PET (limited support)
|
||||
- "amiga500": Commodore Amiga 500 (16/32-bit 68000 CPU; experimental)
|
||||
- "amiga1200": Commodore Amiga 1200 (32-bit 68020 CPU with optional FPU; experimental)
|
||||
- "qemu68k": Qemu M68k simulator (experimental)
|
||||
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64 OS, Foenix F256, ...
|
||||
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
|
||||
|
||||
The m68k targets (amiga500, amiga1200, qemu68k) and the optional `-newcodegen` 6502 code generator are experimental and not yet suitable for production code.
|
||||
|
||||
|
||||
|
||||
Additional required tools
|
||||
-------------------------
|
||||
|
||||
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
||||
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
||||
For other platforms it is very easy to compile it yourself (make ; make install).
|
||||
If you're on Windows, download a recent .exe from the official repository's file section <https://sourceforge.net/projects/tass64/files/binaries/>.
|
||||
For other systems (Mac, Linux) it is very easy to get it via your package manager or to compile it yourself.
|
||||
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run a prepackaged version of the compiler.
|
||||
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
|
||||
@@ -205,3 +215,10 @@ Here is a performance comparison with various C compilers for the 6502/C64 so yo
|
||||
an idea of how Prog8 stacks up.
|
||||
|
||||
[comparison](benchmark-c/benchmark.md)
|
||||
|
||||
|
||||
Agent context instructions
|
||||
--------------------------
|
||||
|
||||
[Agent context instructions](AGENTS.md)
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/lib/bsh-3.0.0-SNAPSHOT.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,65 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
id("application")
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
val serverMainClassName = "prog8lsp.MainKt"
|
||||
val applicationName = "prog8-beanshell"
|
||||
|
||||
application {
|
||||
mainClass.set(serverMainClassName)
|
||||
description = "Code completions, diagnostics and more for Prog8"
|
||||
// applicationDefaultJvmArgs = listOf("-DkotlinLanguageServer.version=$version")
|
||||
applicationDistribution.into("bin") {
|
||||
filePermissions {
|
||||
user {
|
||||
read=true
|
||||
execute=true
|
||||
write=true
|
||||
}
|
||||
other.execute = true
|
||||
group.execute = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("lib/bsh-3.0.0-SNAPSHOT.jar"))
|
||||
}
|
||||
|
||||
configurations.forEach { config ->
|
||||
config.resolutionStrategy {
|
||||
preferProjectModules()
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
java.srcDir("src")
|
||||
resources.srcDir("resources")
|
||||
}
|
||||
|
||||
tasks.startScripts {
|
||||
applicationName = "prog8-beanshell"
|
||||
}
|
||||
|
||||
tasks.register<Exec>("fixFilePermissions") {
|
||||
// When running on macOS or Linux the start script
|
||||
// needs executable permissions to run.
|
||||
|
||||
onlyIf { !System.getProperty("os.name").lowercase().contains("windows") }
|
||||
commandLine("chmod", "+x", "${tasks.installDist.get().destinationDir}/bin/prog8-beanshell")
|
||||
}
|
||||
|
||||
tasks.installDist {
|
||||
finalizedBy("fixFilePermissions")
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
finalizedBy("installDist")
|
||||
}
|
||||
Binary file not shown.
@@ -1,48 +0,0 @@
|
||||
package prog8beanshell
|
||||
|
||||
import java.io.FilterReader
|
||||
import java.io.Reader
|
||||
|
||||
|
||||
class CommandLineReader(val input: Reader): FilterReader(input) {
|
||||
private val normal = 0
|
||||
private val lastCharNL = 1
|
||||
private val sentSemi = 2
|
||||
private var state = lastCharNL
|
||||
|
||||
override fun read(): Int {
|
||||
if (state == sentSemi) {
|
||||
this.state = lastCharNL
|
||||
return 10
|
||||
} else {
|
||||
var b = input.read()
|
||||
while(b==13) b = input.read()
|
||||
|
||||
if (b == 10) {
|
||||
if (this.state == lastCharNL) {
|
||||
b = 59
|
||||
this.state = sentSemi
|
||||
} else {
|
||||
this.state = lastCharNL
|
||||
}
|
||||
} else {
|
||||
this.state = normal
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun read(buff: CharArray, off: Int, len: Int): Int {
|
||||
val b = read()
|
||||
if (b == -1) {
|
||||
return -1
|
||||
} else {
|
||||
buff[off] = b.toChar()
|
||||
return 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package prog8beanshell
|
||||
|
||||
import bsh.FileReader
|
||||
import bsh.Interpreter
|
||||
|
||||
|
||||
class BeanshellInterpreter {
|
||||
|
||||
fun run(symbols: Map<String, Any>) {
|
||||
val interpreter = Interpreter(CommandLineReader(FileReader(System.`in`)), System.out, System.err, true)
|
||||
interpreter.setExitOnEOF(false)
|
||||
symbols.forEach { (name, value) -> interpreter.set(name, value) }
|
||||
interpreter.run()
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val i = BeanshellInterpreter()
|
||||
i.run(mapOf(
|
||||
"env" to System.getenv(),
|
||||
"args" to args
|
||||
))
|
||||
}
|
||||
+13
-12
@@ -41,7 +41,7 @@ rotate3d {
|
||||
for i in 0 to len(matrix_math.xcoor)-1 {
|
||||
rz = matrix_math.rotatedz[i]
|
||||
if rz >= 10 {
|
||||
persp = 600 + rz/64
|
||||
persp = 600 + (rz>>5)
|
||||
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
|
||||
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
|
||||
;; txt.setcc(sx as ubyte, sy as ubyte, 46, 7)
|
||||
@@ -51,7 +51,7 @@ rotate3d {
|
||||
for i in 0 to len(matrix_math.xcoor)-1 {
|
||||
rz = matrix_math.rotatedz[i]
|
||||
if rz < 10 {
|
||||
persp = 600 + rz/64
|
||||
persp = 600 + (rz>>5)
|
||||
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
|
||||
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
|
||||
;; txt.setcc(sx as ubyte, sy as ubyte, 81, 7)
|
||||
@@ -84,18 +84,19 @@ matrix_math {
|
||||
word wcosc = math.cos8(az)
|
||||
word wsinc = math.sin8(az)
|
||||
|
||||
word wcosa_sinb = wcosa*wsinb / 128
|
||||
word wsina_sinb = wsina*wsinb / 128
|
||||
; instead of (slow) /128, we simply shift 7 bits (and take the roundoff error)
|
||||
word wcosa_sinb = wcosa*wsinb >> 7
|
||||
word wsina_sinb = wsina*wsinb >> 7
|
||||
|
||||
word Axx = wcosa*wcosb / 128
|
||||
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) / 128
|
||||
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) / 128
|
||||
word Ayx = wsina*wcosb / 128
|
||||
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) / 128
|
||||
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) / 128
|
||||
word Axx = wcosa*wcosb >> 7
|
||||
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) >> 7
|
||||
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) >> 7
|
||||
word Ayx = wsina*wcosb >> 7
|
||||
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) >> 7
|
||||
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) >> 7
|
||||
word Azx = -wsinb
|
||||
word Azy = wcosb*wsinc / 128
|
||||
word Azz = wcosb*wcosc / 128
|
||||
word Azy = wcosb*wsinc >> 7
|
||||
word Azz = wcosb*wcosc >> 7
|
||||
|
||||
ubyte @zp i
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
|
||||
@@ -113,14 +113,14 @@ btree {
|
||||
sub count_node(^^Node r) {
|
||||
count++
|
||||
if r.left!=0 {
|
||||
sys.pushw(r)
|
||||
pushw(r)
|
||||
count_node(r.left)
|
||||
r = sys.popw()
|
||||
r = popw()
|
||||
}
|
||||
if r.right!=0 {
|
||||
sys.pushw(r)
|
||||
pushw(r)
|
||||
count_node(r.right)
|
||||
r = sys.popw()
|
||||
r = popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,15 +179,15 @@ btree {
|
||||
|
||||
sub process_tree(^^Node r) {
|
||||
if r.left!=0 {
|
||||
sys.pushw(r)
|
||||
pushw(r)
|
||||
process_tree(r.left)
|
||||
r = sys.popw()
|
||||
r = popw()
|
||||
}
|
||||
cx16.r0 = r.value
|
||||
if r.right!=0 {
|
||||
sys.pushw(r)
|
||||
pushw(r)
|
||||
process_tree(r.right)
|
||||
r = sys.popw()
|
||||
r = popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,18 +200,18 @@ btree {
|
||||
sub process_tree(^^Node r, ubyte depth) {
|
||||
cx16.r0 = r.value
|
||||
if r.left!=0 {
|
||||
sys.pushw(r)
|
||||
sys.push(depth)
|
||||
pushw(r)
|
||||
push(depth)
|
||||
process_tree(r.left, depth+1)
|
||||
depth = sys.pop()
|
||||
r = sys.popw()
|
||||
depth = pop()
|
||||
r = popw()
|
||||
}
|
||||
if r.right!=0 {
|
||||
sys.pushw(r)
|
||||
sys.push(depth)
|
||||
pushw(r)
|
||||
push(depth)
|
||||
process_tree(r.right, depth+1)
|
||||
depth = sys.pop()
|
||||
r = sys.popw()
|
||||
depth = pop()
|
||||
r = popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,10 +220,10 @@ btree {
|
||||
|
||||
arena {
|
||||
; extremely trivial arena allocator (that never frees)
|
||||
uword buffer = memory("arena", 2000, 0)
|
||||
uword next = buffer
|
||||
pointer buffer = memory("arena", 2000, 0)
|
||||
pointer next = buffer
|
||||
|
||||
sub alloc(ubyte size) -> uword {
|
||||
sub alloc(ubyte size) -> pointer {
|
||||
defer next += size
|
||||
return next
|
||||
}
|
||||
|
||||
@@ -61,8 +61,7 @@ life {
|
||||
math.rndseed(12345,9999)
|
||||
uword offset = STRIDE+1
|
||||
ubyte x
|
||||
ubyte y
|
||||
for y in 0 to HEIGHT-1 {
|
||||
repeat HEIGHT {
|
||||
for x in 0 to WIDTH-1 {
|
||||
active_world[offset+x] = math.rnd() & 1
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ queens {
|
||||
if could_place(row, col) {
|
||||
board[row] = col
|
||||
; we need to save the local variables row and col.
|
||||
sys.push(row)
|
||||
sys.push(col)
|
||||
push(row)
|
||||
push(col)
|
||||
continue_running = place_queen(row + 1)
|
||||
; restore the local variables after the recursive call.
|
||||
col = sys.pop()
|
||||
row = sys.pop()
|
||||
col = pop()
|
||||
row = pop()
|
||||
board[row] = 0
|
||||
|
||||
if not continue_running
|
||||
|
||||
+78
-7
@@ -1,11 +1,9 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
|
||||
import org.jetbrains.kotlin.gradle.internal.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "2.3.0"
|
||||
kotlin("jvm") version "2.4.20"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -14,12 +12,13 @@ allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs = listOf()
|
||||
jvmTarget = JvmTarget.JVM_11
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
|
||||
// languageVersion.set(KotlinVersion.KOTLIN_2_3)
|
||||
}
|
||||
@@ -32,7 +31,79 @@ allprojects {
|
||||
}
|
||||
|
||||
java {
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Common Configuration for All Subprojects
|
||||
// ============================================================================
|
||||
// This avoids duplication in each module's build.gradle.kts
|
||||
// ============================================================================
|
||||
|
||||
subprojects {
|
||||
// Common dependency exclusions for all subprojects
|
||||
// Note: antlr4 exclusion is done per-module since parser module needs it
|
||||
configurations.all {
|
||||
exclude(group = "com.ibm.icu", module = "icu4j")
|
||||
}
|
||||
|
||||
// Common test dependencies via Kotest BOM (Bill of Materials)
|
||||
// This manages all Kotest module versions centrally
|
||||
dependencies {
|
||||
testImplementation(platform("io.kotest:kotest-bom:6.2.4"))
|
||||
// implementation("org.codeberg.irmen:ksim65:v2.2")
|
||||
}
|
||||
|
||||
// Common sourceSets configuration
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDir("${project.projectDir}/src")
|
||||
resources.srcDir("${project.projectDir}/res")
|
||||
}
|
||||
test {
|
||||
java.srcDir("${project.projectDir}/test")
|
||||
}
|
||||
}
|
||||
|
||||
// Common test configuration
|
||||
tasks.withType<Test>().configureEach {
|
||||
// Enable JUnit 5 (required for Kotest)
|
||||
useJUnitPlatform()
|
||||
|
||||
val testConcurrency = project.findProperty("testConcurrency")?.toString()?.toIntOrNull()
|
||||
?: (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
|
||||
project.findProperty("testMaxHeap")?.toString()?.let { maxHeapSize = it }
|
||||
|
||||
// Enable concurrent test execution for Kotest
|
||||
// Allow CI to override parallelism for memory-constrained runners
|
||||
jvmArgs("-Dkotest.framework.parallelism=$testConcurrency")
|
||||
|
||||
// Disable Kotest autoscan warning
|
||||
jvmArgs("-Dkotest.framework.classpath.scanning.autoscan.disable=true")
|
||||
|
||||
// Enable Gradle's parallel test execution (runs multiple test classes concurrently)
|
||||
// Use 50% of available processors to avoid over-subscription
|
||||
maxParallelForks = testConcurrency
|
||||
|
||||
// Allow filtering tests via -PtestFilter="*TestName*" from command line
|
||||
// Example: gradle test -PtestFilter="*TestLookup*"
|
||||
val testFilter = project.findProperty("testFilter")?.toString()
|
||||
if(testFilter != null) {
|
||||
filter {
|
||||
includeTestsMatching(testFilter)
|
||||
}
|
||||
}
|
||||
|
||||
// Show test results - only show failures to reduce noise
|
||||
testLogging {
|
||||
events("failed")
|
||||
// Show full exception details for failures
|
||||
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,11 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
`java-test-fixtures`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// should have no dependencies to other modules
|
||||
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
|
||||
}
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir("${project.projectDir}/src")
|
||||
}
|
||||
resources {
|
||||
srcDir("${project.projectDir}/res")
|
||||
}
|
||||
}
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
}
|
||||
|
||||
// note: there are no unit tests in this module!
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -29,4 +29,23 @@ fun Path.sanitize(): Path {
|
||||
} catch (iox: IOException) {
|
||||
throw FileSystemException(this.toFile()).also { it.initCause(iox) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object SymbolNames {
|
||||
/**
|
||||
* Strips Prog8 symbol prefixes (p8b_, p8t_, p8s_, p8v_, p8l_, p8c_) from a scoped name.
|
||||
* Example: "p8b_plane.p8t_Point" -> "plane.Point"
|
||||
* Used to compare names across different contexts where prefixes may or may not be applied.
|
||||
* See https://github.com/irmen/prog8/issues/198
|
||||
*/
|
||||
fun stripPrefixes(scopedName: String): String {
|
||||
return scopedName.split('.')
|
||||
.map { part ->
|
||||
if(part.length > 4 && part.startsWith("p8") && part[2].isLetter() && part[3] == '_')
|
||||
part.drop(4)
|
||||
else part
|
||||
}
|
||||
.joinToString(".")
|
||||
}
|
||||
}
|
||||
+32
-9
@@ -1,7 +1,6 @@
|
||||
package prog8.codegen.cpu6502
|
||||
package prog8.code.assembly
|
||||
|
||||
import prog8.code.GENERATED_LABEL_PREFIX
|
||||
import prog8.code.IAssemblyProgram
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IErrorReporter
|
||||
@@ -12,10 +11,14 @@ import prog8.code.target.PETTarget
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
internal class AssemblyProgram(
|
||||
override val name: String,
|
||||
outputDir: Path,
|
||||
private val compTarget: ICompilationTarget) : IAssemblyProgram {
|
||||
class AssemblyProgram6502(
|
||||
override val name: String,
|
||||
outputDir: Path,
|
||||
private val compTarget: ICompilationTarget) : IAssemblyProgram {
|
||||
|
||||
override val irInstructionCount: Int = 0
|
||||
override val irChunkCount: Int = 0
|
||||
override val irRegisterCount: Int = 0
|
||||
|
||||
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
|
||||
@@ -129,12 +132,31 @@ internal class AssemblyProgram(
|
||||
|
||||
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
|
||||
}
|
||||
else -> error("Unsupported output type: ${compTarget.defaultOutputType}")
|
||||
}
|
||||
|
||||
val proc = ProcessBuilder(assemblerCommand)
|
||||
if(!options.quiet)
|
||||
proc.inheritIO()
|
||||
val process = proc.start()
|
||||
.redirectErrorStream(true)
|
||||
|
||||
if (options.quiet) {
|
||||
proc.redirectOutput(ProcessBuilder.Redirect.DISCARD)
|
||||
}
|
||||
|
||||
val process = try {
|
||||
proc.start()
|
||||
} catch (e: Exception) {
|
||||
System.err.println("daemon: assembler failed to start: ${e.message}")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!options.quiet) {
|
||||
process.inputStream.bufferedReader().use { reader ->
|
||||
reader.forEachLine {
|
||||
println(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = process.waitFor()
|
||||
if (result == 0) {
|
||||
removeGeneratedLabelsFromMonlist()
|
||||
@@ -170,3 +192,4 @@ internal class AssemblyProgram(
|
||||
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package prog8.code.assembly
|
||||
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IErrorReporter
|
||||
|
||||
interface IAssemblyProgram {
|
||||
val name: String
|
||||
val irInstructionCount: Int
|
||||
val irChunkCount: Int
|
||||
val irRegisterCount: Int
|
||||
fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean
|
||||
}
|
||||
@@ -24,46 +24,54 @@ class CallConvention(val params: List<ParamConvention>, val returns: ReturnConve
|
||||
class FParam(val name: String, vararg val possibleDatatypes: BaseDataType)
|
||||
|
||||
|
||||
private val IterableDatatypes = arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW)
|
||||
private val IterableDatatypes = arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
|
||||
private val IntegerDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
|
||||
private val NumericDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
|
||||
|
||||
class FSignature(val pure: Boolean, // does it have side effects?
|
||||
val returnType: BaseDataType?,
|
||||
val returnTypes: Array<BaseDataType>,
|
||||
vararg val parameters: FParam) {
|
||||
|
||||
fun callConvention(actualParamTypes: List<BaseDataType>): CallConvention {
|
||||
val returns: ReturnConvention = when (returnType) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY)
|
||||
null -> ReturnConvention(null, null)
|
||||
else -> {
|
||||
// return type depends on arg type
|
||||
when (val paramType = actualParamTypes.first()) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
else -> ReturnConvention(paramType, null)
|
||||
fun callConventionFor6502(actualParamTypes: List<BaseDataType>): CallConvention {
|
||||
val returns: ReturnConvention
|
||||
if(returnTypes.isEmpty())
|
||||
returns = ReturnConvention(null, null)
|
||||
else if(returnTypes.size==1) {
|
||||
val returnType = returnTypes[0]
|
||||
returns = when (returnType) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
else -> {
|
||||
// return type depends on arg type
|
||||
when (val paramType = actualParamTypes.first()) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
else -> ReturnConvention(paramType, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// note: this path is currently never hit because the builtin functions with multiple return values have been handled in specialized optimized codegens already earlier.
|
||||
TODO("multiple return types from builtin function")
|
||||
}
|
||||
|
||||
return when {
|
||||
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
|
||||
actualParamTypes.size==1 -> {
|
||||
// One parameter goes via register/registerpair.
|
||||
// One parameter goes via register/registerpair (except longs)
|
||||
// this avoids repeated code for every caller to store the value in the subroutine's argument variable.
|
||||
// (that store is still done, but only coded once at the start at the subroutine itself rather than at every call site).
|
||||
val paramConv = when(val paramType = actualParamTypes[0]) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
BaseDataType.LONG -> ParamConvention(paramType, RegisterOrPair.R14R15, false)
|
||||
BaseDataType.LONG -> ParamConvention(paramType, null, true)
|
||||
BaseDataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // NOTE: for builtin functions, floating point arguments are passed by reference (so you get a pointer in AY)
|
||||
in IterableDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
else -> ParamConvention(paramType, null, false)
|
||||
@@ -89,96 +97,106 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
}
|
||||
|
||||
val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"setlsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"setmsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"cmp" to FSignature(false, null, FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns result in the cpu status flags, but not asa proper return value
|
||||
"prog8_lib_stringcompare" to FSignature(true, BaseDataType.BYTE, FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
|
||||
"prog8_lib_square_byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"prog8_lib_square_word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
|
||||
"prog8_lib_structalloc" to FSignature(true, BaseDataType.UWORD),
|
||||
"prog8_lib_copylong" to FSignature(false, null, FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"prog8_lib_copyfloat" to FSignature(false, null, FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"abs" to FSignature(true, null, FParam("value", *NumericDatatypes)),
|
||||
"abs__byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)),
|
||||
"abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)),
|
||||
"abs__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG)),
|
||||
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
|
||||
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
|
||||
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
|
||||
"offsetof" to FSignature(true, BaseDataType.UBYTE, FParam("field", BaseDataType.UBYTE)),
|
||||
"sgn" to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)),
|
||||
"sqrt" to FSignature(true, null, FParam("value", *NumericDatatypes)),
|
||||
"sqrt__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)),
|
||||
"sqrt__uword" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD)),
|
||||
"sqrt__long" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.LONG)),
|
||||
"sqrt__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
|
||||
"swap" to FSignature(false, null, FParam("var1", *NumericDatatypes + BaseDataType.BOOL), FParam("var2", *NumericDatatypes + BaseDataType.BOOL)),
|
||||
"swap__byte" to FSignature(false, null, FParam("var1", BaseDataType.BYTE, BaseDataType.UBYTE, BaseDataType.BOOL), FParam("var2",BaseDataType.BYTE, BaseDataType.UBYTE, BaseDataType.BOOL)),
|
||||
"swap__word" to FSignature(false, null, FParam("var1", BaseDataType.WORD, BaseDataType.UWORD), FParam("var2",BaseDataType.WORD, BaseDataType.UWORD)),
|
||||
"swap__long" to FSignature(false, null, FParam("var1", BaseDataType.LONG), FParam("var2",BaseDataType.LONG)),
|
||||
"swap__float" to FSignature(false, null, FParam("var1", BaseDataType.FLOAT), FParam("var2",BaseDataType.FLOAT)),
|
||||
"divmod" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("quotient", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("remainder", BaseDataType.UBYTE, BaseDataType.UWORD)),
|
||||
"divmod__ubyte" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE), FParam("quotient", BaseDataType.UBYTE), FParam("remainder", BaseDataType.UBYTE)),
|
||||
"divmod__uword" to FSignature(false, null, FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD), FParam("quotient", BaseDataType.UWORD), FParam("remainder", BaseDataType.UWORD)),
|
||||
"lsb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"lsb__long" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.LONG)),
|
||||
"msb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"msb__long" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.LONG)),
|
||||
"lsw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"msw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"mkword" to FSignature(true, BaseDataType.UWORD, FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong" to FSignature(true, BaseDataType.LONG, FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong2" to FSignature(true, BaseDataType.LONG, FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
|
||||
"clamp" to FSignature(true, null, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__byte" to FSignature(true, BaseDataType.BYTE, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
|
||||
"clamp__word" to FSignature(true, BaseDataType.WORD, FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
|
||||
"clamp__uword" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
|
||||
"clamp__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
|
||||
"min" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"min__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"min__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"min__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"max" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"max__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"max__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"max__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"peek" to FSignature(true, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD)),
|
||||
"peekbool" to FSignature(true, BaseDataType.BOOL, FParam("address", BaseDataType.UWORD)),
|
||||
"peekw" to FSignature(true, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
|
||||
"peekl" to FSignature(true, BaseDataType.LONG, FParam("address", BaseDataType.UWORD)),
|
||||
"peekf" to FSignature(true, BaseDataType.FLOAT, FParam("address", BaseDataType.UWORD)),
|
||||
"poke" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
|
||||
"pokebool" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
|
||||
"pokebowl" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
|
||||
"pokew" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
|
||||
"pokel" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
|
||||
"pokef" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
|
||||
"pokemon" to FSignature(false, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
|
||||
"rsave" to FSignature(false, null),
|
||||
"rrestore" to FSignature(false, null),
|
||||
"memory" to FSignature(true, BaseDataType.UWORD, FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
|
||||
"callfar" to FSignature(false, BaseDataType.UWORD, FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("arg", BaseDataType.UWORD)),
|
||||
"callfar2" to FSignature(false, BaseDataType.UWORD, FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
|
||||
"call" to FSignature(false, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
|
||||
"setlsb" to FSignature(false, emptyArray(), FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"setmsb" to FSignature(false, emptyArray(), FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"rol" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"rol2" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror2" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"cmp" to FSignature(false, emptyArray(), FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns result in the cpu status flags, but not asa proper return value
|
||||
"prog8_lib_stringcompare" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
|
||||
"prog8_lib_square_byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"prog8_lib_square_word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
|
||||
"prog8_lib_square_long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
|
||||
"prog8_lib_structalloc" to FSignature(true, arrayOf(BaseDataType.UWORD)),
|
||||
"prog8_lib_copylong" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("pointer2", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"prog8_lib_copyfloat" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("pointer2", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"abs" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
|
||||
"abs__byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE)),
|
||||
"abs__word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD)),
|
||||
"abs__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
|
||||
"abs__float" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("value", BaseDataType.FLOAT)),
|
||||
"len" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("values", *IterableDatatypes)),
|
||||
"sizeof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
|
||||
"offsetof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("field", BaseDataType.UBYTE)),
|
||||
"sgn" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", *NumericDatatypes)),
|
||||
"sqrt" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
|
||||
"sqrt__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE)),
|
||||
"sqrt__uword" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD)),
|
||||
"sqrt__long" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
|
||||
"sqrt__float" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("value", BaseDataType.FLOAT)),
|
||||
"divmod" to FSignature(false, arrayOf(BaseDataType.UNDEFINED, BaseDataType.UNDEFINED), FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD)),
|
||||
"divmod__ubyte" to FSignature(false, arrayOf(BaseDataType.UBYTE, BaseDataType.UBYTE), FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE)),
|
||||
"divmod__uword" to FSignature(false, arrayOf(BaseDataType.UWORD, BaseDataType.UWORD), FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD)),
|
||||
"divmod__byte" to FSignature(false, arrayOf(BaseDataType.BYTE, BaseDataType.BYTE), FParam("dividend", BaseDataType.BYTE), FParam("divisor", BaseDataType.BYTE)),
|
||||
"divmod__word" to FSignature(false, arrayOf(BaseDataType.WORD, BaseDataType.WORD), FParam("dividend", BaseDataType.WORD), FParam("divisor", BaseDataType.WORD)),
|
||||
"lmh" to FSignature(true, arrayOf(BaseDataType.UBYTE, BaseDataType.UBYTE, BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
|
||||
"lsb" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"lsb__long" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
|
||||
"msb" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"msb__long" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
|
||||
"lsw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"msw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"mkword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong2" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
|
||||
"clamp" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
|
||||
"clamp__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
|
||||
"clamp__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
|
||||
"clamp__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
|
||||
"min" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"min__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"min__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"min__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"max" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"max__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"max__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"max__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"peek" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekbool" to FSignature(true, arrayOf(BaseDataType.BOOL), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekl" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekf" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekp" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("address", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"poke" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
|
||||
"pokebool" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.BOOL)),
|
||||
"pokebowl" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.BOOL)),
|
||||
"pokew" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
|
||||
"pokel" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.LONG)),
|
||||
"pokef" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.FLOAT)),
|
||||
"pokep" to FSignature(false, emptyArray(), FParam("address", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.POINTER)),
|
||||
"pokemon" to FSignature(false, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UBYTE)),
|
||||
"rsave" to FSignature(false, emptyArray()),
|
||||
"rrestore" to FSignature(false, emptyArray()),
|
||||
"memory" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
|
||||
"memory__ref" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("name", BaseDataType.STR)),
|
||||
"callfar" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.POINTER), FParam("arg", BaseDataType.UWORD)),
|
||||
"callfar2" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.POINTER), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
|
||||
"call" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.POINTER)),
|
||||
"push" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)),
|
||||
"pushw" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER)),
|
||||
"pushl" to FSignature(false, emptyArray(), FParam("value", BaseDataType.LONG)),
|
||||
"pushf" to FSignature(false, emptyArray(), FParam("value", BaseDataType.FLOAT)),
|
||||
"pushp" to FSignature(false, emptyArray(), FParam("value", BaseDataType.POINTER)),
|
||||
"pop" to FSignature(false, arrayOf(BaseDataType.UBYTE)),
|
||||
"popw" to FSignature(false, arrayOf(BaseDataType.UWORD)),
|
||||
"popl" to FSignature(false, arrayOf(BaseDataType.LONG)),
|
||||
"popf" to FSignature(false, arrayOf(BaseDataType.FLOAT)),
|
||||
"popp" to FSignature(false, arrayOf(BaseDataType.POINTER)),
|
||||
)
|
||||
|
||||
val InplaceModifyingBuiltinFunctions = setOf(
|
||||
"setlsb", "setmsb",
|
||||
"rol", "ror", "rol2", "ror2",
|
||||
"divmod", "divmod__ubyte", "divmod__uword",
|
||||
"swap", "swap__byte", "swap__word", "swap__long", "swap__float"
|
||||
"rol", "ror", "rol2", "ror2"
|
||||
)
|
||||
|
||||
val SimpleBuiltinFunctions = setOf(
|
||||
"msb", "lsb", "msw", "lsw",
|
||||
"mkword", "mklong", "mklong2",
|
||||
"set_carry", "set_irqd", "clear_carry", "clear_irqd")
|
||||
"set_carry", "set_irqd", "clear_carry", "clear_irqd")
|
||||
@@ -4,6 +4,7 @@ import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
class CompilationOptions(val output: OutputType,
|
||||
val launcher: CbmPrgLauncherType,
|
||||
val zeropage: ZeropageType,
|
||||
@@ -17,6 +18,7 @@ class CompilationOptions(val output: OutputType,
|
||||
// these are set later, based on command line arguments or options in the source code:
|
||||
var loadAddress: UInt,
|
||||
var memtopAddress: UInt,
|
||||
var varsAddress: UInt? = null,
|
||||
var warnSymbolShadowing: Boolean = false,
|
||||
var warnImplicitTypeCast: Boolean = false,
|
||||
var optimize: Boolean = false,
|
||||
@@ -25,16 +27,16 @@ class CompilationOptions(val output: OutputType,
|
||||
var includeSourcelines: Boolean = false,
|
||||
var dumpVariables: Boolean = false,
|
||||
var dumpSymbols: Boolean = false,
|
||||
var experimentalCodegen: Boolean = false,
|
||||
var newCodegen: Boolean = false,
|
||||
var varsHighBank: Int? = null,
|
||||
var varsGolden: Boolean = false,
|
||||
var slabsHighBank: Int? = null,
|
||||
var slabsGolden: Boolean = false,
|
||||
var breakpointCpuInstruction: String? = null,
|
||||
var ignoreFootguns: Boolean = false,
|
||||
var outputDir: Path = Path(""),
|
||||
var quiet: Boolean = false,
|
||||
var symbolDefs: Map<String, String> = emptyMap()
|
||||
var profilingInstrumentation: Boolean = false,
|
||||
var symbolDefs: Map<String, String> = emptyMap(),
|
||||
var privateSymbols: Boolean = false
|
||||
) {
|
||||
init {
|
||||
compTarget.initializeMemoryAreas(this)
|
||||
@@ -42,5 +44,79 @@ class CompilationOptions(val output: OutputType,
|
||||
|
||||
companion object {
|
||||
val AllZeropageAllowed: List<UIntRange> = listOf(0u..255u)
|
||||
|
||||
fun builder(compTarget: ICompilationTarget) = Builder(compTarget)
|
||||
|
||||
class Builder(private val compTarget: ICompilationTarget) {
|
||||
private var output: OutputType = compTarget.defaultOutputType
|
||||
private var launcher: CbmPrgLauncherType = CbmPrgLauncherType.NONE
|
||||
private var zeropage: ZeropageType = ZeropageType.DONTUSE
|
||||
private var zpReserved: List<UIntRange> = emptyList()
|
||||
private var zpAllowed: List<UIntRange> = AllZeropageAllowed
|
||||
private var floats: Boolean = false
|
||||
private var noSysInit: Boolean = false
|
||||
private var romable: Boolean = false
|
||||
private var compilerVersion: String = "unknown"
|
||||
private var loadAddress: UInt = compTarget.PROGRAM_LOAD_ADDRESS
|
||||
private var memtopAddress: UInt = compTarget.PROGRAM_MEMTOP_ADDRESS
|
||||
private var varsAddress: UInt? = null
|
||||
private var warnSymbolShadowing: Boolean = false
|
||||
private var warnImplicitTypeCast: Boolean = false
|
||||
private var optimize: Boolean = false
|
||||
private var asmQuiet: Boolean = false
|
||||
private var asmListfile: Boolean = false
|
||||
private var includeSourcelines: Boolean = false
|
||||
private var dumpVariables: Boolean = false
|
||||
private var dumpSymbols: Boolean = false
|
||||
private var varsHighBank: Int? = null
|
||||
private var varsGolden: Boolean = false
|
||||
private var breakpointCpuInstruction: String? = null
|
||||
private var ignoreFootguns: Boolean = false
|
||||
private var outputDir: Path = Path("")
|
||||
private var quiet: Boolean = false
|
||||
private var profilingInstrumentation: Boolean = false
|
||||
private var newCodegen: Boolean = false
|
||||
private var symbolDefs: Map<String, String> = emptyMap()
|
||||
|
||||
fun output(output: OutputType) = apply { this.output = output }
|
||||
fun launcher(launcher: CbmPrgLauncherType) = apply { this.launcher = launcher }
|
||||
fun zeropage(zeropage: ZeropageType) = apply { this.zeropage = zeropage }
|
||||
fun zpReserved(zpReserved: List<UIntRange>) = apply { this.zpReserved = zpReserved }
|
||||
fun zpAllowed(zpAllowed: List<UIntRange>) = apply { this.zpAllowed = zpAllowed }
|
||||
fun floats(floats: Boolean) = apply { this.floats = floats }
|
||||
fun noSysInit(noSysInit: Boolean) = apply { this.noSysInit = noSysInit }
|
||||
fun romable(romable: Boolean) = apply { this.romable = romable }
|
||||
fun compilerVersion(compilerVersion: String) = apply { this.compilerVersion = compilerVersion }
|
||||
fun loadAddress(loadAddress: UInt) = apply { this.loadAddress = loadAddress }
|
||||
fun memtopAddress(memtopAddress: UInt) = apply { this.memtopAddress = memtopAddress }
|
||||
fun varsAddress(varsAddress: UInt?) = apply { this.varsAddress = varsAddress }
|
||||
fun warnSymbolShadowing(warnSymbolShadowing: Boolean) = apply { this.warnSymbolShadowing = warnSymbolShadowing }
|
||||
fun warnImplicitTypeCast(warnImplicitTypeCast: Boolean) = apply { this.warnImplicitTypeCast = warnImplicitTypeCast }
|
||||
fun optimize(optimize: Boolean) = apply { this.optimize = optimize }
|
||||
fun asmQuiet(asmQuiet: Boolean) = apply { this.asmQuiet = asmQuiet }
|
||||
fun asmListfile(asmListfile: Boolean) = apply { this.asmListfile = asmListfile }
|
||||
fun includeSourcelines(includeSourcelines: Boolean) = apply { this.includeSourcelines = includeSourcelines }
|
||||
fun dumpVariables(dumpVariables: Boolean) = apply { this.dumpVariables = dumpVariables }
|
||||
fun dumpSymbols(dumpSymbols: Boolean) = apply { this.dumpSymbols = dumpSymbols }
|
||||
fun varsHighBank(varsHighBank: Int?) = apply { this.varsHighBank = varsHighBank }
|
||||
fun varsGolden(varsGolden: Boolean) = apply { this.varsGolden = varsGolden }
|
||||
fun breakpointCpuInstruction(breakpointCpuInstruction: String?) = apply { this.breakpointCpuInstruction = breakpointCpuInstruction }
|
||||
fun ignoreFootguns(ignoreFootguns: Boolean) = apply { this.ignoreFootguns = ignoreFootguns }
|
||||
fun outputDir(outputDir: Path) = apply { this.outputDir = outputDir }
|
||||
fun quiet(quiet: Boolean) = apply { this.quiet = quiet }
|
||||
fun profilingInstrumentation(profilingInstrumentation: Boolean) = apply { this.profilingInstrumentation = profilingInstrumentation }
|
||||
fun symbolDefs(symbolDefs: Map<String, String>) = apply { this.symbolDefs = symbolDefs }
|
||||
fun newCodegen(newCodegen: Boolean) = apply { this.newCodegen = newCodegen }
|
||||
|
||||
fun build(): CompilationOptions {
|
||||
return CompilationOptions(
|
||||
output, launcher, zeropage, zpReserved, zpAllowed, floats, noSysInit, romable, compTarget, compilerVersion,
|
||||
loadAddress, memtopAddress, varsAddress, warnSymbolShadowing, warnImplicitTypeCast, optimize, asmQuiet, asmListfile,
|
||||
includeSourcelines, dumpVariables, dumpSymbols, newCodegen, varsHighBank, varsGolden,
|
||||
breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
|
||||
profilingInstrumentation, symbolDefs
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,3 +98,24 @@ fun String.unescape(): String {
|
||||
}
|
||||
return result.joinToString("")
|
||||
}
|
||||
|
||||
// === Symbol name prefixing ===
|
||||
// These are used by code generators to add p8b_/p8s_/p8v_/etc. prefixes to symbol names.
|
||||
// The prefix character depends on the symbol type (block='b', sub='s', variable='v', etc.)
|
||||
// Scoped names (containing dots) have each part prefixed according to its role.
|
||||
|
||||
fun prefixScopedName(name: String, type: Char): String {
|
||||
val generatedPrefix = "p8_label_gen_"
|
||||
if('.' !in name) {
|
||||
if(name.startsWith(generatedPrefix))
|
||||
return name
|
||||
return "p8${type}_$name"
|
||||
}
|
||||
val parts = name.split('.')
|
||||
val firstPrefixed = "p8b_${parts[0]}"
|
||||
val lastPart = parts.last()
|
||||
val lastPrefixed = if(lastPart.startsWith(generatedPrefix)) lastPart else "p8${type}_$lastPart"
|
||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||
return prefixed.joinToString(".")
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
package prog8.code.core
|
||||
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Base data types supported by the Prog8 compiler.
|
||||
* These represent the fundamental types that can be used in Prog8 programs.
|
||||
*/
|
||||
enum class BaseDataType {
|
||||
UBYTE, // pass by value 8 bits unsigned
|
||||
BYTE, // pass by value 8 bits signed
|
||||
UWORD, // pass by value 16 bits unsigned
|
||||
WORD, // pass by value 16 bits signed
|
||||
LONG, // pass by value 32 bits signed
|
||||
FLOAT, // pass by value machine dependent
|
||||
BOOL, // pass by value bit 0 of an 8-bit byte
|
||||
STR, // pass by reference
|
||||
ARRAY, // pass by reference, subtype is the element type
|
||||
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
|
||||
POINTER, // typed pointer, subtype is whatever type is pointed to
|
||||
STRUCT_INSTANCE, // the actual instance of a struct (not directly supported in the language yet, but we need its type)
|
||||
ARRAY_POINTER, // array of pointers (uwords or longs), subtype is whatever type each element points to
|
||||
UNDEFINED;
|
||||
|
||||
|
||||
fun largerSizeThan(other: BaseDataType) =
|
||||
when {
|
||||
this == other -> false
|
||||
this.isByteOrBool -> false
|
||||
this.isWord -> other.isByteOrBool
|
||||
this == LONG -> other.isByteOrBool || other.isWord
|
||||
this == STR && (other == UWORD || other == LONG) || this == UWORD && other == STR -> false
|
||||
this.isArray && other.isArray -> false
|
||||
this.isArray -> other.isByteOrBool
|
||||
this == STR -> other.isByteOrBool
|
||||
this.isPointer -> other.isByteOrBool
|
||||
else -> true
|
||||
}
|
||||
|
||||
fun equalsSize(other: BaseDataType): Boolean = equalsSize(other, null)
|
||||
fun equalsSize(other: BaseDataType, memsizer: IMemSizer?): Boolean =
|
||||
when {
|
||||
this == other -> true
|
||||
this.isArray && other.isArray -> true
|
||||
this.isByteOrBool -> other.isByteOrBool
|
||||
this.isWord -> other.isWord || (other.isPointer && (memsizer == null || memsizer.POINTER_MEM_SIZE == 2u))
|
||||
this.isLong -> other.isLong || (other.isPointer && memsizer?.POINTER_MEM_SIZE != null && memsizer.POINTER_MEM_SIZE != 2u)
|
||||
this.isPointer -> when(memsizer?.POINTER_MEM_SIZE) {
|
||||
null -> other.isWord || other.isPointer
|
||||
2u -> other.isWord || other.isPointer
|
||||
else -> other.isLong || other.isPointer
|
||||
}
|
||||
this == STR && (other == UWORD || other == LONG) || (this == UWORD || this == LONG) && other == STR -> true
|
||||
this == STR && other.isArray -> true
|
||||
this.isArray && other == STR -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// BaseDataType Extension Properties
|
||||
// ============================================================================
|
||||
|
||||
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
|
||||
val BaseDataType.isUnsignedByte get() = this == BaseDataType.UBYTE
|
||||
val BaseDataType.isSignedByte get() = this == BaseDataType.BYTE
|
||||
val BaseDataType.isBool get() = this == BaseDataType.BOOL
|
||||
val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)
|
||||
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
|
||||
val BaseDataType.isUnsignedWord get() = this == BaseDataType.UWORD
|
||||
val BaseDataType.isSignedWord get() = this == BaseDataType.WORD
|
||||
val BaseDataType.isLong get() = this == BaseDataType.LONG
|
||||
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
|
||||
val BaseDataType.isInteger get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
|
||||
val BaseDataType.maxUnsignedValue: Double
|
||||
get() = when (this) {
|
||||
BaseDataType.UBYTE -> 255.0
|
||||
BaseDataType.BYTE -> 127.0
|
||||
BaseDataType.UWORD -> 65535.0
|
||||
BaseDataType.WORD -> 32767.0
|
||||
BaseDataType.LONG -> 2147483647.0
|
||||
else -> Double.MAX_VALUE
|
||||
}
|
||||
val BaseDataType.isIntegerOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
|
||||
val BaseDataType.isWordOrByteOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
|
||||
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
|
||||
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
|
||||
val BaseDataType.isSigned get() = this in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
val BaseDataType.isUnsigned get() = !this.isSigned
|
||||
val BaseDataType.isSignedInteger get() = this.isSigned && this.isInteger
|
||||
val BaseDataType.isUnsignedInteger get() = this.isUnsigned && this.isInteger
|
||||
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
|
||||
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
|
||||
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isIterable get() = this in setOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
|
||||
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
|
||||
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
|
||||
|
||||
|
||||
/**
|
||||
* Interface for types that can be used as subtypes in DataType.
|
||||
* Primarily used for struct types.
|
||||
*/
|
||||
interface ISubType {
|
||||
val scopedNameString: String
|
||||
fun memsize(sizer: IMemSizer): Int
|
||||
fun sameas(other: ISubType): Boolean
|
||||
fun getFieldType(name: String): DataType?
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents a complete data type in Prog8, including base type and optional subtype.
|
||||
*
|
||||
* DataType is immutable after construction. Use the companion object factory methods
|
||||
* to create instances.
|
||||
*
|
||||
* @property base The base data type (e.g., UBYTE, WORD, ARRAY, POINTER)
|
||||
* @property sub The subtype for arrays and strings (e.g., UBYTE for byte arrays)
|
||||
* @property subType The structured subtype for pointers and struct instances
|
||||
* @property subTypeFromAntlr Deferred subtype resolution from parser
|
||||
*/
|
||||
class DataType private constructor(
|
||||
val base: BaseDataType,
|
||||
val sub: BaseDataType?,
|
||||
var subType: ISubType?,
|
||||
var subTypeFromAntlr: List<String>? = null
|
||||
) {
|
||||
|
||||
init {
|
||||
when {
|
||||
base.isPointerArray -> {
|
||||
require(sub!=null || subType!=null || subTypeFromAntlr!=null)
|
||||
}
|
||||
base.isArray -> {
|
||||
require(sub != null)
|
||||
// subType and subTypeFromAntlr allowed for arrays with struct element types
|
||||
// (from parser creating ARRAY(LONG) directly for 32-bit targets, or from VariousCleanups conversion)
|
||||
if(base == BaseDataType.ARRAY_SPLITW)
|
||||
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
}
|
||||
base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
|
||||
base!=BaseDataType.POINTER -> require(sub == null) { "only string, array and pointer base types can have a subtype"}
|
||||
else -> {
|
||||
require(sub == null || (subType == null && subTypeFromAntlr == null)) {
|
||||
"sub and subtype can't both be set"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is DataType) return false
|
||||
if (base != other.base || sub != other.sub) return false
|
||||
val st1 = subType
|
||||
val st2 = other.subType
|
||||
if (st1 == st2) return true
|
||||
if (st1 == null || st2 == null) return false
|
||||
return st1.sameas(st2)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(base, sub, subType)
|
||||
|
||||
fun setActualSubType(actualSubType: ISubType) {
|
||||
subType = actualSubType
|
||||
subTypeFromAntlr = null
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Companion Object - Factory Methods
|
||||
// ============================================================================
|
||||
|
||||
companion object {
|
||||
|
||||
val UBYTE = DataType(BaseDataType.UBYTE, null, null)
|
||||
val BYTE = DataType(BaseDataType.BYTE, null, null)
|
||||
val UWORD = DataType(BaseDataType.UWORD, null, null)
|
||||
val WORD = DataType(BaseDataType.WORD, null, null)
|
||||
val LONG = DataType(BaseDataType.LONG, null, null)
|
||||
val FLOAT = DataType(BaseDataType.FLOAT, null, null)
|
||||
val BOOL = DataType(BaseDataType.BOOL, null, null)
|
||||
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null)
|
||||
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null)
|
||||
|
||||
private val simpletypes = mapOf(
|
||||
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null),
|
||||
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null),
|
||||
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null),
|
||||
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null),
|
||||
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null),
|
||||
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
|
||||
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
|
||||
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
|
||||
BaseDataType.POINTER to DataType(BaseDataType.POINTER, null, null),
|
||||
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
|
||||
)
|
||||
|
||||
fun forDt(dt: BaseDataType): DataType {
|
||||
if(dt.isStructInstance)
|
||||
return DataType(BaseDataType.STRUCT_INSTANCE, null, null)
|
||||
return simpletypes.getValue(dt)
|
||||
}
|
||||
|
||||
fun arrayFor(elementDt: BaseDataType, memsizer: IMemSizer): DataType {
|
||||
// wether or not a word-array should be split-words, is determined later
|
||||
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
|
||||
// On 32-bit targets (m68k, virtual), str arrays become arrays of LONG (4-byte pointers)
|
||||
// via pointerBaseType. On 6502 targets they become arrays of UWORD (2-byte pointers).
|
||||
val actualElementDt = if(elementDt==BaseDataType.STR) {
|
||||
memsizer.pointerBaseType
|
||||
} else elementDt
|
||||
if(actualElementDt.isNumericOrBool)
|
||||
return DataType(BaseDataType.ARRAY, actualElementDt, null)
|
||||
else
|
||||
throw NoSuchElementException("invalid basic element dt $elementDt")
|
||||
}
|
||||
|
||||
fun splitWordArrayFor(elementDt: BaseDataType): DataType {
|
||||
require(elementDt.isWord) { "split word array element type must be word" }
|
||||
return DataType(BaseDataType.ARRAY_SPLITW, elementDt, null)
|
||||
}
|
||||
|
||||
fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null)
|
||||
fun arrayOfPointersTo(structType: ISubType?): DataType = DataType(BaseDataType.ARRAY_POINTER, null, structType)
|
||||
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
|
||||
DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier)
|
||||
|
||||
// array of struct instances: sub is STRUCT_INSTANCE, subType carries the resolved struct
|
||||
fun arrayOfStructs(structType: ISubType): DataType = DataType(BaseDataType.ARRAY, BaseDataType.STRUCT_INSTANCE, structType)
|
||||
fun arrayOfStructsFromAntlr(struct: List<String>): DataType =
|
||||
DataType(BaseDataType.ARRAY, BaseDataType.STRUCT_INSTANCE, null, subTypeFromAntlr = struct)
|
||||
|
||||
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
|
||||
fun pointer(dt: DataType): DataType = if(dt.isBasic)
|
||||
DataType(BaseDataType.POINTER, dt.base, null)
|
||||
else
|
||||
DataType(BaseDataType.POINTER, null, dt.subType, dt.subTypeFromAntlr)
|
||||
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, structType)
|
||||
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, identifier)
|
||||
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, type)
|
||||
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// DataType Methods
|
||||
// ============================================================================
|
||||
|
||||
fun elementToArray(target: ICompilationTarget, splitwords: Boolean = true): DataType {
|
||||
fun pointerArray(): DataType =
|
||||
if (subType != null)
|
||||
arrayOfPointersTo(subType)
|
||||
else if (subTypeFromAntlr != null)
|
||||
arrayOfPointersFromAntlrTo(sub, subTypeFromAntlr)
|
||||
else
|
||||
arrayOfPointersTo(sub!!)
|
||||
|
||||
if(base == BaseDataType.STRUCT_INSTANCE) {
|
||||
val st = subType
|
||||
return if(st != null)
|
||||
arrayOfStructs(st)
|
||||
else
|
||||
arrayOfStructsFromAntlr(subTypeFromAntlr!!)
|
||||
}
|
||||
|
||||
if(!target.cpu.is6502) {
|
||||
return if (base.isPointer)
|
||||
pointerArray()
|
||||
else
|
||||
arrayFor(base, target)
|
||||
}
|
||||
|
||||
return if (splitwords && (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR))
|
||||
splitWordArrayFor(base)
|
||||
else if(base.isPointer)
|
||||
pointerArray()
|
||||
else
|
||||
arrayFor(base, target)
|
||||
}
|
||||
|
||||
fun elementType(): DataType =
|
||||
when {
|
||||
isPointerArray -> DataType(BaseDataType.POINTER, sub, subType)
|
||||
base.isArray || base==BaseDataType.STR -> {
|
||||
if (sub == BaseDataType.STRUCT_INSTANCE && subType != null)
|
||||
return DataType(BaseDataType.STRUCT_INSTANCE, null, subType)
|
||||
val dt = forDt(sub!!)
|
||||
if (subType != null) DataType(BaseDataType.POINTER, null, subType) else dt
|
||||
}
|
||||
else -> throw IllegalArgumentException("not an array")
|
||||
}
|
||||
|
||||
fun typeForUntypedAddressOf(msb: Boolean, mem: IMemSizer): DataType {
|
||||
val untypedPointerType = if(mem.memorySize(BaseDataType.POINTER)>2) LONG else UWORD
|
||||
if (isUndefined)
|
||||
return if(msb) pointer(BaseDataType.UBYTE) else untypedPointerType
|
||||
else {
|
||||
if (isBasic)
|
||||
return pointer(base)
|
||||
if (isString)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
if (isPointer)
|
||||
return untypedPointerType
|
||||
if (isArray) {
|
||||
val isSplit = base == BaseDataType.ARRAY_SPLITW ||
|
||||
(base == BaseDataType.ARRAY_POINTER && mem.memorySize(BaseDataType.POINTER) <= 2)
|
||||
if (msb || isSplit)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
val elementDt = elementType()
|
||||
if (elementDt.isBasic)
|
||||
return pointer(elementDt)
|
||||
return untypedPointerType
|
||||
}
|
||||
if (subType != null)
|
||||
return pointer(this)
|
||||
return untypedPointerType
|
||||
}
|
||||
}
|
||||
|
||||
fun dereference(): DataType {
|
||||
require(isPointer || isUnsignedWord) { "cannot dereference non-pointer type ${this}"}
|
||||
return when {
|
||||
isUnsignedWord -> forDt(BaseDataType.UBYTE)
|
||||
sub!=null -> forDt(sub)
|
||||
subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, subType)
|
||||
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr)
|
||||
else -> throw IllegalArgumentException("cannot dereference this pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = when(base) {
|
||||
BaseDataType.ARRAY -> {
|
||||
when(sub) {
|
||||
BaseDataType.BOOL -> "bool[]"
|
||||
BaseDataType.FLOAT -> "float[]"
|
||||
BaseDataType.BYTE -> "byte[]"
|
||||
BaseDataType.WORD -> "word[]"
|
||||
BaseDataType.UBYTE -> "ubyte[]"
|
||||
BaseDataType.UWORD -> "uword[]"
|
||||
BaseDataType.LONG -> "long[]"
|
||||
BaseDataType.STRUCT_INSTANCE -> if (subType != null) "${subType!!.scopedNameString}[]" else "$subTypeFromAntlr[]"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_SPLITW -> {
|
||||
when(sub) {
|
||||
BaseDataType.WORD -> "word[] (split)"
|
||||
BaseDataType.UWORD -> "uword[] (split)"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.POINTER -> {
|
||||
if(sub!=null) "^^${sub.name.lowercase()}" else if(subType!=null) "^^${subType!!.scopedNameString}" else "^^${subTypeFromAntlr}"
|
||||
}
|
||||
BaseDataType.ARRAY_POINTER -> {
|
||||
if(sub!=null) "^^${sub.name.lowercase()}[] (split)" else if (subType!=null) "^^${subType!!.scopedNameString}[] (split)" else "^^${subTypeFromAntlr}[] (split)"
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
|
||||
}
|
||||
else -> base.name.lowercase()
|
||||
}
|
||||
|
||||
fun sourceString(): String = when (base) {
|
||||
BaseDataType.BOOL -> "bool"
|
||||
BaseDataType.UBYTE -> "ubyte"
|
||||
BaseDataType.BYTE -> "byte"
|
||||
BaseDataType.UWORD -> "uword"
|
||||
BaseDataType.WORD -> "word"
|
||||
BaseDataType.LONG -> "long"
|
||||
BaseDataType.FLOAT -> "float"
|
||||
BaseDataType.STR -> "str"
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
sub!=null -> "^^${sub.name.lowercase()}"
|
||||
subType!=null -> "^^${subType!!.scopedNameString}"
|
||||
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}"
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
when {
|
||||
sub!=null -> sub.name.lowercase()
|
||||
subType!=null -> subType!!.scopedNameString
|
||||
subTypeFromAntlr!=null -> subTypeFromAntlr!!.joinToString(".")
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_POINTER -> {
|
||||
when {
|
||||
sub!=null -> "^^${sub.name.lowercase()}["
|
||||
subType!=null -> "^^${subType!!.scopedNameString}["
|
||||
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}["
|
||||
else -> "????? ["
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY -> {
|
||||
when(sub) {
|
||||
BaseDataType.UBYTE -> "ubyte["
|
||||
BaseDataType.UWORD -> "@nosplit uword["
|
||||
BaseDataType.BOOL -> "bool["
|
||||
BaseDataType.BYTE -> "byte["
|
||||
BaseDataType.WORD -> "@nosplit word["
|
||||
BaseDataType.LONG -> "long["
|
||||
BaseDataType.FLOAT -> "float["
|
||||
BaseDataType.STRUCT_INSTANCE -> if (subType != null) "${subType!!.scopedNameString}[" else "$subTypeFromAntlr["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_SPLITW -> {
|
||||
when(sub) {
|
||||
BaseDataType.UWORD -> "uword["
|
||||
BaseDataType.WORD -> "word["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.UNDEFINED -> throw IllegalArgumentException("wrong dt")
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this type is assignable to the given target type (perhaps via a typecast)
|
||||
* without loss of precision.
|
||||
*/
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
when(base) {
|
||||
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
|
||||
BaseDataType.UBYTE -> targetType.base in setOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.BYTE -> targetType.base in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.UWORD -> targetType.base in setOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
|
||||
BaseDataType.WORD -> targetType.base in setOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
|
||||
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
|
||||
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD, BaseDataType.LONG) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
|
||||
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in setOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
|
||||
targetType.isPointer -> this.isUnsignedWord || this == targetType
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_POINTER -> targetType.base in setOf(BaseDataType.ARRAY_POINTER, targetType.base == BaseDataType.ARRAY) && targetType.sub == sub
|
||||
BaseDataType.STRUCT_INSTANCE -> false // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it)
|
||||
BaseDataType.UNDEFINED -> false
|
||||
}
|
||||
|
||||
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
|
||||
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
|
||||
fun equalsSize(other: DataType, memsizer: IMemSizer): Boolean = base.equalsSize(other.base, memsizer)
|
||||
|
||||
/**
|
||||
* Returns the memory size in bytes.
|
||||
* Note: for pointer types, size() doesn't return the size of the pointer itself
|
||||
* but the size of the thing it points to.
|
||||
*/
|
||||
fun size(memsizer: IMemSizer): Int = if(sub!=null) {
|
||||
memsizer.memorySize(sub)
|
||||
} else if(subType!=null) {
|
||||
subType!!.memsize(memsizer)
|
||||
} else {
|
||||
memsizer.memorySize(base)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Properties
|
||||
// ============================================================================
|
||||
|
||||
val isBasic = sub==null && subType==null && subTypeFromAntlr==null
|
||||
val isUndefined = base == BaseDataType.UNDEFINED
|
||||
val isByte = base.isByte
|
||||
val isUnsignedByte = base == BaseDataType.UBYTE
|
||||
val isSignedByte = base == BaseDataType.BYTE
|
||||
val isByteOrBool = base.isByteOrBool
|
||||
val isWord = base.isWord
|
||||
val isUnsignedWord = base == BaseDataType.UWORD
|
||||
val isSignedWord = base == BaseDataType.WORD
|
||||
val isInteger = base.isInteger
|
||||
val isWordOrByteOrBool = base.isWordOrByteOrBool
|
||||
val isIntegerOrBool = base.isIntegerOrBool
|
||||
val isNumeric = base.isNumeric
|
||||
val isNumericOrBool = base.isNumericOrBool
|
||||
val isSigned = base.isSigned
|
||||
val isUnsigned = !base.isSigned
|
||||
val isSignedInteger = isSigned && isInteger
|
||||
val isUnsignedInteger = isUnsigned && isInteger
|
||||
val isArray = base.isArray
|
||||
val isPointer = base.isPointer
|
||||
val isPointerToByte = base.isPointer && sub?.isByteOrBool==true
|
||||
val isPointerToWord = base.isPointer && sub?.isWord==true
|
||||
val isStructInstance = base.isStructInstance
|
||||
val isPointerArray = base.isPointerArray
|
||||
val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL
|
||||
val isByteArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
|
||||
val isUnsignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UBYTE
|
||||
val isSignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BYTE
|
||||
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
|
||||
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
|
||||
val isLongArray = base.isArray && sub == BaseDataType.LONG
|
||||
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
|
||||
val isString = base == BaseDataType.STR
|
||||
val isBool = base == BaseDataType.BOOL
|
||||
val isFloat = base == BaseDataType.FLOAT
|
||||
val isLong = base == BaseDataType.LONG
|
||||
fun isStringly(memsizer: IMemSizer): Boolean =
|
||||
base == BaseDataType.STR ||
|
||||
(memsizer.POINTER_MEM_SIZE == 2u && base == BaseDataType.UWORD) ||
|
||||
(memsizer.POINTER_MEM_SIZE > 2u && base == BaseDataType.LONG) ||
|
||||
(base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
|
||||
fun isSplitWordArray(memsizer: IMemSizer): Boolean {
|
||||
return if(base== BaseDataType.ARRAY_SPLITW)
|
||||
true
|
||||
else if(base==BaseDataType.ARRAY_POINTER)
|
||||
memsizer.POINTER_MEM_SIZE<=2u
|
||||
else false
|
||||
}
|
||||
val isSplitUnsignedWordArray = base == BaseDataType.ARRAY_SPLITW && sub == BaseDataType.UWORD
|
||||
val isSplitSignedWordArray = base == BaseDataType.ARRAY_SPLITW && sub == BaseDataType.WORD
|
||||
val isIterable = base.isIterable
|
||||
val isPassByRef = base.isPassByRef
|
||||
val isPassByValue = base.isPassByValue
|
||||
}
|
||||
@@ -1,393 +1,14 @@
|
||||
package prog8.code.core
|
||||
|
||||
import java.util.*
|
||||
|
||||
enum class BaseDataType {
|
||||
UBYTE, // pass by value 8 bits unsigned
|
||||
BYTE, // pass by value 8 bits signed
|
||||
UWORD, // pass by value 16 bits unsigned
|
||||
WORD, // pass by value 16 bits signed
|
||||
LONG, // pass by value 32 bits signed
|
||||
FLOAT, // pass by value machine dependent
|
||||
BOOL, // pass by value bit 0 of an 8-bit byte
|
||||
STR, // pass by reference
|
||||
ARRAY, // pass by reference, subtype is the element type
|
||||
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
|
||||
POINTER, // typed pointer, subtype is whatever type is pointed to
|
||||
STRUCT_INSTANCE, // the actual instance of a struct (not directly supported in the language yet, but we need its type)
|
||||
ARRAY_POINTER, // array of pointers (uwords), subtype is whatever type each element points to
|
||||
UNDEFINED;
|
||||
|
||||
|
||||
fun largerSizeThan(other: BaseDataType) =
|
||||
when {
|
||||
this == other -> false
|
||||
this.isByteOrBool -> false
|
||||
this.isWord -> other.isByteOrBool
|
||||
this == LONG -> other.isByteOrBool || other.isWord
|
||||
this == STR && other == UWORD || this == UWORD && other == STR -> false
|
||||
this.isArray && other.isArray -> false
|
||||
this.isArray -> other != FLOAT
|
||||
this == STR -> other != FLOAT
|
||||
this.isPointer -> other.isByteOrBool
|
||||
else -> true
|
||||
}
|
||||
|
||||
fun equalsSize(other: BaseDataType) =
|
||||
when {
|
||||
this == other -> true
|
||||
this.isArray && other.isArray -> true
|
||||
this.isByteOrBool -> other.isByteOrBool
|
||||
this.isWord -> other.isWord || other.isPointer
|
||||
this.isPointer -> other.isWord
|
||||
this == STR && other== UWORD || this== UWORD && other== STR -> true
|
||||
this == STR && other.isArray -> true
|
||||
this.isArray && other == STR -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
|
||||
val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)
|
||||
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
|
||||
val BaseDataType.isLong get() = this == BaseDataType.LONG
|
||||
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
|
||||
val BaseDataType.isInteger get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
|
||||
val BaseDataType.isIntegerOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
|
||||
val BaseDataType.isWordOrByteOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
|
||||
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
|
||||
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
|
||||
val BaseDataType.isSigned get() = this in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
|
||||
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
|
||||
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
|
||||
val BaseDataType.isIterable get() = this in setOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
|
||||
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
|
||||
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
|
||||
|
||||
|
||||
interface ISubType {
|
||||
val scopedNameString: String
|
||||
fun memsize(sizer: IMemSizer): Int
|
||||
fun sameas(other: ISubType): Boolean
|
||||
fun getFieldType(name: String): DataType?
|
||||
}
|
||||
|
||||
class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?, var subType: ISubType?, var subTypeFromAntlr: List<String>?=null) {
|
||||
|
||||
init {
|
||||
when {
|
||||
base.isPointerArray -> {
|
||||
require(sub!=null || subType!=null || subTypeFromAntlr!=null)
|
||||
}
|
||||
base.isArray -> {
|
||||
require(sub != null && subType==null && subTypeFromAntlr==null)
|
||||
if(base.isSplitWordArray)
|
||||
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
}
|
||||
base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
|
||||
base!=BaseDataType.POINTER -> require(sub == null) { "only string, array and pointer base types can have a subtype"}
|
||||
else -> {
|
||||
require(sub == null || (subType == null && subTypeFromAntlr == null)) {
|
||||
"sub and subtype can't both be set"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is DataType) return false
|
||||
return base == other.base && sub == other.sub && (subType==other.subType || subType!!.sameas(other.subType!!))
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(base, sub, subType)
|
||||
|
||||
fun setActualSubType(actualSubType: ISubType) {
|
||||
subType = actualSubType
|
||||
subTypeFromAntlr = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val UBYTE = DataType(BaseDataType.UBYTE, null, null)
|
||||
val BYTE = DataType(BaseDataType.BYTE, null, null)
|
||||
val UWORD = DataType(BaseDataType.UWORD, null, null)
|
||||
val WORD = DataType(BaseDataType.WORD, null, null)
|
||||
val LONG = DataType(BaseDataType.LONG, null, null)
|
||||
val FLOAT = DataType(BaseDataType.FLOAT, null, null)
|
||||
val BOOL = DataType(BaseDataType.BOOL, null, null)
|
||||
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null)
|
||||
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null)
|
||||
|
||||
private val simpletypes = mapOf(
|
||||
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null),
|
||||
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null),
|
||||
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null),
|
||||
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null),
|
||||
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null),
|
||||
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
|
||||
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
|
||||
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
|
||||
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
|
||||
)
|
||||
|
||||
fun forDt(dt: BaseDataType): DataType {
|
||||
if(dt.isStructInstance)
|
||||
TODO("cannot use struct instance as a data type (yet) - use a pointer instead")
|
||||
return simpletypes.getValue(dt)
|
||||
}
|
||||
|
||||
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
|
||||
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
|
||||
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
|
||||
return if(splitwordarray && actualElementDt.isWord)
|
||||
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
|
||||
else {
|
||||
if(actualElementDt.isNumericOrBool)
|
||||
DataType(BaseDataType.ARRAY, actualElementDt, null)
|
||||
else
|
||||
throw NoSuchElementException("invalid basic element dt $elementDt")
|
||||
}
|
||||
}
|
||||
|
||||
fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null)
|
||||
fun arrayOfPointersTo(structType: ISubType?): DataType = DataType(BaseDataType.ARRAY_POINTER, null, structType)
|
||||
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
|
||||
DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier)
|
||||
|
||||
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
|
||||
fun pointer(dt: DataType): DataType = if(dt.isBasic)
|
||||
DataType(BaseDataType.POINTER, dt.base, null)
|
||||
else
|
||||
DataType(BaseDataType.POINTER, null, dt.subType, dt.subTypeFromAntlr)
|
||||
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, structType)
|
||||
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, identifier)
|
||||
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, type)
|
||||
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
|
||||
}
|
||||
|
||||
|
||||
fun elementToArray(splitwords: Boolean = true): DataType {
|
||||
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
|
||||
else arrayFor(base, false)
|
||||
}
|
||||
|
||||
fun elementType(): DataType =
|
||||
when {
|
||||
isPointerArray -> DataType(BaseDataType.POINTER, sub, subType)
|
||||
base.isArray || base==BaseDataType.STR -> forDt(sub!!)
|
||||
else -> throw IllegalArgumentException("not an array")
|
||||
}
|
||||
|
||||
fun typeForAddressOf(msb: Boolean): DataType {
|
||||
if (isUndefined)
|
||||
return if(msb) pointer(BaseDataType.UBYTE) else UWORD
|
||||
else {
|
||||
if (isBasic)
|
||||
return pointer(base)
|
||||
if (isString)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
if (isPointer)
|
||||
return UWORD
|
||||
if (isArray) {
|
||||
if (msb || isSplitWordArray)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
val elementDt = elementType()
|
||||
require(elementDt.isBasic)
|
||||
return pointer(elementDt)
|
||||
}
|
||||
if (subType != null)
|
||||
return pointer(this)
|
||||
return UWORD
|
||||
}
|
||||
}
|
||||
|
||||
fun dereference(): DataType {
|
||||
require(isPointer || isUnsignedWord)
|
||||
return when {
|
||||
isUnsignedWord -> forDt(BaseDataType.UBYTE)
|
||||
sub!=null -> forDt(sub)
|
||||
subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, subType)
|
||||
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr)
|
||||
else -> throw IllegalArgumentException("cannot dereference this pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = when(base) {
|
||||
BaseDataType.ARRAY -> {
|
||||
when(sub) {
|
||||
BaseDataType.BOOL -> "bool[]"
|
||||
BaseDataType.FLOAT -> "float[]"
|
||||
BaseDataType.BYTE -> "byte[]"
|
||||
BaseDataType.WORD -> "word[]"
|
||||
BaseDataType.UBYTE -> "ubyte[]"
|
||||
BaseDataType.UWORD -> "uword[]"
|
||||
BaseDataType.LONG -> "long[]"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_SPLITW -> {
|
||||
when(sub) {
|
||||
BaseDataType.WORD -> "word[] (split)"
|
||||
BaseDataType.UWORD -> "uword[] (split)"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.POINTER -> {
|
||||
if(sub!=null) "^^${sub.name.lowercase()}" else if(subType!=null) "^^${subType!!.scopedNameString}" else "^^${subTypeFromAntlr}"
|
||||
}
|
||||
BaseDataType.ARRAY_POINTER -> {
|
||||
if(sub!=null) "^^${sub.name.lowercase()}[] (split)" else if (subType!=null) "^^${subType!!.scopedNameString}[] (split)" else "^^${subTypeFromAntlr}[] (split)"
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
|
||||
}
|
||||
else -> base.name.lowercase()
|
||||
}
|
||||
|
||||
fun sourceString(): String = when (base) {
|
||||
BaseDataType.BOOL -> "bool"
|
||||
BaseDataType.UBYTE -> "ubyte"
|
||||
BaseDataType.BYTE -> "byte"
|
||||
BaseDataType.UWORD -> "uword"
|
||||
BaseDataType.WORD -> "word"
|
||||
BaseDataType.LONG -> "long"
|
||||
BaseDataType.FLOAT -> "float"
|
||||
BaseDataType.STR -> "str"
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
sub!=null -> "^^${sub.name.lowercase()}"
|
||||
subType!=null -> "^^${subType!!.scopedNameString}"
|
||||
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}"
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
when {
|
||||
sub!=null -> sub.name.lowercase()
|
||||
subType!=null -> subType!!.scopedNameString
|
||||
subTypeFromAntlr!=null -> subTypeFromAntlr!!.joinToString(".")
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_POINTER -> {
|
||||
when {
|
||||
sub!=null -> "^^${sub.name.lowercase()}["
|
||||
subType!=null -> "^^${subType!!.scopedNameString}["
|
||||
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}["
|
||||
else -> "????? ["
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY -> {
|
||||
when(sub) {
|
||||
BaseDataType.UBYTE -> "ubyte["
|
||||
BaseDataType.UWORD -> "@nosplit uword["
|
||||
BaseDataType.BOOL -> "bool["
|
||||
BaseDataType.BYTE -> "byte["
|
||||
BaseDataType.WORD -> "@nosplit word["
|
||||
BaseDataType.LONG -> "long["
|
||||
BaseDataType.FLOAT -> "float["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_SPLITW -> {
|
||||
when(sub) {
|
||||
BaseDataType.UWORD -> "uword["
|
||||
BaseDataType.WORD -> "word["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.UNDEFINED -> throw IllegalArgumentException("wrong dt")
|
||||
}
|
||||
|
||||
// is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
when(base) {
|
||||
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
|
||||
BaseDataType.UBYTE -> targetType.base in setOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.BYTE -> targetType.base in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.UWORD -> targetType.base in setOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
|
||||
BaseDataType.WORD -> targetType.base in setOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
|
||||
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
|
||||
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in setOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
|
||||
targetType.isPointer -> this.isUnsignedWord || this == targetType
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> false // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it)
|
||||
BaseDataType.ARRAY_POINTER -> false
|
||||
BaseDataType.UNDEFINED -> false
|
||||
}
|
||||
|
||||
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
|
||||
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
|
||||
|
||||
// note: for pointer types, size() doesn't return the size of the pointer itself but the size of the thing it points to
|
||||
fun size(memsizer: IMemSizer): Int = if(sub!=null) {
|
||||
memsizer.memorySize(sub)
|
||||
} else if(subType!=null) {
|
||||
subType!!.memsize(memsizer)
|
||||
} else {
|
||||
memsizer.memorySize(base)
|
||||
}
|
||||
|
||||
val isBasic = sub==null && subType==null && subTypeFromAntlr==null
|
||||
val isUndefined = base == BaseDataType.UNDEFINED
|
||||
val isByte = base.isByte
|
||||
val isUnsignedByte = base == BaseDataType.UBYTE
|
||||
val isSignedByte = base == BaseDataType.BYTE
|
||||
val isByteOrBool = base.isByteOrBool
|
||||
val isWord = base.isWord
|
||||
val isUnsignedWord = base == BaseDataType.UWORD
|
||||
val isSignedWord = base == BaseDataType.WORD
|
||||
val isInteger = base.isInteger
|
||||
val isWordOrByteOrBool = base.isWordOrByteOrBool
|
||||
val isIntegerOrBool = base.isIntegerOrBool
|
||||
val isNumeric = base.isNumeric
|
||||
val isNumericOrBool = base.isNumericOrBool
|
||||
val isSigned = base.isSigned
|
||||
val isUnsigned = !base.isSigned
|
||||
val isArray = base.isArray
|
||||
val isPointer = base.isPointer
|
||||
val isPointerToByte = base.isPointer && sub?.isByteOrBool==true
|
||||
val isPointerToWord = base.isPointer && sub?.isWord==true
|
||||
val isStructInstance = base.isStructInstance
|
||||
val isPointerArray = base.isPointerArray
|
||||
val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL
|
||||
val isByteArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
|
||||
val isUnsignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UBYTE
|
||||
val isSignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BYTE
|
||||
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
|
||||
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
|
||||
val isLongArray = base.isArray && sub == BaseDataType.LONG
|
||||
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
|
||||
val isString = base == BaseDataType.STR
|
||||
val isBool = base == BaseDataType.BOOL
|
||||
val isFloat = base == BaseDataType.FLOAT
|
||||
val isLong = base == BaseDataType.LONG
|
||||
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
|
||||
val isSplitWordArray = base.isSplitWordArray
|
||||
val isSplitUnsignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.UWORD
|
||||
val isSplitSignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.WORD
|
||||
val isIterable = base.isIterable
|
||||
val isPassByRef = base.isPassByRef
|
||||
val isPassByValue = base.isPassByValue
|
||||
}
|
||||
|
||||
|
||||
enum class CpuRegister {
|
||||
A,
|
||||
X,
|
||||
Y
|
||||
Y,
|
||||
// m68k:
|
||||
D0, D1, D2, D3, D4, D5, D6, D7,
|
||||
A0, A1, A2, A3, A4, A5, A6,
|
||||
FP0, FP1, FP2, FP3, FP4, FP5, FP6, FP7
|
||||
}
|
||||
|
||||
enum class RegisterOrPair {
|
||||
@@ -403,15 +24,43 @@ enum class RegisterOrPair {
|
||||
R0, R1, R2, R3, R4, R5, R6, R7,
|
||||
R8, R9, R10, R11, R12, R13, R14, R15,
|
||||
// combined virtual registers to store 32 bits longs:
|
||||
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15;
|
||||
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15,
|
||||
// m68k data & address registers:
|
||||
D0, D1, D2, D3, D4, D5, D6, D7,
|
||||
A0, A1, A2, A3, A4, A5, A6,
|
||||
// m68k FPU registers:
|
||||
FP0, FP1, FP2, FP3, FP4, FP5, FP6, FP7;
|
||||
|
||||
companion object {
|
||||
val names by lazy { entries.map { it.toString()} }
|
||||
val names: Set<String> = entries.map { it.toString() }.toSet()
|
||||
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
|
||||
return when(cpu) {
|
||||
CpuRegister.A -> A
|
||||
CpuRegister.X -> X
|
||||
CpuRegister.Y -> Y
|
||||
CpuRegister.D0 -> D0
|
||||
CpuRegister.D1 -> D1
|
||||
CpuRegister.D2 -> D2
|
||||
CpuRegister.D3 -> D3
|
||||
CpuRegister.D4 -> D4
|
||||
CpuRegister.D5 -> D5
|
||||
CpuRegister.D6 -> D6
|
||||
CpuRegister.D7 -> D7
|
||||
CpuRegister.A0 -> A0
|
||||
CpuRegister.A1 -> A1
|
||||
CpuRegister.A2 -> A2
|
||||
CpuRegister.A3 -> A3
|
||||
CpuRegister.A4 -> A4
|
||||
CpuRegister.A5 -> A5
|
||||
CpuRegister.A6 -> A6
|
||||
CpuRegister.FP0 -> FP0
|
||||
CpuRegister.FP1 -> FP1
|
||||
CpuRegister.FP2 -> FP2
|
||||
CpuRegister.FP3 -> FP3
|
||||
CpuRegister.FP4 -> FP4
|
||||
CpuRegister.FP5 -> FP5
|
||||
CpuRegister.FP6 -> FP6
|
||||
CpuRegister.FP7 -> FP7
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,12 +89,13 @@ enum class RegisterOrPair {
|
||||
}
|
||||
|
||||
fun asScopedNameVirtualReg(type: DataType?): List<String> {
|
||||
require(this in Cx16VirtualRegisters)
|
||||
require(this in Cx16VirtualRegisters || this in CombinedLongRegisters)
|
||||
val suffix = when(type?.base) {
|
||||
BaseDataType.UBYTE, BaseDataType.BOOL -> "L"
|
||||
BaseDataType.BYTE -> "sL"
|
||||
BaseDataType.WORD -> "s"
|
||||
BaseDataType.UWORD, BaseDataType.POINTER, null -> ""
|
||||
BaseDataType.LONG -> "sl"
|
||||
else -> throw IllegalArgumentException("invalid register param type for cx16 virtual reg")
|
||||
}
|
||||
return listOf("cx16", name.lowercase()+suffix)
|
||||
@@ -463,7 +113,7 @@ enum class Statusflag {
|
||||
Pn; // don't use
|
||||
|
||||
companion object {
|
||||
val names by lazy { entries.map { it.toString()} }
|
||||
val names: Set<String> = entries.map { it.toString() }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,14 +132,14 @@ enum class BranchCondition {
|
||||
VC
|
||||
}
|
||||
|
||||
val Cx16VirtualRegisters = arrayOf(
|
||||
val Cx16VirtualRegisters = setOf(
|
||||
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
|
||||
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
|
||||
RegisterOrPair.R8, RegisterOrPair.R9, RegisterOrPair.R10, RegisterOrPair.R11,
|
||||
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
|
||||
)
|
||||
|
||||
val CombinedLongRegisters = arrayOf(
|
||||
val CombinedLongRegisters = setOf(
|
||||
RegisterOrPair.R0R1,
|
||||
RegisterOrPair.R2R3,
|
||||
RegisterOrPair.R4R5,
|
||||
@@ -500,7 +150,16 @@ val CombinedLongRegisters = arrayOf(
|
||||
RegisterOrPair.R14R15
|
||||
)
|
||||
|
||||
val CpuRegisters = arrayOf(
|
||||
val M68kRegisters = setOf(
|
||||
RegisterOrPair.D0, RegisterOrPair.D1, RegisterOrPair.D2, RegisterOrPair.D3,
|
||||
RegisterOrPair.D4, RegisterOrPair.D5, RegisterOrPair.D6, RegisterOrPair.D7,
|
||||
RegisterOrPair.A0, RegisterOrPair.A1, RegisterOrPair.A2, RegisterOrPair.A3,
|
||||
RegisterOrPair.A4, RegisterOrPair.A5, RegisterOrPair.A6,
|
||||
RegisterOrPair.FP0, RegisterOrPair.FP1, RegisterOrPair.FP2, RegisterOrPair.FP3,
|
||||
RegisterOrPair.FP4, RegisterOrPair.FP5, RegisterOrPair.FP6, RegisterOrPair.FP7
|
||||
)
|
||||
|
||||
val CpuRegisters = setOf(
|
||||
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
|
||||
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
|
||||
)
|
||||
@@ -510,6 +169,8 @@ enum class OutputType {
|
||||
RAW,
|
||||
PRG,
|
||||
XEX,
|
||||
ELF,
|
||||
AMIGAHUNK,
|
||||
LIBRARY
|
||||
}
|
||||
|
||||
@@ -536,4 +197,4 @@ enum class ZeropageWish {
|
||||
enum class SplitWish {
|
||||
DONTCARE,
|
||||
NOSPLIT
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,67 @@ package prog8.code.core
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
enum class CpuType {
|
||||
CPU6502,
|
||||
CPU65C02,
|
||||
VIRTUAL
|
||||
/**
|
||||
* Identifies the target CPU type and its key characteristics that affect IR semantics.
|
||||
*
|
||||
* The most important property is [statusBitsOnMultiByteOps], which controls the IR
|
||||
* status-bit contract (see IRInstructions.kt for the full spec):
|
||||
*
|
||||
* - When `true`, the IR generator is allowed to skip emitting explicit CMP/CMPI before
|
||||
* branches that depend on the result of a previous "status-setting" instruction
|
||||
* (LOAD, INC, DEC, AND, OR, XOR, etc.). The contract is: "this instruction sets
|
||||
* Z and N based on the full multi-byte value." Backends for these targets must
|
||||
* honor this contract (e.g., M68000's `move.w #imm,d0` naturally sets Z based on
|
||||
* the 16-bit value, so the IR can rely on it).
|
||||
*
|
||||
* - When `false`, multi-byte operations on this target do NOT set status bits
|
||||
* correctly for the full value (e.g., 6502's `dec lo` only sets Z based on the
|
||||
* low byte of a 16-bit decrement). The IR generator must therefore always emit
|
||||
* an explicit CMP/CMPI before any branch that depends on the result. Backends
|
||||
* for these targets should NOT add `ora` postambles to set Z; instead they
|
||||
* rely on the explicit CMP/CMPI emitted by the IR generator.
|
||||
*
|
||||
* For 8-bit CPUs (6502, 65C02) the natural status flags from a multi-byte
|
||||
* cascade are misleading: Z is set based on the LAST byte operated on, not
|
||||
* the full value. So we treat the contract as "no instruction sets status
|
||||
* bits except CMP/CMPI/SEC/CLC/SGN/BITTST" and have the IR generator emit
|
||||
* explicit compares. This avoids the trap of "this works for BYTE but
|
||||
* silently breaks for WORD/LONG".
|
||||
*/
|
||||
enum class CpuType(val statusBitsOnMultiByteOps: Boolean) {
|
||||
/** 8-bit NMOS 6502. Multi-byte ops don't set Z based on full value. */
|
||||
CPU6502(false),
|
||||
|
||||
/** 8-bit CMOS 65C02. Same status-bit issue as plain 6502. */
|
||||
CPU65C02(false),
|
||||
|
||||
/** 16/32 bit Motorola 68000. has single instructions that set status bits when dealing with multi byte operands */
|
||||
M68000(true),
|
||||
|
||||
/** 32 bit Motorola 68020. has single instructions that set status bits when dealing with multi byte operands */
|
||||
M68020(true),
|
||||
|
||||
/**
|
||||
* Virtual machine target. We use `false` here so the IR generator always
|
||||
* emits explicit CMP/CMPI; the VM's setResultReg then does NOT set Z/N
|
||||
* flags (only CMP/CMPI/SEC/CLC/SGN/BITTST do). This matches the VM's
|
||||
* existing documentation (VirtualMachine.kt top comment) and keeps the
|
||||
* behavior consistent across all current targets.
|
||||
*/
|
||||
VIRTUAL(false)
|
||||
;
|
||||
|
||||
/** True for the 6502-family targets (plain 6502 and 65C02). */
|
||||
val is6502: Boolean
|
||||
get() = this == CPU6502 || this == CPU65C02
|
||||
val is68k: Boolean
|
||||
get() = this == M68000 || this == M68020
|
||||
/** True for targets that use the m68k-style hardware-register calling convention (D0-D7, A0-A6, FP0-FP7). */
|
||||
val usesM68kConvention: Boolean
|
||||
get() = is68k || this == VIRTUAL
|
||||
/** True for big-endian targets. The 6502 family and the VM are little-endian. */
|
||||
val isBigEndian: Boolean
|
||||
get() = this == M68000 || this == M68020
|
||||
}
|
||||
|
||||
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
@@ -13,8 +70,7 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: Int
|
||||
val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this
|
||||
val ARRAY_SIZE_LIMIT: UInt
|
||||
val PROGRAM_LOAD_ADDRESS : UInt
|
||||
val PROGRAM_MEMTOP_ADDRESS: UInt
|
||||
val BSSHIGHRAM_START: UInt
|
||||
@@ -24,11 +80,12 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
|
||||
val cpu: CpuType
|
||||
var zeropage: Zeropage
|
||||
var golden: GoldenRam
|
||||
val libraryPath: Path?
|
||||
val customLauncher: List<String>
|
||||
val additionalAssemblerOptions: List<String>
|
||||
val defaultOutputType: OutputType
|
||||
val defaultLauncherType: CbmPrgLauncherType
|
||||
val supportsBankedCalls: Boolean
|
||||
|
||||
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
|
||||
fun getFloatAsmBytes(num: Number): String
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
package prog8.code.core
|
||||
|
||||
interface IMemSizer {
|
||||
val FLOAT_MEM_SIZE: UInt
|
||||
val POINTER_MEM_SIZE: UInt
|
||||
|
||||
val pointerType: DataType
|
||||
get() = if(POINTER_MEM_SIZE > 2u) DataType.LONG else DataType.UWORD
|
||||
|
||||
val pointerBaseType: BaseDataType
|
||||
get() = if(POINTER_MEM_SIZE > 2u) BaseDataType.LONG else BaseDataType.UWORD
|
||||
|
||||
fun memorySize(dt: DataType, numElements: Int?): Int
|
||||
|
||||
fun memorySize(dt: BaseDataType): Int {
|
||||
if(dt.isPassByRef)
|
||||
return memorySize(DataType.UWORD, null) // a pointer size
|
||||
return POINTER_MEM_SIZE.toInt()
|
||||
try {
|
||||
return memorySize(DataType.forDt(dt), null)
|
||||
} catch (x: NoSuchElementException) {
|
||||
throw IllegalArgumentException(x.message)
|
||||
}
|
||||
}
|
||||
|
||||
fun isSplitWordArray(dt: DataType): Boolean {
|
||||
return dt.isSplitWordArray(this)
|
||||
}
|
||||
}
|
||||
@@ -28,26 +28,41 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
abstract val SCRATCH_PTR : UInt // temp storage for a pointer
|
||||
|
||||
|
||||
// the variables allocated into Zeropage.
|
||||
// Collections for zeropage allocation (single-threaded usage)
|
||||
// name (scoped) ==> pair of address to (Datatype + bytesize)
|
||||
val allocatedVariables = mutableMapOf<String, VarAllocation>()
|
||||
val free = mutableSetOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||
|
||||
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||
|
||||
fun removeReservedFromFreePool() {
|
||||
synchronized(this) {
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
/**
|
||||
* Report a warning for every scratch byte that is excluded from allocation by a %zpreserved
|
||||
* or %zpallowed directive. The compiler's code generators always use these fixed scratch
|
||||
* locations internally (P8ZP_SCRATCH_*), so a %zpreserved/%zpallowed range that excludes one
|
||||
* of them would let the compiler overwrite memory the user explicitly wants to protect.
|
||||
*/
|
||||
fun checkScratchConflicts(errors: IErrorReporter) {
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return
|
||||
|
||||
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
|
||||
val scratchBytes = listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR + 1u)
|
||||
for(byte in scratchBytes) {
|
||||
val preserved = options.zpReserved.any { byte in it }
|
||||
val allowed = options.zpAllowed.any { byte in it }
|
||||
if(preserved || !allowed)
|
||||
errors.warn("zero-page address $${byte.toString(16)} is a fixed scratch location used by the compiler (P8ZP_SCRATCH_*), but it is excluded by a %zpreserved or %zpallowed directive", Position.DUMMY)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeReservedFromFreePool() {
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
|
||||
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
|
||||
}
|
||||
|
||||
fun retainAllowed() {
|
||||
synchronized(this) {
|
||||
for(allowed in options.zpAllowed)
|
||||
free.retainAll { it in allowed }
|
||||
}
|
||||
for(allowed in options.zpAllowed)
|
||||
free.retainAll { it in allowed }
|
||||
}
|
||||
|
||||
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
|
||||
@@ -65,7 +80,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
position: Position?,
|
||||
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
|
||||
|
||||
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
|
||||
require(name.isEmpty() || !allocatedVariables.containsKey(name)) {"name can't be allocated twice"}
|
||||
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return Err(MemAllocationError("zero page usage has been disabled"))
|
||||
@@ -95,19 +110,17 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
else -> throw MemAllocationError("weird dt")
|
||||
}
|
||||
|
||||
synchronized(this) {
|
||||
if(free.isNotEmpty()) {
|
||||
if(size==1) {
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if(oneSeparateByteFree(candidate))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
|
||||
}
|
||||
return Ok(VarAllocation(makeAllocation(free[0], 1, datatype, name), datatype,1))
|
||||
}
|
||||
if(free.isNotEmpty()) {
|
||||
if(size==1) {
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if (sequentialFree(candidate, size))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
|
||||
if(oneSeparateByteFree(candidate))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
|
||||
}
|
||||
return Ok(VarAllocation(makeAllocation(free.first(), 1, datatype, name), datatype,1))
|
||||
}
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if (sequentialFree(candidate, size))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,43 +144,12 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
return address
|
||||
}
|
||||
|
||||
private fun oneSeparateByteFree(address: UInt) = address in free && address-1u !in free && address+1u !in free
|
||||
private fun oneSeparateByteFree(address: UInt) =
|
||||
address in free &&
|
||||
(address == 0u || address-1u !in free) &&
|
||||
address+1u !in free
|
||||
private fun sequentialFree(address: UInt, size: Int): Boolean {
|
||||
require(size>0)
|
||||
return free.containsAll((address until address+size.toUInt()).toList())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: this class is not yet used
|
||||
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
|
||||
private var nextLocation: UInt = region.first
|
||||
|
||||
override fun allocate(
|
||||
name: String,
|
||||
datatype: DataType,
|
||||
numElements: Int?,
|
||||
position: Position?,
|
||||
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
|
||||
|
||||
val size: Int =
|
||||
when {
|
||||
datatype.isIntegerOrBool -> options.compTarget.memorySize(datatype, null)
|
||||
datatype.isString -> numElements!!
|
||||
datatype.isArray -> options.compTarget.memorySize(datatype, numElements!!)
|
||||
datatype.isFloat -> {
|
||||
if (options.floats) {
|
||||
options.compTarget.memorySize(DataType.FLOAT, null)
|
||||
} else return Err(MemAllocationError("floating point option not enabled"))
|
||||
}
|
||||
else -> throw MemAllocationError("weird dt")
|
||||
}
|
||||
|
||||
return if(nextLocation<=region.last && (region.last + 1u - nextLocation) >= size.toUInt()) {
|
||||
val result = Ok(VarAllocation(nextLocation, datatype, size))
|
||||
nextLocation += size.toUInt()
|
||||
result
|
||||
} else
|
||||
Err(MemAllocationError("no more free space in Golden RAM to allocate $size sequential bytes"))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package prog8.code.core
|
||||
|
||||
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not associative because of Shortcircuit/McCarthy evaluation
|
||||
val CommutativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not commutative because of Shortcircuit/McCarthy evaluation
|
||||
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val LogicalOperators = setOf("and", "or", "xor", "not", "in")
|
||||
val BitwiseOperators = setOf("&", "|", "^", "~")
|
||||
|
||||
@@ -4,16 +4,18 @@ import prog8.code.sanitize
|
||||
import prog8.code.source.SourceCode
|
||||
import java.nio.file.InvalidPathException
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.toPath
|
||||
|
||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||
override fun toString(): String = "[$file: line $line col ${startCol}-${endCol}]"
|
||||
|
||||
init {
|
||||
if(!file.startsWith('~') ||!file.endsWith('~'))
|
||||
require(line>0 && startCol>=0 && endCol>=startCol) {
|
||||
"Invalid position: $this"
|
||||
}
|
||||
/**
|
||||
* Source code position.
|
||||
*/
|
||||
data class Position(
|
||||
val file: String,
|
||||
val line: Int, // line number (1-based)
|
||||
val startCol: Int, // start column (1-based, tab-expanded)
|
||||
val endCol: Int // end column (1-based, tab-expanded)
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "[$file: line $line col ${startCol}-${endCol}]"
|
||||
}
|
||||
|
||||
fun toClickableStr(): String {
|
||||
@@ -33,4 +35,4 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
|
||||
companion object {
|
||||
val DUMMY = Position("~dummy~", 0, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,23 +3,20 @@ package prog8.code.source
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.sanitize
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.io.path.Path
|
||||
|
||||
|
||||
// Resource caching "filesystem".
|
||||
// Note that it leaves the decision to load a resource or an actual disk file to the caller.
|
||||
// Thread-safe: uses ConcurrentHashMap for all caches.
|
||||
|
||||
object ImportFileSystem {
|
||||
|
||||
fun clearCaches() {
|
||||
cache.clear()
|
||||
lineSpanCache.clear()
|
||||
}
|
||||
val userHome: Path get() = Path.of(System.getProperty("user.home"))
|
||||
|
||||
fun expandTilde(path: String): String = if (path.startsWith("~")) {
|
||||
val userHome = System.getProperty("user.home")
|
||||
userHome + path.drop(1)
|
||||
userHome.toString() + path.drop(1)
|
||||
} else {
|
||||
path
|
||||
}
|
||||
@@ -28,45 +25,45 @@ object ImportFileSystem {
|
||||
|
||||
fun getFile(path: Path, isLibrary: Boolean=false): SourceCode {
|
||||
val normalized = path.sanitize()
|
||||
val cached = cache[normalized.toString()]
|
||||
if (cached != null)
|
||||
return cached
|
||||
val file = SourceCode.File(normalized, isLibrary)
|
||||
cache[normalized.toString()] = file
|
||||
return file
|
||||
return cache.computeIfAbsent(normalized.toString().lowercase()) {
|
||||
SourceCode.File(normalized, isLibrary)
|
||||
}
|
||||
}
|
||||
|
||||
fun getResource(name: String): SourceCode {
|
||||
val cached = cache[name]
|
||||
if (cached != null) return cached
|
||||
val resource = SourceCode.Resource(name)
|
||||
cache[name] = resource
|
||||
return resource
|
||||
return cache.computeIfAbsent(name.lowercase()) {
|
||||
SourceCode.Resource(name)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCaches() {
|
||||
cache.clear()
|
||||
lineSpanCache.clear()
|
||||
}
|
||||
|
||||
fun retrieveSourceLine(position: Position): String {
|
||||
if(SourceCode.isLibraryResource(position.file)) {
|
||||
val cached = cache[SourceCode.withoutPrefix(position.file)]
|
||||
val key = SourceCode.withoutPrefix(position.file)
|
||||
val cached = cache[key.lowercase()]
|
||||
?: runCatching { getResource(key) }.getOrNull()
|
||||
if(cached != null)
|
||||
return getLine(cached, position.line)
|
||||
}
|
||||
val cached = cache[position.file]
|
||||
val cached = cache[position.file.lowercase()]
|
||||
if(cached != null)
|
||||
return getLine(cached, position.line)
|
||||
val path = Path(position.file).sanitize()
|
||||
val cached2 = cache[path.toString()]
|
||||
val cached2 = cache[path.toString().lowercase()]
|
||||
if(cached2 != null)
|
||||
return getLine(cached2, position.line)
|
||||
throw NoSuchElementException("cannot get source line $position, with path $path")
|
||||
}
|
||||
|
||||
private fun getLine(code: SourceCode, lineIndex: Int): String {
|
||||
var spans = lineSpanCache[code]
|
||||
if(spans==null) {
|
||||
val spans = lineSpanCache.computeIfAbsent(code) {
|
||||
val lineSpans = Regex("^", RegexOption.MULTILINE).findAll(code.text).map { it.range.first }
|
||||
val ends = lineSpans.drop(1) + code.text.length
|
||||
spans = lineSpans.zip(ends).map { (start, end) -> LineSpan(start, end) }.toList().toTypedArray()
|
||||
lineSpanCache[code] = spans
|
||||
lineSpans.zip(ends).map { (start, end) -> LineSpan(start, end) }.toList().toTypedArray()
|
||||
}
|
||||
val span = spans[lineIndex - 1]
|
||||
return code.text.substring(span.start, span.end).trim()
|
||||
@@ -74,6 +71,6 @@ object ImportFileSystem {
|
||||
|
||||
private class LineSpan(val start: Int, val end: Int)
|
||||
|
||||
private val cache = TreeMap<String, SourceCode>(String.CASE_INSENSITIVE_ORDER)
|
||||
private val lineSpanCache = mutableMapOf<SourceCode, Array<LineSpan>>()
|
||||
}
|
||||
private val cache = ConcurrentHashMap<String, SourceCode>()
|
||||
private val lineSpanCache = ConcurrentHashMap<SourceCode, Array<LineSpan>>()
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class Amiga1200Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(false),
|
||||
IMemSizer by NormalMemSizer(4u, 4u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.AMIGAHUNK
|
||||
override val defaultLauncherType = CbmPrgLauncherType.NONE
|
||||
|
||||
companion object {
|
||||
const val NAME = "amiga1200"
|
||||
|
||||
/** needed to map bank numbers to libraries in extsub/CALLFAR library calls */
|
||||
val LibraryNumbers = Amiga500Target.LibraryNumbers
|
||||
}
|
||||
|
||||
override val cpu = CpuType.M68020
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MEM_SIZE = 4u
|
||||
override val POINTER_MEM_SIZE = 4u
|
||||
override val ARRAY_SIZE_LIMIT = 32768u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // amiga executables are relocatable
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xfffffffeu // not actually used
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not used
|
||||
override val BSSHIGHRAM_END = 0u // not used
|
||||
override val BSSGOLDENRAM_START = 0u // not used
|
||||
override val BSSGOLDENRAM_END = 0u // not used
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
TODO("float asm bytes")
|
||||
}
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
TODO("convert float to bytes")
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==4) { "need 4 bytes" }
|
||||
TODO("convert bytes to float")
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The amiga1200 target only supports the main emulator (Amitools's vamos).")
|
||||
return
|
||||
}
|
||||
val cpuStr = when(this.cpu) {
|
||||
CpuType.M68000 -> "68000"
|
||||
CpuType.M68020 -> "68020"
|
||||
else -> error("invalid cpu type")
|
||||
}
|
||||
val exeFile = programNameWithPath.resolveSibling("${programNameWithPath.fileName}")
|
||||
val cmd =
|
||||
listOf(
|
||||
"vamos",
|
||||
"--cpu", cpuStr,
|
||||
exeFile.toString(),
|
||||
)
|
||||
if(!quiet)
|
||||
println("Launching Amitools's Vamos...")
|
||||
val pb = ProcessBuilder(cmd).inheritIO()
|
||||
try {
|
||||
pb.start().waitFor()
|
||||
} catch(_: Exception) {
|
||||
System.err.println("Cannot find 'vamos' (Amitools's Amiga emulator). Install it from: https://github.com/cnvogelg/amitools")
|
||||
}
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean =
|
||||
address in 0xbfd000u..0xbfdfffu || // CIA-B
|
||||
address in 0xbfe000u..0xbfefffu || // CIA-A
|
||||
address in 0xdff000u..0xdff1ffu || // Custom chipset (Paula, Alice, Lisa, Denise)
|
||||
address in 0xe80000u..0xefffffu // Zorro II autoconfig space
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = Amiga1200Zeropage(compilerOptions)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class Amiga1200Zeropage(options: CompilationOptions): Zeropage(options) {
|
||||
override val SCRATCH_B1: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_REG: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_W1: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_W2: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_PTR: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class Amiga500Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(false),
|
||||
IMemSizer by NormalMemSizer(4u, 4u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.AMIGAHUNK
|
||||
override val defaultLauncherType = CbmPrgLauncherType.NONE
|
||||
|
||||
companion object {
|
||||
const val NAME = "amiga500"
|
||||
|
||||
/** needed to map bank numbers to libraries in extsub/CALLFAR library calls */
|
||||
val LibraryNumbers = mapOf(
|
||||
1 to "Sys", // exec
|
||||
2 to "DOS",
|
||||
3 to "Gfx",
|
||||
4 to "Intuition",
|
||||
5 to "GadTools",
|
||||
6 to "Layers",
|
||||
7 to "Asl",
|
||||
8 to "ConsoleDevice",
|
||||
9 to "Utility",
|
||||
10 to "Expansion",
|
||||
11 to "Icon",
|
||||
12 to "Workbench",
|
||||
13 to "Diskfont",
|
||||
14 to "IFFParse",
|
||||
15 to "Input",
|
||||
16 to "Keymap",
|
||||
17 to "Locale",
|
||||
18 to "Timer",
|
||||
19 to "LowLevel",
|
||||
20 to "Math",
|
||||
21 to "MathIeeeSingBas",
|
||||
22 to "MathIeeeSingTrans",
|
||||
23 to "MathIeeeDoubBas",
|
||||
24 to "MathIeeeDoubTrans",
|
||||
25 to "MathTrans",
|
||||
26 to "NV",
|
||||
27 to "RealTime",
|
||||
28 to "Translator",
|
||||
29 to "RexxSys",
|
||||
30 to "Cx",
|
||||
31 to "DataTypes",
|
||||
32 to "Disk",
|
||||
33 to "AmigaGuide",
|
||||
34 to "ARexx",
|
||||
35 to "BattClock",
|
||||
36 to "BattMem"
|
||||
)
|
||||
}
|
||||
|
||||
override val cpu = CpuType.M68000
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MEM_SIZE = 4u
|
||||
override val POINTER_MEM_SIZE = 4u
|
||||
override val ARRAY_SIZE_LIMIT = 32768u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // amiga executables are relocatable
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xfffffffeu // not actually used
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not used
|
||||
override val BSSHIGHRAM_END = 0u // not used
|
||||
override val BSSGOLDENRAM_START = 0u // not used
|
||||
override val BSSGOLDENRAM_END = 0u // not used
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
TODO("float asm bytes")
|
||||
}
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
TODO("convert float to bytes")
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==4) { "need 4 bytes" }
|
||||
TODO("convert bytes to float")
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The amiga500 target only supports the main emulator (Amitools's vamos).")
|
||||
return
|
||||
}
|
||||
val cpuStr = when(this.cpu) {
|
||||
CpuType.M68000 -> "68000"
|
||||
CpuType.M68020 -> "68020"
|
||||
else -> error("invalid cpu type")
|
||||
}
|
||||
val exeFile = programNameWithPath.resolveSibling("${programNameWithPath.fileName}")
|
||||
val cmd =
|
||||
listOf(
|
||||
"vamos",
|
||||
"--cpu", cpuStr,
|
||||
exeFile.toString(),
|
||||
)
|
||||
if(!quiet)
|
||||
println("Launching Amitools's Vamos...")
|
||||
val pb = ProcessBuilder(cmd).inheritIO()
|
||||
try {
|
||||
pb.start().waitFor()
|
||||
} catch(e: Exception) {
|
||||
System.err.println("Cannot find 'vamos' (Amitools's Amiga emulator). Install it from: https://github.com/cnvogelg/amitools")
|
||||
}
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean =
|
||||
address in 0xbfd000u..0xbfdfffu || // CIA-B
|
||||
address in 0xbfe000u..0xbfefffu || // CIA-A
|
||||
address in 0xdff000u..0xdff1ffu || // Custom chipset (Paula, Alice, Lisa, Denise)
|
||||
address in 0xe80000u..0xefffffu // Zorro II autoconfig space
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = AmigaZeropage(compilerOptions)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class AmigaZeropage(options: CompilationOptions): Zeropage(options) {
|
||||
override val SCRATCH_B1: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_REG: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_W1: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_W2: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_PTR: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
}
|
||||
@@ -8,14 +8,16 @@ import java.nio.file.Path
|
||||
|
||||
class C128Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(true),
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "c128"
|
||||
@@ -26,18 +28,18 @@ class C128Target: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val POINTER_MEM_SIZE = 2u
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // TODO address?
|
||||
override val BSSHIGHRAM_END = 0u // TODO address?
|
||||
override val BSSGOLDENRAM_START = 0u // TODO address?
|
||||
override val BSSGOLDENRAM_END = 0u // TODO address?
|
||||
override val BSSHIGHRAM_START = 0u // no high ram
|
||||
override val BSSHIGHRAM_END = 0u // no high ram
|
||||
override val BSSGOLDENRAM_START = 0x1300u
|
||||
override val BSSGOLDENRAM_END = 0x1bdfu // note: $1be0 - $1bff contains the 16 virtual registers
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
@@ -71,11 +73,10 @@ class C128Target: ICompilationTarget,
|
||||
process.waitFor()
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu || address in 0xff00u..0xff04u
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = C128Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, UIntRange.EMPTY) // TODO does the c128 have some of this somewhere?
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,14 +9,16 @@ import java.nio.file.Path
|
||||
|
||||
class C64Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(true),
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "c64"
|
||||
@@ -30,7 +32,8 @@ class C64Target: ICompilationTarget,
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val POINTER_MEM_SIZE = 2u
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
|
||||
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
|
||||
@@ -41,7 +44,6 @@ class C64Target: ICompilationTarget,
|
||||
override val BSSGOLDENRAM_END = 0u
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
@@ -87,7 +89,6 @@ class C64Target: ICompilationTarget,
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -98,6 +99,9 @@ val CompilationTargets = listOf(
|
||||
C128Target.NAME,
|
||||
Cx16Target.NAME,
|
||||
PETTarget.NAME,
|
||||
Qemu68kTarget.NAME,
|
||||
Amiga500Target.NAME,
|
||||
Amiga1200Target.NAME,
|
||||
VMTarget.NAME
|
||||
)
|
||||
|
||||
@@ -106,6 +110,9 @@ fun getCompilationTargetByName(name: String) = when(name.lowercase()) {
|
||||
C128Target.NAME -> C128Target()
|
||||
Cx16Target.NAME -> Cx16Target()
|
||||
PETTarget.NAME -> PETTarget()
|
||||
Qemu68kTarget.NAME -> Qemu68kTarget()
|
||||
Amiga500Target.NAME -> Amiga500Target()
|
||||
Amiga1200Target.NAME -> Amiga1200Target()
|
||||
VMTarget.NAME -> VMTarget()
|
||||
else -> throw IllegalArgumentException("invalid compilation target")
|
||||
}
|
||||
@@ -15,19 +15,21 @@ import kotlin.io.path.nameWithoutExtension
|
||||
|
||||
class ConfigFileTarget(
|
||||
override val name: String,
|
||||
override val supportsBankedCalls: Boolean,
|
||||
override val defaultEncoding: Encoding,
|
||||
override val cpu: CpuType,
|
||||
override val PROGRAM_LOAD_ADDRESS: UInt,
|
||||
override val PROGRAM_MEMTOP_ADDRESS: UInt,
|
||||
override val STARTUP_CODE_RESERVED_SIZE: UInt,
|
||||
override val BSSHIGHRAM_START: UInt,
|
||||
override val BSSHIGHRAM_END: UInt,
|
||||
override val BSSGOLDENRAM_START: UInt,
|
||||
override val BSSGOLDENRAM_END: UInt,
|
||||
override val defaultOutputType: OutputType,
|
||||
override val defaultLauncherType: CbmPrgLauncherType,
|
||||
override val libraryPath: Path,
|
||||
override val customLauncher: List<String>,
|
||||
override val additionalAssemblerOptions: List<String>,
|
||||
val varsAddress: UInt? = null,
|
||||
val ioAddresses: List<UIntRange>,
|
||||
val zpScratchB1: UInt,
|
||||
val zpScratchReg: UInt,
|
||||
@@ -37,8 +39,13 @@ class ConfigFileTarget(
|
||||
val virtualregistersStart: UInt,
|
||||
val zpFullsafe: List<UIntRange>,
|
||||
val zpKernalsafe: List<UIntRange>,
|
||||
val zpBasicsafe: List<UIntRange>
|
||||
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) {
|
||||
val zpBasicsafe: List<UIntRange>,
|
||||
val pointerSize: Int = 2
|
||||
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8u, pointerSize.toUInt()) {
|
||||
|
||||
init {
|
||||
require(pointerSize == 2 || pointerSize == 4) { "unsupported pointer size: $pointerSize" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -65,6 +72,12 @@ class ConfigFileTarget(
|
||||
return value.toUInt()
|
||||
}
|
||||
|
||||
private fun parsePointerSize(props: Properties): Int {
|
||||
val pointerSize = parseInt(props.getProperty("pointer_size", "2")).toInt()
|
||||
require(pointerSize == 2 || pointerSize == 4) { "unsupported pointer size: $pointerSize" }
|
||||
return pointerSize
|
||||
}
|
||||
|
||||
private fun parseAddressRanges(key: String, props: Properties): List<UIntRange> {
|
||||
val rangesStr = props.getString(key)
|
||||
if(rangesStr.isBlank())
|
||||
@@ -110,24 +123,35 @@ class ConfigFileTarget(
|
||||
(customLauncherStr+"\n").lines().map { it.trimEnd() }
|
||||
else emptyList()
|
||||
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
|
||||
val pointerSize = parsePointerSize(props)
|
||||
val outputTypeString = props.getProperty("output_type", "PRG")
|
||||
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
|
||||
val launcherString = props.getProperty("launcher", "BASIC")
|
||||
val defaultLauncherType = try {
|
||||
CbmPrgLauncherType.valueOf(launcherString.uppercase())
|
||||
} catch (_: IllegalArgumentException) {
|
||||
CbmPrgLauncherType.BASIC
|
||||
}
|
||||
|
||||
val varsAddress = props.getProperty("vars_address")?.let { parseInt(it) }
|
||||
|
||||
return ConfigFileTarget(
|
||||
configfile.nameWithoutExtension,
|
||||
props.getProperty("supports_banked_calls", "false").toBoolean(),
|
||||
Encoding.entries.first { it.prefix==props.getString("encoding") },
|
||||
cpuType,
|
||||
props.getInteger("load_address"),
|
||||
props.getInteger("memtop"),
|
||||
0u, // used only in a very specific error condition check in a certain scenario...
|
||||
props.getInteger("bss_highram_start"),
|
||||
props.getInteger("bss_highram_end"),
|
||||
props.getInteger("bss_goldenram_start"),
|
||||
props.getInteger("bss_goldenram_end"),
|
||||
defaultOutputType,
|
||||
defaultLauncherType,
|
||||
libraryPath,
|
||||
customLauncher,
|
||||
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
|
||||
varsAddress,
|
||||
ioAddresses,
|
||||
props.getInteger("zp_scratch_b1"),
|
||||
props.getInteger("zp_scratch_reg"),
|
||||
@@ -138,6 +162,7 @@ class ConfigFileTarget(
|
||||
zpFullsafe,
|
||||
zpKernalsafe,
|
||||
zpBasicsafe,
|
||||
pointerSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -145,10 +170,10 @@ class ConfigFileTarget(
|
||||
// TODO floats are not yet supported here, just enter some values
|
||||
override val FLOAT_MAX_POSITIVE = 9.999999999e97
|
||||
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
|
||||
override val FLOAT_MEM_SIZE = 8
|
||||
|
||||
override val FLOAT_MEM_SIZE = 8u
|
||||
override val POINTER_MEM_SIZE = pointerSize.toUInt()
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam // TODO this is not yet used
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("floats")
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("floats")
|
||||
@@ -168,6 +193,5 @@ class ConfigFileTarget(
|
||||
zpFullsafe,
|
||||
compilerOptions
|
||||
)
|
||||
// note: there's no golden ram yet
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,16 @@ import java.nio.file.Path
|
||||
|
||||
class Cx16Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(true),
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "cx16"
|
||||
@@ -26,8 +28,9 @@ class Cx16Target: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val POINTER_MEM_SIZE = 2u
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
|
||||
|
||||
@@ -37,7 +40,6 @@ class Cx16Target: ICompilationTarget,
|
||||
override val BSSGOLDENRAM_END = 0x07ffu
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
@@ -87,7 +89,6 @@ class Cx16Target: ICompilationTarget,
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, BSSGOLDENRAM_START..BSSGOLDENRAM_END)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
||||
companion object {
|
||||
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
const val FLOAT_MEM_SIZE = 5
|
||||
const val FLOAT_MEM_SIZE = 5u
|
||||
|
||||
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
|
||||
fun fromNumber(num: Number): Mflpt5 {
|
||||
@@ -73,4 +73,4 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
||||
val b4 = "$" + b4.toString(16).padStart(2, '0')
|
||||
return "$b0, $b1, $b2, $b3, $b4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,36 +4,42 @@ import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IMemSizer
|
||||
|
||||
internal class NormalMemSizer(val floatsize: Int): IMemSizer {
|
||||
internal class NormalMemSizer(override val FLOAT_MEM_SIZE: UInt, override val POINTER_MEM_SIZE: UInt): IMemSizer {
|
||||
|
||||
private val pointerSize = POINTER_MEM_SIZE.toInt()
|
||||
private val floatSize = FLOAT_MEM_SIZE.toInt()
|
||||
|
||||
override fun memorySize(dt: DataType, numElements: Int?): Int {
|
||||
if(dt.isPointerArray)
|
||||
return 2 * numElements!! // array of pointers is just array of uwords
|
||||
return pointerSize * numElements!! // array of pointers
|
||||
else if(dt.isArray) {
|
||||
if(numElements==null) return 2 // treat it as a pointer size
|
||||
if(numElements==null) return pointerSize // treat it as a pointer size
|
||||
return when(dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
|
||||
BaseDataType.STR, BaseDataType.POINTER -> numElements * pointerSize
|
||||
BaseDataType.LONG -> numElements * 4
|
||||
BaseDataType.FLOAT-> numElements * floatsize
|
||||
BaseDataType.FLOAT-> numElements * floatSize
|
||||
BaseDataType.STRUCT_INSTANCE -> numElements * (dt.subType?.memsize(this) ?: throw IllegalArgumentException("struct instance array has unresolved subtype"))
|
||||
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
else if (dt.isString) {
|
||||
return numElements // treat it as the size of the given string with the length
|
||||
?: 2 // treat it as the size to store a string pointer
|
||||
?: pointerSize // treat it as the size to store a string pointer
|
||||
}
|
||||
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isFloat -> floatsize * (numElements ?: 1)
|
||||
dt.isFloat -> floatSize * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isPointer -> 2 // pointer is just a uword
|
||||
dt.isStructInstance -> dt.subType!!.memsize(this)
|
||||
dt.isWord -> 2 * (numElements ?: 1)
|
||||
dt.isPointer -> pointerSize * (numElements ?: 1)
|
||||
dt.isStructInstance -> dt.subType?.memsize(this) ?: throw IllegalArgumentException("struct instance has unresolved subtype")
|
||||
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
|
||||
else -> 2 * (numElements ?: 1)
|
||||
else -> throw IllegalArgumentException("invalid dt $dt")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,16 @@ import java.nio.file.Path
|
||||
|
||||
class PETTarget: ICompilationTarget,
|
||||
IStringEncoding by Encoder(true),
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = false
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "pet32"
|
||||
@@ -25,8 +27,9 @@ class PETTarget: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val POINTER_MEM_SIZE = 2u
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0401u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
|
||||
|
||||
@@ -36,7 +39,6 @@ class PETTarget: ICompilationTarget,
|
||||
override val BSSGOLDENRAM_END = 0u
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
@@ -74,7 +76,6 @@ class PETTarget: ICompilationTarget,
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = PETZeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* For now target a m68020 cpu
|
||||
* Eventually the goal is to be able to create programs for the Amiga A1200 (68020 cpu) or A500 (68000 cpu)
|
||||
*/
|
||||
|
||||
class Qemu68kTarget: ICompilationTarget,
|
||||
IStringEncoding by Encoder(false),
|
||||
IMemSizer by NormalMemSizer(4u, 4u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = false
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.ELF
|
||||
override val defaultLauncherType = CbmPrgLauncherType.NONE
|
||||
|
||||
companion object {
|
||||
const val NAME = "qemu68k"
|
||||
}
|
||||
|
||||
override val cpu = CpuType.M68020
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MEM_SIZE = 4u
|
||||
override val POINTER_MEM_SIZE = 4u
|
||||
override val ARRAY_SIZE_LIMIT = 32768u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x10000u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x00100000u // TODO hardcoded at 1 Mb of RAM for now... it starts at $0
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not actually used
|
||||
override val BSSHIGHRAM_END = 0u // not actually used
|
||||
override val BSSGOLDENRAM_START = 0u // not actually used
|
||||
override val BSSGOLDENRAM_END = 0u // not actually used
|
||||
override lateinit var zeropage: Zeropage // not actually used
|
||||
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
TODO("float asm bytes")
|
||||
}
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
TODO("convert float to bytes")
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==4) { "need 4 bytes" }
|
||||
TODO("convert bytes to float")
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The qemu68k target only supports the main emulator (Qemu).")
|
||||
return
|
||||
}
|
||||
val elfFile = programNameWithPath.resolveSibling("${programNameWithPath.fileName}.elf")
|
||||
val binFile = programNameWithPath.resolveSibling("${programNameWithPath.fileName}.bin")
|
||||
val isElf = elfFile.toFile().exists()
|
||||
val cpuStr = this.cpu.toString().lowercase()
|
||||
|
||||
val cmd = if (isElf) {
|
||||
listOf(
|
||||
"qemu-system-m68k",
|
||||
"-M", "virt",
|
||||
"-cpu", cpuStr,
|
||||
"-m", "1M",
|
||||
"-kernel", elfFile.toString(),
|
||||
"-nographic"
|
||||
)
|
||||
} else if (binFile.toFile().exists()) {
|
||||
val loadAddr = PROGRAM_LOAD_ADDRESS.toInt()
|
||||
listOf(
|
||||
"qemu-system-m68k",
|
||||
"-M", "virt",
|
||||
"-cpu", cpuStr,
|
||||
"-m", "1M",
|
||||
"-device", "loader,file=${binFile},addr=0x${loadAddr.toString(16)},cpu-num=0",
|
||||
"-nographic"
|
||||
)
|
||||
} else {
|
||||
System.err.println("No .elf or .bin file found for ${programNameWithPath.fileName}")
|
||||
return
|
||||
}
|
||||
if(!quiet) {
|
||||
val launchMsg = if (isElf) "ELF" else "raw binary"
|
||||
println("Launching QEMU (press Ctrl-A X to exit)... (from $launchMsg)")
|
||||
}
|
||||
val pb = ProcessBuilder(cmd).inheritIO()
|
||||
try {
|
||||
pb.start().waitFor()
|
||||
} catch (_: java.io.IOException) {
|
||||
System.err.println("Cannot launch qemu-system-m68k. Install it via your package manager, e.g.:")
|
||||
System.err.println(" sudo apt install qemu-system-m68k # Debian/Ubuntu")
|
||||
System.err.println(" sudo pacman -S qemu-system-m68k # Arch Linux")
|
||||
System.err.println("or build it from source: https://www.qemu.org/download/")
|
||||
}
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address>=0xff000000u
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = M68kZeropage(compilerOptions)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class M68kZeropage(options: CompilationOptions): Zeropage(options) {
|
||||
override val SCRATCH_B1: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_REG: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_W1: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_W2: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_PTR: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
}
|
||||
@@ -10,35 +10,38 @@ import kotlin.io.path.readText
|
||||
|
||||
class VMTarget: ICompilationTarget,
|
||||
IStringEncoding by Encoder(false),
|
||||
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
|
||||
IMemSizer by NormalMemSizer(8u, 4u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = false
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "virtual"
|
||||
const val FLOAT_MEM_SIZE = 8 // 64-bits double
|
||||
const val RAM_SIZE = 16 * 1024 * 1024 // 16 MB
|
||||
}
|
||||
|
||||
override val cpu = CpuType.VIRTUAL
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Double.MAX_VALUE
|
||||
override val FLOAT_MAX_NEGATIVE = -Double.MAX_VALUE
|
||||
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 0u // not actually used
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used
|
||||
override val FLOAT_MEM_SIZE = 8u
|
||||
override val POINTER_MEM_SIZE = 4u
|
||||
override val ARRAY_SIZE_LIMIT = 65535u
|
||||
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xffffffu
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not actually used
|
||||
override val BSSHIGHRAM_END = 0u // not actually used
|
||||
override val BSSGOLDENRAM_START = 0u // not actually used
|
||||
override val BSSGOLDENRAM_END = 0u // not actually used
|
||||
override lateinit var zeropage: Zeropage // not actually used
|
||||
override lateinit var golden: GoldenRam // not actually used
|
||||
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
// little endian binary representation
|
||||
@@ -69,6 +72,10 @@ class VMTarget: ICompilationTarget,
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
|
||||
launchEmulatorWithTrace(programNameWithPath, quiet, traceEnabled = false)
|
||||
}
|
||||
|
||||
fun launchEmulatorWithTrace(programNameWithPath: Path, quiet: Boolean, traceEnabled: Boolean) {
|
||||
if(!quiet)
|
||||
println("\nStarting Virtual Machine...")
|
||||
|
||||
@@ -76,7 +83,7 @@ class VMTarget: ICompilationTarget,
|
||||
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
|
||||
val withExt = if(programNameWithPath.extension=="p8ir") programNameWithPath else programNameWithPath.resolveSibling("${programNameWithPath.name}.p8ir")
|
||||
if(withExt.isReadable())
|
||||
vm.runProgram(withExt.readText(), quiet)
|
||||
vm.runProgram(withExt.readText(), quiet, traceEnabled)
|
||||
else
|
||||
throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file")
|
||||
}
|
||||
@@ -85,14 +92,13 @@ class VMTarget: ICompilationTarget,
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = VirtualZeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface IVirtualMachineRunner {
|
||||
fun runProgram(irSource: String, quiet: Boolean)
|
||||
fun runProgram(irSource: String, quiet: Boolean, traceEnabled: Boolean = false)
|
||||
}
|
||||
|
||||
private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.io.CharConversionException
|
||||
object PetsciiEncoding {
|
||||
|
||||
// decoding: from Petscii/Screencodes (0-255) to Unicode
|
||||
// character tables used from https://github.com/irmen/cbmcodecs2
|
||||
// character tables used from https://codeberg.org/irmen/cbmcodecs2.git
|
||||
|
||||
private val decodingPetsciiLowercase = charArrayOf(
|
||||
'\u0000', // 0x00 -> \u0000
|
||||
|
||||
@@ -43,13 +43,13 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
0xb0u, 0xb1u, 0xb4u, 0xb5u, 0xb6u
|
||||
))
|
||||
|
||||
// if(options.zeropage==ZeropageType.BASICSAFE) {
|
||||
// can also clobber the FP locations (unconditionally, because the C128 target doesn't support floating point calculations in prog8 at this time0
|
||||
free.addAll(arrayOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu,
|
||||
0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u,
|
||||
0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u,
|
||||
0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u))
|
||||
// }
|
||||
// C128 doesn't support floating point calculations in Prog8, so we can use the FP zeropage locations
|
||||
// for general variables in both BASICSAFE and FLOATSAFE modes.
|
||||
// These addresses are normally used by the C128 BASIC floating point routines.
|
||||
free.addAll(arrayOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu,
|
||||
0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u,
|
||||
0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u,
|
||||
0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u))
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
@@ -63,7 +63,9 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
removeReservedFromFreePool()
|
||||
|
||||
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
|
||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||
// C128 DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
|
||||
// C128 has limited zeropage space in BASICSAFE/FLOATSAFE modes.
|
||||
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
override val SCRATCH_REG = 0x03u // temp storage for a register byte, must be B1+1
|
||||
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
||||
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
|
||||
override val SCRATCH_PTR = 0x9bu // temp storage for a pointer $9b+$9c
|
||||
override val SCRATCH_PTR = 0x04u // temp storage for a pointer $04+$05
|
||||
|
||||
|
||||
init {
|
||||
@@ -57,7 +57,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
// add the free Zp addresses
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(arrayOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6,
|
||||
0x92, 0x96, 0x9c, 0x9e, 0x9f, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
@@ -72,7 +72,10 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
removeReservedFromFreePool()
|
||||
|
||||
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
|
||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||
// C64 DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
|
||||
// C64 has limited zeropage space compared to CX16. In BASICSAFE and FLOATSAFE modes,
|
||||
// there isn't enough contiguous space for all 32 bytes of virtual register variants.
|
||||
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
|
||||
@@ -22,41 +22,47 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||
|
||||
synchronized(this) {
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22u..0xffu)
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xa9u..0xffu)
|
||||
}
|
||||
ZeropageType.FLOATSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xd4u..0xffu)
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
// Initialize free list - no synchronization needed during construction
|
||||
// (object reference doesn't escape until fully constructed)
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22u..0xffu)
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xa9u..0xffu)
|
||||
}
|
||||
ZeropageType.FLOATSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xd4u..0xffu)
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
|
||||
val distinctFree = free.distinct()
|
||||
free.clear()
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
allocateCx16VirtualRegisters()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
val distinctFree = free.distinct()
|
||||
free.clear()
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
allocateCx16VirtualRegisters()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
private fun allocateCx16VirtualRegisters() {
|
||||
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
|
||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||
// CX16 DESIGN: Virtual registers are ALWAYS allocated in zeropage for all ZeropageType modes.
|
||||
// The Commander X16 has ample zeropage space (0x22-0x7F in BASICSAFE, up to 0x22-0xFF in FULL),
|
||||
// so virtual registers can always fit. This ensures consistent behavior across all modes.
|
||||
// Virtual registers occupy addresses 0x02-0x21 (32 bytes for r0-r15 variants).
|
||||
//
|
||||
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped*
|
||||
// elsewhere to fixed addresses. However, to be able for the compiler to "see" them as zeropage
|
||||
// variables, we have to register them here as well. This is important because the compiler
|
||||
// sometimes treats ZP variables more efficiently (for example if it's a pointer).
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
|
||||
@@ -32,7 +32,10 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
}
|
||||
ZeropageType.FLOATSAFE,
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0xb1u..0xbau) // TODO more?
|
||||
// Only scratch register area is safe for transient use.
|
||||
// $b5 and $ba are clobbered by PET BASIC/Kernal calls like CHROUT ($ffd2).
|
||||
// User variables requiring persistent storage must NOT be placed here.
|
||||
free.addAll(listOf(0xb1u, 0xb2u, 0xb3u, 0xb4u, 0xb6u, 0xb7u, 0xb8u, 0xb9u, 0xfbu, 0xfcu, 0xfdu, 0xfeu))
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
@@ -46,7 +49,9 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
removeReservedFromFreePool()
|
||||
|
||||
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
|
||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||
// PET DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
|
||||
// PET has very limited zeropage space in BASICSAFE/FLOATSAFE modes (only 0xB1-0xBA).
|
||||
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
@@ -81,4 +86,4 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package prog8tests.helpers
|
||||
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
/**
|
||||
* Dummy IMemSizer implementation for testing.
|
||||
* Returns standard memory sizes for all data types.
|
||||
*/
|
||||
object DummyMemsizer : IMemSizer {
|
||||
override val FLOAT_MEM_SIZE: UInt = 4u
|
||||
override val POINTER_MEM_SIZE: UInt = 2u
|
||||
|
||||
override fun memorySize(dt: DataType, numElements: Int?): Int {
|
||||
if (dt.isPointerArray)
|
||||
return 2 * numElements!!
|
||||
else if (dt.isArray) {
|
||||
require(numElements != null)
|
||||
return when (dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
|
||||
BaseDataType.LONG -> numElements * 4
|
||||
BaseDataType.FLOAT -> numElements * 5
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isFloat -> 5 * (numElements ?: 1)
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun memorySize(dt: BaseDataType): Int {
|
||||
return memorySize(DataType.forDt(dt), null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dummy IStringEncoding implementation for testing.
|
||||
* Returns empty list/string for all operations.
|
||||
*/
|
||||
object DummyStringEncoder : IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ASCII IStringEncoding implementation for testing.
|
||||
* Encodes/decodes using simple ASCII mapping.
|
||||
*/
|
||||
object AsciiStringEncoder : IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> =
|
||||
str.map { it.code.toUByte() }
|
||||
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String =
|
||||
bytes.joinToString("") { it.toInt().toChar().toString() }
|
||||
}
|
||||
+25
-9
@@ -3,27 +3,38 @@ package prog8tests.helpers
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
|
||||
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
|
||||
|
||||
/**
|
||||
* Test implementation of IErrorReporter that collects errors/warnings/infos in memory.
|
||||
*
|
||||
* @param throwExceptionAtReportIfErrors If true, report() will throw an exception when errors exist
|
||||
* @param keepMessagesAfterReporting If false, clear() is called after report()
|
||||
*/
|
||||
class ErrorReporterForTests(
|
||||
private val throwExceptionAtReportIfErrors: Boolean = true,
|
||||
private val keepMessagesAfterReporting: Boolean = false
|
||||
) : IErrorReporter {
|
||||
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
val infos = mutableListOf<String>()
|
||||
val printedErrors = mutableListOf<String>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in errors)
|
||||
if (text !in errors)
|
||||
errors.add(text)
|
||||
}
|
||||
|
||||
override fun warn(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in warnings)
|
||||
if (text !in warnings)
|
||||
warnings.add(text)
|
||||
}
|
||||
|
||||
override fun info(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in infos)
|
||||
if (text !in infos)
|
||||
infos.add(text)
|
||||
}
|
||||
|
||||
@@ -31,17 +42,22 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
|
||||
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
|
||||
override fun noErrorForLine(position: Position): Boolean =
|
||||
!errors.any { ":${position.line}:" in it }
|
||||
|
||||
override fun printSingleError(errormessage: String) {
|
||||
printedErrors.add(errormessage)
|
||||
}
|
||||
|
||||
override fun report() {
|
||||
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
|
||||
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||
if(throwExceptionAtReportIfErrors)
|
||||
if (throwExceptionAtReportIfErrors)
|
||||
finalizeNumErrors(errors.size, warnings.size, infos.size)
|
||||
if(!keepMessagesAfterReporting) {
|
||||
if (!keepMessagesAfterReporting) {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,66 @@
|
||||
package prog8tests.codecore
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.target.ConfigFileTarget
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
class TestConfigFileTarget: FunSpec({
|
||||
fun loadTarget(extraProperties: String = ""): ConfigFileTarget {
|
||||
val directory = Files.createTempDirectory("prog8-config-target")
|
||||
val library = Files.createDirectories(directory.resolve("library"))
|
||||
val config = directory.resolve("target.properties")
|
||||
config.writeText(
|
||||
"""
|
||||
cpu = 65C02
|
||||
encoding = iso
|
||||
load_address = 2048
|
||||
memtop = 65535
|
||||
bss_highram_start = 0
|
||||
bss_highram_end = 0
|
||||
bss_goldenram_start = 0
|
||||
bss_goldenram_end = 0
|
||||
zp_scratch_b1 = 240
|
||||
zp_scratch_reg = 241
|
||||
zp_scratch_w1 = 242
|
||||
zp_scratch_w2 = 244
|
||||
zp_scratch_ptr = 246
|
||||
virtual_registers = 2
|
||||
zp_fullsafe = 34-239
|
||||
zp_kernalsafe = 34-239
|
||||
zp_basicsafe = 34-239
|
||||
io_regions =
|
||||
""".trimIndent() + "\nlibrary = $library\n$extraProperties"
|
||||
)
|
||||
return try {
|
||||
ConfigFileTarget.fromConfigFile(config)
|
||||
} finally {
|
||||
directory.toFile().deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
test("legacy config defaults to 2-byte pointers and 256 element arrays") {
|
||||
val target = loadTarget()
|
||||
|
||||
target.POINTER_MEM_SIZE shouldBe 2u
|
||||
target.memorySize(BaseDataType.POINTER) shouldBe 2
|
||||
target.ARRAY_SIZE_LIMIT shouldBe 256u
|
||||
}
|
||||
|
||||
test("config supports 4-byte pointers") {
|
||||
val target = loadTarget("pointer_size = 4")
|
||||
|
||||
target.POINTER_MEM_SIZE shouldBe 4u
|
||||
target.memorySize(BaseDataType.POINTER) shouldBe 4
|
||||
target.ARRAY_SIZE_LIMIT shouldBe 256u
|
||||
}
|
||||
|
||||
test("config rejects unsupported pointer sizes") {
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
loadTarget("pointer_size = 3")
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,109 @@
|
||||
package prog8tests.codecore
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.core.escape
|
||||
import prog8.code.core.toHex
|
||||
import prog8.code.core.unescape
|
||||
|
||||
/**
|
||||
* Unit tests for conversion functions in codeCore.
|
||||
* Focus on toHex boundaries and escape/unescape roundtrips.
|
||||
*/
|
||||
class TestConversions: FunSpec({
|
||||
|
||||
// ============================================================================
|
||||
// toHex() Tests
|
||||
// ============================================================================
|
||||
|
||||
test("toHex for 0-15 (decimal output)") {
|
||||
0.toHex() shouldBe "0"
|
||||
10.toHex() shouldBe "10"
|
||||
15.toHex() shouldBe "15"
|
||||
}
|
||||
|
||||
test("toHex for 16-255 (hex with $)") {
|
||||
16.toHex() shouldBe $$"$10"
|
||||
255.toHex() shouldBe $$"$ff"
|
||||
}
|
||||
|
||||
test("toHex for 256-65535 (4 digit hex)") {
|
||||
256.toHex() shouldBe $$"$0100"
|
||||
65535.toHex() shouldBe $$"$ffff"
|
||||
}
|
||||
|
||||
test("toHex for larger numbers (8 digit hex)") {
|
||||
65536.toHex() shouldBe $$"$00010000"
|
||||
0x12345678.toHex() shouldBe $$"$12345678"
|
||||
}
|
||||
|
||||
test("toHex for negative numbers") {
|
||||
(-1).toHex() shouldBe "-1"
|
||||
(-255).toHex() shouldBe $$"-$ff"
|
||||
Int.MIN_VALUE.toHex() shouldBe $$"$80000000"
|
||||
}
|
||||
|
||||
test("UInt.toHex") {
|
||||
0u.toHex() shouldBe "0"
|
||||
15u.toHex() shouldBe "15"
|
||||
255u.toHex() shouldBe $$"$ff"
|
||||
65535u.toHex() shouldBe $$"$ffff"
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// escape() Tests
|
||||
// ============================================================================
|
||||
|
||||
test("escape for plain string") {
|
||||
"hello".escape() shouldBe "hello"
|
||||
}
|
||||
|
||||
test("escape for tab and newline") {
|
||||
"hello\tworld".escape() shouldBe "hello\\tworld"
|
||||
"hello\nworld".escape() shouldBe "hello\\nworld"
|
||||
}
|
||||
|
||||
test("escape for double quote") {
|
||||
"hello\"world".escape() shouldBe "hello\\\"world"
|
||||
}
|
||||
|
||||
test("escape for unicode outside passthrough range") {
|
||||
"€".escape() shouldBe "\\u20ac"
|
||||
}
|
||||
|
||||
test("escape for passthrough characters (\\u8000-\\u80ff)") {
|
||||
"\u8000".escape() shouldBe "\\x00"
|
||||
"\u80ff".escape() shouldBe "\\xff"
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// unescape() Tests
|
||||
// ============================================================================
|
||||
|
||||
test("unescape for plain string") {
|
||||
"hello".unescape() shouldBe "hello"
|
||||
}
|
||||
|
||||
test("unescape for tab and newline") {
|
||||
"hello\\tworld".unescape() shouldBe "hello\tworld"
|
||||
"hello\\nworld".unescape() shouldBe "hello\nworld"
|
||||
}
|
||||
|
||||
test("unescape for double quote") {
|
||||
"hello\\\"world".unescape() shouldBe "hello\"world"
|
||||
}
|
||||
|
||||
test("unescape for unicode") {
|
||||
"\\u0041".unescape() shouldBe "A"
|
||||
"\\u20ac".unescape() shouldBe "€"
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Roundtrip Tests
|
||||
// ============================================================================
|
||||
|
||||
test("escape then unescape roundtrip") {
|
||||
val original = "hello\t\nworld\"test"
|
||||
original.escape().unescape() shouldBe original
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,325 @@
|
||||
package prog8tests.codecore
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.Amiga500Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for DataType and BaseDataType classes in codeCore.
|
||||
* Focus on type predicates, relationships, and key operations.
|
||||
*/
|
||||
class TestDataType: FunSpec({
|
||||
val tgt = Cx16Target()
|
||||
|
||||
val dummyMemSizer = object : IMemSizer {
|
||||
override val FLOAT_MEM_SIZE: UInt = 4u
|
||||
override val POINTER_MEM_SIZE: UInt = 2u
|
||||
|
||||
override fun memorySize(dt: DataType, numElements: Int?): Int {
|
||||
if (dt.isPointerArray) return 2 * numElements!!
|
||||
if (dt.isArray || dt.isSplitWordArray(this)) {
|
||||
require(numElements != null)
|
||||
return when (dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
|
||||
BaseDataType.LONG -> numElements * 4
|
||||
BaseDataType.FLOAT -> numElements * 5
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isFloat -> 5 * (numElements ?: 1)
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun memorySize(dt: BaseDataType): Int {
|
||||
return memorySize(DataType.forDt(dt), null)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BaseDataType Extension Property Tests
|
||||
// ============================================================================
|
||||
|
||||
test("isByte extension") {
|
||||
BaseDataType.UBYTE.isByte shouldBe true
|
||||
BaseDataType.BYTE.isByte shouldBe true
|
||||
BaseDataType.UWORD.isByte shouldBe false
|
||||
BaseDataType.WORD.isByte shouldBe false
|
||||
}
|
||||
|
||||
test("isByteOrBool extension") {
|
||||
BaseDataType.UBYTE.isByteOrBool shouldBe true
|
||||
BaseDataType.BYTE.isByteOrBool shouldBe true
|
||||
BaseDataType.BOOL.isByteOrBool shouldBe true
|
||||
BaseDataType.UWORD.isByteOrBool shouldBe false
|
||||
}
|
||||
|
||||
test("isWord extension") {
|
||||
BaseDataType.UWORD.isWord shouldBe true
|
||||
BaseDataType.WORD.isWord shouldBe true
|
||||
BaseDataType.UBYTE.isWord shouldBe false
|
||||
}
|
||||
|
||||
test("isInteger extension") {
|
||||
BaseDataType.UBYTE.isInteger shouldBe true
|
||||
BaseDataType.WORD.isInteger shouldBe true
|
||||
BaseDataType.LONG.isInteger shouldBe true
|
||||
BaseDataType.FLOAT.isInteger shouldBe false
|
||||
}
|
||||
|
||||
test("isNumeric extension") {
|
||||
BaseDataType.UBYTE.isNumeric shouldBe true
|
||||
BaseDataType.FLOAT.isNumeric shouldBe true
|
||||
BaseDataType.BOOL.isNumeric shouldBe false
|
||||
}
|
||||
|
||||
test("isSigned extension") {
|
||||
BaseDataType.BYTE.isSigned shouldBe true
|
||||
BaseDataType.WORD.isSigned shouldBe true
|
||||
BaseDataType.LONG.isSigned shouldBe true
|
||||
BaseDataType.UBYTE.isSigned shouldBe false
|
||||
}
|
||||
|
||||
test("isArray extension") {
|
||||
BaseDataType.ARRAY.isArray shouldBe true
|
||||
BaseDataType.ARRAY_SPLITW.isArray shouldBe true
|
||||
BaseDataType.ARRAY_POINTER.isArray shouldBe true
|
||||
BaseDataType.UBYTE.isArray shouldBe false
|
||||
}
|
||||
|
||||
test("isPointer extension") {
|
||||
BaseDataType.POINTER.isPointer shouldBe true
|
||||
BaseDataType.UBYTE.isPointer shouldBe false
|
||||
}
|
||||
|
||||
test("isSplitWordArray extension") {
|
||||
DataType.splitWordArrayFor(BaseDataType.UWORD).isSplitWordArray(Cx16Target()) shouldBe true
|
||||
DataType.splitWordArrayFor(BaseDataType.UWORD).isSplitWordArray(VMTarget()) shouldBe true
|
||||
DataType.splitWordArrayFor(BaseDataType.UWORD).isSplitWordArray(Amiga500Target()) shouldBe true
|
||||
|
||||
DataType.arrayOfPointersTo(BaseDataType.UWORD).isSplitWordArray(Cx16Target()) shouldBe true
|
||||
DataType.arrayOfPointersTo(BaseDataType.UWORD).isSplitWordArray(VMTarget()) shouldBe false
|
||||
DataType.arrayOfPointersTo(BaseDataType.UWORD).isSplitWordArray(Amiga500Target()) shouldBe false
|
||||
|
||||
DataType.arrayFor(BaseDataType.UWORD, Cx16Target()).isSplitWordArray(Cx16Target()) shouldBe false
|
||||
DataType.arrayFor(BaseDataType.UWORD, VMTarget()).isSplitWordArray(VMTarget()) shouldBe false
|
||||
DataType.arrayFor(BaseDataType.UWORD, Amiga500Target()).isSplitWordArray(Amiga500Target()) shouldBe false
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Companion Object Tests
|
||||
// ============================================================================
|
||||
|
||||
test("DataType.forDt creates simple types") {
|
||||
DataType.forDt(BaseDataType.UBYTE) shouldBe DataType.UBYTE
|
||||
DataType.forDt(BaseDataType.WORD) shouldBe DataType.WORD
|
||||
DataType.forDt(BaseDataType.LONG) shouldBe DataType.LONG
|
||||
DataType.forDt(BaseDataType.FLOAT) shouldBe DataType.FLOAT
|
||||
}
|
||||
|
||||
test("DataType.forDt supports struct instance") {
|
||||
val dt = DataType.forDt(BaseDataType.STRUCT_INSTANCE)
|
||||
dt.base shouldBe BaseDataType.STRUCT_INSTANCE
|
||||
dt.isStructInstance shouldBe true
|
||||
}
|
||||
|
||||
test("DataType.arrayFor creates array types") {
|
||||
val ubyteArray = DataType.arrayFor(BaseDataType.UBYTE, tgt)
|
||||
ubyteArray.base shouldBe BaseDataType.ARRAY
|
||||
ubyteArray.sub shouldBe BaseDataType.UBYTE
|
||||
|
||||
val wordArray = DataType.arrayFor(BaseDataType.WORD, tgt)
|
||||
wordArray.base shouldBe BaseDataType.ARRAY
|
||||
wordArray.sub shouldBe BaseDataType.WORD
|
||||
}
|
||||
|
||||
test("DataType.pointer creates pointer types") {
|
||||
val ptrByte = DataType.pointer(BaseDataType.UBYTE)
|
||||
ptrByte.base shouldBe BaseDataType.POINTER
|
||||
ptrByte.sub shouldBe BaseDataType.UBYTE
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Property Tests
|
||||
// ============================================================================
|
||||
|
||||
test("DataType.isBasic property") {
|
||||
DataType.UBYTE.isBasic shouldBe true
|
||||
DataType.WORD.isBasic shouldBe true
|
||||
DataType.STR.isBasic shouldBe false
|
||||
DataType.arrayFor(BaseDataType.UBYTE, tgt).isBasic shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isByte property") {
|
||||
DataType.UBYTE.isByte shouldBe true
|
||||
DataType.BYTE.isByte shouldBe true
|
||||
DataType.WORD.isByte shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isNumeric property") {
|
||||
DataType.UBYTE.isNumeric shouldBe true
|
||||
DataType.FLOAT.isNumeric shouldBe true
|
||||
DataType.BOOL.isNumeric shouldBe false
|
||||
DataType.STR.isNumeric shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isArray property") {
|
||||
DataType.arrayFor(BaseDataType.UBYTE, tgt).isArray shouldBe true
|
||||
DataType.UBYTE.isArray shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isPointer property") {
|
||||
DataType.pointer(BaseDataType.UBYTE).isPointer shouldBe true
|
||||
DataType.UBYTE.isPointer shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isString property") {
|
||||
DataType.STR.isString shouldBe true
|
||||
DataType.UBYTE.isString shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isPassByRef property") {
|
||||
DataType.STR.isPassByRef shouldBe true
|
||||
DataType.arrayFor(BaseDataType.UBYTE, tgt).isPassByRef shouldBe true
|
||||
DataType.UBYTE.isPassByRef shouldBe false
|
||||
DataType.pointer(BaseDataType.UBYTE).isPassByRef shouldBe false
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Method Tests
|
||||
// ============================================================================
|
||||
|
||||
test("DataType.elementType returns element type for arrays") {
|
||||
DataType.arrayFor(BaseDataType.UBYTE, tgt).elementType() shouldBe DataType.UBYTE
|
||||
DataType.arrayFor(BaseDataType.WORD, tgt).elementType() shouldBe DataType.WORD
|
||||
DataType.STR.elementType() shouldBe DataType.UBYTE
|
||||
}
|
||||
|
||||
test("DataType.elementType throws for non-arrays") {
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
DataType.UBYTE.elementType()
|
||||
}
|
||||
}
|
||||
|
||||
test("DataType.dereference for pointers") {
|
||||
DataType.pointer(BaseDataType.UBYTE).dereference() shouldBe DataType.UBYTE
|
||||
DataType.pointer(BaseDataType.WORD).dereference() shouldBe DataType.WORD
|
||||
DataType.UWORD.dereference() shouldBe DataType.UBYTE
|
||||
}
|
||||
|
||||
test("DataType.size with memsizer") {
|
||||
DataType.UBYTE.size(dummyMemSizer) shouldBe 1
|
||||
DataType.WORD.size(dummyMemSizer) shouldBe 2
|
||||
DataType.LONG.size(dummyMemSizer) shouldBe 4
|
||||
DataType.FLOAT.size(dummyMemSizer) shouldBe 5
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Comparison Tests
|
||||
// ============================================================================
|
||||
|
||||
test("DataType equals for same types") {
|
||||
DataType.UBYTE shouldBe DataType.UBYTE
|
||||
DataType.WORD shouldBe DataType.WORD
|
||||
DataType.STR shouldBe DataType.STR
|
||||
}
|
||||
|
||||
test("DataType equals for different types") {
|
||||
DataType.UBYTE shouldNotBe DataType.BYTE
|
||||
DataType.UBYTE shouldNotBe DataType.WORD
|
||||
}
|
||||
|
||||
test("BaseDataType.largerSizeThan") {
|
||||
BaseDataType.WORD.largerSizeThan(BaseDataType.UBYTE) shouldBe true
|
||||
BaseDataType.LONG.largerSizeThan(BaseDataType.WORD) shouldBe true
|
||||
BaseDataType.UBYTE.largerSizeThan(BaseDataType.WORD) shouldBe false
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.UBYTE) shouldBe true
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.BOOL) shouldBe true
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.UWORD) shouldBe false
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.WORD) shouldBe false
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.POINTER) shouldBe false
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.LONG) shouldBe false
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.UBYTE) shouldBe true
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.BOOL) shouldBe true
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.UWORD) shouldBe false
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.STR) shouldBe false
|
||||
}
|
||||
|
||||
test("BaseDataType.equalsSize") {
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.UBYTE) shouldBe true
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.BYTE) shouldBe true
|
||||
BaseDataType.WORD.equalsSize(BaseDataType.UWORD) shouldBe true
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.WORD) shouldBe false
|
||||
BaseDataType.STR.equalsSize(BaseDataType.STR) shouldBe true
|
||||
BaseDataType.STR.equalsSize(BaseDataType.UWORD) shouldBe true
|
||||
BaseDataType.STR.equalsSize(BaseDataType.BYTE) shouldBe false
|
||||
BaseDataType.POINTER.equalsSize(BaseDataType.UWORD) shouldBe true
|
||||
BaseDataType.POINTER.equalsSize(BaseDataType.STR) shouldBe false
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType.isAssignableTo Tests
|
||||
// ============================================================================
|
||||
|
||||
test("bool assignability") {
|
||||
DataType.BOOL.isAssignableTo(DataType.BOOL) shouldBe true
|
||||
DataType.BOOL.isAssignableTo(DataType.UBYTE) shouldBe false
|
||||
}
|
||||
|
||||
test("ubyte assignability") {
|
||||
DataType.UBYTE.isAssignableTo(DataType.UBYTE) shouldBe true
|
||||
DataType.UBYTE.isAssignableTo(DataType.UWORD) shouldBe true
|
||||
DataType.UBYTE.isAssignableTo(DataType.LONG) shouldBe true
|
||||
DataType.UBYTE.isAssignableTo(DataType.FLOAT) shouldBe true
|
||||
}
|
||||
|
||||
test("byte assignability") {
|
||||
DataType.BYTE.isAssignableTo(DataType.BYTE) shouldBe true
|
||||
DataType.BYTE.isAssignableTo(DataType.WORD) shouldBe true
|
||||
DataType.BYTE.isAssignableTo(DataType.LONG) shouldBe true
|
||||
}
|
||||
|
||||
test("uword assignability") {
|
||||
DataType.UWORD.isAssignableTo(DataType.UWORD) shouldBe true
|
||||
DataType.UWORD.isAssignableTo(DataType.LONG) shouldBe true
|
||||
DataType.UWORD.isAssignableTo(DataType.FLOAT) shouldBe true
|
||||
}
|
||||
|
||||
test("string assignability") {
|
||||
DataType.STR.isAssignableTo(DataType.STR) shouldBe true
|
||||
DataType.STR.isAssignableTo(DataType.UWORD) shouldBe true
|
||||
}
|
||||
|
||||
test("pointer assignability") {
|
||||
val ptr = DataType.pointer(BaseDataType.UBYTE)
|
||||
ptr.isAssignableTo(DataType.UWORD) shouldBe true
|
||||
ptr.isAssignableTo(DataType.LONG) shouldBe true
|
||||
ptr.isAssignableTo(DataType.pointer(BaseDataType.UBYTE)) shouldBe true
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType.toString Tests
|
||||
// ============================================================================
|
||||
|
||||
test("DataType.toString for basic types") {
|
||||
DataType.UBYTE.toString() shouldBe "ubyte"
|
||||
DataType.WORD.toString() shouldBe "word"
|
||||
DataType.LONG.toString() shouldBe "long"
|
||||
DataType.FLOAT.toString() shouldBe "float"
|
||||
}
|
||||
|
||||
test("DataType.toString for arrays") {
|
||||
DataType.arrayFor(BaseDataType.UBYTE, tgt).toString() shouldBe "ubyte[]"
|
||||
DataType.arrayFor(BaseDataType.WORD, tgt).toString() shouldBe "word[]"
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,729 @@
|
||||
package prog8tests.codecore
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.booleans.shouldBeTrue
|
||||
import io.kotest.matchers.collections.shouldBeEmpty
|
||||
import io.kotest.matchers.ints.shouldBeGreaterThan
|
||||
import io.kotest.matchers.maps.shouldContainKey
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C128Target
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.PETTarget
|
||||
import prog8.code.target.zp.C128Zeropage
|
||||
import prog8.code.target.zp.C64Zeropage
|
||||
import prog8.code.target.zp.CX16Zeropage
|
||||
import prog8.code.target.zp.PETZeropage
|
||||
|
||||
|
||||
/**
|
||||
* Comprehensive tests for Zeropage free list configurations across all targets.
|
||||
*
|
||||
* Tests verify that for each target and ZeropageType combination:
|
||||
* - The exact list of free locations is correct
|
||||
* - Scratch locations are properly reserved
|
||||
* - Virtual registers are allocated when appropriate
|
||||
*/
|
||||
class TestZeropageFreeList: FunSpec({
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
fun createOptions(target: ICompilationTarget, zpType: ZeropageType, floats: Boolean = false): CompilationOptions {
|
||||
return CompilationOptions.builder(target)
|
||||
.output(OutputType.RAW)
|
||||
.zeropage(zpType)
|
||||
.floats(floats)
|
||||
.compilerVersion("test")
|
||||
.build()
|
||||
}
|
||||
|
||||
fun verifyScratchLocationsReserved(zp: Zeropage) {
|
||||
// Verify scratch locations are not in free list
|
||||
zp.free.contains(zp.SCRATCH_B1) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_REG) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_W1) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_W1 + 1u) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_W2) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_W2 + 1u) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_PTR) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_PTR + 1u) shouldBe false
|
||||
}
|
||||
|
||||
fun verifyVirtualRegistersAllocated(zp: Zeropage, expected: Boolean) {
|
||||
if (expected) {
|
||||
// Check that virtual registers are allocated
|
||||
for (reg in 0..15) {
|
||||
zp.allocatedVariables shouldContainKey "cx16.r${reg}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// C64 Zeropage Tests
|
||||
// ============================================================================
|
||||
|
||||
context("C64Zeropage") {
|
||||
|
||||
test("DONTUSE should have no free locations") {
|
||||
val options = createOptions(C64Target(), ZeropageType.DONTUSE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
zp.free.shouldBeEmpty()
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("BASICSAFE should have specific free locations") {
|
||||
val options = createOptions(C64Target(), ZeropageType.BASICSAFE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// BASICSAFE includes specific BASIC-safe locations
|
||||
zp.free.isNotEmpty().shouldBeTrue()
|
||||
zp.free.size shouldBeGreaterThan 0
|
||||
|
||||
// Verify scratch locations are reserved
|
||||
verifyScratchLocationsReserved(zp)
|
||||
|
||||
// Virtual registers should be allocated for BASICSAFE
|
||||
verifyVirtualRegistersAllocated(zp, expected = false) // C64 doesn't allocate virtual regs for BASICSAFE
|
||||
}
|
||||
|
||||
test("KERNALSAFE should have larger free list than BASICSAFE") {
|
||||
val options = createOptions(C64Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
zp.free.isNotEmpty().shouldBeTrue()
|
||||
|
||||
// KERNALSAFE has more locations than BASICSAFE
|
||||
val basicsafeOptions = createOptions(C64Target(), ZeropageType.BASICSAFE)
|
||||
val basicsafeZp = C64Zeropage(basicsafeOptions)
|
||||
|
||||
zp.free.size shouldBeGreaterThan basicsafeZp.free.size
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
verifyVirtualRegistersAllocated(zp, expected = true) // KERNALSAFE gets virtual registers
|
||||
}
|
||||
|
||||
test("FLOATSAFE should exclude float-used locations") {
|
||||
val options = createOptions(C64Target(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
zp.free.isNotEmpty().shouldBeTrue()
|
||||
|
||||
// FLOATSAFE should have fewer locations than KERNALSAFE due to float exclusions
|
||||
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
|
||||
|
||||
zp.free.size shouldBeLessThan kernalsafeZp.free.size
|
||||
|
||||
// Some float-related locations should not be free (exact set depends on implementation)
|
||||
// Just verify the size difference is significant
|
||||
(kernalsafeZp.free.size - zp.free.size) shouldBeGreaterThan 10
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FULL should have maximum free locations minus IRQ") {
|
||||
val options = createOptions(C64Target(), ZeropageType.FULL)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// FULL should have the most free locations
|
||||
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
|
||||
|
||||
zp.free.size shouldBeGreaterThan kernalsafeZp.free.size
|
||||
|
||||
// IRQ locations should not be free
|
||||
zp.free.contains(0xa0u) shouldBe false
|
||||
zp.free.contains(0xa1u) shouldBe false
|
||||
zp.free.contains(0xa2u) shouldBe false
|
||||
zp.free.contains(0x91u) shouldBe false
|
||||
zp.free.contains(0xc0u) shouldBe false
|
||||
zp.free.contains(0xc5u) shouldBe false
|
||||
zp.free.contains(0xcbu) shouldBe false
|
||||
zp.free.contains(0xf5u) shouldBe false
|
||||
zp.free.contains(0xf6u) shouldBe false
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
verifyVirtualRegistersAllocated(zp, expected = true)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CX16 Zeropage Tests
|
||||
// ============================================================================
|
||||
|
||||
context("CX16Zeropage") {
|
||||
|
||||
test("DONTUSE should have no free locations") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
zp.free.shouldBeEmpty()
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("BASICSAFE should have 0x22-0x7F") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Virtual registers 0x02-0x21 should be allocated, not free
|
||||
for (addr in 0x02u..0x21u) {
|
||||
zp.free shouldNotBe addr
|
||||
}
|
||||
|
||||
// Free should be 0x22-0x7F minus scratch locations
|
||||
for (addr in 0x22u..0x7Fu) {
|
||||
if (addr !in setOf(zp.SCRATCH_B1, zp.SCRATCH_REG, zp.SCRATCH_W1, zp.SCRATCH_W1 + 1u,
|
||||
zp.SCRATCH_W2, zp.SCRATCH_W2 + 1u, zp.SCRATCH_PTR, zp.SCRATCH_PTR + 1u)) {
|
||||
zp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("KERNALSAFE should have 0x22-0x7F and 0xA9-0xFF") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
val basicsafeOptions = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
|
||||
val basicsafeZp = CX16Zeropage(basicsafeOptions)
|
||||
|
||||
zp.free.size shouldBeGreaterThan basicsafeZp.free.size
|
||||
|
||||
// Should include high memory range
|
||||
zp.free.contains(0xa9u) shouldBe true
|
||||
zp.free.contains(0xffu) shouldBe true
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FLOATSAFE should have 0x22-0x7F and 0xD4-0xFF") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Should include 0xD4-0xFF range
|
||||
zp.free.contains(0xd4u) shouldBe true
|
||||
zp.free.contains(0xffu) shouldBe true
|
||||
|
||||
// FLOATSAFE has different high range than KERNALSAFE (0xD4-0xFF vs 0xA9-0xFF)
|
||||
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
|
||||
|
||||
// FLOATSAFE should have fewer locations than KERNALSAFE (smaller high range)
|
||||
zp.free.size shouldBeLessThan kernalsafeZp.free.size
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FULL should have 0x22-0xFF") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.FULL)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// FULL should have the most free locations
|
||||
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
|
||||
|
||||
zp.free.size shouldBeGreaterThan kernalsafeZp.free.size
|
||||
|
||||
// Should include all locations from 0x22 to 0xFF (minus scratch)
|
||||
for (addr in 0x80u..0xA8u) {
|
||||
if (addr !in setOf(zp.SCRATCH_B1, zp.SCRATCH_REG, zp.SCRATCH_W1, zp.SCRATCH_W1 + 1u,
|
||||
zp.SCRATCH_W2, zp.SCRATCH_W2 + 1u, zp.SCRATCH_PTR, zp.SCRATCH_PTR + 1u)) {
|
||||
zp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PET Zeropage Tests
|
||||
// ============================================================================
|
||||
|
||||
context("PETZeropage") {
|
||||
|
||||
test("DONTUSE should have no free locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.DONTUSE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
zp.free.shouldBeEmpty()
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("BASICSAFE should have minimal free locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
|
||||
// and $fb-$fe. After scratch removal, $fb-$fe remain usable.
|
||||
zp.free.size shouldBeLessThan 20
|
||||
zp.free.contains(0xfbu) shouldBe true
|
||||
zp.free.contains(0xfcu) shouldBe true
|
||||
zp.free.contains(0xfdu) shouldBe true
|
||||
zp.free.contains(0xfeu) shouldBe true
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FLOATSAFE should be same as BASICSAFE") {
|
||||
val floatOptions = createOptions(PETTarget(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val floatZp = PETZeropage(floatOptions)
|
||||
|
||||
val basicOptions = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val basicZp = PETZeropage(basicOptions)
|
||||
|
||||
// FLOATSAFE and BASICSAFE should have same free locations for PET
|
||||
floatZp.free.size shouldBe basicZp.free.size
|
||||
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
|
||||
floatZp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
test("KERNALSAFE should exclude IRQ locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.KERNALSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// IRQ locations should not be free
|
||||
zp.free.contains(0x8du) shouldBe false
|
||||
zp.free.contains(0x8eu) shouldBe false
|
||||
zp.free.contains(0x8fu) shouldBe false
|
||||
zp.free.contains(0x97u) shouldBe false
|
||||
zp.free.contains(0x98u) shouldBe false
|
||||
zp.free.contains(0x99u) shouldBe false
|
||||
zp.free.contains(0x9au) shouldBe false
|
||||
zp.free.contains(0x9bu) shouldBe false
|
||||
zp.free.contains(0x9eu) shouldBe false
|
||||
zp.free.contains(0xa7u) shouldBe false
|
||||
zp.free.contains(0xa8u) shouldBe false
|
||||
zp.free.contains(0xa9u) shouldBe false
|
||||
zp.free.contains(0xaau) shouldBe false
|
||||
zp.free.contains(0xaaau) shouldBe false
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FULL should have all locations minus IRQ") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.FULL)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// FULL should have most locations
|
||||
zp.free.size shouldBeGreaterThan 100
|
||||
|
||||
// IRQ locations should not be free
|
||||
zp.free.contains(0x8du) shouldBe false
|
||||
zp.free.contains(0x97u) shouldBe false
|
||||
zp.free.contains(0xa7u) shouldBe false
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
verifyVirtualRegistersAllocated(zp, expected = true)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// C128 Zeropage Tests
|
||||
// ============================================================================
|
||||
|
||||
context("C128Zeropage") {
|
||||
|
||||
test("DONTUSE should have no free locations") {
|
||||
val options = createOptions(C128Target(), ZeropageType.DONTUSE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
zp.free.shouldBeEmpty()
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("BASICSAFE should have specific safe locations") {
|
||||
val options = createOptions(C128Target(), ZeropageType.BASICSAFE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
zp.free.isNotEmpty().shouldBeTrue()
|
||||
|
||||
// Should include some specific BASIC-safe locations (excluding scratch)
|
||||
// 0x0b is SCRATCH_PTR, 0x74 is SCRATCH_B1 for C128, so they're not free
|
||||
// Check for other locations that should be free
|
||||
zp.free.contains(0x0du) shouldBe true
|
||||
zp.free.contains(0x1bu) shouldBe true // In BASICSAFE range
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FLOATSAFE should be same as BASICSAFE for C128") {
|
||||
val floatOptions = createOptions(C128Target(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val floatZp = C128Zeropage(floatOptions)
|
||||
|
||||
val basicOptions = createOptions(C128Target(), ZeropageType.BASICSAFE)
|
||||
val basicZp = C128Zeropage(basicOptions)
|
||||
|
||||
// FLOATSAFE and BASICSAFE should have same free locations for C128
|
||||
floatZp.free.size shouldBe basicZp.free.size
|
||||
}
|
||||
|
||||
test("KERNALSAFE should have BASIC variables area") {
|
||||
val options = createOptions(C128Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
// Should include 0x0A-0x8F (BASIC variables area) minus scratch/virtual reg locations
|
||||
// 0x0a is where virtual registers start for C128, so not free
|
||||
// 0x8fu should be free
|
||||
zp.free.contains(0x8fu) shouldBe true
|
||||
|
||||
// Should include additional specific locations
|
||||
zp.free.contains(0x92u) shouldBe true
|
||||
// 0xb6u is SCRATCH_W1 for C128 - won't be free
|
||||
zp.free.contains(0x96u) shouldBe true // Alternative location
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FULL should have maximum locations minus IRQ") {
|
||||
val options = createOptions(C128Target(), ZeropageType.FULL)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
// FULL should have many locations (0x0A-0xFF minus IRQ and scratch)
|
||||
// 0x0a is where virtual registers start for C128, so not free
|
||||
zp.free.size shouldBeGreaterThan 150
|
||||
|
||||
// IRQ locations should not be free
|
||||
zp.free.contains(0x90u) shouldBe false
|
||||
zp.free.contains(0x91u) shouldBe false
|
||||
zp.free.contains(0xa0u) shouldBe false
|
||||
zp.free.contains(0xa1u) shouldBe false
|
||||
zp.free.contains(0xa2u) shouldBe false
|
||||
zp.free.contains(0xc0u) shouldBe false
|
||||
zp.free.contains(0xccu) shouldBe false
|
||||
zp.free.contains(0xcdu) shouldBe false
|
||||
zp.free.contains(0xd0u) shouldBe false
|
||||
zp.free.contains(0xd1u) shouldBe false
|
||||
zp.free.contains(0xd2u) shouldBe false
|
||||
zp.free.contains(0xd3u) shouldBe false
|
||||
zp.free.contains(0xd4u) shouldBe false
|
||||
zp.free.contains(0xd5u) shouldBe false
|
||||
zp.free.contains(0xf7u) shouldBe false
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
verifyVirtualRegistersAllocated(zp, expected = true)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cross-Target Comparison Tests
|
||||
// ============================================================================
|
||||
|
||||
context("Cross-Target Comparisons") {
|
||||
|
||||
test("all targets should have empty free list for DONTUSE") {
|
||||
// C64
|
||||
val c64Options = createOptions(C64Target(), ZeropageType.DONTUSE)
|
||||
C64Zeropage(c64Options).free.shouldBeEmpty()
|
||||
|
||||
// CX16
|
||||
val cx16Options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
|
||||
CX16Zeropage(cx16Options).free.shouldBeEmpty()
|
||||
|
||||
// PET
|
||||
val petOptions = createOptions(PETTarget(), ZeropageType.DONTUSE)
|
||||
PETZeropage(petOptions).free.shouldBeEmpty()
|
||||
|
||||
// C128
|
||||
val c128Options = createOptions(C128Target(), ZeropageType.DONTUSE)
|
||||
C128Zeropage(c128Options).free.shouldBeEmpty()
|
||||
}
|
||||
|
||||
test("scratch locations differ between targets") {
|
||||
val c64Options = createOptions(C64Target(), ZeropageType.BASICSAFE)
|
||||
val c64Zp = C64Zeropage(c64Options)
|
||||
|
||||
val cx16Options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
|
||||
val cx16Zp = CX16Zeropage(cx16Options)
|
||||
|
||||
val petOptions = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val petZp = PETZeropage(petOptions)
|
||||
|
||||
val c128Options = createOptions(C128Target(), ZeropageType.BASICSAFE)
|
||||
val c128Zp = C128Zeropage(c128Options)
|
||||
|
||||
// Each target has different scratch locations
|
||||
c64Zp.SCRATCH_B1 shouldBe 0x02u
|
||||
cx16Zp.SCRATCH_B1 shouldBe 0x7au
|
||||
petZp.SCRATCH_B1 shouldBe 0xb3u
|
||||
c128Zp.SCRATCH_B1 shouldBe 0x74u
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Exhaustive Zeropage Address Tests - All 256 locations
|
||||
// ============================================================================
|
||||
|
||||
context("Exhaustive C64 Zeropage Tests") {
|
||||
|
||||
test("DONTUSE - no addresses should be free") {
|
||||
val options = createOptions(C64Target(), ZeropageType.DONTUSE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
for (addr in 0x00u..0xFFu) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("BASICSAFE - exact free address set") {
|
||||
val options = createOptions(C64Target(), ZeropageType.BASICSAFE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// BASICSAFE: specific BASIC-safe locations plus virtual registers at 0x04-0x23
|
||||
// Scratch locations are removed from free list
|
||||
// Just verify key properties
|
||||
zp.free.size shouldBeGreaterThan 10
|
||||
|
||||
// Scratch locations should NOT be free
|
||||
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// Some known free locations
|
||||
zp.free.contains(0x06u) shouldBe true
|
||||
zp.free.contains(0x92u) shouldBe true
|
||||
}
|
||||
|
||||
test("KERNALSAFE - exact free address set") {
|
||||
val options = createOptions(C64Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// KERNALSAFE: larger set including more locations, plus virtual registers at 0x04-0x23
|
||||
// Scratch locations (0x02,0x03,0x04,0x05,0xFB,0xFC,0xFD,0xFE) are removed
|
||||
// Virtual registers 0x04-0x23 are allocated (not free)
|
||||
|
||||
// Just verify key properties since exact set is complex
|
||||
zp.free.size shouldBeGreaterThan 50
|
||||
|
||||
// IRQ locations should NOT be free
|
||||
setOf(0xA0u, 0xA1u, 0xA2u, 0x91u, 0xC0u, 0xC5u, 0xCBu, 0xF5u, 0xF6u).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// Scratch locations should NOT be free
|
||||
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("FLOATSAFE - excludes float-used locations") {
|
||||
val options = createOptions(C64Target(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// FLOATSAFE is KERNALSAFE minus float-used locations
|
||||
zp.free.size shouldBeGreaterThan 20
|
||||
|
||||
// FLOATSAFE should have fewer locations than KERNALSAFE
|
||||
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
|
||||
|
||||
zp.free.size shouldBeLessThan kernalsafeZp.free.size
|
||||
}
|
||||
|
||||
test("FULL - maximum free locations minus IRQ") {
|
||||
val options = createOptions(C64Target(), ZeropageType.FULL)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// FULL should have the most locations
|
||||
zp.free.size shouldBeGreaterThan 100
|
||||
|
||||
// IRQ locations should NOT be free
|
||||
setOf(0xA0u, 0xA1u, 0xA2u, 0x91u, 0xC0u, 0xC5u, 0xCBu, 0xF5u, 0xF6u).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// Scratch locations should NOT be free
|
||||
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("Exhaustive CX16 Zeropage Tests") {
|
||||
|
||||
test("DONTUSE - no addresses should be free") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
for (addr in 0x00u..0xFFu) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("BASICSAFE - 0x22-0x7F minus scratch and virtual registers") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Virtual registers 0x02-0x21 are allocated (not free)
|
||||
for (addr in 0x02u..0x21u) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// BASICSAFE: 0x22-0x7F minus scratch
|
||||
for (addr in 0x22u..0x7Fu) {
|
||||
val isScratch = addr in setOf(0x7Au, 0x7Bu, 0x7Cu, 0x7Du, 0x7Eu, 0x7Fu, 0x22u, 0x23u)
|
||||
zp.free.contains(addr) shouldBe !isScratch
|
||||
}
|
||||
|
||||
// Above 0x7F should not be free for BASICSAFE
|
||||
for (addr in 0x80u..0xFFu) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("KERNALSAFE - 0x22-0x7F and 0xA9-0xFF") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Virtual registers 0x02-0x21 are allocated (not free)
|
||||
for (addr in 0x02u..0x21u) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// Should include 0xA9-0xFF range
|
||||
zp.free.contains(0xA9u) shouldBe true
|
||||
zp.free.contains(0xFFu) shouldBe true
|
||||
}
|
||||
|
||||
test("FLOATSAFE - 0x22-0x7F and 0xD4-0xFF") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Should include 0xD4-0xFF range
|
||||
zp.free.contains(0xD4u) shouldBe true
|
||||
zp.free.contains(0xFFu) shouldBe true
|
||||
|
||||
// Should NOT include 0xA9-0xD3 range that KERNALSAFE has
|
||||
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
|
||||
|
||||
zp.free.size shouldBeLessThan kernalsafeZp.free.size
|
||||
}
|
||||
|
||||
test("FULL - 0x22-0xFF minus scratch") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.FULL)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Virtual registers 0x02-0x21 are allocated (not free)
|
||||
for (addr in 0x02u..0x21u) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// FULL should have most locations from 0x22-0xFF
|
||||
zp.free.size shouldBeGreaterThan 150
|
||||
}
|
||||
}
|
||||
|
||||
context("Exhaustive PET Zeropage Tests") {
|
||||
|
||||
test("DONTUSE - no addresses should be free") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.DONTUSE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
for (addr in 0x00u..0xFFu) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("BASICSAFE - extra fb-fe free after scratch removal") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
|
||||
// and $fb-$fe. After removing scratch registers from the pool,
|
||||
// only $fb-$fe remain free.
|
||||
zp.free.size shouldBeLessThan 15
|
||||
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
test("KERNALSAFE - excludes IRQ locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.KERNALSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// IRQ locations should NOT be free
|
||||
setOf(0x8Du, 0x8Eu, 0x8Fu, 0x97u, 0x98u, 0x99u, 0x9Au, 0x9Bu, 0x9Eu,
|
||||
0xA7u, 0xA8u, 0xA9u, 0xAAu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("FULL - all minus IRQ locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.FULL)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// FULL should have most locations
|
||||
zp.free.size shouldBeGreaterThan 150
|
||||
|
||||
// IRQ locations should NOT be free
|
||||
setOf(0x8Du, 0x97u, 0xA7u).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("Exhaustive C128 Zeropage Tests") {
|
||||
|
||||
test("DONTUSE - no addresses should be free") {
|
||||
val options = createOptions(C128Target(), ZeropageType.DONTUSE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
for (addr in 0x00u..0xFFu) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("BASICSAFE - specific safe locations") {
|
||||
val options = createOptions(C128Target(), ZeropageType.BASICSAFE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
// BASICSAFE has specific locations
|
||||
zp.free.size shouldBeGreaterThan 50
|
||||
|
||||
// Note: Virtual registers are NOT allocated for BASICSAFE on C128
|
||||
// (only for FULL and KERNALSAFE modes)
|
||||
}
|
||||
|
||||
test("KERNALSAFE - BASIC variables area 0x0A-0x8F") {
|
||||
val options = createOptions(C128Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
// Virtual registers at 0x0A-0x29 are allocated (not free)
|
||||
for (addr in 0x0Au..0x29u) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// Should include some locations in BASIC variables area
|
||||
zp.free.contains(0x8Fu) shouldBe true
|
||||
}
|
||||
|
||||
test("FULL - 0x0A-0xFF minus IRQ") {
|
||||
val options = createOptions(C128Target(), ZeropageType.FULL)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
// FULL should have many locations
|
||||
zp.free.size shouldBeGreaterThan 150
|
||||
|
||||
// IRQ locations should NOT be free
|
||||
setOf(0x90u, 0x91u, 0xA0u, 0xA1u, 0xA2u, 0xC0u, 0xCCu, 0xCDu,
|
||||
0xD0u, 0xD1u, 0xD2u, 0xD3u, 0xD4u, 0xD5u, 0xF7u).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Helper infix function for readability
|
||||
infix fun Int.shouldBeLessThan(value: Int) {
|
||||
if (this >= value) {
|
||||
throw AssertionError("Expected $this to be less than $value")
|
||||
}
|
||||
}
|
||||
@@ -5,39 +5,8 @@ plugins {
|
||||
dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
implementation(project(":simpleAst"))
|
||||
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
|
||||
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1")
|
||||
testImplementation("io.kotest:kotest-framework-datatest:5.9.1")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
setSrcDirs(listOf("$projectDir/src"))
|
||||
}
|
||||
resources {
|
||||
setSrcDirs(listOf("$projectDir/res"))
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
setSrcDirs(listOf("$projectDir/test"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn("cleanTest")
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events("skipped", "failed")
|
||||
}
|
||||
testImplementation(testFixtures(project(":codeCore")))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,12 +1,14 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.PtAsmSub
|
||||
import prog8.code.ast.PtSubroutineParameter
|
||||
import prog8.code.core.CombinedLongRegisters
|
||||
import prog8.code.core.Cx16VirtualRegisters
|
||||
import prog8.code.core.RegisterOrPair
|
||||
import prog8.code.core.RegisterOrStatusflag
|
||||
|
||||
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub, argComplexity: List<Boolean>): List<Int> {
|
||||
val order = mutableListOf<Int>()
|
||||
// order is:
|
||||
// 1) combined cx16 virtual registers (longs),
|
||||
@@ -15,6 +17,8 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
// 4) single CPU registers (order Y,X,A),
|
||||
// 5) floating point registers (FAC1, FAC2),
|
||||
// 6) CPU Carry status flag
|
||||
// Within each group, complex expressions are evaluated first (they may clobber registers,
|
||||
// so running them before simple expressions avoids needing to save/restore already-loaded values).
|
||||
val args = sub.parameters.withIndex()
|
||||
val (combinedLongRegs, args1) = args.partition { it.value.first.registerOrPair in CombinedLongRegisters }
|
||||
val (cx16regs, args2) = args1.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||
@@ -23,12 +27,17 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
val (singleRegsMixed, rest) = args3.partition { it.value.first.registerOrPair != null }
|
||||
val (singleCpuRegs, floatRegs) = singleRegsMixed.partition {it.value.first.registerOrPair != RegisterOrPair.FAC1 && it.value.first.registerOrPair != RegisterOrPair.FAC2 }
|
||||
|
||||
combinedLongRegs.forEach { order += it.index }
|
||||
cx16regs.forEach { order += it.index }
|
||||
pairedRegs.forEach { order += it.index }
|
||||
singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
|
||||
combinedLongRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
cx16regs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
pairedRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
singleCpuRegs
|
||||
.sortedWith(
|
||||
compareByDescending<IndexedValue<Pair<RegisterOrStatusflag, PtSubroutineParameter>>> { argComplexity[it.index] }
|
||||
.thenByDescending { it.value.first.registerOrPair!!.asCpuRegister() }
|
||||
)
|
||||
.forEach { order += it.index }
|
||||
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
|
||||
floatRegs.forEach { order += it.index }
|
||||
floatRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
rest.forEach { order += it.index }
|
||||
require(order.size==sub.parameters.size)
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -5,6 +5,7 @@ import prog8.code.StMemVar
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.ast.PtForLoop
|
||||
import prog8.code.ast.PtIdentifier
|
||||
import prog8.code.ast.PtNumber
|
||||
import prog8.code.ast.PtRange
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.absoluteValue
|
||||
@@ -33,21 +34,502 @@ internal class ForLoopsAsmGen(
|
||||
}
|
||||
|
||||
private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
if(range.step.asConstInteger()!! < -1) {
|
||||
val constStep = range.step.asConstInteger()
|
||||
if(constStep != null && constStep < -1) {
|
||||
val limit = range.to.asConstInteger()
|
||||
if(limit==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")
|
||||
}
|
||||
|
||||
if(range.step !is PtNumber) {
|
||||
translateForOverNonconstStepRange(stmt, iterableDt, range)
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
iterableDt.isByteArray -> forOverNonconstByteRange(stmt, iterableDt, range)
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> forOverNonconstWordRange(stmt, iterableDt, range)
|
||||
else -> throw AssemblyError("range expression can only be byte or word")
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray(asmgen.options.compTarget) -> forOverNonconstWordRange(stmt, iterableDt, range)
|
||||
iterableDt.isLongArray -> forOverNonconstLongRange(stmt, iterableDt, range)
|
||||
else -> throw AssemblyError("range expression can only be byte, word or long")
|
||||
}
|
||||
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun translateForOverNonconstStepRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
when {
|
||||
iterableDt.isByteArray -> forOverNonconstStepByteRange(stmt, iterableDt, range)
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray(asmgen.options.compTarget) -> forOverNonconstStepWordRange(stmt, iterableDt, range)
|
||||
iterableDt.isLongArray -> throw AssemblyError("variable-step for loops over long ranges are not supported by the legacy 6502 code generator at ${stmt.position}")
|
||||
else -> throw AssemblyError("range expression can only be byte, word or long")
|
||||
}
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun forOverNonconstStepByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val loopDt = iterableDt.elementType()
|
||||
val loopvar = asmgen.asmVariableName(stmt.variable)
|
||||
val toValueVar = asmgen.createTempVarReused(loopDt.base, false, range)
|
||||
val stepVar = asmgen.createTempVarReused(range.step.type.base, false, range)
|
||||
val nextVar = asmgen.createTempVarReused(loopDt.base, false, range)
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val descendingLabel = if(range.step.type.isSigned) asmgen.makeLabel("for_desc") else null
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
|
||||
// Keep range expression evaluation in source order and preserve each value across the next one.
|
||||
asmgen.assignExpressionToVariable(range.from, loopvar, loopDt)
|
||||
asmgen.assignExpressionToVariable(range.to, toValueVar, loopDt)
|
||||
asmgen.assignExpressionToVariable(range.step, stepVar, range.step.type)
|
||||
if (range.step.type.isSigned)
|
||||
asmgen.out(" lda $stepVar | beq $endLabel | bmi $descendingLabel")
|
||||
else
|
||||
asmgen.out(" lda $stepVar | beq $endLabel")
|
||||
|
||||
emitByteRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, true)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.statements)
|
||||
emitByteRangeUpdate(loopvar, toValueVar, stepVar, nextVar, endLabel, loopLabel, loopDt.isSigned, range.step.type.isSigned)
|
||||
|
||||
if(descendingLabel != null) {
|
||||
asmgen.out(descendingLabel)
|
||||
emitByteRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, false)
|
||||
asmgen.out(" jmp $loopLabel")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun emitByteRangePrecheck(loopvar: String, toValueVar: String, loopLabel: String, endLabel: String, signed: Boolean, ascending: Boolean) {
|
||||
val notEqualLabel = asmgen.makeLabel("for_not_equal")
|
||||
val compareDoneLabel = asmgen.makeLabel("for_compare_done")
|
||||
if (signed) {
|
||||
asmgen.out("""
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
bne $notEqualLabel
|
||||
jmp $loopLabel
|
||||
$notEqualLabel
|
||||
sec
|
||||
lda $loopvar
|
||||
sbc $toValueVar
|
||||
bvc $compareDoneLabel
|
||||
eor #$80
|
||||
$compareDoneLabel
|
||||
${if (ascending) "bpl" else "bmi"} $endLabel
|
||||
""")
|
||||
} else if (ascending) {
|
||||
asmgen.out("""
|
||||
lda $toValueVar
|
||||
cmp $loopvar
|
||||
bcc $endLabel
|
||||
jmp $loopLabel
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
bcc $endLabel
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitByteRangeUpdate(
|
||||
loopvar: String,
|
||||
toValueVar: String,
|
||||
stepVar: String,
|
||||
nextVar: String,
|
||||
endLabel: String,
|
||||
loopLabel: String,
|
||||
loopvarSigned: Boolean,
|
||||
stepSigned: Boolean
|
||||
) {
|
||||
val notLastLabel = asmgen.makeLabel("for_not_last")
|
||||
val ascendingCompareDoneLabel = asmgen.makeLabel("for_next_compare_done")
|
||||
val descendingCompareDoneLabel = if(stepSigned) asmgen.makeLabel("for_desc_compare_done") else null
|
||||
val descendingUpdateLabel = if(stepSigned) asmgen.makeLabel("for_desc_update") else null
|
||||
val storeNextLabel = asmgen.makeLabel("for_store_next")
|
||||
val ascendingWrapBranch = if (loopvarSigned) "bvs" else "bcs"
|
||||
val descendingWrapBranch = if (loopvarSigned) "bvs" else "bcc"
|
||||
asmgen.out("""
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
bne $notLastLabel
|
||||
jmp $endLabel
|
||||
$notLastLabel
|
||||
""")
|
||||
if (stepSigned)
|
||||
asmgen.out(" lda $stepVar | bmi $descendingUpdateLabel")
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $loopvar
|
||||
adc $stepVar
|
||||
$ascendingWrapBranch $endLabel
|
||||
sta $nextVar
|
||||
""")
|
||||
asmgen.out("""
|
||||
lda $nextVar
|
||||
cmp $toValueVar
|
||||
beq $storeNextLabel
|
||||
sec
|
||||
lda $nextVar
|
||||
sbc $toValueVar
|
||||
bvc $ascendingCompareDoneLabel
|
||||
eor #$80
|
||||
$ascendingCompareDoneLabel
|
||||
""")
|
||||
if (loopvarSigned)
|
||||
asmgen.out(" bpl $endLabel")
|
||||
else
|
||||
asmgen.out(" bcs $endLabel")
|
||||
if(stepSigned) {
|
||||
asmgen.out("""
|
||||
jmp $storeNextLabel
|
||||
|
||||
$descendingUpdateLabel
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
beq $endLabel
|
||||
clc
|
||||
lda $loopvar
|
||||
adc $stepVar
|
||||
$descendingWrapBranch $endLabel
|
||||
sta $nextVar
|
||||
""")
|
||||
if (loopvarSigned)
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $nextVar
|
||||
sbc $toValueVar
|
||||
bvc $descendingCompareDoneLabel
|
||||
eor #$80
|
||||
$descendingCompareDoneLabel
|
||||
bmi $endLabel
|
||||
""")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $nextVar
|
||||
cmp $toValueVar
|
||||
bcc $endLabel
|
||||
""")
|
||||
}
|
||||
asmgen.out("""
|
||||
jmp $storeNextLabel
|
||||
|
||||
$storeNextLabel
|
||||
lda $nextVar
|
||||
sta $loopvar
|
||||
jmp $loopLabel
|
||||
""")
|
||||
}
|
||||
|
||||
private fun forOverNonconstStepWordRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val loopDt = iterableDt.elementType()
|
||||
val loopvar = asmgen.asmVariableName(stmt.variable)
|
||||
val toValueVar = asmgen.createTempVarReused(loopDt.base, false, range)
|
||||
val stepVar = asmgen.createTempVarReused(range.step.type.base, false, range)
|
||||
val nextVar = asmgen.createTempVarReused(loopDt.base, false, range)
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val descendingLabel = if(range.step.type.isSigned) asmgen.makeLabel("for_desc") else null
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
|
||||
// Keep range expression evaluation in source order and preserve each value across the next one.
|
||||
asmgen.assignExpressionToVariable(range.from, loopvar, loopDt)
|
||||
asmgen.assignExpressionToVariable(range.to, toValueVar, loopDt)
|
||||
asmgen.assignExpressionToVariable(range.step, stepVar, range.step.type)
|
||||
if (range.step.type.isSigned)
|
||||
asmgen.out(" lda $stepVar | ora ${stepVar}+1 | beq $endLabel | lda ${stepVar}+1 | bmi $descendingLabel")
|
||||
else
|
||||
asmgen.out(" lda $stepVar | ora ${stepVar}+1 | beq $endLabel")
|
||||
|
||||
emitWordRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, true)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.statements)
|
||||
emitWordRangeUpdate(loopvar, toValueVar, stepVar, nextVar, endLabel, loopLabel, loopDt.isSigned, range.step.type.isSigned)
|
||||
|
||||
if(descendingLabel != null) {
|
||||
asmgen.out(descendingLabel)
|
||||
emitWordRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, false)
|
||||
asmgen.out(" jmp $loopLabel")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun emitWordRangePrecheck(loopvar: String, toValueVar: String, loopLabel: String, endLabel: String, signed: Boolean, ascending: Boolean) {
|
||||
val notEqualLabel = asmgen.makeLabel("for_not_equal")
|
||||
val compareDoneLabel = asmgen.makeLabel("for_compare_done")
|
||||
if (signed) {
|
||||
asmgen.out("""
|
||||
lda $loopvar+1
|
||||
cmp $toValueVar+1
|
||||
bne $notEqualLabel
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
bne $notEqualLabel
|
||||
jmp $loopLabel
|
||||
$notEqualLabel
|
||||
sec
|
||||
lda $loopvar
|
||||
sbc $toValueVar
|
||||
lda $loopvar+1
|
||||
sbc $toValueVar+1
|
||||
bvc $compareDoneLabel
|
||||
eor #$80
|
||||
$compareDoneLabel
|
||||
${if (ascending) "bpl" else "bmi"} $endLabel
|
||||
""")
|
||||
} else if (ascending) {
|
||||
asmgen.out("""
|
||||
lda $toValueVar+1
|
||||
cmp $loopvar+1
|
||||
bcc $endLabel
|
||||
bne $loopLabel
|
||||
lda $toValueVar
|
||||
cmp $loopvar
|
||||
bcc $endLabel
|
||||
jmp $loopLabel
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $loopvar+1
|
||||
cmp $toValueVar+1
|
||||
bcc $endLabel
|
||||
bne $loopLabel
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
bcc $endLabel
|
||||
jmp $loopLabel
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitWordRangeUpdate(
|
||||
loopvar: String,
|
||||
toValueVar: String,
|
||||
stepVar: String,
|
||||
nextVar: String,
|
||||
endLabel: String,
|
||||
loopLabel: String,
|
||||
loopvarSigned: Boolean,
|
||||
stepSigned: Boolean
|
||||
) {
|
||||
val notLastLabel = asmgen.makeLabel("for_not_last")
|
||||
val ascendingCompareLabel = asmgen.makeLabel("for_next_compare")
|
||||
val ascendingCompareDoneLabel = asmgen.makeLabel("for_next_compare_done")
|
||||
val descendingCompareDoneLabel = if(stepSigned) asmgen.makeLabel("for_desc_compare_done") else null
|
||||
val descendingUpdateLabel = if(stepSigned) asmgen.makeLabel("for_desc_update") else null
|
||||
val descendingNotLastLabel = if(stepSigned) asmgen.makeLabel("for_desc_not_last") else null
|
||||
val storeNextLabel = asmgen.makeLabel("for_store_next")
|
||||
val ascendingWrapBranch = if (loopvarSigned) "bvs" else "bcs"
|
||||
val descendingWrapBranch = if (loopvarSigned) "bvs" else "bcc"
|
||||
asmgen.out("""
|
||||
lda $loopvar+1
|
||||
cmp $toValueVar+1
|
||||
bne $notLastLabel
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
beq $endLabel
|
||||
$notLastLabel
|
||||
""")
|
||||
if (stepSigned)
|
||||
asmgen.out(" lda ${stepVar}+1 | bmi $descendingUpdateLabel")
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $loopvar
|
||||
adc $stepVar
|
||||
sta $nextVar
|
||||
lda $loopvar+1
|
||||
adc $stepVar+1
|
||||
$ascendingWrapBranch $endLabel
|
||||
sta $nextVar+1
|
||||
""")
|
||||
asmgen.out("""
|
||||
lda $nextVar+1
|
||||
cmp $toValueVar+1
|
||||
bne $ascendingCompareLabel
|
||||
lda $nextVar
|
||||
cmp $toValueVar
|
||||
beq $storeNextLabel
|
||||
$ascendingCompareLabel
|
||||
sec
|
||||
lda $nextVar
|
||||
sbc $toValueVar
|
||||
lda $nextVar+1
|
||||
sbc $toValueVar+1
|
||||
bvc $ascendingCompareDoneLabel
|
||||
eor #$80
|
||||
$ascendingCompareDoneLabel
|
||||
""")
|
||||
if (loopvarSigned)
|
||||
asmgen.out(" bpl $endLabel")
|
||||
else
|
||||
asmgen.out(" bcs $endLabel")
|
||||
if(stepSigned) {
|
||||
asmgen.out("""
|
||||
jmp $storeNextLabel
|
||||
|
||||
$descendingUpdateLabel
|
||||
lda $loopvar+1
|
||||
cmp $toValueVar+1
|
||||
bne $descendingNotLastLabel
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
beq $endLabel
|
||||
$descendingNotLastLabel
|
||||
clc
|
||||
lda $loopvar
|
||||
adc $stepVar
|
||||
sta $nextVar
|
||||
lda $loopvar+1
|
||||
adc $stepVar+1
|
||||
$descendingWrapBranch $endLabel
|
||||
sta $nextVar+1
|
||||
""")
|
||||
if (loopvarSigned)
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $nextVar
|
||||
sbc $toValueVar
|
||||
lda $nextVar+1
|
||||
sbc $toValueVar+1
|
||||
bvc $descendingCompareDoneLabel
|
||||
eor #$80
|
||||
$descendingCompareDoneLabel
|
||||
bmi $endLabel
|
||||
""")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $nextVar+1
|
||||
cmp $toValueVar+1
|
||||
bcc $endLabel
|
||||
bne $storeNextLabel
|
||||
lda $nextVar
|
||||
cmp $toValueVar
|
||||
bcc $endLabel
|
||||
""")
|
||||
}
|
||||
else asmgen.out(" jmp $storeNextLabel")
|
||||
asmgen.out("""
|
||||
$storeNextLabel
|
||||
lda $nextVar
|
||||
sta $loopvar
|
||||
lda $nextVar+1
|
||||
sta $loopvar+1
|
||||
jmp $loopLabel
|
||||
""")
|
||||
}
|
||||
|
||||
private fun forOverNonconstLongRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
|
||||
asmgen.assignExpressionToVariable(range.from, varname, iterableDt.elementType())
|
||||
|
||||
if (stepsize == 1 || stepsize == -1)
|
||||
forOverLongsRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
|
||||
else
|
||||
forOverLongsRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
|
||||
}
|
||||
|
||||
private fun forOverLongsRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
|
||||
// Use temporary variable to store the limit
|
||||
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
|
||||
asmgen.assignExpressionToVariable(range.to, toValueVar, iterableDt.elementType())
|
||||
|
||||
if (stepsize == 1) {
|
||||
// Increment toValueVar to make the loop inclusive (0 to N runs N+1 times)
|
||||
asmgen.out("""
|
||||
inc $toValueVar
|
||||
bne +
|
||||
inc $toValueVar+1
|
||||
bne +
|
||||
inc $toValueVar+2
|
||||
bne +
|
||||
inc $toValueVar+3
|
||||
+
|
||||
""")
|
||||
} else {
|
||||
// Decrement toValueVar to make the loop inclusive (N downto 0 runs N+1 times)
|
||||
val label1 = asmgen.makeLabel("dec_1")
|
||||
val label2 = asmgen.makeLabel("dec_2")
|
||||
val label3 = asmgen.makeLabel("dec_3")
|
||||
asmgen.out("""
|
||||
lda $toValueVar
|
||||
bne $label1
|
||||
lda $toValueVar+1
|
||||
bne $label2
|
||||
lda $toValueVar+2
|
||||
bne $label3
|
||||
dec $toValueVar+3
|
||||
$label3: dec $toValueVar+2
|
||||
$label2: dec $toValueVar+1
|
||||
$label1: dec $toValueVar
|
||||
""")
|
||||
}
|
||||
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(forloop.statements)
|
||||
|
||||
if (stepsize == 1) {
|
||||
asmgen.out("""
|
||||
inc $varname
|
||||
bne +
|
||||
inc $varname+1
|
||||
bne +
|
||||
inc $varname+2
|
||||
bne +
|
||||
inc $varname+3
|
||||
+ lda $varname
|
||||
cmp $toValueVar
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp $toValueVar+1
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp $toValueVar+2
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp $toValueVar+3
|
||||
bne $loopLabel
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
bne +++
|
||||
lda $varname+1
|
||||
bne ++
|
||||
lda $varname+2
|
||||
bne +
|
||||
dec $varname+3
|
||||
+ dec $varname+2
|
||||
+ dec $varname+1
|
||||
+ dec $varname
|
||||
lda $varname
|
||||
cmp $toValueVar
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp $toValueVar+1
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp $toValueVar+2
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp $toValueVar+3
|
||||
bne $loopLabel
|
||||
""")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun forOverLongsRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
|
||||
TODO("legacy 6502 codegen does not support long FOR ranges with step other than 1 or -1 at ${forloop.position}")
|
||||
}
|
||||
|
||||
private fun forOverNonconstByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
@@ -705,7 +1187,7 @@ $loopLabel sty $indexVar
|
||||
when {
|
||||
iterableDt.isString -> iterateStrings()
|
||||
iterableDt.isByteArray || iterableDt.isBoolArray -> iterateBytes()
|
||||
iterableDt.isSplitWordArray -> iterateSplitWords()
|
||||
iterableDt.isSplitWordArray(asmgen.options.compTarget) -> iterateSplitWords()
|
||||
iterableDt.isWordArray -> iterateWords()
|
||||
iterableDt.isLongArray -> iterateWords(true)
|
||||
iterableDt.isFloatArray -> throw AssemblyError("for loop with floating point variables is not supported")
|
||||
@@ -737,18 +1219,18 @@ $loopLabel sty $indexVar
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
when {
|
||||
iterableDt.isByteArray -> {
|
||||
// loop over byte range via loopvar, step >= 2 or <= -2
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
lda #${range.first}
|
||||
sta $varname
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
when (range.step) {
|
||||
0, 1, -1 -> {
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $range ${stmt.position}")
|
||||
1, -1 -> {
|
||||
throw AssemblyError("step 1 and -1 should have been handled specifically $range ${stmt.position}")
|
||||
}
|
||||
2 -> {
|
||||
if(range.last==255 || range.last==254) {
|
||||
@@ -803,12 +1285,11 @@ $loopLabel""")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> {
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray(asmgen.options.compTarget) -> {
|
||||
// loop over word range via loopvar, step >= 2 or <= -2
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
when (range.step) {
|
||||
0, 1, -1 -> {
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||
1, -1 -> {
|
||||
throw AssemblyError("step 1 and -1 should have been handled specifically $stmt")
|
||||
}
|
||||
else -> {
|
||||
// word, step >= 2 or <= -2
|
||||
@@ -826,7 +1307,6 @@ $loopLabel""")
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #>${range.last}
|
||||
bne +
|
||||
beq $endLabel
|
||||
+ lda $varname
|
||||
clc
|
||||
@@ -843,11 +1323,54 @@ $loopLabel""")
|
||||
iterableDt.isLongArray -> {
|
||||
// loop over long range via loopvar, step >= 2 or <= -2
|
||||
when(range.step) {
|
||||
0, 1, -1 -> {
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||
1, -1 -> {
|
||||
throw AssemblyError("step 1 and -1 should have been handled specifically $stmt")
|
||||
}
|
||||
else -> {
|
||||
TODO("for long range. Report this error please, and use word loops in the meantime ${stmt.position}")
|
||||
// long, step >= 2 or <= -2
|
||||
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
|
||||
val from = range.first.toLongHex()
|
||||
val to = range.last.toLongHex()
|
||||
val step = range.step.toLongHex()
|
||||
asmgen.out("""
|
||||
lda #$${from.substring(6, 8)}
|
||||
sta $varname
|
||||
lda #$${from.substring(4, 6)}
|
||||
sta $varname+1
|
||||
lda #$${from.substring(2, 4)}
|
||||
sta $varname+2
|
||||
lda #$${from.take(2)}
|
||||
sta $varname+3
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #$${to.substring(6, 8)}
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #$${to.substring(4, 6)}
|
||||
bne +
|
||||
lda $varname+2
|
||||
cmp #$${to.substring(2, 4)}
|
||||
bne +
|
||||
lda $varname+3
|
||||
cmp #$${to.take(2)}
|
||||
beq $endLabel
|
||||
+ lda $varname
|
||||
clc
|
||||
adc #$${step.substring(6, 8)}
|
||||
sta $varname
|
||||
lda $varname+1
|
||||
adc #$${step.substring(4, 6)}
|
||||
sta $varname+1
|
||||
lda $varname+2
|
||||
adc #$${step.substring(2, 4)}
|
||||
sta $varname+2
|
||||
lda $varname+3
|
||||
adc #$${step.take(2)}
|
||||
sta $varname+3""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -933,16 +1456,15 @@ $loopLabel""")
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #<${range.last}
|
||||
inc $varname
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #>${range.last}
|
||||
beq $endLabel
|
||||
+ inc $varname
|
||||
inc $varname+1
|
||||
+ lda $varname
|
||||
cmp #<${range.last+1}
|
||||
bne $loopLabel
|
||||
inc $varname+1""")
|
||||
asmgen.jmp(loopLabel)
|
||||
lda $varname+1
|
||||
cmp #>${range.last+1}
|
||||
bne $loopLabel""")
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
@@ -963,36 +1485,123 @@ $loopLabel""")
|
||||
if(range.last==0) {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
bne ++
|
||||
lda $varname+1
|
||||
beq $endLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #<${range.last}
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #>${range.last}
|
||||
beq $endLabel""")
|
||||
}
|
||||
asmgen.out("""
|
||||
+ lda $varname
|
||||
bne +
|
||||
dec $varname+1
|
||||
+ dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
+ dec $varname
|
||||
lda $varname
|
||||
cmp #<${range.last-1}
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp #>${range.last-1}
|
||||
bne $loopLabel""")
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun translateForSimpleLongRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||
require(range.step==1)
|
||||
TODO("for long range step +1. Report this error please, and use nested word loops in the meantime ${stmt.position}")
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
val from = range.first.toLongHex()
|
||||
val toPlus1 = (range.last + 1).toLongHex()
|
||||
asmgen.out("""
|
||||
lda #$${from.substring(6, 8)}
|
||||
sta $varname
|
||||
lda #$${from.substring(4, 6)}
|
||||
sta $varname+1
|
||||
lda #$${from.substring(2, 4)}
|
||||
sta $varname+2
|
||||
lda #$${from.take(2)}
|
||||
sta $varname+3
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
inc $varname
|
||||
bne +
|
||||
inc $varname+1
|
||||
bne +
|
||||
inc $varname+2
|
||||
bne +
|
||||
inc $varname+3
|
||||
+ lda $varname
|
||||
cmp #$${toPlus1.substring(6, 8)}
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp #$${toPlus1.substring(4, 6)}
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp #$${toPlus1.substring(2, 4)}
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp #$${toPlus1.take(2)}
|
||||
bne $loopLabel""")
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun translateForSimpleLongRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||
require(range.step==-1)
|
||||
TODO("for long range step -1. Report this error please, and use nested word loops in the meantime ${stmt.position}")
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
val from = range.first.toLongHex()
|
||||
val toMinus1 = (range.last - 1).toLongHex()
|
||||
asmgen.out("""
|
||||
lda #$${from.substring(6, 8)}
|
||||
sta $varname
|
||||
lda #$${from.substring(4, 6)}
|
||||
sta $varname+1
|
||||
lda #$${from.substring(2, 4)}
|
||||
sta $varname+2
|
||||
lda #$${from.take(2)}
|
||||
sta $varname+3
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
if(range.last==0) {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
bne ++++
|
||||
lda $varname+1
|
||||
bne +++
|
||||
lda $varname+2
|
||||
bne ++
|
||||
lda $varname+3
|
||||
beq $endLabel""")
|
||||
}
|
||||
asmgen.out("""
|
||||
+ lda $varname
|
||||
bne +++
|
||||
lda $varname+1
|
||||
bne ++
|
||||
lda $varname+2
|
||||
bne +
|
||||
dec $varname+3
|
||||
+ dec $varname+2
|
||||
+ dec $varname+1
|
||||
+ dec $varname
|
||||
lda $varname
|
||||
cmp #$${toMinus1.substring(6, 8)}
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp #$${toMinus1.substring(4, 6)}
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp #$${toMinus1.substring(2, 4)}
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp #$${toMinus1.take(2)}
|
||||
bne $loopLabel""")
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
|
||||
|
||||
@@ -9,6 +9,16 @@ import prog8.codegen.cpu6502.assignment.AsmAssignment
|
||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||
|
||||
|
||||
/*
|
||||
* CRITICAL: `inline asmsub` subroutines MUST be inlined at the call site.
|
||||
* They are NOT called with jsr - their assembly body is copied directly into the caller.
|
||||
* This means: no .proc/.pend, no rts, no jsr. The return value is left in the appropriate
|
||||
* CPU register (A/X/Y) and the caller picks it up.
|
||||
*
|
||||
* This is handled at line 68: `if (sub.inline) { ... }`.
|
||||
* Any new codegen backend MUST replicate this behavior.
|
||||
*/
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
|
||||
|
||||
internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
|
||||
@@ -33,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// 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 symbol = asmgen.symbolTable.lookup(call.name)!!
|
||||
val symbol = asmgen.symbolTable.lookup(call.name) ?: asmgen.symbolTable.lookupUnscoped(call.name) ?: throw IllegalStateException("Symbol not found: ${call.name}")
|
||||
if(symbol.type == StNodeType.LABEL) {
|
||||
require(call.void)
|
||||
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedNameString)}")
|
||||
@@ -44,6 +54,26 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
val subAsmName = asmgen.asmSymbolName(call.name)
|
||||
|
||||
if(sub is PtAsmSub) {
|
||||
val varBank = sub.address?.varbank
|
||||
var varbank: String? = null
|
||||
var bankFixLabel: String? = null
|
||||
if (varBank != null) {
|
||||
// the bank variable can also be the name of a subroutine that should be called to return the bank byte dynamically
|
||||
val bankSymbol = asmgen.symbolTable.lookup(varBank.name) ?: asmgen.symbolTable.lookupUnscoped(varBank.name)
|
||||
if (bankSymbol?.type == StNodeType.SUBROUTINE || bankSymbol?.type == StNodeType.EXTSUB) {
|
||||
val extsubId = asmgen.extsubCallSiteIds[sub]
|
||||
if (extsubId != null) {
|
||||
asmgen.out(" lda #${extsubId.toInt()}")
|
||||
}
|
||||
asmgen.out(" jsr ${asmgen.asmSymbolName(varBank.name)}")
|
||||
// store bank byte directly into the JSRFAR instruction data (forward label reference)
|
||||
bankFixLabel = asmgen.makeLabel("bank")
|
||||
asmgen.out(" sta $bankFixLabel")
|
||||
} else {
|
||||
varbank = asmgen.asmVariableName(varBank)
|
||||
}
|
||||
}
|
||||
|
||||
argumentsViaRegisters(sub, call)
|
||||
if (sub.inline) {
|
||||
// inline the subroutine. (regardless of optimization settings!)
|
||||
@@ -56,54 +86,57 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
} else {
|
||||
val bank = sub.address?.constbank?.toString()
|
||||
if(bank==null) {
|
||||
val varbank = if(sub.address?.varbank==null) null else asmgen.asmVariableName(sub.address!!.varbank!!)
|
||||
if(varbank!=null) {
|
||||
if(varbank!=null || bankFixLabel!=null) {
|
||||
if(asmgen.options.romable)
|
||||
TODO("no codegen yet for non-const bank in subroutine call that's usable in ROM ${call.position}")
|
||||
|
||||
// variable bank case: save/restore registers around the self-modifying sta
|
||||
if(varbank!=null) {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp""")
|
||||
}
|
||||
val label = bankFixLabel ?: "+"
|
||||
|
||||
// self-modifying code: set jsrfar bank argument
|
||||
when(asmgen.options.compTarget.name) {
|
||||
"cx16" -> {
|
||||
// JSRFAR can jump to a banked RAM address as well!
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr cx16.JSRFAR
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
"c64" -> {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr c64.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
"c128" -> {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr c128.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
else -> {
|
||||
if(asmgen.options.compTarget.supportsBankedCalls) {
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
asmgen.out("""
|
||||
jsr $targetName.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
} else
|
||||
throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
@@ -133,7 +166,17 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
.byte $bank"""
|
||||
)
|
||||
}
|
||||
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
else -> {
|
||||
if(asmgen.options.compTarget.supportsBankedCalls) {
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
asmgen.out("""
|
||||
jsr $targetName.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
.byte $bank"""
|
||||
)
|
||||
} else
|
||||
throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,7 +246,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
|
||||
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
|
||||
return when(arg) {
|
||||
is PtBuiltinFunctionCall -> {
|
||||
is PtFunctionCall if arg.builtin -> {
|
||||
if (arg.name in setOf("lsb", "msb", "lsw", "msw"))
|
||||
usesOtherRegistersWhileEvaluating(arg.args[0])
|
||||
else if (arg.name == "mkword" || arg.name == "mklong2")
|
||||
@@ -234,7 +277,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
if(sub.parameters.size==1) {
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
|
||||
} else {
|
||||
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
|
||||
val argComplexity = call.args.map { usesOtherRegistersWhileEvaluating(it) }
|
||||
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub, argComplexity)
|
||||
optimalEvalOrder.forEach {
|
||||
val param = sub.parameters[it]
|
||||
val arg = call.args[it]
|
||||
@@ -268,8 +312,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
|
||||
val reg = parameter.register
|
||||
if(reg!=null) {
|
||||
require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val varName = "cx16.${reg.name.lowercase()}"
|
||||
require(reg in Cx16VirtualRegisters || reg in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val varName = reg.asScopedNameVirtualReg(value.type).joinToString(".")
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.type)
|
||||
} else {
|
||||
val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name)
|
||||
@@ -333,7 +377,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// we need to sign extend the source, do this via temporary word variable
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE)
|
||||
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type.base)
|
||||
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, Position.DUMMY)
|
||||
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, value.position)
|
||||
} else {
|
||||
val scope = value.definingISub()
|
||||
val target: AsmAssignTarget =
|
||||
@@ -343,8 +387,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen)
|
||||
}
|
||||
val src = if(value.type.isPassByRef) {
|
||||
if(value is PtIdentifier) {
|
||||
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, Position.DUMMY)
|
||||
if(value is PtIdentifier || value is PtPointerDeref) {
|
||||
val addr = PtAddressOf(value.type.typeForUntypedAddressOf(false, program.memsizer), false, value.position)
|
||||
addr.add(value)
|
||||
addr.parent = scope as PtNode
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||
@@ -382,6 +426,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
if(argType.isUnsignedWord && paramType.isString)
|
||||
return true
|
||||
|
||||
if (argType.isArray && (paramType.isString || paramType.isPointerToByte))
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -173,6 +173,7 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" jsr p8_sys_startup.init_system")
|
||||
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
|
||||
}
|
||||
else -> error("Unsupported output type: ${compTarget.defaultOutputType}")
|
||||
}
|
||||
|
||||
if (options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
|
||||
@@ -223,20 +224,23 @@ internal class ProgramAndVarsGen(
|
||||
private fun footer() {
|
||||
asmgen.out(" .dsection STRUCTINSTANCES\n")
|
||||
|
||||
var relocateBssVars = false
|
||||
var relocateBssSlabs = false
|
||||
var relocatedBssStart = 0u
|
||||
var relocatedBssEnd = 0u
|
||||
var relocateVars = false
|
||||
var relocatedStart = 0u
|
||||
var relocatedEnd = 0u
|
||||
|
||||
if(options.varsGolden) {
|
||||
if(options.varsAddress != null) {
|
||||
relocateVars = true
|
||||
relocatedStart = options.varsAddress!!
|
||||
relocatedEnd = if(options.memtopAddress != 0u) options.memtopAddress else 0xFFFFu
|
||||
} else if(options.varsGolden) {
|
||||
if(options.compTarget.BSSGOLDENRAM_START == 0u ||
|
||||
options.compTarget.BSSGOLDENRAM_END == 0u ||
|
||||
options.compTarget.BSSGOLDENRAM_END <= options.compTarget.BSSGOLDENRAM_START) {
|
||||
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
|
||||
}
|
||||
relocateBssVars = true
|
||||
relocatedBssStart = options.compTarget.BSSGOLDENRAM_START
|
||||
relocatedBssEnd = options.compTarget.BSSGOLDENRAM_END
|
||||
relocateVars = true
|
||||
relocatedStart = options.compTarget.BSSGOLDENRAM_START
|
||||
relocatedEnd = options.compTarget.BSSGOLDENRAM_END
|
||||
}
|
||||
else if(options.varsHighBank!=null) {
|
||||
if(options.compTarget.BSSHIGHRAM_START == 0u ||
|
||||
@@ -244,67 +248,34 @@ internal class ProgramAndVarsGen(
|
||||
options.compTarget.BSSHIGHRAM_END <= options.compTarget.BSSHIGHRAM_START) {
|
||||
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
|
||||
}
|
||||
if(options.slabsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
|
||||
throw AssemblyError("slabs and vars high bank must be the same")
|
||||
relocateBssVars = true
|
||||
relocatedBssStart = options.compTarget.BSSHIGHRAM_START
|
||||
relocatedBssEnd = options.compTarget.BSSHIGHRAM_END
|
||||
}
|
||||
|
||||
if(options.slabsGolden) {
|
||||
if(options.compTarget.BSSGOLDENRAM_START == 0u ||
|
||||
options.compTarget.BSSGOLDENRAM_END == 0u ||
|
||||
options.compTarget.BSSGOLDENRAM_END <= options.compTarget.BSSGOLDENRAM_START) {
|
||||
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
|
||||
}
|
||||
relocateBssSlabs = true
|
||||
relocatedBssStart = options.compTarget.BSSGOLDENRAM_START
|
||||
relocatedBssEnd = options.compTarget.BSSGOLDENRAM_END
|
||||
}
|
||||
else if(options.slabsHighBank!=null) {
|
||||
if(options.compTarget.BSSHIGHRAM_START == 0u ||
|
||||
options.compTarget.BSSHIGHRAM_END == 0u ||
|
||||
options.compTarget.BSSHIGHRAM_END <= options.compTarget.BSSHIGHRAM_START) {
|
||||
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
|
||||
}
|
||||
if(options.varsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
|
||||
throw AssemblyError("slabs and vars high bank must be the same")
|
||||
relocateBssSlabs = true
|
||||
relocatedBssStart = options.compTarget.BSSHIGHRAM_START
|
||||
relocatedBssEnd = options.compTarget.BSSHIGHRAM_END
|
||||
relocateVars = true
|
||||
relocatedStart = options.compTarget.BSSHIGHRAM_START
|
||||
relocatedEnd = options.compTarget.BSSHIGHRAM_END
|
||||
}
|
||||
|
||||
asmgen.out("; bss sections")
|
||||
asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
|
||||
if(relocateBssVars) {
|
||||
if(!relocateBssSlabs)
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
if(relocateVars) {
|
||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||
asmgen.out(" * = ${relocatedBssStart.toHex()}")
|
||||
asmgen.out(" * = ${relocatedStart.toHex()}")
|
||||
asmgen.out(" .dsection BSS_NOCLEAR")
|
||||
asmgen.out("prog8_bss_section_start")
|
||||
asmgen.out(" .dsection BSS")
|
||||
if(relocateBssSlabs)
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many variables/data for BSS section\"")
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
asmgen.out(" .cerror * > ${relocatedEnd.toHex()}, \"too many variables/data for BSS section\"")
|
||||
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
|
||||
} else {
|
||||
asmgen.out(" .dsection BSS_NOCLEAR")
|
||||
asmgen.out("prog8_bss_section_start")
|
||||
asmgen.out(" .dsection BSS")
|
||||
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
|
||||
if(!relocateBssSlabs)
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||
if(relocateBssSlabs) {
|
||||
asmgen.out(" * = ${relocatedBssStart.toHex()}")
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for BSS_SLABS section\"")
|
||||
}
|
||||
}
|
||||
|
||||
if(relocatedBssEnd >= options.memtopAddress)
|
||||
options.memtopAddress = relocatedBssEnd+1u
|
||||
val effectiveEnd = if(relocateVars) relocatedEnd else 0u
|
||||
if(effectiveEnd >= options.memtopAddress && effectiveEnd != 0u)
|
||||
options.memtopAddress = effectiveEnd+1u
|
||||
|
||||
asmgen.out(" ; memtop check")
|
||||
asmgen.out(" .cerror * >= ${options.memtopAddress.toHex()}, \"Program too long by \", * - ${(options.memtopAddress-1u).toHex()}, \" bytes, memtop=${options.memtopAddress.toHex()}\"")
|
||||
@@ -352,7 +323,7 @@ internal class ProgramAndVarsGen(
|
||||
val varsInBlock = getVars(scope)
|
||||
|
||||
// Zeropage Variables
|
||||
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
|
||||
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.mapTo(mutableSetOf()) { it.value.scopedNameString }
|
||||
zeropagevars2asm(varnames)
|
||||
|
||||
// MemDefs and Consts
|
||||
@@ -372,13 +343,14 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
private fun asmTypeString(dt: DataType): String {
|
||||
val effectiveDt = if(dt.isArray) dt.elementType() else dt
|
||||
return when {
|
||||
dt.isBool || dt.isUnsignedByte -> ".byte"
|
||||
dt.isSignedByte -> ".char"
|
||||
dt.isUnsignedWord || dt.isPointer -> ".word"
|
||||
dt.isSignedWord -> ".sint"
|
||||
dt.isLong -> ".dint"
|
||||
dt.isFloat -> ".byte"
|
||||
effectiveDt.isBool || effectiveDt.isUnsignedByte -> ".byte"
|
||||
effectiveDt.isSignedByte -> ".char"
|
||||
effectiveDt.isUnsignedWord || effectiveDt.isPointer -> ".word"
|
||||
effectiveDt.isSignedWord -> ".sint"
|
||||
effectiveDt.isLong -> ".dint"
|
||||
effectiveDt.isFloat -> ".byte"
|
||||
else -> {
|
||||
throw AssemblyError("weird dt")
|
||||
}
|
||||
@@ -387,39 +359,56 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
private fun structInstances2asm() {
|
||||
|
||||
fun initValues(instance: StStructInstance): List<String> {
|
||||
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
|
||||
return structtype.fields.zip(instance.initialValues).map { (field, value) ->
|
||||
if(field.first.isFloat) {
|
||||
"["+compTarget.getFloatAsmBytes(value.number!!)+"]"
|
||||
} else {
|
||||
when {
|
||||
value.number!=null -> {
|
||||
if(field.first.isPointer)
|
||||
"$"+value.number!!.toInt().toString(16)
|
||||
else if(field.first.isInteger)
|
||||
value.number!!.toInt().toString()
|
||||
else
|
||||
value.number.toString()
|
||||
}
|
||||
value.addressOfSymbol!=null -> value.addressOfSymbol!!
|
||||
value.boolean!=null -> if(value.boolean==true) "1" else "0"
|
||||
else -> throw AssemblyError("weird struct initial value $value")
|
||||
}
|
||||
fun formatValue(value: StArrayElement, dt: DataType): String {
|
||||
if(dt.isFloat) {
|
||||
val numValue = (value as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for float field")
|
||||
return "["+compTarget.getFloatAsmBytes(numValue)+"]"
|
||||
}
|
||||
return when (value) {
|
||||
is StArrayElement.Number -> {
|
||||
if(dt.isPointer)
|
||||
"$"+value.value.toInt().toString(16)
|
||||
else if(dt.isInteger)
|
||||
value.value.toInt().toString()
|
||||
else
|
||||
value.value.toString()
|
||||
}
|
||||
is StArrayElement.AddressOf -> value.symbol
|
||||
is StArrayElement.MemorySlab -> "$StMemorySlabBlockName.${value.name}"
|
||||
is StArrayElement.BoolValue -> if(value.value) "1" else "0"
|
||||
is StArrayElement.StructInstance -> throw AssemblyError("struct instance in struct initializer")
|
||||
}
|
||||
}
|
||||
|
||||
fun flatInitValues(instance: StStructInstance, structtype: StStruct): List<String> {
|
||||
var valueIndex = 0
|
||||
return structtype.fields.flatMap { field ->
|
||||
val arraySz = field.arraySize
|
||||
val count = arraySz ?: 1
|
||||
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
|
||||
(0 until count).map {
|
||||
val value = instance.initialValues[valueIndex++]
|
||||
formatValue(value, elemDt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
asmgen.out("; struct types")
|
||||
symboltable.allStructTypes().distinctBy { it.name }.forEach { structtype ->
|
||||
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
|
||||
val paramFields = structtype.fields.filter { !it.isArray }
|
||||
val structargs = paramFields.indices.joinToString(",") { "f$it" }
|
||||
asmgen.out("${structtype.scopedNameString} .struct $structargs\n")
|
||||
structtype.fields.withIndex().forEach { (index, field) ->
|
||||
val dt = field.first
|
||||
val varname = "f${index}"
|
||||
val type = asmTypeString(dt)
|
||||
asmgen.out("p8v_${field.second} $type \\$varname") // note: struct field symbol prefixing done here because that is a lot simpler than fixing up all expressions in the AST
|
||||
var paramIdx = 0
|
||||
for(field in structtype.fields) {
|
||||
val type = asmTypeString(field.type)
|
||||
if(field.isArray) {
|
||||
val anonymous = List(field.arraySize!!) { "?" }.joinToString(",")
|
||||
asmgen.out("p8v_${field.name} $type $anonymous")
|
||||
} else {
|
||||
asmgen.out("p8v_${field.name} $type \\f$paramIdx")
|
||||
paramIdx++
|
||||
}
|
||||
}
|
||||
asmgen.out(" .endstruct\n")
|
||||
}
|
||||
@@ -430,14 +419,7 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("${StStructInstanceBlockName}_bss .block\n")
|
||||
instancesNoInit.forEach {
|
||||
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
|
||||
val zerovalues = structtype.fields.map { field ->
|
||||
if(field.first.isFloat) {
|
||||
val floatbytes = List(compTarget.memorySize(BaseDataType.FLOAT)) { "?" }
|
||||
"[${floatbytes.joinToString(",")}]"
|
||||
}
|
||||
else "?"
|
||||
}
|
||||
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
|
||||
asmgen.out("${it.name} .fill ${structtype.size} ; struct ${it.structName}\n")
|
||||
}
|
||||
asmgen.out(" .endblock\n")
|
||||
asmgen.out(" .send BSS\n")
|
||||
@@ -445,9 +427,28 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("; struct instances with initialization values\n")
|
||||
asmgen.out(" .section STRUCTINSTANCES\n")
|
||||
asmgen.out("$StStructInstanceBlockName .block\n")
|
||||
instances.forEach {
|
||||
val instancename = it.name.substringAfter('.')
|
||||
asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n")
|
||||
instances.forEach { instance ->
|
||||
val instancename = instance.name.substringAfter('.')
|
||||
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
|
||||
val values = flatInitValues(instance, structtype)
|
||||
val hasArrays = structtype.fields.any { fld -> fld.isArray }
|
||||
if(hasArrays) {
|
||||
asmgen.out(instancename)
|
||||
var valueIndex = 0
|
||||
for(field in structtype.fields) {
|
||||
val arraySz = field.arraySize
|
||||
val count = arraySz ?: 1
|
||||
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
|
||||
val type = asmTypeString(elemDt)
|
||||
val fieldValues = (0 until count).joinToString(",") {
|
||||
formatValue(instance.initialValues[valueIndex++], elemDt)
|
||||
}
|
||||
asmgen.out(" $type $fieldValues")
|
||||
}
|
||||
asmgen.out("\n")
|
||||
} else {
|
||||
asmgen.out("$instancename .dstruct ${instance.structName}, ${values.joinToString(",")}\n")
|
||||
}
|
||||
}
|
||||
asmgen.out(" .endblock\n")
|
||||
asmgen.out(" .send STRUCTINSTANCES\n")
|
||||
@@ -502,7 +503,7 @@ internal class ProgramAndVarsGen(
|
||||
val varsInSubroutine = getVars(scope)
|
||||
|
||||
// Zeropage Variables
|
||||
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
|
||||
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.mapTo(mutableSetOf()) { it.value.scopedNameString }
|
||||
zeropagevars2asm(varnames)
|
||||
|
||||
// MemDefs and Consts
|
||||
@@ -614,18 +615,26 @@ internal class ProgramAndVarsGen(
|
||||
arrayVarsWithInitInZp.forEach {
|
||||
val size = it.alloc.size
|
||||
val name = asmgen.asmVariableName(it.name)
|
||||
asmgen.out("""
|
||||
lda #<${name}_init_value
|
||||
ldy #>${name}_init_value
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
lda #<${name}
|
||||
ldy #>${name}
|
||||
sta cx16.r1
|
||||
sty cx16.r1+1
|
||||
lda #<$size
|
||||
ldy #>$size
|
||||
jsr sys.memcopy""")
|
||||
if(it.alloc.dt.isSplitWordArray(compTarget)) {
|
||||
val halfSize = size / 2
|
||||
if(halfSize>0) {
|
||||
asmgen.out("""
|
||||
ldx #${halfSize-1}
|
||||
- lda ${name}_init_value_lsb,x
|
||||
sta ${name}_lsb,x
|
||||
lda ${name}_init_value_msb,x
|
||||
sta ${name}_msb,x
|
||||
dex
|
||||
bpl -""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldx #${size-1}
|
||||
- lda ${name}_init_value,x
|
||||
sta ${name},x
|
||||
dex
|
||||
bpl -""")
|
||||
}
|
||||
}
|
||||
asmgen.out(" jmp +")
|
||||
}
|
||||
@@ -685,7 +694,7 @@ internal class ProgramAndVarsGen(
|
||||
if (scopedName.startsWith("cx16.r"))
|
||||
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
|
||||
val variable = symboltable.flat.getValue(scopedName) as StStaticVariable
|
||||
if(variable.dt.isSplitWordArray) {
|
||||
if(variable.dt.isSplitWordArray(compTarget)) {
|
||||
val lsbAddr = zpvar.address
|
||||
val msbAddr = zpvar.address + (zpvar.size/2).toUInt()
|
||||
asmgen.out("${scopedName.substringAfterLast('.')}_lsb \t= $lsbAddr \t; zp ${zpvar.dt} (lsbs)")
|
||||
@@ -739,7 +748,7 @@ internal class ProgramAndVarsGen(
|
||||
it.initializationStringValue!!.second,
|
||||
it.initializationStringValue!!.first
|
||||
)
|
||||
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
|
||||
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
|
||||
}
|
||||
alignedStrings.sortedBy { it.align }.forEach {
|
||||
outputStringvar(
|
||||
@@ -748,22 +757,22 @@ internal class ProgramAndVarsGen(
|
||||
it.initializationStringValue!!.second,
|
||||
it.initializationStringValue!!.first
|
||||
)
|
||||
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
|
||||
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
|
||||
}
|
||||
|
||||
notAlignedOther.sortedBy { it.type }.forEach {
|
||||
staticVariable2asm(it)
|
||||
if(it.dt.isArray)
|
||||
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
|
||||
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
|
||||
else
|
||||
asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY) // TODO print warning with position of the var
|
||||
asmgen.romableError("inlined variable (${it.dt} ${it.name})", it.astNode?.position ?: Position.DUMMY)
|
||||
}
|
||||
alignedOther.sortedBy { it.align }.sortedBy { it.type }.forEach {
|
||||
staticVariable2asm(it)
|
||||
if(it.dt.isArray)
|
||||
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
|
||||
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
|
||||
else
|
||||
asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY) // TODO print warning with position of the var
|
||||
asmgen.romableError("inlined variable (${it.dt} ${it.name})", it.astNode?.position ?: Position.DUMMY)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -777,16 +786,17 @@ internal class ProgramAndVarsGen(
|
||||
dt.isSignedWord -> asmgen.out("${variable.name}\t.sint ?")
|
||||
dt.isLong -> asmgen.out("${variable.name}\t.dint ?")
|
||||
dt.isFloat -> asmgen.out("${variable.name}\t.fill ${compTarget.FLOAT_MEM_SIZE}")
|
||||
dt.isSplitWordArray -> {
|
||||
dt.isSplitWordArray(compTarget) -> {
|
||||
alignVar(variable.align)
|
||||
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!.toInt()) / 2
|
||||
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf")
|
||||
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf")
|
||||
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf ; split word array of ${variable.length} ${variable.dt.elementType()} (lsbs)")
|
||||
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf ; split word array of ${variable.length} ${variable.dt.elementType()} (msbs)")
|
||||
}
|
||||
dt.isArray -> {
|
||||
alignVar(variable.align)
|
||||
val numbytes = compTarget.memorySize(variable.dt, variable.length!!.toInt())
|
||||
asmgen.out("${variable.name}\t.fill $numbytes")
|
||||
val amount = variable.length!!.toInt()
|
||||
val elementSize = compTarget.memorySize(variable.dt.elementType(), null)
|
||||
asmgen.out("${variable.name}\t.fill $amount * $elementSize ; array of $amount ${variable.dt.elementType()} ")
|
||||
}
|
||||
dt.isPointer -> asmgen.out("${variable.name}\t.word ?") // a pointer is just an uword address
|
||||
dt.isPointerArray -> {
|
||||
@@ -868,17 +878,17 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" .char " + chunk.joinToString())
|
||||
}
|
||||
}
|
||||
dt.isSplitWordArray -> {
|
||||
dt.isSplitWordArray(compTarget) -> {
|
||||
if(dt.elementType().isUnsignedWord || dt.elementType().isPointer) {
|
||||
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
|
||||
asmgen.out("_array_$varname := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
|
||||
asmgen.out("${varname}_msb\t.byte >_array_$varname")
|
||||
asmgen.out("${varname}_words := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
|
||||
asmgen.out("${varname}_msb\t.byte >${varname}_words")
|
||||
} else {
|
||||
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
|
||||
asmgen.out("_array_$varname := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
|
||||
asmgen.out("${varname}_msb\t.byte >_array_$varname")
|
||||
asmgen.out("${varname}_words := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
|
||||
asmgen.out("${varname}_msb\t.byte >${varname}_words")
|
||||
}
|
||||
}
|
||||
dt.isUnsignedWordArray -> {
|
||||
@@ -914,22 +924,64 @@ internal class ProgramAndVarsGen(
|
||||
dt.isFloatArray -> {
|
||||
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
||||
val floatFills = array.map {
|
||||
compTarget.getFloatAsmBytes(it.number!!)
|
||||
val numValue = (it as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for float array")
|
||||
compTarget.getFloatAsmBytes(numValue)
|
||||
}
|
||||
asmgen.out(varname)
|
||||
for (f in array.zip(floatFills))
|
||||
asmgen.out(" .byte ${f.second} ; float ${f.first}")
|
||||
}
|
||||
dt.base==BaseDataType.ARRAY && dt.sub==BaseDataType.STRUCT_INSTANCE -> {
|
||||
val struct = dt.subType as? StStruct ?: throw AssemblyError("struct type not found for $varname")
|
||||
val structSize = struct.size.toInt()
|
||||
if(value==null) {
|
||||
val totalBytes = (orNumberOfZeros ?: 0) * structSize
|
||||
if(totalBytes==0) {
|
||||
asmgen.out("$varname: .byte 0")
|
||||
} else {
|
||||
asmgen.out("$varname: .fill $totalBytes, 0")
|
||||
}
|
||||
} else {
|
||||
// initialized struct array: value contains StructInstance elements
|
||||
asmgen.out("$varname ; array of ${value.size} ${struct.name} structs")
|
||||
// expand struct field types for per-element handling
|
||||
val expandedFieldTypes = struct.fields.flatMap { field ->
|
||||
val arraySz = field.arraySize
|
||||
if(arraySz!=null) List(arraySz) { field.type } else listOf(field.type)
|
||||
}
|
||||
for(elt in value) {
|
||||
val instanceName = (elt as? StArrayElement.StructInstance)?.name ?: throw AssemblyError("expected struct instance in array init for $varname")
|
||||
val instance = symboltable.lookup(instanceName) as? StStructInstance ?: throw AssemblyError("struct instance $instanceName not found")
|
||||
val fieldValues = instance.initialValues
|
||||
require(fieldValues.size==expandedFieldTypes.size) { "field count mismatch for $instanceName" }
|
||||
for((fieldType, fieldVal) in expandedFieldTypes.zip(fieldValues)) {
|
||||
val num = when(fieldVal) {
|
||||
is StArrayElement.Number -> fieldVal.value
|
||||
is StArrayElement.BoolValue -> if(fieldVal.value) 1.0 else 0.0
|
||||
else -> throw AssemblyError("struct field value must be number for $varname (got $fieldVal)")
|
||||
}
|
||||
when {
|
||||
fieldType.isByteOrBool -> asmgen.out(" .byte ${num.toInt()}")
|
||||
fieldType.isWord -> asmgen.out(" .word ${num.toInt()}")
|
||||
fieldType.isLong -> asmgen.out(" .dword ${num.toInt()}")
|
||||
fieldType.isFloat -> {
|
||||
val bytes = compTarget.getFloatAsmBytes(num)
|
||||
asmgen.out(" .byte $bytes ; float $num")
|
||||
}
|
||||
fieldType.isPointer -> asmgen.out(" .word ${num.toInt()} ; pointer")
|
||||
else -> throw AssemblyError("unsupported struct field type $fieldType")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("require array dt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun zeroFilledArray(numElts: Int): StArray {
|
||||
val values = mutableListOf<StArrayElement>()
|
||||
repeat(numElts) {
|
||||
values.add(StArrayElement(0.0, null, null,null,null))
|
||||
}
|
||||
return values
|
||||
return List(numElts) { StArrayElement.Number(0.0) }
|
||||
}
|
||||
|
||||
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
|
||||
@@ -937,10 +989,17 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" ${it.name} = ${it.address.toHex()}")
|
||||
}
|
||||
consts.sortedBy { it.name }.forEach {
|
||||
val nameWithoutColons = it.name.replace("::", "_")
|
||||
if(it.dt.isFloat)
|
||||
asmgen.out(" ${it.name} = ${it.value}")
|
||||
else
|
||||
asmgen.out(" ${it.name} = ${it.value.toHex()}")
|
||||
asmgen.out(" ${nameWithoutColons} = ${it.value}")
|
||||
else {
|
||||
if(it.value!=null)
|
||||
asmgen.out(" ${nameWithoutColons} = ${it.value!!.toHex()}")
|
||||
else if(it.memorySlab!=null)
|
||||
asmgen.out(" ${nameWithoutColons} = ${StMemorySlabBlockName}.${it.memorySlab!!.name}")
|
||||
else
|
||||
TODO("asm gen for ${nameWithoutColons} = memory alloc?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,41 +1029,35 @@ internal class ProgramAndVarsGen(
|
||||
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
||||
return when {
|
||||
dt.isBoolArray ->
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
array.map {
|
||||
if(it.boolean!=null)
|
||||
if(it.boolean==true) "1" else "0"
|
||||
else {
|
||||
val number = it.number!!
|
||||
if(number==0.0) "0" else "1"
|
||||
when(it) {
|
||||
is StArrayElement.BoolValue -> if(it.value) "1" else "0"
|
||||
is StArrayElement.Number -> if(it.value==0.0) "0" else "1"
|
||||
else -> throw AssemblyError("unexpected array element type for bool array")
|
||||
}
|
||||
}
|
||||
dt.isUnsignedByteArray ->
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
array.map {
|
||||
val number = it.number!!.toInt()
|
||||
"$"+number.toString(16).padStart(2, '0')
|
||||
val numValue = (it as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for ubyte array")
|
||||
"$"+numValue.toInt().toString(16).padStart(2, '0')
|
||||
}
|
||||
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
|
||||
if(it.number!=null) {
|
||||
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
|
||||
}
|
||||
else if(it.addressOfSymbol!=null) {
|
||||
val addrOfSymbol = it.addressOfSymbol!!
|
||||
val symbol = symboltable.lookup(addrOfSymbol)!!
|
||||
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
|
||||
asmgen.asmSymbolName(addrOfSymbol+"_lsb") // the _lsb split array comes first in memory
|
||||
else
|
||||
asmgen.asmSymbolName(addrOfSymbol)
|
||||
}
|
||||
else if(it.structInstance!=null) {
|
||||
asmgen.asmSymbolName("${StStructInstanceBlockName}.${it.structInstance!!}")
|
||||
}
|
||||
else if(it.structInstanceUninitialized!=null) {
|
||||
asmgen.asmSymbolName("${StStructInstanceBlockName}_bss.${it.structInstanceUninitialized!!}")
|
||||
}
|
||||
else {
|
||||
throw AssemblyError("weird array elt")
|
||||
when(it) {
|
||||
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
|
||||
is StArrayElement.AddressOf -> {
|
||||
val symbol = symboltable.lookup(it.symbol)!!
|
||||
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray(compTarget))
|
||||
asmgen.asmSymbolName(it.symbol+"_lsb")
|
||||
else
|
||||
asmgen.asmSymbolName(it.symbol)
|
||||
}
|
||||
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
|
||||
is StArrayElement.StructInstance -> {
|
||||
val prefix = if(it.uninitialized) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
|
||||
asmgen.asmSymbolName("$prefix.${it.name}")
|
||||
}
|
||||
is StArrayElement.BoolValue -> throw AssemblyError("bool in word array")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
@@ -1014,41 +1067,66 @@ internal class ProgramAndVarsGen(
|
||||
private fun makeArrayFillDataSigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> {
|
||||
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
||||
return when {
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
dt.isUnsignedByteArray ->
|
||||
array.map {
|
||||
val number = it.number!!.toInt()
|
||||
val numValue = (it as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for ubyte array")
|
||||
val number = numValue.toInt()
|
||||
"$"+number.toString(16).padStart(2, '0')
|
||||
}
|
||||
dt.isSignedByteArray ->
|
||||
array.map {
|
||||
val number = it.number!!.toInt()
|
||||
val numValue = (it as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for byte array")
|
||||
val number = numValue.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
|
||||
if(number>=0)
|
||||
"$$hexnum"
|
||||
else
|
||||
"-$$hexnum"
|
||||
if(number>=0) "$$hexnum" else "-$$hexnum"
|
||||
}
|
||||
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) ->
|
||||
array.map {
|
||||
when(it) {
|
||||
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
|
||||
is StArrayElement.AddressOf -> {
|
||||
val symbol = symboltable.lookup(it.symbol)!!
|
||||
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray(compTarget))
|
||||
asmgen.asmSymbolName(it.symbol+"_lsb")
|
||||
else
|
||||
asmgen.asmSymbolName(it.symbol)
|
||||
}
|
||||
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
|
||||
is StArrayElement.StructInstance -> {
|
||||
val prefix = if(it.uninitialized) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
|
||||
asmgen.asmSymbolName("$prefix.${it.name}")
|
||||
}
|
||||
is StArrayElement.BoolValue -> throw AssemblyError("bool in word array")
|
||||
}
|
||||
}
|
||||
dt.isSignedWordArray ->
|
||||
array.map {
|
||||
when(it) {
|
||||
is StArrayElement.Number -> {
|
||||
val number = it.value.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
||||
if (number >= 0) "$$hexnum" else "-$$hexnum"
|
||||
}
|
||||
is StArrayElement.AddressOf -> asmgen.asmSymbolName(it.symbol)
|
||||
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
|
||||
else -> throw AssemblyError("unexpected array element type for signed word array")
|
||||
}
|
||||
}
|
||||
dt.isLongArray ->
|
||||
array.map {
|
||||
when(it) {
|
||||
is StArrayElement.Number -> {
|
||||
val number = it.value.toInt()
|
||||
val hexnum = number.absoluteValue.toLongHex()
|
||||
if (number >= 0) "$$hexnum" else "-$$hexnum"
|
||||
}
|
||||
is StArrayElement.AddressOf -> asmgen.asmSymbolName(it.symbol)
|
||||
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
|
||||
else -> throw AssemblyError("unexpected array element type for long array")
|
||||
}
|
||||
}
|
||||
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
|
||||
val number = it.number!!.toInt()
|
||||
"$" + number.toString(16).padStart(4, '0')
|
||||
}
|
||||
dt.isSignedWordArray -> array.map {
|
||||
val number = it.number!!.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
||||
if(number>=0)
|
||||
"$$hexnum"
|
||||
else
|
||||
"-$$hexnum"
|
||||
}
|
||||
dt.isLongArray -> array.map {
|
||||
val number = it.number!!.toInt()
|
||||
val hexnum = number.absoluteValue.toLongHex()
|
||||
if(number>=0)
|
||||
"$$hexnum"
|
||||
else
|
||||
"-$$hexnum"
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import com.github.michaelbull.result.onSuccess
|
||||
import com.github.michaelbull.result.onOk
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter
|
||||
private val errors: IErrorReporter,
|
||||
private val program: PtProgram
|
||||
) {
|
||||
|
||||
private val zeropage = options.compTarget.zeropage
|
||||
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||
internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation>
|
||||
private val allStaticVariables by lazy { collectAllVariables(symboltable) }
|
||||
private val usageScores by lazy { computeUsageScores(allStaticVariables) }
|
||||
|
||||
init {
|
||||
allocateZeropageVariables()
|
||||
@@ -46,7 +50,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return
|
||||
|
||||
val allVariables = collectAllVariables(symboltable)
|
||||
val allVariables = allStaticVariables
|
||||
|
||||
val numberOfAllocatableVariables = allVariables.size
|
||||
val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE }
|
||||
@@ -85,14 +89,14 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
variable.astNode?.position ?: Position.DUMMY,
|
||||
errors
|
||||
)
|
||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||
result.onOk { numVariablesAllocatedInZP++ }
|
||||
// no need to check for allocation error, if there is one, just allocate in normal system ram.
|
||||
}
|
||||
|
||||
// try to allocate the "don't care" interger variables into the zeropage until it is full.
|
||||
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
|
||||
// Prioritize by usage frequency: sort by computed hotness score (access frequency × type weight × loop depth)
|
||||
if(errors.noErrors()) {
|
||||
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString }
|
||||
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { usageScores[it.scopedNameString] ?: 0 }
|
||||
for (variable in sortedList) {
|
||||
if(variable.dt.isIntegerOrBool || variable.dt.isPointer) {
|
||||
if(zeropage.free.isEmpty()) {
|
||||
@@ -105,7 +109,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
variable.astNode?.position ?: Position.DUMMY,
|
||||
errors
|
||||
)
|
||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||
result.onOk { numVariablesAllocatedInZP++ }
|
||||
}
|
||||
} else
|
||||
numberOfNonIntegerVariables++
|
||||
@@ -129,4 +133,33 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
collect(st)
|
||||
return vars.sortedBy { it.dt.base }
|
||||
}
|
||||
|
||||
private fun computeUsageScores(vars: Collection<StStaticVariable>): Map<String, Int> {
|
||||
val scores = mutableMapOf<String, Int>()
|
||||
val varInfo = vars.associateBy { it.scopedNameString }
|
||||
var loopDepth = 0
|
||||
|
||||
fun walk(node: PtNode) {
|
||||
val isLoopEnter = node is PtForLoop || node is PtRepeatLoop
|
||||
if(isLoopEnter) loopDepth++
|
||||
|
||||
if(node is PtIdentifier) {
|
||||
val v = varInfo[node.name]
|
||||
if(v != null) {
|
||||
val weight = when {
|
||||
v.dt.isPointer || v.dt.isWord -> 2
|
||||
else -> 1
|
||||
}
|
||||
scores[node.name] = (scores[node.name] ?: 0) + weight * (loopDepth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
node.children.forEach { walk(it) }
|
||||
|
||||
if(isLoopEnter) loopDepth--
|
||||
}
|
||||
|
||||
walk(program)
|
||||
return scores
|
||||
}
|
||||
}
|
||||
@@ -125,10 +125,13 @@ internal class AnyExprAsmGen(
|
||||
}
|
||||
|
||||
private fun assignFloatBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
// C64/PET32 ROM T-variant entries skip CONUPK and need arisgn+Z flag set up beforehand.
|
||||
// CX16 ROM entries are already fixed and handle this internally.
|
||||
val tSuffix = if(asmgen.options.compTarget.name in listOf(C64Target.NAME, PETTarget.NAME)) "_NZ" else ""
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||
asmgen.out(" jsr floats.FADDT")
|
||||
asmgen.out(" jsr floats.FADDT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
@@ -140,13 +143,13 @@ internal class AnyExprAsmGen(
|
||||
}
|
||||
"*" -> {
|
||||
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||
asmgen.out(" jsr floats.FMULTT")
|
||||
asmgen.out(" jsr floats.FMULTT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
"/" -> {
|
||||
assignFloatOperandsToFACandARG(expr.right, expr.left)
|
||||
asmgen.out(" jsr floats.FDIVT")
|
||||
asmgen.out(" jsr floats.FDIVT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.code.StConstant
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
@@ -49,8 +50,12 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
}
|
||||
|
||||
init {
|
||||
if(register!=null && !datatype.isNumericOrBool)
|
||||
throw AssemblyError("must be numeric type")
|
||||
if(register!=null) {
|
||||
if(datatype.isPointer)
|
||||
throw AssemblyError("register target type must be uword, not pointer $datatype $position")
|
||||
else if(!datatype.isNumericOrBool)
|
||||
throw AssemblyError("target type must be numeric not $datatype $position")
|
||||
}
|
||||
if(kind==TargetStorageKind.REGISTER)
|
||||
require(register!=null)
|
||||
else
|
||||
@@ -192,6 +197,19 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
val parameter = asmgen.findSubroutineParameter(value.name, asmgen)
|
||||
if(parameter?.definingAsmSub() != null)
|
||||
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
|
||||
|
||||
// Check if this identifier is a constant with a value - if so, use the value directly instead of reading from memory
|
||||
val symbol = asmgen.symbolTable.lookup(value.name)
|
||||
if(symbol is StConstant && symbol.value != null) {
|
||||
val constValue = symbol.value!!
|
||||
val baseType = when(symbol.dt.base) {
|
||||
BaseDataType.POINTER -> BaseDataType.UWORD
|
||||
else -> symbol.dt.base
|
||||
}
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, symbol.dt,
|
||||
number = PtNumber(baseType, constValue, value.position))
|
||||
}
|
||||
|
||||
val varName=asmgen.asmVariableName(value)
|
||||
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
||||
if(value.type.isUnsignedWord && varName.lowercase().startsWith("cx16.r")) {
|
||||
@@ -208,19 +226,20 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
is PtArrayIndexer -> {
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, value.type, array = value)
|
||||
}
|
||||
is PtBuiltinFunctionCall -> {
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||
}
|
||||
is PtFunctionCall -> {
|
||||
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
|
||||
val sub = symbol.astNode as IPtSubroutine
|
||||
val returnType =
|
||||
if(sub is PtSub && sub.signature.returns.size>1)
|
||||
DataType.UNDEFINED // TODO list of types instead?
|
||||
else
|
||||
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
|
||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||
if(value.builtin) {
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||
} else {
|
||||
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name} ${value.position}")
|
||||
val sub = symbol.astNode as IPtSubroutine
|
||||
val returnType =
|
||||
if(sub is PtSub && sub.signature.returns.size>1)
|
||||
DataType.UNDEFINED // TODO list of types instead?
|
||||
else
|
||||
sub.returnsWhatWhere(asmgen.options.compTarget).firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
|
||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||
@@ -262,7 +281,7 @@ internal sealed class AsmAssignmentBase(val source: AsmAssignSource,
|
||||
}
|
||||
|
||||
val target: AsmAssignTarget
|
||||
get() = targets.single()
|
||||
get() = targets.singleOrNull() ?: throw AssemblyError("code assumes single target but assignment is multi-value $position")
|
||||
}
|
||||
|
||||
internal class AsmAssignment(source: AsmAssignSource,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+312
-53
@@ -44,7 +44,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
"^=", "xor=" -> inplaceModification(assign.target, "^", assign.source)
|
||||
"<<=" -> inplaceModification(assign.target, "<<", assign.source)
|
||||
">>=" -> inplaceModification(assign.target, ">>", assign.source)
|
||||
"%=" -> inplaceModification(assign.target, "%", assign.source)
|
||||
"%=" -> {
|
||||
if(assign.source.datatype.isSigned) {
|
||||
asmgen.errors.err("remainder can only be used on unsigned integer operands on 6502 target for now", assign.position)
|
||||
// TODO implement the signed remainder asm routine
|
||||
}
|
||||
else
|
||||
inplaceModification(assign.target, "%", assign.source)
|
||||
}
|
||||
"==" -> inplaceModification(assign.target, "==", assign.source)
|
||||
"!=" -> inplaceModification(assign.target, "!=", assign.source)
|
||||
"<" -> inplaceModification(assign.target, "<", assign.source)
|
||||
@@ -55,6 +62,63 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModificationIndexedPointerField(target: AsmAssignTarget, operator: String, value: AsmAssignSource) {
|
||||
val array = target.array!!
|
||||
val elementDt = target.datatype
|
||||
if(!elementDt.isByteOrBool && !elementDt.isWord)
|
||||
throw AssemblyError("inplace modification indexed pointer field for type $elementDt not supported")
|
||||
val indexedTarget = IndexedPtrTarget(target)
|
||||
val ptr = ptrgen.indexedEffectiveAddress(indexedTarget)
|
||||
|
||||
val elementTemp = asmgen.createTempVarReused(elementDt.base, false, array)
|
||||
if(elementDt.isByteOrBool) {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda ($ptr),y
|
||||
sta $elementTemp""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda ($ptr),y
|
||||
sta $elementTemp
|
||||
iny
|
||||
lda ($ptr),y
|
||||
sta $elementTemp+1""")
|
||||
}
|
||||
|
||||
// save the computed address on the hardware stack so later code can't clobber it
|
||||
asmgen.out("""
|
||||
lda $ptr
|
||||
pha
|
||||
lda $ptr+1
|
||||
pha""")
|
||||
|
||||
// reuse the existing variable-target logic to modify the temp
|
||||
val tempTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, elementDt, null, target.position, variableAsmName = elementTemp)
|
||||
inplaceModification(tempTarget, operator, value)
|
||||
|
||||
// restore the address and store the temp back
|
||||
asmgen.out("""
|
||||
pla
|
||||
sta $ptr+1
|
||||
pla
|
||||
sta $ptr""")
|
||||
if(elementDt.isByteOrBool) {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $elementTemp
|
||||
sta ($ptr),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $elementTemp
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $elementTemp+1
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification(target: AsmAssignTarget, operator: String, value: AsmAssignSource) {
|
||||
|
||||
// the asm-gen code can deal with situations where you want to assign a byte into a word.
|
||||
@@ -240,7 +304,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
TODO("inplace modification array indexed pointer deref ${target.position}")
|
||||
inplaceModificationIndexedPointerField(target, operator, value)
|
||||
return
|
||||
}
|
||||
val targetArrayVar = target.array.variable!!
|
||||
@@ -311,14 +375,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("inplace long modification with memread value ${target.position}")
|
||||
SourceStorageKind.ARRAY -> TODO("inplace long modification with array value ${target.position}")
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
SourceStorageKind.EXPRESSION ->
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
TODO("inplace long modification ${target.position}")
|
||||
} else {
|
||||
TODO("inplace long modification ${target.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,6 +618,28 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
internal fun inplacemodificationLongWithExpression(targetVar: String, operator: String, value: PtExpression) {
|
||||
// Adding an unsigned byte to a long: skip zero-extending the byte into a
|
||||
// full 4-byte long and just add it with carry propagation through the upper bytes.
|
||||
// The byte shows up here as a (widened) long-typed cast of an ubyte expression,
|
||||
// so detect it via the cast child type.
|
||||
if(operator=="+" && value is PtTypeCast && value.value.type==DataType.UBYTE) {
|
||||
assignmentAsmGen.assignExpressionToRegister(value.value, RegisterOrPair.A, false)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $targetVar+0
|
||||
sta $targetVar+0
|
||||
lda $targetVar+1
|
||||
adc #0
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
adc #0
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
adc #0
|
||||
sta $targetVar+3
|
||||
""")
|
||||
return
|
||||
}
|
||||
// it's not an expression so no need to preserve R14-R15
|
||||
if(value.type.isSigned) {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
@@ -570,11 +655,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
internal fun inplacemodificationLongWithVariable(targetVar: String, operator: String, sourceVar: String) {
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $targetVar
|
||||
adc $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+0
|
||||
adc $sourceVar+0
|
||||
sta $targetVar+0
|
||||
lda $targetVar+1
|
||||
adc $sourceVar+1
|
||||
sta $targetVar+1
|
||||
@@ -583,14 +669,16 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
adc $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
sta $targetVar+3
|
||||
""")
|
||||
}
|
||||
"-" -> {
|
||||
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $targetVar
|
||||
sbc $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+0
|
||||
sbc $sourceVar+0
|
||||
sta $targetVar+0
|
||||
lda $targetVar+1
|
||||
sbc $sourceVar+1
|
||||
sta $targetVar+1
|
||||
@@ -599,7 +687,8 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
sbc $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
sta $targetVar+3
|
||||
""")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
@@ -625,51 +714,102 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
"|" -> {
|
||||
asmgen.out("""
|
||||
lda $targetVar
|
||||
ora $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
ora $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
ora $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
ora $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
ora $sourceVar,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"&" -> {
|
||||
asmgen.out("""
|
||||
lda $targetVar
|
||||
and $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
and $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
and $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
and $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
and $sourceVar,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"^" -> {
|
||||
asmgen.out("""
|
||||
lda $targetVar
|
||||
eor $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
eor $sourceVar+1
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
eor $sourceVar,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"*" -> {
|
||||
asmgen.out("""
|
||||
; Copy target to R12R13 (multiplicand)
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Copy source to R14R15 (multiplier)
|
||||
ldy #3
|
||||
- lda $sourceVar,y
|
||||
sta cx16.r14,y
|
||||
dey
|
||||
bpl -
|
||||
jsr prog8_math.multiply_longs
|
||||
; Copy result from R14R15 back to target
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"/" -> {
|
||||
asmgen.out("""
|
||||
; Copy target to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Copy source to R14R15 (divisor)
|
||||
ldy #3
|
||||
- lda $sourceVar,y
|
||||
sta cx16.r14,y
|
||||
dey
|
||||
bpl -
|
||||
jsr prog8_math.div_longs
|
||||
; Copy result from R14R15 back to target
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"%" -> {
|
||||
asmgen.out("""
|
||||
; Copy target to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Copy source to R14R15 (divisor)
|
||||
ldy #3
|
||||
- lda $sourceVar,y
|
||||
sta cx16.r14,y
|
||||
dey
|
||||
bpl -
|
||||
jsr prog8_math.div_longs
|
||||
; Copy remainder from P8ZP_SCRATCH_W1/W2 back to target
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta $targetVar
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
eor $sourceVar+2
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
eor $sourceVar+3
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $targetVar+3""")
|
||||
}
|
||||
else -> {
|
||||
TODO("in-place modify LONG with variable")
|
||||
TODO("in-place modify LONG with variable, operator=$operator")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -870,8 +1010,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
else -> {
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta $variable
|
||||
lda $variable+3
|
||||
bpl +
|
||||
lda #255
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta $variable
|
||||
sta $variable+1
|
||||
sta $variable+2
|
||||
sta $variable+3""")
|
||||
@@ -994,6 +1138,93 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
}
|
||||
"*" -> {
|
||||
// long *= constant - use multiplication routine with loop-based copies
|
||||
val lo = value and 0xFFFF
|
||||
val hi = (value ushr 16) and 0xFFFF
|
||||
asmgen.out("""
|
||||
; Copy variable to R12R13 (multiplicand)
|
||||
ldy #3
|
||||
- lda $variable,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Load constant into R14R15 (multiplier)
|
||||
lda #<${lo}
|
||||
sta cx16.r14
|
||||
lda #>${lo}
|
||||
sta cx16.r14+1
|
||||
lda #<${hi}
|
||||
sta cx16.r15
|
||||
lda #>${hi}
|
||||
sta cx16.r15+1
|
||||
jsr prog8_math.multiply_longs
|
||||
; Copy result from R14R15 back to variable
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $variable,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"/" -> {
|
||||
// long /= constant - use division routine
|
||||
val lo = value and 0xFFFF
|
||||
val hi = (value ushr 16) and 0xFFFF
|
||||
asmgen.out("""
|
||||
; Copy variable to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $variable,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Load constant into R14R15 (divisor)
|
||||
lda #<${lo}
|
||||
sta cx16.r14
|
||||
lda #>${lo}
|
||||
sta cx16.r14+1
|
||||
lda #<${hi}
|
||||
sta cx16.r15
|
||||
lda #>${hi}
|
||||
sta cx16.r15+1
|
||||
jsr prog8_math.div_longs
|
||||
; Copy result from R14R15 back to variable
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $variable,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"%" -> {
|
||||
// long %= constant - use division routine, remainder is in P8ZP_SCRATCH_W1/W2
|
||||
val lo = value and 0xFFFF
|
||||
val hi = (value ushr 16) and 0xFFFF
|
||||
asmgen.out("""
|
||||
; Copy variable to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $variable,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Load constant into R14R15 (divisor)
|
||||
lda #<${lo}
|
||||
sta cx16.r14
|
||||
lda #>${lo}
|
||||
sta cx16.r14+1
|
||||
lda #<${hi}
|
||||
sta cx16.r15
|
||||
lda #>${hi}
|
||||
sta cx16.r15+1
|
||||
jsr prog8_math.div_longs
|
||||
; Copy remainder from P8ZP_SCRATCH_W1/W2 back to variable
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta $variable
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta $variable+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $variable+2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $variable+3""")
|
||||
}
|
||||
"<<" -> if (value > 0) inplaceLongShiftLeft()
|
||||
">>" -> if (value > 0) inplaceLongShiftRight()
|
||||
"|" -> {
|
||||
@@ -1135,9 +1366,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
val assignValue = AsmAssignment(value,
|
||||
listOf(
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE,
|
||||
address.definingISub(), Position.DUMMY, register = RegisterOrPair.A)
|
||||
address.definingISub(), address.position, register = RegisterOrPair.A)
|
||||
),
|
||||
program.memsizer, Position.DUMMY)
|
||||
program.memsizer, address.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assignValue, address.definingISub()) // calculate value into A
|
||||
}
|
||||
|
||||
@@ -1220,6 +1451,33 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
sbc cx16.r0H
|
||||
sta ${arrayVar}_msb+$index""")
|
||||
}
|
||||
"|" -> {
|
||||
asmgen.out("""
|
||||
lda ${arrayVar}_lsb+$index
|
||||
ora cx16.r0L
|
||||
sta ${arrayVar}_lsb+$index
|
||||
lda ${arrayVar}_msb+$index
|
||||
ora cx16.r0H
|
||||
sta ${arrayVar}_msb+$index""")
|
||||
}
|
||||
"&" -> {
|
||||
asmgen.out("""
|
||||
lda ${arrayVar}_lsb+$index
|
||||
and cx16.r0L
|
||||
sta ${arrayVar}_lsb+$index
|
||||
lda ${arrayVar}_msb+$index
|
||||
and cx16.r0H
|
||||
sta ${arrayVar}_msb+$index""")
|
||||
}
|
||||
"^" -> {
|
||||
asmgen.out("""
|
||||
lda ${arrayVar}_lsb+$index
|
||||
eor cx16.r0L
|
||||
sta ${arrayVar}_lsb+$index
|
||||
lda ${arrayVar}_msb+$index
|
||||
eor cx16.r0H
|
||||
sta ${arrayVar}_msb+$index""")
|
||||
}
|
||||
else -> TODO("inplace split word array value $operator")
|
||||
}
|
||||
}
|
||||
@@ -1495,7 +1753,7 @@ $shortcutLabel:""")
|
||||
// use the already existing optimized codegen for regular assignments x += array[index]
|
||||
val binexpr = PtBinaryExpression(operator, dt, value.position)
|
||||
binexpr.add(PtIdentifier(name, dt, value.position))
|
||||
val arrayValue = PtArrayIndexer(value.type, value.position)
|
||||
val arrayValue = PtArrayIndexer(value.type, valueVar.type.isSplitWordArray(program.memsizer), value.position)
|
||||
arrayValue.add(valueVar)
|
||||
arrayValue.add(value.index)
|
||||
binexpr.add(arrayValue)
|
||||
@@ -1710,8 +1968,8 @@ $shortcutLabel:""")
|
||||
private fun inplacemodificationRegisterAwithVariableWithSwappedOperands(operator: String, variable: String, signed: Boolean) {
|
||||
// A = variable <operator> A
|
||||
|
||||
if(operator in AssociativeOperators)
|
||||
return inplacemodificationRegisterAwithVariable(operator, variable, signed) // just reuse existing code for associative operators
|
||||
if(operator in CommutativeOperators)
|
||||
return inplacemodificationRegisterAwithVariable(operator, variable, signed) // just reuse existing code for commutative operators
|
||||
|
||||
// now implement the non-assiciative operators...
|
||||
when (operator) {
|
||||
@@ -3095,6 +3353,7 @@ $shortcutLabel:""")
|
||||
lda $name+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
txa
|
||||
oweroff
|
||||
jsr prog8_math.divmod_uw_asm
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -113,34 +113,128 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
internal fun inplaceByteInvert(target: PtrTarget) {
|
||||
TODO("inplace byte invert pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
val eorVal = if(target.dt.isBool) 1 else 255
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
asmgen.out(" lda ($zpPtrVar) | eor #$eorVal | sta ($zpPtrVar)")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #$eorVal
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceWordInvert(target: PtrTarget) {
|
||||
TODO("inplace word invert pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceLongInvert(target: PtrTarget) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceByteNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace byte negate to pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
ina
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceWordNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace word negate pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
sec
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceLongNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace long negate pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
sec
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceFloatNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace float negate pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
// flip sign bit in second byte of 5-byte float
|
||||
asmgen.out(
|
||||
$$"""
|
||||
ldy #$$offset
|
||||
iny
|
||||
lda ($$zpPtrVar),y
|
||||
eor #$80
|
||||
sta ($$zpPtrVar),y"""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false): Pair<String, UByte> {
|
||||
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false, loadPointerFieldValue: Boolean=false): Pair<String, UByte> {
|
||||
// walk the pointer deref chain and leaves the final pointer value in a ZP var
|
||||
// this will often be the temp var P8ZP_SCRATCH_PTR but can also be the original pointer variable if it is already in zeropage and there is nothing to add to it
|
||||
// returns the ZP var to use as a pointer, and a Y register offset (which can be zero)
|
||||
// loadPointerFieldValue: for addOffsetToPointer=true, also load the value if the final field is a pointer-typed field (rather than returning its address).
|
||||
|
||||
fun addFieldOffsetToScratchPointer(fieldoffset: UInt) {
|
||||
if(fieldoffset==0u)
|
||||
@@ -185,7 +279,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
if(addOffsetToPointer) {
|
||||
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
addFieldOffsetToScratchPointer(field.second.toUInt())
|
||||
if(pointer.derefLast)
|
||||
if(pointer.derefLast || (loadPointerFieldValue && field.first.isPointer))
|
||||
updateScratchPointer()
|
||||
return "P8ZP_SCRATCH_PTR" to 0u
|
||||
} else {
|
||||
@@ -235,21 +329,80 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
} else {
|
||||
if(addOffsetToPointer) {
|
||||
addFieldOffsetToScratchPointer(fieldinfo.second.toUInt())
|
||||
if(loadPointerFieldValue && fieldinfo.first.isPointer)
|
||||
updateScratchPointer()
|
||||
return "P8ZP_SCRATCH_PTR" to 0u
|
||||
} else
|
||||
return "P8ZP_SCRATCH_PTR" to fieldinfo.second
|
||||
}
|
||||
}
|
||||
|
||||
internal fun indexedEffectiveAddress(target: IndexedPtrTarget): String {
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
// generic: evaluate index as word, scale by element size, add field-corrected base pointer
|
||||
asmgen.assignExpressionToVariable(target.index, "P8ZP_SCRATCH_W1", DataType.UWORD)
|
||||
when (eltSize) {
|
||||
1 -> {}
|
||||
2 -> asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
|
||||
4 -> asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1 | asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
|
||||
8 -> asmgen.out(" 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")
|
||||
else -> {
|
||||
// generic multiply by eltSize
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
sta prog8_math.multiply_words.multiplier
|
||||
sty prog8_math.multiply_words.multiplier+1
|
||||
lda #<$eltSize
|
||||
ldy #>$eltSize
|
||||
jsr prog8_math.multiply_words
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1""")
|
||||
}
|
||||
}
|
||||
// save scaled index to W2, compute base (field-corrected) into P8ZP_SCRATCH_PTR, then add
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | sta P8ZP_SCRATCH_W2 | lda P8ZP_SCRATCH_W1+1 | sta P8ZP_SCRATCH_W2+1")
|
||||
val (basePtr, _) = deref(target.pointer, addOffsetToPointer = true, loadPointerFieldValue = true)
|
||||
if(basePtr != "P8ZP_SCRATCH_PTR") {
|
||||
// deref returned the original zp pointer variable; copy it so we don't mutate it
|
||||
asmgen.out("""
|
||||
lda $basePtr
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $basePtr+1
|
||||
sta P8ZP_SCRATCH_PTR+1""")
|
||||
}
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda P8ZP_SCRATCH_PTR
|
||||
adc P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda P8ZP_SCRATCH_PTR+1
|
||||
adc P8ZP_SCRATCH_W2+1
|
||||
sta P8ZP_SCRATCH_PTR+1""")
|
||||
return "P8ZP_SCRATCH_PTR"
|
||||
}
|
||||
|
||||
internal fun assignPointerDerefExpression(target: AsmAssignTarget, value: PtPointerDeref) {
|
||||
val (zpPtrVar, offset) = deref(value)
|
||||
val (zpPtrVar, offset1) = deref(value)
|
||||
val offset: UByte
|
||||
if(value.derefLast) {
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, offset1)
|
||||
asmgen.out(" sta $zpPtrVar | sty $zpPtrVar+1")
|
||||
offset = 0.toUByte()
|
||||
} else {
|
||||
offset = offset1
|
||||
}
|
||||
if(value.type.isByteOrBool) {
|
||||
asmgen.loadIndirectByte(zpPtrVar, offset)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
}
|
||||
else if(value.type.isWord || value.type.isPointer) {
|
||||
asmgen.loadIndirectWord(zpPtrVar, offset)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, target)
|
||||
if(target.register in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
asmgen.loadIndirectWordIntoRegisters(zpPtrVar, offset, target.register!!)
|
||||
} else {
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, offset)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, target)
|
||||
}
|
||||
}
|
||||
else if(value.type.isFloat) {
|
||||
asmgen.loadIndirectFloat(zpPtrVar, offset)
|
||||
@@ -270,15 +423,17 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
sta ${target.asmVarname}+2
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta ${target.asmVarname}+3
|
||||
""")
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register!! in CombinedLongRegisters)
|
||||
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, target.register)
|
||||
}
|
||||
TargetStorageKind.POINTER -> {
|
||||
// TODO optimize this so it doesn't need a temporary long var all the time
|
||||
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, RegisterOrPair.R14R15)
|
||||
asmgen.assignRegister(RegisterOrPair.R14R15, target)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, RegisterOrPair.R14R15)
|
||||
asmgen.assignRegister(RegisterOrPair.R14R15, target)
|
||||
}
|
||||
@@ -295,24 +450,24 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
internal fun inplaceModification(target: PtrTarget, operator: String, value: AsmAssignSource) {
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
|
||||
if(target.dt.isByte && (value.number?.number==1.0 || value.number?.number==2.0)) {
|
||||
val amount = value.number.number.toInt()
|
||||
inplaceByteInc(target, amount)
|
||||
} else {
|
||||
if (target.dt.isByte) inplaceByteAdd(target, value)
|
||||
else if (target.dt.isWord) inplaceWordAdd(target, value)
|
||||
else if (target.dt.isWord || target.dt.isPointer) inplaceWordAdd(target, value)
|
||||
else if (target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
|
||||
else if (target.dt.isLong) inplaceLongAdd(target, value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
|
||||
if(target.dt.isByte && (value.number?.number==1.0 || value.number?.number==2.0)) {
|
||||
val amount = value.number.number.toInt()
|
||||
inplaceByteDec(target, amount)
|
||||
} else {
|
||||
if (target.dt.isByte) inplaceByteSub(target, value)
|
||||
else if (target.dt.isWord) inplaceWordSub(target, value)
|
||||
else if (target.dt.isWord || target.dt.isPointer) inplaceWordSub(target, value)
|
||||
else if (target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
|
||||
else if (target.dt.isLong) inplaceLongSub(target, value)
|
||||
else throw AssemblyError("weird dt ${target.position}")
|
||||
@@ -395,132 +550,163 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
internal fun assignByte(target: IndexedPtrTarget, byte: Int) {
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
if(offset>255) {
|
||||
// need to add index to the pointer (but not clobber it) to allow index>255
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) { // TODO not very robust....
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1
|
||||
ldy #<$offset
|
||||
lda #$byte
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #<$offset
|
||||
lda #$byte
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #<$offset
|
||||
lda #$byte
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
} else if(target.index is PtIdentifier) {
|
||||
val (zpPtrVar, _) = deref(target.pointer, addOffsetToPointer=true)
|
||||
val indexVarName = asmgen.asmVariableName(target.index)
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
// asmgen.loadScaledArrayIndexIntoRegister() ...
|
||||
}
|
||||
// element size is 1, can immediately add the index value
|
||||
if(target.index.type.isWord) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $zpPtrVar+1
|
||||
adc $indexVarName+1
|
||||
sta $zpPtrVar+1""")
|
||||
}
|
||||
asmgen.out("""
|
||||
ldy $indexVarName
|
||||
lda #$byte
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
// asmgen.loadScaledArrayIndexIntoRegister() ...
|
||||
}
|
||||
if(target.index.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda #$byte | sta ($zpPtrVar),y")
|
||||
}
|
||||
else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.out("""
|
||||
pla
|
||||
clc
|
||||
adc $zpPtrVar+1
|
||||
sta $zpPtrVar+1""")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out(" lda #$byte | sta ($zpPtrVar),y")
|
||||
}
|
||||
}
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectByte(byte, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignWord(target: IndexedPtrTarget, word: Int) {
|
||||
TODO("array ptr assign const word ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectWord(word, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignLong(target: IndexedPtrTarget, long: Int) {
|
||||
TODO("array ptr assign const long ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
val hex = long.toLongHex()
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
|
||||
internal fun assignFloat(target: IndexedPtrTarget, float: Double) {
|
||||
TODO("array ptr assign const float ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectFloat(float, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignFAC1(target: IndexedPtrTarget) {
|
||||
TODO("array ptr assign FAC1 ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectFloatFP1(ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignFloatAY(target: IndexedPtrTarget) {
|
||||
TODO("array ptr assign float AY ${target.position}")
|
||||
// A/Y contains a pointer to the source float. Save it, compute destination address,
|
||||
// restore source pointer into W1, load destination pointer into A/Y, and copy.
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" pha | phy")
|
||||
else
|
||||
asmgen.out(" pha | tya | pha")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
asmgen.out("""
|
||||
ply
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $ptr
|
||||
ldy $ptr+1
|
||||
jsr floats.copy_float""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $ptr
|
||||
ldy $ptr+1
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFloatVar(target: IndexedPtrTarget, varName: String) {
|
||||
TODO("array ptr assign float var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectFloatVar(varName, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignByteReg(target: IndexedPtrTarget, register: CpuRegister) {
|
||||
// save register on the stack so indexedEffectiveAddress can freely use scratch regs
|
||||
asmgen.saveRegisterStack(register, false)
|
||||
TODO("array ptr assign byte reg ${target.position}")
|
||||
asmgen.saveRegisterStack(register, false)
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.restoreRegisterStack(register, false)
|
||||
asmgen.storeIndirectByteReg(register, ptr, 0u, false, false)
|
||||
}
|
||||
|
||||
internal fun assignWordReg(target: IndexedPtrTarget, regs: RegisterOrPair) {
|
||||
// save word reg on the stack so indexedEffectiveAddress can freely use scratch regs
|
||||
saveOnStack(regs)
|
||||
TODO("array ptr assign word reg ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
restoreFromStack(regs)
|
||||
asmgen.storeIndirectWordReg(regs, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignByteVar(target: IndexedPtrTarget, varName: String, extendToWord: Boolean, signed: Boolean) {
|
||||
TODO("array ptr assign byte var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
if(extendToWord) {
|
||||
// need to store byte var extended to word
|
||||
asmgen.out(" lda $varName")
|
||||
if(signed) asmgen.signExtendAXlsb(BaseDataType.BYTE) else asmgen.signExtendAXlsb(BaseDataType.UBYTE)
|
||||
asmgen.storeIndirectWordReg(RegisterOrPair.AX, ptr, 0u)
|
||||
} else {
|
||||
asmgen.storeIndirectByteVar(varName, ptr, 0u)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignWordVar(target: IndexedPtrTarget, varName: String) {
|
||||
TODO("array ptr assign word var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectWordVar(varName, DataType.UWORD, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignLongVar(target: IndexedPtrTarget, varName: String) {
|
||||
TODO("array ptr assign long var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $varName
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $varName+1
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $varName+2
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $varName+3
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
|
||||
internal fun assignLongReg(target: IndexedPtrTarget, regs: RegisterOrPair) {
|
||||
// generic fallback for long array via pointer
|
||||
val startreg = regs.startregname()
|
||||
// save source long on the stack in case index expression evaluation uses the same registers
|
||||
asmgen.out("""
|
||||
lda cx16.$startreg
|
||||
pha
|
||||
lda cx16.$startreg+1
|
||||
pha
|
||||
lda cx16.$startreg+2
|
||||
pha
|
||||
lda cx16.$startreg+3
|
||||
pha""")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.out("""
|
||||
pla
|
||||
sta cx16.$startreg+3
|
||||
pla
|
||||
sta cx16.$startreg+2
|
||||
pla
|
||||
sta cx16.$startreg+1
|
||||
pla
|
||||
sta cx16.$startreg
|
||||
ldy #0
|
||||
lda cx16.$startreg
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda cx16.$startreg+1
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda cx16.$startreg+2
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda cx16.$startreg+3
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
|
||||
internal fun assignLongVar(pointer: PtPointerDeref, varName: String) {
|
||||
@@ -1738,7 +1924,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
|
||||
fun multiply() {
|
||||
// on entry here: number placed in routine argument variable
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
jsr prog8_math.multiply_words
|
||||
tax
|
||||
@@ -1807,7 +1993,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
val number = value.number!!.number.toInt()
|
||||
if(number in powersOfTwoInt)
|
||||
throw AssemblyError("divide by power of two should have been a shift $value.position")
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1818,7 +2004,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
val varname = value.asmVarname
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1830,7 +2016,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
val register = value.register!!
|
||||
require(register.isWord())
|
||||
val regname = asmgen.asmSymbolName(register)
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1839,7 +2025,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
divide(target.dt.isSigned)
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AY)
|
||||
divide(target.dt.isSigned)
|
||||
@@ -1869,7 +2055,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
val number = value.number!!.number.toInt()
|
||||
if(number in powersOfTwoInt)
|
||||
throw AssemblyError("divide by power of two should have been a shift $value.position")
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1880,7 +2066,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
val varname = value.asmVarname
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1892,7 +2078,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
val register = value.register!!
|
||||
require(register.isWord())
|
||||
val regname = asmgen.asmSymbolName(register)
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1901,7 +2087,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
modulus()
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AY)
|
||||
modulus()
|
||||
@@ -2546,46 +2732,42 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, index: PtExpression, arrayDt: DataType) {
|
||||
TODO("assign indexed pointer from array $arrayVarName at ${target.position}")
|
||||
// val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
|
||||
// assignAddressOfIndexedPointer(ptrZp, arrayVarName, arrayDt, index)
|
||||
// when {
|
||||
// target.datatype.isByteOrBool -> {
|
||||
// asmgen.out("""
|
||||
// ldy #0
|
||||
// lda (P8ZP_SCRATCH_PTR),y""")
|
||||
// asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
// }
|
||||
// target.datatype.isWord || target.datatype.isPointer -> {
|
||||
// if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
// asmgen.out("""
|
||||
// ldy #1
|
||||
// lda (P8ZP_SCRATCH_PTR),y
|
||||
// tax
|
||||
// lda (P8ZP_SCRATCH_PTR)""")
|
||||
// else
|
||||
// asmgen.out("""
|
||||
// ldy #1
|
||||
// lda (P8ZP_SCRATCH_PTR),y
|
||||
// tax
|
||||
// dey
|
||||
// lda (P8ZP_SCRATCH_PTR),y""")
|
||||
// asmgen.assignRegister(RegisterOrPair.AX, target)
|
||||
// }
|
||||
// target.datatype.isLong -> {
|
||||
// TODO("assign long from pointer to $target ${target.position}")
|
||||
// }
|
||||
// target.datatype.isFloat -> {
|
||||
// // TODO optimize the float copying to avoid having to go through FAC1
|
||||
// asmgen.out("""
|
||||
// lda P8ZP_SCRATCH_PTR
|
||||
// ldy P8ZP_SCRATCH_PTR+1
|
||||
// jsr floats.MOVFM""")
|
||||
// asmgen.assignRegister(RegisterOrPair.FAC1, target)
|
||||
// }
|
||||
// else -> throw AssemblyError("weird dt ${target.datatype}")
|
||||
// }
|
||||
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, arrayIndexer: PtArrayIndexer) {
|
||||
// Load value from array[index] and store to pointer target
|
||||
if(target.datatype.isLong) {
|
||||
// Get address of array[index] into AY, then 4-byte copy loop to target pointer
|
||||
val index = arrayIndexer.index
|
||||
asmgen.assignExpressionToRegister(index, RegisterOrPair.AY, false)
|
||||
asmgen.out(" sty P8ZP_SCRATCH_REG")
|
||||
asmgen.out(" asl a | rol P8ZP_SCRATCH_REG") // *2
|
||||
asmgen.out(" asl a | rol P8ZP_SCRATCH_REG") // *4
|
||||
asmgen.out(" ldy P8ZP_SCRATCH_REG")
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $arrayVarName
|
||||
pha
|
||||
tya
|
||||
adc $arrayVarName+1
|
||||
tay
|
||||
pla""")
|
||||
// Now AY = address of array[index], save to scratch pointer
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
|
||||
// Get target pointer address
|
||||
target.pointer?.let { ptr ->
|
||||
val (ptrVar, offset) = deref(ptr)
|
||||
// 4-byte copy loop from (P8ZP_SCRATCH_W1) to (ptrVar)+offset
|
||||
asmgen.out("""
|
||||
ldx #4
|
||||
ldy #$offset
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta ($ptrVar),y
|
||||
iny
|
||||
dex
|
||||
bne -""")
|
||||
} ?: throw AssemblyError("expected pointer target ${target.position}")
|
||||
} else {
|
||||
throw AssemblyError("unsupported type for indexed pointer assignment ${target.datatype} ${target.position}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveOnStack(regs: RegisterOrPair) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,860 @@
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
|
||||
internal class TypeCastAssignmentsGen(
|
||||
private val program: PtProgram,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val pointergen: PointerAssignmentsGen
|
||||
) {
|
||||
lateinit var assignmentAsmGen: AssignmentAsmGen
|
||||
|
||||
internal fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: PtExpression, origTypeCastExpression: PtTypeCast) {
|
||||
val valueDt = value.type
|
||||
if(valueDt==targetDt)
|
||||
throw AssemblyError("type cast to identical dt should have been removed: $valueDt at ${value.position}")
|
||||
|
||||
when(value) {
|
||||
is PtIdentifier -> {
|
||||
if(targetDt.isWord) {
|
||||
if(valueDt.isUnsignedByte || valueDt.isBool) {
|
||||
assignmentAsmGen.assignVariableUByteIntoWord(target, value)
|
||||
return
|
||||
}
|
||||
if(valueDt.isSignedByte) {
|
||||
assignmentAsmGen.assignVariableByteIntoWord(target, value)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtMemoryByte -> {
|
||||
if(targetDt.isWord) {
|
||||
|
||||
fun assignViaExprEval(addressExpression: PtExpression) {
|
||||
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
asmgen.out(" ldy #0")
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
|
||||
when (value.address) {
|
||||
is PtNumber -> {
|
||||
val address = (value.address as PtNumber).number.toUInt()
|
||||
assignmentAsmGen.assignMemoryByteIntoWord(target, address, null)
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
assignmentAsmGen.assignMemoryByteIntoWord(target, null, value.address as PtIdentifier)
|
||||
}
|
||||
is PtBinaryExpression -> {
|
||||
val addrExpr = value.address as PtBinaryExpression
|
||||
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, false)) {
|
||||
asmgen.out(" ldy #0")
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
} else {
|
||||
assignViaExprEval(value.address)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
assignViaExprEval(value.address)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
is PtNumber -> {
|
||||
if(targetDt.isPointer) {
|
||||
// assign a number to a pointer type
|
||||
require(valueDt.isInteger)
|
||||
return assignmentAsmGen.assignConstantWord(target, value.number.toInt())
|
||||
}
|
||||
else
|
||||
throw AssemblyError("literal value cast should have been const-folded away (target type=$targetDt)")
|
||||
}
|
||||
is PtBool -> throw AssemblyError("literal value cast should have been const-folded away (target type=$targetDt)")
|
||||
is PtArrayIndexer -> {
|
||||
if(targetDt.isByte && valueDt.isWord) {
|
||||
// just assign the lsb from the array value
|
||||
return assignCastWordViaLsbFunc(value, target)
|
||||
} else if(targetDt.isBool && valueDt.isWord) {
|
||||
return assignWordToBool(value, target)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
|
||||
// special case optimizations
|
||||
if(target.kind == TargetStorageKind.VARIABLE) {
|
||||
if(value is PtIdentifier && !valueDt.isUndefined)
|
||||
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
|
||||
|
||||
when {
|
||||
valueDt.isBool -> {
|
||||
if(targetDt.isInteger) {
|
||||
// optimization to assign boolean expression to integer target (just assign the 0 or 1 directly)
|
||||
val assignDirect = AsmAssignment(
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen),
|
||||
listOf(target),
|
||||
program.memsizer,
|
||||
target.position
|
||||
)
|
||||
assignmentAsmGen.assignExpression(assignDirect, target.scope)
|
||||
} else {
|
||||
TODO("assign bool to non-integer type $targetDt ${value.position}")
|
||||
}
|
||||
}
|
||||
valueDt.isByte -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.A, valueDt.base, target.position)
|
||||
}
|
||||
valueDt.isLong -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.R14R15, valueDt.base, target.position)
|
||||
}
|
||||
valueDt.isWord || valueDt.isPointer -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.AY, valueDt.base, target.position)
|
||||
}
|
||||
valueDt.isFloat -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.FAC1, true)
|
||||
assignTypeCastedFloatFAC1(target.asmVarname, targetDt.base)
|
||||
}
|
||||
valueDt.isPassByRef -> {
|
||||
// str/array value cast (most likely to UWORD, take address-of)
|
||||
assignmentAsmGen.assignExpressionToVariable(value, target.asmVarname, targetDt)
|
||||
}
|
||||
else -> throw AssemblyError("strange dt in typecast assign to var: $valueDt --> $targetDt")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if(valueDt.isWord && origTypeCastExpression.type.isUnsignedByte) {
|
||||
val parentTc = origTypeCastExpression.parent as? PtTypeCast
|
||||
if(parentTc!=null && parentTc.type.isUnsignedWord) {
|
||||
// typecast a word value to ubyte and directly back to uword
|
||||
// generate code for lsb(value) here instead of the ubyte typecast
|
||||
return assignCastWordViaLsbFunc(value, target)
|
||||
}
|
||||
}
|
||||
|
||||
if(valueDt.isByteOrBool) {
|
||||
when(target.register) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> {
|
||||
// 'cast' an ubyte value to a byte register; no cast needed at all
|
||||
return assignmentAsmGen.assignExpressionToRegister(value, target.register, valueDt.isSigned)
|
||||
}
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY,
|
||||
in Cx16VirtualRegisters -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, false)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, valueDt.isSigned, true)
|
||||
return
|
||||
}
|
||||
in CombinedLongRegisters -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, false)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, valueDt.isSigned, true)
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
} else if(valueDt.isWord) {
|
||||
when(target.register) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> {
|
||||
return if(targetDt.isBool)
|
||||
assignWordToBool(value, target)
|
||||
else
|
||||
// cast an uword to a byte register, do this via lsb(value)
|
||||
// generate code for lsb(value) here instead of the ubyte typecast
|
||||
assignCastWordViaLsbFunc(value, target)
|
||||
}
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY,
|
||||
in Cx16VirtualRegisters -> {
|
||||
// 'cast' uword into a 16 bits register, just assign it
|
||||
return assignmentAsmGen.assignExpressionToRegister(value, target.register!!, targetDt.isSigned)
|
||||
}
|
||||
RegisterOrPair.R0R1 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R0, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r0", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R2R3 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R2, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r2", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R4R5 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R4, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r4", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R6R7 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R6, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r6", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R8R9 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R8, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r8", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R10R11 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R10, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r10", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R12R13 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R12, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r12", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R14R15 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r14", valueDt.base)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
if(target.kind==TargetStorageKind.REGISTER) {
|
||||
if(valueDt.isFloat && !target.datatype.isFloat) {
|
||||
// have to typecast the float number on the fly down to an integer
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.FAC1, targetDt.isSigned)
|
||||
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", targetDt.base)
|
||||
assignmentAsmGen.assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, targetDt.isSigned, origTypeCastExpression.definingISub(), target.position)
|
||||
return
|
||||
} else {
|
||||
if(!(valueDt isAssignableTo targetDt)) {
|
||||
return if(valueDt.isWord && targetDt.isByte) {
|
||||
// word to byte, just take the lsb
|
||||
assignCastWordViaLsbFunc(value, target)
|
||||
} else if(valueDt.isWord && targetDt.isBool) {
|
||||
// word to bool
|
||||
assignWordToBool(value, target)
|
||||
} else if(valueDt.isWord && targetDt.isWord) {
|
||||
// word to word, just assign
|
||||
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
|
||||
} else if(valueDt.isByteOrBool && targetDt.isByte) {
|
||||
// byte to byte, just assign
|
||||
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
|
||||
} else if(valueDt.isByteOrBool && targetDt.isWord) {
|
||||
// byte to word, just assign
|
||||
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
|
||||
} else if(valueDt.isLong && targetDt.isByte) {
|
||||
// long to byte, just take the lsb
|
||||
assignCastLongToByte(value, target)
|
||||
} else if(valueDt.isLong && targetDt.isWord) {
|
||||
// long to word, just take the lsw
|
||||
assignCastViaLswFunc(value, target)
|
||||
} else if(valueDt.isPointer && targetDt.isPointer) {
|
||||
// pointer type A to pointer type B, just assign
|
||||
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
|
||||
} else
|
||||
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker ${value.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(targetDt.isInteger && valueDt.isByteOrBool && valueDt.isAssignableTo(targetDt)) {
|
||||
require(targetDt.isWord || targetDt.isLong) {
|
||||
"should be byte to word or long assignment ${origTypeCastExpression.position}"
|
||||
}
|
||||
when(target.kind) {
|
||||
// TargetStorageKind.VARIABLE -> {
|
||||
// This has been handled already earlier on line 961.
|
||||
// // byte to word, just assign to registers first, then assign to variable
|
||||
// assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==BaseDataType.WORD)
|
||||
// assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, targetDt)
|
||||
// return
|
||||
// }
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// byte to word, just assign to registers first, then assign into array
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, targetDt.isSigned)
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null)
|
||||
pointergen.assignWordReg(IndexedPtrTarget(target), RegisterOrPair.AY)
|
||||
else
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
// byte to word or long, just assign to registers
|
||||
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, targetDt.isSigned)
|
||||
return
|
||||
}
|
||||
TargetStorageKind.POINTER -> {
|
||||
pointergen.assignByteToWord(PtrTarget(target), value)
|
||||
return
|
||||
}
|
||||
else -> throw AssemblyError("weird target at ${target.position}")
|
||||
}
|
||||
}
|
||||
|
||||
if(targetDt.isFloat && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
|
||||
if(target.register==RegisterOrPair.FAC2)
|
||||
asmgen.pushFAC1()
|
||||
when {
|
||||
valueDt.isUnsignedByte -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.Y, false)
|
||||
asmgen.out(" jsr floats.FREADUY")
|
||||
}
|
||||
valueDt.isSignedByte -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, true)
|
||||
asmgen.out(" jsr floats.FREADSA")
|
||||
}
|
||||
valueDt.isUnsignedWord -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
||||
asmgen.out(" jsr floats.GIVUAYFAY")
|
||||
}
|
||||
valueDt.isSignedWord -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
asmgen.out(" jsr floats.GIVAYFAY")
|
||||
}
|
||||
valueDt.isLong -> {
|
||||
when(value) {
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(value)
|
||||
asmgen.out("""
|
||||
lda #<$varname
|
||||
ldy #>$varname
|
||||
jsr floats.internal_long_AY_to_FAC""")
|
||||
}
|
||||
else -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
asmgen.out("""
|
||||
lda #<cx16.r14
|
||||
ldy #>cx16.r14
|
||||
jsr floats.internal_long_AY_to_FAC""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt at ${target.position}")
|
||||
}
|
||||
if(target.register==RegisterOrPair.FAC2) {
|
||||
asmgen.out(" jsr floats.MOVEF")
|
||||
asmgen.popFAC1()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if(targetDt.isUnsignedWord && valueDt.isPointer) {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return
|
||||
}
|
||||
|
||||
// No more special optimized cases yet. Do the rest via more complex evaluation
|
||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
||||
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
|
||||
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
||||
}
|
||||
|
||||
|
||||
internal fun assignCastLongToByte(value: PtExpression, target: AsmAssignTarget) {
|
||||
// long to byte, can't use lsb() because that only works on words
|
||||
when(value) {
|
||||
is PtIdentifier -> {
|
||||
val longvar = asmgen.asmVariableName(value)
|
||||
asmgen.out(" lda $longvar")
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, true, false)
|
||||
}
|
||||
is PtNumber -> throw AssemblyError("casting a long number to byte should have been const-folded away ${value.position}")
|
||||
else -> {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
asmgen.out(" lda cx16.r14")
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, true, false)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun assignCastWordViaLsbFunc(value: PtExpression, target: AsmAssignTarget) {
|
||||
val lsb = PtFunctionCall("lsb", true, true,arrayOf(DataType.UBYTE), value.position)
|
||||
lsb.parent = value.parent
|
||||
lsb.add(value)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
|
||||
val assign = AsmAssignment(src, listOf(target), program.memsizer, value.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assign, value.definingISub())
|
||||
}
|
||||
|
||||
|
||||
internal fun assignCastViaLswFunc(value: PtExpression, target: AsmAssignTarget) {
|
||||
val lsb = PtFunctionCall("lsw", true, true, arrayOf(DataType.UWORD), value.position)
|
||||
lsb.parent = value.parent
|
||||
lsb.add(value)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = lsb)
|
||||
val assign = AsmAssignment(src, listOf(target), program.memsizer, value.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assign, value.definingISub())
|
||||
}
|
||||
|
||||
|
||||
internal fun assignWordToBool(value: PtExpression, target: AsmAssignTarget) {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
||||
asmgen.out("""
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ora P8ZP_SCRATCH_REG
|
||||
beq +
|
||||
lda #1
|
||||
+""")
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
|
||||
|
||||
internal fun assignTypeCastedFloatFAC1(targetAsmVarName: String, targetDt: BaseDataType) {
|
||||
|
||||
if(targetDt==BaseDataType.FLOAT)
|
||||
throw AssemblyError("typecast to identical type")
|
||||
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> asmgen.out(" jsr floats.cast_FAC1_as_bool_into_a | sta $targetAsmVarName")
|
||||
BaseDataType.UBYTE -> asmgen.out(" jsr floats.cast_FAC1_as_uw_into_ya | sty $targetAsmVarName")
|
||||
BaseDataType.BYTE -> asmgen.out(" jsr floats.cast_FAC1_as_w_into_ay | sta $targetAsmVarName")
|
||||
BaseDataType.UWORD -> asmgen.out(" jsr floats.cast_FAC1_as_uw_into_ya | sty $targetAsmVarName | sta $targetAsmVarName+1")
|
||||
BaseDataType.WORD -> asmgen.out(" jsr floats.cast_FAC1_as_w_into_ay | sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
jsr floats.QINT
|
||||
lda floats.FAC_ADDR+4
|
||||
sta $targetAsmVarName
|
||||
lda floats.FAC_ADDR+3
|
||||
sta $targetAsmVarName+1
|
||||
lda floats.FAC_ADDR+2
|
||||
sta $targetAsmVarName+2
|
||||
lda floats.FAC_ADDR+1
|
||||
sta $targetAsmVarName+3
|
||||
""".trimIndent())
|
||||
}
|
||||
else -> throw AssemblyError("weird type $targetDt")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal fun assignTypeCastedIdentifier(targetAsmVarName: String, targetDt: DataType,
|
||||
sourceAsmVarName: String, sourceDt: DataType) {
|
||||
if(sourceDt == targetDt)
|
||||
throw AssemblyError("typecast to identical type")
|
||||
|
||||
// also see: PtExpressionAsmGen, fun translateExpression(typecast: PtTypeCast)
|
||||
when {
|
||||
sourceDt.isUnsignedByte || sourceDt.isBool -> {
|
||||
when(targetDt.base) {
|
||||
BaseDataType.BOOL -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
beq +
|
||||
lda #1
|
||||
+ sta $targetAsmVarName""")
|
||||
}
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.POINTER -> TODO("byte should not be cast to pointer")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
sta $targetAsmVarName
|
||||
lda #0
|
||||
sta $targetAsmVarName+1
|
||||
sta $targetAsmVarName+2
|
||||
sta $targetAsmVarName+3""")
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy $sourceAsmVarName
|
||||
jsr floats.cast_from_ub""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type $targetDt")
|
||||
}
|
||||
}
|
||||
sourceDt.isSignedByte -> {
|
||||
when(targetDt.base) {
|
||||
BaseDataType.UBYTE, BaseDataType.BOOL -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
asmgen.signExtendVariableLsb(targetAsmVarName, BaseDataType.BYTE)
|
||||
}
|
||||
BaseDataType.POINTER -> TODO("byte should not be cast to pointer")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
asmgen.signExtendVariableLsb(targetAsmVarName, BaseDataType.BYTE)
|
||||
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda $sourceAsmVarName
|
||||
jsr floats.cast_from_b""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
sourceDt.isUnsignedWord -> {
|
||||
when(targetDt.base) {
|
||||
BaseDataType.BOOL -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
ora $sourceAsmVarName+1
|
||||
beq +
|
||||
lda #1
|
||||
+ sta $targetAsmVarName""")
|
||||
}
|
||||
BaseDataType.BYTE, BaseDataType.UBYTE -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.POINTER -> asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
sta $targetAsmVarName
|
||||
lda $sourceAsmVarName+1
|
||||
sta $targetAsmVarName+1
|
||||
lda #0
|
||||
sta $targetAsmVarName+2
|
||||
sta $targetAsmVarName+3""")
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda $sourceAsmVarName
|
||||
ldy $sourceAsmVarName+1
|
||||
jsr floats.cast_from_uw""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
sourceDt.isSignedWord || sourceDt.isPointer -> {
|
||||
when(targetDt.base) {
|
||||
BaseDataType.BOOL -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
ora $sourceAsmVarName+1
|
||||
beq +
|
||||
lda #1
|
||||
+ sta $targetAsmVarName""")
|
||||
}
|
||||
BaseDataType.BYTE, BaseDataType.UBYTE -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD, BaseDataType.POINTER -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
sta $targetAsmVarName
|
||||
lda $sourceAsmVarName+1
|
||||
sta $targetAsmVarName+1""")
|
||||
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda $sourceAsmVarName
|
||||
ldy $sourceAsmVarName+1
|
||||
jsr floats.cast_from_w""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
sourceDt.isFloat -> {
|
||||
asmgen.out(" lda #<$sourceAsmVarName | ldy #>$sourceAsmVarName")
|
||||
when(targetDt.base) {
|
||||
BaseDataType.BOOL -> asmgen.out(" jsr floats.cast_as_bool_into_a | sta $targetAsmVarName")
|
||||
BaseDataType.UBYTE -> asmgen.out(" jsr floats.cast_as_uw_into_ya | sty $targetAsmVarName")
|
||||
BaseDataType.BYTE -> asmgen.out(" jsr floats.cast_as_w_into_ay | sta $targetAsmVarName")
|
||||
BaseDataType.UWORD -> asmgen.out(" jsr floats.cast_as_uw_into_ya | sty $targetAsmVarName | sta $targetAsmVarName+1")
|
||||
BaseDataType.WORD -> asmgen.out(" jsr floats.cast_as_w_into_ay | sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
sta cx16.r0L
|
||||
sty cx16.r0H
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
jsr floats.cast_as_long""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
sourceDt.isString -> throw AssemblyError("cannot typecast a string value")
|
||||
sourceDt.isLong -> {
|
||||
if(targetDt.isByteOrBool) {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
} else if(targetDt.isWord || targetDt.isPointer) {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
|
||||
} else if(targetDt.isFloat) {
|
||||
asmgen.out("""
|
||||
lda #<$sourceAsmVarName
|
||||
ldy #>$sourceAsmVarName
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
jsr floats.internal_long_R1_to_float_AY""")
|
||||
} else
|
||||
throw AssemblyError("weird type")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal fun assignTypeCastedRegisters(targetAsmVarName: String, targetDt: BaseDataType,
|
||||
regs: RegisterOrPair, sourceDt: BaseDataType, position: Position) {
|
||||
if(sourceDt == targetDt && sourceDt != BaseDataType.POINTER)
|
||||
throw AssemblyError("typecast to identical type")
|
||||
|
||||
// also see: PtExpressionAsmGen, fun translateExpression(typecast: PtTypeCast)
|
||||
when(sourceDt) {
|
||||
BaseDataType.BOOL -> {
|
||||
if (targetDt.isByteOrBool) asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||
else throw AssemblyError("assign bool to non-byte variable")
|
||||
}
|
||||
BaseDataType.UBYTE -> {
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> {
|
||||
val compare = if(regs==RegisterOrPair.A) "cmp" else "cp${regs.toString().lowercase()}"
|
||||
asmgen.out("""
|
||||
$compare #0
|
||||
beq +
|
||||
ld${regs.toString().lowercase()} #1
|
||||
+ st${regs.toString().lowercase()} $targetAsmVarName""")
|
||||
}
|
||||
BaseDataType.BYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
st${regs.toString().lowercase()} $targetAsmVarName
|
||||
lda #0
|
||||
sta $targetAsmVarName+1
|
||||
sta $targetAsmVarName+2
|
||||
sta $targetAsmVarName+3""")
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.A -> asmgen.out(" tay")
|
||||
RegisterOrPair.X -> asmgen.out(" txa | tay")
|
||||
RegisterOrPair.Y -> {}
|
||||
else -> throw AssemblyError("non-byte regs")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jsr floats.cast_from_ub""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.BYTE -> {
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> TODO("assign byte to bool $position")
|
||||
BaseDataType.UBYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.WORD, BaseDataType.POINTER -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.A -> {}
|
||||
RegisterOrPair.X -> asmgen.out(" txa")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya")
|
||||
else -> throw AssemblyError("non-byte regs")
|
||||
}
|
||||
asmgen.signExtendAYlsb(sourceDt)
|
||||
asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||
asmgen.signExtendLongVariable(targetAsmVarName, sourceDt)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.A -> {}
|
||||
RegisterOrPair.X -> asmgen.out(" txa")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya")
|
||||
else -> throw AssemblyError("non-byte regs")
|
||||
}
|
||||
asmgen.out("""
|
||||
ldy #<$targetAsmVarName
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy #>$targetAsmVarName
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
jsr floats.cast_from_b""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> TODO("assign uword to bool $position")
|
||||
BaseDataType.BYTE, BaseDataType.UBYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.WORD, BaseDataType.POINTER -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
asmgen.out(" lda #0 | sta $targetAsmVarName+2 | sta $targetAsmVarName+3")
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
if(regs!=RegisterOrPair.AY)
|
||||
throw AssemblyError("only supports AY here")
|
||||
asmgen.out("""
|
||||
tax
|
||||
lda #<$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
txa
|
||||
jsr floats.cast_from_uw""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> TODO("assign word to bool $position")
|
||||
BaseDataType.BYTE, BaseDataType.UBYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD, BaseDataType.POINTER -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
if(regs!=RegisterOrPair.AY)
|
||||
throw AssemblyError("only supports AY here")
|
||||
asmgen.out("""
|
||||
tax
|
||||
lda #<$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
txa
|
||||
jsr floats.cast_from_w""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.STR -> throw AssemblyError("cannot typecast a string value")
|
||||
BaseDataType.POINTER -> {
|
||||
if(targetDt.isWord || targetDt.isPointer) {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
} else if(targetDt.isLong) {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
asmgen.out(" lda #0 | sta $targetAsmVarName+2 | sta $targetAsmVarName+3")
|
||||
} else {
|
||||
throw AssemblyError("cannot assign pointer to $targetDt")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
val startreg = regs.startregname()
|
||||
if(targetDt.isByteOrBool) {
|
||||
asmgen.out(" lda cx16.$startreg | sta $targetAsmVarName")
|
||||
} else if(targetDt.isWord || targetDt.isPointer) {
|
||||
asmgen.out(" lda cx16.$startreg | sta $targetAsmVarName | lda cx16.$startreg+1 | sta $targetAsmVarName+1")
|
||||
} else if(targetDt.isFloat) {
|
||||
if(regs==RegisterOrPair.R0R1)
|
||||
throw AssemblyError("cannot assign long in R0R1 to float because r1 is used as a parameter $position")
|
||||
asmgen.out("""
|
||||
lda #<cx16.$startreg
|
||||
ldy #>cx16.$startreg
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
jsr floats.internal_long_R1_to_float_AY""")
|
||||
} else
|
||||
throw AssemblyError("weird type $targetDt")
|
||||
}
|
||||
else -> throw AssemblyError("weird type $sourceDt")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute 8-bit offset Y = idx*structSize + fieldOffset for a struct-array element.
|
||||
* Assumes totalBytes <=256 so offset fits in Y. Uses A/Y and multiply_bytes when needed.
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package prog8tests.codegencpu6502
|
||||
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal object DummyMemsizer : IMemSizer {
|
||||
override fun memorySize(dt: DataType, numElements: Int?): Int {
|
||||
if(dt.isPointerArray)
|
||||
return 2 * numElements!!
|
||||
else if(dt.isArray) {
|
||||
require(numElements != null)
|
||||
return when(dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
|
||||
BaseDataType.LONG -> numElements*4
|
||||
BaseDataType.FLOAT -> numElements*5
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isFloat -> 5 * (numElements ?: 1)
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun memorySize(dt: BaseDataType): Int {
|
||||
return memorySize(DataType.forDt(dt), null)
|
||||
}
|
||||
}
|
||||
|
||||
internal object DummyStringEncoder : IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
|
||||
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
val infos = mutableListOf<String>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in errors)
|
||||
errors.add(text)
|
||||
}
|
||||
|
||||
override fun warn(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in warnings)
|
||||
warnings.add(text)
|
||||
}
|
||||
|
||||
override fun info(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in infos)
|
||||
infos.add(text)
|
||||
}
|
||||
|
||||
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
|
||||
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
|
||||
|
||||
override fun report() {
|
||||
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
|
||||
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||
if(throwExceptionAtReportIfErrors)
|
||||
finalizeNumErrors(errors.size, warnings.size, infos.size)
|
||||
if(!keepMessagesAfterReporting) {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
errors.clear()
|
||||
warnings.clear()
|
||||
infos.clear()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,786 @@
|
||||
package prog8tests.codegencpu6502
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.StMemVar
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.codegen.cpu6502.*
|
||||
|
||||
|
||||
class TestAsmOptimizer: FunSpec({
|
||||
|
||||
val machine = C64Target()
|
||||
val program = PtProgram("test", VMTarget())
|
||||
val symbolTable = SymbolTable(program)
|
||||
|
||||
// Add test variables with known non-IO addresses so the optimizer can resolve them
|
||||
// Non-IO addresses in typical C64 RAM range
|
||||
symbolTable.add(StMemVar("myvar", DataType.UBYTE, 0x0300u, null, null))
|
||||
symbolTable.add(StMemVar("var", DataType.UBYTE, 0x0301u, null, null))
|
||||
symbolTable.add(StMemVar("array", DataType.arrayFor(BaseDataType.UBYTE, machine), 0x0302u, 16u, null))
|
||||
symbolTable.add(StMemVar("wordarray", DataType.arrayFor(BaseDataType.UWORD, machine), 0x0310u, 32u, null))
|
||||
symbolTable.add(StMemVar("nswordarray", DataType.arrayFor(BaseDataType.UWORD, machine), 0x0320u, 32u, null))
|
||||
symbolTable.add(StMemVar("var1", DataType.UWORD, 0x0400u, null, null))
|
||||
symbolTable.add(StMemVar("var2", DataType.UWORD, 0x0402u, null, null))
|
||||
symbolTable.add(StMemVar("ptr", DataType.UWORD, 0x0404u, null, null))
|
||||
symbolTable.add(StMemVar("P8ZP_SCRATCH_PTR", DataType.UWORD, 0x02u, null, null))
|
||||
symbolTable.add(StMemVar("P8ZP_SCRATCH_W1", DataType.UWORD, 0x04u, null, null))
|
||||
symbolTable.add(StMemVar("A1", DataType.UWORD, 0x0410u, null, null))
|
||||
symbolTable.add(StMemVar("A2", DataType.UWORD, 0x0412u, null, null))
|
||||
|
||||
fun optimize(lines: MutableList<String>) {
|
||||
optimizeAssembly(lines, machine, symbolTable)
|
||||
}
|
||||
|
||||
// --- optimizeIncDec ---
|
||||
|
||||
test("optimizeIncDec: removes iny+dey sequence") {
|
||||
val lines = mutableListOf(" iny", " dey", " rts", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes iny+dey sequence with comments") {
|
||||
val lines = mutableListOf(" iny ; increment Y", " dey ; decrement Y", " rts", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes inx+dex sequence") {
|
||||
val lines = mutableListOf(" inx", " dex", " rts", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes dey+iny sequence") {
|
||||
val lines = mutableListOf(" dey", " iny", " rts", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes dex+inx sequence") {
|
||||
val lines = mutableListOf(" dex", " inx", " rts", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: preserves non-canceling sequences") {
|
||||
val lines = mutableListOf(" iny", " inx", " rts", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" iny", " inx", " rts", " nop")
|
||||
}
|
||||
|
||||
// --- optimizeStoreLoadSame ---
|
||||
|
||||
test("optimizeStoreLoadSame: removes sta X + lda X") {
|
||||
val lines = mutableListOf(" lda #42", " sta myvar", " lda myvar", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda #42", " sta myvar", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes sta X + lda X with comments") {
|
||||
val lines = mutableListOf(" nop", " sta myvar", " lda myvar", " rts ; return")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " sta myvar", " rts ; return")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes pha + pla") {
|
||||
val lines = mutableListOf(" nop", " pha", " pla", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes phx + plx") {
|
||||
val lines = mutableListOf(" nop", " phx", " plx", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes phy + ply") {
|
||||
val lines = mutableListOf(" nop", " phy", " ply", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes lda X + sta X") {
|
||||
val lines = mutableListOf(" nop", " lda myvar", " sta myvar", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " lda myvar", " rts")
|
||||
}
|
||||
|
||||
// --- optimizeJsrRtsAndOtherCombinations ---
|
||||
|
||||
test("optimizeJsrRts: converts jsr+rts to jmp") {
|
||||
val lines = mutableListOf(" jsr mysub", " rts", " nop", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" jmp mysub", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: converts jsr+rts to jmp with comments") {
|
||||
val lines = mutableListOf(" jsr mysub ; call subroutine", " rts ; return", " nop", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" jmp mysub ; call subroutine", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes rts+jmp") {
|
||||
val lines = mutableListOf(" rts", " jmp somewhere", " nop", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes rts+bra") {
|
||||
val lines = mutableListOf(" rts", " bra somewhere", " nop", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes lda+cmp #0") {
|
||||
val lines = mutableListOf(" lda myvar", " cmp #0", " beq skip", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda myvar", " beq skip", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes lda+cmp #0 with comments") {
|
||||
val lines = mutableListOf(" lda myvar ; load variable", " cmp #0 ; compare to zero", " beq skip ; branch if equal", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda myvar ; load variable", " beq skip ; branch if equal", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes bra+jmp") {
|
||||
val lines = mutableListOf(" bra somewhere", " jmp elsewhere", " nop", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" bra somewhere", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: preserves jsr+rts for floats.pushFAC") {
|
||||
val lines = mutableListOf(" jsr floats.pushFAC", " rts", " nop", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" jsr floats.pushFAC", " rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: three consecutive jsr only tail jsr+rts becomes jmp") {
|
||||
val lines = mutableListOf($$" jsr $4000", $$" jsr $5000", $$" jsr $6000", " rts", " nop", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf($$" jsr $4000", $$" jsr $5000", $$" jmp $6000", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: single line with embedded newlines only tail converted defensively") {
|
||||
val lines = mutableListOf($$" jsr $4000\n jsr $5000\n jsr $6000", " rts", " nop", " nop")
|
||||
optimize(lines)
|
||||
// Before fix this became jmp/jmp/jmp via replace("jsr","jmp"); after fix only one jmp via replaceMnemonic
|
||||
val jmpCount = lines.joinToString("\n").split("jmp").size - 1
|
||||
jmpCount shouldBe 1
|
||||
}
|
||||
|
||||
// --- optimizeUselessPushPopStack ---
|
||||
|
||||
test("optimizeUselessPushPop: removes phy+ldy+pla") {
|
||||
val lines = mutableListOf(" phy", " ldy #5", " pla", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" tya", " ldy #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes phx+ldx+pla") {
|
||||
val lines = mutableListOf(" phx", " ldx #5", " pla", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" txa", " ldx #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes pha+lda+pla") {
|
||||
// Note: optimizer removes all three: pha, lda, and pla
|
||||
val lines = mutableListOf(" pha", " lda #5", " pla", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes pha+tya+tay+pla") {
|
||||
val lines = mutableListOf(" pha", " tya", " tay", " pla", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
// --- optimizeUnneededTempvarInAdd ---
|
||||
|
||||
test("optimizeUnneededTempvarInAdd: removes scratch variable in add") {
|
||||
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" clc", " adc #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUnneededTempvarInAdd: removes scratch variable in add with comments") {
|
||||
// Note: comments don't affect the optimization
|
||||
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts ; return")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" clc", " adc #5", " rts ; return")
|
||||
}
|
||||
|
||||
// --- optimizeTSBtoRegularOr ---
|
||||
|
||||
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta") {
|
||||
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts")
|
||||
}
|
||||
|
||||
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta with comments") {
|
||||
// Note: comments don't affect the optimization
|
||||
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts ; return")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts ; return")
|
||||
}
|
||||
|
||||
// --- optimizeSameAssignments ---
|
||||
|
||||
test("optimizeSameAssignments: removes duplicate lda/ldy/sta/sty word init") {
|
||||
val lines = mutableListOf(
|
||||
" lda #1", " ldy #0", " sta var1", " sty var1+1",
|
||||
" lda #1", " ldy #0", " sta var2", " sty var2+1",
|
||||
" rts", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" lda #1", " ldy #0", " sta var1", " sty var1+1",
|
||||
" sta var2", " sty var2+1",
|
||||
" rts", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
}
|
||||
|
||||
// --- optimizeSamePointerIndexingAndUselessBeq ---
|
||||
|
||||
test("optimizeSamePointerIndexing: removes redundant ldy in pointer indexing") {
|
||||
val lines = mutableListOf(
|
||||
" ldy #0", " lda (ptr),y", " ora #3", " ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora #3", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
// --- optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore ---
|
||||
|
||||
test("optimizeAddWordToSameVariable: optimizes P8ZP_SCRATCH_PTR += AY") {
|
||||
val lines = mutableListOf(
|
||||
" clc", " adc P8ZP_SCRATCH_PTR", " pha", " tya",
|
||||
" adc P8ZP_SCRATCH_PTR+1", " tay", " pla",
|
||||
" sta P8ZP_SCRATCH_PTR", " sty P8ZP_SCRATCH_PTR+1", " rts",
|
||||
" nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" clc", " adc P8ZP_SCRATCH_PTR", " sta P8ZP_SCRATCH_PTR",
|
||||
" tya", " adc P8ZP_SCRATCH_PTR+1", " sta P8ZP_SCRATCH_PTR+1", " rts",
|
||||
" nop", " nop", " nop", " nop"
|
||||
)
|
||||
}
|
||||
|
||||
test("optimizeExtraRegisterLoadInWordStore: removes ldx+sta+txa+ldy+sta") {
|
||||
val lines = mutableListOf(" ldx #42", " sta myvar", " txa", " ldy #1", " sta myvar+1", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" sta myvar", " lda #42", " ldy #1", " sta myvar+1", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
// --- optimizeFoldIndexOffset (byte arrays) ---
|
||||
|
||||
test("optimizeFoldIndexOffset: folds byte array lda+clc+adc+tay+lda to ldy+lda") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #3", " tay", " lda array,y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldy myvar"
|
||||
lines[1] shouldBe " lda array+3,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: folds with x register") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #5", " tax", " lda array,x",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldx var"
|
||||
lines[1] shouldBe " lda array+5,x"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: works with sta instead of lda") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #1", " tay", " sta array,y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldy myvar"
|
||||
lines[1] shouldBe " sta array+1,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: does not fold indirect indexed (ptr),y") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #3", " tay", " lda (ptr),y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines.size shouldBe 10
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: preserves hex constant format") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #$0f", " tay", " lda array,y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldy myvar"
|
||||
lines[1] shouldBe " lda array+$0f,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: does not fold when 5th op is not absolute indexed") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #3", " tay", " nop",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines.size shouldBe 10
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: works with cmp instruction") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #7", " tay", " cmp array,y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldy myvar"
|
||||
lines[1] shouldBe " cmp array+7,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: preserves label on first instruction") {
|
||||
val lines = mutableListOf(
|
||||
"mylabel: lda myvar", " clc", " adc #3", " tay", " lda array,y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe "mylabel: ldy myvar"
|
||||
lines[1] shouldBe " lda array+3,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
// --- optimizeFoldIndexOffsetSplitWord (split word arrays) ---
|
||||
|
||||
test("optimizeFoldIndexOffsetSplitWord: folds split word array access with y") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " tay",
|
||||
" lda wordarray_lsb,y", " ldx wordarray_msb,y",
|
||||
" rts", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldy var"
|
||||
lines[1] shouldBe " lda wordarray_lsb+3,y"
|
||||
lines[2] shouldBe " ldx wordarray_msb+3,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffsetSplitWord: folds split word array access with x") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " tax",
|
||||
" lda wordarray_lsb,x", " ldx wordarray_msb,x",
|
||||
" rts", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldx var"
|
||||
lines[1] shouldBe " lda wordarray_lsb+3,x"
|
||||
lines[2] shouldBe " ldx wordarray_msb+3,x"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffsetSplitWord: does not fold mismatched registers") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " tay",
|
||||
" lda wordarray_lsb,y", " ldx wordarray_msb,x",
|
||||
" rts", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines.size shouldBe 10
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffsetSplitWord: does not fold non-lsb-msb pair") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " tay",
|
||||
" lda wordarray_lsb,y", " ldx other,y",
|
||||
" rts", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines.size shouldBe 10
|
||||
}
|
||||
|
||||
// --- optimizeFoldIndexOffsetNonSplitWord (non-split word arrays) ---
|
||||
|
||||
test("optimizeFoldIndexOffsetNonSplitWord: folds non-split word array access") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " asl a", " tay",
|
||||
" lda nswordarray,y", " ldx nswordarray+1,y",
|
||||
" rts", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " lda var"
|
||||
lines[1] shouldBe " asl a"
|
||||
lines[2] shouldBe " tay"
|
||||
lines[3] shouldBe " lda nswordarray+6,y"
|
||||
lines[4] shouldBe " ldx nswordarray+1+6,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffsetNonSplitWord: scales hex constants") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #$02", " asl a", " tay",
|
||||
" lda nswordarray,y", " ldx nswordarray+1,y",
|
||||
" rts", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[3] shouldBe " lda nswordarray+$04,y"
|
||||
lines[4] shouldBe " ldx nswordarray+1+$04,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffsetNonSplitWord: does not fold without asl a") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " tay",
|
||||
" lda nswordarray,y", " ldx nswordarray+1,y",
|
||||
" rts", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines.size shouldBe 9
|
||||
}
|
||||
|
||||
// --- Edge cases ---
|
||||
|
||||
test("handles empty input") {
|
||||
val lines = mutableListOf<String>()
|
||||
optimize(lines)
|
||||
lines shouldBe emptyList()
|
||||
}
|
||||
|
||||
test("handles single line input") {
|
||||
val lines = mutableListOf(" rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
test("multiple optimizations in one pass") {
|
||||
val lines = mutableListOf(" iny", " dey", " lda #1", " sta var", " lda var", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda #1", " sta var", " rts")
|
||||
}
|
||||
|
||||
// --- Helper function tests ---
|
||||
|
||||
// hasInstr tests
|
||||
test("hasInstr: exact match") {
|
||||
"iny".hasInstr("iny") shouldBe true
|
||||
"pla".hasInstr("pla") shouldBe true
|
||||
"rts".hasInstr("rts") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: starts with mnemonic and space") {
|
||||
"lda #1".hasInstr("lda") shouldBe true
|
||||
"sta myvar".hasInstr("sta") shouldBe true
|
||||
"ldy #0".hasInstr("ldy") shouldBe true
|
||||
"inc a".hasInstr("inc") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: contains mnemonic after label") {
|
||||
"mylabel: lda #1".hasInstr("lda") shouldBe true
|
||||
"loop: iny".hasInstr("iny") shouldBe true
|
||||
"skip: rts".hasInstr("rts") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: does not match named labels without colon") {
|
||||
// Named labels require a colon to distinguish from instructions
|
||||
"mylabel lda #1".hasInstr("lda") shouldBe false
|
||||
"loop iny".hasInstr("iny") shouldBe false
|
||||
"skip rts".hasInstr("rts") shouldBe false
|
||||
}
|
||||
|
||||
test("hasInstr: handles 64tass anonymous labels with colon") {
|
||||
"+: lda #1".hasInstr("lda") shouldBe true
|
||||
"++: lda #1".hasInstr("lda") shouldBe true
|
||||
"-: lda #1".hasInstr("lda") shouldBe true
|
||||
"--: lda #1".hasInstr("lda") shouldBe true
|
||||
"+: rts".hasInstr("rts") shouldBe true
|
||||
"+++: iny".hasInstr("iny") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: handles 64tass anonymous labels without colon") {
|
||||
"+ lda #1".hasInstr("lda") shouldBe true
|
||||
"++ lda #1".hasInstr("lda") shouldBe true
|
||||
"- lda #1".hasInstr("lda") shouldBe true
|
||||
"-- lda #1".hasInstr("lda") shouldBe true
|
||||
"+ rts".hasInstr("rts") shouldBe true
|
||||
"+++ iny".hasInstr("iny") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: handles scoped labels with periods") {
|
||||
"mysub.loop: lda #1".hasInstr("lda") shouldBe true
|
||||
"_private: rts".hasInstr("rts") shouldBe true
|
||||
// Without colon, named labels are not recognized
|
||||
"mysub.loop lda #1".hasInstr("lda") shouldBe false
|
||||
"_private rts".hasInstr("rts") shouldBe false
|
||||
}
|
||||
|
||||
test("hasInstr: does not match numbers as labels") {
|
||||
"1 lda #1".hasInstr("lda") shouldBe false
|
||||
"+1 lda #1".hasInstr("lda") shouldBe false
|
||||
"-1 lda #1".hasInstr("lda") shouldBe false
|
||||
}
|
||||
|
||||
test("hasInstr: does not match partial mnemonics") {
|
||||
"lda #1".hasInstr("ld") shouldBe false
|
||||
"sta var".hasInstr("st") shouldBe false
|
||||
"iny".hasInstr("in") shouldBe false
|
||||
}
|
||||
|
||||
test("hasInstr: does not match mnemonic in operand") {
|
||||
// Note: hasInstr uses contains(" mnemonic") which can match operands
|
||||
// This is a known limitation - the optimizer only calls hasInstr on
|
||||
// trimmed lines where the instruction is at the start or after a label
|
||||
"lda #iny".hasInstr("iny") shouldBe false
|
||||
// "sta pla" would match due to contains(" pla") - this is expected behavior
|
||||
// in the optimizer context where lines are properly formatted
|
||||
}
|
||||
|
||||
test("hasInstr: handles inc a variants") {
|
||||
"inc a".hasInstr("inc a") shouldBe true
|
||||
"inc a".hasInstr("inc a") shouldBe true
|
||||
"inc var".hasInstr("inc a") shouldBe false
|
||||
}
|
||||
|
||||
test("hasInstr: handles lines with trailing comments") {
|
||||
"iny ; increment Y".hasInstr("iny") shouldBe true
|
||||
"lda #1 ; load immediate".hasInstr("lda") shouldBe true
|
||||
"mylabel: rts ; return".hasInstr("rts") shouldBe true
|
||||
// Note: hasInstr expects trimmed lines, so leading whitespace must be removed first
|
||||
"dey ; decrement Y".hasInstr("dey") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: handles dec a variants") {
|
||||
"dec a".hasInstr("dec a") shouldBe true
|
||||
"dec a".hasInstr("dec a") shouldBe true
|
||||
"dec var".hasInstr("dec a") shouldBe false
|
||||
}
|
||||
|
||||
// isBranch tests
|
||||
test("isBranch: matches branch instructions") {
|
||||
"beq skip".isBranch() shouldBe true
|
||||
"bne loop".isBranch() shouldBe true
|
||||
"bcc target".isBranch() shouldBe true
|
||||
"bcs target".isBranch() shouldBe true
|
||||
"bmi neg".isBranch() shouldBe true
|
||||
"bpl pos".isBranch() shouldBe true
|
||||
"bvc noov".isBranch() shouldBe true
|
||||
"bvs ov".isBranch() shouldBe true
|
||||
"bra somewhere".isBranch() shouldBe true
|
||||
}
|
||||
|
||||
test("isBranch: matches branch instructions with trailing comments") {
|
||||
"beq skip ; branch if equal".isBranch() shouldBe true
|
||||
"bne loop ; branch if not equal".isBranch() shouldBe true
|
||||
"bra target ; always branch".isBranch() shouldBe true
|
||||
}
|
||||
|
||||
test("isBranch: does not match non-branch instructions") {
|
||||
"lda #1".isBranch() shouldBe false
|
||||
"sta var".isBranch() shouldBe false
|
||||
"rts".isBranch() shouldBe false
|
||||
"nop".isBranch() shouldBe false
|
||||
}
|
||||
|
||||
// isStoreReg tests
|
||||
test("isStoreReg: matches store instructions") {
|
||||
"sta var".isStoreReg() shouldBe true
|
||||
"sty var".isStoreReg() shouldBe true
|
||||
"stx var".isStoreReg() shouldBe true
|
||||
}
|
||||
|
||||
test("isStoreReg: matches store instructions with trailing comments") {
|
||||
"sta var ; store accumulator".isStoreReg() shouldBe true
|
||||
"sty myvar ; store Y".isStoreReg() shouldBe true
|
||||
"stx counter ; store X".isStoreReg() shouldBe true
|
||||
}
|
||||
|
||||
test("isStoreReg: does not match non-store instructions") {
|
||||
"lda var".isStoreReg() shouldBe false
|
||||
"ldy var".isStoreReg() shouldBe false
|
||||
"ldx var".isStoreReg() shouldBe false
|
||||
"sta".isStoreReg() shouldBe false // no space after
|
||||
}
|
||||
|
||||
// isStoreRegOrZero tests
|
||||
test("isStoreRegOrZero: matches store and stz") {
|
||||
"sta var".isStoreRegOrZero() shouldBe true
|
||||
"sty var".isStoreRegOrZero() shouldBe true
|
||||
"stx var".isStoreRegOrZero() shouldBe true
|
||||
"stz var".isStoreRegOrZero() shouldBe true
|
||||
}
|
||||
|
||||
test("isStoreRegOrZero: does not match non-store") {
|
||||
"lda var".isStoreRegOrZero() shouldBe false
|
||||
"stz".isStoreRegOrZero() shouldBe false // no space after
|
||||
}
|
||||
|
||||
// isLoadReg tests
|
||||
test("isLoadReg: matches load instructions") {
|
||||
"lda var".isLoadReg() shouldBe true
|
||||
"ldy var".isLoadReg() shouldBe true
|
||||
"ldx var".isLoadReg() shouldBe true
|
||||
}
|
||||
|
||||
test("isLoadReg: matches load instructions with trailing comments") {
|
||||
"lda var ; load accumulator".isLoadReg() shouldBe true
|
||||
"ldy #5 ; load Y".isLoadReg() shouldBe true
|
||||
"ldx counter ; load X".isLoadReg() shouldBe true
|
||||
}
|
||||
|
||||
test("isLoadReg: does not match non-load instructions") {
|
||||
"sta var".isLoadReg() shouldBe false
|
||||
"sty var".isLoadReg() shouldBe false
|
||||
"stx var".isLoadReg() shouldBe false
|
||||
"lda".isLoadReg() shouldBe false // no space after
|
||||
}
|
||||
|
||||
// haslabel tests
|
||||
test("haslabel: detects labels") {
|
||||
haslabel("mylabel:") shouldBe true
|
||||
haslabel("loop:") shouldBe true
|
||||
haslabel("mylabel: lda #1") shouldBe true
|
||||
haslabel("loop: iny") shouldBe true
|
||||
}
|
||||
|
||||
test("haslabel: detects labels with trailing comments") {
|
||||
haslabel("mylabel: ; this is a label") shouldBe true
|
||||
haslabel("loop: lda #1 ; load immediate") shouldBe true
|
||||
haslabel("skip: ; skip ahead") shouldBe true
|
||||
}
|
||||
|
||||
test("haslabel: does not detect non-labels") {
|
||||
haslabel(" lda #1") shouldBe false
|
||||
haslabel(" iny") shouldBe false
|
||||
haslabel("; comment") shouldBe false
|
||||
haslabel("") shouldBe false
|
||||
}
|
||||
|
||||
test("haslabel: handles labels with leading whitespace") {
|
||||
haslabel(" label:") shouldBe true
|
||||
haslabel("\tlabel:") shouldBe true
|
||||
}
|
||||
|
||||
// keeplabel tests
|
||||
test("keeplabel: extracts label from line") {
|
||||
keeplabel("mylabel: lda #1") shouldBe "mylabel:"
|
||||
keeplabel("loop: iny") shouldBe "loop:"
|
||||
keeplabel("mylabel:") shouldBe "mylabel:"
|
||||
}
|
||||
|
||||
test("keeplabel: extracts label from line with trailing comment") {
|
||||
keeplabel("mylabel: lda #1 ; load immediate") shouldBe "mylabel:"
|
||||
keeplabel("loop: ; just a label") shouldBe "loop:"
|
||||
keeplabel("skip: rts ; return") shouldBe "skip:"
|
||||
}
|
||||
|
||||
test("keeplabel: returns colon-only for non-label lines with leading whitespace") {
|
||||
// keeplabel splits on first whitespace and returns first part + ":"
|
||||
// For " lda #1", first part is empty string, so returns ":"
|
||||
keeplabel(" lda #1") shouldBe ":"
|
||||
keeplabel(" iny") shouldBe ":"
|
||||
}
|
||||
|
||||
test("keeplabel: handles tab-separated labels") {
|
||||
keeplabel("mylabel\tlda #1") shouldBe "mylabel:"
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// KNOWN BUGS - These tests demonstrate current optimizer defects.
|
||||
// See AsmOptimizer.kt header for the full list.
|
||||
// ========================================================================
|
||||
|
||||
test("float copy init to different destinations should not be removed") {
|
||||
val lines = mutableListOf(
|
||||
// Lines 0-6: first float copy
|
||||
" lda #1", " ldy #0", " sta floatA", " sty floatA+1",
|
||||
" lda #1", " ldy #0", " jsr floats.copy_float",
|
||||
// Lines 7-13: second float copy to DIFFERENT destination
|
||||
" lda #1", " ldy #0", " sta floatB", " sty floatB+1",
|
||||
" lda #1", " ldy #0", " jsr floats.copy_float"
|
||||
)
|
||||
val beforeOpt = lines.toList()
|
||||
optimize(lines)
|
||||
lines shouldBe beforeOpt
|
||||
}
|
||||
|
||||
test("variable name containing 'y' should not trigger false positive") {
|
||||
val lines = mutableListOf(
|
||||
" ldy #0", " lda (ptr),y", " ora myvar", // 'y' in "myvar" no longer triggers false positive
|
||||
" ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
// The second ldy should be removed since "ora" doesn't modify Y
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora myvar", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
test("actual Y-modifying instructions should still be detected") {
|
||||
val lines = mutableListOf(
|
||||
" ldy #0", " lda (ptr),y", " tay", // tay modifies Y, so ldy cannot be removed
|
||||
" ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
// The second ldy should NOT be removed since "tay" modifies Y
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" ldy #0", " lda (ptr),y", " tay",
|
||||
" ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
test("modifiesYRegister: correctly identifies Y-modifying instructions") {
|
||||
" ldy #0".modifiesYRegister() shouldBe true
|
||||
" tay".modifiesYRegister() shouldBe true
|
||||
" iny".modifiesYRegister() shouldBe true
|
||||
" dey".modifiesYRegister() shouldBe true
|
||||
" tya".modifiesYRegister() shouldBe true
|
||||
" phy".modifiesYRegister() shouldBe true
|
||||
" ply".modifiesYRegister() shouldBe true
|
||||
" sty var".modifiesYRegister() shouldBe true
|
||||
" ora myvar".modifiesYRegister() shouldBe false // false positive fixed
|
||||
" lda #0".modifiesYRegister() shouldBe false
|
||||
" cpy #0".modifiesYRegister() shouldBe false // compare doesn't modify
|
||||
" jsr func".modifiesYRegister() shouldBe false
|
||||
}
|
||||
|
||||
test("compiler-prefixed memory-mapped IO variable accesses are not optimized away") {
|
||||
symbolTable.add(StMemVar("p8b_main.p8v_io_data", DataType.UBYTE, 0xd021u, null, null))
|
||||
symbolTable.add(StMemVar("p8b_main.p8v_result", DataType.UBYTE, 0x0300u, null, null))
|
||||
val lines = mutableListOf(
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" rts", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines.size shouldBe 8 // all instructions preserved
|
||||
lines shouldBe listOf(
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" rts", " nop"
|
||||
)
|
||||
}
|
||||
})
|
||||
Loaded 100 of 737 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user