mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 20:11:17 +00:00
Compare commits
1184 Commits
0.4ubuntu2
...
cbranch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
058d637407 | ||
|
|
01335f6862 | ||
|
|
d20d957881 | ||
|
|
008274cda5 | ||
|
|
e07ab7547f | ||
|
|
cf10837eb8 | ||
|
|
291b26f230 | ||
|
|
08e8c9bf57 | ||
|
|
625152440c | ||
|
|
fbd51821d1 | ||
|
|
c3ebf51295 | ||
|
|
9a9ff7f32c | ||
|
|
7b73eec82b | ||
|
|
75ba4a1cdb | ||
|
|
e5ca9065bd | ||
|
|
1042d0825f | ||
|
|
17942925f5 | ||
|
|
7424317d03 | ||
|
|
dbf1d91961 | ||
|
|
eb1644b302 | ||
|
|
cde5bc3263 | ||
|
|
e995e289db | ||
|
|
5020caa9c3 | ||
|
|
f34eb7d9f3 | ||
|
|
bf74ef0e5e | ||
|
|
0ff52311c3 | ||
|
|
e453e6f9ca | ||
|
|
6078598aff | ||
|
|
9fdb3b3b4a | ||
|
|
1362aa655f | ||
|
|
9c22ab8925 | ||
|
|
ca2dbb2f4b | ||
|
|
16bbc5a026 | ||
|
|
627f10cd18 | ||
|
|
13509b9231 | ||
|
|
ca88afbf5b | ||
|
|
42c9c9894b | ||
|
|
b4de62cfc2 | ||
|
|
226fbc191b | ||
|
|
4839d8861d | ||
|
|
789b47d565 | ||
|
|
c13cdcdd36 | ||
|
|
4ae3d0150f | ||
|
|
7d153a162a | ||
|
|
7bc3e94ff3 | ||
|
|
9c1fb26660 | ||
|
|
3dd725a0f0 | ||
|
|
a8aad53038 | ||
|
|
68c60f58c0 | ||
|
|
c9c3f33acc | ||
|
|
5ffc5187eb | ||
|
|
5d31cdfa16 | ||
|
|
e8ff4af5b9 | ||
|
|
723e1dd9a6 | ||
|
|
369a4558a3 | ||
|
|
5e6a3d9d8e | ||
|
|
62dca4f1c5 | ||
|
|
061c02306f | ||
|
|
f006b05010 | ||
|
|
c5a727aa9b | ||
|
|
e67e9d3bbf | ||
|
|
ea86c9d37a | ||
|
|
d1a2e6e5bd | ||
|
|
c96985f1db | ||
|
|
b7010c83e0 | ||
|
|
20a3d19ac7 | ||
|
|
0ded93ab9b | ||
|
|
ec82ec0426 | ||
|
|
6281f8ff89 | ||
|
|
2c9d2d4fd7 | ||
|
|
8569239bc1 | ||
|
|
5463c398cb | ||
|
|
7381734913 | ||
|
|
462322026f | ||
|
|
b5e5c719ed | ||
|
|
a0f4976b07 | ||
|
|
44c2486a74 | ||
|
|
5fc6672784 | ||
|
|
bcb1edba90 | ||
|
|
8099e7a75d | ||
|
|
cc9a501351 | ||
|
|
b536a23124 | ||
|
|
6ca5bcc6b8 | ||
|
|
ac966f118a | ||
|
|
f13472a8c3 | ||
|
|
0e213ae777 | ||
|
|
a0c82a6a47 | ||
|
|
a087ba608b | ||
|
|
9112d63655 | ||
|
|
3f7f7a0aa7 | ||
|
|
8d03aafe72 | ||
|
|
b0c0fd7dc8 | ||
|
|
c273f26cb3 | ||
|
|
60101830cc | ||
|
|
a58d782704 | ||
|
|
9b94c1dda9 | ||
|
|
201a6b350e | ||
|
|
b2724caeda | ||
|
|
9d99d556a1 | ||
|
|
a1a6511e26 | ||
|
|
652456646f | ||
|
|
ca0dc49f64 | ||
|
|
ae1b39eb60 | ||
|
|
22f7faf60e | ||
|
|
f3bf9eb14d | ||
|
|
347a1a48d4 | ||
|
|
c9793457f3 | ||
|
|
50d31ba398 | ||
|
|
2788c4cc00 | ||
|
|
ba54232b8d | ||
|
|
489bb70901 | ||
|
|
dd06dfa5ba | ||
|
|
f39e339726 | ||
|
|
295b374b48 | ||
|
|
8ed390c394 | ||
|
|
f9e9cac6e8 | ||
|
|
f3304acc93 | ||
|
|
a233771433 | ||
|
|
ea9a871d90 | ||
|
|
84d010bb2f | ||
|
|
e0ba468b7e | ||
|
|
f88c0dd645 | ||
|
|
758918c077 | ||
|
|
7b516e6113 | ||
|
|
61a76bb834 | ||
|
|
e6b6edefaf | ||
|
|
a228eb020d | ||
|
|
c46933a81a | ||
|
|
746046c8c0 | ||
|
|
acab95792f | ||
|
|
b882265e52 | ||
|
|
547f2ef189 | ||
|
|
69004943a7 | ||
|
|
658fb4b1f9 | ||
|
|
e675b0c00a | ||
|
|
95ac29559c | ||
|
|
b7a095b4b4 | ||
|
|
08d1b29301 | ||
|
|
741707273d | ||
|
|
bdd6ed0e83 | ||
|
|
8154e275fa | ||
|
|
6258cba79a | ||
|
|
213831289a | ||
|
|
ba34b45651 | ||
|
|
4be5c74798 | ||
|
|
b06a611e69 | ||
|
|
fd5b8d1374 | ||
|
|
4d84811629 | ||
|
|
cd4fd02ee3 | ||
|
|
475972aecd | ||
|
|
b7a23e558a | ||
|
|
827440686f | ||
|
|
7f70145f55 | ||
|
|
0639659270 | ||
|
|
4be08ee0bd | ||
|
|
cc6a24cf80 | ||
|
|
b187d5a3fc | ||
|
|
83f8d24892 | ||
|
|
fd1898b70a | ||
|
|
8ce63106b9 | ||
|
|
c0c571ffe0 | ||
|
|
d8f4da4d90 | ||
|
|
f02387073d | ||
|
|
d344cc6020 | ||
|
|
9100638400 | ||
|
|
d2085654a7 | ||
|
|
78d5939d9d | ||
|
|
c013006671 | ||
|
|
e09a9f0d80 | ||
|
|
705856d51a | ||
|
|
4445b6c054 | ||
|
|
cf03bc0a0a | ||
|
|
f37f1769ae | ||
|
|
31cd839063 | ||
|
|
9f6a4eb300 | ||
|
|
6a2957faf7 | ||
|
|
e6c9cfed42 | ||
|
|
0da7f1b4de | ||
|
|
8ce171877e | ||
|
|
a38c7f6e49 | ||
|
|
d19f628c73 | ||
|
|
526d15b804 | ||
|
|
c308397ed6 | ||
|
|
0cf93a8c32 | ||
|
|
cba25710fe | ||
|
|
1b471283c9 | ||
|
|
09fee2aa4b | ||
|
|
2739c86881 | ||
|
|
cdf67684cc | ||
|
|
4e5ba327fe | ||
|
|
efeb9e627e | ||
|
|
8e24f15d58 | ||
|
|
1adad860f4 | ||
|
|
cf07570871 | ||
|
|
bf30e52993 | ||
|
|
a91ddd40dd | ||
|
|
57ecadd23e | ||
|
|
967f14148f | ||
|
|
7a6544b17c | ||
|
|
bf59efd3f4 | ||
|
|
c1266e9d06 | ||
|
|
557adb6ed9 | ||
|
|
9f10996ac7 | ||
|
|
31f48829b0 | ||
|
|
39d4a97e24 | ||
|
|
a665978b64 | ||
|
|
0cc62058a9 | ||
|
|
7dfdb9d061 | ||
|
|
e3ea1d5049 | ||
|
|
19a96acac8 | ||
|
|
80d016e076 | ||
|
|
ac214b97df | ||
|
|
1a948e4341 | ||
|
|
6bbc77a69c | ||
|
|
217151be8d | ||
|
|
2ef1465244 | ||
|
|
cfc1d0d8f8 | ||
|
|
6fabecac1a | ||
|
|
77ac864995 | ||
|
|
f95769541d | ||
|
|
fa4ca7944f | ||
|
|
02e9bfaf31 | ||
|
|
7f66d5992f | ||
|
|
84e10cf635 | ||
|
|
1b8f8c5f5a | ||
|
|
131a783e89 | ||
|
|
2233f24c8f | ||
|
|
607a625641 | ||
|
|
9b49d19224 | ||
|
|
46ae4353e0 | ||
|
|
44fcba2e3a | ||
|
|
f434795ee5 | ||
|
|
0ea423d022 | ||
|
|
c9b9de2838 | ||
|
|
5918d5120f | ||
|
|
0d73f2fffa | ||
|
|
c8018c5543 | ||
|
|
071bd0ebf2 | ||
|
|
a00fce516e | ||
|
|
94abb3bd0c | ||
|
|
2e94ea1a0d | ||
|
|
3b009e0b50 | ||
|
|
cc130e04dd | ||
|
|
2eca868540 | ||
|
|
eeba5f020a | ||
|
|
ded8bce8e6 | ||
|
|
8e7efaae99 | ||
|
|
fe9e434020 | ||
|
|
8a0b0f5cf5 | ||
|
|
5df321816b | ||
|
|
7ff645c423 | ||
|
|
93f290d57c | ||
|
|
2146a76795 | ||
|
|
b071e24d7e | ||
|
|
a99e4b602a | ||
|
|
4cdea865f0 | ||
|
|
bf9ae2f0d3 | ||
|
|
da2f958614 | ||
|
|
b47201fe61 | ||
|
|
a8932910d9 | ||
|
|
413877b59d | ||
|
|
da47588502 | ||
|
|
6f7ffbeb3c | ||
|
|
a258f2084f | ||
|
|
d067a31ce6 | ||
|
|
c6e4eee789 | ||
|
|
07c49f832e | ||
|
|
01b087238a | ||
|
|
d4832812ef | ||
|
|
9c098fd79f | ||
|
|
029d5e9068 | ||
|
|
f30486adf9 | ||
|
|
e21c337d3d | ||
|
|
f66f324e0e | ||
|
|
885acdac24 | ||
|
|
ccb04c317c | ||
|
|
abbedeec4f | ||
|
|
cdae3ec936 | ||
|
|
d727696830 | ||
|
|
2a9078a3b4 | ||
|
|
ac479c7e40 | ||
|
|
cda6ffea1e | ||
|
|
0900f401c0 | ||
|
|
d5a96ed637 | ||
|
|
8d9eff5297 | ||
|
|
19c1511901 | ||
|
|
8e37e54645 | ||
|
|
c25645eab1 | ||
|
|
b9f7711f21 | ||
|
|
9447913de6 | ||
|
|
8a9834cac0 | ||
|
|
2fec1c83a4 | ||
|
|
ff36380ddf | ||
|
|
f2cfa61a39 | ||
|
|
41156cc45d | ||
|
|
9f51bfcc10 | ||
|
|
9426e813be | ||
|
|
20fd7aba9b | ||
|
|
3bada4560e | ||
|
|
9719abe99a | ||
|
|
5540519e52 | ||
|
|
0b94e73c0b | ||
|
|
5e2a06bfd6 | ||
|
|
ac95e411bc | ||
|
|
08219fc57e | ||
|
|
09643f3c8b | ||
|
|
08a575fa82 | ||
|
|
62887a6ce8 | ||
|
|
297a6c9348 | ||
|
|
1181edc48e | ||
|
|
81f1930349 | ||
|
|
54a1819d46 | ||
|
|
1b5472cc94 | ||
|
|
06a083bafc | ||
|
|
9bb45cb6a3 | ||
|
|
1bfe9c568e | ||
|
|
4f5e5fcdba | ||
|
|
bca44d1c14 | ||
|
|
e6d1d66c8f | ||
|
|
c3a5f5c0f0 | ||
|
|
c0875d7987 | ||
|
|
6b6ac2bcb3 | ||
|
|
f78466452a | ||
|
|
f51230e793 | ||
|
|
3ab201ce10 | ||
|
|
45a94cfe86 | ||
|
|
f16cc999bd | ||
|
|
d39f25efd3 | ||
|
|
6b2ce6de6f | ||
|
|
3f1738e0fe | ||
|
|
3ceaf2ab81 | ||
|
|
4c7d61ae82 | ||
|
|
d53dd57b84 | ||
|
|
6ff5ac5592 | ||
|
|
9ce1bbe3cd | ||
|
|
65c48419d0 | ||
|
|
d376ee6671 | ||
|
|
eaa419a48d | ||
|
|
1b6ec34c61 | ||
|
|
9f4da339c3 | ||
|
|
1b54a99f6a | ||
|
|
2b0d2892af | ||
|
|
27f2d201ed | ||
|
|
d6cf622e49 | ||
|
|
2092e2167e | ||
|
|
503032cbcf | ||
|
|
6f90e13502 | ||
|
|
b22bd459dd | ||
|
|
f67147a405 | ||
|
|
df4eb3d0f0 | ||
|
|
32cc4bcd03 | ||
|
|
1502c6d660 | ||
|
|
d4fb5b747b | ||
|
|
7581651011 | ||
|
|
4f54e273ab | ||
|
|
1cc1b83b6f | ||
|
|
8e9199f453 | ||
|
|
223501eeca | ||
|
|
7649738618 | ||
|
|
78c60ae695 | ||
|
|
f5f122d5a5 | ||
|
|
4b27a33a10 | ||
|
|
15aca2eb84 | ||
|
|
1cb91c0ac9 | ||
|
|
840b3b3161 | ||
|
|
a5cf3ce2f1 | ||
|
|
04c85eb9ce | ||
|
|
82364d2e3c | ||
|
|
3db7bf5dfd | ||
|
|
de13023981 | ||
|
|
35b825c78a | ||
|
|
28428fcf30 | ||
|
|
1e570bf506 | ||
|
|
ad0e97ab7b | ||
|
|
ed5d338a39 | ||
|
|
e795745e43 | ||
|
|
5e4d790fc3 | ||
|
|
7e47f4ed08 | ||
|
|
63fc77a861 | ||
|
|
59ff94c005 | ||
|
|
bbc199cda3 | ||
|
|
df91ee3d2a | ||
|
|
528fecef4d | ||
|
|
d39f1a6af0 | ||
|
|
4367ef11fa | ||
|
|
b8d77d2490 | ||
|
|
2600c3116c | ||
|
|
2506c2579b | ||
|
|
fe7392a656 | ||
|
|
8b7a3f1835 | ||
|
|
e6acc56c1f | ||
|
|
d635cfb90f | ||
|
|
6cb6113c57 | ||
|
|
6aea0f12cd | ||
|
|
99ace59b45 | ||
|
|
31f9ed3e6b | ||
|
|
573c0881e9 | ||
|
|
7134b3ba35 | ||
|
|
bc267e22bd | ||
|
|
3d83316b03 | ||
|
|
dfe80eb050 | ||
|
|
22151a0a03 | ||
|
|
484a9acc6f | ||
|
|
26acce246d | ||
|
|
388578c209 | ||
|
|
b33cce385c | ||
|
|
4b2019cf20 | ||
|
|
61246d713d | ||
|
|
40455f5260 | ||
|
|
78ce03bd62 | ||
|
|
61645d14fa | ||
|
|
d465ba5356 | ||
|
|
8fde7cd6f5 | ||
|
|
734e0f350a | ||
|
|
e1bbab3831 | ||
|
|
a870881fff | ||
|
|
1ed3eab010 | ||
|
|
dd16ecf63a | ||
|
|
d1a0ec5a35 | ||
|
|
cb790b4672 | ||
|
|
19d37ef641 | ||
|
|
0722011385 | ||
|
|
59ed118e66 | ||
|
|
d54468d7ed | ||
|
|
218f1a6ead | ||
|
|
abbd94e89b | ||
|
|
b46463563e | ||
|
|
33ce8e8a75 | ||
|
|
05ab0707fc | ||
|
|
d32861193b | ||
|
|
fb4a231703 | ||
|
|
0963ab4cc0 | ||
|
|
a248511d7b | ||
|
|
79a1639f8a | ||
|
|
476a6424ee | ||
|
|
6de17b9ae9 | ||
|
|
cb7116f08b | ||
|
|
15a4e23b22 | ||
|
|
2b0857baf9 | ||
|
|
20b0bf43ad | ||
|
|
17d6f03bae | ||
|
|
4edaf603c9 | ||
|
|
74b8da1e15 | ||
|
|
16cb756d3f | ||
|
|
f1efdf3d98 | ||
|
|
edfea639cf | ||
|
|
9fd9280132 | ||
|
|
d0bb69516a | ||
|
|
dc44254ba1 | ||
|
|
85c682f7e6 | ||
|
|
2a69f93605 | ||
|
|
ad4950130c | ||
|
|
3ccb4b9ec3 | ||
|
|
6bc486400c | ||
|
|
9228dbb8b8 | ||
|
|
e68b453218 | ||
|
|
1dd2b0ec19 | ||
|
|
e7e9d3b8c7 | ||
|
|
800ad9e898 | ||
|
|
ddecf2d5f0 | ||
|
|
09da17dab7 | ||
|
|
1678e2a939 | ||
|
|
9aab962ebc | ||
|
|
baf6e71a80 | ||
|
|
412fa4b12f | ||
|
|
3cae557b88 | ||
|
|
f7c39ae4a9 | ||
|
|
6d93ce9d33 | ||
|
|
031cbae0d6 | ||
|
|
5fbee47c2b | ||
|
|
2cd25a489a | ||
|
|
e67586b8b0 | ||
|
|
7d643942b4 | ||
|
|
2257a7f4ec | ||
|
|
f8ca173fd8 | ||
|
|
b08e6743be | ||
|
|
a97e4fe42d | ||
|
|
2706495668 | ||
|
|
224c3f4123 | ||
|
|
f2911be116 | ||
|
|
05c5eaed48 | ||
|
|
8541e9535e | ||
|
|
811cb2b95c | ||
|
|
05421223be | ||
|
|
808a6b82f3 | ||
|
|
30af7f1ca6 | ||
|
|
274e5280cb | ||
|
|
f85c4cd79f | ||
|
|
696d39b922 | ||
|
|
d997445284 | ||
|
|
f3e5268083 | ||
|
|
44db4a21fc | ||
|
|
c8a113384c | ||
|
|
07e7bc0a94 | ||
|
|
7c8acbe485 | ||
|
|
65c2126202 | ||
|
|
0ef0f62b69 | ||
|
|
921422a189 | ||
|
|
56b771a7ad | ||
|
|
d2988e6a88 | ||
|
|
16510d2400 | ||
|
|
6f790598ef | ||
|
|
63f0c7b2fe | ||
|
|
800f7970a7 | ||
|
|
b7381fc075 | ||
|
|
b1785606cc | ||
|
|
387d7d5508 | ||
|
|
6adacf8892 | ||
|
|
f7d6f93f1b | ||
|
|
9daa173ab7 | ||
|
|
e748f72447 | ||
|
|
14358417c8 | ||
|
|
8aa3461bf6 | ||
|
|
04c37a98b5 | ||
|
|
4850f1e94b | ||
|
|
60945ffe58 | ||
|
|
746016996c | ||
|
|
67a2734777 | ||
|
|
b208fc7cf5 | ||
|
|
2748cf99b3 | ||
|
|
b49b60ab5f | ||
|
|
620c67b04e | ||
|
|
a5b5f315d1 | ||
|
|
43ea05aad2 | ||
|
|
0ec1c80221 | ||
|
|
d91c289bf6 | ||
|
|
74b9971494 | ||
|
|
f8f116109a | ||
|
|
8498cb6258 | ||
|
|
7a72f44f64 | ||
|
|
a90e3c440b | ||
|
|
1aab8b87ec | ||
|
|
ebf071ac51 | ||
|
|
3d0fc33441 | ||
|
|
c50df85976 | ||
|
|
db9fc20acf | ||
|
|
10058cf271 | ||
|
|
310dadef45 | ||
|
|
3159f036a2 | ||
|
|
c3e426c82a | ||
|
|
b83d388523 | ||
|
|
d8820259d2 | ||
|
|
354d78e893 | ||
|
|
7f00f35f4b | ||
|
|
8c33b073c2 | ||
|
|
d6490c9bab | ||
|
|
b0e104bfd0 | ||
|
|
563e677b08 | ||
|
|
7664d0568e | ||
|
|
e1a13e433f | ||
|
|
d212f7d946 | ||
|
|
8d6dabf65c | ||
|
|
a4c5b85db8 | ||
|
|
e66001c182 | ||
|
|
08c7b35731 | ||
|
|
35cb36fcea | ||
|
|
bf8ca989d6 | ||
|
|
4976ebcef4 | ||
|
|
51661f5c55 | ||
|
|
3cbb10392c | ||
|
|
168ce752d1 | ||
|
|
8fcf9bc6bf | ||
|
|
56f43f55f3 | ||
|
|
9386ac026d | ||
|
|
e1565ccdc5 | ||
|
|
34993a20fd | ||
|
|
ea0124433a | ||
|
|
73b15c691d | ||
|
|
623dd9f3b3 | ||
|
|
379637f214 | ||
|
|
26ca8f7777 | ||
|
|
237f7e7f1a | ||
|
|
34fc9851bf | ||
|
|
abdaca08fe | ||
|
|
bdc9f339c9 | ||
|
|
3188d4d858 | ||
|
|
1bb76b1a49 | ||
|
|
9584efd84c | ||
|
|
c84bc8a8f3 | ||
|
|
edc55a2afd | ||
|
|
480325177c | ||
|
|
03cfa42eb6 | ||
|
|
b25c573ae3 | ||
|
|
7f5757d66b | ||
|
|
a3a275c3d5 | ||
|
|
557f007b12 | ||
|
|
542406c16f | ||
|
|
1fa870411f | ||
|
|
c096487eea | ||
|
|
97a8e0cdd4 | ||
|
|
eb20a5c051 | ||
|
|
5c6acf89da | ||
|
|
9dfe7ddbde | ||
|
|
8285720180 | ||
|
|
a4a1a42842 | ||
|
|
3c3217ab2b | ||
|
|
17ee3887dd | ||
|
|
db75da65db | ||
|
|
cf95257c81 | ||
|
|
b40036c203 | ||
|
|
b18661a8b0 | ||
|
|
bc0d52142a | ||
|
|
24041ed80d | ||
|
|
1a03e6b22e | ||
|
|
68fb916195 | ||
|
|
dfb8a1b8cb | ||
|
|
6c38409c57 | ||
|
|
27fd7a9088 | ||
|
|
0e62423e06 | ||
|
|
3f45ed14b9 | ||
|
|
ca4b782912 | ||
|
|
1976a11154 | ||
|
|
e7d8f64a49 | ||
|
|
5cf1f13328 | ||
|
|
fba706f10b | ||
|
|
c50630989e | ||
|
|
3832be94d0 | ||
|
|
900c1152d3 | ||
|
|
4ea50a8a85 | ||
|
|
0132fd4101 | ||
|
|
0e90ce3b8a | ||
|
|
9368ebfbd3 | ||
|
|
8381dbbd8f | ||
|
|
343ccaa2ef | ||
|
|
3f62775f4b | ||
|
|
c3ecad96b7 | ||
|
|
2ffb0cf5f7 | ||
|
|
ef716f3a69 | ||
|
|
cc935862b7 | ||
|
|
85a535dd0c | ||
|
|
ab626fe3eb | ||
|
|
05011df13a | ||
|
|
fcdb25c426 | ||
|
|
cc9ca35e04 | ||
|
|
4a50de8318 | ||
|
|
12051e7544 | ||
|
|
210508fe4f | ||
|
|
ba5b045351 | ||
|
|
9a19eeacb3 | ||
|
|
10ed03d6bf | ||
|
|
3be1bf4384 | ||
|
|
3396b20661 | ||
|
|
c9e1140189 | ||
|
|
416cd30b42 | ||
|
|
d66a07cc55 | ||
|
|
ce17dbe240 | ||
|
|
326fc501e2 | ||
|
|
91ad3ee0a2 | ||
|
|
2993c422c1 | ||
|
|
6f8cdde7e4 | ||
|
|
f521a0dd77 | ||
|
|
12fdb58da6 | ||
|
|
09876cefde | ||
|
|
d1e2ea7635 | ||
|
|
7b131f2a45 | ||
|
|
f3d5e3d4c2 | ||
|
|
492f83f5e2 | ||
|
|
7dcd1618d8 | ||
|
|
e2a39aa12e | ||
|
|
043833be7b | ||
|
|
ad394c19d5 | ||
|
|
05592183b1 | ||
|
|
079cbb8f68 | ||
|
|
3bddde20ab | ||
|
|
0a8a63bc15 | ||
|
|
fd2491446a | ||
|
|
26f3fe37f4 | ||
|
|
4cff80ecea | ||
|
|
83fe94d497 | ||
|
|
616bde2c4d | ||
|
|
0b971c2bd0 | ||
|
|
201b1b7fbc | ||
|
|
b0b976ee52 | ||
|
|
7020569f45 | ||
|
|
e153c76719 | ||
|
|
e7f9c11a14 | ||
|
|
f2e5c5e9b9 | ||
|
|
e02f73417c | ||
|
|
41db9c43e5 | ||
|
|
0dc2f0e923 | ||
|
|
5940d5ddad | ||
|
|
684850dda1 | ||
|
|
e8e615f4db | ||
|
|
559b060b6b | ||
|
|
581262d736 | ||
|
|
8878a49a1d | ||
|
|
3a7bc4d253 | ||
|
|
316982fb8f | ||
|
|
cfaea34053 | ||
|
|
8fd1d895d6 | ||
|
|
b592ecf6f5 | ||
|
|
65a8826158 | ||
|
|
c9fab898cc | ||
|
|
819049d596 | ||
|
|
147dee6ec7 | ||
|
|
b0b885d506 | ||
|
|
c94610f8a9 | ||
|
|
21fa006850 | ||
|
|
e293c435af | ||
|
|
321c5ec756 | ||
|
|
f04d93f9aa | ||
|
|
9436efe554 | ||
|
|
3acbf708d3 | ||
|
|
92979984ea | ||
|
|
a16d41a1e1 | ||
|
|
ff8b78fc99 | ||
|
|
97c9bd7ce0 | ||
|
|
c40c93340d | ||
|
|
094c105464 | ||
|
|
e36c696624 | ||
|
|
555a4ab4c5 | ||
|
|
7d8cc8776d | ||
|
|
960646ac8a | ||
|
|
ed9f15becf | ||
|
|
b09aa74f2f | ||
|
|
60805fd11d | ||
|
|
89ecd4b33d | ||
|
|
237f142a87 | ||
|
|
a21647a1aa | ||
|
|
e9afe4ee25 | ||
|
|
acd067582a | ||
|
|
82227e8901 | ||
|
|
8b6735a6aa | ||
|
|
9ed8831500 | ||
|
|
e7d726cc2c | ||
|
|
11a3dd26c8 | ||
|
|
04738586b9 | ||
|
|
18b4fce1ca | ||
|
|
3b9babe745 | ||
|
|
a4a85b7bbf | ||
|
|
e8f0275d8e | ||
|
|
3251f58d46 | ||
|
|
204fb211ac | ||
|
|
6cade814e1 | ||
|
|
eb2fbabbb1 | ||
|
|
ee9c5db719 | ||
|
|
d8af01dc46 | ||
|
|
5bead069f2 | ||
|
|
63e1345780 | ||
|
|
3df988d0b8 | ||
|
|
656202dc0d | ||
|
|
eec3253669 | ||
|
|
d9423201b8 | ||
|
|
c6087bc369 | ||
|
|
b7077c7967 | ||
|
|
4acb07f1cb | ||
|
|
5207022a4a | ||
|
|
1a25746343 | ||
|
|
0d7ceb625b | ||
|
|
95fb5f904f | ||
|
|
a0309855d7 | ||
|
|
546754e803 | ||
|
|
86461909d3 | ||
|
|
feebd2a733 | ||
|
|
75e7176675 | ||
|
|
bae5d9c7f8 | ||
|
|
4ba033fc84 | ||
|
|
f0dd0e8f92 | ||
|
|
7ea3d230bb | ||
|
|
b7f4fd9074 | ||
|
|
9a114b38d3 | ||
|
|
bec1116f86 | ||
|
|
d7cc37b951 | ||
|
|
4ce62cf221 | ||
|
|
1f052da0b9 | ||
|
|
812dc0c292 | ||
|
|
798fe0dce9 | ||
|
|
3f6fe55f9a | ||
|
|
aee4aecfe7 | ||
|
|
748c737e8f | ||
|
|
c673101bbb | ||
|
|
d66674655c | ||
|
|
da292e41bd | ||
|
|
deb4cc7c4b | ||
|
|
e91f6e268e | ||
|
|
2595ed5cc9 | ||
|
|
1d61ace302 | ||
|
|
a50c5f4f7c | ||
|
|
a46bf4fbe0 | ||
|
|
0d1eab5c15 | ||
|
|
3255183ee4 | ||
|
|
66b65a042e | ||
|
|
b8db118e64 | ||
|
|
7c15cf2788 | ||
|
|
31538d5955 | ||
|
|
337eac6d2f | ||
|
|
e826f02da5 | ||
|
|
d5281b10dd | ||
|
|
87fdb5956e | ||
|
|
00019f9d76 | ||
|
|
f257befd86 | ||
|
|
07c27f3292 | ||
|
|
ffb0021d04 | ||
|
|
81c93e3488 | ||
|
|
587d5578ab | ||
|
|
9345e4270a | ||
|
|
1dde6092e5 | ||
|
|
5e8816e6df | ||
|
|
dc0aa35522 | ||
|
|
f39aa1a41e | ||
|
|
e31f2a03ba | ||
|
|
1e38ccdd2b | ||
|
|
69470b8738 | ||
|
|
5dedaa8e67 | ||
|
|
eab0b417de | ||
|
|
5e4cfacfcb | ||
|
|
120e21b80b | ||
|
|
cd7a03c2cf | ||
|
|
1aa038c92f | ||
|
|
e4c1328ef2 | ||
|
|
e17bb5f321 | ||
|
|
a0bc03a9f5 | ||
|
|
70e7e4b1d2 | ||
|
|
7d16d9acaf | ||
|
|
a7d032df21 | ||
|
|
9af37fe427 | ||
|
|
8a12dc5bd4 | ||
|
|
d01d8d3663 | ||
|
|
e380075852 | ||
|
|
7df5bc0017 | ||
|
|
9b61ddb876 | ||
|
|
76fa404b89 | ||
|
|
682dfd0e47 | ||
|
|
80a9842a25 | ||
|
|
89d4c2cab7 | ||
|
|
54f32ed71b | ||
|
|
fed343e3bb | ||
|
|
fd21b057eb | ||
|
|
e81e91be93 | ||
|
|
9b714e1dbb | ||
|
|
e67e17ef1e | ||
|
|
942d53a678 | ||
|
|
806d7e965f | ||
|
|
db3e9c7ec7 | ||
|
|
b657724d9b | ||
|
|
5a5b600490 | ||
|
|
1472d60c8a | ||
|
|
a9c28cce6d | ||
|
|
b7a896805d | ||
|
|
6b571fe427 | ||
|
|
3f77e868b1 | ||
|
|
31bc766944 | ||
|
|
ebddbfb416 | ||
|
|
3aa85cf641 | ||
|
|
312a39ee24 | ||
|
|
99cfaa1583 | ||
|
|
f3e3aa231d | ||
|
|
a1bce81ed0 | ||
|
|
dad21bfc6f | ||
|
|
9643a7c2b2 | ||
|
|
d16ad0b4c7 | ||
|
|
32f6d711ac | ||
|
|
a07ba63917 | ||
|
|
70f906c71a | ||
|
|
49c4595457 | ||
|
|
4cc30c0d33 | ||
|
|
757a5b58e8 | ||
|
|
2b9276b495 | ||
|
|
b2c7b713f2 | ||
|
|
99ec44ad78 | ||
|
|
db4298431d | ||
|
|
fc8b185b5b | ||
|
|
f3752d273c | ||
|
|
aa6101d8ea | ||
|
|
ee42992c37 | ||
|
|
c5404c6573 | ||
|
|
2a683a6a05 | ||
|
|
a1ecf2211f | ||
|
|
3675254af4 | ||
|
|
30d794653d | ||
|
|
709fe1c2c0 | ||
|
|
ad776c76a7 | ||
|
|
dde73e029c | ||
|
|
e5b990691e | ||
|
|
d6edd80f3b | ||
|
|
e706a8acd0 | ||
|
|
8dad8f2b1c | ||
|
|
c4228e08c5 | ||
|
|
c074e79069 | ||
|
|
6e0982327d | ||
|
|
a06cc76c9b | ||
|
|
9eef34049d | ||
|
|
e91cb85a66 | ||
|
|
05c2737f46 | ||
|
|
cbdc746c9d | ||
|
|
8d11794f83 | ||
|
|
8ed9be9c58 | ||
|
|
d49365b4a7 | ||
|
|
03345bef10 | ||
|
|
ff05128a87 | ||
|
|
f6e18ded5b | ||
|
|
d129cd49a5 | ||
|
|
9233305bd6 | ||
|
|
d6e9985a26 | ||
|
|
6be61aa19c | ||
|
|
4a232b7935 | ||
|
|
44fafdbd7c | ||
|
|
2eddda9061 | ||
|
|
b2ac4b4253 | ||
|
|
1d04b70efe | ||
|
|
d61482dffc | ||
|
|
37bb16cca1 | ||
|
|
ca1885fe09 | ||
|
|
d67e846712 | ||
|
|
dfe097931c | ||
|
|
4ef74a1205 | ||
|
|
b894e5be69 | ||
|
|
dc7f8057a3 | ||
|
|
66b436f7f8 | ||
|
|
224e38c6c7 | ||
|
|
51a72ccd37 | ||
|
|
40d5ce0937 | ||
|
|
51f76c69c4 | ||
|
|
b87e27d8a3 | ||
|
|
50e99b571f | ||
|
|
e3412da033 | ||
|
|
69418ba44d | ||
|
|
cfe5c649c5 | ||
|
|
f8fa9a057e | ||
|
|
5a2ef79fe6 | ||
|
|
74649ef672 | ||
|
|
e5f9cc26a8 | ||
|
|
6744a41644 | ||
|
|
ffb7935e12 | ||
|
|
53598b8c40 | ||
|
|
fe0ae4a9aa | ||
|
|
d1bb9c55ee | ||
|
|
29cc9ad8b1 | ||
|
|
4c081f59ff | ||
|
|
9a6d83f526 | ||
|
|
a6cff5c2a5 | ||
|
|
e56313a204 | ||
|
|
70b9e811bd | ||
|
|
46582af0ae | ||
|
|
0387816cb9 | ||
|
|
b6756b5b35 | ||
|
|
34e1f89ded | ||
|
|
0142e5fd33 | ||
|
|
cd950a0359 | ||
|
|
770115abd1 | ||
|
|
faf7782b1e | ||
|
|
82ba02a904 | ||
|
|
dd0dc1a936 | ||
|
|
eac19814e1 | ||
|
|
7aca8a02cb | ||
|
|
efb492eace | ||
|
|
79f964dce9 | ||
|
|
a23112fae6 | ||
|
|
092296984a | ||
|
|
565f511cc7 | ||
|
|
b8c92c69b0 | ||
|
|
6ebb3caa20 | ||
|
|
cc2c737357 | ||
|
|
69553fd80e | ||
|
|
190e1b19f1 | ||
|
|
c09b6154f4 | ||
|
|
120d5a672c | ||
|
|
307212a19c | ||
|
|
eedb2c3c52 | ||
|
|
b1f52cf8a9 | ||
|
|
bea9ac010c | ||
|
|
6ebd437a5f | ||
|
|
c0b109fbc1 | ||
|
|
b77f254ab1 | ||
|
|
f5fea69ef9 | ||
|
|
c63b3d4209 | ||
|
|
056ffa5876 | ||
|
|
0120498ec8 | ||
|
|
0b67f1a8e4 | ||
|
|
16e71c14b9 | ||
|
|
27445e6c1d | ||
|
|
fcb4bc0781 | ||
|
|
6c60b0d2a6 | ||
|
|
ed70f39da8 | ||
|
|
d5aebb434c | ||
|
|
17f69d8da8 | ||
|
|
c07dc700df | ||
|
|
957ce320ae | ||
|
|
7a39933c97 | ||
|
|
9b0da89a03 | ||
|
|
b05ba8d110 | ||
|
|
0448038c68 | ||
|
|
e694d60f23 | ||
|
|
8a4337e819 | ||
|
|
5bd21c10b6 | ||
|
|
87c9c29ee8 | ||
|
|
6c3d6a4b05 | ||
|
|
f39dd82adc | ||
|
|
f4ad9fcee0 | ||
|
|
65bea1cb2d | ||
|
|
f912e53038 | ||
|
|
3c8bbc2b90 | ||
|
|
e22afe5424 | ||
|
|
c060569599 | ||
|
|
d83f591184 | ||
|
|
b6f302d1c6 | ||
|
|
a846ab9cc0 | ||
|
|
f0d4c4d2ce | ||
|
|
3e765a3f3e | ||
|
|
356b6bb1b7 | ||
|
|
951a9f2b43 | ||
|
|
6d870fbef0 | ||
|
|
01a89e2145 | ||
|
|
6df6d2c084 | ||
|
|
2ca67d1489 | ||
|
|
eec6ce2210 | ||
|
|
c44a0528df | ||
|
|
5b5bc7fdbb | ||
|
|
68aadc958f | ||
|
|
91bb31856b | ||
|
|
a864822a89 | ||
|
|
a1a0958415 | ||
|
|
def97eea9d | ||
|
|
e4febe62ef | ||
|
|
fc0973f378 | ||
|
|
9b1c75d061 | ||
|
|
09bb7d3525 | ||
|
|
701d6a0746 | ||
|
|
a0df1fd728 | ||
|
|
6a3e618ffd | ||
|
|
d63bc10d74 | ||
|
|
4fd45700a2 | ||
|
|
f0c0efca8d | ||
|
|
72f5bac346 | ||
|
|
20699c1262 | ||
|
|
8a335fc64c | ||
|
|
8a9522a363 | ||
|
|
9315443866 | ||
|
|
95cb2cc28e | ||
|
|
151fc83815 | ||
|
|
b759abc954 | ||
|
|
6808a38c9f | ||
|
|
108b2244d8 | ||
|
|
283a95dea2 | ||
|
|
1219e8ba37 | ||
|
|
ada3ea08fc | ||
|
|
d0fa473d61 | ||
|
|
7b0408f79d | ||
|
|
c18526f10a | ||
|
|
499c82b089 | ||
|
|
a376d8e2bf | ||
|
|
59b077223b | ||
|
|
9c503cf6fd | ||
|
|
7954db9a89 | ||
|
|
3929e2057d | ||
|
|
242006d05d | ||
|
|
b74b62e242 | ||
|
|
d0c00b859b | ||
|
|
3e78a70552 | ||
|
|
fad0adfcd0 | ||
|
|
9e477056ed | ||
|
|
de9bb1d0cc | ||
|
|
45d1b1d671 | ||
|
|
afb902d792 | ||
|
|
5f1ebdcd28 | ||
|
|
e72ec2f605 | ||
|
|
d7d7fd3a10 | ||
|
|
7bccde72ed | ||
|
|
3f41e58dbd | ||
|
|
581ecdb2a8 | ||
|
|
c4f8d5f25e | ||
|
|
7fbedae604 | ||
|
|
b453186de5 | ||
|
|
49ea950f78 | ||
|
|
5aae9f3204 | ||
|
|
5c2e82fc8b | ||
|
|
5a2fe4c9d9 | ||
|
|
4dcfb7a675 | ||
|
|
491c5ceec5 | ||
|
|
2e6c8721bc | ||
|
|
fd5336c56e | ||
|
|
209d994336 | ||
|
|
aa216fa510 | ||
|
|
89e084938f | ||
|
|
11eb187fee | ||
|
|
8a4e6f7dd3 | ||
|
|
35bffdadc2 | ||
|
|
97e5dbf62c | ||
|
|
4325d017c8 | ||
|
|
34306cbf5d | ||
|
|
90d91b4891 | ||
|
|
ab32e0dc9d | ||
|
|
26ee4babcf | ||
|
|
c7d90baad1 | ||
|
|
c99f298cad | ||
|
|
fc316b1031 | ||
|
|
1ffe430df0 | ||
|
|
0efb142c88 | ||
|
|
dddeca1856 | ||
|
|
3b0370c8bb | ||
|
|
2437573a8f | ||
|
|
e2676a5c7f | ||
|
|
68af987c60 | ||
|
|
cd73b9bc42 | ||
|
|
943d010dfc | ||
|
|
38cc24af27 | ||
|
|
053f7880e5 | ||
|
|
9543fbbf1c | ||
|
|
8b605d9183 | ||
|
|
77b3214746 | ||
|
|
5f711408c0 | ||
|
|
d709c18f5f | ||
|
|
8780df8467 | ||
|
|
7dc1eab185 | ||
|
|
79e2d683b6 | ||
|
|
4f7b42cdc4 | ||
|
|
276281c3f9 | ||
|
|
b74de0b1e4 | ||
|
|
df9bc377dd | ||
|
|
97ded16ea2 | ||
|
|
731729cf1b | ||
|
|
daa952d990 | ||
|
|
f8a3e4f6f0 | ||
|
|
6231cc83d9 | ||
|
|
20c0bbc911 | ||
|
|
559dcffdf2 | ||
|
|
7ed0aeced2 | ||
|
|
c249c3f3b6 | ||
|
|
55d17ec990 | ||
|
|
bbbcd9bf48 | ||
|
|
c2c6f09d68 | ||
|
|
848a5212ef | ||
|
|
eaf45436f8 | ||
|
|
50784d4df6 | ||
|
|
21d8a8b6da | ||
|
|
45820d45e5 | ||
|
|
2ac213a3ce | ||
|
|
70ea6ce04b | ||
|
|
9102fc6032 | ||
|
|
378ea1deea | ||
|
|
f74e294dc2 | ||
|
|
57c8b5fc75 | ||
|
|
550b1f23ec | ||
|
|
f651a59294 | ||
|
|
fee80682b1 | ||
|
|
b88916214f | ||
|
|
685be0981f | ||
|
|
fc054dad81 | ||
|
|
83f8bbb91b | ||
|
|
0ec64c3be8 | ||
|
|
f878191e6f | ||
|
|
8c73a450a1 | ||
|
|
1dccd6af79 | ||
|
|
dcfbca076f | ||
|
|
b68b1e01b3 | ||
|
|
be04473af4 | ||
|
|
fedffc2f35 | ||
|
|
a187c55dfe | ||
|
|
55a1f794cf | ||
|
|
68f6cb1286 | ||
|
|
0ab0f727ad | ||
|
|
c46017f0dc | ||
|
|
1bd729a4bb | ||
|
|
0eee9daf1d | ||
|
|
d90fa5e292 | ||
|
|
503a4de277 | ||
|
|
ae9fca52ca | ||
|
|
eddae3b7f7 | ||
|
|
d5b01d3a8f | ||
|
|
ab93389031 | ||
|
|
5c9eb264e8 | ||
|
|
4d552ae44d | ||
|
|
3dd1741484 | ||
|
|
f9548cb213 | ||
|
|
c3da240bc0 | ||
|
|
f439539c6e | ||
|
|
57424d8b6b | ||
|
|
3bdeec3bc2 | ||
|
|
2d46bdf8e3 | ||
|
|
5f87cb4c4f | ||
|
|
76d75ac375 | ||
|
|
75a6ae7111 | ||
|
|
cf83651c79 | ||
|
|
4c1edfb941 | ||
|
|
82c3facb65 | ||
|
|
266dba466c | ||
|
|
379a5f670f | ||
|
|
8eaad81800 | ||
|
|
4baacc7d52 | ||
|
|
0de47d7c83 | ||
|
|
cfd21f8ca2 | ||
|
|
d0e8944c56 | ||
|
|
3e54d13b62 | ||
|
|
b30d130d92 |
25
.editorconfig
Normal file
25
.editorconfig
Normal file
@@ -0,0 +1,25 @@
|
||||
# EditorConfig is awesome: https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
|
||||
[CMakeLists.txt]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{c,cc,h}]
|
||||
indent_style = tab
|
||||
|
||||
[*.{c3}]
|
||||
indent_style = tab
|
||||
|
||||
[*.{json,toml,yml,gyp}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{py,pyi}]
|
||||
indent_style = tab
|
||||
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,4 +1,2 @@
|
||||
$ cat .gitattributes
|
||||
* text=auto
|
||||
*.c3 linguist-language=C
|
||||
*.c3t linguist-language=C
|
||||
14
.github/FUNDING.yml
vendored
Normal file
14
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [c3lang]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: c3lang
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
540
.github/workflows/main.yml
vendored
540
.github/workflows/main.yml
vendored
@@ -2,13 +2,16 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev, ci_testing ]
|
||||
branches: [ master, dev, ci_testing, experiments ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [ master, dev ]
|
||||
|
||||
env:
|
||||
LLVM_RELEASE_VERSION: 16
|
||||
|
||||
LLVM_RELEASE_VERSION_WINDOWS: 18
|
||||
LLVM_RELEASE_VERSION_MAC: 17
|
||||
LLVM_RELEASE_VERSION_LINUX: 17
|
||||
LLVM_RELEASE_VERSION_UBUNTU20: 17
|
||||
LLVM_DEV_VERSION: 20
|
||||
jobs:
|
||||
|
||||
build-msvc:
|
||||
@@ -23,7 +26,7 @@ jobs:
|
||||
run:
|
||||
shell: cmd
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: CMake
|
||||
run: |
|
||||
@@ -33,26 +36,56 @@ jobs:
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
cd resources
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\hello_world_many.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\time.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\fannkuch-redux.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run -L C:\ --print-linking examples\hello_world_many.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\time.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\fannkuch-redux.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\contextfree\boolerr.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\ls.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\load_world.c3
|
||||
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\process.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\args.c3 -- foo -bar "baz baz"
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run msvc_stack.c3
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
cd resources/testproject
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log run hello_world_win32
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32
|
||||
dir build\llvm_ir
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe clean
|
||||
dir build\llvm_ir
|
||||
|
||||
|
||||
- name: Build testproject lib
|
||||
run: |
|
||||
cd resources/testproject
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log build hello_world_win32_lib
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv build hello_world_win32_lib
|
||||
|
||||
- name: Compile and run dynlib-test
|
||||
run: |
|
||||
cd resources/examples/dynlib-test
|
||||
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv dynamic-lib add.c3
|
||||
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv compile-run test.c3 -l ./add.lib
|
||||
|
||||
- name: Compile and run staticlib-test
|
||||
run: |
|
||||
cd resources/examples/staticlib-test
|
||||
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv static-lib add.c3
|
||||
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv compile-run test.c3 -l ./add.lib
|
||||
|
||||
- name: Vendor-fetch
|
||||
run: |
|
||||
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib
|
||||
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
|
||||
|
||||
- name: Try raylib5
|
||||
run: |
|
||||
cd resources
|
||||
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_arkanoid.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_snake.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_tetris.c3
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
@@ -62,16 +95,24 @@ jobs:
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -g1 --safe
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
|
||||
|
||||
- name: Test python script
|
||||
run: |
|
||||
py msvc_build_libraries.py --accept-license
|
||||
dir msvc_sdk
|
||||
|
||||
- name: upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: c3-windows-${{ matrix.build_type }}
|
||||
path: build\${{ matrix.build_type }}\c3c.exe
|
||||
path: |
|
||||
build\${{ matrix.build_type }}\c3c.exe
|
||||
build\${{ matrix.build_type }}\c3c_rt
|
||||
|
||||
build-msys2-mingw:
|
||||
runs-on: windows-latest
|
||||
# if: ${{ false }}
|
||||
strategy:
|
||||
# Don't abort runners if a single one fails
|
||||
fail-fast: false
|
||||
@@ -82,44 +123,46 @@ jobs:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
update: true
|
||||
install: git binutils mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
|
||||
install: git binutils mingw-w64-x86_64-clang mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
|
||||
- shell: msys2 {0}
|
||||
run: |
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-15.0.3-1-any.pkg.tar.zst
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-15.0.3-1-any.pkg.tar.zst
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-19.1.6-1-any.pkg.tar.zst
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-19.1.6-1-any.pkg.tar.zst
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake --build build
|
||||
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
cd resources
|
||||
../build/c3c compile-run examples/hello_world_many.c3
|
||||
../build/c3c compile-run examples/time.c3
|
||||
../build/c3c compile-run examples/fannkuch-redux.c3
|
||||
../build/c3c compile-run examples/contextfree/boolerr.c3
|
||||
../build/c3c compile-run examples/load_world.c3
|
||||
../build/c3c compile-run --print-linking examples/hello_world_many.c3
|
||||
../build/c3c compile-run --print-linking examples/time.c3
|
||||
../build/c3c compile-run --print-linking examples/fannkuch-redux.c3
|
||||
../build/c3c compile-run --print-linking examples/contextfree/boolerr.c3
|
||||
../build/c3c compile-run --print-linking examples/load_world.c3
|
||||
../build/c3c compile-run --print-linking examples/args.c3 -- foo -bar "baz baz"
|
||||
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log
|
||||
../../build/c3c run -vvv
|
||||
|
||||
- name: Vendor-fetch
|
||||
run: |
|
||||
./build/c3c vendor-fetch raylib
|
||||
./build/c3c vendor-fetch raylib5
|
||||
|
||||
- name: Build testproject lib
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c build hello_world_lib --debug-log
|
||||
../../build/c3c build hello_world_lib --cc cc -vvv
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
@@ -140,7 +183,7 @@ jobs:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
@@ -161,16 +204,17 @@ jobs:
|
||||
../build/c3c compile-run examples/fannkuch-redux.c3
|
||||
../build/c3c compile-run examples/contextfree/boolerr.c3
|
||||
../build/c3c compile-run examples/load_world.c3
|
||||
|
||||
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
|
||||
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log
|
||||
../../build/c3c run -vvv
|
||||
|
||||
- name: Build testproject lib
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c build hello_world_lib --debug-log
|
||||
../../build/c3c build hello_world_lib -vvv
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
@@ -184,10 +228,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
llvm_version: [16]
|
||||
llvm_version: [17, 18, 19]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install common deps
|
||||
run: |
|
||||
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
|
||||
@@ -195,16 +239,29 @@ jobs:
|
||||
- name: Install Clang ${{matrix.llvm_version}}
|
||||
run: |
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
if [[ "${{matrix.llvm_version}}" < 17 ]]; then
|
||||
if [[ "${{matrix.llvm_version}}" < 18 ]]; then
|
||||
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \
|
||||
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
|
||||
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \
|
||||
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
|
||||
else
|
||||
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
|
||||
if [[ "${{matrix.llvm_version}}" < "${{env.LLVM_DEV_VERSION}}" ]]; then
|
||||
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \
|
||||
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
|
||||
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
|
||||
else
|
||||
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
|
||||
sudo apt-get install -y -t llvm-toolchain-focal libpolly-${{matrix.llvm_version}}-dev \
|
||||
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
|
||||
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
|
||||
fi
|
||||
fi
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
|
||||
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
|
||||
sudo apt-get install -y libpolly-${{matrix.llvm_version}}-dev
|
||||
- name: CMake
|
||||
if: matrix.llvm_version < 18 || matrix.llvm_version == env.LLVM_DEV_VERSION
|
||||
run: |
|
||||
cmake -B build \
|
||||
-G Ninja \
|
||||
@@ -217,6 +274,20 @@ jobs:
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
|
||||
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
|
||||
cmake --build build
|
||||
- name: CMake18
|
||||
if: matrix.llvm_version >= 18 && matrix.llvm_version != env.LLVM_DEV_VERSION
|
||||
run: |
|
||||
cmake -B build \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
|
||||
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
|
||||
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
|
||||
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
|
||||
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
|
||||
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
|
||||
cmake --build build
|
||||
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
@@ -228,9 +299,9 @@ jobs:
|
||||
../build/c3c compile examples/fasta.c3
|
||||
../build/c3c compile examples/gameoflife.c3
|
||||
../build/c3c compile examples/hash.c3
|
||||
../build/c3c compile examples/levenshtein.c3
|
||||
../build/c3c compile-only examples/levenshtein.c3
|
||||
../build/c3c compile examples/load_world.c3
|
||||
../build/c3c compile examples/map.c3
|
||||
../build/c3c compile-only examples/map.c3
|
||||
../build/c3c compile examples/mandelbrot.c3
|
||||
../build/c3c compile examples/plus_minus.c3
|
||||
../build/c3c compile examples/nbodies.c3
|
||||
@@ -239,28 +310,72 @@ jobs:
|
||||
../build/c3c compile examples/contextfree/boolerr.c3
|
||||
../build/c3c compile examples/contextfree/dynscope.c3
|
||||
../build/c3c compile examples/contextfree/guess_number.c3
|
||||
../build/c3c compile examples/contextfree/multi.c3
|
||||
../build/c3c compile examples/contextfree/cleanup.c3
|
||||
../build/c3c compile examples/contextfree/multi.c3
|
||||
../build/c3c compile examples/contextfree/cleanup.c3
|
||||
../build/c3c compile-run examples/hello_world_many.c3
|
||||
../build/c3c compile-run examples/time.c3
|
||||
../build/c3c compile-run examples/fannkuch-redux.c3
|
||||
../build/c3c compile-run examples/contextfree/boolerr.c3
|
||||
../build/c3c compile-run examples/load_world.c3
|
||||
../build/c3c compile-run examples/process.c3
|
||||
../build/c3c compile-run examples/ls.c3
|
||||
../build/c3c compile-run --linker=builtin linux_stack.c3
|
||||
../build/c3c compile-run linux_stack.c3
|
||||
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
|
||||
|
||||
- name: Compile and run dynlib-test
|
||||
run: |
|
||||
cd resources/examples/dynlib-test
|
||||
../../../build/c3c -vv dynamic-lib add.c3
|
||||
mv add.so libadd.so
|
||||
cc test.c -L. -ladd -Wl,-rpath=.
|
||||
./a.out
|
||||
../../../build/c3c compile-run test.c3 -L . -l add -z -Wl,-rpath=.
|
||||
|
||||
- name: Compile and run staticlib-test
|
||||
run: |
|
||||
cd resources/examples/staticlib-test
|
||||
../../../build/c3c -vv static-lib add.c3
|
||||
mv add.a libadd.a
|
||||
cc test.c -L. -ladd
|
||||
./a.out
|
||||
../../../build/c3c compile-run test.c3 -L . -l add
|
||||
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit -g1 --safe
|
||||
../build/c3c compile-test unit
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log
|
||||
../../build/c3c run -vvv
|
||||
|
||||
- name: Test WASM
|
||||
run: |
|
||||
cd resources/testfragments
|
||||
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
|
||||
|
||||
- name: Install QEMU and Risc-V toolchain
|
||||
run: |
|
||||
sudo apt-get install opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf
|
||||
|
||||
- name: Compile and run Baremetal Risc-V Example
|
||||
run: |
|
||||
cd resources/examples/embedded/riscv-qemu
|
||||
make C3C_PATH=../../../../build/ run
|
||||
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log --forcelinker
|
||||
../../build/c3c run -vvv --linker=builtin
|
||||
|
||||
- name: Init a library & a project
|
||||
run: |
|
||||
./build/c3c init-lib mylib
|
||||
ls mylib.c3l
|
||||
./build/c3c init myproject
|
||||
ls myproject
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
@@ -268,17 +383,17 @@ jobs:
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == 16
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
|
||||
run: |
|
||||
mkdir linux
|
||||
cp -r lib linux
|
||||
cp msvc_build_libraries.py linux
|
||||
cp build/c3c linux
|
||||
tar czf c3-linux-${{matrix.build_type}}.tar.gz linux
|
||||
mkdir c3
|
||||
cp -r lib c3
|
||||
cp msvc_build_libraries.py c3
|
||||
cp build/c3c c3
|
||||
tar czf c3-linux-${{matrix.build_type}}.tar.gz c3
|
||||
|
||||
- name: upload artifacts
|
||||
if: matrix.llvm_version == 16
|
||||
uses: actions/upload-artifact@v3
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: c3-linux-${{matrix.build_type}}
|
||||
path: c3-linux-${{matrix.build_type}}.tar.gz
|
||||
@@ -290,10 +405,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
llvm_version: [16]
|
||||
|
||||
llvm_version: [17, 18, 19]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install common deps
|
||||
run: |
|
||||
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
|
||||
@@ -301,16 +415,17 @@ jobs:
|
||||
- name: Install Clang ${{matrix.llvm_version}}
|
||||
run: |
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
if [[ "${{matrix.llvm_version}}" < 17 ]]; then
|
||||
if [[ "${{matrix.llvm_version}}" < "${{env.LLVM_DEV_VERSION}}" ]]; then
|
||||
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
|
||||
else
|
||||
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
|
||||
fi
|
||||
sudo apt-get update
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
|
||||
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
|
||||
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
|
||||
sudo apt-get install -y libpolly-${{matrix.llvm_version}}-dev
|
||||
- name: CMake
|
||||
- name: CMake Old
|
||||
if: matrix.llvm_version < 18 || matrix.llvm_version == env.LLVM_DEV_VERSION
|
||||
run: |
|
||||
cmake -B build \
|
||||
-G Ninja \
|
||||
@@ -323,7 +438,109 @@ jobs:
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
|
||||
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
|
||||
cmake --build build
|
||||
- name: CMake
|
||||
if: matrix.llvm_version >= 18 && matrix.llvm_version != env.LLVM_DEV_VERSION
|
||||
run: |
|
||||
cmake -B build \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
|
||||
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
|
||||
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
|
||||
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
|
||||
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
|
||||
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
|
||||
cmake --build build
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
cd resources
|
||||
../build/c3c compile examples/gameoflife.c3
|
||||
../build/c3c compile-only examples/levenshtein.c3
|
||||
../build/c3c compile-only examples/map.c3
|
||||
../build/c3c compile examples/mandelbrot.c3
|
||||
../build/c3c compile examples/plus_minus.c3
|
||||
../build/c3c compile examples/spectralnorm.c3
|
||||
../build/c3c compile examples/swap.c3
|
||||
../build/c3c compile examples/contextfree/guess_number.c3
|
||||
../build/c3c compile-run examples/hash.c3
|
||||
../build/c3c compile-run examples/nbodies.c3
|
||||
../build/c3c compile-run examples/contextfree/boolerr.c3
|
||||
../build/c3c compile-run examples/contextfree/dynscope.c3
|
||||
../build/c3c compile-run examples/contextfree/multi.c3
|
||||
../build/c3c compile-run examples/contextfree/cleanup.c3
|
||||
../build/c3c compile-run examples/hello_world_many.c3
|
||||
../build/c3c compile-run examples/time.c3
|
||||
../build/c3c compile-run examples/fannkuch-redux.c3
|
||||
../build/c3c compile-run examples/load_world.c3
|
||||
../build/c3c compile-run examples/base64.c3
|
||||
../build/c3c compile-run examples/binarydigits.c3
|
||||
../build/c3c compile-run examples/brainfk.c3
|
||||
../build/c3c compile-run examples/factorial_macro.c3
|
||||
../build/c3c compile-run examples/fasta.c3
|
||||
../build/c3c compile-run examples/process.c3
|
||||
../build/c3c compile-run --linker=builtin linux_stack.c3
|
||||
../build/c3c compile-run linux_stack.c3
|
||||
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
|
||||
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv
|
||||
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv --linker=builtin
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
|
||||
run: |
|
||||
mkdir c3
|
||||
cp -r lib c3
|
||||
cp msvc_build_libraries.py c3
|
||||
cp build/c3c c3
|
||||
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz c3
|
||||
|
||||
- name: upload artifacts
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: c3-ubuntu-20-${{matrix.build_type}}
|
||||
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
|
||||
|
||||
build-with-docker:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ubuntu_version: [20.04, 22.04]
|
||||
build_type: [Release, Debug]
|
||||
llvm_version: [17, 18, 19]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Make script executable
|
||||
run: chmod +x ./build-with-docker.sh
|
||||
|
||||
- name: Run build
|
||||
run: |
|
||||
LLVM_VERSION=${{ matrix.llvm_version }} UBUNTU_VERSION=${{ matrix.ubuntu_version }} CMAKE_BUILD_TYPE=${{ matrix.build_type }} ./build-with-docker.sh
|
||||
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
cd resources
|
||||
@@ -334,9 +551,9 @@ jobs:
|
||||
../build/c3c compile examples/fasta.c3
|
||||
../build/c3c compile examples/gameoflife.c3
|
||||
../build/c3c compile examples/hash.c3
|
||||
../build/c3c compile examples/levenshtein.c3
|
||||
../build/c3c compile-only examples/levenshtein.c3
|
||||
../build/c3c compile examples/load_world.c3
|
||||
../build/c3c compile examples/map.c3
|
||||
../build/c3c compile-only examples/map.c3
|
||||
../build/c3c compile examples/mandelbrot.c3
|
||||
../build/c3c compile examples/plus_minus.c3
|
||||
../build/c3c compile examples/nbodies.c3
|
||||
@@ -345,50 +562,51 @@ jobs:
|
||||
../build/c3c compile examples/contextfree/boolerr.c3
|
||||
../build/c3c compile examples/contextfree/dynscope.c3
|
||||
../build/c3c compile examples/contextfree/guess_number.c3
|
||||
../build/c3c compile examples/contextfree/multi.c3
|
||||
../build/c3c compile examples/contextfree/cleanup.c3
|
||||
../build/c3c compile examples/contextfree/multi.c3
|
||||
../build/c3c compile examples/contextfree/cleanup.c3
|
||||
../build/c3c compile-run examples/hello_world_many.c3
|
||||
../build/c3c compile-run examples/time.c3
|
||||
../build/c3c compile-run examples/fannkuch-redux.c3
|
||||
../build/c3c compile-run examples/contextfree/boolerr.c3
|
||||
../build/c3c compile-run examples/load_world.c3
|
||||
../build/c3c compile-run examples/process.c3
|
||||
../build/c3c compile-run examples/ls.c3
|
||||
../build/c3c compile-run --linker=builtin linux_stack.c3
|
||||
../build/c3c compile-run linux_stack.c3
|
||||
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
|
||||
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit -g1 --safe
|
||||
../build/c3c compile-test unit
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log
|
||||
../../build/c3c run -vvv
|
||||
|
||||
- name: Test WASM
|
||||
run: |
|
||||
cd resources/testfragments
|
||||
../../build/c3c compile --reloc=none --target wasm32 -g0 --no-entry -Os wasm4.c3
|
||||
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log --forcelinker
|
||||
../../build/c3c run -vvv --linker=builtin
|
||||
|
||||
- name: Init a library & a project
|
||||
run: |
|
||||
./build/c3c init-lib mylib
|
||||
ls mylib.c3l
|
||||
./build/c3c init myproject
|
||||
ls myproject
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == 16
|
||||
run: |
|
||||
mkdir linux
|
||||
cp -r lib linux
|
||||
cp msvc_build_libraries.py linux
|
||||
cp build/c3c linux
|
||||
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz linux
|
||||
|
||||
- name: upload artifacts
|
||||
if: matrix.llvm_version == 16
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: c3-ubuntu-20-${{matrix.build_type}}
|
||||
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
|
||||
|
||||
build-mac:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
@@ -396,24 +614,30 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
llvm_version: [15, 16]
|
||||
llvm_version: [17, 18]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download LLVM
|
||||
run: |
|
||||
brew install llvm@${{ matrix.llvm_version }} ninja curl
|
||||
echo "/usr/local/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
|
||||
echo "/opt/homebrew/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
|
||||
TMP_PATH=$(xcrun --show-sdk-path)/user/include
|
||||
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
|
||||
|
||||
- name: CMake
|
||||
if: matrix.llvm_version < 18
|
||||
run: |
|
||||
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake --build build
|
||||
- name: CMake18
|
||||
if: matrix.llvm_version >= 18
|
||||
run: |
|
||||
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}}.1 -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake --build build
|
||||
|
||||
- name: Vendor-fetch
|
||||
run: |
|
||||
./build/c3c vendor-fetch raylib
|
||||
./build/c3c vendor-fetch raylib5
|
||||
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
@@ -422,27 +646,41 @@ jobs:
|
||||
../build/c3c compile-run examples/time.c3
|
||||
../build/c3c compile-run examples/fannkuch-redux.c3
|
||||
../build/c3c compile-run examples/contextfree/boolerr.c3
|
||||
../build/c3c compile-run examples/process.c3
|
||||
../build/c3c compile-run examples/load_world.c3
|
||||
../build/c3c compile-run -O5 examples/load_world.c3
|
||||
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
|
||||
|
||||
- name: Compile and run dynlib-test
|
||||
run: |
|
||||
cd resources/examples/dynlib-test
|
||||
../../../build/c3c -vv dynamic-lib add.c3
|
||||
../../../build/c3c compile-run test.c3 -l ./add.dylib
|
||||
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit -g1 --safe
|
||||
../build/c3c compile-test unit
|
||||
|
||||
- name: Test WASM
|
||||
run: |
|
||||
cd resources/testfragments
|
||||
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log
|
||||
../../build/c3c run -vvv
|
||||
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log --forcelinker
|
||||
../../build/c3c run -vvv --linker=builtin
|
||||
|
||||
- name: Build testproject lib
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c build hello_world_lib --debug-log
|
||||
../../build/c3c build hello_world_lib -vvv
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
@@ -450,7 +688,7 @@ jobs:
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
|
||||
run: |
|
||||
mkdir macos
|
||||
cp -r lib macos
|
||||
@@ -459,8 +697,8 @@ jobs:
|
||||
zip -r c3-macos-${{matrix.build_type}}.zip macos
|
||||
|
||||
- name: upload artifacts
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
|
||||
uses: actions/upload-artifact@v3
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: c3-macos-${{matrix.build_type}}
|
||||
path: c3-macos-${{matrix.build_type}}.zip
|
||||
@@ -468,11 +706,11 @@ jobs:
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-msvc, build-linux, build-mac]
|
||||
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu20]
|
||||
if: github.ref == 'refs/heads/master'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: delete tag
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@v6
|
||||
@@ -495,18 +733,22 @@ jobs:
|
||||
sha: context.sha
|
||||
})
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
- run: cp -r lib c3-windows-Release
|
||||
- run: cp -r lib c3-windows-Debug
|
||||
- run: cp msvc_build_libraries.py c3-windows-Release
|
||||
- run: cp msvc_build_libraries.py c3-windows-Debug
|
||||
- run: cp install_win_reqs.bat c3-windows-Release
|
||||
- run: cp install_win_reqs.bat c3-windows-Debug
|
||||
- run: zip -r c3-windows-Release.zip c3-windows-Release
|
||||
- run: zip -r c3-windows-Debug.zip c3-windows-Debug
|
||||
- run: zip -r c3-windows.zip c3-windows-Release
|
||||
- run: zip -r c3-windows-debug.zip c3-windows-Debug
|
||||
- run: mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux-Release/c3-linux.tar.gz
|
||||
- run: mv c3-linux-Debug/c3-linux-Debug.tar.gz c3-linux-Debug/c3-linux-debug.tar.gz
|
||||
- run: mv c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
|
||||
- run: mv c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
|
||||
- run: mv c3-macos-Release/c3-macos-Release.zip c3-macos-Release/c3-macos.zip
|
||||
- run: mv c3-macos-Debug/c3-macos-Debug.zip c3-macos-Debug/c3-macos-debug.zip
|
||||
|
||||
- id: create_release
|
||||
uses: actions/create-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -514,84 +756,12 @@ jobs:
|
||||
release_name: latest
|
||||
draft: false
|
||||
prerelease: true
|
||||
|
||||
- name: upload windows
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: c3-windows-Release.zip
|
||||
asset_name: c3-windows.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: upload windows debug
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: c3-windows-Debug.zip
|
||||
asset_name: c3-windows-debug.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: upload linux
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: c3-linux-Release/c3-linux-Release.tar.gz
|
||||
asset_name: c3-linux.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
|
||||
- name: upload linux debug
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: c3-linux-Debug/c3-linux-Debug.tar.gz
|
||||
asset_name: c3-linux-debug.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
|
||||
- name: upload ubuntu 20
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
|
||||
asset_path: c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz
|
||||
asset_name: c3-ubuntu-20.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
|
||||
- name: upload ubuntu 20 debug
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz
|
||||
asset_name: c3-ubuntu-20-debug.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
|
||||
- name: upload macos
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: c3-macos-Release/c3-macos-Release.zip
|
||||
asset_name: c3-macos.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: upload macos debug
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: c3-macos-Debug/c3-macos-Debug.zip
|
||||
asset_name: c3-macos-debug.zip
|
||||
asset_content_type: application/zip
|
||||
files: |
|
||||
c3-windows.zip
|
||||
c3-windows-debug.zip
|
||||
c3-linux-Release/c3-linux.tar.gz
|
||||
c3-linux-Debug/c3-linux-debug.tar.gz
|
||||
c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
|
||||
c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
|
||||
c3-macos-Release/c3-macos.zip
|
||||
c3-macos-Debug/c3-macos-debug.zip
|
||||
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -19,6 +19,7 @@
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.tlb
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
@@ -67,3 +68,14 @@ out/
|
||||
|
||||
/cmake-build-debug/
|
||||
/cmake-build-release/
|
||||
|
||||
# Emacs files
|
||||
TAGS
|
||||
|
||||
# Clangd LSP files
|
||||
/.cache/
|
||||
/compile_commands.json
|
||||
|
||||
# 'nix build' resulting symlink
|
||||
result
|
||||
|
||||
|
||||
472
CMakeLists.txt
472
CMakeLists.txt
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
# Grab the version
|
||||
file(READ "src/version.h" ver)
|
||||
@@ -10,11 +10,25 @@ endif()
|
||||
project(c3c VERSION ${CMAKE_MATCH_1})
|
||||
message("C3C version: ${CMAKE_PROJECT_VERSION}")
|
||||
|
||||
# Avoid warning for FetchContent
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
||||
cmake_policy(SET CMP0135 NEW)
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
|
||||
if (MSVC)
|
||||
set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib")
|
||||
set(CMAKE_INSTALL_BINDIR "c:\\c3c")
|
||||
else ()
|
||||
set(CMAKE_INSTALL_LIBDIR "/usr/local/lib/c3")
|
||||
set(CMAKE_INSTALL_BINDIR "/usr/local/bin/c3c")
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
# Enable fetching (for Windows)
|
||||
include(FetchContent)
|
||||
include(FeatureSummary)
|
||||
|
||||
|
||||
set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
|
||||
set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
|
||||
|
||||
@@ -23,26 +37,34 @@ set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
if(MSVC)
|
||||
message(STATUS "MSVC version ${MSVC_VERSION}")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHsc")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHsc")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
|
||||
if (true)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined,address -fno-exceptions")
|
||||
endif()
|
||||
endif()
|
||||
#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O1 -fsanitize=undefined")
|
||||
#set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O1 -fsanitize=undefined")
|
||||
#set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined")
|
||||
#set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fsanitize=undefined")
|
||||
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined")
|
||||
|
||||
option(C3_LINK_DYNAMIC "link dynamically with LLVM/LLD libs")
|
||||
|
||||
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
|
||||
option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF)
|
||||
option(C3_USE_TB "Use TB" OFF)
|
||||
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
|
||||
option(C3_WITH_LLVM "Build with LLVM" ON)
|
||||
option(C3_LLD_DIR "Use custom LLD directory" "")
|
||||
|
||||
set(C3_USE_MIMALLOC OFF)
|
||||
if(C3_USE_MIMALLOC)
|
||||
@@ -51,18 +73,20 @@ if(C3_USE_MIMALLOC)
|
||||
option(MI_PADDING OFF)
|
||||
option(MI_DEBUG_FULL OFF)
|
||||
FetchContent_Declare(
|
||||
mimalloc
|
||||
GIT_REPOSITORY https://github.com/microsoft/mimalloc.git
|
||||
GIT_TAG ${C3_MIMALLOC_TAG}
|
||||
mimalloc
|
||||
GIT_REPOSITORY https://github.com/microsoft/mimalloc.git
|
||||
GIT_TAG ${C3_MIMALLOC_TAG}
|
||||
)
|
||||
FetchContent_MakeAvailable(mimalloc)
|
||||
endif()
|
||||
if (NOT WIN32)
|
||||
find_package(CURL)
|
||||
endif()
|
||||
if (NOT C3_LLVM_VERSION STREQUAL "auto")
|
||||
if (${C3_LLVM_VERSION} VERSION_LESS 15 OR ${C3_LLVM_VERSION} VERSION_GREATER 17)
|
||||
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
|
||||
if(C3_WITH_LLVM)
|
||||
if (NOT C3_LLVM_VERSION STREQUAL "auto")
|
||||
if (${C3_LLVM_VERSION} VERSION_LESS 17 OR ${C3_LLVM_VERSION} VERSION_GREATER 20)
|
||||
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -81,141 +105,195 @@ if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
|
||||
if (C3_LLVM_VERSION STREQUAL "auto")
|
||||
set(C3_LLVM_VERSION "16")
|
||||
endif()
|
||||
FetchContent_Declare(
|
||||
LLVM_Windows
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_16_0_2/llvm-16.0.2-windows-amd64-msvc17-libcmt.7z
|
||||
# Clangd LSP support
|
||||
option(C3_ENABLE_CLANGD_LSP "Enable/Disable output of compile commands during generation." OFF)
|
||||
if(C3_ENABLE_CLANGD_LSP)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||
${CMAKE_BINARY_DIR}/compile_commands.json
|
||||
${CMAKE_SOURCE_DIR}/compile_commands.json
|
||||
)
|
||||
FetchContent_Declare(
|
||||
LLVM_Windows_debug
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_16_0_2/llvm-16.0.2-windows-amd64-msvc17-libcmt-dbg.7z
|
||||
)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
message("Loading Windows LLVM debug libraries, this may take a while...")
|
||||
FetchContent_MakeAvailable(LLVM_Windows_debug)
|
||||
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_windows_debug_SOURCE_DIR} ${CMAKE_SYSTEM_PREFIX_PATH})
|
||||
else()
|
||||
message("Loading Windows LLVM libraries, this may take a while...")
|
||||
FetchContent_MakeAvailable(LLVM_Windows)
|
||||
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_windows_SOURCE_DIR} ${CMAKE_SYSTEM_PREFIX_PATH})
|
||||
endif()
|
||||
find_package(LLVM REQUIRED CONFIG)
|
||||
find_package(LLD REQUIRED CONFIG)
|
||||
else()
|
||||
if (NOT C3_LLVM_VERSION STREQUAL "auto")
|
||||
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
|
||||
else()
|
||||
endif(C3_ENABLE_CLANGD_LSP)
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
|
||||
if (C3_LLVM_VERSION STREQUAL "auto")
|
||||
set(C3_LLVM_VERSION "19")
|
||||
endif()
|
||||
FetchContent_Declare(
|
||||
LLVM_Windows
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt.7z
|
||||
)
|
||||
FetchContent_Declare(
|
||||
LLVM_Windows_debug
|
||||
URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt-dbg.7z
|
||||
)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
message("Loading Windows LLVM debug libraries, this may take a while...")
|
||||
FetchContent_MakeAvailable(LLVM_Windows_debug)
|
||||
set(llvm_dir ${llvm_windows_debug_SOURCE_DIR})
|
||||
else()
|
||||
message("Loading Windows LLVM libraries, this may take a while...")
|
||||
FetchContent_MakeAvailable(LLVM_Windows)
|
||||
set(llvm_dir ${llvm_windows_SOURCE_DIR})
|
||||
endif()
|
||||
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH})
|
||||
|
||||
find_package(LLVM REQUIRED CONFIG)
|
||||
find_package(LLD REQUIRED CONFIG)
|
||||
else()
|
||||
if (NOT C3_LLVM_VERSION STREQUAL "auto")
|
||||
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
|
||||
else()
|
||||
find_package(LLVM REQUIRED CONFIG)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
|
||||
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
|
||||
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
|
||||
|
||||
if (NOT LLVM_PACKAGE_VERSION VERSION_GREATER_EQUAL 15.0)
|
||||
message(FATAL_ERROR "LLVM version 15.0 or later is required.")
|
||||
endif()
|
||||
|
||||
if(LLVM_ENABLE_RTTI)
|
||||
message(STATUS "LLVM was built with RTTI")
|
||||
else()
|
||||
message(STATUS "LLVM was not built with RTTI")
|
||||
endif()
|
||||
|
||||
string(REPLACE "." ";" VERSION_LIST ${LLVM_PACKAGE_VERSION})
|
||||
list(GET VERSION_LIST 0 LLVM_MAJOR_VERSION)
|
||||
|
||||
include_directories(${LLVM_INCLUDE_DIRS})
|
||||
link_directories(${LLVM_LIBRARY_DIRS})
|
||||
add_definitions(${LLVM_DEFINITIONS})
|
||||
|
||||
if(NOT C3_LINK_DYNAMIC)
|
||||
set(LLVM_LINK_COMPONENTS
|
||||
AllTargetsAsmParsers
|
||||
AllTargetsCodeGens
|
||||
AllTargetsDescs
|
||||
AllTargetsDisassemblers
|
||||
AllTargetsInfos
|
||||
Analysis
|
||||
AsmPrinter
|
||||
BitReader
|
||||
Core
|
||||
DebugInfoPDB
|
||||
InstCombine
|
||||
IrReader
|
||||
LibDriver
|
||||
Linker
|
||||
LTO
|
||||
MC
|
||||
MCDisassembler
|
||||
native
|
||||
nativecodegen
|
||||
Object
|
||||
Option
|
||||
ScalarOpts
|
||||
Support
|
||||
Target
|
||||
TransformUtils
|
||||
WindowsManifest
|
||||
WindowsDriver
|
||||
)
|
||||
|
||||
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
|
||||
|
||||
if(NOT ${C3_LLD_DIR} EQUAL "" AND EXISTS ${C3_LLD_DIR})
|
||||
message("C3_LLD_DIR: " ${C3_LLD_DIR})
|
||||
set(LLVM_LIBRARY_DIRS
|
||||
"${LLVM_LIBRARY_DIRS}"
|
||||
"${C3_LLD_DIR}"
|
||||
)
|
||||
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
|
||||
endif()
|
||||
|
||||
# These don't seem to be reliable on windows.
|
||||
message(STATUS "using find_library")
|
||||
find_library(LLD_COFF NAMES liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.dll.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.dll.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.dll.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.dll.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.dll.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
else()
|
||||
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
set(llvm_libs ${LLVM})
|
||||
|
||||
# These don't seem to be reliable on windows.
|
||||
message(STATUS "using find_library")
|
||||
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
|
||||
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
|
||||
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
|
||||
include_directories(${LLVM_INCLUDE_DIRS})
|
||||
link_directories(${LLVM_LIBRARY_DIRS})
|
||||
add_definitions(${LLVM_DEFINITIONS})
|
||||
if (NOT(${CMAKE_BINARY_DIR} EQUAL ${CMAKE_SOURCE_DIR}))
|
||||
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
|
||||
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
|
||||
endif()
|
||||
|
||||
set(LLVM_LINK_COMPONENTS
|
||||
AllTargetsAsmParsers
|
||||
AllTargetsCodeGens
|
||||
AllTargetsDescs
|
||||
AllTargetsDisassemblers
|
||||
AllTargetsInfos
|
||||
Analysis
|
||||
AsmPrinter
|
||||
BitReader
|
||||
Core
|
||||
DebugInfoPDB
|
||||
InstCombine
|
||||
IrReader
|
||||
LibDriver
|
||||
Linker
|
||||
LTO
|
||||
MC
|
||||
MCDisassembler
|
||||
native
|
||||
nativecodegen
|
||||
Object
|
||||
Option
|
||||
ScalarOpts
|
||||
Support
|
||||
Target
|
||||
TransformUtils
|
||||
WindowsManifest
|
||||
WindowsDriver
|
||||
)
|
||||
|
||||
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
|
||||
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
|
||||
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
|
||||
|
||||
|
||||
# These don't seem to be reliable on windows.
|
||||
message(STATUS "using find_library")
|
||||
find_library(LLD_COFF NAMES lldCOFF.lib lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_COMMON NAMES lldCommon.lib lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_ELF NAMES lldELF.lib lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_MACHO NAMES lldMachO.lib lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_MINGW NAMES lldMinGW.lib lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
find_library(LLD_WASM NAMES lldWasm.lib lldWasm.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
|
||||
if (${LLVM_PACKAGE_VERSION} VERSION_GREATER_EQUAL 16)
|
||||
if(C3_WITH_LLVM)
|
||||
find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
set(lld_libs
|
||||
${LLD_COFF}
|
||||
${LLD_COMMON}
|
||||
${LLD_WASM}
|
||||
${LLD_MINGW}
|
||||
${LLD_ELF}
|
||||
${LLD_MACHO}
|
||||
)
|
||||
else()
|
||||
set(lld_libs
|
||||
${LLD_COFF}
|
||||
${LLD_COMMON}
|
||||
${LLD_WASM}
|
||||
${LLD_MINGW}
|
||||
${LLD_ELF}
|
||||
${LLD_MACHO}
|
||||
${LLD_COFF}
|
||||
${LLD_COMMON}
|
||||
${LLD_WASM}
|
||||
${LLD_MINGW}
|
||||
${LLD_ELF}
|
||||
${LLD_MACHO}
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
set(lld_libs ${lld_libs} xar)
|
||||
find_file(RT_ASAN_DYNAMIC NAMES libclang_rt.asan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
|
||||
find_file(RT_TSAN_DYNAMIC NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
|
||||
find_file(RT_UBSAN_DYNAMIC NAMES libclang_rt.ubsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
|
||||
find_file(RT_LSAN_DYNAMIC NAMES libclang_rt.lsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
|
||||
set(sanitizer_runtime_libraries
|
||||
${RT_ASAN_DYNAMIC}
|
||||
${RT_TSAN_DYNAMIC}
|
||||
# Unused
|
||||
# ${RT_UBSAN_DYNAMIC}
|
||||
# ${RT_LSAN_DYNAMIC}
|
||||
)
|
||||
endif()
|
||||
|
||||
message(STATUS "linking to llvm libs ${lld_libs}")
|
||||
message(STATUS "Found lld libs ${lld_libs}")
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
set(lld_libs ${lld_libs} xar)
|
||||
endif ()
|
||||
|
||||
message(STATUS "linking to llvm libs ${lld_libs}")
|
||||
message(STATUS "Found lld libs ${lld_libs}")
|
||||
|
||||
|
||||
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
|
||||
add_library(miniz STATIC dependencies/miniz/miniz.c)
|
||||
|
||||
add_executable(c3c
|
||||
src/build/builder.c
|
||||
src/build/build_options.c
|
||||
src/build/project_creation.c
|
||||
src/build/project_manipulation.c
|
||||
src/build/libraries.c
|
||||
src/compiler/ast.c
|
||||
src/compiler/bigint.c
|
||||
src/compiler/c_abi_internal.h
|
||||
src/compiler/codegen_general.c
|
||||
src/compiler/compiler.c
|
||||
src/compiler/compiler.h
|
||||
src/compiler/subprocess.c
|
||||
src/compiler/subprocess.h
|
||||
src/compiler/context.c
|
||||
src/compiler/copying.c
|
||||
src/compiler/diagnostics.c
|
||||
src/compiler/dwarf.h
|
||||
src/compiler/enums.h
|
||||
src/compiler/float.c
|
||||
src/compiler/headers.c
|
||||
src/compiler/json_output.c
|
||||
src/compiler/lexer.c
|
||||
src/compiler/libraries.c
|
||||
src/compiler/linker.c
|
||||
src/compiler/llvm_codegen.c
|
||||
src/compiler/abi/c_abi_aarch64.c
|
||||
src/compiler/abi/c_abi.c
|
||||
src/compiler/abi/c_abi_riscv.c
|
||||
@@ -223,14 +301,6 @@ add_executable(c3c
|
||||
src/compiler/abi/c_abi_win64.c
|
||||
src/compiler/abi/c_abi_x64.c
|
||||
src/compiler/abi/c_abi_x86.c
|
||||
src/compiler/llvm_codegen_debug_info.c
|
||||
src/compiler/llvm_codegen_expr.c
|
||||
src/compiler/llvm_codegen_function.c
|
||||
src/compiler/llvm_codegen_instr.c
|
||||
src/compiler/llvm_codegen_module.c
|
||||
src/compiler/llvm_codegen_stmt.c
|
||||
src/compiler/llvm_codegen_type.c
|
||||
src/compiler/llvm_codegen_value.c
|
||||
src/compiler/module.c
|
||||
src/compiler/number.c
|
||||
src/compiler/parse_expr.c
|
||||
@@ -274,16 +344,59 @@ add_executable(c3c
|
||||
src/utils/unzipper.c
|
||||
src/compiler/decltable.c
|
||||
src/compiler/mac_support.c
|
||||
src/compiler/llvm_codegen_storeload.c
|
||||
src/compiler/windows_support.c
|
||||
src/compiler/codegen_asm.c
|
||||
src/compiler/asm_target.c
|
||||
src/compiler/llvm_codegen_builtins.c
|
||||
src/compiler/expr.c
|
||||
src/utils/time.c
|
||||
src/utils/http.c
|
||||
src/compiler/sema_liveness.c)
|
||||
src/compiler/sema_liveness.c
|
||||
src/build/common_build.c
|
||||
src/compiler/sema_const.c
|
||||
${CMAKE_BINARY_DIR}/git_hash.h
|
||||
)
|
||||
|
||||
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
# We are inside of a git repository so rebuilding the hash every time something changes.
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/git_hash.h
|
||||
COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_LIST_DIR}/git_hash.cmake"
|
||||
DEPENDS "${CMAKE_CURRENT_LIST_DIR}/.git")
|
||||
else()
|
||||
# We are NOT inside of a git repository. Building the has only once.
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/git_hash.h
|
||||
COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_LIST_DIR}/git_hash.cmake")
|
||||
endif()
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
target_sources(c3c PRIVATE
|
||||
src/compiler/c_codegen.c
|
||||
src/compiler/llvm_codegen.c
|
||||
src/compiler/llvm_codegen_debug_info.c
|
||||
src/compiler/llvm_codegen_expr.c
|
||||
src/compiler/llvm_codegen_function.c
|
||||
src/compiler/llvm_codegen_instr.c
|
||||
src/compiler/llvm_codegen_module.c
|
||||
src/compiler/llvm_codegen_stmt.c
|
||||
src/compiler/llvm_codegen_type.c
|
||||
src/compiler/llvm_codegen_value.c
|
||||
src/compiler/llvm_codegen_storeload.c
|
||||
src/compiler/llvm_codegen_builtins.c)
|
||||
|
||||
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=1)
|
||||
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
|
||||
else()
|
||||
target_sources(c3c PRIVATE src/utils/hostinfo.c)
|
||||
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0)
|
||||
endif()
|
||||
|
||||
target_include_directories(c3c PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/src/"
|
||||
"${CMAKE_BINARY_DIR}")
|
||||
|
||||
target_include_directories(miniz PUBLIC
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/miniz/")
|
||||
|
||||
if (C3_USE_TB)
|
||||
file(GLOB tilde-sources
|
||||
@@ -297,7 +410,7 @@ if (C3_USE_TB)
|
||||
tilde-backend/src/tb/x64/*.c
|
||||
tilde-backend/src/tb/wasm/*.c
|
||||
tilde-backend/src/tb/aarch64/*.c
|
||||
)
|
||||
)
|
||||
target_sources(c3c PRIVATE
|
||||
src/compiler/tilde_codegen.c
|
||||
src/compiler/tilde_codegen_instr.c
|
||||
@@ -317,23 +430,29 @@ if (C3_USE_TB)
|
||||
|
||||
target_include_directories(c3c PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/tilde-backend/include/")
|
||||
|
||||
else()
|
||||
|
||||
target_compile_definitions(c3c PUBLIC TB_AVAILABLE=0)
|
||||
|
||||
endif()
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
target_link_libraries(c3c ${llvm_libs} miniz c3c_wrappers ${lld_libs})
|
||||
|
||||
target_include_directories(c3c PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/src/")
|
||||
target_include_directories(c3c PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/wrapper/include/")
|
||||
|
||||
target_include_directories(c3c_wrappers PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/wrapper/include/")
|
||||
|
||||
target_include_directories(c3c_wrappers PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/wrapper/src/")
|
||||
target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
|
||||
|
||||
target_include_directories(miniz PUBLIC
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/miniz/")
|
||||
else()
|
||||
|
||||
target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
|
||||
target_link_libraries(c3c ${llvm_libs} miniz c3c_wrappers ${lld_libs})
|
||||
target_link_libraries(c3c ${llvm_libs} miniz ${lld_libs})
|
||||
|
||||
endif()
|
||||
|
||||
if(C3_USE_MIMALLOC)
|
||||
target_link_libraries(c3c mimalloc-static)
|
||||
@@ -343,6 +462,11 @@ if (WIN32)
|
||||
target_link_libraries(c3c Winhttp.lib)
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
message("Increase stack for msys")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,8388608")
|
||||
endif ()
|
||||
|
||||
if (CURL_FOUND)
|
||||
target_link_libraries(c3c ${CURL_LIBRARIES})
|
||||
target_include_directories(c3c PRIVATE ${CURL_INCLUDES})
|
||||
@@ -351,38 +475,78 @@ else()
|
||||
target_compile_definitions(c3c PUBLIC CURL_FOUND=0)
|
||||
endif()
|
||||
|
||||
|
||||
if(MSVC)
|
||||
message("Adding MSVC options")
|
||||
target_compile_options(c3c PRIVATE /wd4068 /wd4090 /WX /Wv:18)
|
||||
target_compile_options(c3c_wrappers PUBLIC /wd4624 /wd4267 /wd4244 /WX /Wv:18)
|
||||
target_link_options(c3c_wrappers PUBLIC /ignore:4099)
|
||||
if(C3_WITH_LLVM)
|
||||
target_compile_options(c3c_wrappers PUBLIC /wd4624 /wd4267 /wd4244 /WX /Wv:18)
|
||||
if(NOT LLVM_ENABLE_RTTI)
|
||||
target_compile_options(c3c_wrappers PUBLIC /GR-)
|
||||
endif()
|
||||
target_link_options(c3c_wrappers PUBLIC /ignore:4099)
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_options(c3c PUBLIC /MTd)
|
||||
target_compile_options(c3c_wrappers PUBLIC /MTd)
|
||||
if (C3_WITH_LLVM)
|
||||
target_compile_options(c3c_wrappers PUBLIC /MTd)
|
||||
endif()
|
||||
target_compile_options(miniz PUBLIC /MTd)
|
||||
if (C3_USE_TB)
|
||||
target_compile_options(tilde-backend PUBLIC /MTd)
|
||||
endif()
|
||||
else()
|
||||
target_compile_options(c3c PUBLIC /MT)
|
||||
target_compile_options(c3c_wrappers PUBLIC /MT)
|
||||
if (C3_WITH_LLVM)
|
||||
target_compile_options(c3c_wrappers PUBLIC /MT)
|
||||
endif()
|
||||
target_compile_options(miniz PUBLIC /MT)
|
||||
if (C3_USE_TB)
|
||||
target_compile_options(tilde-backend PUBLIC /MT)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
set(clang_lib_dir ${llvm_dir}/lib/clang/${C3_LLVM_VERSION}/lib/windows)
|
||||
set(sanitizer_runtime_libraries
|
||||
${clang_lib_dir}/clang_rt.asan-x86_64.lib
|
||||
${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.lib
|
||||
${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.dll
|
||||
${clang_lib_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "using gcc/clang warning switches")
|
||||
target_link_options(c3c PRIVATE -pthread)
|
||||
if (C3_WITH_LLVM AND NOT LLVM_ENABLE_RTTI)
|
||||
target_compile_options(c3c_wrappers PRIVATE -fno-rtti)
|
||||
endif()
|
||||
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
|
||||
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)
|
||||
if (WIN32)
|
||||
target_compile_definitions(c3c PRIVATE USE_PTHREAD=1)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
install(TARGETS c3c DESTINATION bin)
|
||||
install(DIRECTORY lib/ DESTINATION lib/c3)
|
||||
|
||||
# Man page install (OSX/Linux only)
|
||||
if (NOT WIN32)
|
||||
install(FILES c3c.1 DESTINATION "share/man/man1")
|
||||
endif()
|
||||
|
||||
if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
|
||||
add_custom_command(TARGET c3c POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E rm -rf -- $<TARGET_FILE_DIR:c3c>/c3c_rt
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory $<TARGET_FILE_DIR:c3c>/c3c_rt
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy ${sanitizer_runtime_libraries} $<TARGET_FILE_DIR:c3c>/c3c_rt
|
||||
VERBATIM
|
||||
COMMENT "Copying sanitizer runtime libraries to output directory")
|
||||
|
||||
if (APPLE)
|
||||
# Change LC_ID_DYLIB to be rpath-based instead of having an absolute path
|
||||
add_custom_command(TARGET c3c POST_BUILD
|
||||
COMMAND find $<TARGET_FILE_DIR:c3c>/c3c_rt -type f -name "*.dylib" -execdir ${LLVM_TOOLS_BINARY_DIR}/llvm-install-name-tool -id @rpath/{} {} $<SEMICOLON>
|
||||
VERBATIM)
|
||||
endif()
|
||||
|
||||
install(DIRECTORY $<TARGET_FILE_DIR:c3c>/c3c_rt/ DESTINATION bin/c3c_rt)
|
||||
endif()
|
||||
|
||||
feature_summary(WHAT ALL)
|
||||
|
||||
139
README.md
139
README.md
@@ -10,7 +10,8 @@ Precompiled binaries for the following operating systems are available:
|
||||
|
||||
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
|
||||
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
|
||||
- MacOS x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-mac-with-precompiled-binaries).
|
||||
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
|
||||
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-mac-with-precompiled-binaries).
|
||||
|
||||
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
|
||||
|
||||
@@ -32,10 +33,10 @@ whole new language.
|
||||
|
||||
### Example code
|
||||
|
||||
The following code shows [generic modules](http://www.c3-lang.org/generics/) (more examples can be found at http://www.c3-lang.org/examples/).
|
||||
The following code shows [generic modules](https://c3-lang.org/references/docs/generics/) (more examples can be found at https://c3-lang.org/references/docs/examples/).
|
||||
|
||||
```c++
|
||||
module stack <Type>;
|
||||
```cpp
|
||||
module stack (<Type>);
|
||||
// Above: the parameterized type is applied to the entire module.
|
||||
|
||||
struct Stack
|
||||
@@ -54,8 +55,8 @@ fn void Stack.push(Stack* this, Type element)
|
||||
if (this.capacity == this.size)
|
||||
{
|
||||
this.capacity *= 2;
|
||||
if (this.capacity < 16) this.capacity = 16;
|
||||
this.elems = mem::realloc(this.elems, Type.sizeof * this.capacity);
|
||||
if (this.capacity < 16) this.capacity = 16;
|
||||
this.elems = realloc(this.elems, Type.sizeof * this.capacity);
|
||||
}
|
||||
this.elems[this.size++] = element;
|
||||
}
|
||||
@@ -79,13 +80,13 @@ import stack;
|
||||
|
||||
// Define our new types, the first will implicitly create
|
||||
// a complete copy of the entire Stack module with "Type" set to "int"
|
||||
def IntStack = Stack<int>;
|
||||
def IntStack = Stack(<int>);
|
||||
// The second creates another copy with "Type" set to "double"
|
||||
def DoubleStack = Stack<double>;
|
||||
def DoubleStack = Stack(<double>);
|
||||
|
||||
// If we had added "define IntStack2 = Stack<int>"
|
||||
// If we had added "define IntStack2 = Stack(<int>)"
|
||||
// no additional copy would have been made (since we already
|
||||
// have an parameterization of Stack<int>) so it would
|
||||
// have an parameterization of Stack(<int>)) so it would
|
||||
// be same as declaring IntStack2 an alias of IntStack
|
||||
|
||||
// Importing an external C function is straightforward
|
||||
@@ -122,7 +123,7 @@ fn void main()
|
||||
- No mandatory header files
|
||||
- New semantic macro system
|
||||
- Module based name spacing
|
||||
- Subarrays (slices)
|
||||
- Slices
|
||||
- Compile time reflection
|
||||
- Enhanced compile time execution
|
||||
- Generics based on generic modules
|
||||
@@ -131,17 +132,15 @@ fn void main()
|
||||
- Value methods
|
||||
- Associated enum data
|
||||
- No preprocessor
|
||||
- Less undefined behaviour and runtime checks in "safe" mode
|
||||
- Less undefined behaviour and added runtime checks in "safe" mode
|
||||
- Limited operator overloading to enable userland dynamic arrays
|
||||
- Optional pre and post conditions
|
||||
|
||||
### Current status
|
||||
|
||||
The current version of the compiler is alpha release 0.4.
|
||||
The current stable version of the compiler is **version 0.6.5**.
|
||||
|
||||
Design work on C3 is complete aside from fleshing out details, such as
|
||||
inline asm. As the standard library work progresses, changes and improvements
|
||||
to the language will happen continuously.
|
||||
The upcoming 0.6.x releases will focus on expanding the standard library.
|
||||
Follow the issues [here](https://github.com/c3lang/c3c/issues).
|
||||
|
||||
If you have suggestions on how to improve the language, either [file an issue](https://github.com/c3lang/c3c/issues)
|
||||
@@ -149,7 +148,38 @@ or discuss C3 on its dedicated Discord: [https://discord.gg/qN76R87](https://dis
|
||||
|
||||
The compiler is currently verified to compile on Linux, Windows and MacOS.
|
||||
|
||||
**Support matrix**
|
||||
|
||||
| Platform | Native C3 compiler available? | Target supported | Stack trace | Threads | Sockets | Inline asm |
|
||||
|--------------------------|-------------------------------|-------------------------|-------------|----------|----------|------------|
|
||||
| Win32 x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
|
||||
| Win32 Aarch64 | Untested | Untested | Untested | Untested | Untested | Yes* |
|
||||
| MacOS x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
|
||||
| MacOS Aarch64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
|
||||
| iOS Aarch64 | No | Untested | Untested | Yes | Yes | Yes* |
|
||||
| Linux x86 | Yes | Yes | Yes | Yes | Yes | Yes* |
|
||||
| Linux x64 | Yes | Yes | Yes | Yes | Yes | Yes* |
|
||||
| Linux Aarch64 | Yes | Yes | Yes | Yes | Yes | Yes* |
|
||||
| Linux Riscv32 | Yes | Yes | Yes | Yes | Yes | Untested |
|
||||
| Linux Riscv64 | Yes | Yes | Yes | Yes | Yes | Untested |
|
||||
| ELF freestanding x86 | No | Untested | No | No | No | Yes* |
|
||||
| ELF freestanding x64 | No | Untested | No | No | No | Yes* |
|
||||
| ELF freestanding Aarch64 | No | Untested | No | No | No | Yes* |
|
||||
| ELF freestanding Riscv64 | No | Untested | No | No | No | Untested |
|
||||
| ELF freestanding Riscv32 | No | Untested | No | No | No | Untested |
|
||||
| FreeBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| FreeBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| NetBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| NetBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| OpenBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| OpenBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| MCU x86 | No | Untested | No | No | No | Yes* |
|
||||
| Wasm32 | No | Yes | No | No | No | No |
|
||||
| Wasm64 | No | Untested | No | No | No | No |
|
||||
|
||||
*\* Inline asm is still a work in progress*
|
||||
|
||||
More platforms will be supported in the future.
|
||||
|
||||
#### What can you help with?
|
||||
|
||||
@@ -176,16 +206,41 @@ The compiler is currently verified to compile on Linux, Windows and MacOS.
|
||||
2. Unpack executable and standard lib.
|
||||
3. Run `./c3c`.
|
||||
|
||||
#### Installing on Mac with precompiled binaries
|
||||
#### Installing on Ubuntu with precompiled binaries
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20-debug.tar.gz))
|
||||
2. Unpack executable and standard lib.
|
||||
3. Run `./c3c`.
|
||||
|
||||
#### Installing on MacOS with precompiled binaries
|
||||
1. Make sure you have XCode with command line tools installed.
|
||||
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-macos-debug.zip))
|
||||
3. Unzip executable and standard lib.
|
||||
4. Run `./c3c`.
|
||||
|
||||
(*Note that there is a known issue with debug symbol generation on MacOS 13, see [issue #1086](https://github.com/c3lang/c3c/issues/1086))
|
||||
|
||||
#### Installing on Arch Linux
|
||||
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git)
|
||||
You can use your AUR package manager or clone it manually:
|
||||
Arch includes c3c in the official 'extra' repo. It can be easily installed the usual way:
|
||||
|
||||
```sh
|
||||
sudo pacman -S c3c
|
||||
# or paru -S c3c
|
||||
# or yay -S c3c
|
||||
# or aura -A c3c
|
||||
```
|
||||
|
||||
There is also an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
|
||||
|
||||
You can use your AUR package manager:
|
||||
```sh
|
||||
paru -S c3c-git
|
||||
# or yay -S c3c-git
|
||||
# or aura -A c3c-git
|
||||
```
|
||||
|
||||
Or clone it manually:
|
||||
```sh
|
||||
git clone https://aur.archlinux.org/c3c-git.git
|
||||
cd c3c-git
|
||||
@@ -212,7 +267,7 @@ A `c3c` executable will be found under `bin/`.
|
||||
#### Installing on OS X using Homebrew
|
||||
|
||||
2. Install CMake: `brew install cmake`
|
||||
3. Install LLVM 15: `brew install llvm`
|
||||
3. Install LLVM 17+: `brew install llvm`
|
||||
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
5. Enter the C3C directory `cd c3c`.
|
||||
6. Create a build directory `mkdir build`
|
||||
@@ -248,7 +303,7 @@ called `hello_world` or `hello_world.exe`depending on platform.
|
||||
|
||||
#### Compiling on Windows
|
||||
|
||||
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++" (there is also `c3c/resources/install_win_reqs.bat` to automate this)
|
||||
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++"
|
||||
2. Install CMake
|
||||
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
4. Enter the C3C directory `cd c3c`.
|
||||
@@ -263,27 +318,41 @@ You can try it out by running some sample code: `c3c.exe compile ../resources/ex
|
||||
*Note that if you run into linking issues when building, make sure that you are using the latest version of VS17.*
|
||||
|
||||
|
||||
#### Compiling on Ubuntu 20.10
|
||||
#### Compiling on Ubuntu 24.04 LTS
|
||||
|
||||
1. Make sure you have a C compiler that handles C11 and a C++ compiler, such as GCC or Clang. Git also needs to be installed.
|
||||
2. Install CMake: `sudo apt install cmake`
|
||||
3. Install LLVM 15 (or greater: C3C supports LLVM 15-17): `sudo apt-get install clang-15 zlib1g zlib1g-dev libllvm15 llvm-15 llvm-15-dev llvm-15-runtime liblld-15-dev liblld-15`
|
||||
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
5. Enter the C3C directory `cd c3c`.
|
||||
6. Create a build directory `mkdir build`
|
||||
7. Change directory to the build directory `cd build`
|
||||
8. Set up CMake build: `cmake ..`
|
||||
9. Build: `cmake --build .`
|
||||
2. Install LLVM 18 `sudo apt-get install cmake git clang zlib1g zlib1g-dev libllvm18 llvm llvm-dev llvm-runtime liblld-dev liblld-18 libpolly-18-dev`
|
||||
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
4. Enter the C3C directory `cd c3c`.
|
||||
5. Create a build directory `mkdir build`
|
||||
6. Change directory to the build directory `cd build`
|
||||
7. Set up CMake build: `cmake ..`
|
||||
8. Build: `cmake --build .`
|
||||
|
||||
You should now have a `c3c` executable.
|
||||
|
||||
You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
|
||||
|
||||
|
||||
#### Compiling on Void Linux
|
||||
|
||||
1. As root, ensure that all project dependencies are installed: `xbps-install git cmake llvm17 llvm17-devel lld17-devel libcurl-devel ncurses-devel zlib-devel libzstd-devel libxml2-devel`
|
||||
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
- If you only need the latest commit, you may want to make a shallow clone instead: `git clone https://github.com/c3lang/c3c.git --depth=1`
|
||||
3. Enter the directory: `cd c3c`
|
||||
4. Create a build directory: `mkdir build`
|
||||
5. Enter the build directory: `cd build`
|
||||
6. Create the CMake build cache: `cmake ..`
|
||||
7. Build: `cmake --build .`
|
||||
|
||||
Your c3c executable should have compiled properly. You may want to test it: `./c3c compile ../resources/examples/hash.c3`
|
||||
For a sytem-wide installation, run the following as root: `cmake --install .`
|
||||
|
||||
|
||||
#### Compiling on other Linux / Unix variants
|
||||
|
||||
1. Install CMake.
|
||||
2. Install or compile LLVM and LLD *libraries* (version 15+ or higher)
|
||||
2. Install or compile LLVM and LLD *libraries* (version 17+ or higher)
|
||||
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
4. Enter the C3C directory `cd c3c`.
|
||||
5. Create a build directory `mkdir build`
|
||||
@@ -310,6 +379,12 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
|
||||
1. Write the test, either adding to existing test files in `/test/unit/` or add
|
||||
a new file. (If testing the standard library, put it in the `/test/unit/stdlib/` subdirectory).
|
||||
2. Make sure that the test functions have the `@test` attribute.
|
||||
3. Run tests and see that they pass. (Recommended settings: `c3c compile-test --safe -g1 -O0 test/unit`.
|
||||
3. Run tests and see that they pass. (Recommended settings: `c3c compile-test -O0 test/unit`.
|
||||
- in this example `test/unit/` is the relative path to the test directory, so adjust as required)
|
||||
4. Make a pull request for the new tests.
|
||||
|
||||
## Thank yous
|
||||
|
||||
A huge **THANK YOU** goes out to all contributors and sponsors.
|
||||
|
||||
A special thank you to sponsors [Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad) for going the extra mile.
|
||||
|
||||
69
benchmarks/stdlib/sort/quicksort.c3
Normal file
69
benchmarks/stdlib/sort/quicksort.c3
Normal file
@@ -0,0 +1,69 @@
|
||||
module sort_bench;
|
||||
|
||||
import std::sort;
|
||||
|
||||
fn void init() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(5);
|
||||
set_benchmark_max_iterations(10_000);
|
||||
}
|
||||
|
||||
fn void! quicksort_bench() @benchmark
|
||||
{
|
||||
// test set: 500 numbers between 0 and 99;
|
||||
int[] data = {
|
||||
71, 28, 2, 13, 62, 10, 54, 78, 63, 86,
|
||||
33, 65, 89, 51, 58, 0, 51, 16, 87, 30,
|
||||
89, 14, 52, 41, 88, 25, 83, 91, 56, 86,
|
||||
14, 64, 76, 18, 39, 24, 79, 62, 34, 58,
|
||||
90, 24, 56, 73, 85, 82, 79, 63, 47, 69,
|
||||
78, 29, 49, 28, 43, 47, 56, 53, 79, 56,
|
||||
19, 63, 29, 52, 71, 93, 61, 46, 30, 11,
|
||||
21, 26, 37, 86, 93, 74, 62, 0, 41, 17,
|
||||
26, 27, 34, 11, 54, 69, 72, 44, 74, 3,
|
||||
61, 62, 80, 90, 3, 82, 16, 12, 28, 1,
|
||||
2, 49, 4, 44, 57, 86, 63, 74, 33, 41,
|
||||
76, 77, 56, 57, 56, 88, 74, 71, 6, 59,
|
||||
40, 42, 94, 55, 21, 17, 17, 63, 21, 83,
|
||||
73, 19, 39, 88, 93, 74, 21, 0, 63, 45,
|
||||
69, 66, 22, 68, 86, 86, 85, 67, 8, 50,
|
||||
23, 98, 64, 80, 64, 36, 40, 30, 73, 36,
|
||||
23, 14, 1, 77, 82, 8, 18, 73, 37, 86,
|
||||
29, 70, 27, 87, 64, 81, 13, 0, 4, 83,
|
||||
90, 17, 71, 66, 38, 39, 54, 22, 86, 18,
|
||||
84, 66, 77, 25, 64, 93, 80, 91, 2, 92,
|
||||
47, 32, 90, 16, 46, 29, 56, 87, 70, 73,
|
||||
89, 41, 5, 54, 93, 63, 16, 39, 71, 84,
|
||||
74, 91, 69, 59, 49, 87, 74, 37, 75, 83,
|
||||
77, 19, 51, 44, 79, 62, 94, 20, 24, 83,
|
||||
37, 70, 57, 32, 93, 8, 29, 11, 7, 92,
|
||||
8, 23, 20, 21, 7, 70, 28, 20, 96, 6,
|
||||
50, 58, 30, 61, 66, 42, 50, 54, 64, 7,
|
||||
10, 53, 63, 44, 16, 39, 83, 73, 3, 29,
|
||||
97, 32, 36, 68, 84, 64, 73, 5, 29, 13,
|
||||
48, 3, 84, 65, 75, 68, 66, 22, 39, 33,
|
||||
39, 24, 27, 85, 18, 34, 3, 63, 32, 9,
|
||||
29, 66, 24, 90, 75, 50, 11, 95, 47, 14,
|
||||
92, 1, 76, 45, 76, 41, 55, 54, 38, 67,
|
||||
43, 40, 5, 61, 97, 11, 61, 24, 92, 24,
|
||||
76, 53, 60, 34, 78, 80, 70, 75, 30, 90,
|
||||
65, 99, 80, 61, 94, 75, 63, 67, 10, 35,
|
||||
23, 42, 31, 48, 14, 68, 84, 14, 79, 1,
|
||||
25, 94, 23, 53, 49, 69, 44, 73, 63, 51,
|
||||
44, 96, 88, 51, 94, 24, 64, 72, 59, 81,
|
||||
73, 93, 14, 35, 9, 53, 25, 48, 50, 88,
|
||||
46, 97, 67, 40, 27, 17, 2, 42, 11, 82,
|
||||
0, 46, 44, 38, 31, 88, 63, 88, 10, 82,
|
||||
77, 61, 24, 39, 27, 33, 10, 91, 69, 22,
|
||||
42, 74, 71, 13, 32, 56, 12, 46, 81, 74,
|
||||
17, 26, 45, 50, 76, 84, 76, 36, 43, 65,
|
||||
81, 64, 0, 49, 70, 11, 76, 19, 60, 55,
|
||||
15, 98, 31, 91, 56, 8, 97, 9, 3, 94,
|
||||
3, 88, 7, 2, 3, 98, 10, 51, 21, 79,
|
||||
99, 3, 8, 76, 52, 13, 40, 90, 85, 15,
|
||||
70, 77, 43, 30, 4, 89, 18, 21, 59, 17,
|
||||
};
|
||||
sort::quicksort(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
#!/bin/bash
|
||||
## build-with-docker.sh
|
||||
## @author gdm85
|
||||
## @modified by Kenta
|
||||
##
|
||||
## Script to build c3c for Ubuntu 22
|
||||
##
|
||||
#
|
||||
|
||||
read -p "Select Build Type: Debug/Release: " config
|
||||
: ${DOCKER:=docker}
|
||||
: ${IMAGE:="c3c-builder"}
|
||||
: ${CMAKE_BUILD_TYPE:=Release}
|
||||
: ${LLVM_VERSION:=18}
|
||||
: ${UBUNTU_VERSION:="22.04"}
|
||||
: ${CMAKE_VERSION:="3.20.0"}
|
||||
|
||||
set -e
|
||||
cd docker || exit 1 # Exit if the 'docker' directory doesn't exist
|
||||
|
||||
DOCKER=docker
|
||||
DOCKER_RUN=""
|
||||
IMAGE="c3c-builder"
|
||||
if type podman 2>/dev/null >/dev/null; then
|
||||
DOCKER=podman
|
||||
DOCKER_RUN="--userns=keep-id"
|
||||
IMAGE="localhost/$IMAGE"
|
||||
$DOCKER build \
|
||||
--build-arg LLVM_VERSION=$LLVM_VERSION \
|
||||
--build-arg CMAKE_VERSION=$CMAKE_VERSION \
|
||||
--build-arg UBUNTU_VERSION=$UBUNTU_VERSION \
|
||||
-t $IMAGE .
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Docker image build failed. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ $config == "Debug" ]; then
|
||||
echo "debug???"
|
||||
CMAKE_BUILD_TYPE=Debug
|
||||
else
|
||||
CMAKE_BUILD_TYPE="$config"
|
||||
fi
|
||||
|
||||
UBUNTU_VERSION="22.10"
|
||||
LLVM_VERSION="15"
|
||||
|
||||
IMAGE="$IMAGE:22"
|
||||
|
||||
cd docker && $DOCKER build -t $IMAGE\
|
||||
--build-arg DEPS="llvm-$LLVM_VERSION-dev liblld-$LLVM_VERSION-dev clang-$LLVM_VERSION libllvm$LLVM_VERSION llvm-$LLVM_VERSION-runtime" \
|
||||
--build-arg UBUNTU_VERSION="$UBUNTU_VERSION" .
|
||||
cd ..
|
||||
|
||||
rm -rf build bin
|
||||
mkdir -p build bin
|
||||
|
||||
exec $DOCKER run -ti --rm --tmpfs=/tmp $DOCKER_RUN -v "$PWD":/home/c3c/source -w /home/c3c/source $IMAGE bash -c \
|
||||
"cd build && cmake -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE -DC3_LLVM_VERSION=$LLVM_VERSION .. && cmake --build . && mv c3c lib ../bin/"
|
||||
chmod -R 777 build bin
|
||||
|
||||
exec $DOCKER run -i --rm \
|
||||
-v "$PWD":/home/c3c/source \
|
||||
-w /home/c3c/source $IMAGE bash -c \
|
||||
"cmake -S . -B build \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
|
||||
-DCMAKE_C_COMPILER=clang-$LLVM_VERSION \
|
||||
-DCMAKE_CXX_COMPILER=clang++-$LLVM_VERSION \
|
||||
-DCMAKE_LINKER=lld-$LLVM_VERSION \
|
||||
-DCMAKE_OBJCOPY=llvm-objcopy-$LLVM_VERSION \
|
||||
-DCMAKE_STRIP=llvm-strip-$LLVM_VERSION \
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-$LLVM_VERSION \
|
||||
-DC3_LLVM_VERSION=auto && \
|
||||
cmake --build build && \
|
||||
cp -r build/c3c build/lib bin"
|
||||
552
c3c.1
Normal file
552
c3c.1
Normal file
@@ -0,0 +1,552 @@
|
||||
.TH "c3c" "1" "2024-10-27" "C3 Compiler" "User Commands"
|
||||
.SH NAME
|
||||
c3c \- Compiler for the C3 programming language
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B c3c
|
||||
[\fIoptions\fR ...] \fIcommand\fR [\fIargs\fR ...]
|
||||
|
||||
.SH DESCRIPTION
|
||||
.B c3c
|
||||
is the compiler for the C3 language, providing commands for compilation, project
|
||||
management, testing, and distribution. The available commands allow users to
|
||||
compile files, initialize projects, build targets, run benchmarks, clean build
|
||||
files, and more.
|
||||
|
||||
.SH COMMANDS
|
||||
.PP
|
||||
.B c3c compile
|
||||
\fIfile1\fR [\fIfile2\fR ...]
|
||||
.RS
|
||||
Compile files without a project into an executable.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c init
|
||||
\fIproject name\fR
|
||||
.RS
|
||||
Initialize a new project structure.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c init-lib
|
||||
\fIlibrary name\fR
|
||||
.RS
|
||||
Initialize a new library structure.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c build
|
||||
[\fItarget\fR]
|
||||
.RS
|
||||
Build the target in the current project.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c benchmark
|
||||
.RS
|
||||
Run the benchmarks in the current project.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c test
|
||||
.RS
|
||||
Run the unit tests in the current project.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c clean
|
||||
.RS
|
||||
Clean all build files.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c run
|
||||
[\fItarget\fR] [-- [\fIarg1\fR ...]]
|
||||
.RS
|
||||
Run (and build if needed) the target in the current project.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c dist
|
||||
[\fItarget\fR]
|
||||
.RS
|
||||
Clean and build a target for distribution.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c directives
|
||||
[\fItarget\fR]
|
||||
.RS
|
||||
Generate documentation for the target.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c bench
|
||||
[\fItarget\fR]
|
||||
.RS
|
||||
Benchmark a target.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c clean-run
|
||||
[\fItarget\fR] [-- [\fIarg1\fR ...]]
|
||||
.RS
|
||||
Clean, then run the target.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c compile-run
|
||||
\fIfile1\fR [\fIfile2\fR ...] [-- [\fIarg1\fR ...]]
|
||||
.RS
|
||||
Compile files then immediately run the result.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c compile-only
|
||||
\fIfile1\fR [\fIfile2\fR ...]
|
||||
.RS
|
||||
Compile files but do not perform linking.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c compile-benchmark
|
||||
\fIfile1\fR [\fIfile2\fR ...]
|
||||
.RS
|
||||
Compile files into an executable and run benchmarks.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c compile-test
|
||||
\fIfile1\fR [\fIfile2\fR ...]
|
||||
.RS
|
||||
Compile files into an executable and run unit tests.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c static-lib
|
||||
\fIfile1\fR [\fIfile2\fR ...]
|
||||
.RS
|
||||
Compile files without a project into a static library.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c dynamic-lib
|
||||
\fIfile1\fR [\fIfile2\fR ...]
|
||||
.RS
|
||||
Compile files without a project into a dynamic library.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c headers
|
||||
\fIfile1\fR [\fIfile2\fR ...]
|
||||
.RS
|
||||
Analyze files and generate C headers for public methods.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c vendor-fetch
|
||||
\fIlibrary\fR ...
|
||||
.RS
|
||||
Fetch one or more libraries from the vendor collection.
|
||||
.RE
|
||||
.PP
|
||||
.B c3c project
|
||||
\fIsubcommand\fR ...
|
||||
.RS
|
||||
Manipulate or view project files.
|
||||
.RE
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
.B --stdlib
|
||||
\fIdir\fR
|
||||
.RS
|
||||
Use this directory as the C3 standard library path.
|
||||
.RE
|
||||
.PP
|
||||
.B --no-entry
|
||||
.RS
|
||||
Do not generate (or require) a main function.
|
||||
.RE
|
||||
.PP
|
||||
.B --libdir
|
||||
\fIdir\fR
|
||||
.RS
|
||||
Add this directory to the C3 library search paths.
|
||||
.RE
|
||||
.PP
|
||||
.B --lib
|
||||
\fIname\fR
|
||||
.RS
|
||||
Add this library to the compilation.
|
||||
.RE
|
||||
.PP
|
||||
.B --path
|
||||
\fIdir\fR
|
||||
.RS
|
||||
Use this as the base directory for the current command.
|
||||
.RE
|
||||
.PP
|
||||
.B --template
|
||||
\fItemplate\fR
|
||||
.RS
|
||||
Select template for 'init': "exe", "static-lib", "dynamic-lib" or a path.
|
||||
.RE
|
||||
.PP
|
||||
.B --about
|
||||
Prints a short description of C3.
|
||||
.PP
|
||||
.B --symtab
|
||||
\fIvalue\fR
|
||||
.RS
|
||||
Sets the preferred symtab size.
|
||||
.RE
|
||||
.PP
|
||||
.B --max-mem
|
||||
\fIvalue\fR
|
||||
.RS
|
||||
Sets the preferred max memory size.
|
||||
.RE
|
||||
.PP
|
||||
.B --run-once
|
||||
.RS
|
||||
After running the output file, delete it immediately.
|
||||
.RE
|
||||
.PP
|
||||
.B -V, --version
|
||||
Print version information.
|
||||
.PP
|
||||
.B -E
|
||||
Lex only.
|
||||
.PP
|
||||
.B -P
|
||||
Only parse and output the AST as JSON.
|
||||
.PP
|
||||
.B -C
|
||||
Only lex, parse and check.
|
||||
.PP
|
||||
.B -
|
||||
\fIcode\fR...
|
||||
.RS
|
||||
Read code from standard in.
|
||||
.RE
|
||||
.PP
|
||||
.B -o
|
||||
\fIfile\fR
|
||||
.RS
|
||||
Write output to \fIfile\fR.
|
||||
.RE
|
||||
.PP
|
||||
.B -O0
|
||||
Safe, no optimizations, emit debug info.
|
||||
.PP
|
||||
.B -O1
|
||||
Safe, high optimization, emit debug info.
|
||||
.PP
|
||||
.B -O2
|
||||
Unsafe, high optimization, emit debug info.
|
||||
.PP
|
||||
.B -O3
|
||||
Unsafe, high optimization, single module, emit debug info.
|
||||
.PP
|
||||
.B -O4
|
||||
Unsafe, highest optimization, relaxed maths, single module, emit debug info, no panic messages.
|
||||
.PP
|
||||
.B -O5
|
||||
Unsafe, highest optimization, fast maths, single module, emit debug info, no panic messages, no backtrace.
|
||||
.PP
|
||||
.B -Os
|
||||
Unsafe, high optimization, small code, single module, no debug info, no panic messages.
|
||||
.PP
|
||||
.B -Oz
|
||||
Unsafe, high optimization, tiny code, single module, no debug info, no panic messages, no backtrace.
|
||||
.PP
|
||||
.B -D
|
||||
\fIname\fR
|
||||
.RS
|
||||
Add feature flag \fIname\fR.
|
||||
.RE
|
||||
.PP
|
||||
.B -U
|
||||
\fIname\fR
|
||||
.RS
|
||||
Remove feature flag \fIname\fR.
|
||||
.RE
|
||||
.PP
|
||||
.B --trust=
|
||||
\fIoption\fR
|
||||
.RS
|
||||
Trust level: none (default), include ($include allowed), full ($exec / exec allowed).
|
||||
.RE
|
||||
.PP
|
||||
.B --output-dir
|
||||
\fIdir\fR
|
||||
.RS
|
||||
Override general output directory.
|
||||
.RE
|
||||
.PP
|
||||
.B --threads
|
||||
\fInumber\fR
|
||||
.RS
|
||||
Set the number of threads to use for compilation.
|
||||
.RE
|
||||
.PP
|
||||
.B --show-backtrace=
|
||||
\fIyes|no\fR
|
||||
.RS
|
||||
Show detailed backtrace on segfaults.
|
||||
.RE
|
||||
|
||||
.PP
|
||||
.B -g
|
||||
Emit debug info.
|
||||
.PP
|
||||
.B -g0
|
||||
Emit no debug info.
|
||||
|
||||
|
||||
.PP
|
||||
.B -l
|
||||
\fIlibrary\fR
|
||||
.RS
|
||||
Link with the library provided.
|
||||
.RE
|
||||
.PP
|
||||
.B -L
|
||||
\fIlibrary\fR \fIdir\fR
|
||||
.RS
|
||||
Append the directory to the linker search paths.
|
||||
.RE
|
||||
.PP
|
||||
.B -z
|
||||
\fIargument\fR
|
||||
.RS
|
||||
Send the \fIargument\fR as a parameter to the linker.
|
||||
.RE
|
||||
.PP
|
||||
.B --cc
|
||||
\fIpath\fR
|
||||
.RS
|
||||
Set C compiler (for C files in projects and use as system linker).
|
||||
.RE
|
||||
.PP
|
||||
.B --linker=
|
||||
\fIoption\fR [\fIpath\fR]
|
||||
.RS
|
||||
Specify the linker: builtin, cc, custom (default is 'cc'). 'Custom' requires a path.
|
||||
.RE
|
||||
|
||||
.PP
|
||||
.B --use-stdlib=
|
||||
\fIyes|no\fR
|
||||
.RS
|
||||
Include the standard library (default: yes).
|
||||
.RE
|
||||
.PP
|
||||
.B --link-libc=
|
||||
\fIyes|no\fR
|
||||
.RS
|
||||
Link libc and other default libraries (default: yes).
|
||||
.RE
|
||||
.PP
|
||||
.B --emit-stdlib=
|
||||
\fIyes|no\fR
|
||||
.RS
|
||||
Output files for the standard library (default: yes).
|
||||
.RE
|
||||
.PP
|
||||
.B --panicfn
|
||||
\fIname\fR
|
||||
.RS
|
||||
Override the panic function name.
|
||||
.RE
|
||||
.PP
|
||||
.B --testfn
|
||||
\fIname\fR
|
||||
.RS
|
||||
Override the test runner function name.
|
||||
.RE
|
||||
.PP
|
||||
.B --benchfn
|
||||
\fIname\fR
|
||||
.RS
|
||||
Override the benchmark runner function name.
|
||||
.RE
|
||||
|
||||
.PP
|
||||
.B --reloc=
|
||||
\fIoption\fR
|
||||
.RS
|
||||
Specify the relocation model: none, pic, PIC, pie, PIE.
|
||||
.RE
|
||||
.PP
|
||||
.B --x86cpu=
|
||||
\fIoption\fR
|
||||
.RS
|
||||
Set general level of x64 CPU: baseline, ssse3, sse4, avx1, avx2-v1, avx2-v2 (Skylake/Zen1+), avx512 (Icelake/Zen4+), native.
|
||||
.RE
|
||||
.PP
|
||||
.B --x86vec=
|
||||
\fIoption\fR
|
||||
.RS
|
||||
Set maximum type of vector use: none, mmx, sse, avx, avx512, default.
|
||||
.RE
|
||||
.PP
|
||||
.B --riscvfloat=
|
||||
\fIoption\fR
|
||||
.RS
|
||||
Set type of RISC-V float support: none, float, double.
|
||||
.RE
|
||||
.PP
|
||||
.B --memory-env=
|
||||
\fIoption\fR
|
||||
.RS
|
||||
Set the memory environment: normal, small, tiny, none.
|
||||
.RE
|
||||
.PP
|
||||
.B --strip-unused=
|
||||
\fIyes|no\fR
|
||||
.RS
|
||||
Strip unused code and globals from the output (default: yes).
|
||||
.RE
|
||||
.PP
|
||||
.B --fp-math=
|
||||
\fIoption\fR
|
||||
.RS
|
||||
Specify floating-point math behavior: strict, relaxed, fast.
|
||||
.RE
|
||||
.PP
|
||||
.B --win64-simd=
|
||||
\fIoption\fR
|
||||
.RS
|
||||
Windows SIMD ABI: array, full.
|
||||
.RE
|
||||
|
||||
.PP
|
||||
.B --debug-stats
|
||||
Print debug statistics.
|
||||
.PP
|
||||
.B --print-linking
|
||||
Print linker arguments.
|
||||
.PP
|
||||
.B --debug-log
|
||||
Print debug logging to stdout.
|
||||
|
||||
|
||||
.PP
|
||||
.B --benchmarking
|
||||
Run built-in benchmarks.
|
||||
.PP
|
||||
.B --testing
|
||||
Run built-in tests.
|
||||
|
||||
|
||||
.PP
|
||||
.B --list-attributes
|
||||
List all attributes.
|
||||
.PP
|
||||
.B --list-builtins
|
||||
List all builtins.
|
||||
.PP
|
||||
.B --list-keywords
|
||||
List all keywords.
|
||||
.PP
|
||||
.B --list-operators
|
||||
List all operators.
|
||||
.PP
|
||||
.B --list-precedence
|
||||
List operator precedence order.
|
||||
.PP
|
||||
.B --list-project-properties
|
||||
List all available keys used in project.json files.
|
||||
.PP
|
||||
.B --list-manifest-properties
|
||||
List all available keys used in manifest.json files.
|
||||
.PP
|
||||
.B --list-targets
|
||||
List all architectures the compiler supports.
|
||||
.PP
|
||||
.B --list-type-properties
|
||||
List all type properties.
|
||||
|
||||
|
||||
.PP
|
||||
.B --print-output
|
||||
Print the object files created to stdout.
|
||||
.PP
|
||||
.B --print-input
|
||||
Print inputted C3 files to stdout.
|
||||
|
||||
|
||||
.PP
|
||||
.B --winsdk
|
||||
\fIdir\fR
|
||||
.RS
|
||||
Set the directory for Windows system library files for cross-compilation.
|
||||
.RE
|
||||
.PP
|
||||
.B --wincrt=
|
||||
\fIoption\fR
|
||||
.RS
|
||||
Windows CRT linking: none, static-debug, static, dynamic-debug (default if debug info enabled), dynamic (default).
|
||||
.RE
|
||||
.PP
|
||||
.B --windef
|
||||
\fIfile\fR
|
||||
.RS
|
||||
Use Windows 'def' file for function exports instead of 'dllexport'.
|
||||
.RE
|
||||
|
||||
.PP
|
||||
.B --macossdk
|
||||
\fIdir\fR
|
||||
.RS
|
||||
Set the directory for the MacOS SDK for cross-compilation.
|
||||
.RE
|
||||
.PP
|
||||
.B --macos-min-version
|
||||
\fIver\fR
|
||||
.RS
|
||||
Set the minimum MacOS version to compile for.
|
||||
.RE
|
||||
.PP
|
||||
.B --macos-sdk-version
|
||||
\fIver\fR
|
||||
.RS
|
||||
Set the MacOS SDK version to compile for.
|
||||
.RE
|
||||
|
||||
.PP
|
||||
.B --linux-crt
|
||||
\fIdir\fR
|
||||
.RS
|
||||
Set the directory to use for finding crt1.o and related files.
|
||||
.RE
|
||||
.PP
|
||||
.B --linux-crtbegin
|
||||
\fIdir\fR
|
||||
.RS
|
||||
Set the directory to use for finding crtbegin.o and related files.
|
||||
.RE
|
||||
|
||||
.PP
|
||||
.B --vector-conv=
|
||||
\fIoption\fR
|
||||
.RS
|
||||
Set vector conversion behavior: default, old.
|
||||
.RE
|
||||
.PP
|
||||
.B --sanitize=
|
||||
\fIoption\fR
|
||||
.RS
|
||||
Enable a sanitizer: address, memory, thread.
|
||||
.RE
|
||||
|
||||
.SH EXAMPLES
|
||||
.PP
|
||||
Create a project:
|
||||
.RS
|
||||
.B c3c init new_project
|
||||
.RE
|
||||
.PP
|
||||
Create a library project:
|
||||
.RS
|
||||
.B c3c init-lib new_library
|
||||
.RE
|
||||
.PP
|
||||
Compile a file:
|
||||
.RS
|
||||
.B c3c compile main.c3
|
||||
.RE
|
||||
.PP
|
||||
Build the current project:
|
||||
.RS
|
||||
.B c3c build
|
||||
.RE
|
||||
.PP
|
||||
Run tests for the current project:
|
||||
.RS
|
||||
.B c3c test
|
||||
.RE
|
||||
@@ -1,16 +1,49 @@
|
||||
ARG UBUNTU_VERSION=22.04
|
||||
FROM ubuntu:${UBUNTU_VERSION}
|
||||
|
||||
ARG UBUNTU_VERSION
|
||||
FROM ubuntu:$UBUNTU_VERSION
|
||||
ARG LLVM_VERSION=18
|
||||
ENV LLVM_DEV_VERSION=20
|
||||
|
||||
ARG DEPS
|
||||
ARG CMAKE_VERSION=3.20
|
||||
|
||||
RUN export DEBIAN_FRONTEND=noninteractive && export TERM=xterm && apt-get update && apt-get install -y build-essential cmake zlib1g zlib1g-dev \
|
||||
$DEPS && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ && \
|
||||
wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-$CMAKE_VERSION-linux-x86_64.sh && \
|
||||
mkdir -p /opt/cmake && \
|
||||
sh cmake-${CMAKE_VERSION}-linux-x86_64.sh --prefix=/opt/cmake --skip-license && \
|
||||
rm cmake-${CMAKE_VERSION}-linux-x86_64.sh && \
|
||||
ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake
|
||||
|
||||
ARG GID=1000
|
||||
ARG UID=1000
|
||||
RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \
|
||||
if [ "${LLVM_VERSION}" -lt 18 ]; then \
|
||||
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
|
||||
apt-get update && \
|
||||
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
|
||||
libpolly-${LLVM_VERSION}-dev \
|
||||
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
|
||||
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev libmlir-${LLVM_VERSION} \
|
||||
libmlir-${LLVM_VERSION}-dev mlir-${LLVM_VERSION}-tools; \
|
||||
elif [ "${LLVM_VERSION}" -lt "${LLVM_DEV_VERSION}" ]; then \
|
||||
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
|
||||
apt-get update && \
|
||||
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
|
||||
libpolly-${LLVM_VERSION}-dev \
|
||||
clang-${LLVM_VERSION} clang++-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
|
||||
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
|
||||
else \
|
||||
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main" && \
|
||||
apt-get update && \
|
||||
apt-get install -y -t llvm-toolchain-focal \
|
||||
libpolly-${LLVM_VERSION}-dev \
|
||||
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
|
||||
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
|
||||
fi && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN groupadd -o --gid=$GID c3c && useradd --gid=$GID --uid=$GID --create-home --shell /bin/bash c3c
|
||||
RUN groupadd -g 1337 c3c && \
|
||||
useradd -m -u 1337 -g c3c c3c
|
||||
|
||||
# Add cmake to PATH for user c3c
|
||||
USER c3c
|
||||
ENV PATH="/opt/cmake/bin:${PATH}"
|
||||
|
||||
WORKDIR /home/c3c
|
||||
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1730958623,
|
||||
"narHash": "sha256-JwQZIGSYnRNOgDDoIgqKITrPVil+RMWHsZH1eE1VGN0=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "85f7e662eda4fa3a995556527c87b2524b691933",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
39
flake.nix
Normal file
39
flake.nix
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
description = "C3 compiler flake";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem
|
||||
(system:
|
||||
let pkgs = import inputs.nixpkgs { inherit system; }; in
|
||||
{
|
||||
packages = {
|
||||
default = self.packages.${system}.c3c;
|
||||
|
||||
c3c = pkgs.callPackage ./nix/default.nix {};
|
||||
|
||||
c3c-checks = pkgs.callPackage ./nix/default.nix {
|
||||
checks = true;
|
||||
};
|
||||
|
||||
c3c-debug = pkgs.callPackage ./nix/default.nix {
|
||||
debug = true;
|
||||
};
|
||||
|
||||
c3c-debug-checks = pkgs.callPackage ./nix/default.nix {
|
||||
debug = true;
|
||||
checks = true;
|
||||
};
|
||||
};
|
||||
|
||||
devShells = {
|
||||
default = pkgs.callPackage ./nix/shell.nix {
|
||||
c3c = self.packages.${system}.c3c-debug;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
14
git_hash.cmake
Normal file
14
git_hash.cmake
Normal file
@@ -0,0 +1,14 @@
|
||||
find_package(Git QUIET)
|
||||
|
||||
set(GIT_HASH "unknown")
|
||||
|
||||
if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git")
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
|
||||
OUTPUT_VARIABLE GIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
COMMAND_ERROR_IS_FATAL ANY)
|
||||
endif()
|
||||
|
||||
message("Git Hash: ${GIT_HASH}")
|
||||
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/git_hash.h "#pragma once\n#define GIT_HASH \"${GIT_HASH}\"\n")
|
||||
@@ -1,17 +0,0 @@
|
||||
@echo off
|
||||
|
||||
set DOWNLOAD_URL=https://aka.ms/vs/17/release
|
||||
|
||||
mkdir tmp 2> NUL
|
||||
|
||||
if not exist "tmp\vs_buildtools.exe" (
|
||||
bitsadmin /transfer /download /priority foreground %DOWNLOAD_URL%/vs_buildtools.exe %CD%\tmp\vs_buildtools.exe
|
||||
)
|
||||
|
||||
echo Preparing Build Tools, please wait...
|
||||
tmp\vs_BuildTools.exe --quiet --wait --layout tmp\ --add Microsoft.VisualStudio.Component.Windows10SDK.19041
|
||||
|
||||
echo Installing Build Tools, please wait...
|
||||
tmp\vs_BuildTools.exe --quiet --wait --noweb --add Microsoft.VisualStudio.Component.Windows10SDK.19041
|
||||
|
||||
REM rmdir tmp /s /q
|
||||
@@ -53,6 +53,10 @@ fn bool char.is_blank(char c) => is_blank_m(c);
|
||||
fn bool char.is_cntrl(char c) => is_cntrl_m(c);
|
||||
fn char char.to_lower(char c) => (char)to_lower_m(c);
|
||||
fn char char.to_upper(char c) => (char)to_upper_m(c);
|
||||
<*
|
||||
@require c.is_xdigit()
|
||||
*>
|
||||
fn char char.from_hex(char c) => c.is_digit() ? c - '0' : 10 + (c | 0x20) - 'a';
|
||||
|
||||
fn bool uint.in_range(uint c, uint start, uint len) => in_range_m(c, start, len);
|
||||
fn bool uint.is_lower(uint c) => is_lower_m(c);
|
||||
|
||||
@@ -1,5 +1,520 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::atomic;
|
||||
module std::atomic::types(<Type>);
|
||||
|
||||
struct Atomic
|
||||
{
|
||||
Type data;
|
||||
}
|
||||
|
||||
<*
|
||||
Loads data atomically, by default this uses SEQ_CONSISTENT ordering.
|
||||
|
||||
@param ordering "The ordering, cannot be release or acquire-release."
|
||||
@require ordering != RELEASE && ordering != ACQUIRE_RELEASE : "Release and acquire-release are not valid for load"
|
||||
*>
|
||||
macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
switch(ordering)
|
||||
{
|
||||
case NOT_ATOMIC: return $$atomic_load(data, false, AtomicOrdering.NOT_ATOMIC.ordinal);
|
||||
case UNORDERED: return $$atomic_load(data, false, AtomicOrdering.UNORDERED.ordinal);
|
||||
case RELAXED: return $$atomic_load(data, false, AtomicOrdering.RELAXED.ordinal);
|
||||
case ACQUIRE: return $$atomic_load(data, false, AtomicOrdering.ACQUIRE.ordinal);
|
||||
case SEQ_CONSISTENT: return $$atomic_load(data, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
|
||||
case ACQUIRE_RELEASE:
|
||||
case RELEASE: unreachable("Invalid ordering.");
|
||||
}
|
||||
}
|
||||
<*
|
||||
Stores data atomically, by default this uses SEQ_CONSISTENT ordering.
|
||||
|
||||
@param ordering "The ordering, cannot be acquire or acquire-release."
|
||||
@require ordering != ACQUIRE && ordering != ACQUIRE_RELEASE : "Acquire and acquire-release are not valid for store"
|
||||
*>
|
||||
macro void Atomic.store(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
switch(ordering)
|
||||
{
|
||||
case NOT_ATOMIC: $$atomic_store(data, value, false, AtomicOrdering.NOT_ATOMIC.ordinal);
|
||||
case UNORDERED: $$atomic_store(data, value, false, AtomicOrdering.UNORDERED.ordinal);
|
||||
case RELAXED: $$atomic_store(data, value, false, AtomicOrdering.RELAXED.ordinal);
|
||||
case RELEASE: $$atomic_store(data, value, false, AtomicOrdering.RELEASE.ordinal);
|
||||
case SEQ_CONSISTENT: $$atomic_store(data, value, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
|
||||
case ACQUIRE_RELEASE:
|
||||
case ACQUIRE: unreachable("Invalid ordering.");
|
||||
}
|
||||
}
|
||||
|
||||
macro Type Atomic.add(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_add, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.sub(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_sub, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.mul(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_mul, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_div, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_div, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_min, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.or(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_or, data, value, ordering);
|
||||
}
|
||||
|
||||
fn Type Atomic.xor(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.and(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_and, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.shift_right(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.shift_left(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
|
||||
}
|
||||
|
||||
macro @atomic_exec(#func, data, value, ordering) @local
|
||||
{
|
||||
switch(ordering)
|
||||
{
|
||||
case RELAXED: return #func(data, value, RELAXED);
|
||||
case ACQUIRE: return #func(data, value, ACQUIRE);
|
||||
case RELEASE: return #func(data, value, RELEASE);
|
||||
case ACQUIRE_RELEASE: return #func(data, value, ACQUIRE_RELEASE);
|
||||
case SEQ_CONSISTENT: return #func(data, value, SEQ_CONSISTENT);
|
||||
default: unreachable("Ordering may not be non-atomic or unordered.");
|
||||
}
|
||||
}
|
||||
|
||||
module std::atomic;
|
||||
import std::math;
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
return $$atomic_fetch_add(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
return $$atomic_fetch_sub(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = old_value * y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = old_value / y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value | storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value ^ storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value & storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value >> storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value << storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value = true;
|
||||
|
||||
do {
|
||||
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
|
||||
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value = false;
|
||||
|
||||
do {
|
||||
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
|
||||
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
return $$atomic_fetch_max(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
return $$atomic_fetch_min(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
63
lib/std/atomic_nolibc.c3
Normal file
63
lib/std/atomic_nolibc.c3
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::atomic;
|
||||
|
||||
macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment) {
|
||||
switch(failure)
|
||||
{
|
||||
case AtomicOrdering.RELAXED.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.RELAXED.ordinal, $alignment);
|
||||
case AtomicOrdering.ACQUIRE.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.ACQUIRE.ordinal, $alignment);
|
||||
case AtomicOrdering.SEQ_CONSISTENT.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.SEQ_CONSISTENT.ordinal, $alignment);
|
||||
default: unreachable("Unrecognized failure ordering");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, success, failure, $alignment)
|
||||
{
|
||||
switch(success)
|
||||
{
|
||||
case AtomicOrdering.RELAXED.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELAXED.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.ACQUIRE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELEASE.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.ACQUIRE_RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE_RELEASE.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.SEQ_CONSISTENT.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.SEQ_CONSISTENT.ordinal, failure, $alignment);
|
||||
default: unreachable("Unrecognized success ordering");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, CInt success, CInt failure) @extern("__atomic_compare_exchange") @export
|
||||
{
|
||||
switch (size)
|
||||
{
|
||||
case 1:
|
||||
char* pt = (char*)ptr;
|
||||
char ex = *(char*)expected;
|
||||
char de = *(char*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 1)) return 1;
|
||||
case 2:
|
||||
short* pt = (short*)ptr;
|
||||
short ex = *(short*)expected;
|
||||
short de = *(short*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 2)) return 1;
|
||||
case 4:
|
||||
int* pt = (int*)ptr;
|
||||
int ex = *(int*)expected;
|
||||
int de = *(int*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 4)) return 1;
|
||||
case 8:
|
||||
$if iptr.sizeof >= 8:
|
||||
long* pt = (long*)ptr;
|
||||
long ex = *(long*)expected;
|
||||
long de = *(long*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 8)) return 1;
|
||||
$else
|
||||
nextcase;
|
||||
$endif
|
||||
default:
|
||||
unreachable("Unsuported size (%d) for atomic_compare_exchange", size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
294
lib/std/bits.c3
294
lib/std/bits.c3
@@ -1,173 +1,171 @@
|
||||
module std::bits;
|
||||
|
||||
|
||||
/**
|
||||
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
|
||||
**/
|
||||
<*
|
||||
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
|
||||
*>
|
||||
macro reverse(i) => $$bitreverse(i);
|
||||
|
||||
/**
|
||||
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
|
||||
**/
|
||||
<*
|
||||
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
|
||||
*>
|
||||
macro bswap(i) @builtin => $$bswap(i);
|
||||
|
||||
macro uint[<*>].popcount(self) => $$popcount(self);
|
||||
macro uint[<*>].ctz(self) => $$ctz(self);
|
||||
macro uint[<*>].clz(self) => $$clz(self);
|
||||
macro uint[<*>] uint[<*>].fshl(hi, uint[<*>] lo, uint[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro uint[<*>] uint[<*>].fshr(hi, uint[<*>] lo, uint[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro uint[<*>] uint[<*>].rotl(self, uint[<*>] shift) => $$fshl(self, self, shift);
|
||||
macro uint[<*>] uint[<*>].rotr(self, uint[<*>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro uint[<*>].popcount(uint[<*>] i) => $$popcount(i);
|
||||
macro uint[<*>].ctz(uint[<*>] i) => $$ctz(i);
|
||||
macro uint[<*>].clz(uint[<*>] i) => $$clz(i);
|
||||
macro uint[<*>] uint[<*>].fshl(uint[<*>] hi, uint[<*>] lo, uint[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro uint[<*>] uint[<*>].fshr(uint[<*>] hi, uint[<*>] lo, uint[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro uint[<*>] uint[<*>].rotl(uint[<*>] i, uint[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro uint[<*>] uint[<*>].rotr(uint[<*>] i, uint[<*>] shift) => $$fshr(i, i, shift);
|
||||
macro int[<*>].popcount(self) => $$popcount(self);
|
||||
macro int[<*>].ctz(self) => $$ctz(self);
|
||||
macro int[<*>].clz(self) => $$clz(self);
|
||||
macro int[<*>] int[<*>].fshl(hi, int[<*>] lo, int[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro int[<*>] int[<*>].fshr(hi, int[<*>] lo, int[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro int[<*>] int[<*>].rotl(self, int[<*>] shift) => $$fshl(self, self, shift);
|
||||
macro int[<*>] int[<*>].rotr(self, int[<*>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro int[<*>].popcount(int[<*>] i) => $$popcount(i);
|
||||
macro int[<*>].ctz(int[<*>] i) => $$ctz(i);
|
||||
macro int[<*>].clz(int[<*>] i) => $$clz(i);
|
||||
macro int[<*>] int[<*>].fshl(int[<*>] hi, int[<*>] lo, int[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro int[<*>] int[<*>].fshr(int[<*>] hi, int[<*>] lo, int[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro int[<*>] int[<*>].rotl(int[<*>] i, int[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro int[<*>] int[<*>].rotr(int[<*>] i, int[<*>] shift) => $$fshr(i, i, shift);
|
||||
macro ushort[<*>].popcount(self) => $$popcount(self);
|
||||
macro ushort[<*>].ctz(self) => $$ctz(self);
|
||||
macro ushort[<*>].clz(self) => $$clz(self);
|
||||
macro ushort[<*>] ushort[<*>].fshl(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ushort[<*>] ushort[<*>].fshr(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ushort[<*>] ushort[<*>].rotl(self, ushort[<*>] shift) => $$fshl(self, self, shift);
|
||||
macro ushort[<*>] ushort[<*>].rotr(self, ushort[<*>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ushort[<*>].popcount(ushort[<*>] i) => $$popcount(i);
|
||||
macro ushort[<*>].ctz(ushort[<*>] i) => $$ctz(i);
|
||||
macro ushort[<*>].clz(ushort[<*>] i) => $$clz(i);
|
||||
macro ushort[<*>] ushort[<*>].fshl(ushort[<*>] hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ushort[<*>] ushort[<*>].fshr(ushort[<*>] hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ushort[<*>] ushort[<*>].rotl(ushort[<*>] i, ushort[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro ushort[<*>] ushort[<*>].rotr(ushort[<*>] i, ushort[<*>] shift) => $$fshr(i, i, shift);
|
||||
macro short[<*>].popcount(self) => $$popcount(self);
|
||||
macro short[<*>].ctz(self) => $$ctz(self);
|
||||
macro short[<*>].clz(self) => $$clz(self);
|
||||
macro short[<*>] short[<*>].fshl(hi, short[<*>] lo, short[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro short[<*>] short[<*>].fshr(hi, short[<*>] lo, short[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro short[<*>] short[<*>].rotl(self, short[<*>] shift) => $$fshl(self, self, shift);
|
||||
macro short[<*>] short[<*>].rotr(self, short[<*>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro short[<*>].popcount(short[<*>] i) => $$popcount(i);
|
||||
macro short[<*>].ctz(short[<*>] i) => $$ctz(i);
|
||||
macro short[<*>].clz(short[<*>] i) => $$clz(i);
|
||||
macro short[<*>] short[<*>].fshl(short[<*>] hi, short[<*>] lo, short[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro short[<*>] short[<*>].fshr(short[<*>] hi, short[<*>] lo, short[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro short[<*>] short[<*>].rotl(short[<*>] i, short[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro short[<*>] short[<*>].rotr(short[<*>] i, short[<*>] shift) => $$fshr(i, i, shift);
|
||||
macro char[<*>].popcount(self) => $$popcount(self);
|
||||
macro char[<*>].ctz(self) => $$ctz(self);
|
||||
macro char[<*>].clz(self) => $$clz(self);
|
||||
macro char[<*>] char[<*>].fshl(hi, char[<*>] lo, char[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro char[<*>] char[<*>].fshr(hi, char[<*>] lo, char[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro char[<*>] char[<*>].rotl(self, char[<*>] shift) => $$fshl(self, self, shift);
|
||||
macro char[<*>] char[<*>].rotr(self, char[<*>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro char[<*>].popcount(char[<*>] i) => $$popcount(i);
|
||||
macro char[<*>].ctz(char[<*>] i) => $$ctz(i);
|
||||
macro char[<*>].clz(char[<*>] i) => $$clz(i);
|
||||
macro char[<*>] char[<*>].fshl(char[<*>] hi, char[<*>] lo, char[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro char[<*>] char[<*>].fshr(char[<*>] hi, char[<*>] lo, char[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro char[<*>] char[<*>].rotl(char[<*>] i, char[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro char[<*>] char[<*>].rotr(char[<*>] i, char[<*>] shift) => $$fshr(i, i, shift);
|
||||
macro ichar[<*>].popcount(self) => $$popcount(self);
|
||||
macro ichar[<*>].ctz(self) => $$ctz(self);
|
||||
macro ichar[<*>].clz(self) => $$clz(self);
|
||||
macro ichar[<*>] ichar[<*>].fshl(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ichar[<*>] ichar[<*>].fshr(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ichar[<*>] ichar[<*>].rotl(self, ichar[<*>] shift) => $$fshl(self, self, shift);
|
||||
macro ichar[<*>] ichar[<*>].rotr(self, ichar[<*>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ichar[<*>].popcount(ichar[<*>] i) => $$popcount(i);
|
||||
macro ichar[<*>].ctz(ichar[<*>] i) => $$ctz(i);
|
||||
macro ichar[<*>].clz(ichar[<*>] i) => $$clz(i);
|
||||
macro ichar[<*>] ichar[<*>].fshl(ichar[<*>] hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ichar[<*>] ichar[<*>].fshr(ichar[<*>] hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ichar[<*>] ichar[<*>].rotl(ichar[<*>] i, ichar[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro ichar[<*>] ichar[<*>].rotr(ichar[<*>] i, ichar[<*>] shift) => $$fshr(i, i, shift);
|
||||
macro ulong[<*>].popcount(self) => $$popcount(self);
|
||||
macro ulong[<*>].ctz(self) => $$ctz(self);
|
||||
macro ulong[<*>].clz(self) => $$clz(self);
|
||||
macro ulong[<*>] ulong[<*>].fshl(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ulong[<*>] ulong[<*>].fshr(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ulong[<*>] ulong[<*>].rotl(self, ulong[<*>] shift) => $$fshl(self, self, shift);
|
||||
macro ulong[<*>] ulong[<*>].rotr(self, ulong[<*>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ulong[<*>].popcount(ulong[<*>] i) => $$popcount(i);
|
||||
macro ulong[<*>].ctz(ulong[<*>] i) => $$ctz(i);
|
||||
macro ulong[<*>].clz(ulong[<*>] i) => $$clz(i);
|
||||
macro ulong[<*>] ulong[<*>].fshl(ulong[<*>] hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ulong[<*>] ulong[<*>].fshr(ulong[<*>] hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ulong[<*>] ulong[<*>].rotl(ulong[<*>] i, ulong[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro ulong[<*>] ulong[<*>].rotr(ulong[<*>] i, ulong[<*>] shift) => $$fshr(i, i, shift);
|
||||
macro long[<*>].popcount(self) => $$popcount(self);
|
||||
macro long[<*>].ctz(self) => $$ctz(self);
|
||||
macro long[<*>].clz(self) => $$clz(self);
|
||||
macro long[<*>] long[<*>].fshl(hi, long[<*>] lo, long[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro long[<*>] long[<*>].fshr(hi, long[<*>] lo, long[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro long[<*>] long[<*>].rotl(self, long[<*>] shift) => $$fshl(self, self, shift);
|
||||
macro long[<*>] long[<*>].rotr(self, long[<*>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro long[<*>].popcount(long[<*>] i) => $$popcount(i);
|
||||
macro long[<*>].ctz(long[<*>] i) => $$ctz(i);
|
||||
macro long[<*>].clz(long[<*>] i) => $$clz(i);
|
||||
macro long[<*>] long[<*>].fshl(long[<*>] hi, long[<*>] lo, long[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro long[<*>] long[<*>].fshr(long[<*>] hi, long[<*>] lo, long[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro long[<*>] long[<*>].rotl(long[<*>] i, long[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro long[<*>] long[<*>].rotr(long[<*>] i, long[<*>] shift) => $$fshr(i, i, shift);
|
||||
macro uint128[<*>].popcount(self) => $$popcount(self);
|
||||
macro uint128[<*>].ctz(self) => $$ctz(self);
|
||||
macro uint128[<*>].clz(self) => $$clz(self);
|
||||
macro uint128[<*>] uint128[<*>].fshl(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro uint128[<*>] uint128[<*>].fshr(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro uint128[<*>] uint128[<*>].rotl(self, uint128[<*>] shift) => $$fshl(self, self, shift);
|
||||
macro uint128[<*>] uint128[<*>].rotr(self, uint128[<*>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro uint128[<*>].popcount(uint128[<*>] i) => $$popcount(i);
|
||||
macro uint128[<*>].ctz(uint128[<*>] i) => $$ctz(i);
|
||||
macro uint128[<*>].clz(uint128[<*>] i) => $$clz(i);
|
||||
macro uint128[<*>] uint128[<*>].fshl(uint128[<*>] hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro uint128[<*>] uint128[<*>].fshr(uint128[<*>] hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro uint128[<*>] uint128[<*>].rotl(uint128[<*>] i, uint128[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro uint128[<*>] uint128[<*>].rotr(uint128[<*>] i, uint128[<*>] shift) => $$fshr(i, i, shift);
|
||||
macro int128[<*>].popcount(self) => $$popcount(self);
|
||||
macro int128[<*>].ctz(self) => $$ctz(self);
|
||||
macro int128[<*>].clz(self) => $$clz(self);
|
||||
macro int128[<*>] int128[<*>].fshl(hi, int128[<*>] lo, int128[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro int128[<*>] int128[<*>].fshr(hi, int128[<*>] lo, int128[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro int128[<*>] int128[<*>].rotl(self, int128[<*>] shift) => $$fshl(self, self, shift);
|
||||
macro int128[<*>] int128[<*>].rotr(self, int128[<*>] shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro int128[<*>].popcount(int128[<*>] i) => $$popcount(i);
|
||||
macro int128[<*>].ctz(int128[<*>] i) => $$ctz(i);
|
||||
macro int128[<*>].clz(int128[<*>] i) => $$clz(i);
|
||||
macro int128[<*>] int128[<*>].fshl(int128[<*>] hi, int128[<*>] lo, int128[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro int128[<*>] int128[<*>].fshr(int128[<*>] hi, int128[<*>] lo, int128[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro int128[<*>] int128[<*>].rotl(int128[<*>] i, int128[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro int128[<*>] int128[<*>].rotr(int128[<*>] i, int128[<*>] shift) => $$fshr(i, i, shift);
|
||||
macro uint.popcount(self) => $$popcount(self);
|
||||
macro uint.ctz(self) => $$ctz(self);
|
||||
macro uint.clz(self) => $$clz(self);
|
||||
macro uint uint.fshl(hi, uint lo, uint shift) => $$fshl(hi, lo, shift);
|
||||
macro uint uint.fshr(hi, uint lo, uint shift) => $$fshr(hi, lo, shift);
|
||||
macro uint uint.rotl(self, uint shift) => $$fshl(self, self, shift);
|
||||
macro uint uint.rotr(self, uint shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro uint.popcount(uint i) => $$popcount(i);
|
||||
macro uint.ctz(uint i) => $$ctz(i);
|
||||
macro uint.clz(uint i) => $$clz(i);
|
||||
macro uint uint.fshl(uint hi, uint lo, uint shift) => $$fshl(hi, lo, shift);
|
||||
macro uint uint.fshr(uint hi, uint lo, uint shift) => $$fshr(hi, lo, shift);
|
||||
macro uint uint.rotl(uint i, uint shift) => $$fshl(i, i, shift);
|
||||
macro uint uint.rotr(uint i, uint shift) => $$fshr(i, i, shift);
|
||||
macro int.popcount(self) => $$popcount(self);
|
||||
macro int.ctz(self) => $$ctz(self);
|
||||
macro int.clz(self) => $$clz(self);
|
||||
macro int int.fshl(hi, int lo, int shift) => $$fshl(hi, lo, shift);
|
||||
macro int int.fshr(hi, int lo, int shift) => $$fshr(hi, lo, shift);
|
||||
macro int int.rotl(self, int shift) => $$fshl(self, self, shift);
|
||||
macro int int.rotr(self, int shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro int.popcount(int i) => $$popcount(i);
|
||||
macro int.ctz(int i) => $$ctz(i);
|
||||
macro int.clz(int i) => $$clz(i);
|
||||
macro int int.fshl(int hi, int lo, int shift) => $$fshl(hi, lo, shift);
|
||||
macro int int.fshr(int hi, int lo, int shift) => $$fshr(hi, lo, shift);
|
||||
macro int int.rotl(int i, int shift) => $$fshl(i, i, shift);
|
||||
macro int int.rotr(int i, int shift) => $$fshr(i, i, shift);
|
||||
macro ushort.popcount(self) => $$popcount(self);
|
||||
macro ushort.ctz(self) => $$ctz(self);
|
||||
macro ushort.clz(self) => $$clz(self);
|
||||
macro ushort ushort.fshl(hi, ushort lo, ushort shift) => $$fshl(hi, lo, shift);
|
||||
macro ushort ushort.fshr(hi, ushort lo, ushort shift) => $$fshr(hi, lo, shift);
|
||||
macro ushort ushort.rotl(self, ushort shift) => $$fshl(self, self, shift);
|
||||
macro ushort ushort.rotr(self, ushort shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ushort.popcount(ushort i) => $$popcount(i);
|
||||
macro ushort.ctz(ushort i) => $$ctz(i);
|
||||
macro ushort.clz(ushort i) => $$clz(i);
|
||||
macro ushort ushort.fshl(ushort hi, ushort lo, ushort shift) => $$fshl(hi, lo, shift);
|
||||
macro ushort ushort.fshr(ushort hi, ushort lo, ushort shift) => $$fshr(hi, lo, shift);
|
||||
macro ushort ushort.rotl(ushort i, ushort shift) => $$fshl(i, i, shift);
|
||||
macro ushort ushort.rotr(ushort i, ushort shift) => $$fshr(i, i, shift);
|
||||
macro short.popcount(self) => $$popcount(self);
|
||||
macro short.ctz(self) => $$ctz(self);
|
||||
macro short.clz(self) => $$clz(self);
|
||||
macro short short.fshl(hi, short lo, short shift) => $$fshl(hi, lo, shift);
|
||||
macro short short.fshr(hi, short lo, short shift) => $$fshr(hi, lo, shift);
|
||||
macro short short.rotl(self, short shift) => $$fshl(self, self, shift);
|
||||
macro short short.rotr(self, short shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro short.popcount(short i) => $$popcount(i);
|
||||
macro short.ctz(short i) => $$ctz(i);
|
||||
macro short.clz(short i) => $$clz(i);
|
||||
macro short short.fshl(short hi, short lo, short shift) => $$fshl(hi, lo, shift);
|
||||
macro short short.fshr(short hi, short lo, short shift) => $$fshr(hi, lo, shift);
|
||||
macro short short.rotl(short i, short shift) => $$fshl(i, i, shift);
|
||||
macro short short.rotr(short i, short shift) => $$fshr(i, i, shift);
|
||||
macro char.popcount(self) => $$popcount(self);
|
||||
macro char.ctz(self) => $$ctz(self);
|
||||
macro char.clz(self) => $$clz(self);
|
||||
macro char char.fshl(hi, char lo, char shift) => $$fshl(hi, lo, shift);
|
||||
macro char char.fshr(hi, char lo, char shift) => $$fshr(hi, lo, shift);
|
||||
macro char char.rotl(self, char shift) => $$fshl(self, self, shift);
|
||||
macro char char.rotr(self, char shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro char.popcount(char i) => $$popcount(i);
|
||||
macro char.ctz(char i) => $$ctz(i);
|
||||
macro char.clz(char i) => $$clz(i);
|
||||
macro char char.fshl(char hi, char lo, char shift) => $$fshl(hi, lo, shift);
|
||||
macro char char.fshr(char hi, char lo, char shift) => $$fshr(hi, lo, shift);
|
||||
macro char char.rotl(char i, char shift) => $$fshl(i, i, shift);
|
||||
macro char char.rotr(char i, char shift) => $$fshr(i, i, shift);
|
||||
macro ichar.popcount(self) => $$popcount(self);
|
||||
macro ichar.ctz(self) => $$ctz(self);
|
||||
macro ichar.clz(self) => $$clz(self);
|
||||
macro ichar ichar.fshl(hi, ichar lo, ichar shift) => $$fshl(hi, lo, shift);
|
||||
macro ichar ichar.fshr(hi, ichar lo, ichar shift) => $$fshr(hi, lo, shift);
|
||||
macro ichar ichar.rotl(self, ichar shift) => $$fshl(self, self, shift);
|
||||
macro ichar ichar.rotr(self, ichar shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ichar.popcount(ichar i) => $$popcount(i);
|
||||
macro ichar.ctz(ichar i) => $$ctz(i);
|
||||
macro ichar.clz(ichar i) => $$clz(i);
|
||||
macro ichar ichar.fshl(ichar hi, ichar lo, ichar shift) => $$fshl(hi, lo, shift);
|
||||
macro ichar ichar.fshr(ichar hi, ichar lo, ichar shift) => $$fshr(hi, lo, shift);
|
||||
macro ichar ichar.rotl(ichar i, ichar shift) => $$fshl(i, i, shift);
|
||||
macro ichar ichar.rotr(ichar i, ichar shift) => $$fshr(i, i, shift);
|
||||
macro ulong.popcount(self) => $$popcount(self);
|
||||
macro ulong.ctz(self) => $$ctz(self);
|
||||
macro ulong.clz(self) => $$clz(self);
|
||||
macro ulong ulong.fshl(hi, ulong lo, ulong shift) => $$fshl(hi, lo, shift);
|
||||
macro ulong ulong.fshr(hi, ulong lo, ulong shift) => $$fshr(hi, lo, shift);
|
||||
macro ulong ulong.rotl(self, ulong shift) => $$fshl(self, self, shift);
|
||||
macro ulong ulong.rotr(self, ulong shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro ulong.popcount(ulong i) => $$popcount(i);
|
||||
macro ulong.ctz(ulong i) => $$ctz(i);
|
||||
macro ulong.clz(ulong i) => $$clz(i);
|
||||
macro ulong ulong.fshl(ulong hi, ulong lo, ulong shift) => $$fshl(hi, lo, shift);
|
||||
macro ulong ulong.fshr(ulong hi, ulong lo, ulong shift) => $$fshr(hi, lo, shift);
|
||||
macro ulong ulong.rotl(ulong i, ulong shift) => $$fshl(i, i, shift);
|
||||
macro ulong ulong.rotr(ulong i, ulong shift) => $$fshr(i, i, shift);
|
||||
macro long.popcount(self) => $$popcount(self);
|
||||
macro long.ctz(self) => $$ctz(self);
|
||||
macro long.clz(self) => $$clz(self);
|
||||
macro long long.fshl(hi, long lo, long shift) => $$fshl(hi, lo, shift);
|
||||
macro long long.fshr(hi, long lo, long shift) => $$fshr(hi, lo, shift);
|
||||
macro long long.rotl(self, long shift) => $$fshl(self, self, shift);
|
||||
macro long long.rotr(self, long shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro long.popcount(long i) => $$popcount(i);
|
||||
macro long.ctz(long i) => $$ctz(i);
|
||||
macro long.clz(long i) => $$clz(i);
|
||||
macro long long.fshl(long hi, long lo, long shift) => $$fshl(hi, lo, shift);
|
||||
macro long long.fshr(long hi, long lo, long shift) => $$fshr(hi, lo, shift);
|
||||
macro long long.rotl(long i, long shift) => $$fshl(i, i, shift);
|
||||
macro long long.rotr(long i, long shift) => $$fshr(i, i, shift);
|
||||
macro uint128.popcount(self) => $$popcount(self);
|
||||
macro uint128.ctz(self) => $$ctz(self);
|
||||
macro uint128.clz(self) => $$clz(self);
|
||||
macro uint128 uint128.fshl(hi, uint128 lo, uint128 shift) => $$fshl(hi, lo, shift);
|
||||
macro uint128 uint128.fshr(hi, uint128 lo, uint128 shift) => $$fshr(hi, lo, shift);
|
||||
macro uint128 uint128.rotl(self, uint128 shift) => $$fshl(self, self, shift);
|
||||
macro uint128 uint128.rotr(self, uint128 shift) => $$fshr(self, self, shift);
|
||||
|
||||
macro uint128.popcount(uint128 i) => $$popcount(i);
|
||||
macro uint128.ctz(uint128 i) => $$ctz(i);
|
||||
macro uint128.clz(uint128 i) => $$clz(i);
|
||||
macro uint128 uint128.fshl(uint128 hi, uint128 lo, uint128 shift) => $$fshl(hi, lo, shift);
|
||||
macro uint128 uint128.fshr(uint128 hi, uint128 lo, uint128 shift) => $$fshr(hi, lo, shift);
|
||||
macro uint128 uint128.rotl(uint128 i, uint128 shift) => $$fshl(i, i, shift);
|
||||
macro uint128 uint128.rotr(uint128 i, uint128 shift) => $$fshr(i, i, shift);
|
||||
|
||||
macro int128.popcount(int128 i) => $$popcount(i);
|
||||
macro int128.ctz(int128 i) => $$ctz(i);
|
||||
macro int128.clz(int128 i) => $$clz(i);
|
||||
macro int128 int128.fshl(int128 hi, int128 lo, int128 shift) => $$fshl(hi, lo, shift);
|
||||
macro int128 int128.fshr(int128 hi, int128 lo, int128 shift) => $$fshr(hi, lo, shift);
|
||||
macro int128 int128.rotl(int128 i, int128 shift) => $$fshl(i, i, shift);
|
||||
macro int128 int128.rotr(int128 i, int128 shift) => $$fshr(i, i, shift);
|
||||
macro int128.popcount(self) => $$popcount(self);
|
||||
macro int128.ctz(self) => $$ctz(self);
|
||||
macro int128.clz(self) => $$clz(self);
|
||||
macro int128 int128.fshl(hi, int128 lo, int128 shift) => $$fshl(hi, lo, shift);
|
||||
macro int128 int128.fshr(hi, int128 lo, int128 shift) => $$fshr(hi, lo, shift);
|
||||
macro int128 int128.rotl(self, int128 shift) => $$fshl(self, self, shift);
|
||||
macro int128 int128.rotr(self, int128 shift) => $$fshr(self, self, shift);
|
||||
|
||||
503
lib/std/collections/anylist.c3
Normal file
503
lib/std/collections/anylist.c3
Normal file
@@ -0,0 +1,503 @@
|
||||
// Copyright (c) 2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::anylist;
|
||||
import std::io,std::math;
|
||||
|
||||
def AnyPredicate = fn bool(any value);
|
||||
def AnyTest = fn bool(any type, any context);
|
||||
|
||||
struct AnyList (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
any* entries;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Use `init` for to use a custom allocator.
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = null)
|
||||
{
|
||||
return self.init(allocator ?: allocator::heap(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
if (initial_capacity > 0)
|
||||
{
|
||||
initial_capacity = math::next_power_of_2(initial_capacity);
|
||||
self.entries = allocator::alloc_array(allocator, any, initial_capacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.entries = null;
|
||||
}
|
||||
self.capacity = initial_capacity;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(allocator::temp(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
case 0:
|
||||
return formatter.print("[]")!;
|
||||
case 1:
|
||||
return formatter.printf("[%s]", self.entries[0])!;
|
||||
default:
|
||||
usz n = formatter.print("[")!;
|
||||
foreach (i, element : self.entries[:self.size])
|
||||
{
|
||||
if (i != 0) formatter.print(", ")!;
|
||||
n += formatter.printf("%s", element)!;
|
||||
}
|
||||
n += formatter.print("]")!;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
fn String AnyList.to_new_string(&self, Allocator allocator = null) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
|
||||
}
|
||||
|
||||
|
||||
fn String AnyList.to_string(&self, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String AnyList.to_tstring(&self) => string::tformat("%s", *self);
|
||||
|
||||
<*
|
||||
Push an element on the list by cloning it.
|
||||
*>
|
||||
macro void AnyList.push(&self, element)
|
||||
{
|
||||
if (!self.allocator) self.allocator = allocator::heap();
|
||||
self.append_internal(allocator::clone(self.allocator, element));
|
||||
}
|
||||
|
||||
fn void AnyList.append_internal(&self, any element) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
Free a retained element removed using *_retained.
|
||||
*>
|
||||
fn void AnyList.free_element(&self, any element) @inline
|
||||
{
|
||||
allocator::free(self.allocator, element.ptr);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@return! CastResult.TYPE_MISMATCH, IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.pop(&self, $Type)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return *anycast(self.entries[--self.size], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the last value and allocate the copy using the given allocator.
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any! AnyList.copy_pop(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return allocator::clone_any(allocator, self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the last value and allocate the copy using the given allocator.
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
@deprecated `use copy_pop`
|
||||
*>
|
||||
fn any! AnyList.new_pop(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return self.copy_pop(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the last value and allocate the copy using the temp allocator
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
@deprecated `use tcopy_pop`
|
||||
*>
|
||||
fn any! AnyList.temp_pop(&self) => self.copy_pop(allocator::temp());
|
||||
|
||||
<*
|
||||
Pop the last value and allocate the copy using the temp allocator
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any! AnyList.tcopy_pop(&self) => self.copy_pop(allocator::temp());
|
||||
|
||||
<*
|
||||
Pop the last value. It must later be released using list.free_element()
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any! AnyList.pop_retained(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
fn void AnyList.clear(&self)
|
||||
{
|
||||
for (usz i = 0; i < self.size; i++)
|
||||
{
|
||||
self.free_element(self.entries[i]);
|
||||
}
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Same as pop() but pops the first value instead.
|
||||
*>
|
||||
macro AnyList.pop_first(&self, $Type)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return *anycast(self.entries[0], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Same as pop_retained() but pops the first value instead.
|
||||
*>
|
||||
fn any! AnyList.pop_first_retained(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
<*
|
||||
Same as new_pop() but pops the first value instead.
|
||||
@deprecated `use copy_pop_first`
|
||||
*>
|
||||
fn any! AnyList.new_pop_first(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return self.copy_pop_first(allocator) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Same as new_pop() but pops the first value instead.
|
||||
*>
|
||||
fn any! AnyList.copy_pop_first(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
defer self.remove_at(0);
|
||||
return allocator::clone_any(allocator, self.entries[0]);
|
||||
}
|
||||
|
||||
<*
|
||||
Same as temp_pop() but pops the first value instead.
|
||||
*>
|
||||
fn any! AnyList.tcopy_pop_first(&self) => self.copy_pop_first(allocator::temp());
|
||||
|
||||
<*
|
||||
Same as temp_pop() but pops the first value instead.
|
||||
@deprecated `use tcopy_pop_first`
|
||||
*>
|
||||
fn any! AnyList.temp_pop_first(&self) => self.new_pop_first(allocator::temp());
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void AnyList.remove_at(&self, usz index)
|
||||
{
|
||||
if (!--self.size || index == self.size) return;
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
}
|
||||
|
||||
fn void AnyList.add_all(&self, AnyList* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
self.reserve(other_list.size);
|
||||
foreach (value : other_list)
|
||||
{
|
||||
self.entries[self.size++] = allocator::clone_any(self.allocator, value);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the elements in a list.
|
||||
*>
|
||||
fn void AnyList.reverse(&self)
|
||||
{
|
||||
if (self.size < 2) return;
|
||||
usz half = self.size / 2U;
|
||||
usz end = self.size - 1;
|
||||
for (usz i = 0; i < half; i++)
|
||||
{
|
||||
self.swap(i, end - i);
|
||||
}
|
||||
}
|
||||
|
||||
fn any[] AnyList.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Push an element to the front of the list.
|
||||
*>
|
||||
macro void AnyList.push_front(&self, type)
|
||||
{
|
||||
self.insert_at(0, type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
macro void AnyList.insert_at(&self, usz index, type) @local
|
||||
{
|
||||
any value = allocator::copy(self.allocator, type);
|
||||
self.insert_at_internal(self, index, value);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void AnyList.insert_at_internal(&self, usz index, any value) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
for (usz i = self.size; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.size++;
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn void AnyList.remove_last(&self)
|
||||
{
|
||||
self.free_element(self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn void AnyList.remove_first(&self)
|
||||
{
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
macro AnyList.first(&self, $Type)
|
||||
{
|
||||
return *anycast(self.first_any(), $Type);
|
||||
}
|
||||
|
||||
fn any! AnyList.first_any(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[0] : IteratorResult.NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
macro AnyList.last(&self, $Type)
|
||||
{
|
||||
return *anycast(self.last_any(), $Type);
|
||||
}
|
||||
|
||||
fn any! AnyList.last_any(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[self.size - 1] : IteratorResult.NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
fn bool AnyList.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
fn usz AnyList.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size "Index out of range"
|
||||
*>
|
||||
macro AnyList.get(&self, usz index, $Type)
|
||||
{
|
||||
return *anycast(self.entries[index], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size "Index out of range"
|
||||
*>
|
||||
fn any AnyList.get_any(&self, usz index) @inline
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn void AnyList.free(&self)
|
||||
{
|
||||
if (!self.allocator) return;
|
||||
self.clear();
|
||||
allocator::free(self.allocator, self.entries);
|
||||
self.capacity = 0;
|
||||
self.entries = null;
|
||||
}
|
||||
|
||||
fn void AnyList.swap(&self, usz i, usz j)
|
||||
{
|
||||
any temp = self.entries[i];
|
||||
self.entries[i] = self.entries[j];
|
||||
self.entries[j] = temp;
|
||||
}
|
||||
|
||||
<*
|
||||
@param filter "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.remove_if(&self, AnyPredicate filter)
|
||||
{
|
||||
return self._remove_if(filter, false);
|
||||
}
|
||||
|
||||
<*
|
||||
@param selection "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.retain_if(&self, AnyPredicate selection)
|
||||
{
|
||||
return self._remove_if(selection, true);
|
||||
}
|
||||
|
||||
macro usz AnyList._remove_if(&self, AnyPredicate filter, bool $invert) @local
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(&self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && filter(&self.entries[i - 1])) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(&self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(&self.entries[i - 1])) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
|
||||
{
|
||||
return self._remove_using_test(filter, false, context);
|
||||
}
|
||||
|
||||
fn usz AnyList.retain_using_test(&self, AnyTest filter, any context)
|
||||
{
|
||||
return self._remove_using_test(filter, true, context);
|
||||
}
|
||||
|
||||
macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @local
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Reserve at least min_capacity
|
||||
*>
|
||||
fn void AnyList.reserve(&self, usz min_capacity)
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = allocator::heap();
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
self.entries = allocator::realloc(self.allocator, self.entries, any.sizeof * min_capacity);
|
||||
self.capacity = min_capacity;
|
||||
}
|
||||
|
||||
macro any AnyList.@item_at(&self, usz index) @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size "Index out of range"
|
||||
*>
|
||||
macro void AnyList.set(&self, usz index, value)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(value);
|
||||
return;
|
||||
}
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index] = allocator::copy(self.allocator, value);
|
||||
}
|
||||
|
||||
fn void AnyList.ensure_capacity(&self, usz added = 1) @inline @private
|
||||
{
|
||||
usz new_size = self.size + added;
|
||||
if (self.capacity >= new_size) return;
|
||||
|
||||
assert(new_size < usz.max / 2U);
|
||||
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
|
||||
while (new_capacity < new_size) new_capacity *= 2U;
|
||||
self.reserve(new_capacity);
|
||||
}
|
||||
160
lib/std/collections/bitset.c3
Normal file
160
lib/std/collections/bitset.c3
Normal file
@@ -0,0 +1,160 @@
|
||||
<*
|
||||
@require SIZE > 0
|
||||
*>
|
||||
module std::collections::bitset(<SIZE>);
|
||||
|
||||
def Type = uint;
|
||||
|
||||
const BITS = Type.sizeof * 8;
|
||||
const SZ = (SIZE + BITS - 1) / BITS;
|
||||
|
||||
struct BitSet
|
||||
{
|
||||
Type[SZ] data;
|
||||
}
|
||||
|
||||
fn usz BitSet.cardinality(&self)
|
||||
{
|
||||
usz n;
|
||||
foreach (x : self.data)
|
||||
{
|
||||
n += x.popcount();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
*>
|
||||
fn void BitSet.set(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
self.data[q] |= 1 << r;
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
*>
|
||||
fn void BitSet.unset(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
self.data[q] &= ~(1 << r);
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
*>
|
||||
fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
return self.data[q] & (1 << r) != 0;
|
||||
}
|
||||
|
||||
fn usz BitSet.len(&self) @operator(len) @inline
|
||||
{
|
||||
return SZ * BITS;
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
*>
|
||||
fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
|
||||
{
|
||||
if (value) return self.set(i);
|
||||
self.unset(i);
|
||||
}
|
||||
|
||||
<*
|
||||
@require Type.kindof == UNSIGNED_INT
|
||||
*>
|
||||
module std::collections::growablebitset(<Type>);
|
||||
import std::collections::list;
|
||||
|
||||
const BITS = Type.sizeof * 8;
|
||||
|
||||
def GrowableBitSetList = List(<Type>);
|
||||
|
||||
struct GrowableBitSet
|
||||
{
|
||||
GrowableBitSetList data;
|
||||
}
|
||||
|
||||
<*
|
||||
@param initial_capacity
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
*>
|
||||
fn GrowableBitSet* GrowableBitSet.new_init(&self, usz initial_capacity = 1, Allocator allocator = allocator::heap())
|
||||
{
|
||||
self.data.new_init(initial_capacity, allocator);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn GrowableBitSet* GrowableBitSet.temp_init(&self, usz initial_capacity = 1)
|
||||
{
|
||||
return self.new_init(initial_capacity, allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.free(&self)
|
||||
{
|
||||
self.data.free();
|
||||
}
|
||||
|
||||
fn usz GrowableBitSet.cardinality(&self)
|
||||
{
|
||||
usz n;
|
||||
foreach (x : self.data)
|
||||
{
|
||||
n += x.popcount();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.set(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
usz current_len = self.data.len();
|
||||
if (q >= current_len)
|
||||
{
|
||||
usz n = q + 1;
|
||||
self.data.reserve(n);
|
||||
if (n - 1 >= current_len)
|
||||
{
|
||||
self.data.entries[current_len .. (n - 1)] = 0;
|
||||
}
|
||||
self.data.size = n;
|
||||
}
|
||||
self.data.set(q, self.data[q] | (1 << r));
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.unset(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
if (q >= self.data.len()) return;
|
||||
self.data.set(q, self.data[q] &~ (1 << r));
|
||||
}
|
||||
|
||||
fn bool GrowableBitSet.get(&self, usz i) @operator([]) @inline
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
if (q >= self.data.len()) return false;
|
||||
return self.data[q] & (1 << r) != 0;
|
||||
}
|
||||
|
||||
fn usz GrowableBitSet.len(&self) @operator(len)
|
||||
{
|
||||
usz n = self.data.len() * BITS;
|
||||
if (n > 0) n -= (usz)self.data[^1].clz();
|
||||
return n;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
|
||||
{
|
||||
if (value) return self.set(i);
|
||||
self.unset(i);
|
||||
}
|
||||
455
lib/std/collections/elastic_array.c3
Normal file
455
lib/std/collections/elastic_array.c3
Normal file
@@ -0,0 +1,455 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
<*
|
||||
@require MAX_SIZE >= 1 `The size must be at least 1 element big.`
|
||||
*>
|
||||
module std::collections::elastic_array(<Type, MAX_SIZE>);
|
||||
import std::io, std::math, std::collections::list_common;
|
||||
|
||||
def ElementPredicate = fn bool(Type *type);
|
||||
def ElementTest = fn bool(Type *type, any context);
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
|
||||
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct ElasticArray (Printable)
|
||||
{
|
||||
usz size;
|
||||
Type[MAX_SIZE] entries;
|
||||
}
|
||||
|
||||
fn usz! ElasticArray.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
case 0:
|
||||
return formatter.print("[]")!;
|
||||
case 1:
|
||||
return formatter.printf("[%s]", self.entries[0])!;
|
||||
default:
|
||||
usz n = formatter.print("[")!;
|
||||
foreach (i, element : self.entries[:self.size])
|
||||
{
|
||||
if (i != 0) formatter.print(", ")!;
|
||||
n += formatter.printf("%s", element)!;
|
||||
}
|
||||
n += formatter.print("]")!;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
fn String ElasticArray.to_string(&self, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String ElasticArray.to_new_string(&self, Allocator allocator = nul) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
|
||||
}
|
||||
|
||||
fn String ElasticArray.to_tstring(&self)
|
||||
{
|
||||
return string::tformat("%s", *self);
|
||||
}
|
||||
|
||||
fn void! ElasticArray.push_try(&self, Type element) @inline
|
||||
{
|
||||
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `Tried to exceed the max size`
|
||||
*>
|
||||
fn void ElasticArray.push(&self, Type element) @inline
|
||||
{
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
fn Type! ElasticArray.pop(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
fn void ElasticArray.clear(&self)
|
||||
{
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn Type! ElasticArray.pop_first(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void ElasticArray.remove_at(&self, usz index)
|
||||
{
|
||||
if (!--self.size || index == self.size) return;
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
@require other_list.size + self.size <= MAX_SIZE
|
||||
*>
|
||||
fn void ElasticArray.add_all(&self, ElasticArray* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
foreach (&value : other_list)
|
||||
{
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Add as many elements as possible to the new array,
|
||||
returning the number of elements that didn't fit.
|
||||
*>
|
||||
fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
|
||||
{
|
||||
if (!other_list.size) return 0;
|
||||
foreach (i, &value : other_list)
|
||||
{
|
||||
if (self.size == MAX_SIZE) return other_list.size - i;
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Add as many values from this array as possible, returning the
|
||||
number of elements that didn't fit.
|
||||
|
||||
@param [in] array
|
||||
*>
|
||||
fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return 0;
|
||||
foreach (i, &value : array)
|
||||
{
|
||||
if (self.size == MAX_SIZE) return array.len - i;
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Add the values of an array to this list.
|
||||
|
||||
@param [in] array
|
||||
@require array.len + self.size <= MAX_SIZE `Size would exceed max.`
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void ElasticArray.add_array(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return;
|
||||
foreach (&value : array)
|
||||
{
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
fn Type[] ElasticArray.to_new_aligned_array(&self)
|
||||
{
|
||||
return list_common::list_to_new_aligned_array(Type, self, allocator::heap());
|
||||
}
|
||||
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
fn Type[] ElasticArray.to_aligned_array(&self, Allocator allocator)
|
||||
{
|
||||
return list_common::list_to_new_aligned_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
@require !type_is_overaligned() : "This function is not available on overaligned types"
|
||||
*>
|
||||
macro Type[] ElasticArray.to_new_array(&self)
|
||||
{
|
||||
return list_common::list_to_array(Type, self, allocator::heap());
|
||||
}
|
||||
|
||||
<*
|
||||
@require !type_is_overaligned() : "This function is not available on overaligned types"
|
||||
*>
|
||||
macro Type[] ElasticArray.to_array(&self, Allocator allocator)
|
||||
{
|
||||
return list_common::list_to_new_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
fn Type[] ElasticArray.to_tarray(&self)
|
||||
{
|
||||
$if type_is_overaligned():
|
||||
return self.to_aligned_array(allocator::temp());
|
||||
$else
|
||||
return self.to_array(allocator::temp());
|
||||
$endif;
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the elements in a list.
|
||||
*>
|
||||
fn void ElasticArray.reverse(&self)
|
||||
{
|
||||
list_common::list_reverse(self);
|
||||
}
|
||||
|
||||
fn Type[] ElasticArray.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `List would exceed max size`
|
||||
*>
|
||||
fn void ElasticArray.push_front(&self, Type type) @inline
|
||||
{
|
||||
self.insert_at(0, type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `List would exceed max size`
|
||||
*>
|
||||
fn void! ElasticArray.push_front_try(&self, Type type) @inline
|
||||
{
|
||||
return self.insert_at_try(0, type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size
|
||||
*>
|
||||
fn void! ElasticArray.insert_at_try(&self, usz index, Type value)
|
||||
{
|
||||
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.insert_at(index, value);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `List would exceed max size`
|
||||
@require index <= self.size
|
||||
*>
|
||||
fn void ElasticArray.insert_at(&self, usz index, Type type)
|
||||
{
|
||||
for (usz i = self.size; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.size++;
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void ElasticArray.set_at(&self, usz index, Type type)
|
||||
{
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
fn void! ElasticArray.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.size--;
|
||||
}
|
||||
|
||||
fn void! ElasticArray.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type! ElasticArray.first(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
fn Type! ElasticArray.last(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
fn bool ElasticArray.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
fn usz ElasticArray.byte_size(&self) @inline
|
||||
{
|
||||
return Type.sizeof * self.size;
|
||||
}
|
||||
|
||||
fn usz ElasticArray.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
fn Type ElasticArray.get(&self, usz index) @inline
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn void ElasticArray.swap(&self, usz i, usz j)
|
||||
{
|
||||
@swap(self.entries[i], self.entries[j]);
|
||||
}
|
||||
|
||||
<*
|
||||
@param filter "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz ElasticArray.remove_if(&self, ElementPredicate filter)
|
||||
{
|
||||
return list_common::list_remove_if(self, filter, false);
|
||||
}
|
||||
|
||||
<*
|
||||
@param selection "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz ElasticArray.retain_if(&self, ElementPredicate selection)
|
||||
{
|
||||
return list_common::list_remove_if(self, selection, true);
|
||||
}
|
||||
|
||||
fn usz ElasticArray.remove_using_test(&self, ElementTest filter, any context)
|
||||
{
|
||||
return list_common::list_remove_using_test(self, filter, false, context);
|
||||
}
|
||||
|
||||
fn usz ElasticArray.retain_using_test(&self, ElementTest filter, any context)
|
||||
{
|
||||
return list_common::list_remove_using_test(self, filter, true, context);
|
||||
}
|
||||
|
||||
|
||||
macro Type ElasticArray.@item_at(&self, usz index) @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn Type* ElasticArray.get_ref(&self, usz index) @operator(&[]) @inline
|
||||
{
|
||||
return &self.entries[index];
|
||||
}
|
||||
|
||||
fn void ElasticArray.set(&self, usz index, Type value) @operator([]=)
|
||||
{
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
|
||||
// Functions for equatable types
|
||||
fn usz! ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn usz! ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach_r (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (self.size != other_list.size) return false;
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (!equals(v, other_list.entries[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Check for presence of a value in a list.
|
||||
|
||||
@param [&in] self "the list to find elements in"
|
||||
@param value "The value to search for"
|
||||
@return "True if the value is found, false otherwise"
|
||||
*>
|
||||
fn bool ElasticArray.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (equals(v, value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool ElasticArray.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
return @ok(self.remove_at(self.rindex_of(value)));
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool ElasticArray.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
return @ok(self.remove_at(self.index_of(value)));
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "the number of deleted elements."
|
||||
*>
|
||||
fn usz ElasticArray.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
return list_common::list_remove_item(self, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn void ElasticArray.remove_all_from(&self, ElasticArray* other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
foreach (v : other_list) self.remove_item(v);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] self
|
||||
@return "The number non-null values in the list"
|
||||
*>
|
||||
fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
usz vals = 0;
|
||||
foreach (v : self) if (v) vals++;
|
||||
return vals;
|
||||
}
|
||||
|
||||
fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
return list_common::list_compact(self);
|
||||
}
|
||||
@@ -1,18 +1,71 @@
|
||||
module std::collections::enummap<Enum, ValueType>;
|
||||
|
||||
struct EnumMap
|
||||
<*
|
||||
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap"
|
||||
*>
|
||||
module std::collections::enummap(<Enum, ValueType>);
|
||||
import std::io;
|
||||
struct EnumMap (Printable)
|
||||
{
|
||||
ValueType[Enum.len] values;
|
||||
ValueType[Enum.len] values;
|
||||
}
|
||||
|
||||
fn void EnumMap.init(EnumMap* this, ValueType init_value)
|
||||
fn void EnumMap.init(&self, ValueType init_value)
|
||||
{
|
||||
foreach(&a : this.values)
|
||||
{
|
||||
*a = init_value;
|
||||
}
|
||||
foreach (&a : self.values)
|
||||
{
|
||||
*a = init_value;
|
||||
}
|
||||
}
|
||||
|
||||
fn uint EnumMap.len(EnumMap* this) @operator(len) => this.values.len;
|
||||
fn ValueType EnumMap.get(EnumMap* this, Enum key) @operator([]) => this.values[key.ordinal];
|
||||
fn void EnumMap.set(EnumMap* this, Enum key, ValueType value) @operator([]=) => this.values[key.ordinal] = value;
|
||||
fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
usz n = formatter.print("{ ")!;
|
||||
foreach (i, &value : self.values)
|
||||
{
|
||||
if (i != 0) formatter.print(", ")!;
|
||||
n += formatter.printf("%s: %s", Enum.from_ordinal(i), *value)!;
|
||||
}
|
||||
n += formatter.print(" }")!;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn String EnumMap.to_string(&self, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String EnumMap.to_new_string(&self, Allocator allocator = null) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
|
||||
}
|
||||
|
||||
fn String EnumMap.to_tstring(&self) @dynamic
|
||||
{
|
||||
return string::tformat("%s", *self);
|
||||
}
|
||||
|
||||
<*
|
||||
@return "The total size of this map, which is the same as the number of enum values"
|
||||
@pure
|
||||
*>
|
||||
fn usz EnumMap.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.values.len;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "Retrieve a value given the underlying enum, if there is no entry, then the zero value for the value is returned."
|
||||
*>
|
||||
fn ValueType EnumMap.get(&self, Enum key) @operator([]) @inline
|
||||
{
|
||||
return self.values[key.ordinal];
|
||||
}
|
||||
|
||||
fn ValueType* EnumMap.get_ref(&self, Enum key) @operator(&[]) @inline
|
||||
{
|
||||
return &self.values[key.ordinal];
|
||||
}
|
||||
|
||||
fn void EnumMap.set(&self, Enum key, ValueType value) @operator([]=) @inline
|
||||
{
|
||||
self.values[key.ordinal] = value;
|
||||
}
|
||||
@@ -1,148 +1,177 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
/**
|
||||
* @require Enum.kindof == TypeKind.ENUM : "Only enums maybe be used with an enumset"
|
||||
**/
|
||||
module std::collections::enumset<Enum>;
|
||||
<*
|
||||
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enumset"
|
||||
*>
|
||||
module std::collections::enumset(<Enum>);
|
||||
import std::io;
|
||||
|
||||
def EnumSetType = $typefrom(private::type_for_enum_elements(Enum.elements)) @private;
|
||||
|
||||
const IS_CHAR_ARRAY = Enum.elements > 128;
|
||||
distinct EnumSet (Printable) = EnumSetType;
|
||||
|
||||
$switch
|
||||
|
||||
$case (Enum.elements > 128):
|
||||
def EnumSetType @private = char[(Enum.elements + 7) / 8];
|
||||
|
||||
$case (Enum.elements > 64):
|
||||
def EnumSetType @private = uint128;
|
||||
|
||||
$case (Enum.elements > 32 || $$C_INT_SIZE > 32):
|
||||
def EnumSetType @private = ulong;
|
||||
|
||||
$case (Enum.elements > 16 || $$C_INT_SIZE > 16):
|
||||
def EnumSetType @private = uint;
|
||||
|
||||
$case (Enum.elements > 8 || $$C_INT_SIZE > 8):
|
||||
def EnumSetType @private = ushort;
|
||||
|
||||
$default:
|
||||
def EnumSetType @private = char;
|
||||
|
||||
$endswitch
|
||||
|
||||
def EnumSet = distinct EnumSetType;
|
||||
|
||||
fn void EnumSet.add(EnumSet* this, Enum v)
|
||||
fn void EnumSet.add(&self, Enum v)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
(*this)[v / 8] |= (char)(1u << (v % 8));
|
||||
$else
|
||||
*this = (EnumSet)((EnumSetType)*this | 1u << (EnumSetType)v);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
(*self)[(usz)v.ordinal / 8] |= (char)(1u << ((usz)v.ordinal % 8));
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v.ordinal);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.clear(EnumSet* this)
|
||||
fn void EnumSet.clear(&self)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
*this = {};
|
||||
$else
|
||||
*this = 0;
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
*self = {};
|
||||
$else
|
||||
*self = 0;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn bool EnumSet.remove(EnumSet* this, Enum v)
|
||||
fn bool EnumSet.remove(&self, Enum v)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
if (!this.has(v) @inline) return false;
|
||||
(*this)[v / 8] &= (char)~(1 << (v % 8));
|
||||
return true;
|
||||
$else
|
||||
EnumSetType old = (EnumSetType)*this;
|
||||
EnumSetType new = old & ~(1u << (EnumSetType)v);
|
||||
*this = (EnumSet)new;
|
||||
return old != new;
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
if (!self.has(v) @inline) return false;
|
||||
(*self)[(usz)v.ordinal / 8] &= (char)~(1u << ((usz)v.ordinal % 8));
|
||||
return true;
|
||||
$else
|
||||
EnumSetType old = (EnumSetType)*self;
|
||||
EnumSetType new = old & ~(1u << (EnumSetType)v.ordinal);
|
||||
*self = (EnumSet)new;
|
||||
return old != new;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn bool EnumSet.has(EnumSet* this, Enum v)
|
||||
fn bool EnumSet.has(&self, Enum v)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
return (bool)(((*this)[v / 8] << (v % 8)) & 0x01);
|
||||
$else
|
||||
return ((EnumSetType)*this & (1u << (EnumSetType)v)) != 0;
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
return (bool)(((*self)[(usz)v.ordinal / 8] << ((usz)v.ordinal % 8)) & 0x01);
|
||||
$else
|
||||
return ((EnumSetType)*self & (1u << (EnumSetType)v.ordinal)) != 0;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.add_all(EnumSet* this, EnumSet s)
|
||||
fn void EnumSet.add_all(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*this)[i] |= c;
|
||||
$else
|
||||
*this = (EnumSet)((EnumSetType)*this | (EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*self)[i] |= c;
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self | (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.retain_all(EnumSet* this, EnumSet s)
|
||||
fn void EnumSet.retain_all(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*this)[i] &= c;
|
||||
$else
|
||||
*this = (EnumSet)((EnumSetType)*this & (EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*self)[i] &= c;
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self & (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.remove_all(EnumSet* this, EnumSet s)
|
||||
fn void EnumSet.remove_all(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*this)[i] &= ~c;
|
||||
$else
|
||||
*this = (EnumSet)((EnumSetType)*this & ~(EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*self)[i] &= ~c;
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self & ~(EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn EnumSet EnumSet.and_of(EnumSet* this, EnumSet s)
|
||||
fn EnumSet EnumSet.and_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *this;
|
||||
copy.retain_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*this & (EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
copy.retain_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self & (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn EnumSet EnumSet.or_of(EnumSet* this, EnumSet s)
|
||||
fn EnumSet EnumSet.or_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *this;
|
||||
copy.add_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*this | (EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
copy.add_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self | (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
fn EnumSet EnumSet.diff_of(EnumSet* this, EnumSet s)
|
||||
fn EnumSet EnumSet.diff_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *this;
|
||||
copy.remove_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*this & ~(EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
copy.remove_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self & ~(EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn EnumSet EnumSet.xor_of(EnumSet* this, EnumSet s)
|
||||
fn EnumSet EnumSet.xor_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *this;
|
||||
foreach (i, c : s) copy[i] ^= c;
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*this ^ (EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
foreach (i, c : s) copy[i] ^= c;
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self ^ (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
|
||||
{
|
||||
usz n = formatter.print("[")!;
|
||||
bool found;
|
||||
foreach (value : Enum.values)
|
||||
{
|
||||
if (!set.has(value)) continue;
|
||||
if (found) n += formatter.print(", ")!;
|
||||
found = true;
|
||||
n += formatter.printf("%s", value)!;
|
||||
}
|
||||
n += formatter.print("]")!;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn String EnumSet.to_new_string(&set, Allocator allocator = allocator::heap()) @dynamic
|
||||
{
|
||||
return string::format("%s", *set, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String EnumSet.to_string(&set, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("%s", *set, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String EnumSet.to_tstring(&set) @dynamic
|
||||
{
|
||||
return string::tformat("%s", *set);
|
||||
}
|
||||
|
||||
module std::collections::enumset::private;
|
||||
|
||||
macro typeid type_for_enum_elements(usz $elements)
|
||||
{
|
||||
$switch
|
||||
$case ($elements > 128):
|
||||
return char[($elements + 7) / 8].typeid;
|
||||
$case ($elements > 64):
|
||||
return uint128.typeid;
|
||||
$case ($elements > 32 || $$C_INT_SIZE > 32):
|
||||
return ulong.typeid;
|
||||
$case ($elements > 16 || $$C_INT_SIZE > 16):
|
||||
return uint.typeid;
|
||||
$case ($elements > 8 || $$C_INT_SIZE > 8):
|
||||
return ushort.typeid;
|
||||
$default:
|
||||
return char.typeid;
|
||||
$endswitch
|
||||
}
|
||||
600
lib/std/collections/hashmap.c3
Normal file
600
lib/std/collections/hashmap.c3
Normal file
@@ -0,0 +1,600 @@
|
||||
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
<*
|
||||
@require $defined(Key{}.hash()) `No .hash function found on the key`
|
||||
*>
|
||||
module std::collections::map(<Key, Value>);
|
||||
import std::math;
|
||||
|
||||
struct HashMap
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator allocator;
|
||||
uint count; // Number of elements
|
||||
uint threshold; // Resize limit
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.new_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = null)
|
||||
{
|
||||
return self.init(allocator ?: allocator::heap(), capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
self.allocator = allocator;
|
||||
self.load_factor = load_factor;
|
||||
self.threshold = (uint)(capacity * load_factor);
|
||||
self.table = allocator::new_array(allocator, Entry*, capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init(allocator::temp(), capacity, load_factor) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashMap* HashMap.new_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
self.new_init(capacity, load_factor, allocator);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
self.set($vaarg[$i], $vaarg[$i+1]);
|
||||
$endfor
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys "The keys for the HashMap entries"
|
||||
@param [in] values "The values for the HashMap entries"
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require keys.len == values.len "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.new_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
assert(keys.len == values.len);
|
||||
self.new_init(capacity, load_factor, allocator);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
self.set(keys[i], values[i]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashMap* HashMap.temp_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.temp_init(capacity, load_factor);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
self.set($vaarg[$i], $vaarg[$i+1]);
|
||||
$endfor
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys "The keys for the HashMap entries"
|
||||
@param [in] values "The values for the HashMap entries"
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require keys.len == values.len "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.temp_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
assert(keys.len == values.len);
|
||||
self.temp_init(capacity, load_factor);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
self.set(keys[i], values[i]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Has this hash map been initialized yet?
|
||||
|
||||
@param [&in] map "The hash map we are testing"
|
||||
@return "Returns true if it has been initialized, false otherwise"
|
||||
*>
|
||||
fn bool HashMap.is_initialized(&map)
|
||||
{
|
||||
return (bool)map.allocator;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn HashMap* HashMap.new_init_from_map(&self, HashMap* other_map)
|
||||
{
|
||||
return self.init_from_map(other_map, allocator::heap()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn HashMap* HashMap.init_from_map(&self, HashMap* other_map, Allocator allocator)
|
||||
{
|
||||
self.new_init(other_map.table.len, other_map.load_factor, allocator);
|
||||
self.put_all_for_create(other_map);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map)
|
||||
{
|
||||
return map.init_from_map(other_map, allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
fn bool HashMap.is_empty(&map) @inline
|
||||
{
|
||||
return !map.count;
|
||||
}
|
||||
|
||||
fn usz HashMap.len(&map) @inline
|
||||
{
|
||||
return map.count;
|
||||
}
|
||||
|
||||
fn Value*! HashMap.get_ref(&map, Key key)
|
||||
{
|
||||
if (!map.count) return SearchResult.MISSING?;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return &e.value;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn Entry*! HashMap.get_entry(&map, Key key)
|
||||
{
|
||||
if (!map.count) return SearchResult.MISSING?;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value or update and
|
||||
@require $assignable(#expr, Value)
|
||||
*>
|
||||
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
{
|
||||
if (!map.count)
|
||||
{
|
||||
Value val = #expr;
|
||||
map.set(key, val);
|
||||
return val;
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e.value;
|
||||
}
|
||||
Value val = #expr;
|
||||
map.add_entry(hash, key, val, index);
|
||||
return val;
|
||||
}
|
||||
|
||||
fn Value! HashMap.get(&map, Key key) @operator([])
|
||||
{
|
||||
return *map.get_ref(key) @inline;
|
||||
}
|
||||
|
||||
fn bool HashMap.has_key(&map, Key key)
|
||||
{
|
||||
return @ok(map.get_ref(key));
|
||||
}
|
||||
|
||||
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
|
||||
{
|
||||
// If the map isn't initialized, use the defaults to initialize it.
|
||||
if (!map.allocator)
|
||||
{
|
||||
map.new_init();
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
e.value = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
map.add_entry(hash, key, value, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void! HashMap.remove(&map, Key key) @maydiscard
|
||||
{
|
||||
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn void HashMap.clear(&map)
|
||||
{
|
||||
if (!map.count) return;
|
||||
foreach (Entry** &entry_ref : map.table)
|
||||
{
|
||||
Entry* entry = *entry_ref;
|
||||
if (!entry) continue;
|
||||
Entry *next = entry.next;
|
||||
while (next)
|
||||
{
|
||||
Entry *to_delete = next;
|
||||
next = next.next;
|
||||
map.free_entry(to_delete);
|
||||
}
|
||||
map.free_entry(entry);
|
||||
*entry_ref = null;
|
||||
}
|
||||
map.count = 0;
|
||||
}
|
||||
|
||||
fn void HashMap.free(&map)
|
||||
{
|
||||
if (!map.allocator) return;
|
||||
map.clear();
|
||||
map.free_internal(map.table.ptr);
|
||||
map.table = {};
|
||||
}
|
||||
|
||||
fn Key[] HashMap.tcopy_keys(&map)
|
||||
{
|
||||
return map.copy_keys(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
fn Key[] HashMap.key_tlist(&map) @deprecated("Use 'tcopy_keys'")
|
||||
{
|
||||
return map.copy_keys(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@deprecated "use copy_keys"
|
||||
*>
|
||||
fn Key[] HashMap.key_new_list(&map, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return map.copy_keys(allocator) @inline;
|
||||
}
|
||||
|
||||
fn Key[] HashMap.copy_keys(&map, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!map.count) return {};
|
||||
|
||||
Key[] list = allocator::alloc_array(allocator, Key, map.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
list[index++] = entry.key.copy(allocator);
|
||||
$else
|
||||
list[index++] = entry.key;
|
||||
$endif
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
macro HashMap.@each(map; @body(key, value))
|
||||
{
|
||||
map.@each_entry(; Entry* entry) {
|
||||
@body(entry.key, entry.value);
|
||||
};
|
||||
}
|
||||
|
||||
macro HashMap.@each_entry(map; @body(entry))
|
||||
{
|
||||
if (map.count)
|
||||
{
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@body(entry);
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@deprecated `use tcopy_values`
|
||||
*>
|
||||
fn Value[] HashMap.value_tlist(&map)
|
||||
{
|
||||
return map.copy_values(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
fn Value[] HashMap.tcopy_values(&map)
|
||||
{
|
||||
return map.copy_values(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@deprecated `use copy_values`
|
||||
*>
|
||||
fn Value[] HashMap.value_new_list(&map, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return map.copy_values(allocator);
|
||||
}
|
||||
|
||||
fn Value[] HashMap.copy_values(&map, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!map.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, map.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
list[index++] = entry.value;
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
|
||||
{
|
||||
if (!map.count) return false;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
if (equals(v, entry.value)) return true;
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn HashMapIterator HashMap.iter(&self)
|
||||
{
|
||||
return { .map = self, .index = -1 };
|
||||
}
|
||||
|
||||
fn HashMapValueIterator HashMap.value_iter(&self)
|
||||
{
|
||||
return { .map = self, .index = -1 };
|
||||
}
|
||||
|
||||
fn HashMapKeyIterator HashMap.key_iter(&self)
|
||||
{
|
||||
return { .map = self, .index = -1 };
|
||||
}
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
|
||||
map.table[bucket_index] = entry;
|
||||
if (map.count++ >= map.threshold)
|
||||
{
|
||||
map.resize(map.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.resize(&map, uint new_capacity) @private
|
||||
{
|
||||
Entry*[] old_table = map.table;
|
||||
uint old_capacity = old_table.len;
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
map.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
|
||||
map.transfer(new_table);
|
||||
map.table = new_table;
|
||||
map.free_internal(old_table.ptr);
|
||||
map.threshold = (uint)(new_capacity * map.load_factor);
|
||||
}
|
||||
|
||||
fn void HashMap.transfer(&map, Entry*[] new_table) @private
|
||||
{
|
||||
Entry*[] src = map.table;
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, Entry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
Entry* next = e.next;
|
||||
uint i = index_for(e.hash, new_capacity);
|
||||
e.next = new_table[i];
|
||||
new_table[i] = e;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
|
||||
{
|
||||
if (!other_map.count) return;
|
||||
foreach (Entry *e : other_map.table)
|
||||
{
|
||||
while (e)
|
||||
{
|
||||
map.put_for_create(e.key, e.value);
|
||||
e = e.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.put_for_create(&map, Key key, Value value) @private
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
e.value = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
map.create_entry(hash, key, value, i);
|
||||
}
|
||||
|
||||
fn void HashMap.free_internal(&map, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(map.allocator, ptr);
|
||||
}
|
||||
|
||||
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
if (!map.count) return false;
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
Entry* prev = map.table[i];
|
||||
Entry* e = prev;
|
||||
while (e)
|
||||
{
|
||||
Entry *next = e.next;
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
map.count--;
|
||||
if (prev == e)
|
||||
{
|
||||
map.table[i] = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
map.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
|
||||
{
|
||||
Entry *e = map.table[bucket_index];
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
|
||||
map.table[bucket_index] = entry;
|
||||
map.count++;
|
||||
}
|
||||
|
||||
fn void HashMap.free_entry(&self, Entry *entry) @local
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
allocator::free(self.allocator, entry.key);
|
||||
$endif
|
||||
self.free_internal(entry);
|
||||
}
|
||||
|
||||
|
||||
struct HashMapIterator
|
||||
{
|
||||
HashMap* map;
|
||||
int top_index;
|
||||
int index;
|
||||
Entry* current_entry;
|
||||
}
|
||||
|
||||
distinct HashMapValueIterator = HashMapIterator;
|
||||
distinct HashMapKeyIterator = HashMapIterator;
|
||||
|
||||
|
||||
<*
|
||||
@require idx < self.map.count
|
||||
*>
|
||||
fn Entry HashMapIterator.get(&self, usz idx) @operator([])
|
||||
{
|
||||
if (idx < self.index)
|
||||
{
|
||||
self.top_index = 0;
|
||||
self.current_entry = null;
|
||||
self.index = -1;
|
||||
}
|
||||
while (self.index != idx)
|
||||
{
|
||||
if (self.current_entry)
|
||||
{
|
||||
self.current_entry = self.current_entry.next;
|
||||
if (self.current_entry) self.index++;
|
||||
continue;
|
||||
}
|
||||
self.current_entry = self.map.table[self.top_index++];
|
||||
if (self.current_entry) self.index++;
|
||||
}
|
||||
return *self.current_entry;
|
||||
}
|
||||
|
||||
fn Value HashMapValueIterator.get(&self, usz idx) @operator([])
|
||||
{
|
||||
return ((HashMapIterator*)self).get(idx).value;
|
||||
}
|
||||
|
||||
fn Key HashMapKeyIterator.get(&self, usz idx) @operator([])
|
||||
{
|
||||
return ((HashMapIterator*)self).get(idx).key;
|
||||
}
|
||||
|
||||
fn usz HashMapValueIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz HashMapKeyIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz HashMapIterator.len(self) @operator(len) => self.map.count;
|
||||
@@ -1,312 +1,343 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::linkedlist<Type>;
|
||||
module std::collections::linkedlist(<Type>);
|
||||
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
|
||||
struct Node @private
|
||||
{
|
||||
Node *next;
|
||||
Node *prev;
|
||||
Type value;
|
||||
Node *next;
|
||||
Node *prev;
|
||||
Type value;
|
||||
}
|
||||
|
||||
struct LinkedList
|
||||
{
|
||||
Allocator *allocator;
|
||||
usz size;
|
||||
Node *_first;
|
||||
Node *_last;
|
||||
Allocator allocator;
|
||||
usz size;
|
||||
Node *_first;
|
||||
Node *_last;
|
||||
}
|
||||
|
||||
fn void LinkedList.push(LinkedList* list, Type value)
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
@return "the initialized list"
|
||||
*>
|
||||
fn LinkedList* LinkedList.init(&self, Allocator allocator)
|
||||
{
|
||||
list.link_first(value);
|
||||
*self = { .allocator = allocator };
|
||||
return self;
|
||||
}
|
||||
|
||||
fn void LinkedList.push_last(LinkedList* list, Type value)
|
||||
<*
|
||||
@return "the initialized list"
|
||||
*>
|
||||
fn LinkedList* LinkedList.new_init(&self)
|
||||
{
|
||||
list.link_last(value);
|
||||
return self.init(allocator::heap()) @inline;
|
||||
}
|
||||
|
||||
fn void LinkedList.init(LinkedList* list, Allocator* using = mem::heap())
|
||||
|
||||
fn LinkedList* LinkedList.temp_init(&self)
|
||||
{
|
||||
*list = { .allocator = using };
|
||||
return self.init(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
fn void LinkedList.tinit(LinkedList* list) => list.init(mem::temp()) @inline;
|
||||
|
||||
/**
|
||||
* @require list.allocator
|
||||
**/
|
||||
macro void LinkedList.@free_node(LinkedList &list, Node* node) @private
|
||||
<*
|
||||
@require self.allocator
|
||||
*>
|
||||
macro void LinkedList.free_node(&self, Node* node) @private
|
||||
{
|
||||
list.allocator.free(node)!!;
|
||||
}
|
||||
macro Node* LinkedList.@alloc_node(LinkedList &list) @private
|
||||
{
|
||||
if (!list.allocator) list.allocator = mem::heap();
|
||||
return malloc(Node, .using = list.allocator);
|
||||
allocator::free(self.allocator, node);
|
||||
}
|
||||
|
||||
fn void LinkedList.link_first(LinkedList* list, Type value) @private
|
||||
macro Node* LinkedList.alloc_node(&self) @private
|
||||
{
|
||||
Node *first = list._first;
|
||||
Node *new_node = list.@alloc_node();
|
||||
*new_node = { .next = first, .value = value };
|
||||
list._first = new_node;
|
||||
if (!first)
|
||||
{
|
||||
list._last = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
first.prev = new_node;
|
||||
}
|
||||
list.size++;
|
||||
if (!self.allocator) self.allocator = allocator::heap();
|
||||
return allocator::alloc(self.allocator, Node);
|
||||
}
|
||||
|
||||
fn void LinkedList.link_last(LinkedList* list, Type value) @private
|
||||
fn void LinkedList.push_front(&self, Type value)
|
||||
{
|
||||
Node *last = list._last;
|
||||
Node *new_node = list.@alloc_node();
|
||||
*new_node = { .prev = last, .value = value };
|
||||
list._last = new_node;
|
||||
if (!last)
|
||||
{
|
||||
list._first = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
last.next = new_node;
|
||||
}
|
||||
list.size++;
|
||||
}
|
||||
|
||||
fn Type! peek(LinkedList* list) => list.first() @inline;
|
||||
fn Type! peek_last(LinkedList* list) => list.last() @inline;
|
||||
|
||||
fn Type! LinkedList.first(LinkedList *list)
|
||||
{
|
||||
if (!list._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return list._first.value;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.last(LinkedList* list)
|
||||
{
|
||||
if (!list._last) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return list._last.value;
|
||||
}
|
||||
|
||||
fn void LinkedList.free(LinkedList* list) => list.clear() @inline;
|
||||
|
||||
fn void LinkedList.clear(LinkedList* list)
|
||||
{
|
||||
for (Node* node = list._first; node != null;)
|
||||
{
|
||||
Node* next = node.next;
|
||||
list.@free_node(node);
|
||||
node = next;
|
||||
}
|
||||
list._first = null;
|
||||
list._last = null;
|
||||
list.size = 0;
|
||||
}
|
||||
|
||||
fn usz LinkedList.len(LinkedList* list) @inline => list.size;
|
||||
|
||||
/**
|
||||
* @require index < list.size
|
||||
**/
|
||||
macro Node* LinkedList.node_at_index(LinkedList* list, usz index)
|
||||
{
|
||||
if (index * 2 >= list.size)
|
||||
Node *first = self._first;
|
||||
Node *new_node = self.alloc_node();
|
||||
*new_node = { .next = first, .value = value };
|
||||
self._first = new_node;
|
||||
if (!first)
|
||||
{
|
||||
Node* node = list._last;
|
||||
index = list.size - index - 1;
|
||||
while (index--) node = node.prev;
|
||||
return node;
|
||||
self._last = new_node;
|
||||
}
|
||||
Node* node = list._first;
|
||||
while (index--) node = node.next;
|
||||
return node;
|
||||
}
|
||||
/**
|
||||
* @require index < list.size
|
||||
**/
|
||||
fn Type LinkedList.get(LinkedList* list, usz index)
|
||||
{
|
||||
return list.node_at_index(index).value;
|
||||
else
|
||||
{
|
||||
first.prev = new_node;
|
||||
}
|
||||
self.size++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < list.size
|
||||
**/
|
||||
fn void LinkedList.set(LinkedList* list, usz index, Type element)
|
||||
fn void LinkedList.push(&self, Type value)
|
||||
{
|
||||
list.node_at_index(index).value = element;
|
||||
Node *last = self._last;
|
||||
Node *new_node = self.alloc_node();
|
||||
*new_node = { .prev = last, .value = value };
|
||||
self._last = new_node;
|
||||
if (!last)
|
||||
{
|
||||
self._first = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
last.next = new_node;
|
||||
}
|
||||
self.size++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < list.size
|
||||
**/
|
||||
fn void LinkedList.remove(LinkedList* list, usz index)
|
||||
fn Type! LinkedList.peek(&self) => self.first() @inline;
|
||||
fn Type! LinkedList.peek_last(&self) => self.last() @inline;
|
||||
|
||||
fn Type! LinkedList.first(&self)
|
||||
{
|
||||
list.unlink(list.node_at_index(index));
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index <= list.size
|
||||
**/
|
||||
fn void LinkedList.insert(LinkedList* list, usz index, Type element)
|
||||
fn Type! LinkedList.last(&self)
|
||||
{
|
||||
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self._last.value;
|
||||
}
|
||||
|
||||
fn void LinkedList.free(&self) => self.clear() @inline;
|
||||
|
||||
fn void LinkedList.clear(&self)
|
||||
{
|
||||
for (Node* node = self._first; node != null;)
|
||||
{
|
||||
Node* next = node.next;
|
||||
self.free_node(node);
|
||||
node = next;
|
||||
}
|
||||
self._first = null;
|
||||
self._last = null;
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
fn usz LinkedList.len(&self) @inline => self.size;
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
macro Node* LinkedList.node_at_index(&self, usz index)
|
||||
{
|
||||
if (index * 2 >= self.size)
|
||||
{
|
||||
Node* node = self._last;
|
||||
index = self.size - index - 1;
|
||||
while (index--) node = node.prev;
|
||||
return node;
|
||||
}
|
||||
Node* node = self._first;
|
||||
while (index--) node = node.next;
|
||||
return node;
|
||||
}
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn Type LinkedList.get(&self, usz index)
|
||||
{
|
||||
return self.node_at_index(index).value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void LinkedList.set(&self, usz index, Type element)
|
||||
{
|
||||
self.node_at_index(index).value = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void LinkedList.remove_at(&self, usz index)
|
||||
{
|
||||
self.unlink(self.node_at_index(index));
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size
|
||||
*>
|
||||
fn void LinkedList.insert_at(&self, usz index, Type element)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
list.push(element);
|
||||
case list.size:
|
||||
list.push_last(element);
|
||||
self.push_front(element);
|
||||
case self.size:
|
||||
self.push(element);
|
||||
default:
|
||||
list.link_before(list.node_at_index(index), element);
|
||||
self.link_before(self.node_at_index(index), element);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @require succ != null
|
||||
**/
|
||||
fn void LinkedList.link_before(LinkedList *list, Node *succ, Type value) @private
|
||||
<*
|
||||
@require succ != null
|
||||
*>
|
||||
fn void LinkedList.link_before(&self, Node *succ, Type value) @private
|
||||
{
|
||||
Node* pred = succ.prev;
|
||||
Node* new_node = malloc(Node);
|
||||
*new_node = { .prev = pred, .next = succ, .value = value };
|
||||
succ.prev = new_node;
|
||||
if (!pred)
|
||||
{
|
||||
list._first = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
pred.next = new_node;
|
||||
}
|
||||
list.size++;
|
||||
Node* pred = succ.prev;
|
||||
Node* new_node = self.alloc_node();
|
||||
*new_node = { .prev = pred, .next = succ, .value = value };
|
||||
succ.prev = new_node;
|
||||
if (!pred)
|
||||
{
|
||||
self._first = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
pred.next = new_node;
|
||||
}
|
||||
self.size++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require list && list._first
|
||||
**/
|
||||
fn void LinkedList.unlink_first(LinkedList* list) @private
|
||||
<*
|
||||
@require self._first
|
||||
*>
|
||||
fn void LinkedList.unlink_first(&self) @private
|
||||
{
|
||||
Node* f = list._first;
|
||||
Node* next = f.next;
|
||||
list.@free_node(f);
|
||||
list._first = next;
|
||||
if (!next)
|
||||
{
|
||||
list._last = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
next.prev = null;
|
||||
}
|
||||
list.size--;
|
||||
Node* f = self._first;
|
||||
Node* next = f.next;
|
||||
self.free_node(f);
|
||||
self._first = next;
|
||||
if (!next)
|
||||
{
|
||||
self._last = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
next.prev = null;
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
|
||||
fn bool LinkedList.remove_value(LinkedList* list, Type t)
|
||||
fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = list._first; node != null; node = node.next)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
list.unlink(node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
usz start = self.size;
|
||||
Node* node = self._first;
|
||||
while (node)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case equals(node.value, t):
|
||||
Node* next = node.next;
|
||||
self.unlink(node);
|
||||
node = next;
|
||||
default:
|
||||
node = node.next;
|
||||
}
|
||||
}
|
||||
return start - self.size;
|
||||
}
|
||||
|
||||
fn bool LinkedList.remove_last_value(LinkedList* list, Type t)
|
||||
fn Type! LinkedList.pop(&self)
|
||||
{
|
||||
for (Node* node = list._last; node != null; node = node.prev)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
list.unlink(node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.unlink_last();
|
||||
return self._last.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list
|
||||
**/
|
||||
fn Type! LinkedList.pop(LinkedList* list)
|
||||
fn bool LinkedList.is_empty(&self)
|
||||
{
|
||||
if (!list._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer list.unlink_first();
|
||||
return list._first.value;
|
||||
return !self._first;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list
|
||||
**/
|
||||
fn void! LinkedList.remove_last(LinkedList* list)
|
||||
fn Type! LinkedList.pop_front(&self)
|
||||
{
|
||||
if (!list._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
list.unlink_last();
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.unlink_first();
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list
|
||||
**/
|
||||
fn void! LinkedList.remove_first(LinkedList* list)
|
||||
fn void! LinkedList.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!list._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
list.unlink_first();
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.unlink_last();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list
|
||||
* @require list._last
|
||||
**/
|
||||
fn void LinkedList.unlink_last(LinkedList *list) @inline @private
|
||||
fn void! LinkedList.remove_first(&self) @maydiscard
|
||||
{
|
||||
Node* l = list._last;
|
||||
Node* prev = l.prev;
|
||||
list._last = prev;
|
||||
list.@free_node(l);
|
||||
if (!prev)
|
||||
{
|
||||
list._first = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = null;
|
||||
}
|
||||
list.size--;
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.unlink_first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @require list != null, x != null
|
||||
**/
|
||||
fn void LinkedList.unlink(LinkedList* list, Node* x) @private
|
||||
|
||||
fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
Node* next = x.next;
|
||||
Node* prev = x.prev;
|
||||
if (!prev)
|
||||
{
|
||||
list._first = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
if (!next)
|
||||
{
|
||||
list._last = prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
next.prev = prev;
|
||||
}
|
||||
list.@free_node(x);
|
||||
list.size--;
|
||||
for (Node* node = self._first; node != null; node = node.next)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
self.unlink(node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = self._last; node != null; node = node.prev)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
self.unlink(node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
<*
|
||||
@require self._last
|
||||
*>
|
||||
fn void LinkedList.unlink_last(&self) @inline @private
|
||||
{
|
||||
Node* l = self._last;
|
||||
Node* prev = l.prev;
|
||||
self._last = prev;
|
||||
self.free_node(l);
|
||||
if (!prev)
|
||||
{
|
||||
self._first = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = null;
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
|
||||
<*
|
||||
@require x != null
|
||||
*>
|
||||
fn void LinkedList.unlink(&self, Node* x) @private
|
||||
{
|
||||
Node* next = x.next;
|
||||
Node* prev = x.prev;
|
||||
if (!prev)
|
||||
{
|
||||
self._first = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
if (!next)
|
||||
{
|
||||
self._last = prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
next.prev = prev;
|
||||
}
|
||||
self.free_node(x);
|
||||
self.size--;
|
||||
}
|
||||
|
||||
@@ -1,388 +1,578 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::list<Type>;
|
||||
import std::math;
|
||||
module std::collections::list(<Type>);
|
||||
import std::io, std::math, std::collections::list_common;
|
||||
|
||||
def ElementPredicate = fn bool(Type *type);
|
||||
def ElementTest = fn bool(Type *type, any context);
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
|
||||
|
||||
struct List
|
||||
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct List (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator *allocator;
|
||||
Type *entries;
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
Type *entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require using != null "A valid allocator must be provided"
|
||||
**/
|
||||
fn void List.init(List* list, usz initial_capacity = 16, Allocator* using = mem::heap())
|
||||
<*
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
*>
|
||||
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
|
||||
{
|
||||
list.allocator = using;
|
||||
list.size = 0;
|
||||
if (initial_capacity > 0)
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
self.capacity = 0;
|
||||
self.entries = null;
|
||||
self.reserve(initial_capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn List* List.temp_init(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.new_init(initial_capacity, allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a new list with an array.
|
||||
|
||||
@param [in] values `The values to initialize the list with.`
|
||||
@require self.size == 0 "The List must be empty"
|
||||
*>
|
||||
fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = allocator::heap())
|
||||
{
|
||||
self.new_init(values.len, allocator) @inline;
|
||||
self.add_array(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a temporary list with an array.
|
||||
|
||||
@param [in] values `The values to initialize the list with.`
|
||||
@require self.size == 0 "The List must be empty"
|
||||
*>
|
||||
fn List* List.temp_init_with_array(&self, Type[] values)
|
||||
{
|
||||
self.temp_init(values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.capacity == 0 "The List must not be allocated"
|
||||
*>
|
||||
fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allocator::heap())
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.capacity = types.len;
|
||||
self.entries = types.ptr;
|
||||
self.set_size(types.len);
|
||||
}
|
||||
|
||||
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
initial_capacity = math::next_power_of_2(initial_capacity);
|
||||
list.entries = malloc_aligned(Type, initial_capacity, .alignment = Type[1].alignof, .using = using)!!;
|
||||
case 0:
|
||||
return formatter.print("[]")!;
|
||||
case 1:
|
||||
return formatter.printf("[%s]", self.entries[0])!;
|
||||
default:
|
||||
usz n = formatter.print("[")!;
|
||||
foreach (i, element : self.entries[:self.size])
|
||||
{
|
||||
if (i != 0) formatter.print(", ")!;
|
||||
n += formatter.printf("%s", element)!;
|
||||
}
|
||||
n += formatter.print("]")!;
|
||||
return n;
|
||||
}
|
||||
else
|
||||
{
|
||||
list.entries = null;
|
||||
}
|
||||
list.capacity = initial_capacity;
|
||||
}
|
||||
|
||||
fn void List.tinit(List* list, usz initial_capacity = 16)
|
||||
fn String List.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
|
||||
{
|
||||
list.init(initial_capacity, mem::temp()) @inline;
|
||||
return string::format("%s", *self, allocator: allocator);
|
||||
}
|
||||
|
||||
fn void List.push(List* list, Type element) @inline
|
||||
fn String List.to_tstring(&self)
|
||||
{
|
||||
list.append(element);
|
||||
return string::tformat("%s", *self);
|
||||
}
|
||||
|
||||
fn void List.append(List* list, Type element)
|
||||
fn void List.push(&self, Type element) @inline
|
||||
{
|
||||
list.ensure_capacity();
|
||||
list.entries[list.size++] = element;
|
||||
self.reserve(1);
|
||||
self.entries[self.set_size(self.size + 1)] = element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require list.size > 0
|
||||
*/
|
||||
fn Type List.pop(List* list)
|
||||
fn Type! List.pop(&self)
|
||||
{
|
||||
return list.entries[--list.size];
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.set_size(self.size - 1);
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
fn void List.clear(List* list)
|
||||
fn void List.clear(&self)
|
||||
{
|
||||
list.size = 0;
|
||||
self.set_size(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require list.size > 0
|
||||
*/
|
||||
fn Type List.pop_first(List* list)
|
||||
fn Type! List.pop_first(&self)
|
||||
{
|
||||
Type value = list.entries[0];
|
||||
list.remove_at(0);
|
||||
return value;
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
fn void List.remove_at(List* list, usz index)
|
||||
<*
|
||||
@require index < self.size `Removed element out of bounds`
|
||||
*>
|
||||
fn void List.remove_at(&self, usz index)
|
||||
{
|
||||
for (usz i = index + 1; i < list.size; i++)
|
||||
{
|
||||
list.entries[i - 1] = list.entries[i];
|
||||
}
|
||||
list.size--;
|
||||
self.set_size(self.size - 1);
|
||||
if (!self.size || index == self.size) return;
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
}
|
||||
|
||||
fn void List.add_all(List* list, List* other_list)
|
||||
fn void List.add_all(&self, List* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
list.reserve(other_list.size);
|
||||
self.reserve(other_list.size);
|
||||
usz index = self.set_size(self.size + other_list.size);
|
||||
foreach (&value : other_list)
|
||||
{
|
||||
list.entries[list.size++] = *value;
|
||||
self.entries[index++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn Type[] List.to_array(List* list, Allocator* using = mem::heap())
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
fn Type[] List.to_new_aligned_array(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!list.size) return Type[] {};
|
||||
Type[] result = malloc(Type, list.size, .using = using);
|
||||
result[..] = list.entries[:list.size];
|
||||
return result;
|
||||
return list_common::list_to_new_aligned_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the elements in a list.
|
||||
*
|
||||
* @param [&inout] list "The list to reverse"
|
||||
**/
|
||||
fn void List.reverse(List* list)
|
||||
<*
|
||||
@require !type_is_overaligned() : "This function is not available on overaligned types"
|
||||
*>
|
||||
macro Type[] List.to_new_array(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (list.size < 2) return;
|
||||
usz half = list.size / 2U;
|
||||
usz end = list.size - 1;
|
||||
for (usz i = 0; i < half; i++)
|
||||
{
|
||||
@swap(list.entries[i], list.entries[end - i]);
|
||||
}
|
||||
return list_common::list_to_new_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
fn Type[] List.array_view(List* list)
|
||||
fn Type[] List.to_tarray(&self)
|
||||
{
|
||||
return list.entries[:list.size];
|
||||
$if type_is_overaligned():
|
||||
return self.to_new_aligned_array(allocator::temp());
|
||||
$else
|
||||
return self.to_new_array(allocator::temp());
|
||||
$endif;
|
||||
}
|
||||
|
||||
fn void List.add_array(List* list, Type[] array)
|
||||
<*
|
||||
Reverse the elements in a list.
|
||||
*>
|
||||
fn void List.reverse(&self)
|
||||
{
|
||||
list_common::list_reverse(self);
|
||||
}
|
||||
|
||||
fn Type[] List.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Add the values of an array to this list.
|
||||
|
||||
@param [in] array
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void List.add_array(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return;
|
||||
list.reserve(array.len);
|
||||
foreach (&value : array)
|
||||
self.reserve(array.len);
|
||||
usz index = self.set_size(self.size + array.len);
|
||||
self.entries[index : array.len] = array[..];
|
||||
}
|
||||
|
||||
fn void List.push_front(&self, Type type) @inline
|
||||
{
|
||||
self.insert_at(0, type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size `Insert was out of bounds`
|
||||
*>
|
||||
fn void List.insert_at(&self, usz index, Type type)
|
||||
{
|
||||
self.reserve(1);
|
||||
for (usz i = self.size; i > index; i--)
|
||||
{
|
||||
list.entries[list.size++] = *value;
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.set_size(self.size + 1);
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
fn void List.push_front(List* list, Type type) @inline
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void List.set_at(&self, usz index, Type type)
|
||||
{
|
||||
list.insert_at(0, type);
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
fn void List.insert_at(List* list, usz index, Type type)
|
||||
fn void! List.remove_last(&self) @maydiscard
|
||||
{
|
||||
list.ensure_capacity();
|
||||
for (usz i = list.size; i > index; i--)
|
||||
{
|
||||
list.entries[i] = list.entries[i - 1];
|
||||
}
|
||||
list.size++;
|
||||
list.entries[index] = type;
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.set_size(self.size - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < list.size
|
||||
**/
|
||||
fn void List.set_at(List* list, usz index, Type type)
|
||||
fn void! List.remove_first(&self) @maydiscard
|
||||
{
|
||||
list.entries[index] = type;
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn void List.remove_last(List* list)
|
||||
fn Type! List.first(&self)
|
||||
{
|
||||
list.size--;
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
fn void List.remove_first(List* list)
|
||||
fn Type! List.last(&self)
|
||||
{
|
||||
list.remove_at(0);
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
fn Type* List.first(List* list)
|
||||
fn bool List.is_empty(&self) @inline
|
||||
{
|
||||
return list.size ? &list.entries[0] : null;
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
fn Type* List.last(List* list)
|
||||
fn usz List.byte_size(&self) @inline
|
||||
{
|
||||
return list.size ? &list.entries[list.size - 1] : null;
|
||||
return Type.sizeof * self.size;
|
||||
}
|
||||
|
||||
fn bool List.is_empty(List* list)
|
||||
fn usz List.len(&self) @operator(len) @inline
|
||||
{
|
||||
return !list.size;
|
||||
return self.size;
|
||||
}
|
||||
|
||||
fn usz List.len(List* list) @operator(len)
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
*>
|
||||
fn Type List.get(&self, usz index) @inline
|
||||
{
|
||||
return list.size;
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn Type List.get(List* list, usz index)
|
||||
fn void List.free(&self)
|
||||
{
|
||||
return list.entries[index];
|
||||
if (!self.allocator || !self.capacity) return;
|
||||
|
||||
self.pre_free(); // Remove sanitizer annotation
|
||||
|
||||
$if type_is_overaligned():
|
||||
allocator::free_aligned(self.allocator, self.entries);
|
||||
$else
|
||||
allocator::free(self.allocator, self.entries);
|
||||
$endif;
|
||||
self.capacity = 0;
|
||||
self.size = 0;
|
||||
self.entries = null;
|
||||
}
|
||||
|
||||
fn void List.free(List* list)
|
||||
<*
|
||||
@require i < self.size && j < self.size `Access out of bounds`
|
||||
*>
|
||||
fn void List.swap(&self, usz i, usz j)
|
||||
{
|
||||
if (!list.allocator) return;
|
||||
free_aligned(list.entries, .using = list.allocator);
|
||||
list.capacity = 0;
|
||||
list.size = 0;
|
||||
list.entries = null;
|
||||
@swap(self.entries[i], self.entries[j]);
|
||||
}
|
||||
|
||||
fn void List.swap(List* list, usz i, usz j)
|
||||
<*
|
||||
@param filter "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz List.remove_if(&self, ElementPredicate filter)
|
||||
{
|
||||
@swap(list.entries[i], list.entries[j]);
|
||||
return list_common::list_remove_if(self, filter, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list "The list to remove elements from"
|
||||
* @param filter "The function to determine if it should be removed or not"
|
||||
* @return "the number of deleted elements"
|
||||
**/
|
||||
fn usz List.remove_if(List* list, ElementPredicate filter)
|
||||
<*
|
||||
@param selection "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz List.retain_if(&self, ElementPredicate selection)
|
||||
{
|
||||
usz size = list.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (filter(&list.entries[i - 1])) continue;
|
||||
for (usz j = i; j < size; j++)
|
||||
{
|
||||
list.entries[j - 1] = list.entries[j];
|
||||
}
|
||||
list.size--;
|
||||
return list_common::list_remove_if(self, selection, true);
|
||||
}
|
||||
|
||||
fn usz List.remove_using_test(&self, ElementTest filter, any context)
|
||||
{
|
||||
usz old_size = self.size;
|
||||
defer {
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
return size - list.size;
|
||||
return list_common::list_remove_using_test(self, filter, false, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list "The list to remove elements from"
|
||||
* @param selection "The function to determine if it should be kept or not"
|
||||
* @return "the number of deleted elements"
|
||||
**/
|
||||
fn usz List.retain_if(List* list, ElementPredicate selection)
|
||||
|
||||
|
||||
fn usz List.retain_using_test(&self, ElementTest filter, any context)
|
||||
{
|
||||
usz size = list.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (!selection(&list.entries[i - 1])) continue;
|
||||
for (usz j = i; j < size; j++)
|
||||
{
|
||||
list.entries[j - 1] = list.entries[j];
|
||||
}
|
||||
list.size--;
|
||||
usz old_size = self.size;
|
||||
defer {
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
return size - list.size;
|
||||
return list_common::list_remove_using_test(self, filter, true, context);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reserve at least min_capacity
|
||||
**/
|
||||
fn void List.reserve(List* list, usz min_capacity)
|
||||
fn void List.ensure_capacity(&self, usz min_capacity) @local
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (list.capacity >= min_capacity) return;
|
||||
if (!list.allocator) list.allocator = mem::heap();
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = allocator::heap();
|
||||
|
||||
self.pre_free(); // Remove sanitizer annotation
|
||||
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
list.entries = realloc_aligned(list.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof, .using = list.allocator) ?? null;
|
||||
list.capacity = min_capacity;
|
||||
$if type_is_overaligned():
|
||||
self.entries = allocator::realloc_aligned(self.allocator, self.entries, Type.sizeof * min_capacity, alignment: Type[1].alignof)!!;
|
||||
$else
|
||||
self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity);
|
||||
$endif;
|
||||
self.capacity = min_capacity;
|
||||
|
||||
self.post_alloc(); // Add sanitizer annotation
|
||||
}
|
||||
|
||||
macro Type List.@item_at(List &list, usz index) @operator([])
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
*>
|
||||
macro Type List.@item_at(&self, usz index) @operator([])
|
||||
{
|
||||
return list.entries[index];
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn Type* List.get_ref(List* list, usz index) @operator(&[]) @inline
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
*>
|
||||
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
|
||||
{
|
||||
return &list.entries[index];
|
||||
return &self.entries[index];
|
||||
}
|
||||
|
||||
fn void List.ensure_capacity(List* list, usz added = 1) @inline @private
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
*>
|
||||
fn void List.set(&self, usz index, Type value) @operator([]=)
|
||||
{
|
||||
usz new_size = list.size + added;
|
||||
if (list.capacity > new_size) return;
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
fn void List.reserve(&self, usz added)
|
||||
{
|
||||
usz new_size = self.size + added;
|
||||
if (self.capacity >= new_size) return;
|
||||
|
||||
assert(new_size < usz.max / 2U);
|
||||
usz new_capacity = list.capacity ? 2U * list.capacity : 16U;
|
||||
while (new_size >= new_capacity) new_capacity *= 2U;
|
||||
list.reserve(new_capacity);
|
||||
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
|
||||
while (new_capacity < new_size) new_capacity *= 2U;
|
||||
self.ensure_capacity(new_capacity);
|
||||
}
|
||||
|
||||
fn void List._update_size_change(&self,usz old_size, usz new_size)
|
||||
{
|
||||
if (old_size == new_size) return;
|
||||
sanitizer::annotate_contiguous_container(self.entries,
|
||||
&self.entries[self.capacity],
|
||||
&self.entries[old_size],
|
||||
&self.entries[new_size]);
|
||||
}
|
||||
<*
|
||||
@require new_size == 0 || self.capacity != 0
|
||||
*>
|
||||
fn usz List.set_size(&self, usz new_size) @inline @private
|
||||
{
|
||||
usz old_size = self.size;
|
||||
self._update_size_change(old_size, new_size);
|
||||
self.size = new_size;
|
||||
return old_size;
|
||||
}
|
||||
|
||||
macro void List.pre_free(&self) @private
|
||||
{
|
||||
if (!self.capacity) return;
|
||||
self._update_size_change(self.size, self.capacity);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.capacity
|
||||
*>
|
||||
macro void List.post_alloc(&self) @private
|
||||
{
|
||||
self._update_size_change(self.capacity, self.size);
|
||||
}
|
||||
|
||||
// Functions for equatable types
|
||||
|
||||
$if types::is_equatable_type(Type):
|
||||
|
||||
fn usz! List.index_of(List* list, Type type)
|
||||
fn usz! List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : list)
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (v == type) return i;
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn usz! List.rindex_of(List* list, Type type)
|
||||
fn usz! List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach_r (i, v : list)
|
||||
foreach_r (i, v : self)
|
||||
{
|
||||
if (v == type) return i;
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn bool List.equals(List* list, List other_list)
|
||||
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (list.size != other_list.size) return false;
|
||||
foreach (i, v : list)
|
||||
if (self.size != other_list.size) return false;
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (v != other_list.entries[i]) return false;
|
||||
if (!equals(v, other_list.entries[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for presence of a value in a list.
|
||||
*
|
||||
* @param [&in] list "the list to find elements in"
|
||||
* @param value "The value to search for"
|
||||
* @return "True if the value is found, false otherwise"
|
||||
**/
|
||||
fn bool List.contains(List* list, Type value)
|
||||
<*
|
||||
Check for presence of a value in a list.
|
||||
|
||||
@param [&in] self "the list to find elements in"
|
||||
@param value "The value to search for"
|
||||
@return "True if the value is found, false otherwise"
|
||||
*>
|
||||
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : list)
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (v == value) return true;
|
||||
if (equals(v, value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param [&inout] list "The list to remove elements from"
|
||||
* @param value "The value to remove"
|
||||
* @return "the number of deleted elements."
|
||||
**/
|
||||
fn usz List.remove(List* list, Type value)
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
usz size = list.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (list.entries[i - 1] != value) continue;
|
||||
for (usz j = i; j < size; j++)
|
||||
{
|
||||
list.entries[j - 1] = list.entries[j];
|
||||
}
|
||||
list.size--;
|
||||
}
|
||||
return size - list.size;
|
||||
return @ok(self.remove_at(self.rindex_of(value)));
|
||||
}
|
||||
|
||||
fn void List.remove_all(List* list, List* other_list)
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
return @ok(self.remove_at(self.index_of(value)));
|
||||
}
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "the number of deleted elements."
|
||||
*>
|
||||
fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
usz old_size = self.size;
|
||||
defer {
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
return list_common::list_remove_item(self, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
foreach (v : other_list) list.remove(v);
|
||||
usz old_size = self.size;
|
||||
defer {
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
foreach (v : other_list) self.remove_item(v);
|
||||
}
|
||||
|
||||
|
||||
$endif
|
||||
|
||||
$if Type.kindof == POINTER:
|
||||
|
||||
/**
|
||||
* @param [&in] list
|
||||
* @return "The number non-null values in the list"
|
||||
**/
|
||||
fn usz List.compact_count(List* list)
|
||||
<*
|
||||
@param [&in] self
|
||||
@return "The number non-null values in the list"
|
||||
*>
|
||||
fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
usz vals = 0;
|
||||
foreach (v : list) if (v) vals++;
|
||||
foreach (v : self) if (v) vals++;
|
||||
return vals;
|
||||
}
|
||||
|
||||
fn usz List.compact(List* list)
|
||||
fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
usz size = list.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (list.entries[i - 1]) continue;
|
||||
for (usz j = i; j < size; j++)
|
||||
{
|
||||
list.entries[j - 1] = list.entries[j];
|
||||
}
|
||||
list.size--;
|
||||
usz old_size = self.size;
|
||||
defer {
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
return size - list.size;
|
||||
return list_common::list_compact(self);
|
||||
}
|
||||
|
||||
$endif
|
||||
// --> Deprecated
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_last_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
|
||||
{
|
||||
return self.remove_last_item(value) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_first_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
|
||||
{
|
||||
return self.remove_first_item(value) @inline;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "the number of deleted elements."
|
||||
*>
|
||||
fn usz List.remove_all_matches(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
|
||||
{
|
||||
return self.remove_item(value) @inline;
|
||||
}
|
||||
|
||||
112
lib/std/collections/list_common.c3
Normal file
112
lib/std/collections/list_common.c3
Normal file
@@ -0,0 +1,112 @@
|
||||
module std::collections::list_common;
|
||||
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
macro list_to_new_aligned_array($Type, self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return $Type[] {};
|
||||
$Type[] result = allocator::alloc_array_aligned(allocator, $Type, self.size);
|
||||
result[..] = self.entries[:self.size];
|
||||
return result;
|
||||
}
|
||||
|
||||
macro list_to_new_array($Type, self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return $Type[] {};
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, self.size);
|
||||
result[..] = self.entries[:self.size];
|
||||
return result;
|
||||
}
|
||||
|
||||
macro void list_reverse(self)
|
||||
{
|
||||
if (self.size < 2) return;
|
||||
usz half = self.size / 2U;
|
||||
usz end = self.size - 1;
|
||||
for (usz i = 0; i < half; i++)
|
||||
{
|
||||
@swap(self.entries[i], self.entries[end - i]);
|
||||
}
|
||||
}
|
||||
|
||||
macro usz list_remove_using_test(self, filter, bool $invert, ctx)
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
macro usz list_compact(self)
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (self.entries[i - 1]) continue;
|
||||
for (usz j = i; j < size; j++)
|
||||
{
|
||||
self.entries[j - 1] = self.entries[j];
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
macro usz list_remove_item(self, value)
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (!equals(self.entries[i - 1], value)) continue;
|
||||
for (usz j = i; j < self.size; j++)
|
||||
{
|
||||
self.entries[j - 1] = self.entries[j];
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
|
||||
macro usz list_remove_if(self, filter, bool $invert)
|
||||
{
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
// Find last index of item to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && !filter(&self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && filter(&self.entries[i - 1])) i--;
|
||||
$endif
|
||||
// Remove the items from this index up to the one not to be deleted.
|
||||
usz n = self.size - k;
|
||||
self.entries[i:n] = self.entries[k:n];
|
||||
self.size -= k - i;
|
||||
// Find last index of item not to be deleted.
|
||||
$if $invert:
|
||||
while (i > 0 && filter(&self.entries[i - 1])) i--;
|
||||
$else
|
||||
while (i > 0 && !filter(&self.entries[i - 1])) i--;
|
||||
$endif
|
||||
}
|
||||
return size - self.size;
|
||||
}
|
||||
@@ -1,80 +1,167 @@
|
||||
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::map<Key, Value>;
|
||||
module std::collections::map(<Key, Value>);
|
||||
import std::math;
|
||||
|
||||
const uint DEFAULT_INITIAL_CAPACITY = 16;
|
||||
const uint MAXIMUM_CAPACITY = 1u << 31;
|
||||
const float DEFAULT_LOAD_FACTOR = 0.75;
|
||||
const VALUE_IS_EQUATABLE = Value.is_eq;
|
||||
const bool COPY_KEYS = types::implements_copy(Key);
|
||||
|
||||
distinct Map = void*;
|
||||
|
||||
struct HashMap
|
||||
struct MapImpl
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator* allocator;
|
||||
Allocator allocator;
|
||||
uint count; // Number of elements
|
||||
uint threshold; // Resize limit
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require capacity > 0 "The capacity must be 1 or higher"
|
||||
* @require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
* @require !map.allocator "Map was already initialized"
|
||||
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
* @require using != null "The allocator must be non-null"
|
||||
**/
|
||||
fn void HashMap.init(HashMap* map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator* using = mem::heap())
|
||||
<*
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn Map new(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
map.allocator = using;
|
||||
map.load_factor = load_factor;
|
||||
map.threshold = (uint)(capacity * load_factor);
|
||||
map.table = calloc(Entry*, capacity, .using = using);
|
||||
MapImpl* map = allocator::alloc(allocator, MapImpl);
|
||||
_init(map, capacity, load_factor, allocator);
|
||||
return (Map)map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require capacity > 0 "The capacity must be 1 or higher"
|
||||
* @require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
* @require !map.allocator "Map was already initialized"
|
||||
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
**/
|
||||
fn void HashMap.tinit(HashMap* map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
<*
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn Map temp(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
map.init(capacity, load_factor, mem::temp());
|
||||
MapImpl* map = mem::temp_alloc(MapImpl);
|
||||
_init(map, capacity, load_factor, allocator::temp());
|
||||
return (Map)map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has this hash map been initialized yet?
|
||||
*
|
||||
* @param [&in] map "The hash map we are testing"
|
||||
* @return "Returns true if it has been initialized, false otherwise"
|
||||
**/
|
||||
fn bool HashMap.is_initialized(HashMap* map)
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro Map new_init_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return map.allocator != null;
|
||||
Map map = new(capacity, load_factor, allocator);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
map.set($vaarg[$i], $vaarg[$i+1]);
|
||||
$endfor
|
||||
return map;
|
||||
}
|
||||
|
||||
fn void HashMap.init_from_map(HashMap* map, HashMap* other_map, Allocator* using = mem::heap())
|
||||
<*
|
||||
@param [in] keys "Array of keys for the Map entries"
|
||||
@param [in] values "Array of values for the Map entries"
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require keys.len == values.len "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn Map new_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
map.init(other_map.table.len, other_map.load_factor, using);
|
||||
map.put_all_for_create(other_map);
|
||||
assert(keys.len == values.len);
|
||||
Map map = new(capacity, load_factor, allocator);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
map.set(keys[i], values[i]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
fn void HashMap.tinit_from_map(HashMap* map, HashMap* other_map)
|
||||
<*
|
||||
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro Map temp_new_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
map.init_from_map(other_map, mem::temp()) @inline;
|
||||
Map map = temp(capacity, load_factor);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
map.set($vaarg[$i], $vaarg[$i+1]);
|
||||
$endfor
|
||||
return map;
|
||||
}
|
||||
|
||||
fn bool HashMap.is_empty(HashMap* map) @inline
|
||||
<*
|
||||
@param [in] keys "The keys for the HashMap entries"
|
||||
@param [in] values "The values for the HashMap entries"
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require keys.len == values.len "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn Map temp_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return !map.count;
|
||||
assert(keys.len == values.len);
|
||||
Map map = temp(capacity, load_factor);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
map.set(keys[i], values[i]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
fn Value*! HashMap.get_ref(HashMap* map, Key key)
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn Map new_from_map(Map other_map, Allocator allocator = null)
|
||||
{
|
||||
if (!map.count) return SearchResult.MISSING?;
|
||||
MapImpl* other_map_impl = (MapImpl*)other_map;
|
||||
if (!other_map_impl)
|
||||
{
|
||||
if (allocator) return new(allocator: allocator);
|
||||
return null;
|
||||
}
|
||||
MapImpl* map = (MapImpl*)new(other_map_impl.table.len, other_map_impl.load_factor, allocator ?: allocator::heap());
|
||||
if (!other_map_impl.count) return (Map)map;
|
||||
foreach (Entry *e : other_map_impl.table)
|
||||
{
|
||||
while (e)
|
||||
{
|
||||
map._put_for_create(e.key, e.value);
|
||||
e = e.next;
|
||||
}
|
||||
}
|
||||
return (Map)map;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn Map temp_from_map(Map other_map)
|
||||
{
|
||||
return new_from_map(other_map, allocator::temp());
|
||||
}
|
||||
|
||||
fn bool Map.is_empty(map) @inline
|
||||
{
|
||||
return !map || !((MapImpl*)map).count;
|
||||
}
|
||||
|
||||
fn usz Map.len(map) @inline
|
||||
{
|
||||
return map ? ((MapImpl*)map).count : 0;
|
||||
}
|
||||
|
||||
fn Value*! Map.get_ref(self, Key key)
|
||||
{
|
||||
MapImpl *map = (MapImpl*)self;
|
||||
if (!map || !map.count) return SearchResult.MISSING?;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
@@ -83,23 +170,26 @@ fn Value*! HashMap.get_ref(HashMap* map, Key key)
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn Entry*! HashMap.get_entry(HashMap* map, Key key)
|
||||
fn Entry*! Map.get_entry(map, Key key)
|
||||
{
|
||||
if (!map.count) return SearchResult.MISSING?;
|
||||
MapImpl *map_impl = (MapImpl*)map;
|
||||
if (!map_impl || !map_impl.count) return SearchResult.MISSING?;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
for (Entry *e = map_impl.table[index_for(hash, map_impl.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value or update and
|
||||
**/
|
||||
macro Value HashMap.@get_or_set(HashMap* map, Key key, Value #expr)
|
||||
<*
|
||||
Get the value or update and
|
||||
@require $assignable(#expr, Value)
|
||||
*>
|
||||
macro Value Map.@get_or_set(&self, Key key, Value #expr)
|
||||
{
|
||||
if (!map.count)
|
||||
MapImpl *map = (MapImpl*)*self;
|
||||
if (!map || !map.count)
|
||||
{
|
||||
Value val = #expr;
|
||||
map.set(key, val);
|
||||
@@ -116,23 +206,27 @@ macro Value HashMap.@get_or_set(HashMap* map, Key key, Value #expr)
|
||||
return val;
|
||||
}
|
||||
|
||||
fn Value! HashMap.get(HashMap* map, Key key) @operator([])
|
||||
fn Value! Map.get(map, Key key) @operator([])
|
||||
{
|
||||
return *map.get_ref(key) @inline;
|
||||
}
|
||||
|
||||
fn bool HashMap.has_key(HashMap* map, Key key)
|
||||
fn bool Map.has_key(map, Key key)
|
||||
{
|
||||
return @ok(map.get_ref(key));
|
||||
}
|
||||
|
||||
fn bool HashMap.set(HashMap* map, Key key, Value value) @operator([]=)
|
||||
macro Value Map.set_value_return(&map, Key key, Value value) @operator([]=)
|
||||
{
|
||||
map.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
fn bool Map.set(&self, Key key, Value value)
|
||||
{
|
||||
// If the map isn't initialized, use the defaults to initialize it.
|
||||
if (!map.allocator)
|
||||
{
|
||||
map.init();
|
||||
}
|
||||
if (!*self) *self = new();
|
||||
MapImpl* map = (MapImpl*)*self;
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[index]; e != null; e = e.next)
|
||||
@@ -143,46 +237,57 @@ fn bool HashMap.set(HashMap* map, Key key, Value value) @operator([]=)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
map.add_entry(hash, key, value, index);
|
||||
map._add_entry(hash, key, value, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void! HashMap.remove(HashMap* map, Key key) @maydiscard
|
||||
fn void! Map.remove(map, Key key) @maydiscard
|
||||
{
|
||||
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
|
||||
if (!map || !((MapImpl*)map)._remove_entry_for_key(key)) return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn void HashMap.clear(HashMap* map)
|
||||
fn void Map.clear(self)
|
||||
{
|
||||
if (!map.count) return;
|
||||
MapImpl* map = (MapImpl*)self;
|
||||
if (!map || !map.count) return;
|
||||
foreach (Entry** &entry_ref : map.table)
|
||||
{
|
||||
Entry* entry = *entry_ref;
|
||||
if (!entry) continue;
|
||||
map.free_internal(entry);
|
||||
Entry *next = entry.next;
|
||||
while (next)
|
||||
{
|
||||
Entry *to_delete = next;
|
||||
next = next.next;
|
||||
map._free_entry(to_delete);
|
||||
}
|
||||
map._free_entry(entry);
|
||||
*entry_ref = null;
|
||||
}
|
||||
map.count = 0;
|
||||
}
|
||||
|
||||
fn void HashMap.free(HashMap* map)
|
||||
fn void Map.free(self)
|
||||
{
|
||||
if (!map.allocator) return;
|
||||
map.clear();
|
||||
map.free_internal(map.table.ptr);
|
||||
map.table = Entry*[] {};
|
||||
if (!self) return;
|
||||
MapImpl* map = (MapImpl*)self;
|
||||
self.clear();
|
||||
map._free_internal(map.table.ptr);
|
||||
map.table = {};
|
||||
allocator::free(map.allocator, map);
|
||||
}
|
||||
|
||||
fn Key[] HashMap.key_tlist(HashMap* map)
|
||||
fn Key[] Map.temp_keys_list(map)
|
||||
{
|
||||
return map.key_list(mem::temp()) @inline;
|
||||
return map.new_keys_list(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
fn Key[] HashMap.key_list(HashMap* map, Allocator* using = mem::heap())
|
||||
fn Key[] Map.new_keys_list(self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!map.count) return Key[] {};
|
||||
MapImpl* map = (MapImpl*)self;
|
||||
if (!map || !map.count) return {};
|
||||
|
||||
Key[] list = calloc(Key, map.count, .using = using);
|
||||
Key[] list = allocator::alloc_array(allocator, Key, map.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
@@ -195,15 +300,37 @@ fn Key[] HashMap.key_list(HashMap* map, Allocator* using = mem::heap())
|
||||
return list;
|
||||
}
|
||||
|
||||
fn Value[] HashMap.value_tlist(HashMap* map)
|
||||
macro Map.@each(map; @body(key, value))
|
||||
{
|
||||
return map.value_list(mem::temp()) @inline;
|
||||
map.@each_entry(; Entry* entry) {
|
||||
@body(entry.key, entry.value);
|
||||
};
|
||||
}
|
||||
|
||||
fn Value[] HashMap.value_list(HashMap* map, Allocator* using = mem::heap())
|
||||
macro Map.@each_entry(self; @body(entry))
|
||||
{
|
||||
if (!map.count) return Value[] {};
|
||||
Value[] list = calloc(Value, map.count, .using = using);
|
||||
MapImpl *map = (MapImpl*)self;
|
||||
if (!map || !map.count) return;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@body(entry);
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] Map.temp_values_list(map)
|
||||
{
|
||||
return map.new_values_list(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
fn Value[] Map.new_values_list(self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
MapImpl* map = (MapImpl*)self;
|
||||
if (!map || !map.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, map.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
@@ -216,10 +343,10 @@ fn Value[] HashMap.value_list(HashMap* map, Allocator* using = mem::heap())
|
||||
return list;
|
||||
}
|
||||
|
||||
$if types::is_equatable(Value):
|
||||
fn bool HashMap.has_value(HashMap* map, Value v)
|
||||
fn bool Map.has_value(self, Value v) @if(VALUE_IS_EQUATABLE)
|
||||
{
|
||||
if (!map.count) return false;
|
||||
MapImpl* map = (MapImpl*)self;
|
||||
if (!map || !map.count) return false;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
@@ -230,22 +357,23 @@ fn bool HashMap.has_value(HashMap* map, Value v)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
$endif
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void HashMap.add_entry(HashMap* map, uint hash, Key key, Value value, uint bucket_index) @private
|
||||
fn void MapImpl._add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
|
||||
{
|
||||
Entry* entry = malloc(Entry, .using = map.allocator);
|
||||
*entry = { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] };
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
|
||||
map.table[bucket_index] = entry;
|
||||
if (map.count++ >= map.threshold)
|
||||
{
|
||||
map.resize(map.table.len * 2);
|
||||
}
|
||||
map._resize(map.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.resize(HashMap* map, uint new_capacity) @private
|
||||
fn void MapImpl._resize(&map, uint new_capacity) @private
|
||||
{
|
||||
Entry*[] old_table = map.table;
|
||||
uint old_capacity = old_table.len;
|
||||
@@ -254,17 +382,17 @@ fn void HashMap.resize(HashMap* map, uint new_capacity) @private
|
||||
map.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
Entry*[] new_table = calloc(Entry*, new_capacity, .using = map.allocator);
|
||||
map.transfer(new_table);
|
||||
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
|
||||
map._transfer(new_table);
|
||||
map.table = new_table;
|
||||
map.free_internal(old_table.ptr);
|
||||
map._free_internal(old_table.ptr);
|
||||
map.threshold = (uint)(new_capacity * map.load_factor);
|
||||
}
|
||||
|
||||
fn uint rehash(uint hash) @inline @private
|
||||
{
|
||||
hash ^= (hash >> 20) ^ (hash >> 12);
|
||||
return hash ^ ((hash >> 7) ^ (hash >> 4));
|
||||
hash ^= (hash >> 20) ^ (hash >> 12);
|
||||
return hash ^ ((hash >> 7) ^ (hash >> 4));
|
||||
}
|
||||
|
||||
macro uint index_for(uint hash, uint capacity) @private
|
||||
@@ -272,39 +400,40 @@ macro uint index_for(uint hash, uint capacity) @private
|
||||
return hash & (capacity - 1);
|
||||
}
|
||||
|
||||
fn void HashMap.transfer(HashMap* map, Entry*[] new_table) @private
|
||||
fn void MapImpl._transfer(&map, Entry*[] new_table) @private
|
||||
{
|
||||
Entry*[] src = map.table;
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, Entry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
Entry* next = e.next;
|
||||
uint i = index_for(e.hash, new_capacity);
|
||||
e.next = new_table[i];
|
||||
new_table[i] = e;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
}
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, Entry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
Entry* next = e.next;
|
||||
uint i = index_for(e.hash, new_capacity);
|
||||
e.next = new_table[i];
|
||||
new_table[i] = e;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.put_all_for_create(HashMap* map, HashMap* other_map) @private
|
||||
fn void _init(MapImpl* impl, uint capacity, float load_factor, Allocator allocator) @private
|
||||
{
|
||||
if (!other_map.count) return;
|
||||
foreach (Entry *e : other_map.table)
|
||||
{
|
||||
if (!e) continue;
|
||||
map.put_for_create(e.key, e.value);
|
||||
}
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
*impl = {
|
||||
.allocator = allocator,
|
||||
.load_factor = load_factor,
|
||||
.threshold = (uint)(capacity * load_factor),
|
||||
.table = allocator::new_array(allocator, Entry*, capacity)
|
||||
};
|
||||
}
|
||||
|
||||
fn void HashMap.put_for_create(HashMap* map, Key key, Value value) @private
|
||||
fn void MapImpl._put_for_create(&map, Key key, Value value) @private
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
@@ -313,16 +442,17 @@ fn void HashMap.put_for_create(HashMap* map, Key key, Value value) @private
|
||||
return;
|
||||
}
|
||||
}
|
||||
map.create_entry(hash, key, value, i);
|
||||
map._create_entry(hash, key, value, i);
|
||||
}
|
||||
|
||||
fn void HashMap.free_internal(HashMap* map, void* ptr) @inline @private
|
||||
fn void MapImpl._free_internal(&map, void* ptr) @inline @private
|
||||
{
|
||||
map.allocator.free(ptr)!!;
|
||||
allocator::free(map.allocator, ptr);
|
||||
}
|
||||
|
||||
fn bool HashMap.remove_entry_for_key(HashMap* map, Key key) @private
|
||||
fn bool MapImpl._remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
if (!map.count) return false;
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
Entry* prev = map.table[i];
|
||||
@@ -341,7 +471,7 @@ fn bool HashMap.remove_entry_for_key(HashMap* map, Key key) @private
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
map.free_internal(e);
|
||||
map._free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
@@ -350,15 +480,25 @@ fn bool HashMap.remove_entry_for_key(HashMap* map, Key key) @private
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void HashMap.create_entry(HashMap* map, uint hash, Key key, Value value, int bucket_index) @private
|
||||
fn void MapImpl._create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
|
||||
{
|
||||
Entry *e = map.table[bucket_index];
|
||||
Entry* entry = malloc(Entry, .using = map.allocator);
|
||||
*entry = { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] };
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
|
||||
map.table[bucket_index] = entry;
|
||||
map.count++;
|
||||
}
|
||||
|
||||
fn void MapImpl._free_entry(&self, Entry *entry) @local
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
allocator::free(self.allocator, entry.key);
|
||||
$endif
|
||||
self._free_internal(entry);
|
||||
}
|
||||
|
||||
struct Entry
|
||||
{
|
||||
uint hash;
|
||||
|
||||
19
lib/std/collections/maybe.c3
Normal file
19
lib/std/collections/maybe.c3
Normal file
@@ -0,0 +1,19 @@
|
||||
module std::collections::maybe(<Type>);
|
||||
|
||||
struct Maybe
|
||||
{
|
||||
Type value;
|
||||
bool has_value;
|
||||
}
|
||||
|
||||
fn Maybe value(Type val)
|
||||
{
|
||||
return { .value = val, .has_value = true };
|
||||
}
|
||||
|
||||
const Maybe EMPTY = { };
|
||||
|
||||
macro Type! Maybe.get(self)
|
||||
{
|
||||
return self.has_value ? self.value : SearchResult.MISSING?;
|
||||
}
|
||||
@@ -2,18 +2,16 @@
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::object;
|
||||
import std::collections::map;
|
||||
import std::collections::list;
|
||||
import std::io;
|
||||
import std::collections::map, std::collections::list, std::io;
|
||||
|
||||
const Object TRUE_OBJECT = { .b = true, .type = bool.typeid };
|
||||
const Object FALSE_OBJECT = { .b = false, .type = bool.typeid };
|
||||
const Object NULL_OBJECT = { .type = void*.typeid };
|
||||
|
||||
struct Object
|
||||
struct Object (Printable)
|
||||
{
|
||||
typeid type;
|
||||
Allocator* allocator;
|
||||
Allocator allocator;
|
||||
union
|
||||
{
|
||||
uint128 i;
|
||||
@@ -27,60 +25,60 @@ struct Object
|
||||
}
|
||||
|
||||
|
||||
fn void! Object.to_format(Object* o, Formatter* formatter) @dynamic
|
||||
fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (o.type)
|
||||
switch (self.type)
|
||||
{
|
||||
case void:
|
||||
formatter.printf("{}")!;
|
||||
return formatter.printf("{}")!;
|
||||
case void*:
|
||||
formatter.printf("null")!;
|
||||
return formatter.printf("null")!;
|
||||
case String:
|
||||
formatter.printf(`"%s"`, o.s)!;
|
||||
return formatter.printf(`"%s"`, self.s)!;
|
||||
case bool:
|
||||
formatter.printf(o.b ? "true" : "false")!;
|
||||
return formatter.printf(self.b ? "true" : "false")!;
|
||||
case ObjectInternalList:
|
||||
formatter.printf("[")!;
|
||||
foreach (i, ol : o.array)
|
||||
usz n = formatter.printf("[")!;
|
||||
foreach (i, ol : self.array)
|
||||
{
|
||||
formatter.printf(i == 0 ? " " : ", ")!;
|
||||
ol.to_format(formatter)!;
|
||||
if (i > 0) n += formatter.printf(",")!;
|
||||
n += ol.to_format(formatter)!;
|
||||
}
|
||||
formatter.printf(" ]")!;
|
||||
n += formatter.printf("]")!;
|
||||
return n;
|
||||
case ObjectInternalMap:
|
||||
formatter.printf("{")!;
|
||||
@pool()
|
||||
usz n = formatter.printf("{")!;
|
||||
@stack_mem(1024; Allocator mem)
|
||||
{
|
||||
foreach (i, key : o.map.key_tlist())
|
||||
foreach (i, key : self.map.copy_keys(mem))
|
||||
{
|
||||
formatter.printf(i == 0 ? " " : ", ")!;
|
||||
formatter.printf(`"%s": `, key)!;
|
||||
o.map.get(key).to_format(formatter)!;
|
||||
if (i > 0) n += formatter.printf(",")!;
|
||||
n += formatter.printf(`"%s":`, key)!;
|
||||
n += self.map.get(key).to_format(formatter)!;
|
||||
}
|
||||
};
|
||||
formatter.printf(" }")!;
|
||||
n += formatter.printf("}")!;
|
||||
return n;
|
||||
default:
|
||||
switch (o.type.kindof)
|
||||
switch (self.type.kindof)
|
||||
{
|
||||
case SIGNED_INT:
|
||||
formatter.printf("%d", o.i)!;
|
||||
return formatter.printf("%d", (int128)self.i)!;
|
||||
case UNSIGNED_INT:
|
||||
formatter.printf("%d", (uint128)o.i)!;
|
||||
return formatter.printf("%d", (uint128)self.i)!;
|
||||
case FLOAT:
|
||||
formatter.printf("%d", o.f)!;
|
||||
return formatter.printf("%g", self.f)!;
|
||||
case ENUM:
|
||||
formatter.printf("%d", o.i)!;
|
||||
return formatter.printf("%d", self.i)!;
|
||||
default:
|
||||
formatter.printf("<>")!;
|
||||
return formatter.printf("<>")!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn Object* new_obj(Allocator* using = mem::heap())
|
||||
fn Object* new_obj(Allocator allocator)
|
||||
{
|
||||
Object* o = malloc(Object, .using = using);
|
||||
*o = { .allocator = using, .type = void.typeid };
|
||||
return o;
|
||||
return allocator::new(allocator, Object, { .allocator = allocator, .type = void.typeid });
|
||||
}
|
||||
|
||||
fn Object* new_null()
|
||||
@@ -88,32 +86,24 @@ fn Object* new_null()
|
||||
return &NULL_OBJECT;
|
||||
}
|
||||
|
||||
fn Object* new_int(int128 i, Allocator* using = mem::heap())
|
||||
fn Object* new_int(int128 i, Allocator allocator)
|
||||
{
|
||||
Object* o = malloc(Object, .using = using);
|
||||
*o = { .i = i, .allocator = using, .type = int128.typeid };
|
||||
return o;
|
||||
return allocator::new(allocator, Object, { .i = i, .allocator = allocator, .type = int128.typeid });
|
||||
}
|
||||
|
||||
macro Object* new_enum(e, Allocator* using = mem::heap())
|
||||
macro Object* new_enum(e, Allocator allocator)
|
||||
{
|
||||
Object* o = malloc(Object, .using = using);
|
||||
*o = { .i = (int128)e, .allocator = using, .type = $typeof(e).typeid };
|
||||
return o;
|
||||
return allocator::new(allocator, Object, { .i = (int128)e, .allocator = allocator, .type = @typeid(e) });
|
||||
}
|
||||
|
||||
fn Object* new_float(double f, Allocator* using = mem::current_allocator())
|
||||
fn Object* new_float(double f, Allocator allocator)
|
||||
{
|
||||
Object* o = malloc(Object, .using = using);
|
||||
*o = { .f = f, .allocator = using, .type = double.typeid };
|
||||
return o;
|
||||
return allocator::new(allocator, Object, { .f = f, .allocator = allocator, .type = double.typeid });
|
||||
}
|
||||
|
||||
fn Object* new_string(String s, Allocator* using = mem::heap())
|
||||
fn Object* new_string(String s, Allocator allocator)
|
||||
{
|
||||
Object* o = malloc(Object, .using = using);
|
||||
*o = { .s = s.copy(using), .allocator = using, .type = String.typeid };
|
||||
return o;
|
||||
return allocator::new(allocator, Object, { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid });
|
||||
}
|
||||
|
||||
|
||||
@@ -122,187 +112,188 @@ fn Object* new_bool(bool b)
|
||||
return b ? &TRUE_OBJECT : &FALSE_OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] o
|
||||
**/
|
||||
fn void Object.free(Object* o)
|
||||
fn void Object.free(&self)
|
||||
{
|
||||
switch (o.type)
|
||||
switch (self.type)
|
||||
{
|
||||
case void:
|
||||
break;
|
||||
case String:
|
||||
free(o.s, .using = o.allocator);
|
||||
allocator::free(self.allocator, self.s);
|
||||
case ObjectInternalList:
|
||||
foreach (ol : o.array)
|
||||
foreach (ol : self.array)
|
||||
{
|
||||
ol.free();
|
||||
}
|
||||
o.array.free();
|
||||
self.array.free();
|
||||
case ObjectInternalMap:
|
||||
@pool()
|
||||
{
|
||||
foreach (key : o.map.key_tlist())
|
||||
{
|
||||
o.map.get(key).free();
|
||||
free(key, .using = o.allocator);
|
||||
}
|
||||
o.map.free();
|
||||
self.map.@each_entry(; ObjectInternalMapEntry* entry) {
|
||||
entry.value.free();
|
||||
};
|
||||
self.map.free();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (o.allocator) free(o, .using = o.allocator);
|
||||
if (self.allocator) allocator::free(self.allocator, self);
|
||||
}
|
||||
|
||||
fn bool Object.is_null(Object* this) @inline => this == &NULL_OBJECT;
|
||||
fn bool Object.is_empty(Object* this) @inline => this.type == void.typeid;
|
||||
fn bool Object.is_map(Object* this) @inline => this.type == ObjectInternalMap.typeid;
|
||||
fn bool Object.is_array(Object* this) @inline => this.type == ObjectInternalList.typeid;
|
||||
fn bool Object.is_bool(Object* this) @inline => this.type == bool.typeid;
|
||||
fn bool Object.is_string(Object* this) @inline => this.type == String.typeid;
|
||||
fn bool Object.is_float(Object* this) @inline => this.type == double.typeid;
|
||||
fn bool Object.is_int(Object* this) @inline => this.type == int128.typeid;
|
||||
fn bool Object.is_keyable(Object* this) => this.is_empty() || this.is_map();
|
||||
fn bool Object.is_indexable(Object* this) => this.is_empty() || this.is_array();
|
||||
fn bool Object.is_null(&self) @inline => self == &NULL_OBJECT;
|
||||
fn bool Object.is_empty(&self) @inline => self.type == void.typeid;
|
||||
fn bool Object.is_map(&self) @inline => self.type == ObjectInternalMap.typeid;
|
||||
fn bool Object.is_array(&self) @inline => self.type == ObjectInternalList.typeid;
|
||||
fn bool Object.is_bool(&self) @inline => self.type == bool.typeid;
|
||||
fn bool Object.is_string(&self) @inline => self.type == String.typeid;
|
||||
fn bool Object.is_float(&self) @inline => self.type == double.typeid;
|
||||
fn bool Object.is_int(&self) @inline => self.type == int128.typeid;
|
||||
fn bool Object.is_keyable(&self) => self.is_empty() || self.is_map();
|
||||
fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array();
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
**/
|
||||
fn void Object.init_map_if_needed(Object* o) @private
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn void Object.init_map_if_needed(&self) @private
|
||||
{
|
||||
if (o.is_empty())
|
||||
if (self.is_empty())
|
||||
{
|
||||
o.type = ObjectInternalMap.typeid;
|
||||
o.map.init(.using = o.allocator);
|
||||
self.type = ObjectInternalMap.typeid;
|
||||
self.map.new_init(allocator: self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
**/
|
||||
fn void Object.init_array_if_needed(Object* o) @private
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn void Object.init_array_if_needed(&self) @private
|
||||
{
|
||||
if (o.is_empty())
|
||||
if (self.is_empty())
|
||||
{
|
||||
o.type = ObjectInternalList.typeid;
|
||||
o.array.init(.using = o.allocator);
|
||||
self.type = ObjectInternalList.typeid;
|
||||
self.array.new_init(allocator: self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
**/
|
||||
fn void Object.set_object(Object* o, String key, Object* new_object) @private
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn void Object.set_object(&self, String key, Object* new_object) @private
|
||||
{
|
||||
o.init_map_if_needed();
|
||||
ObjectInternalMapEntry*! entry = o.map.get_entry(key);
|
||||
self.init_map_if_needed();
|
||||
ObjectInternalMapEntry*! entry = self.map.get_entry(key);
|
||||
defer
|
||||
{
|
||||
(void)free(entry.key, .using = o.allocator);
|
||||
entry.value.free();
|
||||
(void)entry.value.free();
|
||||
}
|
||||
o.map.set(key.copy(o.map.allocator), new_object);
|
||||
self.map.set(key, new_object);
|
||||
}
|
||||
|
||||
macro Object* object_from_value(value) @private
|
||||
macro Object* Object.object_from_value(&self, value) @private
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
|
||||
$switch
|
||||
$case types::is_int($Type):
|
||||
return new_int(value);
|
||||
$case types::is_float($Type):
|
||||
return new_float(value);
|
||||
$case $Type.typeid == String.typeid:
|
||||
return new_string(value);
|
||||
$case $Type.typeid == bool.typeid:
|
||||
return new_bool(value);
|
||||
$case $Type.typeid == Object*.typeid:
|
||||
return value;
|
||||
$case $Type.typeid == void*.typeid:
|
||||
assert(value == null);
|
||||
return &NULL_OBJECT;
|
||||
$case $checks(String s = value):
|
||||
return new_string(value);
|
||||
$default:
|
||||
$error "Unsupported object type.";
|
||||
$endswitch
|
||||
$switch
|
||||
$case types::is_int($Type):
|
||||
return new_int(value, self.allocator);
|
||||
$case types::is_float($Type):
|
||||
return new_float(value, self.allocator);
|
||||
$case $Type.typeid == String.typeid:
|
||||
return new_string(value, self.allocator);
|
||||
$case $Type.typeid == bool.typeid:
|
||||
return new_bool(value);
|
||||
$case $Type.typeid == Object*.typeid:
|
||||
return value;
|
||||
$case $Type.typeid == void*.typeid:
|
||||
if (value != null) return CastResult.TYPE_MISMATCH?;
|
||||
return &NULL_OBJECT;
|
||||
$case $assignable(value, String):
|
||||
return new_string(value, self.allocator);
|
||||
$default:
|
||||
$error "Unsupported object type.";
|
||||
$endswitch
|
||||
|
||||
}
|
||||
|
||||
macro Object* Object.set(Object* o, String key, value)
|
||||
macro Object* Object.set(&self, String key, value)
|
||||
{
|
||||
Object* val = object_from_value(value);
|
||||
o.set_object(key, val);
|
||||
Object* val = self.object_from_value(value);
|
||||
self.set_object(key, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
**/
|
||||
macro Object* Object.set_at(Object* o, usz index, String key, value)
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
macro Object* Object.set_at(&self, usz index, String key, value)
|
||||
{
|
||||
Object* val = object_from_value(value);
|
||||
o.set_object_at(key, index, val);
|
||||
Object* val = self.object_from_value(value);
|
||||
self.set_object_at(key, index, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @ensure return != null
|
||||
**/
|
||||
macro Object* Object.append(Object* o, value)
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
@ensure return != null
|
||||
*>
|
||||
macro Object* Object.push(&self, value)
|
||||
{
|
||||
Object* val = object_from_value(value);
|
||||
o.append_object(val);
|
||||
Object* val = self.object_from_value(value);
|
||||
self.push_object(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
**/
|
||||
fn Object*! Object.get(Object* o, String key) => o.is_empty() ? SearchResult.MISSING? : o.map.get(key);
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn Object*! Object.get(&self, String key) => self.is_empty() ? SearchResult.MISSING? : self.map.get(key);
|
||||
|
||||
|
||||
fn bool Object.has_key(Object* o, String key) => o.is_map() && o.map.has_key(key);
|
||||
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
**/
|
||||
fn Object* Object.get_at(Object* o, usz index)
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn Object* Object.get_at(&self, usz index)
|
||||
{
|
||||
return o.array.get(index);
|
||||
return self.array.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
**/
|
||||
fn void Object.append_object(Object* o, Object* to_append)
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn usz Object.get_len(&self)
|
||||
{
|
||||
o.init_array_if_needed();
|
||||
o.array.append(to_append);
|
||||
return self.array.len();
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
**/
|
||||
fn void Object.set_object_at(Object* o, usz index, Object* to_set)
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn void Object.push_object(&self, Object* to_append)
|
||||
{
|
||||
o.init_array_if_needed();
|
||||
while (o.array.len() < index)
|
||||
self.init_array_if_needed();
|
||||
self.array.push(to_append);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn void Object.set_object_at(&self, usz index, Object* to_set)
|
||||
{
|
||||
self.init_array_if_needed();
|
||||
while (self.array.len() < index)
|
||||
{
|
||||
o.array.append(&NULL_OBJECT);
|
||||
self.array.push(&NULL_OBJECT);
|
||||
}
|
||||
if (o.array.len() == index)
|
||||
if (self.array.len() == index)
|
||||
{
|
||||
o.array.append(to_set);
|
||||
self.array.push(to_set);
|
||||
return;
|
||||
}
|
||||
o.array.get(index).free();
|
||||
o.array.set_at(index, to_set);
|
||||
self.array.get(index).free();
|
||||
self.array.set_at(index, to_set);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof.is_int() "Expected an integer type."
|
||||
*>
|
||||
macro get_integer_value(Object* value, $Type)
|
||||
{
|
||||
if (value.is_float())
|
||||
@@ -321,113 +312,116 @@ macro get_integer_value(Object* value, $Type)
|
||||
return ($Type)value.i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
**/
|
||||
macro Object.get_integer_at(Object* o, $Type, usz index) @private
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
@require $Type.kindof.is_int() : "Expected an integer type"
|
||||
*>
|
||||
macro Object.get_integer_at(&self, $Type, usz index) @private
|
||||
{
|
||||
return get_integer_value(o.get_at(index), $Type);
|
||||
return get_integer_value(self.get_at(index), $Type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
**/
|
||||
macro Object.get_integer(Object* o, $Type, String key) @private
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
@require $Type.kindof.is_int() : "Expected an integer type"
|
||||
*>
|
||||
macro Object.get_integer(&self, $Type, String key) @private
|
||||
{
|
||||
return get_integer_value(o.get(key), $Type);
|
||||
return get_integer_value(self.get(key), $Type);
|
||||
}
|
||||
|
||||
fn ichar! Object.get_ichar(Object* o, String key) => o.get_integer(ichar, key);
|
||||
fn short! Object.get_short(Object* o, String key) => o.get_integer(short, key);
|
||||
fn int! Object.get_int(Object* o, String key) => o.get_integer(int, key);
|
||||
fn long! Object.get_long(Object* o, String key) => o.get_integer(long, key);
|
||||
fn int128! Object.get_int128(Object* o, String key) => o.get_integer(int128, key);
|
||||
fn ichar! Object.get_ichar(&self, String key) => self.get_integer(ichar, key);
|
||||
fn short! Object.get_short(&self, String key) => self.get_integer(short, key);
|
||||
fn int! Object.get_int(&self, String key) => self.get_integer(int, key);
|
||||
fn long! Object.get_long(&self, String key) => self.get_integer(long, key);
|
||||
fn int128! Object.get_int128(&self, String key) => self.get_integer(int128, key);
|
||||
|
||||
fn ichar! Object.get_ichar_at(Object* o, usz index) => o.get_integer_at(ichar, index);
|
||||
fn short! Object.get_short_at(Object* o, usz index) => o.get_integer_at(short, index);
|
||||
fn int! Object.get_int_at(Object* o, usz index) => o.get_integer_at(int, index);
|
||||
fn long! Object.get_long_at(Object* o, usz index) => o.get_integer_at(long, index);
|
||||
fn int128! Object.get_int128_at(Object* o, usz index) => o.get_integer_at(int128, index);
|
||||
fn ichar! Object.get_ichar_at(&self, usz index) => self.get_integer_at(ichar, index);
|
||||
fn short! Object.get_short_at(&self, usz index) => self.get_integer_at(short, index);
|
||||
fn int! Object.get_int_at(&self, usz index) => self.get_integer_at(int, index);
|
||||
fn long! Object.get_long_at(&self, usz index) => self.get_integer_at(long, index);
|
||||
fn int128! Object.get_int128_at(&self, usz index) => self.get_integer_at(int128, index);
|
||||
|
||||
fn char! Object.get_char(Object* o, String key) => o.get_integer(ichar, key);
|
||||
fn short! Object.get_ushort(Object* o, String key) => o.get_integer(ushort, key);
|
||||
fn uint! Object.get_uint(Object* o, String key) => o.get_integer(uint, key);
|
||||
fn ulong! Object.get_ulong(Object* o, String key) => o.get_integer(ulong, key);
|
||||
fn uint128! Object.get_uint128(Object* o, String key) => o.get_integer(uint128, key);
|
||||
fn char! Object.get_char(&self, String key) => self.get_integer(ichar, key);
|
||||
fn short! Object.get_ushort(&self, String key) => self.get_integer(ushort, key);
|
||||
fn uint! Object.get_uint(&self, String key) => self.get_integer(uint, key);
|
||||
fn ulong! Object.get_ulong(&self, String key) => self.get_integer(ulong, key);
|
||||
fn uint128! Object.get_uint128(&self, String key) => self.get_integer(uint128, key);
|
||||
|
||||
fn char! Object.get_char_at(Object* o, usz index) => o.get_integer_at(char, index);
|
||||
fn ushort! Object.get_ushort_at(Object* o, usz index) => o.get_integer_at(ushort, index);
|
||||
fn uint! Object.get_uint_at(Object* o, usz index) => o.get_integer_at(uint, index);
|
||||
fn ulong! Object.get_ulong_at(Object* o, usz index) => o.get_integer_at(ulong, index);
|
||||
fn uint128! Object.get_uint128_at(Object* o, usz index) => o.get_integer_at(uint128, index);
|
||||
fn char! Object.get_char_at(&self, usz index) => self.get_integer_at(char, index);
|
||||
fn ushort! Object.get_ushort_at(&self, usz index) => self.get_integer_at(ushort, index);
|
||||
fn uint! Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index);
|
||||
fn ulong! Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index);
|
||||
fn uint128! Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
**/
|
||||
fn String! Object.get_string(Object* o, String key)
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn String! Object.get_string(&self, String key)
|
||||
{
|
||||
Object* value = o.get(key)!;
|
||||
assert(value.is_string());
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
**/
|
||||
fn String Object.get_string_at(Object* o, usz index)
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn String! Object.get_string_at(&self, usz index)
|
||||
{
|
||||
Object* value = o.get_at(index);
|
||||
assert(value.is_string());
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
**/
|
||||
macro String! Object.get_enum(Object* o, $EnumType, String key)
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
macro String! Object.get_enum(&self, $EnumType, String key)
|
||||
{
|
||||
Object value = o.get(key)!;
|
||||
assert($EnumType.typeid == value.type);
|
||||
Object value = self.get(key)!;
|
||||
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
**/
|
||||
macro String Object.get_enum_at(Object* o, $EnumType, usz index)
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
macro String! Object.get_enum_at(&self, $EnumType, usz index)
|
||||
{
|
||||
Object value = o.get_at(index);
|
||||
assert($EnumType.typeid == value.type);
|
||||
Object value = self.get_at(index);
|
||||
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
**/
|
||||
fn bool! Object.get_bool(Object* o, String key)
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn bool! Object.get_bool(&self, String key)
|
||||
{
|
||||
Object* value = o.get(key)!;
|
||||
assert(value.is_bool());
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
**/
|
||||
fn bool Object.get_bool_at(Object* o, usz index)
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn bool! Object.get_bool_at(&self, usz index)
|
||||
{
|
||||
Object* value = o.get_at(index);
|
||||
assert(value.is_bool());
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
**/
|
||||
fn double! Object.get_float(Object* o, String key)
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn double! Object.get_float(&self, String key)
|
||||
{
|
||||
Object* value = o.get(key)!;
|
||||
Object* value = self.get(key)!;
|
||||
switch (value.type.kindof)
|
||||
{
|
||||
case SIGNED_INT:
|
||||
@@ -437,16 +431,16 @@ fn double! Object.get_float(Object* o, String key)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
unreachable();
|
||||
return CastResult.TYPE_MISMATCH?;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
**/
|
||||
fn double Object.get_float_at(Object* o, usz index)
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn double! Object.get_float_at(&self, usz index)
|
||||
{
|
||||
Object* value = o.get_at(index);
|
||||
Object* value = self.get_at(index);
|
||||
switch (value.type.kindof)
|
||||
{
|
||||
case SIGNED_INT:
|
||||
@@ -456,19 +450,19 @@ fn double Object.get_float_at(Object* o, usz index)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
unreachable();
|
||||
return CastResult.TYPE_MISMATCH?;
|
||||
}
|
||||
}
|
||||
|
||||
fn Object* Object.get_or_create_obj(Object* o, String key)
|
||||
fn Object* Object.get_or_create_obj(&self, String key)
|
||||
{
|
||||
if (try obj = o.get(key) && !obj.is_null()) return obj;
|
||||
Object* container = new_obj();
|
||||
o.set(key, container);
|
||||
if (try obj = self.get(key) && !obj.is_null()) return obj;
|
||||
Object* container = new_obj(self.allocator);
|
||||
self.set(key, container);
|
||||
return container;
|
||||
}
|
||||
|
||||
def ObjectInternalMap @private = HashMap<String, Object*>;
|
||||
def ObjectInternalList @private = List<Object*>;
|
||||
def ObjectInternalMapEntry @private = Entry<String, Object*>;
|
||||
def ObjectInternalMap = HashMap(<String, Object*>) @private;
|
||||
def ObjectInternalList = List(<Object*>) @private;
|
||||
def ObjectInternalMapEntry = Entry(<String, Object*>) @private;
|
||||
|
||||
|
||||
@@ -20,94 +20,139 @@
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
module std::collections::priorityqueue<Type>;
|
||||
import std::collections::list;
|
||||
module std::collections::priorityqueue(<Type>);
|
||||
import std::collections::priorityqueue::private;
|
||||
|
||||
def Heap = List<Type>;
|
||||
distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
|
||||
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);
|
||||
|
||||
struct PriorityQueue
|
||||
module std::collections::priorityqueue::private(<Type, MAX>);
|
||||
import std::collections::list, std::io;
|
||||
|
||||
def Heap = List(<Type>);
|
||||
|
||||
struct PrivatePriorityQueue (Printable)
|
||||
{
|
||||
Heap heap;
|
||||
bool max; // true if max-heap, false if min-heap
|
||||
}
|
||||
|
||||
fn void PriorityQueue.push(PriorityQueue* pq, Type element)
|
||||
fn void PrivatePriorityQueue.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @inline
|
||||
{
|
||||
pq.heap.push(element);
|
||||
usz i = pq.heap.len() - 1;
|
||||
self.heap.new_init(initial_capacity, allocator);
|
||||
}
|
||||
|
||||
fn void PrivatePriorityQueue.temp_init(&self, usz initial_capacity = 16) @inline
|
||||
{
|
||||
self.heap.new_init(initial_capacity, allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
|
||||
fn void PrivatePriorityQueue.push(&self, Type element)
|
||||
{
|
||||
self.heap.push(element);
|
||||
usz i = self.heap.len() - 1;
|
||||
while (i > 0)
|
||||
{
|
||||
usz parent = (i - 1) / 2;
|
||||
if ((pq.max && greater(pq.heap.get(i), pq.heap.get(parent))) || (!pq.max && less(pq.heap.get(i), pq.heap.get(parent))))
|
||||
{
|
||||
pq.heap.swap(i, parent);
|
||||
i = parent;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
Type item = self.heap[i];
|
||||
Type parent_item = self.heap[parent];
|
||||
$if MAX:
|
||||
bool ok = greater(item, parent_item);
|
||||
$else
|
||||
bool ok = less(item, parent_item);
|
||||
$endif
|
||||
if (!ok) break;
|
||||
self.heap.swap(i, parent);
|
||||
i = parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require pq != null
|
||||
*/
|
||||
fn Type! PriorityQueue.pop(PriorityQueue* pq)
|
||||
fn void PrivatePriorityQueue.remove_at(&self, usz index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
self.pop()!!;
|
||||
return;
|
||||
}
|
||||
self.heap.remove_at(index);
|
||||
}
|
||||
<*
|
||||
@require self != null
|
||||
*>
|
||||
fn Type! PrivatePriorityQueue.pop(&self)
|
||||
{
|
||||
usz i = 0;
|
||||
usz len = pq.heap.len() @inline;
|
||||
usz len = self.heap.len();
|
||||
if (!len) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
usz newCount = len - 1;
|
||||
pq.heap.swap(0, newCount);
|
||||
while ((2 * i + 1) < newCount)
|
||||
usz new_count = len - 1;
|
||||
self.heap.swap(0, new_count);
|
||||
while OUTER: ((2 * i + 1) < new_count)
|
||||
{
|
||||
usz j = 2 * i + 1;
|
||||
if (((j + 1) < newCount) &&
|
||||
((pq.max && greater(pq.heap.get(j + 1), pq.heap[j]))
|
||||
|| (!pq.max && less(pq.heap.get(j + 1), pq.heap.get(j)))))
|
||||
Type left = self.heap[j];
|
||||
Type item = self.heap[i];
|
||||
switch
|
||||
{
|
||||
j++;
|
||||
case j + 1 < new_count:
|
||||
Type right = self.heap[j + 1];
|
||||
$if MAX:
|
||||
if (!greater(right, left)) nextcase;
|
||||
if (!greater(right, item)) break OUTER;
|
||||
$else
|
||||
if (!greater(left, right)) nextcase;
|
||||
if (!greater(item, right)) break OUTER;
|
||||
$endif
|
||||
j++;
|
||||
default:
|
||||
$if MAX:
|
||||
if (!greater(left, item)) break OUTER;
|
||||
$else
|
||||
if (!greater(item, left)) break OUTER;
|
||||
$endif
|
||||
}
|
||||
if ((pq.max && less(pq.heap.get(i), pq.heap.get(j))) || (!pq.max && greater(pq.heap.get(i), pq.heap.get(j))))
|
||||
{
|
||||
pq.heap.swap(i, j);
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
self.heap.swap(i, j);
|
||||
i = j;
|
||||
}
|
||||
|
||||
return pq.heap.pop();
|
||||
return self.heap.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @require pq != null
|
||||
*/
|
||||
fn Type! PriorityQueue.peek(PriorityQueue* pq)
|
||||
fn Type! PrivatePriorityQueue.first(&self)
|
||||
{
|
||||
if (!pq.len()) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return pq.heap.get(0);
|
||||
if (!self.len()) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.heap.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require pq != null
|
||||
*/
|
||||
fn void PriorityQueue.free(PriorityQueue* pq)
|
||||
fn void PrivatePriorityQueue.free(&self)
|
||||
{
|
||||
pq.heap.free();
|
||||
self.heap.free();
|
||||
}
|
||||
|
||||
/**
|
||||
* @require pq != null
|
||||
*/
|
||||
fn usz PriorityQueue.len(PriorityQueue* pq) @operator(len)
|
||||
fn usz PrivatePriorityQueue.len(&self) @operator(len)
|
||||
{
|
||||
return pq.heap.len();
|
||||
return self.heap.len();
|
||||
}
|
||||
|
||||
/**
|
||||
* @require pq != null, index < pq.len()
|
||||
*/
|
||||
fn Type PriorityQueue.peek_at(PriorityQueue* pq, usz index) @operator([])
|
||||
fn bool PrivatePriorityQueue.is_empty(&self)
|
||||
{
|
||||
return pq.heap[index];
|
||||
return self.heap.is_empty();
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len()
|
||||
*>
|
||||
fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
|
||||
{
|
||||
return self.heap[index];
|
||||
}
|
||||
|
||||
fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return self.heap.to_format(formatter);
|
||||
}
|
||||
|
||||
fn String PrivatePriorityQueue.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
|
||||
{
|
||||
return self.heap.to_new_string(allocator);
|
||||
}
|
||||
|
||||
|
||||
95
lib/std/collections/range.c3
Normal file
95
lib/std/collections/range.c3
Normal file
@@ -0,0 +1,95 @@
|
||||
<*
|
||||
@require Type.is_ordered : "The type must be ordered"
|
||||
*>
|
||||
module std::collections::range(<Type>);
|
||||
import std::io;
|
||||
|
||||
struct Range (Printable)
|
||||
{
|
||||
Type start;
|
||||
Type end;
|
||||
}
|
||||
|
||||
fn usz Range.len(&self) @operator(len)
|
||||
{
|
||||
if (self.end < self.start) return 0;
|
||||
return (usz)(self.end - self.start) + 1;
|
||||
}
|
||||
|
||||
fn bool Range.contains(&self, Type value) @inline
|
||||
{
|
||||
return value >= self.start && value <= self.end;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len() : "Can't index into an empty range"
|
||||
*>
|
||||
fn Type Range.get(&self, usz index) @operator([])
|
||||
{
|
||||
return (Type)(self.start + (usz)index);
|
||||
}
|
||||
|
||||
fn String Range.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic @deprecated
|
||||
{
|
||||
return string::format("[%s..%s]", self.start, self.end, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String Range.to_string(&self, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("[%s..%s]", self.start, self.end, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String Range.to_tstring(&self)
|
||||
{
|
||||
return self.to_string(allocator::temp());
|
||||
}
|
||||
|
||||
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return formatter.printf("[%s..%s]", self.start, self.end)!;
|
||||
}
|
||||
|
||||
struct ExclusiveRange (Printable)
|
||||
{
|
||||
Type start;
|
||||
Type end;
|
||||
}
|
||||
|
||||
fn usz ExclusiveRange.len(&self) @operator(len)
|
||||
{
|
||||
if (self.end < self.start) return 0;
|
||||
return (usz)(self.end - self.start);
|
||||
}
|
||||
|
||||
fn bool ExclusiveRange.contains(&self, Type value) @inline
|
||||
{
|
||||
return value >= self.start && value < self.end;
|
||||
}
|
||||
|
||||
fn usz! ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return formatter.printf("[%s..<%s]", self.start, self.end)!;
|
||||
}
|
||||
|
||||
fn String ExclusiveRange.to_new_string(&self, Allocator allocator = null) @dynamic
|
||||
{
|
||||
return self.to_string(allocator ?: allocator::heap());
|
||||
}
|
||||
|
||||
fn String ExclusiveRange.to_string(&self, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("[%s..<%s]", self.start, self.end, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String ExclusiveRange.to_tstring(&self)
|
||||
{
|
||||
return self.to_new_string(allocator::temp());
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len() : "Can't index into an empty range"
|
||||
*>
|
||||
fn Type ExclusiveRange.get(&self, usz index) @operator([])
|
||||
{
|
||||
return (Type)(self.start + index);
|
||||
}
|
||||
106
lib/std/collections/ringbuffer.c3
Normal file
106
lib/std/collections/ringbuffer.c3
Normal file
@@ -0,0 +1,106 @@
|
||||
<*
|
||||
@require values::@is_int(SIZE) &&& SIZE > 0 "The size must be positive integer"
|
||||
*>
|
||||
module std::collections::ringbuffer(<Type, SIZE>);
|
||||
|
||||
struct RingBuffer
|
||||
{
|
||||
Type[SIZE] buf;
|
||||
usz written;
|
||||
usz head;
|
||||
}
|
||||
|
||||
fn void RingBuffer.init(&self) @inline
|
||||
{
|
||||
*self = {};
|
||||
}
|
||||
|
||||
fn void RingBuffer.push(&self, Type c)
|
||||
{
|
||||
if (self.written < SIZE)
|
||||
{
|
||||
self.buf[self.written] = c;
|
||||
self.written++;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.buf[self.head] = c;
|
||||
self.head = (self.head + 1) % SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
fn Type RingBuffer.get(&self, usz index) @operator([])
|
||||
{
|
||||
index %= SIZE;
|
||||
usz avail = SIZE - self.head;
|
||||
if (index < avail)
|
||||
{
|
||||
return self.buf[self.head + index];
|
||||
}
|
||||
return self.buf[index - avail];
|
||||
}
|
||||
|
||||
fn Type! RingBuffer.pop(&self)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case self.written == 0:
|
||||
return SearchResult.MISSING?;
|
||||
case self.written < SIZE:
|
||||
self.written--;
|
||||
return self.buf[self.written];
|
||||
default:
|
||||
self.head = (self.head - 1) % SIZE;
|
||||
return self.buf[self.head];
|
||||
}
|
||||
}
|
||||
|
||||
fn usz RingBuffer.read(&self, usz index, Type[] buffer)
|
||||
{
|
||||
index %= SIZE;
|
||||
if (self.written < SIZE)
|
||||
{
|
||||
if (index >= self.written) return 0;
|
||||
usz end = self.written - index;
|
||||
usz n = min(end, buffer.len);
|
||||
buffer[:n] = self.buf[index:n];
|
||||
return n;
|
||||
}
|
||||
usz end = SIZE - self.head;
|
||||
if (index >= end)
|
||||
{
|
||||
index -= end;
|
||||
if (index >= self.head) return 0;
|
||||
usz n = min(self.head - index, buffer.len);
|
||||
buffer[:n] = self.buf[index:n];
|
||||
return n;
|
||||
}
|
||||
if (buffer.len <= SIZE - index)
|
||||
{
|
||||
usz n = buffer.len;
|
||||
buffer[:n] = self.buf[self.head + index:n];
|
||||
return n;
|
||||
}
|
||||
usz n1 = SIZE - index;
|
||||
buffer[:n1] = self.buf[self.head + index:n1];
|
||||
buffer = buffer[n1..];
|
||||
index -= n1;
|
||||
usz n2 = min(self.head - index, buffer.len);
|
||||
buffer[:n2] = self.buf[index:n2];
|
||||
return n1 + n2;
|
||||
}
|
||||
|
||||
fn void RingBuffer.write(&self, Type[] buffer)
|
||||
{
|
||||
usz i;
|
||||
while (self.written < SIZE && i < buffer.len)
|
||||
{
|
||||
self.buf[self.written] = buffer[i++];
|
||||
self.written++;
|
||||
}
|
||||
foreach (c : buffer[i..])
|
||||
{
|
||||
self.buf[self.head] = c;
|
||||
self.head = (self.head + 1) % SIZE;
|
||||
}
|
||||
}
|
||||
16
lib/std/collections/tuple.c3
Normal file
16
lib/std/collections/tuple.c3
Normal file
@@ -0,0 +1,16 @@
|
||||
module std::collections::tuple(<Type1, Type2>);
|
||||
|
||||
struct Tuple
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
}
|
||||
|
||||
module std::collections::triple(<Type1, Type2, Type3>);
|
||||
|
||||
struct Triple
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
Type3 third;
|
||||
}
|
||||
476
lib/std/compression/qoi.c3
Normal file
476
lib/std/compression/qoi.c3
Normal file
@@ -0,0 +1,476 @@
|
||||
module std::compression::qoi;
|
||||
|
||||
const uint PIXELS_MAX = 400000000;
|
||||
|
||||
<*
|
||||
Colorspace.
|
||||
Purely informative. It will be saved to the file header,
|
||||
but does not affect how chunks are en-/decoded.
|
||||
*>
|
||||
enum QOIColorspace : char (char id)
|
||||
{
|
||||
SRGB = 0, // sRGB with linear alpha
|
||||
LINEAR = 1 // all channels linear
|
||||
}
|
||||
|
||||
<*
|
||||
Channels.
|
||||
The channels used in an image.
|
||||
AUTO can be used when decoding to automatically determine
|
||||
the channels from the file's header.
|
||||
*>
|
||||
enum QOIChannels : char (char id)
|
||||
{
|
||||
AUTO = 0,
|
||||
RGB = 3,
|
||||
RGBA = 4
|
||||
}
|
||||
|
||||
<*
|
||||
Descriptor.
|
||||
Contains information about an image.
|
||||
*>
|
||||
struct QOIDesc
|
||||
{
|
||||
uint width;
|
||||
uint height;
|
||||
QOIChannels channels;
|
||||
QOIColorspace colorspace;
|
||||
}
|
||||
|
||||
<*
|
||||
QOI Errors.
|
||||
These are all the possible bad outcomes.
|
||||
*>
|
||||
fault QOIError
|
||||
{
|
||||
INVALID_PARAMETERS,
|
||||
FILE_OPEN_FAILED,
|
||||
FILE_WRITE_FAILED,
|
||||
INVALID_DATA,
|
||||
TOO_MANY_PIXELS
|
||||
}
|
||||
|
||||
|
||||
// Let the user decide if they want to use std::io
|
||||
module std::compression::qoi @if(!$feature(QOI_NO_STDIO));
|
||||
import std::io;
|
||||
|
||||
<*
|
||||
Encode raw RGB or RGBA pixels into a QOI image and write it to the
|
||||
file system.
|
||||
|
||||
The desc struct must be filled with the image width, height, the
|
||||
used channels (QOIChannels.RGB or RGBA) and the colorspace
|
||||
(QOIColorspace.SRGB or LINEAR).
|
||||
|
||||
The function returns an optional, which can either be a QOIError
|
||||
or the number of bytes written on success.
|
||||
|
||||
@param [in] filename `The file's name to write the image to`
|
||||
@param [in] input `The raw RGB or RGBA pixels to encode`
|
||||
@param [&in] desc `The descriptor of the image`
|
||||
*>
|
||||
fn usz! write(String filename, char[] input, QOIDesc* desc)
|
||||
{
|
||||
@pool() {
|
||||
// encode data
|
||||
char[] output = encode(input, desc)!;
|
||||
|
||||
// open file
|
||||
File! f = file::open(filename, "wb");
|
||||
if (catch f) { return QOIError.FILE_OPEN_FAILED?; }
|
||||
|
||||
// write data to file and close it
|
||||
usz! written = f.write(output);
|
||||
if (catch written) { return QOIError.FILE_WRITE_FAILED?; }
|
||||
if (catch f.close()) { return QOIError.FILE_WRITE_FAILED?; }
|
||||
|
||||
return written;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Read and decode a QOI image from the file system.
|
||||
|
||||
If channels is set to QOIChannels.AUTO, the function will
|
||||
automatically determine the channels from the file's header.
|
||||
However, if channels is RGB or RGBA, the output format will be
|
||||
forced into this number of channels.
|
||||
|
||||
The desc struct will be filled with the width, height,
|
||||
channels and colorspace of the image.
|
||||
|
||||
The function returns an optional, which can either be a QOIError
|
||||
or a char[] pointing to the decoded pixels on success.
|
||||
|
||||
The returned pixel data should be free()d after use, or the decoding
|
||||
and use of the data should be wrapped in a @pool() { ... }; block.
|
||||
|
||||
@param [in] filename `The file's name to read the image from`
|
||||
@param [&out] desc `The descriptor to fill with the image's info`
|
||||
@param channels `The channels to be used`
|
||||
*>
|
||||
fn char[]! read(String filename, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap())
|
||||
{
|
||||
// read file
|
||||
char[]! data = file::load_new(filename);
|
||||
if (catch data) return QOIError.FILE_OPEN_FAILED?;
|
||||
defer mem::free(data);
|
||||
|
||||
// pass data to decode function
|
||||
return decode(data, desc, channels, allocator);
|
||||
}
|
||||
|
||||
|
||||
// Back to basic non-stdio mode
|
||||
module std::compression::qoi;
|
||||
import std::bits;
|
||||
|
||||
<*
|
||||
Encode raw RGB or RGBA pixels into a QOI image in memory.
|
||||
|
||||
The function returns an optional, which can either be a QOIError
|
||||
or a char[] pointing to the encoded data on success.
|
||||
|
||||
The returned qoi data should be free()d after use, or the encoding
|
||||
and use of the data should be wrapped in a @pool() { ... }; block.
|
||||
See the write() function for an example.
|
||||
|
||||
@param [in] input `The raw RGB or RGBA pixels to encode`
|
||||
@param [&in] desc `The descriptor of the image`
|
||||
*>
|
||||
fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::heap())
|
||||
{
|
||||
// check info in desc
|
||||
if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_PARAMETERS?;
|
||||
if (desc.channels == AUTO) return QOIError.INVALID_PARAMETERS?;
|
||||
uint pixels = desc.width * desc.height;
|
||||
if (pixels > PIXELS_MAX) return QOIError.TOO_MANY_PIXELS?;
|
||||
|
||||
// check input data size
|
||||
uint image_size = pixels * desc.channels.id;
|
||||
if (image_size != input.len) return QOIError.INVALID_DATA?;
|
||||
|
||||
// allocate memory for encoded data (output)
|
||||
// header + chunk tag and RGB(A) data for each pixel + end of stream
|
||||
uint max_size = Header.sizeof + pixels + image_size + END_OF_STREAM.len;
|
||||
char[] output = allocator::alloc_array(allocator, char, max_size); // no need to init
|
||||
defer catch allocator::free(allocator, output);
|
||||
|
||||
// write header
|
||||
*(Header*)output.ptr = {
|
||||
.be_magic = bswap('qoif'),
|
||||
.be_width = bswap(desc.width),
|
||||
.be_height = bswap(desc.height),
|
||||
.channels = desc.channels.id,
|
||||
.colorspace = desc.colorspace.id
|
||||
};
|
||||
|
||||
uint pos = Header.sizeof; // Current position in output
|
||||
uint loc; // Current position in image (top-left corner)
|
||||
uint loc_end = image_size - desc.channels.id; // End of image data
|
||||
char run_length = 0; // Length of the current run
|
||||
|
||||
Pixel[64] palette; // Zero-initialized by default
|
||||
Pixel prev = { 0, 0, 0, 255 };
|
||||
Pixel p = { 0, 0, 0, 255 };
|
||||
|
||||
ichar[<3>] diff; // pre-allocate for diff
|
||||
ichar[<3>] luma; // ...and luma
|
||||
|
||||
// write chunks
|
||||
for (loc = 0; loc < image_size; loc += desc.channels.id)
|
||||
{
|
||||
// set previous pixel
|
||||
prev = p;
|
||||
|
||||
// get current pixel
|
||||
p[:3] = input[loc:3]; // cutesy slices :3
|
||||
if (desc.channels == RGBA) p.a = input[loc + 3];
|
||||
|
||||
// check if we can run the previous pixel
|
||||
if (prev == p) {
|
||||
run_length++;
|
||||
if (run_length == 62 || loc == loc_end) {
|
||||
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
|
||||
run_length = 0;
|
||||
}
|
||||
} else {
|
||||
// end last run if there was one
|
||||
if (run_length > 0) {
|
||||
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
|
||||
run_length = 0;
|
||||
}
|
||||
|
||||
switch {
|
||||
// check if we can index the palette
|
||||
case (palette[p.hash()] == p):
|
||||
*@extract(OpIndex, output, &pos) = {
|
||||
OP_INDEX,
|
||||
p.hash()
|
||||
};
|
||||
|
||||
// check if we can use diff or luma
|
||||
case (prev != p && prev.a == p.a):
|
||||
// diff the pixels
|
||||
diff = p.rgb - prev.rgb;
|
||||
if (
|
||||
diff.r > -3 && diff.r < 2 &&
|
||||
diff.g > -3 && diff.g < 2 &&
|
||||
diff.b > -3 && diff.b < 2
|
||||
) {
|
||||
*@extract(OpDiff, output, &pos) = {
|
||||
OP_DIFF,
|
||||
(char)diff.r + 2,
|
||||
(char)diff.g + 2,
|
||||
(char)diff.b + 2
|
||||
};
|
||||
palette[p.hash()] = p;
|
||||
} else {
|
||||
// check luma eligibility
|
||||
luma = { diff.r - diff.g, diff.g, diff.b - diff.g };
|
||||
if (
|
||||
luma.r >= -8 && luma.r <= 7 &&
|
||||
luma.g >= -32 && luma.g <= 31 &&
|
||||
luma.b >= -8 && luma.b <= 7
|
||||
) {
|
||||
*@extract(OpLuma, output, &pos) = {
|
||||
OP_LUMA,
|
||||
(char)luma.g + 32,
|
||||
(char)luma.r + 8,
|
||||
(char)luma.b + 8
|
||||
};
|
||||
palette[p.hash()] = p;
|
||||
} else { nextcase; }
|
||||
}
|
||||
|
||||
// worst case scenario: just encode the raw pixel
|
||||
default:
|
||||
if (prev.a != p.a) {
|
||||
*@extract(OpRGBA, output, &pos) = { OP_RGBA, p.r, p.g, p.b, p.a };
|
||||
} else {
|
||||
*@extract(OpRGB, output, &pos) = { OP_RGB, p.r, p.g, p.b };
|
||||
}
|
||||
palette[p.hash()] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write end of stream
|
||||
output[pos:END_OF_STREAM.len] = END_OF_STREAM;
|
||||
pos += END_OF_STREAM.len;
|
||||
|
||||
return output[:pos];
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Decode a QOI image from memory.
|
||||
|
||||
If channels is set to QOIChannels.AUTO, the function will
|
||||
automatically determine the channels from the file's header.
|
||||
However, if channels is RGB or RGBA, the output format will be
|
||||
forced into this number of channels.
|
||||
|
||||
The desc struct will be filled with the width, height,
|
||||
channels and colorspace of the image.
|
||||
|
||||
The function returns an optional, which can either be a QOIError
|
||||
or a char[] pointing to the decoded pixels on success.
|
||||
|
||||
The returned pixel data should be free()d after use, or the decoding
|
||||
and use of the data should be wrapped in a @pool() { ... }; block.
|
||||
|
||||
@param [in] data `The QOI image data to decode`
|
||||
@param [&out] desc `The descriptor to fill with the image's info`
|
||||
@param channels `The channels to be used`
|
||||
*>
|
||||
fn char[]! decode(char[] data, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap())
|
||||
{
|
||||
// check input data
|
||||
if (data.len < Header.sizeof + END_OF_STREAM.len) return QOIError.INVALID_DATA?;
|
||||
|
||||
// get header
|
||||
Header* header = (Header*)data.ptr;
|
||||
|
||||
// check magic bytes (FourCC)
|
||||
if (bswap(header.be_magic) != 'qoif') return QOIError.INVALID_DATA?;
|
||||
|
||||
// copy header data to desc
|
||||
desc.width = bswap(header.be_width);
|
||||
desc.height = bswap(header.be_height);
|
||||
desc.channels = @enumcast(QOIChannels, header.channels)!; // Rethrow if invalid
|
||||
desc.colorspace = @enumcast(QOIColorspace, header.colorspace)!; // Rethrow if invalid
|
||||
if (desc.channels == AUTO) return QOIError.INVALID_DATA?; // Channels must be specified in the header
|
||||
|
||||
// check width and height
|
||||
if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_DATA?;
|
||||
|
||||
// check pixel count
|
||||
ulong pixels = (ulong)desc.width * (ulong)desc.height;
|
||||
if (pixels > PIXELS_MAX) return QOIError.TOO_MANY_PIXELS?;
|
||||
|
||||
uint pos = Header.sizeof; // Current position in data
|
||||
uint loc; // Current position in image (top-left corner)
|
||||
char run_length = 0; // Length of the current run
|
||||
char tag; // Current chunk tag
|
||||
|
||||
Pixel[64] palette; // Zero-initialized by default
|
||||
Pixel p = { 0, 0, 0, 255 };
|
||||
|
||||
if (channels == AUTO) channels = desc.channels;
|
||||
|
||||
// allocate memory for image data
|
||||
usz image_size = (usz)pixels * channels.id;
|
||||
char[] image = allocator::alloc_array(allocator, char, image_size);
|
||||
defer catch allocator::free(allocator, image);
|
||||
|
||||
for (loc = 0; loc < image_size; loc += channels.id)
|
||||
{
|
||||
// get chunk tag
|
||||
tag = data[pos];
|
||||
|
||||
// check for chunk type
|
||||
switch
|
||||
{
|
||||
case run_length > 0:
|
||||
run_length--;
|
||||
|
||||
case tag == OP_RGB:
|
||||
OpRGB* op = @extract(OpRGB, data, &pos);
|
||||
p = { op.red, op.green, op.blue, p.a };
|
||||
palette[p.hash()] = p;
|
||||
|
||||
case tag == OP_RGBA:
|
||||
OpRGBA* op = @extract(OpRGBA, data, &pos);
|
||||
p = { op.red, op.green, op.blue, op.alpha };
|
||||
palette[p.hash()] = p;
|
||||
|
||||
case tag >> 6 == OP_INDEX:
|
||||
OpIndex* op = @extract(OpIndex, data, &pos);
|
||||
p = palette[op.index];
|
||||
|
||||
case tag >> 6 == OP_DIFF:
|
||||
OpDiff* op = @extract(OpDiff, data, &pos);
|
||||
p.r += op.diff_red - 2;
|
||||
p.g += op.diff_green - 2;
|
||||
p.b += op.diff_blue - 2;
|
||||
palette[p.hash()] = p;
|
||||
|
||||
case tag >> 6 == OP_LUMA:
|
||||
OpLuma* op = @extract(OpLuma, data, &pos);
|
||||
int diff_green = op.diff_green - 32;
|
||||
p.r += (char)(op.diff_red_minus_green - 8 + diff_green);
|
||||
p.g += (char)(diff_green);
|
||||
p.b += (char)(op.diff_blue_minus_green - 8 + diff_green);
|
||||
palette[p.hash()] = p;
|
||||
|
||||
case tag >> 6 == OP_RUN:
|
||||
OpRun* op = @extract(OpRun, data, &pos);
|
||||
run_length = op.run;
|
||||
}
|
||||
|
||||
// draw the pixel
|
||||
if (channels == RGBA) { image[loc:4] = p.rgba; } else { image[loc:3] = p.rgb; }
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ***************************************************************************
|
||||
// *** ***
|
||||
// *** Main functions are at the top to make the file more readable. ***
|
||||
// *** From here on, helper functions and types are defined. ***
|
||||
// *** ***
|
||||
// ***************************************************************************
|
||||
module std::compression::qoi @private;
|
||||
|
||||
// 8-bit opcodes
|
||||
const OP_RGB = 0b11111110;
|
||||
const OP_RGBA = 0b11111111;
|
||||
// 2-bit opcodes
|
||||
const OP_INDEX = 0b00;
|
||||
const OP_DIFF = 0b01;
|
||||
const OP_LUMA = 0b10;
|
||||
const OP_RUN = 0b11;
|
||||
|
||||
struct Header @packed
|
||||
{
|
||||
uint be_magic; // magic bytes "qoif"
|
||||
uint be_width; // image width in pixels (BE)
|
||||
uint be_height; // image height in pixels (BE)
|
||||
|
||||
// informative fields
|
||||
char channels; // 3 = RGB, 4 = RGB
|
||||
char colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
|
||||
}
|
||||
|
||||
const char[*] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1};
|
||||
|
||||
// inefficient, but it's only run once at a time
|
||||
macro @enumcast($Type, raw)
|
||||
{
|
||||
foreach (value : $Type.values) {
|
||||
if (value.id == raw) return value;
|
||||
}
|
||||
return QOIError.INVALID_DATA?;
|
||||
}
|
||||
|
||||
distinct Pixel = inline char[<4>];
|
||||
macro char Pixel.hash(Pixel p) {
|
||||
return (p.r * 3 + p.g * 5 + p.b * 7 + p.a * 11) % 64;
|
||||
}
|
||||
|
||||
struct OpRGB // No need to use @packed here, the alignment is 1 anyways.
|
||||
{
|
||||
char tag;
|
||||
char red;
|
||||
char green;
|
||||
char blue;
|
||||
}
|
||||
struct OpRGBA @packed
|
||||
{
|
||||
char tag;
|
||||
char red;
|
||||
char green;
|
||||
char blue;
|
||||
char alpha;
|
||||
}
|
||||
bitstruct OpIndex : char
|
||||
{
|
||||
char tag : 6..7;
|
||||
char index : 0..5;
|
||||
}
|
||||
bitstruct OpDiff : char
|
||||
{
|
||||
char tag : 6..7;
|
||||
char diff_red : 4..5;
|
||||
char diff_green : 2..3;
|
||||
char diff_blue : 0..1;
|
||||
}
|
||||
bitstruct OpLuma : ushort
|
||||
{
|
||||
char tag : 6..7;
|
||||
char diff_green : 0..5;
|
||||
char diff_red_minus_green : 12..15;
|
||||
char diff_blue_minus_green : 8..11;
|
||||
}
|
||||
bitstruct OpRun : char
|
||||
{
|
||||
char tag : 6..7;
|
||||
char run : 0..5;
|
||||
}
|
||||
|
||||
// Macro used to locate chunks in data buffers.
|
||||
// Can be used both for reading and writing.
|
||||
macro @extract($Type, char[] data, uint* pos)
|
||||
{
|
||||
// slice data, then double cast
|
||||
$Type* chunk = ($Type*)data[*pos : $Type.sizeof].ptr;
|
||||
*pos += $Type.sizeof;
|
||||
return chunk;
|
||||
}
|
||||
@@ -2,148 +2,106 @@
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
struct ArenaAllocator
|
||||
struct ArenaAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
char[] data;
|
||||
usz used;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a memory arena for use using the provided bytes.
|
||||
*
|
||||
* @require this != null
|
||||
**/
|
||||
fn void ArenaAllocator.init(ArenaAllocator* this, char[] data)
|
||||
<*
|
||||
Initialize a memory arena for use using the provided bytes.
|
||||
*>
|
||||
fn ArenaAllocator* ArenaAllocator.init(&self, char[] data)
|
||||
{
|
||||
this.function = &arena_allocator_function;
|
||||
this.data = data;
|
||||
this.used = 0;
|
||||
self.data = data;
|
||||
self.used = 0;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require this != null
|
||||
**/
|
||||
fn void ArenaAllocator.reset(ArenaAllocator* this)
|
||||
fn void ArenaAllocator.clear(&self)
|
||||
{
|
||||
this.used = 0;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
struct ArenaAllocatorHeader
|
||||
struct ArenaAllocatorHeader @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require data `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! arena_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
|
||||
<*
|
||||
@require ptr != null
|
||||
*>
|
||||
fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
{
|
||||
ArenaAllocator* arena = (ArenaAllocator*)data;
|
||||
bool clear = false;
|
||||
switch (kind)
|
||||
assert((uptr)ptr >= (uptr)self.data.ptr, "Pointer originates from a different allocator.");
|
||||
ArenaAllocatorHeader* header = ptr - ArenaAllocatorHeader.sizeof;
|
||||
// Reclaim memory if it's the last element.
|
||||
if (ptr + header.size == &self.data[self.used])
|
||||
{
|
||||
case CALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
clear = true;
|
||||
nextcase;
|
||||
case ALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
assert(!old_pointer, "Unexpected old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
void* mem = arena._alloc(size, alignment, offset)!;
|
||||
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
case ALIGNED_REALLOC:
|
||||
case REALLOC:
|
||||
if (!size) nextcase FREE;
|
||||
if (!old_pointer) nextcase ALLOC;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
return arena._realloc(old_pointer, size, alignment, offset)!;
|
||||
case ALIGNED_FREE:
|
||||
case FREE:
|
||||
if (!old_pointer) return null;
|
||||
assert((uptr)old_pointer >= (uptr)arena.data.ptr, "Pointer originates from a different allocator.");
|
||||
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
|
||||
// Reclaim memory if it's the last element.
|
||||
if (old_pointer + header.size == &arena.data[arena.used])
|
||||
{
|
||||
arena.used -= header.size + ArenaAllocatorHeader.sizeof;
|
||||
}
|
||||
return null;
|
||||
case MARK:
|
||||
return (void*)(uptr)arena.used;
|
||||
case RESET:
|
||||
arena.used = size;
|
||||
return null;
|
||||
self.used -= header.size + ArenaAllocatorHeader.sizeof;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @require alignment > 0 `alignment must be non zero`
|
||||
* @require math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
|
||||
* @require this != null
|
||||
**/
|
||||
fn void*! ArenaAllocator._alloc(ArenaAllocator* this, usz size, usz alignment, usz offset) @private
|
||||
fn usz ArenaAllocator.mark(&self) @dynamic => self.used;
|
||||
fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark;
|
||||
|
||||
<*
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require size > 0
|
||||
*>
|
||||
fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
usz total_len = this.data.len;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
|
||||
void* start_mem = this.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + this.used + ArenaAllocatorHeader.sizeof + offset;
|
||||
void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(aligned_pointer_to_offset - this.data.ptr) + size - offset;
|
||||
void* start_mem = self.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof;
|
||||
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(mem - self.data.ptr) + size;
|
||||
if (end > total_len) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
this.used = end;
|
||||
void* mem = aligned_pointer_to_offset - offset;
|
||||
self.used = end;
|
||||
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
|
||||
header.size = size;
|
||||
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require alignment > 0 `alignment must be non zero`
|
||||
* @require math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
|
||||
* @require this != null
|
||||
**/
|
||||
fn void*! ArenaAllocator._realloc(ArenaAllocator* this, void *old_pointer, usz size, usz alignment, usz offset) @private
|
||||
<*
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require old_pointer != null
|
||||
@require size > 0
|
||||
*>
|
||||
fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
assert(old_pointer >= this.data.ptr, "Pointer originates from a different allocator.");
|
||||
usz total_len = this.data.len;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
|
||||
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
// Do last allocation and alignment match?
|
||||
if (&this.data[this.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer + offset, alignment))
|
||||
if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
if (old_size >= size)
|
||||
{
|
||||
this.used -= old_size - size;
|
||||
}
|
||||
else
|
||||
{
|
||||
usz new_used = this.used + size - old_size;
|
||||
if (new_used > total_len) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
this.used = new_used;
|
||||
}
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
{
|
||||
self.used -= old_size - size;
|
||||
}
|
||||
else
|
||||
{
|
||||
usz new_used = self.used + size - old_size;
|
||||
if (new_used > total_len) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.used = new_used;
|
||||
}
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
}
|
||||
// Otherwise just allocate new memory.
|
||||
void* mem = this._alloc(size, alignment, offset)!;
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
@@ -1,61 +1,58 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
struct DynamicArenaAllocator
|
||||
struct DynamicArenaAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
Allocator* backing_allocator;
|
||||
Allocator backing_allocator;
|
||||
DynamicArenaPage* page;
|
||||
DynamicArenaPage* unused_page;
|
||||
usz page_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require page_size >= 128
|
||||
* @require this != null
|
||||
**/
|
||||
fn void DynamicArenaAllocator.init(DynamicArenaAllocator* this, usz page_size, Allocator* using = mem::heap())
|
||||
<*
|
||||
@param [&inout] allocator
|
||||
@require page_size >= 128
|
||||
*>
|
||||
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator allocator)
|
||||
{
|
||||
this.function = &dynamic_arena_allocator_function;
|
||||
this.page = null;
|
||||
this.unused_page = null;
|
||||
this.page_size = page_size;
|
||||
this.backing_allocator = using;
|
||||
self.page = null;
|
||||
self.unused_page = null;
|
||||
self.page_size = page_size;
|
||||
self.backing_allocator = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require this != null
|
||||
**/
|
||||
fn void DynamicArenaAllocator.free(DynamicArenaAllocator* this)
|
||||
fn void DynamicArenaAllocator.free(&self)
|
||||
{
|
||||
DynamicArenaPage* page = this.page;
|
||||
DynamicArenaPage* page = self.page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
free(page, .using = this.backing_allocator);
|
||||
allocator::free(self.backing_allocator, page.memory);
|
||||
allocator::free(self.backing_allocator, page);
|
||||
page = next_page;
|
||||
}
|
||||
page = this.unused_page;
|
||||
page = self.unused_page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
free(page, .using = this.backing_allocator);
|
||||
allocator::free(self.backing_allocator, page.memory);
|
||||
allocator::free(self.backing_allocator, page);
|
||||
page = next_page;
|
||||
}
|
||||
this.page = null;
|
||||
this.unused_page = null;
|
||||
self.page = null;
|
||||
self.unused_page = null;
|
||||
}
|
||||
|
||||
struct DynamicArenaPage
|
||||
struct DynamicArenaPage @local
|
||||
{
|
||||
void* memory;
|
||||
void* prev_arena;
|
||||
usz total;
|
||||
usz used;
|
||||
void* last_ptr;
|
||||
void* current_stack_ptr;
|
||||
}
|
||||
|
||||
struct DynamicArenaChunk @local
|
||||
@@ -63,27 +60,28 @@ struct DynamicArenaChunk @local
|
||||
usz size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require ptr && this
|
||||
* @require this.page `tried to free pointer on invalid allocator`
|
||||
*/
|
||||
fn void DynamicArenaAllocator.free_ptr(DynamicArenaAllocator* this, void* ptr) @private
|
||||
<*
|
||||
@require ptr
|
||||
@require self.page `tried to free pointer on invalid allocator`
|
||||
*>
|
||||
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
{
|
||||
DynamicArenaPage* current_page = this.page;
|
||||
if (ptr == current_page.last_ptr)
|
||||
DynamicArenaPage* current_page = self.page;
|
||||
if (ptr == current_page.current_stack_ptr)
|
||||
{
|
||||
current_page.used = (usz)((ptr - DEFAULT_SIZE_PREFIX) - current_page.memory);
|
||||
}
|
||||
current_page.last_ptr = null;
|
||||
current_page.current_stack_ptr = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require old_pointer && size > 0
|
||||
* @require this.page `tried to realloc pointer on invalid allocator`
|
||||
*/
|
||||
fn void*! DynamicArenaAllocator._realloc(DynamicArenaAllocator* this, void* old_pointer, usz size, usz alignment, usz offset) @local
|
||||
<*
|
||||
@require size > 0 `Resize doesn't support zeroing`
|
||||
@require old_pointer != null `Resize doesn't handle null pointers`
|
||||
@require self.page `tried to realloc pointer on invalid allocator`
|
||||
*>
|
||||
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
DynamicArenaPage* current_page = this.page;
|
||||
DynamicArenaPage* current_page = self.page;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
|
||||
usz old_size = *old_size_ptr;
|
||||
@@ -91,13 +89,13 @@ fn void*! DynamicArenaAllocator._realloc(DynamicArenaAllocator* this, void* old_
|
||||
if (old_size >= size && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
*old_size_ptr = size;
|
||||
if (current_page.last_ptr == old_pointer)
|
||||
if (current_page.current_stack_ptr == old_pointer)
|
||||
{
|
||||
current_page.used = (usz)((old_pointer - DEFAULT_SIZE_PREFIX) - current_page.memory);
|
||||
}
|
||||
return old_pointer;
|
||||
}
|
||||
if REUSE: (current_page.last_ptr == old_pointer && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
if REUSE: (current_page.current_stack_ptr == old_pointer && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
assert(size > old_size);
|
||||
usz add_size = size - old_size;
|
||||
@@ -106,142 +104,99 @@ fn void*! DynamicArenaAllocator._realloc(DynamicArenaAllocator* this, void* old_
|
||||
current_page.used += add_size;
|
||||
return old_pointer;
|
||||
}
|
||||
void* new_mem = this._alloc(size, alignment, offset)!;
|
||||
void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return new_mem;
|
||||
}
|
||||
|
||||
fn void DynamicArenaAllocator.reset(DynamicArenaAllocator* this) @private
|
||||
fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
|
||||
{
|
||||
DynamicArenaPage* page = this.page;
|
||||
DynamicArenaPage** unused_page_ptr = &this.unused_page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
page.used = 0;
|
||||
DynamicArenaPage* prev_unused = *unused_page_ptr;
|
||||
*unused_page_ptr = page;
|
||||
page.prev_arena = prev_unused;
|
||||
page = next_page;
|
||||
}
|
||||
this.page = page;
|
||||
assert(mark == 0, "Unexpectedly reset dynamic arena allocator with mark %d", mark);
|
||||
DynamicArenaPage* page = self.page;
|
||||
DynamicArenaPage** unused_page_ptr = &self.unused_page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
page.used = 0;
|
||||
DynamicArenaPage* prev_unused = *unused_page_ptr;
|
||||
*unused_page_ptr = page;
|
||||
page.prev_arena = prev_unused;
|
||||
page = next_page;
|
||||
}
|
||||
self.page = page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
*/
|
||||
fn void*! DynamicArenaAllocator._alloc_new(DynamicArenaAllocator* this, usz size, usz alignment, usz offset) @local
|
||||
<*
|
||||
@require math::is_power_of_2(alignment)
|
||||
@require size > 0
|
||||
*>
|
||||
fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @local
|
||||
{
|
||||
// First, make sure that we can align it, extending the page size if needed.
|
||||
usz page_size = max(this.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + offset, alignment) - offset);
|
||||
|
||||
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + alignment, alignment));
|
||||
assert(page_size > size + DynamicArenaChunk.sizeof);
|
||||
// Grab the page without alignment (we do it ourselves)
|
||||
void* mem = this.backing_allocator.alloc(page_size)!;
|
||||
DynamicArenaPage*! page = malloc(DynamicArenaPage, .using = this.backing_allocator);
|
||||
void* mem = allocator::malloc_try(self.backing_allocator, page_size)!;
|
||||
DynamicArenaPage*! page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
|
||||
if (catch err = page)
|
||||
{
|
||||
free(mem, .using = this.backing_allocator);
|
||||
allocator::free(self.backing_allocator, mem);
|
||||
return err?;
|
||||
}
|
||||
page.memory = mem;
|
||||
void* mem_start = mem::aligned_pointer(mem + offset + DynamicArenaChunk.sizeof, alignment) - offset;
|
||||
assert(mem_start + size < mem + page_size);
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
|
||||
chunk.size = size;
|
||||
page.prev_arena = this.page;
|
||||
page.memory = mem;
|
||||
void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment);
|
||||
assert(mem_start + size < mem + page_size);
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
|
||||
chunk.size = size;
|
||||
page.prev_arena = self.page;
|
||||
page.total = page_size;
|
||||
page.used = mem_start + size - page.memory;
|
||||
this.page = page;
|
||||
page.last_ptr = mem_start;
|
||||
self.page = page;
|
||||
page.current_stack_ptr = mem_start;
|
||||
return mem_start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
* @require this
|
||||
*/
|
||||
fn void*! DynamicArenaAllocator._alloc(DynamicArenaAllocator* this, usz size, usz alignment, usz offset) @local
|
||||
<*
|
||||
@require size > 0 `acquire expects size > 0`
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
*>
|
||||
fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
DynamicArenaPage* page = this.page;
|
||||
if (!page && this.unused_page)
|
||||
{
|
||||
this.page = page = this.unused_page;
|
||||
this.unused_page = page.prev_arena;
|
||||
page.prev_arena = null;
|
||||
}
|
||||
if (!page) return this._alloc_new(size, alignment, offset);
|
||||
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
|
||||
usz new_used = start - page.memory + size;
|
||||
if ALLOCATE_NEW: (new_used > page.total)
|
||||
{
|
||||
if ((page = this.unused_page))
|
||||
{
|
||||
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
|
||||
new_used = start + size - page.memory;
|
||||
if (page.total >= new_used)
|
||||
{
|
||||
this.unused_page = page.prev_arena;
|
||||
page.prev_arena = this.page;
|
||||
this.page = page;
|
||||
break ALLOCATE_NEW;
|
||||
}
|
||||
}
|
||||
return this._alloc_new(size, alignment, offset);
|
||||
}
|
||||
|
||||
page.used = new_used;
|
||||
assert(start + size == page.memory + page.used);
|
||||
void* mem = start;
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
|
||||
chunk.size = size;
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require data `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! dynamic_arena_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
{
|
||||
DynamicArenaAllocator* allocator = (DynamicArenaAllocator*)data;
|
||||
switch (kind)
|
||||
{
|
||||
case CALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
assert(!old_pointer, "Unexpected no old pointer for calloc.");
|
||||
if (!size) return null;
|
||||
void* mem = allocator._alloc(size, alignment, offset)!;
|
||||
mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
case ALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
assert(!old_pointer, "Unexpected no old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
return allocator._alloc(size, alignment, offset);
|
||||
case REALLOC:
|
||||
case ALIGNED_REALLOC:
|
||||
if (!size)
|
||||
DynamicArenaPage* page = self.page;
|
||||
void* ptr = {|
|
||||
if (!page && self.unused_page)
|
||||
{
|
||||
self.page = page = self.unused_page;
|
||||
self.unused_page = page.prev_arena;
|
||||
page.prev_arena = null;
|
||||
}
|
||||
if (!page) return self._alloc_new(size, alignment);
|
||||
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
|
||||
usz new_used = start - page.memory + size;
|
||||
if ALLOCATE_NEW: (new_used > page.total)
|
||||
{
|
||||
if ((page = self.unused_page))
|
||||
{
|
||||
if (!old_pointer) return null;
|
||||
allocator.free_ptr(old_pointer);
|
||||
return null;
|
||||
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
|
||||
new_used = start + size - page.memory;
|
||||
if (page.total >= new_used)
|
||||
{
|
||||
self.unused_page = page.prev_arena;
|
||||
page.prev_arena = self.page;
|
||||
self.page = page;
|
||||
break ALLOCATE_NEW;
|
||||
}
|
||||
}
|
||||
if (!old_pointer) return allocator._alloc(size, alignment, offset);
|
||||
void* mem = allocator._realloc(old_pointer, size, alignment, offset)!;
|
||||
return mem;
|
||||
case ALIGNED_FREE:
|
||||
case FREE:
|
||||
if (!old_pointer) return null;
|
||||
allocator.free_ptr(old_pointer);
|
||||
return null;
|
||||
case MARK:
|
||||
unreachable("Tried to mark a dynamic arena");
|
||||
case RESET:
|
||||
allocator.reset();
|
||||
return null;
|
||||
}
|
||||
unreachable();
|
||||
return self._alloc_new(size, alignment);
|
||||
}
|
||||
page.used = new_used;
|
||||
assert(start + size == page.memory + page.used);
|
||||
void* mem = start;
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
|
||||
chunk.size = size;
|
||||
return mem;
|
||||
|}!;
|
||||
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -1,99 +1,85 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
def MemoryAllocFn = fn char[]!(usz);
|
||||
|
||||
struct SimpleHeapAllocator
|
||||
struct SimpleHeapAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
MemoryAllocFn alloc_fn;
|
||||
Header* free_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require this "Unexpectedly missing the allocator"
|
||||
* @require allocator "An underlying memory provider must be given"
|
||||
* @require !this.free_list "The allocator may not be already initialized"
|
||||
**/
|
||||
fn void SimpleHeapAllocator.init(SimpleHeapAllocator* this, MemoryAllocFn allocator)
|
||||
<*
|
||||
@require allocator "An underlying memory provider must be given"
|
||||
@require !self.free_list "The allocator may not be already initialized"
|
||||
*>
|
||||
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
|
||||
{
|
||||
this.alloc_fn = allocator;
|
||||
this.allocator = { &simple_heap_allocator_function };
|
||||
this.free_list = null;
|
||||
self.alloc_fn = allocator;
|
||||
self.free_list = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require this `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! simple_heap_allocator_function(Allocator* this, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
SimpleHeapAllocator* heap = (SimpleHeapAllocator*)this;
|
||||
switch (kind)
|
||||
{
|
||||
case ALIGNED_ALLOC:
|
||||
return @aligned_alloc(heap._alloc, size, alignment, offset);
|
||||
case ALLOC:
|
||||
return heap._alloc(size);
|
||||
case ALIGNED_CALLOC:
|
||||
return @aligned_calloc(heap._calloc, size, alignment, offset);
|
||||
case CALLOC:
|
||||
return heap._calloc(size);
|
||||
case ALIGNED_REALLOC:
|
||||
if (!size) nextcase ALIGNED_FREE;
|
||||
if (!old_pointer) nextcase ALIGNED_CALLOC;
|
||||
return @aligned_realloc(heap._calloc, heap._free, old_pointer, size, alignment, offset);
|
||||
case REALLOC:
|
||||
if (!size) nextcase FREE;
|
||||
if (!old_pointer) nextcase CALLOC;
|
||||
return heap._realloc(old_pointer, size);
|
||||
case RESET:
|
||||
return AllocationFailure.UNSUPPORTED_OPERATION?;
|
||||
case ALIGNED_FREE:
|
||||
@aligned_free(heap._free, old_pointer)!;
|
||||
return null;
|
||||
case FREE:
|
||||
heap._free(old_pointer);
|
||||
return null;
|
||||
default:
|
||||
unreachable();
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
return alignment > 0 ? @aligned_alloc(self._calloc, size, alignment) : self._calloc(size);
|
||||
}
|
||||
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size);
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
return alignment > 0
|
||||
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment)
|
||||
: self._realloc(old_pointer, size);
|
||||
}
|
||||
|
||||
fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
|
||||
{
|
||||
if (aligned)
|
||||
{
|
||||
@aligned_free(self._free, old_pointer)!!;
|
||||
}
|
||||
else
|
||||
{
|
||||
self._free(old_pointer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require this && old_pointer && bytes > 0
|
||||
**/
|
||||
fn void*! SimpleHeapAllocator._realloc(SimpleHeapAllocator* this, void* old_pointer, usz bytes)
|
||||
<*
|
||||
@require old_pointer && bytes > 0
|
||||
*>
|
||||
fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
|
||||
{
|
||||
// Find the block header.
|
||||
Header* block = (Header*)old_pointer - 1;
|
||||
if (block.size >= bytes) return old_pointer;
|
||||
void* new = this._alloc(bytes)!;
|
||||
void* new = self._alloc(bytes)!;
|
||||
usz max_to_copy = math::min(block.size, bytes);
|
||||
mem::copy(new, old_pointer, max_to_copy);
|
||||
this._free(old_pointer);
|
||||
self._free(old_pointer);
|
||||
return new;
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator._calloc(SimpleHeapAllocator* this, usz bytes) @local
|
||||
fn void*! SimpleHeapAllocator._calloc(&self, usz bytes) @local
|
||||
{
|
||||
void* data = this._alloc(bytes)!;
|
||||
void* data = self._alloc(bytes)!;
|
||||
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator._alloc(SimpleHeapAllocator* this, usz bytes) @local
|
||||
fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
|
||||
{
|
||||
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
if (!this.free_list)
|
||||
{
|
||||
this.add_block(aligned_bytes)!;
|
||||
}
|
||||
if (!self.free_list)
|
||||
{
|
||||
self.add_block(aligned_bytes)!;
|
||||
}
|
||||
|
||||
Header* current = this.free_list;
|
||||
Header* current = self.free_list;
|
||||
Header* previous = current;
|
||||
while (current)
|
||||
{
|
||||
@@ -102,21 +88,21 @@ fn void*! SimpleHeapAllocator._alloc(SimpleHeapAllocator* this, usz bytes) @loca
|
||||
case current.size >= aligned_bytes && current.size <= aligned_bytes + Header.sizeof + 64:
|
||||
if (current == previous)
|
||||
{
|
||||
this.free_list = current.next;
|
||||
self.free_list = current.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
previous.next = current.next;
|
||||
}
|
||||
current.next = null;
|
||||
return current + 1;
|
||||
case current.size > aligned_bytes:
|
||||
}
|
||||
current.next = null;
|
||||
return current + 1;
|
||||
case current.size > aligned_bytes:
|
||||
Header* unallocated = (Header*)((char*)current + aligned_bytes + Header.sizeof);
|
||||
unallocated.size = current.size - aligned_bytes;
|
||||
unallocated.size = current.size - aligned_bytes - Header.sizeof;
|
||||
unallocated.next = current.next;
|
||||
if (current == this.free_list)
|
||||
if (current == self.free_list)
|
||||
{
|
||||
this.free_list = unallocated;
|
||||
self.free_list = unallocated;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -130,22 +116,22 @@ fn void*! SimpleHeapAllocator._alloc(SimpleHeapAllocator* this, usz bytes) @loca
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
this.add_block(aligned_bytes)!;
|
||||
return this.alloc(aligned_bytes);
|
||||
self.add_block(aligned_bytes)!;
|
||||
return self._alloc(aligned_bytes);
|
||||
}
|
||||
|
||||
fn void! SimpleHeapAllocator.add_block(SimpleHeapAllocator* this, usz aligned_bytes) @local
|
||||
fn void! SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
|
||||
{
|
||||
assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes);
|
||||
char[] result = this.alloc_fn(aligned_bytes + Header.sizeof)!;
|
||||
char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!;
|
||||
Header* new_block = (Header*)result.ptr;
|
||||
new_block.size = result.len - Header.sizeof;
|
||||
new_block.next = null;
|
||||
this._free(new_block + 1);
|
||||
self._free(new_block + 1);
|
||||
}
|
||||
|
||||
|
||||
fn void SimpleHeapAllocator._free(SimpleHeapAllocator* this, void* ptr) @local
|
||||
fn void SimpleHeapAllocator._free(&self, void* ptr) @local
|
||||
{
|
||||
// Empty ptr -> do nothing.
|
||||
if (!ptr) return;
|
||||
@@ -153,15 +139,15 @@ fn void SimpleHeapAllocator._free(SimpleHeapAllocator* this, void* ptr) @local
|
||||
// Find the block header.
|
||||
Header* block = (Header*)ptr - 1;
|
||||
|
||||
// No free list? Then just return this.
|
||||
if (!this.free_list)
|
||||
// No free list? Then just return self.
|
||||
if (!self.free_list)
|
||||
{
|
||||
this.free_list = block;
|
||||
self.free_list = block;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find where in the list it should be inserted.
|
||||
Header* current = this.free_list;
|
||||
Header* current = self.free_list;
|
||||
Header* prev = current;
|
||||
while (current)
|
||||
{
|
||||
@@ -178,46 +164,46 @@ fn void SimpleHeapAllocator._free(SimpleHeapAllocator* this, void* ptr) @local
|
||||
if (current)
|
||||
{
|
||||
// Insert after the current block.
|
||||
// Are the blocks adjacent?
|
||||
if (current == (Header*)((char*)(block + 1) + block.size))
|
||||
{
|
||||
// Merge
|
||||
block.size += current.size + Header.sizeof;
|
||||
block.next = current.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chain to current
|
||||
block.next = current;
|
||||
}
|
||||
// Are the blocks adjacent?
|
||||
if (current == (Header*)((char*)(block + 1) + block.size))
|
||||
{
|
||||
// Merge
|
||||
block.size += current.size + Header.sizeof;
|
||||
block.next = current.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chain to current
|
||||
block.next = current;
|
||||
}
|
||||
}
|
||||
if (prev == current)
|
||||
{
|
||||
// Swap new start of free list
|
||||
this.free_list = block;
|
||||
self.free_list = block;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prev adjacent?
|
||||
if (block == (Header*)((char*)(prev + 1) + prev.size))
|
||||
{
|
||||
prev.size += block.size + Header.sizeof;
|
||||
prev.size += block.size + Header.sizeof;
|
||||
prev.next = block.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Link prev to block
|
||||
prev.next = block;
|
||||
// Link prev to block
|
||||
prev.next = block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
union Header @private
|
||||
union Header @local
|
||||
{
|
||||
struct
|
||||
{
|
||||
Header* next;
|
||||
usz size;
|
||||
}
|
||||
usz align;
|
||||
usz size;
|
||||
}
|
||||
usz align;
|
||||
}
|
||||
|
||||
161
lib/std/core/allocators/libc_allocator.c3
Normal file
161
lib/std/core/allocators/libc_allocator.c3
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator @if(env::LIBC);
|
||||
import std::io;
|
||||
import libc;
|
||||
|
||||
const LibcAllocator LIBC_ALLOCATOR = {};
|
||||
distinct LibcAllocator (Allocator, Printable) = uptr;
|
||||
|
||||
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
|
||||
fn usz! LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
|
||||
|
||||
module std::core::mem::allocator @if(env::POSIX);
|
||||
import std::os;
|
||||
import libc;
|
||||
|
||||
|
||||
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
void* data @noinit;
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
}
|
||||
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
else
|
||||
{
|
||||
void* data @noinit;
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(data = libc::malloc(bytes))) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
void* new_ptr;
|
||||
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
|
||||
$switch
|
||||
$case env::DARWIN:
|
||||
usz old_usable_size = darwin::malloc_size(old_ptr);
|
||||
$case env::LINUX:
|
||||
usz old_usable_size = linux::malloc_usable_size(old_ptr);
|
||||
$default:
|
||||
usz old_usable_size = new_bytes;
|
||||
$endswitch
|
||||
|
||||
usz copy_size = new_bytes < old_usable_size ? new_bytes : old_usable_size;
|
||||
mem::copy(new_ptr, old_ptr, copy_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
libc::free(old_ptr);
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
{
|
||||
libc::free(old_ptr);
|
||||
}
|
||||
|
||||
module std::core::mem::allocator @if(env::WIN32);
|
||||
import std::os::win32;
|
||||
import libc;
|
||||
|
||||
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
if (alignment > 0)
|
||||
{
|
||||
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
|
||||
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
if (alignment)
|
||||
{
|
||||
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
{
|
||||
if (aligned)
|
||||
{
|
||||
win32::_aligned_free(old_ptr);
|
||||
return;
|
||||
}
|
||||
libc::free(old_ptr);
|
||||
}
|
||||
|
||||
module std::core::mem::allocator @if(!env::WIN32 && !env::POSIX && env::LIBC);
|
||||
import libc;
|
||||
|
||||
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1);
|
||||
return data ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
else
|
||||
{
|
||||
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
|
||||
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
if (alignment)
|
||||
{
|
||||
void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!;
|
||||
return data ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
|
||||
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
{
|
||||
if (aligned)
|
||||
{
|
||||
@aligned_free(libc::free, old_ptr)!!;
|
||||
}
|
||||
else
|
||||
{
|
||||
libc::free(old_ptr);
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator;
|
||||
import libc;
|
||||
|
||||
const Allocator _NULL_ALLOCATOR @private = { &null_allocator_fn };
|
||||
const Allocator _SYSTEM_ALLOCATOR @private = { &libc_allocator_fn };
|
||||
|
||||
fn void*! null_allocator_fn(Allocator* this, usz bytes, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case ALLOC:
|
||||
case CALLOC:
|
||||
case REALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
case ALIGNED_REALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
return AllocationFailure.OUT_OF_MEMORY?;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
struct AlignedBlock
|
||||
{
|
||||
usz len;
|
||||
void* start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
|
||||
$if $checks(#alloc_fn(bytes)!):
|
||||
void* data = #alloc_fn(header + bytes)!;
|
||||
$else
|
||||
void* data = #alloc_fn(header + bytes);
|
||||
$endif
|
||||
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
|
||||
assert(mem > data);
|
||||
AlignedBlock* desc = (AlignedBlock*)mem - 1;
|
||||
*desc = { bytes, data };
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_calloc(#calloc_fn, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
|
||||
$if $checks(#calloc_fn(bytes)!):
|
||||
void* data = #calloc_fn(header + bytes)!;
|
||||
$else
|
||||
void* data = #calloc_fn(header + bytes);
|
||||
$endif
|
||||
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
|
||||
AlignedBlock* desc = (AlignedBlock*)mem - 1;
|
||||
assert(mem > data);
|
||||
*desc = { bytes, data };
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
void* data_start = desc.start;
|
||||
void* new_data = @aligned_calloc(#calloc_fn, bytes, alignment, offset)!;
|
||||
mem::copy(new_data, old_pointer, desc.len > bytes ? desc.len : bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
$if $checks(#free_fn(data_start)!):
|
||||
#free_fn(data_start)!;
|
||||
$else
|
||||
#free_fn(data_start);
|
||||
$endif
|
||||
return new_data;
|
||||
}
|
||||
|
||||
macro void! @aligned_free(#free_fn, void* old_pointer)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if $checks(#free_fn(desc.start)!):
|
||||
#free_fn(desc.start)!;
|
||||
$else
|
||||
#free_fn(desc.start);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void*! libc_allocator_fn(Allocator* unused, usz bytes, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @inline
|
||||
{
|
||||
if (!alignment) alignment = mem::DEFAULT_MEM_ALIGNMENT;
|
||||
assert(math::is_power_of_2(alignment), "Alignment was not a power of 2");
|
||||
|
||||
void* data;
|
||||
switch (kind)
|
||||
{
|
||||
case ALIGNED_ALLOC:
|
||||
data = @aligned_alloc(libc::malloc, bytes, alignment, offset)!!;
|
||||
case ALLOC:
|
||||
data = libc::malloc(bytes);
|
||||
case ALIGNED_CALLOC:
|
||||
data = @aligned_calloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment, offset)!!;
|
||||
case CALLOC:
|
||||
data = libc::calloc(bytes, 1);
|
||||
case ALIGNED_REALLOC:
|
||||
if (!bytes) nextcase ALIGNED_FREE;
|
||||
if (!old_pointer) nextcase ALIGNED_CALLOC;
|
||||
data = @aligned_realloc(fn void*(usz bytes) => libc::calloc(bytes, 1), libc::free, old_pointer, bytes, alignment, offset)!!;
|
||||
case REALLOC:
|
||||
if (!bytes) nextcase FREE;
|
||||
if (!old_pointer) nextcase CALLOC;
|
||||
data = libc::realloc(old_pointer, bytes);
|
||||
case RESET:
|
||||
return AllocationFailure.UNSUPPORTED_OPERATION?;
|
||||
case ALIGNED_FREE:
|
||||
@aligned_free(libc::free, old_pointer)!!;
|
||||
return null;
|
||||
case FREE:
|
||||
libc::free(old_pointer);
|
||||
return null;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
return data;
|
||||
}
|
||||
@@ -1,34 +1,13 @@
|
||||
module std::core::mem::allocator;
|
||||
|
||||
struct OnStackAllocator
|
||||
struct OnStackAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
Allocator* backing_allocator;
|
||||
Allocator backing_allocator;
|
||||
char[] data;
|
||||
usz used;
|
||||
OnStackAllocatorExtraChunk* chunk;
|
||||
}
|
||||
|
||||
macro void @stack_mem(usz $size; @body(Allocator* mem)) @builtin
|
||||
{
|
||||
char[$size] buffer;
|
||||
OnStackAllocator allocator;
|
||||
allocator.init(&buffer, mem::heap());
|
||||
defer allocator.free();
|
||||
@body(&allocator);
|
||||
}
|
||||
|
||||
macro void @stack_pool(usz $size; @body) @builtin
|
||||
{
|
||||
char[$size] buffer;
|
||||
OnStackAllocator allocator;
|
||||
allocator.init(&buffer, mem::heap());
|
||||
defer allocator.free();
|
||||
mem::@scoped(&allocator)
|
||||
{
|
||||
@body();
|
||||
};
|
||||
}
|
||||
|
||||
struct OnStackAllocatorExtraChunk @local
|
||||
{
|
||||
@@ -37,41 +16,37 @@ struct OnStackAllocatorExtraChunk @local
|
||||
void* data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a memory arena for use using the provided bytes.
|
||||
*
|
||||
* @require this != null
|
||||
**/
|
||||
fn void OnStackAllocator.init(OnStackAllocator* this, char[] data, Allocator* using = mem::heap())
|
||||
<*
|
||||
Initialize a memory arena for use using the provided bytes.
|
||||
|
||||
@param [&inout] allocator
|
||||
*>
|
||||
fn void OnStackAllocator.init(&self, char[] data, Allocator allocator)
|
||||
{
|
||||
this.function = &on_stack_allocator_function;
|
||||
this.data = data;
|
||||
this.backing_allocator = using;
|
||||
this.used = 0;
|
||||
self.data = data;
|
||||
self.backing_allocator = allocator;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require this != null
|
||||
**/
|
||||
fn void OnStackAllocator.free(OnStackAllocator* this)
|
||||
fn void OnStackAllocator.free(&self)
|
||||
{
|
||||
OnStackAllocatorExtraChunk* chunk = this.chunk;
|
||||
OnStackAllocatorExtraChunk* chunk = self.chunk;
|
||||
while (chunk)
|
||||
{
|
||||
if (chunk.is_aligned)
|
||||
{
|
||||
this.backing_allocator.free_aligned(chunk.data)!!;
|
||||
allocator::free_aligned(self.backing_allocator, chunk.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.backing_allocator.free(chunk.data)!!;
|
||||
allocator::free(self.backing_allocator, chunk.data);
|
||||
}
|
||||
void* old = chunk;
|
||||
chunk = chunk.prev;
|
||||
this.backing_allocator.free(old)!!;
|
||||
allocator::free(self.backing_allocator, old);
|
||||
}
|
||||
this.chunk = null;
|
||||
this.used = 0;
|
||||
self.chunk = null;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
struct OnStackAllocatorHeader
|
||||
@@ -80,49 +55,14 @@ struct OnStackAllocatorHeader
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require data `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! on_stack_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
<*
|
||||
@require old_pointer
|
||||
*>
|
||||
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
|
||||
{
|
||||
OnStackAllocator* allocator = (OnStackAllocator*)data;
|
||||
bool clear = false;
|
||||
switch (kind)
|
||||
{
|
||||
case CALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
clear = true;
|
||||
nextcase;
|
||||
case ALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
assert(!old_pointer, "Unexpected old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
return on_stack_allocator_alloc(allocator, size, alignment, offset, clear, kind == AllocationKind.ALIGNED_ALLOC || kind == AllocationKind.ALIGNED_CALLOC);
|
||||
case ALIGNED_REALLOC:
|
||||
case REALLOC:
|
||||
if (!size) nextcase FREE;
|
||||
if (!old_pointer) nextcase ALLOC;
|
||||
return on_stack_allocator_realloc(allocator, old_pointer, size, alignment, offset, kind == AllocationKind.ALIGNED_REALLOC);
|
||||
case ALIGNED_FREE:
|
||||
case FREE:
|
||||
if (!old_pointer) return null;
|
||||
if (allocation_in_stack_mem(allocator, old_pointer)) return null;
|
||||
on_stack_allocator_remove_chunk(allocator, old_pointer);
|
||||
if (kind == AllocationKind.ALIGNED_FREE)
|
||||
{
|
||||
allocator.backing_allocator.free_aligned(old_pointer)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator.backing_allocator.free(old_pointer)!;
|
||||
}
|
||||
return null;
|
||||
case MARK:
|
||||
case RESET:
|
||||
return AllocationFailure.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
unreachable();
|
||||
if (allocation_in_stack_mem(self, old_pointer)) return;
|
||||
on_stack_allocator_remove_chunk(self, old_pointer);
|
||||
self.backing_allocator.release(old_pointer, aligned);
|
||||
}
|
||||
|
||||
fn bool allocation_in_stack_mem(OnStackAllocator* a, void* ptr) @local
|
||||
@@ -139,7 +79,7 @@ fn void on_stack_allocator_remove_chunk(OnStackAllocator* a, void* ptr) @local
|
||||
if (chunk.data == ptr)
|
||||
{
|
||||
*addr = chunk.prev;
|
||||
a.backing_allocator.free(chunk)!!;
|
||||
allocator::free(a.backing_allocator, chunk);
|
||||
return;
|
||||
}
|
||||
addr = &chunk.prev;
|
||||
@@ -159,76 +99,51 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require size > 0
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
|
||||
* @require a != null
|
||||
**/
|
||||
fn void*! on_stack_allocator_realloc(OnStackAllocator* a, void* old_pointer, usz size, usz alignment, usz offset, bool aligned) @local @inline
|
||||
<*
|
||||
@require size > 0
|
||||
@require old_pointer != null
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
*>
|
||||
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
if (!allocation_in_stack_mem(a, old_pointer))
|
||||
if (!allocation_in_stack_mem(self, old_pointer))
|
||||
{
|
||||
OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(a, old_pointer);
|
||||
OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(self, old_pointer);
|
||||
assert(chunk, "Tried to realloc pointer not belonging to the allocator");
|
||||
if (aligned)
|
||||
{
|
||||
return chunk.data = a.backing_allocator.realloc_aligned(old_pointer, size, alignment, offset)!;
|
||||
}
|
||||
return chunk.data = a.backing_allocator.realloc(old_pointer, size)!;
|
||||
return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment)!;
|
||||
}
|
||||
|
||||
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
void* mem = on_stack_allocator_alloc(a, size, alignment, offset, true, aligned)!;
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
import std::io;
|
||||
/**
|
||||
* @require size > 0
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
|
||||
* @require a != null
|
||||
**/
|
||||
fn void*! on_stack_allocator_alloc(OnStackAllocator* a, usz size, usz alignment, usz offset, bool clear, bool aligned) @local @inline
|
||||
<*
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require size > 0
|
||||
*>
|
||||
fn void*! OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
bool aligned = alignment > 0;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = a.data.len;
|
||||
void* start_mem = a.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + a.used + OnStackAllocatorHeader.sizeof + offset;
|
||||
void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(aligned_pointer_to_offset - a.data.ptr) + size - offset;
|
||||
|
||||
Allocator* backing_allocator = a.backing_allocator;
|
||||
usz total_len = self.data.len;
|
||||
void* start_mem = self.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof ;
|
||||
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(mem - self.data.ptr) + size;
|
||||
Allocator backing_allocator = self.backing_allocator;
|
||||
|
||||
if (end > total_len)
|
||||
{
|
||||
OnStackAllocatorExtraChunk* chunk = backing_allocator.alloc(OnStackAllocatorExtraChunk.sizeof)!;
|
||||
defer catch backing_allocator.free(chunk)!!;
|
||||
defer try a.chunk = chunk;
|
||||
*chunk = { .prev = a.chunk, .is_aligned = aligned };
|
||||
void* data @noinit;
|
||||
switch
|
||||
{
|
||||
case !aligned && !clear:
|
||||
data = backing_allocator.alloc(size)!;
|
||||
case aligned && !clear:
|
||||
data = backing_allocator.alloc_aligned(size, alignment, offset)!;
|
||||
case !aligned && clear:
|
||||
data = backing_allocator.calloc(size)!;
|
||||
case aligned && clear:
|
||||
data = backing_allocator.calloc_aligned(size, alignment, offset)!;
|
||||
}
|
||||
return chunk.data = data;
|
||||
OnStackAllocatorExtraChunk* chunk = allocator::alloc_try(backing_allocator, OnStackAllocatorExtraChunk)!;
|
||||
defer catch allocator::free(backing_allocator, chunk);
|
||||
defer try self.chunk = chunk;
|
||||
*chunk = { .prev = self.chunk, .is_aligned = aligned };
|
||||
return chunk.data = backing_allocator.acquire(size, init_type, aligned ? alignment : 0)!;
|
||||
}
|
||||
a.used = end;
|
||||
void *mem = aligned_pointer_to_offset - offset;
|
||||
self.used = end;
|
||||
OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader.sizeof;
|
||||
header.size = size;
|
||||
return mem;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::io;
|
||||
import std::io, std::math;
|
||||
|
||||
struct TempAllocatorChunk @local
|
||||
{
|
||||
@@ -7,17 +7,15 @@ struct TempAllocatorChunk @local
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
struct TempAllocator
|
||||
struct TempAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
Allocator* backing_allocator;
|
||||
Allocator backing_allocator;
|
||||
TempAllocatorPage* last_page;
|
||||
usz used;
|
||||
usz capacity;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
|
||||
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
|
||||
|
||||
|
||||
@@ -31,98 +29,80 @@ struct TempAllocatorPage
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
macro usz TempAllocatorPage.pagesize(TempAllocatorPage* page) => page.size & ~PAGE_IS_ALIGNED;
|
||||
macro bool TempAllocatorPage.is_aligned(TempAllocatorPage* page) => page.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
|
||||
macro usz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
|
||||
macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
|
||||
|
||||
/**
|
||||
* @require size >= 16
|
||||
**/
|
||||
fn TempAllocator*! new_temp(usz size, Allocator* using)
|
||||
<*
|
||||
@require size >= 16
|
||||
*>
|
||||
fn TempAllocator*! new_temp_allocator(usz size, Allocator allocator)
|
||||
{
|
||||
TempAllocator* allocator = malloc_checked(TempAllocator, .using = using, .end_padding = size)!;
|
||||
allocator.last_page = null;
|
||||
allocator.function = &temp_allocator_function;
|
||||
allocator.backing_allocator = using;
|
||||
allocator.used = 0;
|
||||
allocator.capacity = size;
|
||||
return allocator;
|
||||
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
|
||||
temp.last_page = null;
|
||||
temp.backing_allocator = allocator;
|
||||
temp.used = 0;
|
||||
temp.capacity = size;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require data `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! temp_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
fn void TempAllocator.destroy(&self)
|
||||
{
|
||||
TempAllocator* arena = (TempAllocator*)data;
|
||||
switch (kind)
|
||||
{
|
||||
case CALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
assert(!old_pointer, "Unexpected old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
return arena._alloc(size, alignment_for_allocation(alignment), offset, true);
|
||||
case ALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
assert(!old_pointer, "Unexpected old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
return arena._alloc(size, alignment_for_allocation(alignment), offset, false);
|
||||
case ALIGNED_REALLOC:
|
||||
case REALLOC:
|
||||
if (!size) nextcase FREE;
|
||||
if (!old_pointer) nextcase ALLOC;
|
||||
return arena._realloc(old_pointer, size, alignment_for_allocation(alignment), offset);
|
||||
case FREE:
|
||||
case ALIGNED_FREE:
|
||||
if (!old_pointer) return null;
|
||||
arena._free(old_pointer)!;
|
||||
return null;
|
||||
case MARK:
|
||||
return (void*)(uptr)arena.used;
|
||||
case RESET:
|
||||
arena._reset(size)!;
|
||||
return null;
|
||||
}
|
||||
unreachable();
|
||||
self.reset(0);
|
||||
if (self.last_page) (void)self._free_page(self.last_page);
|
||||
allocator::free(self.backing_allocator, self);
|
||||
}
|
||||
|
||||
fn void! TempAllocator._free(TempAllocator* this, void* old_pointer) @local
|
||||
fn usz TempAllocator.mark(&self) @dynamic => self.used;
|
||||
|
||||
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
|
||||
{
|
||||
// TODO fix free
|
||||
assert((uptr)old_pointer >= (uptr)&this.data, "Pointer originates from a different allocator.");
|
||||
usz old_size = *(usz*)(old_pointer - DEFAULT_SIZE_PREFIX);
|
||||
if (old_pointer + old_size == &this.data[this.used])
|
||||
{
|
||||
this.used -= old_size;
|
||||
}
|
||||
if (old_pointer + old_size == &self.data[self.used])
|
||||
{
|
||||
self.used -= old_size;
|
||||
asan::poison_memory_region(&self.data[self.used], old_size);
|
||||
}
|
||||
}
|
||||
fn void! TempAllocator._reset(TempAllocator* this, usz mark) @local
|
||||
fn void TempAllocator.reset(&self, usz mark) @dynamic
|
||||
{
|
||||
TempAllocatorPage *last_page = this.last_page;
|
||||
TempAllocatorPage *last_page = self.last_page;
|
||||
while (last_page && last_page.mark > mark)
|
||||
{
|
||||
self.used = last_page.mark;
|
||||
TempAllocatorPage *to_free = last_page;
|
||||
last_page = last_page.prev_page;
|
||||
this._free_page(to_free)!;
|
||||
self._free_page(to_free)!!;
|
||||
}
|
||||
this.last_page = last_page;
|
||||
this.used = mark;
|
||||
self.last_page = last_page;
|
||||
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
|
||||
if (!last_page)
|
||||
{
|
||||
usz cleaned = self.used - mark;
|
||||
if (cleaned > 0)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE:
|
||||
self.data[mark : cleaned] = 0xAA;
|
||||
$endif
|
||||
asan::poison_memory_region(&self.data[mark], cleaned);
|
||||
}
|
||||
}
|
||||
$endif
|
||||
self.used = mark;
|
||||
}
|
||||
|
||||
fn void! TempAllocator._free_page(TempAllocator* this, TempAllocatorPage* page) @inline @local
|
||||
fn void! TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
|
||||
{
|
||||
void* mem = page.start;
|
||||
if (page.is_aligned()) return this.backing_allocator.free_aligned(mem);
|
||||
return this.backing_allocator.free(mem);
|
||||
return self.backing_allocator.release(mem, page.is_aligned());
|
||||
}
|
||||
|
||||
fn void*! TempAllocator._realloc_page(TempAllocator* this, TempAllocatorPage* page, usz size, usz alignment, usz offset) @inline @local
|
||||
fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
|
||||
{
|
||||
// Then the actual start pointer:
|
||||
void* real_pointer = page.start;
|
||||
|
||||
// Walk backwards to find the pointer to this page.
|
||||
TempAllocatorPage **pointer_to_prev = &this.last_page;
|
||||
TempAllocatorPage **pointer_to_prev = &self.last_page;
|
||||
// Remove the page from the list
|
||||
while (*pointer_to_prev != page)
|
||||
{
|
||||
@@ -131,82 +111,78 @@ fn void*! TempAllocator._realloc_page(TempAllocator* this, TempAllocatorPage* pa
|
||||
*pointer_to_prev = page.prev_page;
|
||||
usz page_size = page.pagesize();
|
||||
// Clear on size > original size.
|
||||
void* data = this._alloc(size, alignment, offset, false)!;
|
||||
void* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
if (page.is_aligned())
|
||||
{
|
||||
this.backing_allocator.free_aligned(real_pointer)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.backing_allocator.free(real_pointer)!;
|
||||
}
|
||||
self.backing_allocator.release(real_pointer, page.is_aligned());
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! TempAllocator._realloc(TempAllocator* this, void* pointer, usz size, usz alignment, usz offset) @inline @local
|
||||
fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof;
|
||||
if (chunk.size == (usz)-1)
|
||||
{
|
||||
assert(this.last_page, "Realloc of non temp pointer");
|
||||
assert(self.last_page, "Realloc of non temp pointer");
|
||||
// First grab the page
|
||||
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
|
||||
return this._realloc_page(page, size, alignment, offset);
|
||||
return self._realloc_page(page, size, alignment);
|
||||
}
|
||||
|
||||
// TODO optimize last allocation
|
||||
TempAllocatorChunk* data = this._alloc(size, alignment, offset, size > chunk.size)!;
|
||||
TempAllocatorChunk* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require this != null
|
||||
**/
|
||||
fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz offset, bool clear) @local
|
||||
<*
|
||||
@require size > 0
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
*>
|
||||
fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
void* start_mem = &this.data;
|
||||
void* starting_ptr = start_mem + this.used;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
void* start_mem = &self.data;
|
||||
void* starting_ptr = start_mem + self.used;
|
||||
void* aligned_header_start = mem::aligned_pointer(starting_ptr, TempAllocatorChunk.alignof);
|
||||
void* mem = aligned_header_start + TempAllocatorChunk.sizeof;
|
||||
if (alignment > TempAllocatorChunk.alignof)
|
||||
{
|
||||
mem = mem::aligned_pointer(mem + offset, alignment) - offset;
|
||||
mem = mem::aligned_pointer(mem, alignment);
|
||||
}
|
||||
usz new_usage = (usz)(mem - start_mem) + size;
|
||||
|
||||
// Arena alignment, simple!
|
||||
if (new_usage <= this.capacity)
|
||||
// Arena allocation, simple!
|
||||
if (new_usage <= self.capacity)
|
||||
{
|
||||
asan::unpoison_memory_region(starting_ptr, new_usage - self.used);
|
||||
TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof;
|
||||
chunk_start.size = size;
|
||||
this.used = new_usage;
|
||||
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
chunk_start.size = size;
|
||||
self.used = new_usage;
|
||||
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
// Fallback to backing allocator
|
||||
TempAllocatorPage* page;
|
||||
|
||||
// We have something we need to align.
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT || offset)
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
// This is actually simpler, since it will create the offset for us.
|
||||
usz total_alloc_size = TempAllocatorPage.sizeof + size;
|
||||
if (clear)
|
||||
usz total_alloc_size = mem::aligned_offset(TempAllocatorPage.sizeof + size, alignment);
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
page = this.backing_allocator.calloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!;
|
||||
mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
page = this.backing_allocator.alloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!;
|
||||
mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
|
||||
}
|
||||
page.start = page;
|
||||
void* start = mem;
|
||||
mem += mem::aligned_offset(TempAllocatorPage.sizeof, alignment);
|
||||
page = (TempAllocatorPage*)mem - 1;
|
||||
page.start = start;
|
||||
page.size = size | PAGE_IS_ALIGNED;
|
||||
}
|
||||
else
|
||||
@@ -214,7 +190,7 @@ fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz
|
||||
// Here we might need to pad
|
||||
usz padded_header_size = mem::aligned_offset(TempAllocatorPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
usz total_alloc_size = padded_header_size + size;
|
||||
void* alloc = (clear ? this.backing_allocator.calloc(total_alloc_size) : this.backing_allocator.alloc(total_alloc_size))!;
|
||||
void* alloc = self.backing_allocator.acquire(total_alloc_size, init_type, 0)!;
|
||||
|
||||
// Find the page.
|
||||
page = alloc + padded_header_size - TempAllocatorPage.sizeof;
|
||||
@@ -227,28 +203,28 @@ fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz
|
||||
// Mark it as a page
|
||||
page.ident = ~(usz)0;
|
||||
// Store when it was created
|
||||
page.mark = ++this.used;
|
||||
page.mark = ++self.used;
|
||||
// Hook up the page.
|
||||
page.prev_page = this.last_page;
|
||||
this.last_page = page;
|
||||
page.prev_page = self.last_page;
|
||||
self.last_page = page;
|
||||
return &page.data[0];
|
||||
}
|
||||
|
||||
fn void TempAllocator.print_pages(TempAllocator* this, File f)
|
||||
fn void! TempAllocator.print_pages(&self, File* f)
|
||||
{
|
||||
TempAllocatorPage *last_page = this.last_page;
|
||||
TempAllocatorPage *last_page = self.last_page;
|
||||
if (!last_page)
|
||||
{
|
||||
f.printf("No pages.\n");
|
||||
io::fprintf(f, "No pages.\n")!;
|
||||
return;
|
||||
}
|
||||
f.printf("---Pages----\n");
|
||||
io::fprintf(f, "---Pages----\n")!;
|
||||
uint index = 0;
|
||||
while (last_page)
|
||||
{
|
||||
bool is_not_aligned = !(last_page.size & (1u64 << 63));
|
||||
f.printf("%d. Alloc: %d %d at %p%s\n", ++index,
|
||||
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]");
|
||||
io::fprintf(f, "%d. Alloc: %d %d at %p%s\n", ++index,
|
||||
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]")!;
|
||||
last_page = last_page.prev_page;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +1,216 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator;
|
||||
import std::collections::map;
|
||||
import std::collections, std::io, std::os::backtrace;
|
||||
|
||||
def PtrMap = HashMap<uptr, usz>;
|
||||
const MAX_BACKTRACE = 16;
|
||||
struct Allocation
|
||||
{
|
||||
void* ptr;
|
||||
usz size;
|
||||
void*[MAX_BACKTRACE] backtrace;
|
||||
}
|
||||
|
||||
def AllocMap = HashMap(<uptr, Allocation>);
|
||||
|
||||
// A simple tracking allocator.
|
||||
// It tracks allocations using a hash map but
|
||||
// is not compatible with allocators that uses mark()
|
||||
struct TrackingAllocator
|
||||
struct TrackingAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
Allocator* inner_allocator;
|
||||
PtrMap map;
|
||||
Allocator inner_allocator;
|
||||
AllocMap map;
|
||||
usz mem_total;
|
||||
usz allocs_total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a memory arena for use using the provided bytes.
|
||||
*
|
||||
* @require this != null
|
||||
**/
|
||||
fn void TrackingAllocator.init(TrackingAllocator* this, Allocator* using)
|
||||
<*
|
||||
Initialize a tracking allocator to wrap (and track) another allocator.
|
||||
|
||||
@param [&inout] allocator "The allocator to track"
|
||||
*>
|
||||
fn void TrackingAllocator.init(&self, Allocator allocator)
|
||||
{
|
||||
*this = { .inner_allocator = using, .allocator.function = &tracking_allocator_fn };
|
||||
this.map.init(.using = using);
|
||||
*self = { .inner_allocator = allocator };
|
||||
self.map.new_init(allocator: allocator);
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.free(TrackingAllocator* this)
|
||||
<*
|
||||
Free this tracking allocator.
|
||||
*>
|
||||
fn void TrackingAllocator.free(&self)
|
||||
{
|
||||
this.map.free();
|
||||
*this = {};
|
||||
self.map.free();
|
||||
*self = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [inout] data
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! tracking_allocator_fn(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
{
|
||||
TrackingAllocator* this = (TrackingAllocator*)data;
|
||||
void* result = this.inner_allocator.function(this.inner_allocator, size, alignment, offset, old_pointer, kind)!;
|
||||
switch (kind)
|
||||
{
|
||||
case CALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
case ALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
this.map.set((uptr)result, size);
|
||||
this.mem_total += size;
|
||||
this.allocs_total++;
|
||||
return result;
|
||||
case REALLOC:
|
||||
case ALIGNED_REALLOC:
|
||||
this.map.remove((uptr)old_pointer);
|
||||
this.map.set((uptr)result, size);
|
||||
this.mem_total += size;
|
||||
if (size > 0) this.allocs_total++;
|
||||
return result;
|
||||
case ALIGNED_FREE:
|
||||
case FREE:
|
||||
if (!old_pointer) return null;
|
||||
this.map.remove((uptr)old_pointer);
|
||||
return null;
|
||||
case MARK:
|
||||
// Unsupported
|
||||
return null;
|
||||
case RESET:
|
||||
this.map.clear();
|
||||
return null;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
fn usz TrackingAllocator.allocated(TrackingAllocator* this)
|
||||
<*
|
||||
@return "the total allocated memory not yet freed."
|
||||
*>
|
||||
fn usz TrackingAllocator.allocated(&self)
|
||||
{
|
||||
usz allocated = 0;
|
||||
@pool()
|
||||
{
|
||||
foreach (usz allocation : this.map.value_tlist())
|
||||
{
|
||||
allocated += allocation;
|
||||
}
|
||||
foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
|
||||
};
|
||||
return allocated;
|
||||
}
|
||||
|
||||
fn usz TrackingAllocator.total_allocated(TrackingAllocator* this)
|
||||
<*
|
||||
@return "the total memory allocated (freed or not)."
|
||||
*>
|
||||
fn usz TrackingAllocator.total_allocated(&self) => self.mem_total;
|
||||
|
||||
<*
|
||||
@return "the total number of allocations (freed or not)."
|
||||
*>
|
||||
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
|
||||
|
||||
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
|
||||
{
|
||||
return this.mem_total;
|
||||
return self.map.value_tlist();
|
||||
}
|
||||
|
||||
fn usz TrackingAllocator.total_allocation_count(TrackingAllocator* this)
|
||||
<*
|
||||
@return "the number of non-freed allocations."
|
||||
*>
|
||||
fn usz TrackingAllocator.allocation_count(&self) => self.map.count;
|
||||
|
||||
fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
return this.allocs_total;
|
||||
void* data = self.inner_allocator.acquire(size, init_type, alignment)!;
|
||||
self.allocs_total++;
|
||||
void*[MAX_BACKTRACE] bt;
|
||||
backtrace::capture_current(&bt);
|
||||
self.map.set((uptr)data, { data, size, bt });
|
||||
self.mem_total += size;
|
||||
return data;
|
||||
}
|
||||
|
||||
fn usz TrackingAllocator.allocation_count(TrackingAllocator* this)
|
||||
fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
return this.map.count;
|
||||
void* data = self.inner_allocator.resize(old_pointer, size, alignment)!;
|
||||
self.map.remove((uptr)old_pointer);
|
||||
void*[MAX_BACKTRACE] bt;
|
||||
backtrace::capture_current(&bt);
|
||||
self.map.set((uptr)data, { data, size, bt });
|
||||
self.mem_total += size;
|
||||
self.allocs_total++;
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
|
||||
{
|
||||
if (catch self.map.remove((uptr)old_pointer))
|
||||
{
|
||||
unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
|
||||
}
|
||||
self.inner_allocator.release(old_pointer, is_aligned);
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.clear(&self)
|
||||
{
|
||||
self.map.clear();
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.print_report(&self) => self.fprint_report(io::stdout())!!;
|
||||
|
||||
fn void! TrackingAllocator.fprint_report(&self, OutStream out)
|
||||
{
|
||||
|
||||
usz total = 0;
|
||||
usz entries = 0;
|
||||
bool leaks = false;
|
||||
@pool()
|
||||
{
|
||||
Allocation[] allocs = self.map.value_tlist();
|
||||
if (allocs.len)
|
||||
{
|
||||
if (!allocs[0].backtrace[0])
|
||||
{
|
||||
io::fprintn(out, "======== Memory Report ========")!;
|
||||
io::fprintn(out, "Size in bytes Address")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
entries++;
|
||||
total += allocation.size;
|
||||
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
|
||||
}
|
||||
io::fprintn(out, "===============================")!;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
io::fprintn(out, "================================== Memory Report ==================================")!;
|
||||
io::fprintn(out, "Size in bytes Address Function ")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
entries++;
|
||||
total += allocation.size;
|
||||
BacktraceList backtraces = {};
|
||||
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
|
||||
if (allocation.backtrace[3])
|
||||
{
|
||||
trace = backtrace::symbolize_backtrace(allocation.backtrace[3:1], allocator::temp()).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
|
||||
}
|
||||
if (trace.function.len) leaks = true;
|
||||
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
|
||||
allocation.ptr, trace.function.len ? trace.function : "???",
|
||||
trace.line ? trace.line : 0)!;
|
||||
}
|
||||
io::fprintn(out, "===================================================================================")!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
|
||||
}
|
||||
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
|
||||
io::fprintfn(out, "- Total current allocations: %d", entries)!;
|
||||
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
|
||||
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
|
||||
if (leaks)
|
||||
{
|
||||
io::fprintn(out)!;
|
||||
io::fprintn(out, "Full leak report:")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
if (!allocation.backtrace[3])
|
||||
{
|
||||
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
|
||||
continue;
|
||||
}
|
||||
BacktraceList backtraces = {};
|
||||
usz end = MAX_BACKTRACE;
|
||||
foreach (j, val : allocation.backtrace)
|
||||
{
|
||||
if (!val)
|
||||
{
|
||||
end = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], allocator::temp())!;
|
||||
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
|
||||
foreach (trace : list)
|
||||
{
|
||||
if (trace.has_file())
|
||||
{
|
||||
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
|
||||
continue;
|
||||
}
|
||||
if (trace.is_unknown())
|
||||
{
|
||||
io::fprintfn(out, " ??? (in unknown)");
|
||||
continue;
|
||||
}
|
||||
io::fprintfn(out, " %s (source unavailable)", trace.function);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
module std::core::array;
|
||||
import std::core::array::slice;
|
||||
|
||||
/**
|
||||
* @param [in] array
|
||||
* @param [in] element
|
||||
* @return "the first index of the element"
|
||||
* @return! SearchResult.MISSING
|
||||
**/
|
||||
<*
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@return "the first index of the element"
|
||||
@return! SearchResult.MISSING
|
||||
*>
|
||||
macro index_of(array, element)
|
||||
{
|
||||
foreach (i, &e : array)
|
||||
@@ -15,12 +16,26 @@ macro index_of(array, element)
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [in] array
|
||||
* @param [in] element
|
||||
* @return "the last index of the element"
|
||||
* @return! SearchResult.MISSING
|
||||
**/
|
||||
<*
|
||||
@require @typekind(array_ptr) == POINTER
|
||||
@require @typekind(*array_ptr) == VECTOR || @typekind(*array_ptr) == ARRAY
|
||||
@require @typekind((*array_ptr)[0]) == VECTOR || @typekind((*array_ptr)[0]) == ARRAY
|
||||
*>
|
||||
macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
|
||||
{
|
||||
if (xlen < 1) xlen = $typeof((*array_ptr)[0]).len + xlen;
|
||||
if (ylen < 1) ylen = $typeof((*array_ptr)).len + ylen;
|
||||
var $ElementType = $typeof((*array_ptr)[0][0]);
|
||||
return Slice2d(<$ElementType>) { ($ElementType*)array_ptr, $typeof((*array_ptr)[0]).len, y, ylen, x, xlen };
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@return "the last index of the element"
|
||||
@return! SearchResult.MISSING
|
||||
*>
|
||||
macro rindex_of(array, element)
|
||||
{
|
||||
foreach_r (i, &e : array)
|
||||
@@ -30,21 +45,21 @@ macro rindex_of(array, element)
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate two arrays or subarrays, returning a subarray containing the concatenation of them.
|
||||
*
|
||||
* @param [in] arr1
|
||||
* @param [in] arr2
|
||||
* @param [&inout] using "The allocator to use, default is the heap allocator"
|
||||
* @require @typekind(arr1) == SUBARRAY || @typekind(arr1) == ARRAY
|
||||
* @require @typekind(arr2) == SUBARRAY || @typekind(arr2) == ARRAY
|
||||
* @require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
|
||||
* @ensure result.len == arr1.len + arr2.len
|
||||
**/
|
||||
macro concat(arr1, arr2, Allocator* using = mem::heap())
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
|
||||
|
||||
@param [in] arr1
|
||||
@param [in] arr2
|
||||
@param [&inout] allocator "The allocator to use, default is the heap allocator"
|
||||
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
|
||||
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
|
||||
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
|
||||
@ensure result.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro concat(arr1, arr2, Allocator allocator) @nodiscard
|
||||
{
|
||||
var $Type = $typeof(arr1[0]);
|
||||
$Type[] result = malloc($Type, arr1.len + arr2.len, .using = using);
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len);
|
||||
if (arr1.len > 0)
|
||||
{
|
||||
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $Type.alignof, $Type.alignof);
|
||||
@@ -55,16 +70,124 @@ macro concat(arr1, arr2, Allocator* using = mem::heap())
|
||||
}
|
||||
return result;
|
||||
}
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
|
||||
|
||||
/**
|
||||
* Concatenate two arrays or subarrays, returning a subarray containing the concatenation of them,
|
||||
* allocated using the temp allocator.
|
||||
*
|
||||
* @param [in] arr1
|
||||
* @param [in] arr2
|
||||
* @require @typekind(arr1) == SUBARRAY || @typekind(arr1) == ARRAY
|
||||
* @require @typekind(arr2) == SUBARRAY || @typekind(arr2) == ARRAY
|
||||
* @require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
|
||||
* @ensure result.len == arr1.len + arr2.len
|
||||
**/
|
||||
macro tconcat(arr1, arr2) => concat(arr1, arr2, mem::temp());
|
||||
@param [in] arr1
|
||||
@param [in] arr2
|
||||
@param [&inout] allocator "The allocator to use, default is the heap allocator"
|
||||
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
|
||||
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
|
||||
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
|
||||
@ensure result.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro concat_new(arr1, arr2, Allocator allocator = allocator::heap()) @nodiscard
|
||||
{
|
||||
return concat(arr1, arr2, allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice containing the concatenation of them,
|
||||
allocated using the temp allocator.
|
||||
|
||||
@param [in] arr1
|
||||
@param [in] arr2
|
||||
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
|
||||
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
|
||||
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
|
||||
@ensure result.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro tconcat(arr1, arr2) @nodiscard => concat(arr1, arr2, allocator::temp());
|
||||
|
||||
module std::core::array::slice(<Type>);
|
||||
|
||||
struct Slice2d
|
||||
{
|
||||
Type* ptr;
|
||||
usz inner_len;
|
||||
usz ystart;
|
||||
usz ylen;
|
||||
usz xstart;
|
||||
usz xlen;
|
||||
}
|
||||
|
||||
fn usz Slice2d.len(&self) @operator(len)
|
||||
{
|
||||
return self.ylen;
|
||||
}
|
||||
|
||||
fn usz Slice2d.count(&self)
|
||||
{
|
||||
return self.ylen * self.xlen;
|
||||
}
|
||||
|
||||
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
|
||||
{
|
||||
foreach (y, line : *self)
|
||||
{
|
||||
foreach (x, val : line)
|
||||
{
|
||||
@body({ x, y }, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
|
||||
{
|
||||
foreach (y, line : *self)
|
||||
{
|
||||
foreach (x, &val : line)
|
||||
{
|
||||
@body({ x, y }, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require idy >= 0 && idy < self.ylen
|
||||
*>
|
||||
macro Type[] Slice2d.get_row(self, usz idy) @operator([])
|
||||
{
|
||||
return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen];
|
||||
}
|
||||
|
||||
macro Type Slice2d.get_coord(self, usz[<2>] coord)
|
||||
{
|
||||
return *self.get_coord_ref(coord);
|
||||
}
|
||||
|
||||
macro Type Slice2d.get_xy(self, x, y)
|
||||
{
|
||||
return *self.get_xy_ref(x, y);
|
||||
}
|
||||
|
||||
macro Type* Slice2d.get_xy_ref(self, x, y)
|
||||
{
|
||||
return self.ptr + self.inner_len * (y + self.ystart) + self.xstart + x;
|
||||
}
|
||||
|
||||
macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord)
|
||||
{
|
||||
return self.get_xy_ref(coord.x, coord.y);
|
||||
}
|
||||
|
||||
macro void Slice2d.set_coord(self, usz[<2>] coord, Type value)
|
||||
{
|
||||
*self.get_coord_ref(coord) = value;
|
||||
}
|
||||
|
||||
macro void Slice2d.set_xy(self, x, y, Type value)
|
||||
{
|
||||
*self.get_xy_ref(x, y) = value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require y >= 0 && y < self.ylen
|
||||
@require x >= 0 && x < self.xlen
|
||||
*>
|
||||
fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0)
|
||||
{
|
||||
if (xlen < 1) xlen = self.xlen + xlen;
|
||||
if (ylen < 1) ylen = self.ylen + ylen;
|
||||
return { self.ptr, self.inner_len, y + self.ystart, ylen, x + self.xstart, xlen };
|
||||
}
|
||||
|
||||
@@ -87,3 +87,94 @@ bitstruct UInt128LE : uint128 @littleendian
|
||||
uint128 val : 0..127;
|
||||
}
|
||||
|
||||
<*
|
||||
@require is_array_or_slice_of_char(bytes) "argument must be an array, a pointer to an array or a slice of char"
|
||||
@require is_bitorder($Type) "type must be a bitorder integer"
|
||||
*>
|
||||
macro read(bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$endswitch
|
||||
return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
|
||||
}
|
||||
|
||||
<*
|
||||
@require is_arrayptr_or_slice_of_char(bytes) "argument must be a pointer to an array or a slice of char"
|
||||
@require is_bitorder($Type) "type must be a bitorder integer"
|
||||
*>
|
||||
macro write(x, bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$endswitch
|
||||
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
|
||||
}
|
||||
|
||||
macro is_bitorder($Type)
|
||||
{
|
||||
$switch ($Type)
|
||||
$case UShortLE:
|
||||
$case ShortLE:
|
||||
$case UIntLE:
|
||||
$case IntLE:
|
||||
$case ULongLE:
|
||||
$case LongLE:
|
||||
$case UInt128LE:
|
||||
$case Int128LE:
|
||||
$case UShortBE:
|
||||
$case ShortBE:
|
||||
$case UIntBE:
|
||||
$case IntBE:
|
||||
$case ULongBE:
|
||||
$case LongBE:
|
||||
$case UInt128BE:
|
||||
$case Int128BE:
|
||||
return true;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_array_or_slice_of_char(bytes)
|
||||
{
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
$if $Inner.kindof == ARRAY:
|
||||
var $Inner2 = $typefrom($Inner.inner);
|
||||
return $Inner2.typeid == char.typeid;
|
||||
$endif
|
||||
$case ARRAY:
|
||||
$case SLICE:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
return $Inner.typeid == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_arrayptr_or_slice_of_char(bytes)
|
||||
{
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
$if $Inner.kindof == ARRAY:
|
||||
var $Inner2 = $typefrom($Inner.inner);
|
||||
return $Inner2.typeid == char.typeid;
|
||||
$endif
|
||||
$case SLICE:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
return $Inner.typeid == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
@@ -1,106 +1,133 @@
|
||||
// Copyright (c) 2021-2022 Christoffer Lerno and contributors. All rights reserved.
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno and contributors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::builtin;
|
||||
import libc;
|
||||
import std::hash;
|
||||
import libc, std::hash, std::io, std::os::backtrace;
|
||||
|
||||
/**
|
||||
* Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
|
||||
**/
|
||||
fault IteratorResult
|
||||
{
|
||||
NO_MORE_ELEMENT
|
||||
}
|
||||
/*
|
||||
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
|
||||
*/
|
||||
fault IteratorResult { NO_MORE_ELEMENT }
|
||||
|
||||
/**
|
||||
* Use `SearchResult` when trying to return a value from some collection but the element is missing.
|
||||
**/
|
||||
fault SearchResult
|
||||
{
|
||||
MISSING
|
||||
}
|
||||
/*
|
||||
Use `SearchResult` when trying to return a value from some collection but the element is missing.
|
||||
*/
|
||||
fault SearchResult { MISSING }
|
||||
|
||||
/**
|
||||
* Use `CastResult` when an attempt at conversion fails.
|
||||
**/
|
||||
fault CastResult
|
||||
{
|
||||
TYPE_MISMATCH
|
||||
}
|
||||
/*
|
||||
Use `CastResult` when an attempt at conversion fails.
|
||||
*/
|
||||
fault CastResult { TYPE_MISMATCH }
|
||||
|
||||
/**
|
||||
* Stores a variable on the stack, then restores it at the end of the
|
||||
* macro scope.
|
||||
*
|
||||
* @param variable `the variable to store and restore`
|
||||
**/
|
||||
|
||||
def VoidFn = fn void();
|
||||
|
||||
<*
|
||||
Stores a variable on the stack, then restores it at the end of the
|
||||
macro scope.
|
||||
|
||||
@param variable `the variable to store and restore`
|
||||
*>
|
||||
macro void @scope(&variable; @body) @builtin
|
||||
{
|
||||
var temp = variable;
|
||||
defer variable = temp;
|
||||
var temp = *variable;
|
||||
defer *variable = temp;
|
||||
@body();
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap two variables
|
||||
**/
|
||||
<*
|
||||
Swap two variables
|
||||
@require $assignable(*b, $typeof(*a)) && $assignable(*a, $typeof(*b))
|
||||
*>
|
||||
macro void @swap(&a, &b) @builtin
|
||||
{
|
||||
var temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
var temp = *a;
|
||||
*a = *b;
|
||||
*b = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an `any` type to a type, returning an failure if there is a type mismatch.
|
||||
*
|
||||
* @param v `the any to convert to the given type.`
|
||||
* @param $Type `the type to convert to`
|
||||
* @return `The any.ptr converted to its type.`
|
||||
* @ensure @typeis(return, $Type*)
|
||||
* @return! CastResult.TYPE_MISMATCH
|
||||
**/
|
||||
<*
|
||||
Convert an `any` type to a type, returning an failure if there is a type mismatch.
|
||||
|
||||
@param v `the any to convert to the given type.`
|
||||
@param $Type `the type to convert to`
|
||||
@return `The any.ptr converted to its type.`
|
||||
@ensure @typeis(return, $Type*)
|
||||
@return! CastResult.TYPE_MISMATCH
|
||||
*>
|
||||
macro anycast(any v, $Type) @builtin
|
||||
{
|
||||
if (v.type != $Type.typeid) return CastResult.TYPE_MISMATCH?;
|
||||
return ($Type*)v.ptr;
|
||||
}
|
||||
|
||||
struct CallstackElement
|
||||
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE)
|
||||
{
|
||||
CallstackElement* prev;
|
||||
String function;
|
||||
String file;
|
||||
uint line;
|
||||
@pool()
|
||||
{
|
||||
void*[256] buffer;
|
||||
void*[] backtraces = backtrace::capture_current(&buffer);
|
||||
backtraces_to_ignore++;
|
||||
BacktraceList! backtrace = backtrace::symbolize_backtrace(backtraces, allocator::temp());
|
||||
if (catch backtrace) return false;
|
||||
if (backtrace.len() <= backtraces_to_ignore) return false;
|
||||
io::eprint("\nERROR: '");
|
||||
io::eprint(message);
|
||||
io::eprintn("'");
|
||||
foreach (i, &trace : backtrace)
|
||||
{
|
||||
if (i < backtraces_to_ignore) continue;
|
||||
String inline_suffix = trace.is_inline ? " [inline]" : "";
|
||||
if (trace.is_unknown())
|
||||
{
|
||||
io::eprintfn(" in ???%s", inline_suffix);
|
||||
continue;
|
||||
}
|
||||
if (trace.has_file())
|
||||
{
|
||||
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
|
||||
continue;
|
||||
}
|
||||
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
|
||||
{
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace(message, 2))
|
||||
{
|
||||
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
|
||||
return;
|
||||
}
|
||||
$endif
|
||||
$$trap();
|
||||
|
||||
}
|
||||
|
||||
fn void default_panic(String message, String file, String function, uint line)
|
||||
macro void abort(String string = "Unrecoverable error reached", ...) @builtin @noreturn
|
||||
{
|
||||
CallstackElement* stack = $$stacktrace();
|
||||
$if $defined(io::stderr) && $defined(File.printf):
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
|
||||
$$trap();
|
||||
}
|
||||
|
||||
if (stack) stack = stack.prev;
|
||||
if (stack)
|
||||
{
|
||||
(void)io::stderr().print("\nERROR: '");
|
||||
(void)io::stderr().print(message);
|
||||
(void)io::stderr().printn("'");
|
||||
}
|
||||
else
|
||||
{
|
||||
(void)io::stderr().print("\nERROR: '");
|
||||
(void)io::stderr().print(message);
|
||||
(void)io::stderr().printfn("', in function %s (%s:%d)", function, file, line);
|
||||
}
|
||||
while (stack)
|
||||
{
|
||||
(void)io::stderr().printfn(" in function %s (%s:%d)", stack.function, stack.file, stack.line);
|
||||
if (stack == stack.prev) break;
|
||||
stack = stack.prev;
|
||||
}
|
||||
bool in_panic @local = false;
|
||||
|
||||
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
|
||||
{
|
||||
if (in_panic)
|
||||
{
|
||||
io::eprintn("Panic inside of panic.");
|
||||
return;
|
||||
}
|
||||
in_panic = true;
|
||||
$if $defined(io::stderr):
|
||||
io::eprint("\nERROR: '");
|
||||
io::eprint(message);
|
||||
io::eprintfn("', in %s (%s:%d)", function, file, line);
|
||||
$endif
|
||||
in_panic = false;
|
||||
$$trap();
|
||||
}
|
||||
|
||||
@@ -110,118 +137,181 @@ PanicFn panic = &default_panic;
|
||||
|
||||
fn void panicf(String fmt, String file, String function, uint line, args...)
|
||||
{
|
||||
@stack_mem(512; Allocator* mem)
|
||||
if (in_panic)
|
||||
{
|
||||
io::eprint("Panic inside of panic: ");
|
||||
io::eprintn(fmt);
|
||||
return;
|
||||
}
|
||||
in_panic = true;
|
||||
@stack_mem(512; Allocator allocator)
|
||||
{
|
||||
DString s;
|
||||
s.init(.using = mem);
|
||||
s.printf(fmt, ...args);
|
||||
panic(s.str(), file, function, line);
|
||||
s.new_init(allocator: allocator);
|
||||
s.appendf(fmt, ...args);
|
||||
in_panic = false;
|
||||
panic(s.str_view(), file, function, line);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the path as unreachable. This will panic in safe mode, and in fast will simply be assumed
|
||||
* never happens.
|
||||
* @param [in] string "The panic message or format string"
|
||||
**/
|
||||
<*
|
||||
Marks the path as unreachable. This will panic in safe mode, and in fast will simply be assumed
|
||||
never happens.
|
||||
@param [in] string "The panic message or format string"
|
||||
*>
|
||||
macro void unreachable(String string = "Unreachable statement reached.", ...) @builtin @noreturn
|
||||
{
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat());
|
||||
$if env::COMPILER_SAFE_MODE:
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
|
||||
$endif;
|
||||
$$unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the path as unsupported, this is similar to unreachable.
|
||||
* @param [in] string "The error message"
|
||||
**/
|
||||
<*
|
||||
Marks the path as unsupported, this is similar to unreachable.
|
||||
@param [in] string "The error message"
|
||||
*>
|
||||
macro void unsupported(String string = "Unsupported function invoked") @builtin @noreturn
|
||||
{
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat());
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
|
||||
$$unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expr "the expression to cast"
|
||||
* @param $Type "the type to cast to"
|
||||
*
|
||||
* @require $sizeof(expr) == $Type.sizeof "Cannot bitcast between types of different size."
|
||||
* @ensure @typeis(result, $Type)
|
||||
**/
|
||||
macro bitcast(expr, $Type) @builtin
|
||||
<*
|
||||
Unconditionally break into an attached debugger when reached.
|
||||
*>
|
||||
macro void breakpoint() @builtin
|
||||
{
|
||||
usz $size = $sizeof(expr);
|
||||
$Type x @noinit;
|
||||
mem::copy(&x, &expr, $size, $Type.alignof, $alignof(expr));
|
||||
return x;
|
||||
$$breakpoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $Type `The type of the enum`
|
||||
* @param [in] enum_name `The name of the enum to search for`
|
||||
* @require $Type.kindof == ENUM `Only enums may be used`
|
||||
* @ensure @typeis(return, $Type)
|
||||
* @return! SearchResult.MISSING
|
||||
**/
|
||||
macro any_make(void* ptr, typeid type) @builtin
|
||||
{
|
||||
return $$any_make(ptr, type);
|
||||
}
|
||||
|
||||
macro any.retype_to(&self, typeid type)
|
||||
{
|
||||
return $$any_make(self.ptr, type);
|
||||
}
|
||||
|
||||
macro any.as_inner(&self)
|
||||
{
|
||||
return $$any_make(self.ptr, self.type.inner);
|
||||
}
|
||||
|
||||
<*
|
||||
@param expr "the expression to cast"
|
||||
@param $Type "the type to cast to"
|
||||
|
||||
@require $sizeof(expr) == $Type.sizeof "Cannot bitcast between types of different size."
|
||||
@ensure @typeis(return, $Type)
|
||||
*>
|
||||
macro bitcast(expr, $Type) @builtin
|
||||
{
|
||||
$if $Type.alignof <= $alignof(expr):
|
||||
return *($Type*)&expr;
|
||||
$else
|
||||
$Type x @noinit;
|
||||
$$memcpy(&x, &expr, $sizeof(expr), false, $Type.alignof, $alignof(expr));
|
||||
return x;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param $Type `The type of the enum`
|
||||
@param [in] enum_name `The name of the enum to search for`
|
||||
@require $Type.kindof == ENUM `Only enums may be used`
|
||||
@ensure @typeis(return, $Type)
|
||||
@return! SearchResult.MISSING
|
||||
*>
|
||||
macro enum_by_name($Type, String enum_name) @builtin
|
||||
{
|
||||
typeid x = $Type.typeid;
|
||||
foreach (i, name : x.names)
|
||||
{
|
||||
if (name == enum_name) return ($Type)i;
|
||||
if (name == enum_name) return $Type.from_ordinal(i);
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an expression as likely to be true
|
||||
*
|
||||
* @param #value "expression to be marked likely"
|
||||
* @param $probability "in the range 0 - 1"
|
||||
* @require $probability >= 0 && $probability <= 1.0
|
||||
**/
|
||||
<*
|
||||
@param $Type `The type of the enum`
|
||||
@require $Type.kindof == ENUM `Only enums may be used`
|
||||
@require $defined($Type.#value1) `Expected '#value' to match an enum associated value`
|
||||
@require $assignable(value, $typeof($Type{}.#value)) `Expected the value to match the type of the associated value`
|
||||
@ensure @typeis(return, $Type)
|
||||
@return! SearchResult.MISSING
|
||||
*>
|
||||
macro @enum_from_value($Type, #value, value) @builtin
|
||||
{
|
||||
usz elements = $Type.elements;
|
||||
foreach (e : $Type.values)
|
||||
{
|
||||
if (e.#value == value) return e;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
Mark an expression as likely to be true
|
||||
|
||||
@param #value "expression to be marked likely"
|
||||
@param $probability "in the range 0 - 1"
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro bool @likely(bool #value, $probability = 1.0) @builtin
|
||||
{
|
||||
$if $probability == 1.0:
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value;
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, true);
|
||||
$else
|
||||
$default:
|
||||
return $$expect_with_probability(#value, true, $probability);
|
||||
$endif
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an expression as unlikely to be true
|
||||
*
|
||||
* @param #value "expression to be marked unlikely"
|
||||
* @param $probability "in the range 0 - 1"
|
||||
* @require $probability >= 0 && $probability <= 1.0
|
||||
**/
|
||||
<*
|
||||
Mark an expression as unlikely to be true
|
||||
|
||||
@param #value "expression to be marked unlikely"
|
||||
@param $probability "in the range 0 - 1"
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro bool @unlikely(bool #value, $probability = 1.0) @builtin
|
||||
{
|
||||
$if $probability == 1.0:
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value;
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, false);
|
||||
$else
|
||||
$default:
|
||||
return $$expect_with_probability(#value, false, $probability);
|
||||
$endif
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
* @require values::@is_int(#value) || values::@is_bool(#value)
|
||||
* @checked $typeof(#value) a = expected
|
||||
* @require $probability >= 0 && $probability <= 1.0
|
||||
**/
|
||||
<*
|
||||
@require values::@is_int(#value) || values::@is_bool(#value)
|
||||
@require $assignable(expected, $typeof(#value))
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro @expect(#value, expected, $probability = 1.0) @builtin
|
||||
{
|
||||
$if $probability == 1.0:
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value == expected;
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, ($typeof(#value))expected);
|
||||
$else
|
||||
$default:
|
||||
return $$expect_with_probability(#value, expected, $probability);
|
||||
$endif
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
* Locality for prefetch, levels 0 - 3, corresponding
|
||||
* to "extremely local" to "no locality"
|
||||
**/
|
||||
<*
|
||||
Locality for prefetch, levels 0 - 3, corresponding
|
||||
to "extremely local" to "no locality"
|
||||
*>
|
||||
enum PrefetchLocality
|
||||
{
|
||||
NO_LOCALITY,
|
||||
@@ -230,50 +320,63 @@ enum PrefetchLocality
|
||||
VERY_NEAR,
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch a pointer.
|
||||
<*
|
||||
Prefetch a pointer.
|
||||
|
||||
* @param [in] ptr `Pointer to prefetch`
|
||||
* @param $locality `Locality ranging from none to extremely local`
|
||||
* @param $write `Prefetch for write, otherwise prefetch for read.`
|
||||
**/
|
||||
macro prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
|
||||
@param [in] ptr `Pointer to prefetch`
|
||||
@param $locality `Locality ranging from none to extremely local`
|
||||
@param $write `Prefetch for write, otherwise prefetch for read.`
|
||||
*>
|
||||
macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
|
||||
{
|
||||
$$prefetch(ptr, $write ? 1 : 0, $locality.ordinal);
|
||||
$if !env::BUILTIN_PREFETCH_IS_DISABLED:
|
||||
$$prefetch(ptr, $write ? 1 : 0, $locality.ordinal);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro swizzle(v, ...) @builtin
|
||||
{
|
||||
return $$swizzle(v, $vasplat());
|
||||
return $$swizzle(v, $vasplat);
|
||||
}
|
||||
|
||||
macro swizzle2(v, v2, ...) @builtin
|
||||
{
|
||||
return $$swizzle2(v, v2, $vasplat());
|
||||
return $$swizzle2(v, v2, $vasplat);
|
||||
}
|
||||
<*
|
||||
Return the excuse in the Optional if it is Empty, otherwise
|
||||
return a null fault.
|
||||
|
||||
macro bool @castable(#expr, $To) @builtin
|
||||
{
|
||||
return $checks(($To)#expr);
|
||||
}
|
||||
|
||||
macro bool @convertible(#expr, $To) @builtin
|
||||
{
|
||||
return $checks($To x = #expr);
|
||||
}
|
||||
|
||||
macro anyfault @catchof(#expr) @builtin
|
||||
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
|
||||
*>
|
||||
macro anyfault @catch(#expr) @builtin
|
||||
{
|
||||
if (catch f = #expr) return f;
|
||||
return anyfault {};
|
||||
return anyfault {};
|
||||
}
|
||||
|
||||
<*
|
||||
Check if an Optional expression holds a value or is empty, returning true
|
||||
if it has a value.
|
||||
|
||||
@require @typekind(#expr) == OPTIONAL : `@ok expects an Optional value`
|
||||
*>
|
||||
macro bool @ok(#expr) @builtin
|
||||
{
|
||||
if (catch #expr) return false;
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
macro char[] @as_char_view(&value) @builtin
|
||||
{
|
||||
return ((char*)value)[:$sizeof(*value)];
|
||||
}
|
||||
|
||||
macro isz @str_find(String $string, String $needle) @builtin => $$str_find($string, $needle);
|
||||
macro String @str_upper(String $str) @builtin => $$str_upper($str);
|
||||
macro String @str_lower(String $str) @builtin => $$str_lower($str);
|
||||
macro uint @str_hash(String $str) @builtin => $$str_hash($str);
|
||||
|
||||
macro uint int.hash(int i) => i;
|
||||
macro uint uint.hash(uint i) => i;
|
||||
macro uint short.hash(short s) => s;
|
||||
@@ -287,4 +390,349 @@ macro uint uint128.hash(uint128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^
|
||||
macro uint bool.hash(bool b) => (uint)b;
|
||||
macro uint typeid.hash(typeid t) => ((ulong)(uptr)t).hash();
|
||||
macro uint String.hash(String c) => (uint)fnv32a::encode(c);
|
||||
macro uint char[].hash(char[] c) => (uint)fnv32a::encode(c);
|
||||
macro uint char[].hash(char[] c) => (uint)fnv32a::encode(c);
|
||||
macro uint void*.hash(void* ptr) => ((ulong)(uptr)ptr).hash();
|
||||
|
||||
distinct EmptySlot = void*;
|
||||
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
|
||||
macro @is_empty_macro_slot(#arg) @builtin => @typeis(#arg, EmptySlot);
|
||||
macro @is_valid_macro_slot(#arg) @builtin => !@typeis(#arg, EmptySlot);
|
||||
|
||||
const MAX_FRAMEADDRESS = 128;
|
||||
<*
|
||||
@require n >= 0
|
||||
*>
|
||||
macro void* get_frameaddress(int n)
|
||||
{
|
||||
if (n > MAX_FRAMEADDRESS) return null;
|
||||
switch (n)
|
||||
{
|
||||
case 0: return $$frameaddress(0);
|
||||
case 1: return $$frameaddress(1);
|
||||
case 2: return $$frameaddress(2);
|
||||
case 3: return $$frameaddress(3);
|
||||
case 4: return $$frameaddress(4);
|
||||
case 5: return $$frameaddress(5);
|
||||
case 6: return $$frameaddress(6);
|
||||
case 7: return $$frameaddress(7);
|
||||
case 8: return $$frameaddress(8);
|
||||
case 9: return $$frameaddress(9);
|
||||
case 10: return $$frameaddress(10);
|
||||
case 11: return $$frameaddress(11);
|
||||
case 12: return $$frameaddress(12);
|
||||
case 13: return $$frameaddress(13);
|
||||
case 14: return $$frameaddress(14);
|
||||
case 15: return $$frameaddress(15);
|
||||
case 16: return $$frameaddress(16);
|
||||
case 17: return $$frameaddress(17);
|
||||
case 18: return $$frameaddress(18);
|
||||
case 19: return $$frameaddress(19);
|
||||
case 20: return $$frameaddress(20);
|
||||
case 21: return $$frameaddress(21);
|
||||
case 22: return $$frameaddress(22);
|
||||
case 23: return $$frameaddress(23);
|
||||
case 24: return $$frameaddress(24);
|
||||
case 25: return $$frameaddress(25);
|
||||
case 26: return $$frameaddress(26);
|
||||
case 27: return $$frameaddress(27);
|
||||
case 28: return $$frameaddress(28);
|
||||
case 29: return $$frameaddress(29);
|
||||
case 30: return $$frameaddress(30);
|
||||
case 31: return $$frameaddress(31);
|
||||
case 32: return $$frameaddress(32);
|
||||
case 33: return $$frameaddress(33);
|
||||
case 34: return $$frameaddress(34);
|
||||
case 35: return $$frameaddress(35);
|
||||
case 36: return $$frameaddress(36);
|
||||
case 37: return $$frameaddress(37);
|
||||
case 38: return $$frameaddress(38);
|
||||
case 39: return $$frameaddress(39);
|
||||
case 40: return $$frameaddress(40);
|
||||
case 41: return $$frameaddress(41);
|
||||
case 42: return $$frameaddress(42);
|
||||
case 43: return $$frameaddress(43);
|
||||
case 44: return $$frameaddress(44);
|
||||
case 45: return $$frameaddress(45);
|
||||
case 46: return $$frameaddress(46);
|
||||
case 47: return $$frameaddress(47);
|
||||
case 48: return $$frameaddress(48);
|
||||
case 49: return $$frameaddress(49);
|
||||
case 50: return $$frameaddress(50);
|
||||
case 51: return $$frameaddress(51);
|
||||
case 52: return $$frameaddress(52);
|
||||
case 53: return $$frameaddress(53);
|
||||
case 54: return $$frameaddress(54);
|
||||
case 55: return $$frameaddress(55);
|
||||
case 56: return $$frameaddress(56);
|
||||
case 57: return $$frameaddress(57);
|
||||
case 58: return $$frameaddress(58);
|
||||
case 59: return $$frameaddress(59);
|
||||
case 60: return $$frameaddress(60);
|
||||
case 61: return $$frameaddress(61);
|
||||
case 62: return $$frameaddress(62);
|
||||
case 63: return $$frameaddress(63);
|
||||
case 64: return $$frameaddress(64);
|
||||
case 65: return $$frameaddress(65);
|
||||
case 66: return $$frameaddress(66);
|
||||
case 67: return $$frameaddress(67);
|
||||
case 68: return $$frameaddress(68);
|
||||
case 69: return $$frameaddress(69);
|
||||
case 70: return $$frameaddress(70);
|
||||
case 71: return $$frameaddress(71);
|
||||
case 72: return $$frameaddress(72);
|
||||
case 73: return $$frameaddress(73);
|
||||
case 74: return $$frameaddress(74);
|
||||
case 75: return $$frameaddress(75);
|
||||
case 76: return $$frameaddress(76);
|
||||
case 77: return $$frameaddress(77);
|
||||
case 78: return $$frameaddress(78);
|
||||
case 79: return $$frameaddress(79);
|
||||
case 80: return $$frameaddress(80);
|
||||
case 81: return $$frameaddress(81);
|
||||
case 82: return $$frameaddress(82);
|
||||
case 83: return $$frameaddress(83);
|
||||
case 84: return $$frameaddress(84);
|
||||
case 85: return $$frameaddress(85);
|
||||
case 86: return $$frameaddress(86);
|
||||
case 87: return $$frameaddress(87);
|
||||
case 88: return $$frameaddress(88);
|
||||
case 89: return $$frameaddress(89);
|
||||
case 90: return $$frameaddress(90);
|
||||
case 91: return $$frameaddress(91);
|
||||
case 92: return $$frameaddress(92);
|
||||
case 93: return $$frameaddress(93);
|
||||
case 94: return $$frameaddress(94);
|
||||
case 95: return $$frameaddress(95);
|
||||
case 96: return $$frameaddress(96);
|
||||
case 97: return $$frameaddress(97);
|
||||
case 98: return $$frameaddress(98);
|
||||
case 99: return $$frameaddress(99);
|
||||
case 100: return $$frameaddress(100);
|
||||
case 101: return $$frameaddress(101);
|
||||
case 102: return $$frameaddress(102);
|
||||
case 103: return $$frameaddress(103);
|
||||
case 104: return $$frameaddress(104);
|
||||
case 105: return $$frameaddress(105);
|
||||
case 106: return $$frameaddress(106);
|
||||
case 107: return $$frameaddress(107);
|
||||
case 108: return $$frameaddress(108);
|
||||
case 109: return $$frameaddress(109);
|
||||
case 110: return $$frameaddress(110);
|
||||
case 111: return $$frameaddress(111);
|
||||
case 112: return $$frameaddress(112);
|
||||
case 113: return $$frameaddress(113);
|
||||
case 114: return $$frameaddress(114);
|
||||
case 115: return $$frameaddress(115);
|
||||
case 116: return $$frameaddress(116);
|
||||
case 117: return $$frameaddress(117);
|
||||
case 118: return $$frameaddress(118);
|
||||
case 119: return $$frameaddress(119);
|
||||
case 120: return $$frameaddress(120);
|
||||
case 121: return $$frameaddress(121);
|
||||
case 122: return $$frameaddress(122);
|
||||
case 123: return $$frameaddress(123);
|
||||
case 124: return $$frameaddress(124);
|
||||
case 125: return $$frameaddress(125);
|
||||
case 126: return $$frameaddress(126);
|
||||
case 127: return $$frameaddress(127);
|
||||
case 128: return $$frameaddress(128);
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require n >= 0
|
||||
*>
|
||||
macro void* get_returnaddress(int n)
|
||||
{
|
||||
if (n > MAX_FRAMEADDRESS) return null;
|
||||
switch (n)
|
||||
{
|
||||
case 0: return $$returnaddress(0);
|
||||
case 1: return $$returnaddress(1);
|
||||
case 2: return $$returnaddress(2);
|
||||
case 3: return $$returnaddress(3);
|
||||
case 4: return $$returnaddress(4);
|
||||
case 5: return $$returnaddress(5);
|
||||
case 6: return $$returnaddress(6);
|
||||
case 7: return $$returnaddress(7);
|
||||
case 8: return $$returnaddress(8);
|
||||
case 9: return $$returnaddress(9);
|
||||
case 10: return $$returnaddress(10);
|
||||
case 11: return $$returnaddress(11);
|
||||
case 12: return $$returnaddress(12);
|
||||
case 13: return $$returnaddress(13);
|
||||
case 14: return $$returnaddress(14);
|
||||
case 15: return $$returnaddress(15);
|
||||
case 16: return $$returnaddress(16);
|
||||
case 17: return $$returnaddress(17);
|
||||
case 18: return $$returnaddress(18);
|
||||
case 19: return $$returnaddress(19);
|
||||
case 20: return $$returnaddress(20);
|
||||
case 21: return $$returnaddress(21);
|
||||
case 22: return $$returnaddress(22);
|
||||
case 23: return $$returnaddress(23);
|
||||
case 24: return $$returnaddress(24);
|
||||
case 25: return $$returnaddress(25);
|
||||
case 26: return $$returnaddress(26);
|
||||
case 27: return $$returnaddress(27);
|
||||
case 28: return $$returnaddress(28);
|
||||
case 29: return $$returnaddress(29);
|
||||
case 30: return $$returnaddress(30);
|
||||
case 31: return $$returnaddress(31);
|
||||
case 32: return $$returnaddress(32);
|
||||
case 33: return $$returnaddress(33);
|
||||
case 34: return $$returnaddress(34);
|
||||
case 35: return $$returnaddress(35);
|
||||
case 36: return $$returnaddress(36);
|
||||
case 37: return $$returnaddress(37);
|
||||
case 38: return $$returnaddress(38);
|
||||
case 39: return $$returnaddress(39);
|
||||
case 40: return $$returnaddress(40);
|
||||
case 41: return $$returnaddress(41);
|
||||
case 42: return $$returnaddress(42);
|
||||
case 43: return $$returnaddress(43);
|
||||
case 44: return $$returnaddress(44);
|
||||
case 45: return $$returnaddress(45);
|
||||
case 46: return $$returnaddress(46);
|
||||
case 47: return $$returnaddress(47);
|
||||
case 48: return $$returnaddress(48);
|
||||
case 49: return $$returnaddress(49);
|
||||
case 50: return $$returnaddress(50);
|
||||
case 51: return $$returnaddress(51);
|
||||
case 52: return $$returnaddress(52);
|
||||
case 53: return $$returnaddress(53);
|
||||
case 54: return $$returnaddress(54);
|
||||
case 55: return $$returnaddress(55);
|
||||
case 56: return $$returnaddress(56);
|
||||
case 57: return $$returnaddress(57);
|
||||
case 58: return $$returnaddress(58);
|
||||
case 59: return $$returnaddress(59);
|
||||
case 60: return $$returnaddress(60);
|
||||
case 61: return $$returnaddress(61);
|
||||
case 62: return $$returnaddress(62);
|
||||
case 63: return $$returnaddress(63);
|
||||
case 64: return $$returnaddress(64);
|
||||
case 65: return $$returnaddress(65);
|
||||
case 66: return $$returnaddress(66);
|
||||
case 67: return $$returnaddress(67);
|
||||
case 68: return $$returnaddress(68);
|
||||
case 69: return $$returnaddress(69);
|
||||
case 70: return $$returnaddress(70);
|
||||
case 71: return $$returnaddress(71);
|
||||
case 72: return $$returnaddress(72);
|
||||
case 73: return $$returnaddress(73);
|
||||
case 74: return $$returnaddress(74);
|
||||
case 75: return $$returnaddress(75);
|
||||
case 76: return $$returnaddress(76);
|
||||
case 77: return $$returnaddress(77);
|
||||
case 78: return $$returnaddress(78);
|
||||
case 79: return $$returnaddress(79);
|
||||
case 80: return $$returnaddress(80);
|
||||
case 81: return $$returnaddress(81);
|
||||
case 82: return $$returnaddress(82);
|
||||
case 83: return $$returnaddress(83);
|
||||
case 84: return $$returnaddress(84);
|
||||
case 85: return $$returnaddress(85);
|
||||
case 86: return $$returnaddress(86);
|
||||
case 87: return $$returnaddress(87);
|
||||
case 88: return $$returnaddress(88);
|
||||
case 89: return $$returnaddress(89);
|
||||
case 90: return $$returnaddress(90);
|
||||
case 91: return $$returnaddress(91);
|
||||
case 92: return $$returnaddress(92);
|
||||
case 93: return $$returnaddress(93);
|
||||
case 94: return $$returnaddress(94);
|
||||
case 95: return $$returnaddress(95);
|
||||
case 96: return $$returnaddress(96);
|
||||
case 97: return $$returnaddress(97);
|
||||
case 98: return $$returnaddress(98);
|
||||
case 99: return $$returnaddress(99);
|
||||
case 100: return $$returnaddress(100);
|
||||
case 101: return $$returnaddress(101);
|
||||
case 102: return $$returnaddress(102);
|
||||
case 103: return $$returnaddress(103);
|
||||
case 104: return $$returnaddress(104);
|
||||
case 105: return $$returnaddress(105);
|
||||
case 106: return $$returnaddress(106);
|
||||
case 107: return $$returnaddress(107);
|
||||
case 108: return $$returnaddress(108);
|
||||
case 109: return $$returnaddress(109);
|
||||
case 110: return $$returnaddress(110);
|
||||
case 111: return $$returnaddress(111);
|
||||
case 112: return $$returnaddress(112);
|
||||
case 113: return $$returnaddress(113);
|
||||
case 114: return $$returnaddress(114);
|
||||
case 115: return $$returnaddress(115);
|
||||
case 116: return $$returnaddress(116);
|
||||
case 117: return $$returnaddress(117);
|
||||
case 118: return $$returnaddress(118);
|
||||
case 119: return $$returnaddress(119);
|
||||
case 120: return $$returnaddress(120);
|
||||
case 121: return $$returnaddress(121);
|
||||
case 122: return $$returnaddress(122);
|
||||
case 123: return $$returnaddress(123);
|
||||
case 124: return $$returnaddress(124);
|
||||
case 125: return $$returnaddress(125);
|
||||
case 126: return $$returnaddress(126);
|
||||
case 127: return $$returnaddress(127);
|
||||
case 128: return $$returnaddress(128);
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
|
||||
import libc, std::io;
|
||||
|
||||
fn void sig_panic(String message)
|
||||
{
|
||||
default_panic(message, "???", "???", 0);
|
||||
}
|
||||
|
||||
SignalFunction old_bus_error;
|
||||
SignalFunction old_segmentation_fault;
|
||||
|
||||
fn void sig_bus_error(CInt i)
|
||||
{
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Illegal memory access.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Illegal memory access.", 1))
|
||||
{
|
||||
io::eprintn("\nERROR: 'Illegal memory access'.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
$$trap();
|
||||
}
|
||||
|
||||
fn void sig_segmentation_fault(CInt i)
|
||||
{
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Out of bounds memory access.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Out of bounds memory access.", 1))
|
||||
{
|
||||
io::eprintn("\nERROR: Memory error without backtrace, possible stack overflow.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
$$trap();
|
||||
}
|
||||
|
||||
fn void install_signal_handler(CInt signal, SignalFunction func) @local
|
||||
{
|
||||
SignalFunction old = libc::signal(signal, func);
|
||||
// Restore
|
||||
if ((iptr)old > 1024) libc::signal(signal, old);
|
||||
}
|
||||
|
||||
// Clean this up
|
||||
fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE)
|
||||
{
|
||||
install_signal_handler(libc::SIGBUS, &sig_bus_error);
|
||||
install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,114 +1,128 @@
|
||||
// Copyright (c) 2021-2022 Christoffer Lerno and contributors. All rights reserved.
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno and contributors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::builtin;
|
||||
|
||||
/**
|
||||
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
|
||||
**/
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro less(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return a.less(b);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) < 0;
|
||||
$default:
|
||||
return a < b;
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
|
||||
**/
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro less_eq(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return !b.less(a);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) <= 0;
|
||||
$default:
|
||||
return a <= b;
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
|
||||
**/
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro greater(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return b.less(a);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) > 0;
|
||||
$default:
|
||||
return a > b;
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
|
||||
**/
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro int compare_to(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b);
|
||||
$case $defined(a.less):
|
||||
return (int)b.less(a) - (int)a.less(b);
|
||||
$default:
|
||||
return (int)(a > b) - (int)(a < b);
|
||||
$endswitch
|
||||
}
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro greater_eq(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return !a.less(b);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) >= 0;
|
||||
$default:
|
||||
return a >= b;
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
* @require types::is_equatable_value(a) && types::is_equatable_value(b) `values must be equatable`
|
||||
**/
|
||||
<*
|
||||
@require types::@equatable_value(a) && types::@equatable_value(b) `values must be equatable`
|
||||
*>
|
||||
macro bool equals(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$case $defined(a.equals):
|
||||
$switch
|
||||
$case $defined(a.equals, a.equals(b)):
|
||||
return a.equals(b);
|
||||
$case $defined(a.compare_to):
|
||||
$case $defined(a.compare_to, a.compare_to(b)):
|
||||
return a.compare_to(b) == 0;
|
||||
$case $defined(a.less):
|
||||
return !a.less(b) && !b.less(a);
|
||||
$default:
|
||||
return a == b;
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro min(x, ...) @builtin
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return less(x, $vaarg(0)) ? x : $vaarg(0);
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (less($vaarg($i), result))
|
||||
{
|
||||
result = $vaarg($i);
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
$endif
|
||||
$if $vacount == 1:
|
||||
return less(x, $vaarg[0]) ? x : $vaarg[0];
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (less($vaarg[$i], result))
|
||||
{
|
||||
result = $vaarg[$i];
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro max(x, ...) @builtin
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return greater(x, $vaarg(0)) ? x : $vaarg(0);
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (greater($vaarg($i), result))
|
||||
{
|
||||
result = $vaarg($i);
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
$endif
|
||||
$if $vacount == 1:
|
||||
return greater(x, $vaarg[0]) ? x : $vaarg[0];
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (greater($vaarg[$i], result))
|
||||
{
|
||||
result = $vaarg[$i];
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::cinterop;
|
||||
@@ -16,72 +16,46 @@ $assert C_SHORT_SIZE <= C_INT_SIZE;
|
||||
$assert C_INT_SIZE <= C_LONG_SIZE;
|
||||
$assert C_LONG_SIZE <= C_LONG_LONG_SIZE;
|
||||
|
||||
$switch ($$C_INT_SIZE)
|
||||
$case 64:
|
||||
def CInt = long;
|
||||
def CUInt = ulong;
|
||||
$case 32:
|
||||
def CInt = int;
|
||||
def CUInt = uint;
|
||||
$case 16:
|
||||
def CInt = short;
|
||||
def CUInt = ushort;
|
||||
$default:
|
||||
$error "Invalid C int size";
|
||||
$endswitch
|
||||
|
||||
$switch ($$C_LONG_SIZE)
|
||||
$case 64:
|
||||
def CLong = long;
|
||||
def CULong = ulong;
|
||||
$case 32:
|
||||
def CLong = int;
|
||||
def CULong = uint;
|
||||
$case 16:
|
||||
def CLong = short;
|
||||
def CULong = ushort;
|
||||
$default:
|
||||
$error "Invalid C long size";
|
||||
$endswitch
|
||||
|
||||
$switch ($$C_SHORT_SIZE)
|
||||
$case 32:
|
||||
def CShort = int;
|
||||
def CUShort = uint;
|
||||
$case 16:
|
||||
def CShort = short;
|
||||
def CUShort = ushort;
|
||||
$case 8:
|
||||
def CShort = ichar;
|
||||
def CUShort = char;
|
||||
$default:
|
||||
$error "Invalid C short size";
|
||||
$endswitch
|
||||
|
||||
$switch ($$C_LONG_LONG_SIZE)
|
||||
$case 128:
|
||||
def CLongLong = int128;
|
||||
def CULongLong = uint128;
|
||||
$case 64:
|
||||
def CLongLong = long;
|
||||
def CULongLong = ulong;
|
||||
$case 32:
|
||||
def CLongLong = int;
|
||||
def CULongLong = uint;
|
||||
$case 16:
|
||||
def CLongLong = short;
|
||||
def CULongLong = ushort;
|
||||
$default:
|
||||
$error "Invalid C long long size";
|
||||
$endswitch
|
||||
|
||||
|
||||
|
||||
def CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
|
||||
def CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
|
||||
def CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
|
||||
def CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
|
||||
def CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
|
||||
def CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
|
||||
def CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
def CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
def CSChar = ichar;
|
||||
def CUChar = char;
|
||||
|
||||
$if $$C_CHAR_IS_SIGNED:
|
||||
def CChar = ichar;
|
||||
$else
|
||||
def CChar = char;
|
||||
$endif
|
||||
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
|
||||
|
||||
enum CBool : CInt
|
||||
{
|
||||
FALSE,
|
||||
TRUE
|
||||
}
|
||||
|
||||
// Helper macros
|
||||
macro typeid signed_int_from_bitsize(usz $bitsize) @private
|
||||
{
|
||||
$switch ($bitsize)
|
||||
$case 128: return int128.typeid;
|
||||
$case 64: return long.typeid;
|
||||
$case 32: return int.typeid;
|
||||
$case 16: return short.typeid;
|
||||
$case 8: return ichar.typeid;
|
||||
$default: $error("Invalid bitsize");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
|
||||
{
|
||||
$switch ($bitsize)
|
||||
$case 128: return uint128.typeid;
|
||||
$case 64: return ulong.typeid;
|
||||
$case 32: return uint.typeid;
|
||||
$case 16: return ushort.typeid;
|
||||
$case 8: return char.typeid;
|
||||
$default: $error("Invalid bitsize");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
@@ -9,49 +9,48 @@ const uint UTF16_SURROGATE_BITS @private = 10;
|
||||
const uint UTF16_SURROGATE_LOW_VALUE @private = 0xDC00;
|
||||
const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
|
||||
|
||||
/**
|
||||
* @param c `The utf32 codepoint to convert`
|
||||
* @param [out] output `the resulting buffer`
|
||||
* @param available `the size available`
|
||||
**/
|
||||
fn usz! char32_to_utf8(Char32 c, char* output, usz available)
|
||||
<*
|
||||
@param c `The utf32 codepoint to convert`
|
||||
@param [out] output `the resulting buffer`
|
||||
*>
|
||||
fn usz! char32_to_utf8(Char32 c, char[] output)
|
||||
{
|
||||
if (!available) return UnicodeResult.CONVERSION_FAILED?;
|
||||
if (!output.len) return UnicodeResult.CONVERSION_FAILED?;
|
||||
switch (true)
|
||||
{
|
||||
case c <= 0x7f:
|
||||
output[0] = (char)c;
|
||||
return 1;
|
||||
case c <= 0x7ff:
|
||||
if (available < 2) return UnicodeResult.CONVERSION_FAILED?;
|
||||
if (output.len < 2) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xC0 | c >> 6);
|
||||
output[1] = (char)(0x80 | (c & 0x3F));
|
||||
return 2;
|
||||
case c <= 0xffff:
|
||||
if (available < 3) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xE0 | c >> 12);
|
||||
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c & 0x3F));
|
||||
return 3;
|
||||
case c <= 0x10ffff:
|
||||
if (available < 4) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xF0 | c >> 18);
|
||||
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[3] = (char)(0x80 | (c & 0x3F));
|
||||
return 4;
|
||||
default:
|
||||
// 0x10FFFF and above is not defined.
|
||||
return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[1] = (char)(0x80 | (c & 0x3F));
|
||||
return 2;
|
||||
case c <= 0xffff:
|
||||
if (output.len < 3) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xE0 | c >> 12);
|
||||
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c & 0x3F));
|
||||
return 3;
|
||||
case c <= 0x10ffff:
|
||||
if (output.len < 4) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xF0 | c >> 18);
|
||||
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[3] = (char)(0x80 | (c & 0x3F));
|
||||
return 4;
|
||||
default:
|
||||
// 0x10FFFF and above is not defined.
|
||||
return UnicodeResult.CONVERSION_FAILED?;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a code pointer into 1-2 UTF16 characters.
|
||||
*
|
||||
* @param c `The character to convert.`
|
||||
* @param [inout] output `the resulting UTF16 buffer to write to.`
|
||||
**/
|
||||
<*
|
||||
Convert a code pointer into 1-2 UTF16 characters.
|
||||
|
||||
@param c `The character to convert.`
|
||||
@param [inout] output `the resulting UTF16 buffer to write to.`
|
||||
*>
|
||||
fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
|
||||
{
|
||||
if (c < UTF16_SURROGATE_OFFSET)
|
||||
@@ -67,13 +66,13 @@ fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
|
||||
(*output)++[0] = (Char16)low;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert 1-2 UTF16 data points into UTF8.
|
||||
*
|
||||
* @param [in] ptr `The UTF16 data to convert.`
|
||||
* @param [inout] available `amount of UTF16 data available.`
|
||||
* @param [inout] output `the resulting utf8 buffer to write to.`
|
||||
**/
|
||||
<*
|
||||
Convert 1-2 UTF16 data points into UTF8.
|
||||
|
||||
@param [in] ptr `The UTF16 data to convert.`
|
||||
@param [inout] available `amount of UTF16 data available.`
|
||||
@param [inout] output `the resulting utf8 buffer to write to.`
|
||||
*>
|
||||
fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
{
|
||||
Char16 high = *ptr;
|
||||
@@ -83,7 +82,7 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
*available = 1;
|
||||
return;
|
||||
}
|
||||
// Low surrogate first is an error
|
||||
// Low surrogate first is an error
|
||||
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return UnicodeResult.INVALID_UTF16?;
|
||||
|
||||
// Unmatched high surrogate is an error
|
||||
@@ -94,69 +93,73 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
// Unmatched high surrogate, invalid
|
||||
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return UnicodeResult.INVALID_UTF16?;
|
||||
|
||||
// The high bits of the codepoint are the value bits of the high surrogate
|
||||
// The low bits of the codepoint are the value bits of the low surrogate
|
||||
Char32 uc = (high & UTF16_SURROGATE_CODEPOINT_MASK) << UTF16_SURROGATE_BITS
|
||||
| (low & UTF16_SURROGATE_CODEPOINT_MASK) + UTF16_SURROGATE_OFFSET;
|
||||
// The high bits of the codepoint are the value bits of the high surrogate
|
||||
// The low bits of the codepoint are the value bits of the low surrogate
|
||||
Char32 uc = (high & UTF16_SURROGATE_CODEPOINT_MASK) << UTF16_SURROGATE_BITS
|
||||
| (low & UTF16_SURROGATE_CODEPOINT_MASK) + UTF16_SURROGATE_OFFSET;
|
||||
char32_to_utf8_unsafe(uc, output);
|
||||
*available = 2;
|
||||
}
|
||||
/**
|
||||
* @param c `The utf32 codepoint to convert`
|
||||
* @param [inout] output `the resulting buffer`
|
||||
**/
|
||||
fn void char32_to_utf8_unsafe(Char32 c, char** output)
|
||||
<*
|
||||
@param c `The utf32 codepoint to convert`
|
||||
@param [inout] output `the resulting buffer`
|
||||
*>
|
||||
fn usz char32_to_utf8_unsafe(Char32 c, char** output)
|
||||
{
|
||||
switch (true)
|
||||
switch
|
||||
{
|
||||
case c < 0x7f:
|
||||
case c <= 0x7f:
|
||||
(*output)++[0] = (char)c;
|
||||
case c < 0x7ff:
|
||||
return 1;
|
||||
case c <= 0x7ff:
|
||||
(*output)++[0] = (char)(0xC0 | c >> 6);
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
case c < 0xffff:
|
||||
(*output)++[0] = (char)(0xE0 | c >> 12);
|
||||
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
default:
|
||||
(*output)++[0] = (char)(0xF0 | c >> 18);
|
||||
(*output)++[0] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
return 2;
|
||||
case c <= 0xffff:
|
||||
(*output)++[0] = (char)(0xE0 | c >> 12);
|
||||
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
return 3;
|
||||
default:
|
||||
(*output)++[0] = (char)(0xF0 | c >> 18);
|
||||
(*output)++[0] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [in] ptr `pointer to the first character to parse`
|
||||
* @param [inout] size `Set to max characters to read, set to characters read`
|
||||
* @return `the parsed 32 bit codepoint`
|
||||
**/
|
||||
<*
|
||||
@param [in] ptr `pointer to the first character to parse`
|
||||
@param [inout] size `Set to max characters to read, set to characters read`
|
||||
@return `the parsed 32 bit codepoint`
|
||||
*>
|
||||
fn Char32! utf8_to_char32(char* ptr, usz* size)
|
||||
{
|
||||
usz max_size = *size;
|
||||
if (max_size < 1) return UnicodeResult.INVALID_UTF8?;
|
||||
char c = (ptr++)[0];
|
||||
|
||||
if ((c & 0x80) == 0)
|
||||
{
|
||||
*size = 1;
|
||||
return c;
|
||||
}
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
if ((c & 0x80) == 0)
|
||||
{
|
||||
*size = 1;
|
||||
return c;
|
||||
}
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
if (max_size < 2) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 2;
|
||||
Char32 uc = (c & 0x1F) << 6;
|
||||
c = *ptr;
|
||||
// Overlong sequence or invalid second.
|
||||
*size = 2;
|
||||
Char32 uc = (c & 0x1F) << 6;
|
||||
c = *ptr;
|
||||
// Overlong sequence or invalid second.
|
||||
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
if (max_size < 3) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 3;
|
||||
Char32 uc = (c & 0x0F) << 12;
|
||||
*size = 3;
|
||||
Char32 uc = (c & 0x0F) << 12;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
uc += (c & 0x3F) << 6;
|
||||
@@ -164,11 +167,11 @@ fn Char32! utf8_to_char32(char* ptr, usz* size)
|
||||
// Overlong sequence or invalid last
|
||||
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if (max_size < 4) return UnicodeResult.INVALID_UTF8?;
|
||||
if ((c & 0xF8) != 0xF0) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 4;
|
||||
Char32 uc = (c & 0x07) << 18;
|
||||
}
|
||||
if (max_size < 4) return UnicodeResult.INVALID_UTF8?;
|
||||
if ((c & 0xF8) != 0xF0) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 4;
|
||||
Char32 uc = (c & 0x07) << 18;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
uc += (c & 0x3F) << 12;
|
||||
@@ -181,10 +184,10 @@ fn Char32! utf8_to_char32(char* ptr, usz* size)
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param utf8 `An UTF-8 encoded slice of bytes`
|
||||
* @return `the number of encoded code points`
|
||||
**/
|
||||
<*
|
||||
@param utf8 `An UTF-8 encoded slice of bytes`
|
||||
@return `the number of encoded code points`
|
||||
*>
|
||||
fn usz utf8_codepoints(String utf8)
|
||||
{
|
||||
usz len = 0;
|
||||
@@ -195,11 +198,11 @@ fn usz utf8_codepoints(String utf8)
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the UTF8 length required to encode an UTF32 array.
|
||||
* @param [in] utf32 `the utf32 data to calculate from`
|
||||
* @return `the length of the resulting UTF8 array`
|
||||
**/
|
||||
<*
|
||||
Calculate the UTF8 length required to encode an UTF32 array.
|
||||
@param [in] utf32 `the utf32 data to calculate from`
|
||||
@return `the length of the resulting UTF8 array`
|
||||
*>
|
||||
fn usz utf8len_for_utf32(Char32[] utf32)
|
||||
{
|
||||
usz len = 0;
|
||||
@@ -207,11 +210,11 @@ fn usz utf8len_for_utf32(Char32[] utf32)
|
||||
{
|
||||
switch (true)
|
||||
{
|
||||
case uc < 0x7f:
|
||||
case uc <= 0x7f:
|
||||
len++;
|
||||
case uc < 0x7ff:
|
||||
case uc <= 0x7ff:
|
||||
len += 2;
|
||||
case uc < 0xffff:
|
||||
case uc <= 0xffff:
|
||||
len += 3;
|
||||
default:
|
||||
len += 4;
|
||||
@@ -220,11 +223,11 @@ fn usz utf8len_for_utf32(Char32[] utf32)
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the UTF8 length required to encode an UTF16 array.
|
||||
* @param [in] utf16 `the utf16 data to calculate from`
|
||||
* @return `the length of the resulting UTF8 array`
|
||||
**/
|
||||
<*
|
||||
Calculate the UTF8 length required to encode an UTF16 array.
|
||||
@param [in] utf16 `the utf16 data to calculate from`
|
||||
@return `the length of the resulting UTF8 array`
|
||||
*>
|
||||
fn usz utf8len_for_utf16(Char16[] utf16)
|
||||
{
|
||||
usz len = 0;
|
||||
@@ -234,12 +237,12 @@ fn usz utf8len_for_utf16(Char16[] utf16)
|
||||
Char16 c = utf16[i];
|
||||
if (c & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
|
||||
{
|
||||
if (c < 0x7f)
|
||||
if (c <= 0x7f)
|
||||
{
|
||||
len++;
|
||||
continue;
|
||||
}
|
||||
if (c < 0x7ff)
|
||||
if (c <= 0x7ff)
|
||||
{
|
||||
len += 2;
|
||||
continue;
|
||||
@@ -252,11 +255,11 @@ fn usz utf8len_for_utf16(Char16[] utf16)
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the UTF16 length required to encode a UTF8 array.
|
||||
* @param utf8 `the utf8 data to calculate from`
|
||||
* @return `the length of the resulting UTF16 array`
|
||||
**/
|
||||
<*
|
||||
Calculate the UTF16 length required to encode a UTF8 array.
|
||||
@param utf8 `the utf8 data to calculate from`
|
||||
@return `the length of the resulting UTF16 array`
|
||||
*>
|
||||
fn usz utf16len_for_utf8(String utf8)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
@@ -272,14 +275,14 @@ fn usz utf16len_for_utf8(String utf8)
|
||||
if (c & 0xF0 == 0xE0) continue;
|
||||
i++;
|
||||
len16++;
|
||||
}
|
||||
return len16;
|
||||
}
|
||||
return len16;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [in] utf32 `the UTF32 array to check the length for`
|
||||
* @return `the required length of an UTF16 array to hold the UTF32 data.`
|
||||
**/
|
||||
<*
|
||||
@param [in] utf32 `the UTF32 array to check the length for`
|
||||
@return `the required length of an UTF16 array to hold the UTF32 data.`
|
||||
*>
|
||||
fn usz utf16len_for_utf32(Char32[] utf32)
|
||||
{
|
||||
usz len = utf32.len;
|
||||
@@ -290,62 +293,60 @@ fn usz utf16len_for_utf32(Char32[] utf32)
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an UTF32 array to an UTF8 array.
|
||||
*
|
||||
* @param [in] utf32
|
||||
* @param [out] utf8_buffer
|
||||
* @return `the number of bytes written.`
|
||||
**/
|
||||
fn usz! utf32to8(Char32[] utf32, String utf8_buffer)
|
||||
<*
|
||||
Convert an UTF32 array to an UTF8 array.
|
||||
|
||||
@param [in] utf32
|
||||
@param [out] utf8_buffer
|
||||
@return `the number of bytes written.`
|
||||
*>
|
||||
fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
|
||||
{
|
||||
usz len = utf8_buffer.len;
|
||||
char* ptr = utf8_buffer.ptr;
|
||||
foreach (Char32 uc : utf32)
|
||||
char[] buffer = utf8_buffer;
|
||||
foreach (uc : utf32)
|
||||
{
|
||||
usz used = char32_to_utf8(uc, ptr, len) @inline!;
|
||||
len -= used;
|
||||
ptr += used;
|
||||
usz used = char32_to_utf8(uc, buffer) @inline!;
|
||||
buffer = buffer[used..];
|
||||
}
|
||||
// Zero terminate if there is space.
|
||||
if (len > 0) ptr[0] = 0;
|
||||
return utf8_buffer.len - len;
|
||||
if (buffer.len > 0) buffer[0] = 0;
|
||||
return utf8_buffer.len - buffer.len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an UTF8 array to an UTF32 array.
|
||||
*
|
||||
* @param [in] utf8
|
||||
* @param [out] utf32_buffer
|
||||
* @return `the number of Char32s written.`
|
||||
**/
|
||||
<*
|
||||
Convert an UTF8 array to an UTF32 array.
|
||||
|
||||
@param [in] utf8
|
||||
@param [out] utf32_buffer
|
||||
@return `the number of Char32s written.`
|
||||
*>
|
||||
fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
Char32* ptr = utf32_buffer.ptr;
|
||||
usz len32 = 0;
|
||||
usz buf_len = utf32_buffer.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED?;
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
ptr[len32++] = uc;
|
||||
}
|
||||
// Zero terminate if possible
|
||||
if (len32 + 1 < buf_len) ptr[len32] = 0;
|
||||
return len32;
|
||||
usz len32 = 0;
|
||||
usz buf_len = utf32_buffer.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED?;
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
ptr[len32++] = uc;
|
||||
}
|
||||
// Zero terminate if possible
|
||||
if (len32 + 1 < buf_len) ptr[len32] = 0;
|
||||
return len32;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy an array of UTF16 data into an UTF8 buffer without bounds
|
||||
* checking. This will assume the buffer is sufficiently large to hold
|
||||
* the converted data.
|
||||
*
|
||||
* @param [in] utf16 `The UTF16 array containing the data to convert.`
|
||||
* @param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF16 data.`
|
||||
**/
|
||||
<*
|
||||
Copy an array of UTF16 data into an UTF8 buffer without bounds
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf16 `The UTF16 array containing the data to convert.`
|
||||
@param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF16 data.`
|
||||
*>
|
||||
fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
|
||||
{
|
||||
usz len16 = utf16.len;
|
||||
@@ -357,54 +358,54 @@ fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy an array of UTF8 data into an UTF32 buffer without bounds
|
||||
* checking. This will assume the buffer is sufficiently large to hold
|
||||
* the converted data.
|
||||
*
|
||||
* @param [in] utf8 `The UTF8 buffer containing the data to convert.`
|
||||
* @param [out] utf32_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
**/
|
||||
<*
|
||||
Copy an array of UTF8 data into an UTF32 buffer without bounds
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf8 `The UTF8 buffer containing the data to convert.`
|
||||
@param [out] utf32_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
*>
|
||||
fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
(utf32_buffer++)[0] = uc;
|
||||
}
|
||||
{
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
(utf32_buffer++)[0] = uc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy an array of UTF8 data into an UTF16 buffer without bounds
|
||||
* checking. This will assume the buffer is sufficiently large to hold
|
||||
* the converted data.
|
||||
*
|
||||
* @param [in] utf8 `The UTF8 buffer containing the data to convert.`
|
||||
* @param [out] utf16_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
**/
|
||||
<*
|
||||
Copy an array of UTF8 data into an UTF16 buffer without bounds
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf8 `The UTF8 buffer containing the data to convert.`
|
||||
@param [out] utf16_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
*>
|
||||
fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
char32_to_utf16_unsafe(uc, &utf16_buffer) @inline;
|
||||
i += width;
|
||||
}
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
char32_to_utf16_unsafe(uc, &utf16_buffer) @inline;
|
||||
i += width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy an array of UTF32 code points into an UTF8 buffer without bounds
|
||||
* checking. This will assume the buffer is sufficiently large to hold
|
||||
* the converted data.
|
||||
*
|
||||
* @param [in] utf32 `The UTF32 buffer containing the data to convert.`
|
||||
* @param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
**/
|
||||
<*
|
||||
Copy an array of UTF32 code points into an UTF8 buffer without bounds
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf32 `The UTF32 buffer containing the data to convert.`
|
||||
@param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
*>
|
||||
fn void utf32to8_unsafe(Char32[] utf32, char* utf8_buffer)
|
||||
{
|
||||
char* start = utf8_buffer;
|
||||
|
||||
@@ -1,40 +1,44 @@
|
||||
module std::core::dstring;
|
||||
import std::io;
|
||||
|
||||
def DString = distinct void*;
|
||||
distinct DString (OutStream) = DStringOpaque*;
|
||||
distinct DStringOpaque = void;
|
||||
|
||||
const usz MIN_CAPACITY @private = 16;
|
||||
|
||||
/**
|
||||
* @require !str.data() "String already initialized"
|
||||
**/
|
||||
fn void DString.init(DString *str, usz capacity = MIN_CAPACITY, Allocator* using = mem::heap())
|
||||
<*
|
||||
@require !self.data() "String already initialized"
|
||||
*>
|
||||
fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
|
||||
StringData* data = malloc(StringData, 1, .using = using, .end_padding = capacity);
|
||||
data.allocator = using;
|
||||
StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
|
||||
data.allocator = allocator;
|
||||
data.len = 0;
|
||||
data.capacity = capacity;
|
||||
*str = (DString)data;
|
||||
return *self = (DString)data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !str.data() "String already initialized"
|
||||
**/
|
||||
fn void DString.tinit(DString *str, usz capacity = MIN_CAPACITY) => str.init(capacity, mem::temp()) @inline;
|
||||
|
||||
fn DString new_with_capacity(usz capacity, Allocator* using = mem::heap())
|
||||
<*
|
||||
@require !self.data() "String already initialized"
|
||||
*>
|
||||
fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY)
|
||||
{
|
||||
DString dstr;
|
||||
dstr.init(capacity, using);
|
||||
return dstr;
|
||||
self.new_init(capacity, allocator::temp()) @inline;
|
||||
return *self;
|
||||
}
|
||||
|
||||
fn DString tnew_with_capacity(usz capacity) => new_with_capacity(capacity, mem::temp()) @inline;
|
||||
fn DString new_with_capacity(usz capacity, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return DString{}.new_init(capacity, allocator);
|
||||
}
|
||||
|
||||
fn DString new(String c = "", Allocator* using = mem::heap())
|
||||
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, allocator::temp()) @inline;
|
||||
|
||||
fn DString new(String c = "", Allocator allocator = allocator::heap())
|
||||
{
|
||||
usz len = c.len;
|
||||
StringData* data = (StringData*)new_with_capacity(len, using);
|
||||
StringData* data = (StringData*)new_with_capacity(len, allocator);
|
||||
if (len)
|
||||
{
|
||||
data.len = len;
|
||||
@@ -43,26 +47,77 @@ fn DString new(String c = "", Allocator* using = mem::heap())
|
||||
return (DString)data;
|
||||
}
|
||||
|
||||
fn DString tnew(String s = "") => new(s, mem::temp()) @inline;
|
||||
fn DString temp_new(String s = "") => new(s, allocator::temp()) @inline;
|
||||
|
||||
fn DString DString.new_concat(DString a, DString b, Allocator* using = mem::heap())
|
||||
|
||||
fn void DString.replace_char(self, char ch, char replacement)
|
||||
{
|
||||
StringData* data = self.data();
|
||||
foreach (&c : data.chars[:data.len])
|
||||
{
|
||||
if (*c == ch) *c = replacement;
|
||||
}
|
||||
}
|
||||
|
||||
fn void DString.replace(&self, String needle, String replacement)
|
||||
{
|
||||
StringData* data = self.data();
|
||||
usz needle_len = needle.len;
|
||||
if (!data || data.len < needle_len) return;
|
||||
usz replace_len = replacement.len;
|
||||
if (needle_len == 1 && replace_len == 1)
|
||||
{
|
||||
self.replace_char(needle[0], replacement[0]);
|
||||
return;
|
||||
}
|
||||
@pool(data.allocator) {
|
||||
String str = self.tcopy_str();
|
||||
self.clear();
|
||||
usz len = str.len;
|
||||
usz match = 0;
|
||||
foreach (i, c : str)
|
||||
{
|
||||
if (c == needle[match])
|
||||
{
|
||||
match++;
|
||||
if (match == needle_len)
|
||||
{
|
||||
self.append_chars(replacement);
|
||||
match = 0;
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (match > 0)
|
||||
{
|
||||
self.append_chars(str[i - match:match]);
|
||||
match = 0;
|
||||
}
|
||||
self.append_char(c);
|
||||
}
|
||||
if (match > 0) self.append_chars(str[^match:match]);
|
||||
};
|
||||
}
|
||||
|
||||
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
|
||||
{
|
||||
DString string;
|
||||
string.init(a.len() + b.len(), using);
|
||||
string.append(a);
|
||||
string.new_init(self.len() + b.len(), allocator);
|
||||
string.append(self);
|
||||
string.append(b);
|
||||
return string;
|
||||
}
|
||||
|
||||
fn DString DString.new_tconcat(DString a, DString b) => a.new_concat(b, mem::temp());
|
||||
fn DString DString.temp_concat(self, DString b) => self.new_concat(b, allocator::temp());
|
||||
|
||||
fn ZString DString.zstr(DString str)
|
||||
fn ZString DString.zstr_view(&self)
|
||||
{
|
||||
StringData* data = str.data();
|
||||
StringData* data = self.data();
|
||||
if (!data) return "";
|
||||
if (data.capacity == data.len)
|
||||
{
|
||||
str.reserve(1);
|
||||
self.reserve(1);
|
||||
data = self.data();
|
||||
data.chars[data.len] = 0;
|
||||
}
|
||||
else if (data.chars[data.len] != 0)
|
||||
@@ -72,139 +127,137 @@ fn ZString DString.zstr(DString str)
|
||||
return (ZString)&data.chars[0];
|
||||
}
|
||||
|
||||
fn usz DString.capacity(DString this)
|
||||
fn usz DString.capacity(self)
|
||||
{
|
||||
if (!this) return 0;
|
||||
return this.data().capacity;
|
||||
if (!self) return 0;
|
||||
return self.data().capacity;
|
||||
}
|
||||
|
||||
fn usz DString.len(DString this)
|
||||
fn usz DString.len(&self) @dynamic @operator(len)
|
||||
{
|
||||
if (!this) return 0;
|
||||
return this.data().len;
|
||||
if (!*self) return 0;
|
||||
return self.data().len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require new_size <= this.len()
|
||||
*/
|
||||
fn void DString.chop(DString this, usz new_size)
|
||||
<*
|
||||
@require new_size <= self.len()
|
||||
*>
|
||||
fn void DString.chop(self, usz new_size)
|
||||
{
|
||||
if (!this) return;
|
||||
this.data().len = new_size;
|
||||
if (!self) return;
|
||||
self.data().len = new_size;
|
||||
}
|
||||
|
||||
fn String DString.str(DString str)
|
||||
fn String DString.str_view(self)
|
||||
{
|
||||
StringData* data = (StringData*)str;
|
||||
StringData* data = self.data();
|
||||
if (!data) return "";
|
||||
return (String)data.chars[:data.len];
|
||||
}
|
||||
|
||||
fn void DString.append_utf32(DString* str, Char32[] chars)
|
||||
<*
|
||||
@require index < self.len()
|
||||
@require self.data() "Empty string"
|
||||
*>
|
||||
fn char DString.char_at(self, usz index) @operator([])
|
||||
{
|
||||
str.reserve(chars.len);
|
||||
return self.data().chars[index];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len()
|
||||
@require self.data() "Empty string"
|
||||
*>
|
||||
fn char* DString.char_ref(&self, usz index) @operator(&[])
|
||||
{
|
||||
return &self.data().chars[index];
|
||||
}
|
||||
|
||||
fn usz DString.append_utf32(&self, Char32[] chars)
|
||||
{
|
||||
self.reserve(chars.len);
|
||||
usz end = self.len();
|
||||
foreach (Char32 c : chars)
|
||||
{
|
||||
str.append_char32(c);
|
||||
self.append_char32(c);
|
||||
}
|
||||
return self.data().len - end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < str.len()
|
||||
**/
|
||||
fn void DString.set(DString str, usz index, char c)
|
||||
<*
|
||||
@require index < self.len()
|
||||
*>
|
||||
fn void DString.set(self, usz index, char c) @operator([]=)
|
||||
{
|
||||
str.data().chars[index] = c;
|
||||
self.data().chars[index] = c;
|
||||
}
|
||||
|
||||
fn void DString.append_repeat(DString* str, char c, usz times)
|
||||
fn void DString.append_repeat(&self, char c, usz times)
|
||||
{
|
||||
if (times == 0) return;
|
||||
str.reserve(times);
|
||||
StringData* data = str.data();
|
||||
self.reserve(times);
|
||||
StringData* data = self.data();
|
||||
for (usz i = 0; i < times; i++)
|
||||
{
|
||||
data.chars[data.len++] = c;
|
||||
data.chars[data.len++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require c <= 0x10ffff
|
||||
*/
|
||||
fn void DString.append_char32(DString* str, Char32 c)
|
||||
<*
|
||||
@require c <= 0x10ffff
|
||||
*>
|
||||
fn usz DString.append_char32(&self, Char32 c)
|
||||
{
|
||||
if (c < 0x7f)
|
||||
{
|
||||
str.reserve(1);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)c;
|
||||
return;
|
||||
}
|
||||
if (c < 0x7ff)
|
||||
{
|
||||
str.reserve(2);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xC0 | c >> 6);
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
return;
|
||||
}
|
||||
if (c < 0xffff)
|
||||
{
|
||||
str.reserve(3);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xE0 | c >> 12);
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
return;
|
||||
}
|
||||
str.reserve(4);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xF0 | c >> 18);
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
char[4] buffer @noinit;
|
||||
char* p = &buffer;
|
||||
usz n = conv::char32_to_utf8_unsafe(c, &p);
|
||||
self.reserve(n);
|
||||
StringData* data = self.data();
|
||||
data.chars[data.len:n] = buffer[:n];
|
||||
data.len += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn DString DString.tcopy(DString* str) => str.copy(mem::temp());
|
||||
fn DString DString.tcopy(&self) => self.copy(allocator::temp());
|
||||
|
||||
fn DString DString.copy(DString* str, Allocator* using = null)
|
||||
fn DString DString.copy(self, Allocator allocator = null)
|
||||
{
|
||||
if (!str)
|
||||
if (!self)
|
||||
{
|
||||
if (using) return new_with_capacity(0, using);
|
||||
if (allocator) return new_with_capacity(0, allocator);
|
||||
return (DString)null;
|
||||
}
|
||||
if (!using) using = mem::heap();
|
||||
StringData* data = str.data();
|
||||
DString new_string = new_with_capacity(data.capacity, using);
|
||||
StringData* data = self.data();
|
||||
if (!allocator) allocator = allocator::heap();
|
||||
DString new_string = new_with_capacity(data.capacity, allocator);
|
||||
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
|
||||
return new_string;
|
||||
}
|
||||
|
||||
fn ZString DString.copy_zstr(DString* str, Allocator* using = mem::heap())
|
||||
fn ZString DString.copy_zstr(self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
usz str_len = str.len();
|
||||
usz str_len = self.len();
|
||||
if (!str_len)
|
||||
{
|
||||
return (ZString)calloc(1, .using = using);
|
||||
return (ZString)allocator::calloc(allocator, 1);
|
||||
}
|
||||
char* zstr = malloc(str_len + 1, .using = using);
|
||||
StringData* data = str.data();
|
||||
char* zstr = allocator::malloc(allocator, str_len + 1);
|
||||
StringData* data = self.data();
|
||||
mem::copy(zstr, &data.chars, str_len);
|
||||
zstr[str_len] = 0;
|
||||
return (ZString)zstr;
|
||||
}
|
||||
|
||||
fn String DString.copy_str(DString* str, Allocator* using = mem::heap())
|
||||
fn String DString.copy_str(self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return (String)str.copy_zstr(using)[:str.len()];
|
||||
return (String)self.copy_zstr(allocator)[:self.len()];
|
||||
}
|
||||
|
||||
fn String DString.tcopy_str(DString* str) => str.copy_str(mem::temp()) @inline;
|
||||
fn String DString.tcopy_str(self) => self.copy_str(allocator::temp()) @inline;
|
||||
|
||||
fn bool DString.equals(DString str, DString other_string)
|
||||
fn bool DString.equals(self, DString other_string)
|
||||
{
|
||||
StringData *str1 = str.data();
|
||||
StringData *str1 = self.data();
|
||||
StringData *str2 = other_string.data();
|
||||
if (str1 == str2) return true;
|
||||
if (!str1) return str2.len == 0;
|
||||
@@ -218,18 +271,18 @@ fn bool DString.equals(DString str, DString other_string)
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void DString.free(DString* str)
|
||||
fn void DString.free(&self)
|
||||
{
|
||||
if (!*str) return;
|
||||
StringData* data = str.data();
|
||||
if (!*self) return;
|
||||
StringData* data = self.data();
|
||||
if (!data) return;
|
||||
free(data, .using = data.allocator);
|
||||
*str = (DString)null;
|
||||
allocator::free(data.allocator, data);
|
||||
*self = (DString)null;
|
||||
}
|
||||
|
||||
fn bool DString.less(DString str, DString other_string)
|
||||
fn bool DString.less(self, DString other_string)
|
||||
{
|
||||
StringData* str1 = str.data();
|
||||
StringData* str1 = self.data();
|
||||
StringData* str2 = other_string.data();
|
||||
if (str1 == str2) return false;
|
||||
if (!str1) return str2.len != 0;
|
||||
@@ -244,93 +297,275 @@ fn bool DString.less(DString str, DString other_string)
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void DString.append_chars(DString* this, String str)
|
||||
fn void DString.append_chars(&self, String str)
|
||||
{
|
||||
usz other_len = str.len;
|
||||
if (!other_len) return;
|
||||
if (!*this)
|
||||
if (!*self)
|
||||
{
|
||||
*this = new(str);
|
||||
*self = new(str);
|
||||
return;
|
||||
}
|
||||
this.reserve(other_len);
|
||||
StringData* data = (StringData*)*this;
|
||||
self.reserve(other_len);
|
||||
StringData* data = self.data();
|
||||
mem::copy(&data.chars[data.len], str.ptr, other_len);
|
||||
data.len += other_len;
|
||||
}
|
||||
|
||||
fn Char32[] DString.copy_utf32(DString* this, Allocator* using = mem::heap())
|
||||
fn Char32[] DString.copy_utf32(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return this.str().to_utf32(using) @inline!!;
|
||||
return self.str_view().to_utf32(allocator) @inline!!;
|
||||
}
|
||||
|
||||
fn void DString.append_string(DString* this, DString str)
|
||||
fn void DString.append_string(&self, DString str)
|
||||
{
|
||||
StringData* other = (StringData*)str;
|
||||
StringData* other = str.data();
|
||||
if (!other) return;
|
||||
this.append(str.str());
|
||||
self.append(str.str_view());
|
||||
}
|
||||
|
||||
fn void DString.clear(DString* str)
|
||||
fn void DString.clear(self)
|
||||
{
|
||||
str.data().len = 0;
|
||||
if (!self) return;
|
||||
self.data().len = 0;
|
||||
}
|
||||
|
||||
fn void DString.append_char(DString* str, char c)
|
||||
fn usz! DString.write(&self, char[] buffer) @dynamic
|
||||
{
|
||||
if (!*str)
|
||||
self.append_chars((String)buffer);
|
||||
return buffer.len;
|
||||
}
|
||||
|
||||
fn void! DString.write_byte(&self, char c) @dynamic
|
||||
{
|
||||
self.append_char(c);
|
||||
}
|
||||
|
||||
fn void DString.append_char(&self, char c)
|
||||
{
|
||||
if (!*self)
|
||||
{
|
||||
*str = new_with_capacity(MIN_CAPACITY);
|
||||
*self = new_with_capacity(MIN_CAPACITY);
|
||||
}
|
||||
str.reserve(1);
|
||||
StringData* data = (StringData*)*str;
|
||||
self.reserve(1);
|
||||
StringData* data = self.data();
|
||||
data.chars[data.len++] = c;
|
||||
}
|
||||
|
||||
<*
|
||||
@require start < self.len()
|
||||
@require end < self.len()
|
||||
@require end >= start "End must be same or equal to the start"
|
||||
*>
|
||||
fn void DString.delete_range(&self, usz start, usz end)
|
||||
{
|
||||
self.delete(start, end - start + 1);
|
||||
}
|
||||
|
||||
macro void DString.append(DString* str, value)
|
||||
<*
|
||||
@require start < self.len()
|
||||
@require start + len <= self.len()
|
||||
*>
|
||||
fn void DString.delete(&self, usz start, usz len = 1)
|
||||
{
|
||||
if (!len) return;
|
||||
StringData* data = self.data();
|
||||
usz new_len = data.len - len;
|
||||
if (new_len == 0)
|
||||
{
|
||||
data.len = 0;
|
||||
return;
|
||||
}
|
||||
usz len_after = data.len - start - len;
|
||||
if (len_after > 0)
|
||||
{
|
||||
data.chars[start:len_after] = data.chars[start + len:len_after];
|
||||
}
|
||||
data.len = new_len;
|
||||
}
|
||||
|
||||
macro void DString.append(&self, value)
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
$switch ($Type)
|
||||
$case char:
|
||||
$case ichar:
|
||||
str.append_char(value);
|
||||
self.append_char(value);
|
||||
$case DString:
|
||||
str.append_string(value);
|
||||
self.append_string(value);
|
||||
$case String:
|
||||
str.append_chars(value);
|
||||
self.append_chars(value);
|
||||
$case Char32:
|
||||
str.append_char32(value);
|
||||
self.append_char32(value);
|
||||
$default:
|
||||
$switch
|
||||
$case @convertible(value, Char32):
|
||||
str.append_char32(value);
|
||||
$case @convertible(value, String):
|
||||
str.append_chars(value);
|
||||
$case $defined((Char32)value):
|
||||
self.append_char32((Char32)value);
|
||||
$case $defined((String)value):
|
||||
self.append_chars((String)value);
|
||||
$default:
|
||||
$error "Unsupported type for append – use printf instead.";
|
||||
$error "Unsupported type for append – use appendf instead.";
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
|
||||
fn usz! DString.printf(DString* str, String format, args...) @maydiscard
|
||||
<*
|
||||
@require index <= self.len()
|
||||
*>
|
||||
fn void DString.insert_chars_at(&self, usz index, String s)
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_string_append_fn, str);
|
||||
return formatter.vprintf(format, args);
|
||||
if (s.len == 0) return;
|
||||
self.reserve(s.len);
|
||||
StringData* data = self.data();
|
||||
usz len = self.len();
|
||||
if (data.chars[:len].ptr == s.ptr)
|
||||
{
|
||||
// Source and destination are the same: nothing to do.
|
||||
return;
|
||||
}
|
||||
index = min(index, len);
|
||||
data.len += s.len;
|
||||
|
||||
char* start = data.chars[index:s.len].ptr; // area to insert into
|
||||
mem::move(start + s.len, start, len - index); // move existing data
|
||||
switch
|
||||
{
|
||||
case s.ptr <= start && start < s.ptr + s.len:
|
||||
// Overlapping areas.
|
||||
foreach_r (i, c : s)
|
||||
{
|
||||
data.chars[index + i] = c;
|
||||
}
|
||||
case start <= s.ptr && s.ptr < start + len:
|
||||
// Source has moved.
|
||||
mem::move(start, s.ptr + s.len, s.len);
|
||||
default:
|
||||
mem::move(start, s, s.len);
|
||||
}
|
||||
}
|
||||
|
||||
fn usz! DString.printfn(DString* str, String format, args...) @maydiscard
|
||||
<*
|
||||
@require index <= self.len()
|
||||
*>
|
||||
fn void DString.insert_string_at(&self, usz index, DString str)
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_string_append_fn, str);
|
||||
usz len = formatter.vprintf(format, args)!;
|
||||
str.append('\n');
|
||||
return len + 1;
|
||||
StringData* other = str.data();
|
||||
if (!other) return;
|
||||
self.insert_at(index, str.str_view());
|
||||
}
|
||||
|
||||
fn DString new_join(String[] s, String joiner, Allocator* using = mem::heap())
|
||||
<*
|
||||
@require index <= self.len()
|
||||
*>
|
||||
fn void DString.insert_char_at(&self, usz index, char c)
|
||||
{
|
||||
self.reserve(1);
|
||||
StringData* data = self.data();
|
||||
|
||||
char* start = &data.chars[index];
|
||||
mem::move(start + 1, start, self.len() - index);
|
||||
data.chars[index] = c;
|
||||
data.len++;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.len()
|
||||
*>
|
||||
fn usz DString.insert_char32_at(&self, usz index, Char32 c)
|
||||
{
|
||||
char[4] buffer @noinit;
|
||||
char* p = &buffer;
|
||||
usz n = conv::char32_to_utf8_unsafe(c, &p);
|
||||
|
||||
self.reserve(n);
|
||||
StringData* data = self.data();
|
||||
|
||||
char* start = &data.chars[index];
|
||||
mem::move(start + n, start, self.len() - index);
|
||||
data.chars[index:n] = buffer[:n];
|
||||
data.len += n;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.len()
|
||||
*>
|
||||
fn usz DString.insert_utf32_at(&self, usz index, Char32[] chars)
|
||||
{
|
||||
usz n = conv::utf8len_for_utf32(chars);
|
||||
|
||||
self.reserve(n);
|
||||
StringData* data = self.data();
|
||||
|
||||
char* start = &data.chars[index];
|
||||
mem::move(start + n, start, self.len() - index);
|
||||
|
||||
char[4] buffer @noinit;
|
||||
|
||||
foreach(c : chars)
|
||||
{
|
||||
char* p = &buffer;
|
||||
usz m = conv::char32_to_utf8_unsafe(c, &p);
|
||||
data.chars[index:m] = buffer[:m];
|
||||
index += m;
|
||||
}
|
||||
|
||||
data.len += n;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
macro void DString.insert_at(&self, usz index, value)
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
$switch ($Type)
|
||||
$case char:
|
||||
$case ichar:
|
||||
self.insert_char_at(index, value);
|
||||
$case DString:
|
||||
self.insert_string_at(index, value);
|
||||
$case String:
|
||||
self.insert_chars_at(index, value);
|
||||
$case Char32:
|
||||
self.insert_char32_at(index, value);
|
||||
$default:
|
||||
$switch
|
||||
$case $defined((Char32)value):
|
||||
self.insert_char32_at(index, (Char32)value);
|
||||
$case $defined((String)value):
|
||||
self.insert_chars_at(index, (String)value);
|
||||
$default:
|
||||
$error "Unsupported type for insert";
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn usz! DString.appendf(&self, String format, args...) @maydiscard
|
||||
{
|
||||
if (!self.data()) self.new_init(format.len + 20);
|
||||
@pool(self.data().allocator)
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_string_append_fn, self);
|
||||
return formatter.vprintf(format, args);
|
||||
};
|
||||
}
|
||||
|
||||
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
|
||||
{
|
||||
if (!self.data()) self.new_init(format.len + 20);
|
||||
@pool(self.data().allocator)
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_string_append_fn, self);
|
||||
usz len = formatter.vprintf(format, args)!;
|
||||
self.append('\n');
|
||||
return len + 1;
|
||||
};
|
||||
}
|
||||
|
||||
fn DString new_join(String[] s, String joiner, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!s.len) return (DString)null;
|
||||
usz total_size = joiner.len * s.len;
|
||||
@@ -338,7 +573,7 @@ fn DString new_join(String[] s, String joiner, Allocator* using = mem::heap())
|
||||
{
|
||||
total_size += str.len;
|
||||
}
|
||||
DString res = new_with_capacity(total_size, using);
|
||||
DString res = new_with_capacity(total_size, allocator);
|
||||
res.append(s[0]);
|
||||
foreach (String* &str : s[1..])
|
||||
{
|
||||
@@ -348,42 +583,57 @@ fn DString new_join(String[] s, String joiner, Allocator* using = mem::heap())
|
||||
return res;
|
||||
}
|
||||
|
||||
fn void! out_string_append_fn(char c, void* data) @private
|
||||
fn void! out_string_append_fn(void* data, char c) @private
|
||||
{
|
||||
DString* s = data;
|
||||
s.append_char(c);
|
||||
}
|
||||
|
||||
|
||||
fn StringData* DString.data(DString str) @inline @private
|
||||
fn void DString.reverse(self)
|
||||
{
|
||||
return (StringData*)str;
|
||||
StringData *data = self.data();
|
||||
if (!data) return;
|
||||
isz mid = data.len / 2;
|
||||
for (isz i = 0; i < mid; i++)
|
||||
{
|
||||
char temp = data.chars[i];
|
||||
isz reverse_index = data.len - 1 - i;
|
||||
data.chars[i] = data.chars[reverse_index];
|
||||
data.chars[reverse_index] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
fn void DString.reserve(DString* str, usz addition)
|
||||
fn StringData* DString.data(self) @inline @private
|
||||
{
|
||||
StringData* data = str.data();
|
||||
return (StringData*)self;
|
||||
}
|
||||
|
||||
fn void DString.reserve(&self, usz addition)
|
||||
{
|
||||
StringData* data = self.data();
|
||||
if (!data)
|
||||
{
|
||||
*str = dstring::new_with_capacity(addition);
|
||||
*self = dstring::new_with_capacity(addition);
|
||||
return;
|
||||
}
|
||||
usz len = data.len + addition;
|
||||
if (data.capacity >= len) return;
|
||||
usz new_capacity = data.capacity *= 2;
|
||||
usz new_capacity = data.capacity * 2;
|
||||
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
|
||||
*str = (DString)realloc(data, StringData.sizeof + new_capacity, .using = data.allocator);
|
||||
while (new_capacity < len) new_capacity *= 2;
|
||||
data.capacity = new_capacity;
|
||||
*self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity);
|
||||
}
|
||||
|
||||
fn usz! DString.read_from_stream(DString* string, Stream* reader)
|
||||
fn usz! DString.read_from_stream(&self, InStream reader)
|
||||
{
|
||||
if (reader.supports_available())
|
||||
if (&reader.available)
|
||||
{
|
||||
usz total_read = 0;
|
||||
while (usz available = reader.available()!)
|
||||
{
|
||||
string.reserve(available);
|
||||
StringData* data = string.data();
|
||||
self.reserve(available);
|
||||
StringData* data = self.data();
|
||||
usz len = reader.read(data.chars[data.len..(data.capacity - 1)])!;
|
||||
total_read += len;
|
||||
data.len += len;
|
||||
@@ -394,8 +644,8 @@ fn usz! DString.read_from_stream(DString* string, Stream* reader)
|
||||
while (true)
|
||||
{
|
||||
// Reserve at least 16 bytes
|
||||
string.reserve(16);
|
||||
StringData* data = string.data();
|
||||
self.reserve(16);
|
||||
StringData* data = self.data();
|
||||
// Read into the rest of the buffer
|
||||
usz read = reader.read(data.chars[data.len..(data.capacity - 1)])!;
|
||||
data.len += read;
|
||||
@@ -407,7 +657,7 @@ fn usz! DString.read_from_stream(DString* string, Stream* reader)
|
||||
|
||||
struct StringData @private
|
||||
{
|
||||
Allocator* allocator;
|
||||
Allocator allocator;
|
||||
usz len;
|
||||
usz capacity;
|
||||
char[*] chars;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::env;
|
||||
import libc;
|
||||
|
||||
enum CompilerOptLevel
|
||||
{
|
||||
O0,
|
||||
O1,
|
||||
O2,
|
||||
O3
|
||||
O0,
|
||||
O1,
|
||||
O2,
|
||||
O3
|
||||
}
|
||||
|
||||
enum MemoryEnvironment
|
||||
@@ -112,28 +112,51 @@ enum ArchType
|
||||
WASM64, // WebAssembly with 64-bit pointers
|
||||
RSCRIPT32, // 32-bit RenderScript
|
||||
RSCRIPT64, // 64-bit RenderScript
|
||||
XTENSA, // Xtensa
|
||||
}
|
||||
|
||||
const OsType OS_TYPE = (OsType)$$OS_TYPE;
|
||||
const ArchType ARCH_TYPE = (ArchType)$$ARCH_TYPE;
|
||||
const bool COMPILER_LIBC_AVAILABLE = $$COMPILER_LIBC_AVAILABLE;
|
||||
const CompilerOptLevel COMPILER_OPT_LEVEL = (CompilerOptLevel)$$COMPILER_OPT_LEVEL;
|
||||
const String COMPILER_BUILD_HASH = $$BUILD_HASH;
|
||||
const String COMPILER_BUILD_DATE = $$BUILD_DATE;
|
||||
const OsType OS_TYPE = OsType.from_ordinal($$OS_TYPE);
|
||||
const ArchType ARCH_TYPE = ArchType.from_ordinal($$ARCH_TYPE);
|
||||
const bool ARCH_32_BIT = $$REGISTER_SIZE == 32;
|
||||
const bool ARCH_64_BIT = $$REGISTER_SIZE == 64;
|
||||
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
|
||||
const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
|
||||
const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel.from_ordinal($$COMPILER_OPT_LEVEL);
|
||||
const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
|
||||
const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED;
|
||||
const bool F16_SUPPORT = $$PLATFORM_F16_SUPPORTED;
|
||||
const bool F128_SUPPORT = $$PLATFORM_F128_SUPPORTED;
|
||||
const REGISTER_SIZE = $$REGISTER_SIZE;
|
||||
const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE;
|
||||
const bool DEBUG_SYMBOLS = $$DEBUG_SYMBOLS;
|
||||
const bool BACKTRACE = $$BACKTRACE;
|
||||
const usz LLVM_VERSION = $$LLVM_VERSION;
|
||||
const bool BENCHMARKING = $$BENCHMARKING;
|
||||
const bool TESTING = $$TESTING;
|
||||
const MemoryEnvironment MEMORY_ENV = (MemoryEnvironment)$$MEMORY_ENVIRONMENT;
|
||||
const MemoryEnvironment MEMORY_ENV = MemoryEnvironment.from_ordinal($$MEMORY_ENVIRONMENT);
|
||||
const bool TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING);
|
||||
const bool X86_64 = ARCH_TYPE == X86_64;
|
||||
const bool X86 = ARCH_TYPE == X86;
|
||||
const bool AARCH64 = ARCH_TYPE == AARCH64;
|
||||
const bool NATIVE_STACKTRACE = LINUX || DARWIN || WIN32;
|
||||
const bool LINUX = LIBC && OS_TYPE == LINUX;
|
||||
const bool DARWIN = LIBC && os_is_darwin();
|
||||
const bool WIN32 = LIBC && OS_TYPE == WIN32;
|
||||
const bool POSIX = LIBC && os_is_posix();
|
||||
const bool OPENBSD = LIBC && OS_TYPE == OPENBSD;
|
||||
const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
|
||||
const bool NETBSD = LIBC && OS_TYPE == NETBSD;
|
||||
const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD;
|
||||
const bool WASI = LIBC && OS_TYPE == WASI;
|
||||
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
|
||||
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
|
||||
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
|
||||
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
|
||||
|
||||
macro bool os_is_win32()
|
||||
{
|
||||
return OS_TYPE == WIN32;
|
||||
}
|
||||
|
||||
macro bool os_is_darwin()
|
||||
macro bool os_is_darwin() @const
|
||||
{
|
||||
$switch (OS_TYPE)
|
||||
$case IOS:
|
||||
@@ -146,7 +169,7 @@ macro bool os_is_darwin()
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool os_is_posix()
|
||||
macro bool os_is_posix() @const
|
||||
{
|
||||
$switch (OS_TYPE)
|
||||
$case IOS:
|
||||
@@ -170,57 +193,5 @@ macro bool os_is_posix()
|
||||
$endswitch
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param [&in] name
|
||||
* @require name.len > 0
|
||||
**/
|
||||
fn String! get_var(String name)
|
||||
{
|
||||
$if COMPILER_LIBC_AVAILABLE && !os_is_win32():
|
||||
@pool()
|
||||
{
|
||||
ZString val = libc::getenv(name.zstr_tcopy());
|
||||
return val ? val.as_str() : SearchResult.MISSING?;
|
||||
};
|
||||
$else
|
||||
return "";
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param [&in] name
|
||||
* @param [&in] value
|
||||
* @require name.len > 0
|
||||
**/
|
||||
fn void set_var(String name, String value, bool overwrite = true)
|
||||
{
|
||||
$if COMPILER_LIBC_AVAILABLE && !os_is_win32():
|
||||
@pool()
|
||||
{
|
||||
if (libc::setenv(name.zstr_tcopy(), value.zstr_copy(), (int)overwrite))
|
||||
{
|
||||
unreachable();
|
||||
}
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] name
|
||||
* @require name.len > 0
|
||||
**/
|
||||
fn void clear_var(String name)
|
||||
{
|
||||
$if COMPILER_LIBC_AVAILABLE && !os_is_win32():
|
||||
@pool()
|
||||
{
|
||||
if (libc::unsetenv(name.zstr_tcopy()))
|
||||
{
|
||||
unreachable();
|
||||
}
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
|
||||
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,107 +3,452 @@ module std::core::mem::allocator;
|
||||
const DEFAULT_SIZE_PREFIX = usz.sizeof;
|
||||
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
|
||||
|
||||
const Allocator* NULL_ALLOCATOR = &_NULL_ALLOCATOR;
|
||||
const Allocator* LIBC_ALLOCATOR = &_SYSTEM_ALLOCATOR;
|
||||
|
||||
def AllocatorFunction = fn void*!(Allocator* allocator, usz new_size, usz alignment, usz offset, void* old_pointer, AllocationKind kind);
|
||||
|
||||
struct Allocator
|
||||
struct TrackingEnv
|
||||
{
|
||||
AllocatorFunction function;
|
||||
String file;
|
||||
String function;
|
||||
uint line;
|
||||
}
|
||||
|
||||
enum AllocationKind
|
||||
enum AllocInitType
|
||||
{
|
||||
ALLOC,
|
||||
CALLOC,
|
||||
REALLOC,
|
||||
FREE,
|
||||
ALIGNED_ALLOC,
|
||||
ALIGNED_CALLOC,
|
||||
ALIGNED_REALLOC,
|
||||
ALIGNED_FREE,
|
||||
RESET,
|
||||
MARK,
|
||||
NO_ZERO,
|
||||
ZERO
|
||||
}
|
||||
|
||||
interface Allocator
|
||||
{
|
||||
fn void reset(usz mark) @optional;
|
||||
fn usz mark() @optional;
|
||||
<*
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require size > 0
|
||||
*>
|
||||
fn void*! acquire(usz size, AllocInitType init_type, usz alignment = 0);
|
||||
<*
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require ptr != null
|
||||
@require new_size > 0
|
||||
*>
|
||||
fn void*! resize(void* ptr, usz new_size, usz alignment = 0);
|
||||
<*
|
||||
@require ptr != null
|
||||
*>
|
||||
fn void release(void* ptr, bool aligned);
|
||||
}
|
||||
|
||||
def MemoryAllocFn = fn char[]!(usz);
|
||||
|
||||
fault AllocationFailure
|
||||
{
|
||||
OUT_OF_MEMORY,
|
||||
UNSUPPORTED_OPERATION,
|
||||
CHUNK_TOO_LARGE,
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn void*! Allocator.alloc(Allocator* allocator, usz size) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, 0, 0, null, ALLOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require alignment && math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! Allocator.alloc_aligned(Allocator* allocator, usz size, usz alignment, usz offset = 0) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, alignment, offset, null, ALIGNED_ALLOC);
|
||||
}
|
||||
|
||||
fn void*! Allocator.realloc(Allocator* allocator, void* old_pointer, usz size) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, 0, 0, old_pointer, REALLOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require alignment && math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! Allocator.realloc_aligned(Allocator* allocator, void* old_pointer, usz size, usz alignment, usz offset = 0) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, alignment, offset, old_pointer, ALIGNED_REALLOC);
|
||||
}
|
||||
|
||||
fn usz! Allocator.mark(Allocator* allocator) @inline
|
||||
{
|
||||
return (usz)(uptr)allocator.function(allocator, 0, 0, 0, null, MARK);
|
||||
}
|
||||
|
||||
|
||||
fn void*! Allocator.calloc(Allocator* allocator, usz size) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, 0, 0, null, CALLOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require alignment && math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! Allocator.calloc_aligned(Allocator* allocator, usz size, usz alignment, usz offset = 0) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, alignment, offset, null, ALIGNED_CALLOC);
|
||||
}
|
||||
|
||||
fn void! Allocator.free(Allocator* allocator, void* old_pointer) @inline
|
||||
{
|
||||
allocator.function(allocator, 0, 0, 0, old_pointer, FREE)!;
|
||||
}
|
||||
|
||||
fn void! Allocator.free_aligned(Allocator* allocator, void* old_pointer) @inline
|
||||
{
|
||||
allocator.function(allocator, 0, 0, 0, old_pointer, ALIGNED_FREE)!;
|
||||
}
|
||||
|
||||
fn void Allocator.reset(Allocator* allocator, usz mark = 0)
|
||||
{
|
||||
allocator.function(allocator, mark, 0, 0, null, RESET)!!;
|
||||
OUT_OF_MEMORY,
|
||||
CHUNK_TOO_LARGE,
|
||||
}
|
||||
|
||||
fn usz alignment_for_allocation(usz alignment) @inline @private
|
||||
{
|
||||
if (alignment < mem::DEFAULT_MEM_ALIGNMENT)
|
||||
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
|
||||
}
|
||||
|
||||
macro void* malloc(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
return malloc_try(allocator, size)!!;
|
||||
}
|
||||
|
||||
macro void*! malloc_try(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
$if env::TESTING:
|
||||
char* data = allocator.acquire(size, NO_ZERO)!;
|
||||
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
$else
|
||||
return allocator.acquire(size, NO_ZERO);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void* calloc(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
return calloc_try(allocator, size)!!;
|
||||
}
|
||||
|
||||
macro void*! calloc_try(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator.acquire(size, ZERO);
|
||||
}
|
||||
|
||||
macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard
|
||||
{
|
||||
return realloc_try(allocator, ptr, new_size)!!;
|
||||
}
|
||||
|
||||
macro void*! realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
|
||||
{
|
||||
if (!new_size)
|
||||
{
|
||||
alignment = mem::DEFAULT_MEM_ALIGNMENT;
|
||||
free(allocator, ptr);
|
||||
return null;
|
||||
}
|
||||
return alignment;
|
||||
if (!ptr) return allocator.acquire(new_size, NO_ZERO);
|
||||
return allocator.resize(ptr, new_size);
|
||||
}
|
||||
|
||||
macro void free(Allocator allocator, void* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
$if env::TESTING:
|
||||
((char*)ptr)[0] = 0xBA;
|
||||
$endif
|
||||
allocator.release(ptr, false);
|
||||
}
|
||||
|
||||
macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
$if env::TESTING:
|
||||
char* data = allocator.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
$else
|
||||
return allocator.acquire(size, NO_ZERO, alignment);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void*! calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator.acquire(size, ZERO, alignment);
|
||||
}
|
||||
|
||||
macro void*! realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!new_size)
|
||||
{
|
||||
free_aligned(allocator, ptr);
|
||||
return null;
|
||||
}
|
||||
if (!ptr)
|
||||
{
|
||||
return malloc_aligned(allocator, new_size, alignment);
|
||||
}
|
||||
return allocator.resize(ptr, new_size, alignment);
|
||||
}
|
||||
|
||||
macro void free_aligned(Allocator allocator, void* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
$if env::TESTING:
|
||||
((char*)ptr)[0] = 0xBA;
|
||||
$endif
|
||||
allocator.release(ptr, aligned: true);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc(allocator, $Type.sizeof);
|
||||
$else
|
||||
$Type* val = malloc(allocator, $Type.sizeof);
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_try(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc_try(allocator, $Type.sizeof);
|
||||
$else
|
||||
$Type* val = malloc_try(allocator, $Type.sizeof)!;
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_aligned($Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc_aligned(allocator, $Type.sizeof, $Type.alignof);
|
||||
$else
|
||||
$Type* val = malloc_aligned(allocator, $Type.sizeof, $Type.alignof);
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT
|
||||
*>
|
||||
macro new_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
|
||||
{
|
||||
return ($Type*)calloc_try(allocator, $Type.sizeof + padding);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
*>
|
||||
macro alloc(Allocator allocator, $Type) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc(allocator, $Type.sizeof);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
*>
|
||||
macro alloc_try(Allocator allocator, $Type) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc_try(allocator, $Type.sizeof);
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
macro alloc_aligned(Allocator allocator, $Type) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc_aligned(allocator, $Type.sizeof, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT
|
||||
*>
|
||||
macro alloc_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc_try(allocator, $Type.sizeof + padding);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
|
||||
*>
|
||||
macro new_array(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return new_array_try(allocator, $Type, elements)!!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
|
||||
*>
|
||||
macro new_array_try(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)calloc_try(allocator, $Type.sizeof * elements))[:elements];
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
macro new_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)calloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
|
||||
*>
|
||||
macro alloc_array(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return alloc_array_try(allocator, $Type, elements)!!;
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
*>
|
||||
macro alloc_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)malloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
|
||||
*>
|
||||
macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
|
||||
}
|
||||
|
||||
macro clone(Allocator allocator, value) @nodiscard
|
||||
{
|
||||
return new(allocator, $typeof(value), value);
|
||||
}
|
||||
|
||||
fn any clone_any(Allocator allocator, any value) @nodiscard
|
||||
{
|
||||
usz size = value.type.sizeof;
|
||||
void* data = malloc(allocator, size);
|
||||
mem::copy(data, value.ptr, size);
|
||||
return any_make(data, value.type);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require bytes > 0
|
||||
@require alignment > 0
|
||||
@require bytes <= isz.max
|
||||
*>
|
||||
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
|
||||
{
|
||||
if (alignment < void*.alignof) alignment = void*.alignof;
|
||||
usz header = AlignedBlock.sizeof + alignment;
|
||||
usz alignsize = bytes + header;
|
||||
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
|
||||
void* data = #alloc_fn(alignsize)!;
|
||||
$else
|
||||
void* data = #alloc_fn(alignsize);
|
||||
$endif
|
||||
void* mem = mem::aligned_pointer(data + AlignedBlock.sizeof, alignment);
|
||||
AlignedBlock* desc = (AlignedBlock*)mem - 1;
|
||||
assert(mem > data);
|
||||
*desc = { bytes, data };
|
||||
return mem;
|
||||
}
|
||||
|
||||
struct AlignedBlock
|
||||
{
|
||||
usz len;
|
||||
void* start;
|
||||
}
|
||||
|
||||
macro void! @aligned_free(#free_fn, void* old_pointer)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
|
||||
#free_fn(desc.start)!;
|
||||
$else
|
||||
#free_fn(desc.start);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require bytes > 0
|
||||
@require alignment > 0
|
||||
*>
|
||||
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
void* data_start = desc.start;
|
||||
void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!;
|
||||
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
|
||||
$if @typekind(#free_fn(data_start)) == OPTIONAL:
|
||||
#free_fn(data_start)!;
|
||||
$else
|
||||
#free_fn(data_start);
|
||||
$endif
|
||||
return new_data;
|
||||
}
|
||||
|
||||
|
||||
// All allocators
|
||||
|
||||
|
||||
tlocal Allocator thread_allocator @private = base_allocator();
|
||||
Allocator temp_base_allocator @private = base_allocator();
|
||||
|
||||
tlocal TempAllocator* thread_temp_allocator @private = null;
|
||||
tlocal TempAllocator*[2] temp_allocator_pair @private;
|
||||
|
||||
macro Allocator base_allocator() @private
|
||||
{
|
||||
$if env::LIBC:
|
||||
return &allocator::LIBC_ALLOCATOR;
|
||||
$else
|
||||
return &allocator::NULL_ALLOCATOR;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro TempAllocator* create_default_sized_temp_allocator(Allocator allocator) @local
|
||||
{
|
||||
$switch (env::MEMORY_ENV)
|
||||
$case NORMAL:
|
||||
return new_temp_allocator(1024 * 256, allocator)!!;
|
||||
$case SMALL:
|
||||
return new_temp_allocator(1024 * 16, allocator)!!;
|
||||
$case TINY:
|
||||
return new_temp_allocator(1024 * 2, allocator)!!;
|
||||
$case NONE:
|
||||
unreachable("Temp allocator must explicitly created when memory-env is set to 'none'.");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro Allocator heap() => thread_allocator;
|
||||
|
||||
macro TempAllocator* temp()
|
||||
{
|
||||
if (!thread_temp_allocator)
|
||||
{
|
||||
init_default_temp_allocators();
|
||||
}
|
||||
return thread_temp_allocator;
|
||||
}
|
||||
|
||||
fn void init_default_temp_allocators() @private
|
||||
{
|
||||
temp_allocator_pair[0] = create_default_sized_temp_allocator(temp_base_allocator);
|
||||
temp_allocator_pair[1] = create_default_sized_temp_allocator(temp_base_allocator);
|
||||
thread_temp_allocator = temp_allocator_pair[0];
|
||||
}
|
||||
|
||||
fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::LIBC)
|
||||
{
|
||||
destroy_temp_allocators();
|
||||
}
|
||||
|
||||
<*
|
||||
Call this to destroy any memory used by the temp allocators. This will invalidate all temp memory.
|
||||
*>
|
||||
fn void destroy_temp_allocators()
|
||||
{
|
||||
if (!thread_temp_allocator) return;
|
||||
temp_allocator_pair[0].destroy();
|
||||
temp_allocator_pair[1].destroy();
|
||||
temp_allocator_pair[..] = null;
|
||||
thread_temp_allocator = null;
|
||||
}
|
||||
|
||||
fn TempAllocator* temp_allocator_next() @private
|
||||
{
|
||||
if (!thread_temp_allocator)
|
||||
{
|
||||
init_default_temp_allocators();
|
||||
return thread_temp_allocator;
|
||||
}
|
||||
usz index = thread_temp_allocator == temp_allocator_pair[0] ? 1 : 0;
|
||||
return thread_temp_allocator = temp_allocator_pair[index];
|
||||
}
|
||||
|
||||
const NullAllocator NULL_ALLOCATOR = {};
|
||||
distinct NullAllocator (Allocator) = uptr;
|
||||
|
||||
fn void*! NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
return AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
fn void*! NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
return AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -11,22 +11,22 @@ struct WasmMemory
|
||||
uptr use;
|
||||
}
|
||||
|
||||
fn char[]! WasmMemory.allocate_block(WasmMemory* this, usz bytes)
|
||||
fn char[]! WasmMemory.allocate_block(&self, usz bytes)
|
||||
{
|
||||
if (!this.allocation)
|
||||
if (!self.allocation)
|
||||
{
|
||||
this.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
}
|
||||
isz bytes_required = bytes + this.use - this.allocation;
|
||||
if (bytes_required <= 0)
|
||||
isz bytes_required = bytes + self.use - self.allocation;
|
||||
if (bytes_required <= 0)
|
||||
{
|
||||
defer this.use += bytes;
|
||||
return ((char*)this.use)[:bytes];
|
||||
defer self.use += bytes;
|
||||
return ((char*)self.use)[:bytes];
|
||||
}
|
||||
|
||||
usz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE;
|
||||
if ($$wasm_memory_grow(0, blocks_required) == -1) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
this.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
defer this.use += bytes;
|
||||
return ((char*)this.use)[:bytes];
|
||||
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
defer self.use += bytes;
|
||||
return ((char*)self.use)[:bytes];
|
||||
}
|
||||
268
lib/std/core/private/cpu_detect.c3
Normal file
268
lib/std/core/private/cpu_detect.c3
Normal file
@@ -0,0 +1,268 @@
|
||||
module std::core::cpudetect @if(env::X86 || env::X86_64);
|
||||
|
||||
struct CpuId
|
||||
{
|
||||
uint eax, ebx, ecx, edx;
|
||||
}
|
||||
fn CpuId x86_cpuid(uint eax, uint ecx = 0)
|
||||
{
|
||||
int edx;
|
||||
int ebx;
|
||||
asm
|
||||
{
|
||||
movl $eax, eax;
|
||||
movl $ecx, ecx;
|
||||
cpuid;
|
||||
movl eax, $eax;
|
||||
movl ebx, $ebx;
|
||||
movl ecx, $ecx;
|
||||
movl edx, $edx;
|
||||
}
|
||||
return { eax, ebx, ecx, edx };
|
||||
}
|
||||
|
||||
enum X86Feature
|
||||
{
|
||||
ADX,
|
||||
AES,
|
||||
AMX_AVX512,
|
||||
AMX_FP8,
|
||||
AMX_MOVRS,
|
||||
AMX_TF32,
|
||||
AMX_TRANSPOSE,
|
||||
AMX_BF16,
|
||||
AMX_COMPLEX,
|
||||
AMX_FP16,
|
||||
AMX_INT8,
|
||||
AMX_TILE,
|
||||
APXF,
|
||||
AVX,
|
||||
AVX10_1_256,
|
||||
AVX10_1_512,
|
||||
AVX10_2_256,
|
||||
AVX10_2_512,
|
||||
AVX2,
|
||||
AVX5124FMAPS,
|
||||
AVX5124VNNIW,
|
||||
AVX512BF16,
|
||||
AVX512BITALG,
|
||||
AVX512BW,
|
||||
AVX512CD,
|
||||
AVX512DQ,
|
||||
AVX512ER,
|
||||
AVX512F,
|
||||
AVX512FP16,
|
||||
AVX512IFMA,
|
||||
AVX512PF,
|
||||
AVX512VBMI,
|
||||
AVX512VBMI2,
|
||||
AVX512VL,
|
||||
AVX512VNNI,
|
||||
AVX512VP2INTERSECT,
|
||||
AVX512VPOPCNTDQ,
|
||||
AVXIFMA,
|
||||
AVXNECONVERT,
|
||||
AVXVNNI,
|
||||
AVXVNNIINT16,
|
||||
AVXVNNIINT8,
|
||||
BMI,
|
||||
BMI2,
|
||||
CLDEMOTE,
|
||||
CLFLUSHOPT,
|
||||
CLWB,
|
||||
CLZERO,
|
||||
CMOV,
|
||||
CMPCCXADD,
|
||||
CMPXCHG16B,
|
||||
CX8,
|
||||
ENQCMD,
|
||||
F16C,
|
||||
FMA,
|
||||
FMA4,
|
||||
FSGSBASE,
|
||||
FXSR,
|
||||
GFNI,
|
||||
HRESET,
|
||||
INVPCID,
|
||||
KL,
|
||||
LWP,
|
||||
LZCNT,
|
||||
MMX,
|
||||
MOVBE,
|
||||
MOVDIR64B,
|
||||
MOVDIRI,
|
||||
MOVRS,
|
||||
MWAITX,
|
||||
PCLMUL,
|
||||
PCONFIG,
|
||||
PKU,
|
||||
POPCNT,
|
||||
PREFETCHI,
|
||||
PREFETCHWT1,
|
||||
PRFCHW,
|
||||
PTWRITE,
|
||||
RAOINT,
|
||||
RDPID,
|
||||
RDPRU,
|
||||
RDRND,
|
||||
RDSEED,
|
||||
RTM,
|
||||
SAHF,
|
||||
SERIALIZE,
|
||||
SGX,
|
||||
SHA,
|
||||
SHA512,
|
||||
SHSTK,
|
||||
SM3,
|
||||
SM4,
|
||||
SSE,
|
||||
SSE2,
|
||||
SSE3,
|
||||
SSE4_1,
|
||||
SSE4_2,
|
||||
SSE4_A,
|
||||
SSSE3,
|
||||
TBM,
|
||||
TSXLDTRK,
|
||||
UINTR,
|
||||
USERMSR,
|
||||
VAES,
|
||||
VPCLMULQDQ,
|
||||
WAITPKG,
|
||||
WBNOINVD,
|
||||
WIDEKL,
|
||||
X87,
|
||||
XOP,
|
||||
XSAVE,
|
||||
XSAVEC,
|
||||
XSAVEOPT,
|
||||
XSAVES,
|
||||
}
|
||||
|
||||
uint128 x86_features;
|
||||
|
||||
fn void add_feature_if_bit(X86Feature feature, uint register, int bit)
|
||||
{
|
||||
if (register & 1U << bit) x86_features |= 1u128 << feature.ordinal;
|
||||
}
|
||||
|
||||
fn void x86_initialize_cpu_features()
|
||||
{
|
||||
uint max_level = x86_cpuid(0).eax;
|
||||
CpuId feat = x86_cpuid(1);
|
||||
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : CpuId {};
|
||||
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : CpuId {};
|
||||
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : CpuId {};
|
||||
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : CpuId {};
|
||||
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : CpuId {};
|
||||
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : CpuId {};
|
||||
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : CpuId {};
|
||||
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : CpuId {};
|
||||
add_feature_if_bit(ADX, leaf7.ebx, 19);
|
||||
add_feature_if_bit(AES, feat.ecx, 25);
|
||||
add_feature_if_bit(AMX_BF16, leaf7.edx, 22);
|
||||
add_feature_if_bit(AMX_COMPLEX, leaf7s1.edx, 8);
|
||||
add_feature_if_bit(AMX_FP16, leaf7s1.eax, 21);
|
||||
add_feature_if_bit(AMX_INT8, leaf7.edx, 25);
|
||||
add_feature_if_bit(AMX_TILE, leaf7.edx, 24);
|
||||
add_feature_if_bit(APXF, leaf7s1.edx, 21);
|
||||
add_feature_if_bit(AVX, feat.ecx, 28);
|
||||
add_feature_if_bit(AVX10_1_256, leaf7s1.edx, 19);
|
||||
add_feature_if_bit(AVX10_1_512, leaf_24.ebx, 18);
|
||||
add_feature_if_bit(AVX2, leaf7.ebx, 5);
|
||||
add_feature_if_bit(AVX5124FMAPS, leaf7.edx, 3);
|
||||
add_feature_if_bit(AVX5124VNNIW, leaf7.edx, 2);
|
||||
add_feature_if_bit(AVX512BF16, leaf7s1.eax, 5);
|
||||
add_feature_if_bit(AVX512BITALG, leaf7.ecx, 12);
|
||||
add_feature_if_bit(AVX512BW, leaf7.ebx, 30);
|
||||
add_feature_if_bit(AVX512CD, leaf7.ebx, 28);
|
||||
add_feature_if_bit(AVX512DQ, leaf7.ebx, 17);
|
||||
add_feature_if_bit(AVX512ER, leaf7.ebx, 27);
|
||||
add_feature_if_bit(AVX512F, leaf7.ebx, 16);
|
||||
add_feature_if_bit(AVX512FP16, leaf7.edx, 23);
|
||||
add_feature_if_bit(AVX512IFMA, leaf7.ebx, 21);
|
||||
add_feature_if_bit(AVX512PF, leaf7.ebx, 26);
|
||||
add_feature_if_bit(AVX512VBMI, leaf7.ecx, 1);
|
||||
add_feature_if_bit(AVX512VBMI2, leaf7.ecx, 6);
|
||||
add_feature_if_bit(AVX512VL, leaf7.ebx, 31);
|
||||
add_feature_if_bit(AVX512VNNI, leaf7.ecx, 11);
|
||||
add_feature_if_bit(AVX512VP2INTERSECT, leaf7.edx, 8);
|
||||
add_feature_if_bit(AVX512VPOPCNTDQ, leaf7.ecx, 14);
|
||||
add_feature_if_bit(AVXIFMA, leaf7s1.eax, 23);
|
||||
add_feature_if_bit(AVXNECONVERT, leaf7s1.edx, 5);
|
||||
add_feature_if_bit(AVXVNNI, leaf7s1.eax, 4);
|
||||
add_feature_if_bit(AVXVNNIINT16, leaf7s1.edx, 10);
|
||||
add_feature_if_bit(AVXVNNIINT8, leaf7s1.edx, 4);
|
||||
add_feature_if_bit(BMI, leaf7.ebx, 3);
|
||||
add_feature_if_bit(BMI2, leaf7.ebx, 8);
|
||||
add_feature_if_bit(CLDEMOTE, leaf7.ecx, 25);
|
||||
add_feature_if_bit(CLFLUSHOPT, leaf7.ebx, 23);
|
||||
add_feature_if_bit(CLWB, leaf7.ebx, 24);
|
||||
add_feature_if_bit(CLZERO, ext8.ecx, 0);
|
||||
add_feature_if_bit(CMOV, feat.edx, 15);
|
||||
add_feature_if_bit(CMPCCXADD, leaf7s1.eax, 7);
|
||||
add_feature_if_bit(CMPXCHG16B, feat.ecx, 12);
|
||||
add_feature_if_bit(CX8, feat.edx, 8);
|
||||
add_feature_if_bit(ENQCMD, leaf7.ecx, 29);
|
||||
add_feature_if_bit(F16C, feat.ecx, 29);
|
||||
add_feature_if_bit(FMA, feat.ecx, 12);
|
||||
add_feature_if_bit(FMA4, ext1.ecx, 16);
|
||||
add_feature_if_bit(FSGSBASE, leaf7.ebx, 0);
|
||||
add_feature_if_bit(FXSR, feat.edx, 24);
|
||||
add_feature_if_bit(GFNI, leaf7.ecx, 8);
|
||||
add_feature_if_bit(HRESET, leaf7s1.eax, 22);
|
||||
add_feature_if_bit(INVPCID, leaf7.ebx, 10);
|
||||
add_feature_if_bit(KL, leaf7.ecx, 23);
|
||||
add_feature_if_bit(LWP, ext1.ecx, 15);
|
||||
add_feature_if_bit(LZCNT, ext1.ecx, 5);
|
||||
add_feature_if_bit(MMX, feat.edx, 23);
|
||||
add_feature_if_bit(MOVBE, feat.ecx, 22);
|
||||
add_feature_if_bit(MOVDIR64B, leaf7.ecx, 28);
|
||||
add_feature_if_bit(MOVDIRI, leaf7.ecx, 27);
|
||||
add_feature_if_bit(MWAITX, ext1.ecx, 29);
|
||||
add_feature_if_bit(PCLMUL, feat.ecx, 1);
|
||||
add_feature_if_bit(PCONFIG, leaf7.edx, 18);
|
||||
add_feature_if_bit(PKU, leaf7.ecx, 4);
|
||||
add_feature_if_bit(POPCNT, feat.ecx, 23);
|
||||
add_feature_if_bit(PREFETCHI, leaf7s1.edx, 14);
|
||||
add_feature_if_bit(PREFETCHWT1, leaf7.ecx, 0);
|
||||
add_feature_if_bit(PRFCHW, ext1.ecx, 8);
|
||||
add_feature_if_bit(PTWRITE, leaf_14.ebx, 4);
|
||||
add_feature_if_bit(RAOINT, leaf7s1.eax, 3);
|
||||
add_feature_if_bit(RDPID, leaf7.ecx, 22);
|
||||
add_feature_if_bit(RDPRU, ext8.ecx, 4);
|
||||
add_feature_if_bit(RDRND, feat.ecx, 30);
|
||||
add_feature_if_bit(RDSEED, leaf7.ebx, 18);
|
||||
add_feature_if_bit(RTM, leaf7.ebx, 11);
|
||||
add_feature_if_bit(SAHF, ext1.ecx, 0);
|
||||
add_feature_if_bit(SERIALIZE, leaf7.edx, 14);
|
||||
add_feature_if_bit(SGX, leaf7.ebx, 2);
|
||||
add_feature_if_bit(SHA, leaf7.ebx, 29);
|
||||
add_feature_if_bit(SHA512, leaf7s1.eax, 0);
|
||||
add_feature_if_bit(SHSTK, leaf7.ecx, 7);
|
||||
add_feature_if_bit(SM3, leaf7s1.eax, 1);
|
||||
add_feature_if_bit(SM4, leaf7s1.eax, 2);
|
||||
add_feature_if_bit(SSE, feat.edx, 25);
|
||||
add_feature_if_bit(SSE2, feat.edx, 26);
|
||||
add_feature_if_bit(SSE3, feat.ecx, 0);
|
||||
add_feature_if_bit(SSE4_1, feat.ecx, 19);
|
||||
add_feature_if_bit(SSE4_2, feat.ecx, 20);
|
||||
add_feature_if_bit(SSE4_A, ext1.ecx, 6);
|
||||
add_feature_if_bit(SSSE3, feat.ecx, 9);
|
||||
add_feature_if_bit(TBM, ext1.ecx, 21);
|
||||
add_feature_if_bit(TSXLDTRK, leaf7.edx, 16);
|
||||
add_feature_if_bit(UINTR, leaf7.edx, 5);
|
||||
add_feature_if_bit(USERMSR, leaf7s1.edx, 15);
|
||||
add_feature_if_bit(VAES, leaf7.ecx, 9);
|
||||
add_feature_if_bit(VPCLMULQDQ, leaf7.ecx, 10);
|
||||
add_feature_if_bit(WAITPKG, leaf7.ecx, 5);
|
||||
add_feature_if_bit(WBNOINVD, ext8.ecx, 9);
|
||||
add_feature_if_bit(WIDEKL, leaf_19.ebx, 2);
|
||||
add_feature_if_bit(X87, feat.edx, 0);
|
||||
add_feature_if_bit(XOP, ext1.ecx, 11);
|
||||
add_feature_if_bit(XSAVE, feat.ecx, 26);
|
||||
add_feature_if_bit(XSAVEC, leaf_d.eax, 1);
|
||||
add_feature_if_bit(XSAVEOPT, leaf_d.eax, 0);
|
||||
add_feature_if_bit(XSAVES, leaf_d.eax, 3);
|
||||
|
||||
}
|
||||
254
lib/std/core/private/macho_runtime.c3
Normal file
254
lib/std/core/private/macho_runtime.c3
Normal file
@@ -0,0 +1,254 @@
|
||||
module std::core::machoruntime @if(env::DARWIN) @private;
|
||||
|
||||
struct SegmentCommand64
|
||||
{
|
||||
uint cmd;
|
||||
uint cmdsize;
|
||||
char[16] segname;
|
||||
ulong vmaddr;
|
||||
ulong vmsize;
|
||||
ulong fileoff;
|
||||
ulong filesize;
|
||||
uint maxprot;
|
||||
uint initprot;
|
||||
uint nsects;
|
||||
uint flags;
|
||||
}
|
||||
|
||||
struct LoadCommand
|
||||
{
|
||||
uint cmd;
|
||||
uint cmdsize;
|
||||
}
|
||||
|
||||
struct Section64
|
||||
{
|
||||
char[16] sectname;
|
||||
char[16] segname;
|
||||
ulong addr;
|
||||
ulong size;
|
||||
uint offset;
|
||||
uint align;
|
||||
uint reloff;
|
||||
uint nreloc;
|
||||
uint flags;
|
||||
uint reserved1;
|
||||
uint reserved2;
|
||||
uint reserved3;
|
||||
}
|
||||
|
||||
struct MachHeader
|
||||
{
|
||||
uint magic;
|
||||
uint cputype;
|
||||
uint cpusubtype;
|
||||
uint filetype;
|
||||
uint ncmds;
|
||||
uint sizeofcmds;
|
||||
uint flags;
|
||||
}
|
||||
|
||||
struct MachHeader64
|
||||
{
|
||||
inline MachHeader header;
|
||||
uint reserved;
|
||||
}
|
||||
|
||||
const LC_SEGMENT_64 = 0x19;
|
||||
|
||||
fault MachoSearch
|
||||
{
|
||||
NOT_FOUND
|
||||
}
|
||||
fn bool name_cmp(char* a, char[16]* b)
|
||||
{
|
||||
for (usz i = 0; i < 16; i++)
|
||||
{
|
||||
if (a[i] != (*b)[i]) return false;
|
||||
if (a[i] == '\0') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn SegmentCommand64*! find_segment(MachHeader* header, char* segname)
|
||||
{
|
||||
LoadCommand* command = (void*)header + MachHeader64.sizeof;
|
||||
for (uint i = 0; i < header.ncmds; i++)
|
||||
{
|
||||
if (command.cmd == LC_SEGMENT_64)
|
||||
{
|
||||
SegmentCommand64* segment = (SegmentCommand64*)command;
|
||||
if (name_cmp(segname, &segment.segname)) return segment;
|
||||
}
|
||||
command = (void*)command + command.cmdsize;
|
||||
}
|
||||
return MachoSearch.NOT_FOUND?;
|
||||
}
|
||||
fn Section64*! find_section(SegmentCommand64* command, char* sectname)
|
||||
{
|
||||
Section64* section = (void*)command + SegmentCommand64.sizeof;
|
||||
for (uint i = 0; i < command.nsects; i++)
|
||||
{
|
||||
if (name_cmp(sectname, §ion.sectname)) return section;
|
||||
section++;
|
||||
}
|
||||
return MachoSearch.NOT_FOUND?;
|
||||
}
|
||||
|
||||
macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type)
|
||||
{
|
||||
|
||||
Section64*! section = find_section(find_segment(header, segname), sectname);
|
||||
if (catch section)
|
||||
{
|
||||
return $Type[] {};
|
||||
}
|
||||
$Type* ptr = (void*)header + section.offset;
|
||||
return ptr[:section.size / $Type.sizeof];
|
||||
}
|
||||
|
||||
def DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide);
|
||||
|
||||
extern fn void _dyld_register_func_for_add_image(DyldCallback);
|
||||
|
||||
|
||||
struct DlInfo
|
||||
{
|
||||
char* dli_fname;
|
||||
void* dli_fbase;
|
||||
char* dli_sname;
|
||||
void* dli_saddr;
|
||||
}
|
||||
|
||||
extern fn void printf(char*, ...);
|
||||
extern fn int dladdr(MachHeader* mh, DlInfo* dlinfo);
|
||||
extern fn void* realloc(void* ptr, usz size);
|
||||
extern fn void* malloc(usz size);
|
||||
extern fn void free(void* ptr);
|
||||
|
||||
def CallbackFn = fn void();
|
||||
struct Callback
|
||||
{
|
||||
uint priority;
|
||||
CallbackFn xtor;
|
||||
Callback* next;
|
||||
}
|
||||
struct DynamicMethod
|
||||
{
|
||||
void* fn_ptr;
|
||||
char* sel;
|
||||
union
|
||||
{
|
||||
DynamicMethod* next;
|
||||
TypeId* type;
|
||||
}
|
||||
}
|
||||
|
||||
enum StartupState
|
||||
{
|
||||
NOT_STARTED,
|
||||
INIT,
|
||||
RUN_CTORS,
|
||||
READ_DYLIB,
|
||||
RUN_DYLIB_CTORS,
|
||||
RUN_DTORS,
|
||||
SHUTDOWN
|
||||
}
|
||||
|
||||
StartupState runtime_state = NOT_STARTED;
|
||||
|
||||
Callback* ctor_first;
|
||||
Callback* dtor_first;
|
||||
|
||||
fn void runtime_startup() @public @export("__c3_runtime_startup")
|
||||
{
|
||||
if (runtime_state != NOT_STARTED) return;
|
||||
runtime_state = INIT;
|
||||
_dyld_register_func_for_add_image(&dl_reg_callback);
|
||||
assert(runtime_state == INIT);
|
||||
runtime_state = RUN_CTORS;
|
||||
Callback* ctor = ctor_first;
|
||||
while (ctor)
|
||||
{
|
||||
ctor.xtor();
|
||||
ctor = ctor.next;
|
||||
}
|
||||
assert(runtime_state == RUN_CTORS);
|
||||
runtime_state = READ_DYLIB;
|
||||
ctor_first = null;
|
||||
}
|
||||
|
||||
fn void runtime_finalize() @public @export("__c3_runtime_finalize")
|
||||
{
|
||||
if (runtime_state != READ_DYLIB) return;
|
||||
runtime_state = RUN_DTORS;
|
||||
Callback* dtor = dtor_first;
|
||||
while (dtor)
|
||||
{
|
||||
dtor.xtor();
|
||||
dtor = dtor.next;
|
||||
}
|
||||
assert(runtime_state == RUN_DTORS);
|
||||
runtime_state = SHUTDOWN;
|
||||
}
|
||||
|
||||
fn void append_xxlizer(Callback** ref, Callback* cb)
|
||||
{
|
||||
while (Callback* current = *ref, current)
|
||||
{
|
||||
if (current.priority > cb.priority)
|
||||
{
|
||||
cb.next = current;
|
||||
break;
|
||||
}
|
||||
ref = ¤t.next;
|
||||
}
|
||||
*ref = cb;
|
||||
}
|
||||
|
||||
struct TypeId
|
||||
{
|
||||
char type;
|
||||
TypeId* parentof;
|
||||
DynamicMethod* dtable;
|
||||
usz sizeof;
|
||||
TypeId* inner;
|
||||
usz len;
|
||||
typeid[*] additional;
|
||||
}
|
||||
|
||||
fn void dl_reg_callback(MachHeader* mh, isz vmaddr_slide)
|
||||
{
|
||||
usz size = 0;
|
||||
assert(runtime_state == INIT || runtime_state == READ_DYLIB, "State was %s", runtime_state);
|
||||
foreach (&dm : find_segment_section_body(mh, "__DATA", "__c3_dynamic", DynamicMethod))
|
||||
{
|
||||
TypeId* type = dm.type;
|
||||
dm.next = type.dtable;
|
||||
type.dtable = dm;
|
||||
DynamicMethod* m = dm;
|
||||
while (m)
|
||||
{
|
||||
m = m.next;
|
||||
}
|
||||
}
|
||||
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3dtor", Callback))
|
||||
{
|
||||
append_xxlizer(&dtor_first, cb);
|
||||
}
|
||||
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3ctor", Callback))
|
||||
{
|
||||
append_xxlizer(&ctor_first, cb);
|
||||
}
|
||||
if (runtime_state != READ_DYLIB) return;
|
||||
runtime_state = RUN_DYLIB_CTORS;
|
||||
Callback* ctor = ctor_first;
|
||||
ctor_first = null;
|
||||
while (ctor)
|
||||
{
|
||||
ctor.xtor();
|
||||
ctor = ctor.next;
|
||||
}
|
||||
assert(runtime_state == RUN_DYLIB_CTORS);
|
||||
runtime_state = READ_DYLIB;
|
||||
}
|
||||
@@ -21,7 +21,7 @@ macro int @main_to_void_main(#m, int, char**)
|
||||
|
||||
macro String[] args_to_strings(int argc, char** argv) @private
|
||||
{
|
||||
String[] list = malloc(String, argc);
|
||||
String[] list = mem::alloc_array(String, argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
char* arg = argv[i];
|
||||
@@ -55,7 +55,7 @@ macro int @main_to_void_main_args(#m, int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
$if env::os_is_win32():
|
||||
module std::core::main_stub @if(env::WIN32);
|
||||
|
||||
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @extern("CommandLineToArgvW");
|
||||
|
||||
@@ -68,12 +68,12 @@ macro String[] win_command_line_to_strings(ushort* cmd_line) @private
|
||||
|
||||
macro String[] wargs_strings(int argc, Char16** argv) @private
|
||||
{
|
||||
String[] list = malloc(String, argc);
|
||||
String[] list = mem::alloc_array(String, argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
Char16* arg = argv[i];
|
||||
Char16[] argstring = arg[:_strlen(arg)];
|
||||
list[i] = string::from_utf16(argstring) ?? "?".copy();
|
||||
list[i] = string::new_from_utf16(argstring) ?? "?".copy();
|
||||
}
|
||||
return list[:argc];
|
||||
}
|
||||
@@ -84,34 +84,34 @@ macro void release_wargs(String[] list) @private
|
||||
free(list.ptr);
|
||||
}
|
||||
|
||||
macro int @win_to_err_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_to_err_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
if (catch #m()) return 1;
|
||||
return 0;
|
||||
}
|
||||
macro int @win_to_int_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) => #m();
|
||||
macro int @win_to_void_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_to_int_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd) => #m();
|
||||
macro int @win_to_void_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
#m();
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @win_to_err_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(args)) return 1;
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @win_to_int_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_to_int_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
return #m(args);
|
||||
return #m(args);
|
||||
}
|
||||
|
||||
macro int @win_to_void_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_to_void_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
@@ -119,26 +119,26 @@ macro int @win_to_void_main_args(#m, void* handle, Char16* cmd_line, int show_cm
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @win_to_err_main(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_to_err_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(handle, args, show_cmd)) return 1;
|
||||
return 0;
|
||||
if (catch #m(handle, prev_handle, args, show_cmd)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @win_to_int_main(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_to_int_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
return #m(handle, args, show_cmd);
|
||||
return #m(handle, prev_handle, args, show_cmd);
|
||||
}
|
||||
|
||||
macro int @win_to_void_main(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_to_void_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
#m(handle, args, show_cmd);
|
||||
#m(handle, prev_handle, args, show_cmd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
return #m(args);
|
||||
return #m(args);
|
||||
}
|
||||
|
||||
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
|
||||
@@ -164,5 +164,3 @@ macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
|
||||
#m(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$endif
|
||||
@@ -2,39 +2,184 @@
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::runtime;
|
||||
import libc, std::time, std::io, std::sort;
|
||||
|
||||
struct VirtualAny
|
||||
struct ReflectedParam (Printable) @if(!$defined(ReflectedParam))
|
||||
{
|
||||
void* ptr;
|
||||
typeid type_id;
|
||||
String name;
|
||||
typeid type;
|
||||
}
|
||||
|
||||
struct SubArrayContainer
|
||||
struct AnyRaw
|
||||
{
|
||||
void* ptr;
|
||||
usz len;
|
||||
void* ptr;
|
||||
typeid type;
|
||||
}
|
||||
|
||||
struct SliceRaw
|
||||
{
|
||||
void* ptr;
|
||||
usz len;
|
||||
}
|
||||
|
||||
def BenchmarkFn = fn void!();
|
||||
|
||||
struct BenchmarkUnit
|
||||
{
|
||||
String name;
|
||||
BenchmarkFn func;
|
||||
}
|
||||
|
||||
fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator = allocator::heap())
|
||||
{
|
||||
BenchmarkFn[] fns = $$BENCHMARK_FNS;
|
||||
String[] names = $$BENCHMARK_NAMES;
|
||||
BenchmarkUnit[] benchmarks = allocator::alloc_array(allocator, BenchmarkUnit, names.len);
|
||||
foreach (i, benchmark : fns)
|
||||
{
|
||||
benchmarks[i] = { names[i], fns[i] };
|
||||
}
|
||||
return benchmarks;
|
||||
}
|
||||
|
||||
const DEFAULT_BENCHMARK_WARMUP_ITERATIONS = 3;
|
||||
const DEFAULT_BENCHMARK_MAX_ITERATIONS = 10000;
|
||||
|
||||
uint benchmark_warmup_iterations @private = DEFAULT_BENCHMARK_WARMUP_ITERATIONS;
|
||||
uint benchmark_max_iterations @private = DEFAULT_BENCHMARK_MAX_ITERATIONS;
|
||||
|
||||
fn void set_benchmark_warmup_iterations(uint value) @builtin
|
||||
{
|
||||
benchmark_warmup_iterations = value;
|
||||
}
|
||||
|
||||
fn void set_benchmark_max_iterations(uint value) @builtin
|
||||
{
|
||||
assert(value > 0);
|
||||
benchmark_max_iterations = value;
|
||||
}
|
||||
|
||||
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
|
||||
{
|
||||
int benchmarks_passed = 0;
|
||||
int benchmark_count = benchmarks.len;
|
||||
usz max_name;
|
||||
|
||||
foreach (&unit : benchmarks)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
}
|
||||
|
||||
usz len = max_name + 9;
|
||||
|
||||
DString name = dstring::temp_with_capacity(64);
|
||||
name.append_repeat('-', len / 2);
|
||||
name.append(" BENCHMARKS ");
|
||||
name.append_repeat('-', len - len / 2);
|
||||
|
||||
io::printn(name);
|
||||
|
||||
name.clear();
|
||||
|
||||
long sys_clock_started;
|
||||
long sys_clock_finished;
|
||||
long sys_clocks;
|
||||
Clock clock;
|
||||
anyfault err;
|
||||
|
||||
foreach(unit : benchmarks)
|
||||
{
|
||||
defer name.clear();
|
||||
name.appendf("Benchmarking %s ", unit.name);
|
||||
name.append_repeat('.', max_name - unit.name.len + 2);
|
||||
io::printf("%s ", name.str_view());
|
||||
|
||||
for (uint i = 0; i < benchmark_warmup_iterations; i++)
|
||||
{
|
||||
err = @catch(unit.func()) @inline;
|
||||
@volatile_load(err);
|
||||
}
|
||||
|
||||
clock = std::time::clock::now();
|
||||
sys_clock_started = $$sysclock();
|
||||
|
||||
for (uint i = 0; i < benchmark_max_iterations; i++)
|
||||
{
|
||||
err = @catch(unit.func()) @inline;
|
||||
@volatile_load(err);
|
||||
}
|
||||
|
||||
sys_clock_finished = $$sysclock();
|
||||
NanoDuration nano_seconds = clock.mark();
|
||||
sys_clocks = sys_clock_finished - sys_clock_started;
|
||||
|
||||
if (err)
|
||||
{
|
||||
io::printfn("[failed] Failed due to: %s", err);
|
||||
continue;
|
||||
}
|
||||
|
||||
io::printfn("[ok] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
|
||||
benchmarks_passed++;
|
||||
}
|
||||
|
||||
io::printfn("\n%d benchmark%s run.\n", benchmark_count, benchmark_count > 1 ? "s" : "");
|
||||
io::printfn("Benchmarks Result: %s. %d passed, %d failed.",
|
||||
benchmarks_passed < benchmark_count ? "FAILED" : "ok",
|
||||
benchmarks_passed,
|
||||
benchmark_count - benchmarks_passed);
|
||||
|
||||
return benchmark_count == benchmarks_passed;
|
||||
}
|
||||
|
||||
fn bool default_benchmark_runner()
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
return run_benchmarks(benchmark_collection_create(allocator::temp()));
|
||||
};
|
||||
}
|
||||
|
||||
def TestFn = fn void!();
|
||||
|
||||
struct TestRunner
|
||||
struct TestUnit
|
||||
{
|
||||
String name;
|
||||
TestFn func;
|
||||
}
|
||||
|
||||
fn TestUnit[] test_collection_create(Allocator allocator = allocator::heap())
|
||||
{
|
||||
TestFn[] fns = $$TEST_FNS;
|
||||
String[] names = $$TEST_NAMES;
|
||||
TestUnit[] tests = allocator::alloc_array(allocator, TestUnit, names.len);
|
||||
foreach (i, test : fns)
|
||||
{
|
||||
tests[i] = { names[i], fns[i] };
|
||||
}
|
||||
return tests;
|
||||
}
|
||||
|
||||
struct TestContext
|
||||
{
|
||||
String[] test_names;
|
||||
TestFn[] test_fns;
|
||||
JmpBuf buf;
|
||||
}
|
||||
|
||||
fn TestRunner test_runner_create()
|
||||
// Sort the tests by their name in ascending order.
|
||||
fn int cmp_test_unit(TestUnit a, TestUnit b)
|
||||
{
|
||||
return TestRunner {
|
||||
.test_fns = $$TEST_FNS,
|
||||
.test_names = $$TEST_NAMES,
|
||||
};
|
||||
usz an = a.name.len;
|
||||
usz bn = b.name.len;
|
||||
if (an > bn) @swap(a, b);
|
||||
foreach (i, ac : a.name)
|
||||
{
|
||||
char bc = b.name[i];
|
||||
if (ac != bc) return an > bn ? bc - ac : ac - bc;
|
||||
}
|
||||
return (int)(an - bn);
|
||||
}
|
||||
|
||||
import libc;
|
||||
|
||||
TestRunner* current_runner @private;
|
||||
TestContext* test_context @private;
|
||||
|
||||
fn void test_panic(String message, String file, String function, uint line)
|
||||
{
|
||||
@@ -43,57 +188,70 @@ fn void test_panic(String message, String file, String function, uint line)
|
||||
io::print(message);
|
||||
io::printn();
|
||||
io::printfn(" - in %s %s:%s.\n", function, file, line);
|
||||
libc::longjmp(¤t_runner.buf, 1);
|
||||
libc::longjmp(&test_context.buf, 1);
|
||||
}
|
||||
|
||||
fn bool TestRunner.run(TestRunner* runner)
|
||||
fn bool run_tests(TestUnit[] tests)
|
||||
{
|
||||
current_runner = runner;
|
||||
usz max_name;
|
||||
foreach (&unit : tests)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
}
|
||||
quicksort(tests, &cmp_test_unit);
|
||||
|
||||
TestContext context;
|
||||
test_context = &context;
|
||||
|
||||
PanicFn old_panic = builtin::panic;
|
||||
defer builtin::panic = old_panic;
|
||||
builtin::panic = &test_panic;
|
||||
int tests_passed = 0;
|
||||
int tests = runner.test_names.len;
|
||||
io::printn("----- TESTS -----");
|
||||
foreach(i, String name : runner.test_names)
|
||||
int test_count = tests.len;
|
||||
DString name = dstring::temp_with_capacity(64);
|
||||
usz len = max_name + 9;
|
||||
name.append_repeat('-', len / 2);
|
||||
name.append(" TESTS ");
|
||||
name.append_repeat('-', len - len / 2);
|
||||
io::printn(name);
|
||||
name.clear();
|
||||
foreach(unit : tests)
|
||||
{
|
||||
io::printf("Testing %s ... ", name);
|
||||
if (libc::setjmp(&runner.buf) == 0)
|
||||
defer name.clear();
|
||||
name.appendf("Testing %s ", unit.name);
|
||||
name.append_repeat('.', max_name - unit.name.len + 2);
|
||||
io::printf("%s ", name.str_view());
|
||||
(void)io::stdout().flush();
|
||||
if (libc::setjmp(&context.buf) == 0)
|
||||
{
|
||||
if (catch err = runner.test_fns[i]())
|
||||
if (catch err = unit.func())
|
||||
{
|
||||
io::printn("[failed]");
|
||||
io::printfn("[failed] Failed due to: %s", err);
|
||||
continue;
|
||||
}
|
||||
io::printn("[ok]");
|
||||
tests_passed++;
|
||||
}
|
||||
}
|
||||
io::printfn("\n%d test(s) run.\n", tests);
|
||||
io::print("Test Result: ");
|
||||
if (tests_passed < tests)
|
||||
{
|
||||
io::print("FAILED");
|
||||
}
|
||||
else
|
||||
{
|
||||
io::print("ok");
|
||||
}
|
||||
io::printfn(". %d passed, %d failed.", tests_passed, tests - tests_passed);
|
||||
return tests == tests_passed;
|
||||
io::printfn("\n%d test%s run.\n", test_count, test_count > 1 ? "s" : "");
|
||||
io::printfn("Test Result: %s. %d passed, %d failed.",
|
||||
tests_passed < test_count ? "FAILED" : "ok", tests_passed, test_count - tests_passed);
|
||||
return test_count == tests_passed;
|
||||
}
|
||||
|
||||
fn bool __run_default_test_runner()
|
||||
fn bool default_test_runner()
|
||||
{
|
||||
return test_runner_create().run();
|
||||
@pool()
|
||||
{
|
||||
return run_tests(test_collection_create(allocator::temp()));
|
||||
};
|
||||
}
|
||||
|
||||
$if !env::COMPILER_LIBC_AVAILABLE && env::ARCH_TYPE == ArchType.WASM32 || env::ARCH_TYPE == ArchType.WASM64:
|
||||
module std::core::runtime @if(WASM_NOLIBC);
|
||||
|
||||
extern fn void __wasm_call_ctors();
|
||||
fn void wasm_initialize() @extern("_initialize") @wasm
|
||||
{
|
||||
// The linker synthesizes this to call constructors.
|
||||
__wasm_call_ctors();
|
||||
}
|
||||
$endif
|
||||
}
|
||||
127
lib/std/core/sanitizer/asan.c3
Normal file
127
lib/std/core/sanitizer/asan.c3
Normal file
@@ -0,0 +1,127 @@
|
||||
// Add this to your code to suppress leak detection or set other default options
|
||||
// fn ZString __asan_default_options() @export("__asan_default_options") @if(env::ADDRESS_SANITIZER)
|
||||
// {
|
||||
// return "detect_leaks=0";
|
||||
// }
|
||||
|
||||
// Add this to break on error
|
||||
// asan::set_error_report_callback(fn (ZString err)
|
||||
// {
|
||||
// breakpoint();
|
||||
// });
|
||||
|
||||
module std::core::sanitizer::asan;
|
||||
|
||||
def ErrorCallback = fn void (ZString);
|
||||
|
||||
<*
|
||||
Marks a memory region ([addr, addr+size)) as unaddressable.
|
||||
|
||||
This memory must be previously allocated by your program. Instrumented
|
||||
code is forbidden from accessing addresses in this region until it is
|
||||
unpoisoned. This function is not guaranteed to poison the entire region -
|
||||
it could poison only a subregion of [addr, addr+size) due to ASan
|
||||
alignment restrictions.
|
||||
|
||||
NOTE This function is not thread-safe because no two threads can poison or
|
||||
unpoison memory in the same memory region simultaneously.
|
||||
|
||||
@param addr "Start of memory region."
|
||||
@param size "Size of memory region."
|
||||
*>
|
||||
macro poison_memory_region(void* addr, usz size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__asan_poison_memory_region(addr, size);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Marks a memory region ([addr, addr+size)) as addressable.
|
||||
|
||||
This memory must be previously allocated by your program. Accessing
|
||||
addresses in this region is allowed until this region is poisoned again.
|
||||
This function could unpoison a super-region of [addr, addr+size) due
|
||||
to ASan alignment restrictions.
|
||||
|
||||
NOTE This function is not thread-safe because no two threads can
|
||||
poison or unpoison memory in the same memory region simultaneously.
|
||||
|
||||
@param addr "Start of memory region."
|
||||
@param size "Size of memory region."
|
||||
*>
|
||||
macro unpoison_memory_region(void* addr, usz size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__asan_unpoison_memory_region(addr, size);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Checks if an address is poisoned.
|
||||
@return "True if 'addr' is poisoned (that is, 1-byte read/write access to this address would result in an error report from ASan). Otherwise returns false."
|
||||
@param addr "Address to check."
|
||||
*>
|
||||
macro bool address_is_poisoned(void* addr)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
return (bool)__asan_address_is_poisoned(addr);
|
||||
$else
|
||||
return false;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Checks if a region is poisoned.
|
||||
|
||||
If at least one byte in [beg, beg+size) is poisoned, returns the
|
||||
address of the first such byte. Otherwise returns 0.
|
||||
|
||||
@param beg "Start of memory region."
|
||||
@param size "Start of memory region."
|
||||
@return "Address of first poisoned byte."
|
||||
*>
|
||||
macro void* region_is_poisoned(void* beg, usz size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
return __asan_region_is_poisoned(beg, size);
|
||||
$else
|
||||
return null;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Sets the callback function to be called during ASan error reporting.
|
||||
*>
|
||||
fn void set_error_report_callback(ErrorCallback callback)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__asan_set_error_report_callback(callback);
|
||||
$endif
|
||||
}
|
||||
|
||||
module std::core::sanitizer::asan @if(env::ADDRESS_SANITIZER);
|
||||
|
||||
extern fn void __asan_poison_memory_region(void* addr, usz size);
|
||||
extern fn void __asan_unpoison_memory_region(void* addr, usz size);
|
||||
extern fn CInt __asan_address_is_poisoned(void* addr);
|
||||
extern fn void* __asan_region_is_poisoned(void* beg, usz size);
|
||||
extern fn void __asan_describe_address(void* addr);
|
||||
extern fn CInt __asan_report_present();
|
||||
extern fn void* __asan_get_report_pc();
|
||||
extern fn void* __asan_get_report_bp();
|
||||
extern fn void* __asan_get_report_sp();
|
||||
extern fn void* __asan_get_report_address();
|
||||
extern fn CInt __asan_get_report_access_type();
|
||||
extern fn usz __asan_get_report_access_size();
|
||||
extern fn ZString __asan_get_report_description();
|
||||
extern fn ZString __asan_locate_address(void* addr, char* name, usz name_size, void** region_address, usz* region_size);
|
||||
extern fn usz __asan_get_alloc_stack(void* addr, void** trace, usz size, CInt* thread_id);
|
||||
extern fn usz __asan_get_free_stack(void* addr, void** trace, usz size, CInt* thread_id);
|
||||
extern fn void __asan_get_shadow_mapping(usz* shadow_scale, usz* shadow_offset);
|
||||
extern fn void __asan_set_error_report_callback(ErrorCallback callback);
|
||||
extern fn void __asan_print_accumulated_stats();
|
||||
extern fn void* __asan_get_current_fake_stack();
|
||||
extern fn void* __asan_addr_is_in_fake_stack(void* fake_stack, void* addr, void** beg, void** end);
|
||||
extern fn void __asan_handle_no_return();
|
||||
extern fn CInt __asan_update_allocation_context(void* addr);
|
||||
80
lib/std/core/sanitizer/sanitizer.c3
Normal file
80
lib/std/core/sanitizer/sanitizer.c3
Normal file
@@ -0,0 +1,80 @@
|
||||
module std::core::sanitizer;
|
||||
|
||||
macro void annotate_contiguous_container(void* beg, void* end, void* old_mid, void* new_mid)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__sanitizer_annotate_contiguous_container(beg, end, old_mid, new_mid);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void annotate_double_ended_contiguous_container(void* storage_beg, void* storage_end, void* old_container_beg, void* old_container_end, void* new_container_beg, void* new_container_end)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__sanitizer_annotate_double_ended_contiguous_container(storage_beg, storage_end, old_container_beg, old_container_end, new_container_beg, new_container_end);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void print_stack_trace()
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__sanitizer_print_stack_trace();
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void set_death_callback(VoidFn callback)
|
||||
{
|
||||
$if env::ANY_SANITIZER:
|
||||
__sanitizer_set_death_callback(callback);
|
||||
$endif
|
||||
}
|
||||
|
||||
module std::core::sanitizer @if (env::ANY_SANITIZER);
|
||||
|
||||
struct __Sanitizer_sandbox_arguments
|
||||
{
|
||||
CInt coverage_sandboxed;
|
||||
iptr coverage_fd;
|
||||
CUInt coverage_max_block_size;
|
||||
}
|
||||
|
||||
extern fn void __sanitizer_set_report_path(ZString path);
|
||||
extern fn void __sanitizer_set_report_fd(void* fd);
|
||||
extern fn ZString __sanitizer_get_report_path();
|
||||
extern fn void __sanitizer_sandbox_on_notify(__Sanitizer_sandbox_arguments* args);
|
||||
extern fn void __sanitizer_report_error_summary(ZString error_summary);
|
||||
extern fn ushort __sanitizer_unaligned_load16(void* p);
|
||||
extern fn uint __sanitizer_unaligned_load32(void* p);
|
||||
extern fn ulong __sanitizer_unaligned_load64(void* p);
|
||||
extern fn void __sanitizer_unaligned_store16(void* p, ushort x);
|
||||
extern fn void __sanitizer_unaligned_store32(void* p, uint x);
|
||||
extern fn void __sanitizer_unaligned_store64(void* p, ulong x);
|
||||
extern fn CInt __sanitizer_acquire_crash_state();
|
||||
extern fn void __sanitizer_annotate_contiguous_container(void* beg, void* end, void* old_mid, void* new_mid);
|
||||
extern fn void __sanitizer_annotate_double_ended_contiguous_container(void* storage_beg, void* storage_end,
|
||||
void* old_container_beg, void* old_container_end,
|
||||
void* new_container_beg, void* new_container_end);
|
||||
extern fn CInt __sanitizer_verify_contiguous_container(void* beg, void* mid, void* end);
|
||||
extern fn CInt __sanitizer_verify_double_ended_contiguous_container(
|
||||
void* storage_beg, void* container_beg,
|
||||
void* container_end, void* storage_end);
|
||||
extern fn void* __sanitizer_contiguous_container_find_bad_address(void* beg, void* mid, void* end);
|
||||
extern fn void* __sanitizer_double_ended_contiguous_container_find_bad_address(
|
||||
void* storage_beg, void* container_beg,
|
||||
void* container_end, void* storage_end);
|
||||
|
||||
extern fn void __sanitizer_print_stack_trace();
|
||||
extern fn void __sanitizer_symbolize_pc(void* pc, ZString fmt, char* out_buf, usz out_buf_size);
|
||||
extern fn void __sanitizer_symbolize_global(void* data_ptr, ZString fmt, char* out_buf, usz out_buf_size);
|
||||
extern fn void __sanitizer_set_death_callback(VoidFn callback);
|
||||
extern fn void __sanitizer_weak_hook_memcmp(void* called_pc, void* s1, void* s2, usz n, CInt result);
|
||||
extern fn void __sanitizer_weak_hook_strncmp(void* called_pc, ZString s1, ZString s2, usz n, CInt result);
|
||||
extern fn void __sanitizer_weak_hook_strncasecmp(void* called_pc, ZString s1, ZString s2, usz n, CInt result);
|
||||
extern fn void __sanitizer_weak_hook_strcmp(void* called_pc, ZString s1, ZString s2, CInt result);
|
||||
extern fn void __sanitizer_weak_hook_strcasecmp(void* called_pc, ZString s1, ZString s2, CInt result);
|
||||
extern fn void __sanitizer_weak_hook_strstr(void* called_pc, ZString s1, ZString s2, char* result);
|
||||
extern fn void __sanitizer_weak_hook_strcasestr(void* called_pc, ZString s1, ZString s2, char* result);
|
||||
extern fn void __sanitizer_weak_hook_memmem(void* called_pc, void* s1, usz len1, void* s2, usz len2, void* result);
|
||||
extern fn void __sanitizer_print_memory_profile(usz top_percent, usz max_number_of_contexts);
|
||||
extern fn void __sanitizer_start_switch_fiber(void** fake_stack_save, void* bottom, usz size);
|
||||
extern fn void __sanitizer_finish_switch_fiber(void* fake_stack_save, void** bottom_old, usz* size_old);
|
||||
extern fn CInt __sanitizer_get_module_and_offset_for_pc(void* pc, char* module_path, usz module_path_len, void** pc_offset);
|
||||
39
lib/std/core/sanitizer/tsan.c3
Normal file
39
lib/std/core/sanitizer/tsan.c3
Normal file
@@ -0,0 +1,39 @@
|
||||
module std::core::sanitizer::tsan;
|
||||
|
||||
distinct MutexFlags = inline CUInt;
|
||||
|
||||
const MutexFlags MUTEX_LINKER_INIT = 1 << 0;
|
||||
const MutexFlags MUTEX_WRITE_REENTRANT = 1 << 1;
|
||||
const MutexFlags MUTEX_READ_REENTRANT = 1 << 2;
|
||||
const MutexFlags MUTEX_NOT_STATIC = 1 << 8;
|
||||
const MutexFlags MUTEX_READ_LOCK = 1 << 3;
|
||||
const MutexFlags MUTEX_TRY_LOCK = 1 << 4;
|
||||
const MutexFlags MUTEX_TRY_LOCK_FAILED = 1 << 5;
|
||||
const MutexFlags MUTEX_RECURSIVE_LOCK = 1 << 6;
|
||||
const MutexFlags MUTEX_RECURSIVE_UNLOCK = 1 << 7;
|
||||
const MutexFlags MUTEX_TRY_READ_LOCK = MUTEX_READ_LOCK | MUTEX_TRY_LOCK;
|
||||
const MutexFlags MUTEX_TRY_READ_LOCK_FAILED = MUTEX_TRY_READ_LOCK | MUTEX_TRY_LOCK_FAILED;
|
||||
|
||||
macro void mutex_create(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_create(addr, flags); $endif }
|
||||
macro void mutex_destroy(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_destroy(addr, flags); $endif }
|
||||
macro void mutex_pre_lock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_lock(addr, flags); $endif }
|
||||
macro void mutex_post_lock(void* addr, MutexFlags flags, CInt recursion) { $if env::THREAD_SANITIZER: __tsan_mutex_post_lock(addr, flags, recursion); $endif }
|
||||
macro CInt mutex_pre_unlock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: return __tsan_mutex_pre_unlock(addr, flags); $else return 0; $endif }
|
||||
macro void mutex_post_unlock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_unlock(addr, flags); $endif }
|
||||
macro void mutex_pre_signal(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_signal(addr, flags); $endif }
|
||||
macro void mutex_post_signal(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_signal(addr, flags); $endif }
|
||||
macro void mutex_pre_divert(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_divert(addr, flags); $endif }
|
||||
macro void mutex_post_divert(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_divert(addr, flags); $endif }
|
||||
|
||||
module std::core::sanitizer::tsan @if(env::THREAD_SANITIZER) @private;
|
||||
|
||||
extern fn void __tsan_mutex_create(void* addr, CUInt flags);
|
||||
extern fn void __tsan_mutex_destroy(void* addr, CUInt flags);
|
||||
extern fn void __tsan_mutex_pre_lock(void* addr, CUInt flags);
|
||||
extern fn void __tsan_mutex_post_lock(void* addr, CUInt flags, CInt recursion);
|
||||
extern fn CInt __tsan_mutex_pre_unlock(void* addr, CUInt flags);
|
||||
extern fn void __tsan_mutex_post_unlock(void* addr, CUInt flags);
|
||||
extern fn void __tsan_mutex_pre_signal(void* addr, CUInt flags);
|
||||
extern fn void __tsan_mutex_post_signal(void* addr, CUInt flags);
|
||||
extern fn void __tsan_mutex_pre_divert(void* addr, CUInt flags);
|
||||
extern fn void __tsan_mutex_post_divert(void* addr, CUInt flags);
|
||||
@@ -1,7 +1,10 @@
|
||||
module std::core::string;
|
||||
import std::ascii;
|
||||
import std::io;
|
||||
|
||||
def ZString = distinct inline char*;
|
||||
distinct String @if(!$defined(String)) = inline char[];
|
||||
distinct ZString = inline char*;
|
||||
distinct WString = inline Char16*;
|
||||
def Char32 = uint;
|
||||
def Char16 = ushort;
|
||||
|
||||
@@ -30,36 +33,89 @@ fault NumberConversion
|
||||
FLOAT_OUT_OF_RANGE,
|
||||
}
|
||||
|
||||
macro String printf(String fmt, ..., Allocator* using = mem::heap())
|
||||
|
||||
<*
|
||||
Return a temporary ZString created using the formatting function.
|
||||
|
||||
@param [in] fmt `The formatting string`
|
||||
*>
|
||||
fn ZString tformat_zstr(String fmt, args...)
|
||||
{
|
||||
@stack_mem(256; Allocator* mem)
|
||||
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
|
||||
str.appendf(fmt, ...args);
|
||||
return str.zstr_view();
|
||||
}
|
||||
|
||||
<*
|
||||
Return a new String created using the formatting function.
|
||||
|
||||
@param [inout] allocator `The allocator to use`
|
||||
@param [in] fmt `The formatting string`
|
||||
*>
|
||||
fn String format(String fmt, args..., Allocator allocator)
|
||||
{
|
||||
@pool(allocator)
|
||||
{
|
||||
DString str;
|
||||
str.init(.using = mem);
|
||||
str.printf(fmt, $vasplat());
|
||||
return str.copy_str(using);
|
||||
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
|
||||
str.appendf(fmt, ...args);
|
||||
return str.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
macro String tprintf(String fmt, ...)
|
||||
<*
|
||||
Return a heap allocated String created using the formatting function.
|
||||
|
||||
@param [in] fmt `The formatting string`
|
||||
*>
|
||||
fn String new_format(String fmt, args..., Allocator allocator = null) => format(fmt, ...args, allocator: allocator ?: allocator::heap());
|
||||
|
||||
<*
|
||||
Return a temporary String created using the formatting function.
|
||||
|
||||
@param [in] fmt `The formatting string`
|
||||
*>
|
||||
fn String tformat(String fmt, args...)
|
||||
{
|
||||
DString str;
|
||||
str.tinit();
|
||||
str.printf(fmt, $vasplat());
|
||||
return str.str();
|
||||
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
|
||||
str.appendf(fmt, ...args);
|
||||
return str.str_view();
|
||||
}
|
||||
|
||||
<*
|
||||
Return a new ZString created using the formatting function.
|
||||
|
||||
@param [in] fmt `The formatting string`
|
||||
@param [inout] allocator `The allocator to use`
|
||||
*>
|
||||
fn ZString new_format_zstr(String fmt, args..., Allocator allocator = allocator::heap())
|
||||
{
|
||||
@pool(allocator)
|
||||
{
|
||||
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
|
||||
str.appendf(fmt, ...args);
|
||||
return str.copy_zstr(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Check if a character is in a set.
|
||||
|
||||
@param c `the character to check`
|
||||
@param [in] set `The formatting string`
|
||||
@pure
|
||||
@return `True if a character is in the set`
|
||||
*>
|
||||
macro bool char_in_set(char c, String set)
|
||||
{
|
||||
foreach (ch : set) if (ch == c) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
fn String join(String[] s, String joiner, Allocator* using = mem::heap())
|
||||
fn String join_new(String[] s, String joiner, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!s)
|
||||
{
|
||||
return (String)(calloc(char, 2, .using = using)[:0]);
|
||||
return (String)allocator::new_array(allocator, char, 2)[:0];
|
||||
}
|
||||
|
||||
usz total_size = joiner.len * s.len;
|
||||
@@ -67,24 +123,28 @@ fn String join(String[] s, String joiner, Allocator* using = mem::heap())
|
||||
{
|
||||
total_size += str.len;
|
||||
}
|
||||
@stack_mem(256; Allocator* mem)
|
||||
@pool(allocator)
|
||||
{
|
||||
DString res = dstring::new_with_capacity(total_size, .using = mem);
|
||||
DString res = dstring::temp_with_capacity(total_size);
|
||||
res.append(s[0]);
|
||||
foreach (String* &str : s[1..])
|
||||
{
|
||||
res.append(joiner);
|
||||
res.append(*str);
|
||||
}
|
||||
return res.copy_str(using);
|
||||
return res.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [in] string
|
||||
* @param [in] to_trim
|
||||
**/
|
||||
fn String String.trim(String string, String to_trim = "\t\n\r ")
|
||||
<*
|
||||
Remove characters from the front and end of a string.
|
||||
|
||||
@param [in] string `The string to trim`
|
||||
@param [in] to_trim `The set of characters to trim, defaults to whitespace`
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim(string, String to_trim = "\t\n\r ")
|
||||
{
|
||||
usz start = 0;
|
||||
usz len = string.len;
|
||||
@@ -95,69 +155,82 @@ fn String String.trim(String string, String to_trim = "\t\n\r ")
|
||||
return string[start..end];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn bool String.starts_with(String string, String needle)
|
||||
<*
|
||||
Check if the String starts with the needle.
|
||||
|
||||
@param [in] string
|
||||
@param [in] needle
|
||||
@pure
|
||||
@return `'true' if the string starts with the needle`
|
||||
*>
|
||||
fn bool String.starts_with(string, String needle)
|
||||
{
|
||||
if (needle.len > string.len) return false;
|
||||
if (!needle.len) return true;
|
||||
return string[:needle.len] == needle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn bool String.ends_with(String string, String needle)
|
||||
<*
|
||||
Check if the String ends with the needle.
|
||||
|
||||
@param [in] string
|
||||
@param [in] needle
|
||||
@pure
|
||||
@return `'true' if the string ends with the needle`
|
||||
*>
|
||||
fn bool String.ends_with(string, String needle)
|
||||
{
|
||||
if (needle.len > string.len) return false;
|
||||
if (!needle.len) return true;
|
||||
return string[^needle.len..] == needle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the front of the string if the prefix exists.
|
||||
*
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn String String.strip(String string, String needle)
|
||||
<*
|
||||
Strip the front of the string if the prefix exists.
|
||||
|
||||
@param [in] string
|
||||
@param [in] needle
|
||||
@pure
|
||||
@return `the substring with the prefix removed`
|
||||
*>
|
||||
fn String String.strip(string, String needle)
|
||||
{
|
||||
if (!needle.len || !string.starts_with(needle)) return string;
|
||||
return string[needle.len..];
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the end of the string if the suffix exists.
|
||||
*
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn String String.strip_end(String string, String needle)
|
||||
<*
|
||||
Strip the end of the string if the suffix exists.
|
||||
|
||||
@param [in] string
|
||||
@param [in] needle
|
||||
@pure
|
||||
@return `the substring with the suffix removed`
|
||||
*>
|
||||
fn String String.strip_end(string, String needle)
|
||||
{
|
||||
if (!needle.len || !string.ends_with(needle)) return string;
|
||||
// Note that this is the safe way if we want to support zero length.
|
||||
return string[:(string.len - needle.len)];
|
||||
}
|
||||
|
||||
<*
|
||||
Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
|
||||
|
||||
/**
|
||||
* Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @param [&inout] using "The allocator, defaults to the heap allocator"
|
||||
* @param max "Max number of elements, 0 means no limit, defaults to 0"
|
||||
* @require needle.len > 0 "The needle must be at least 1 character long"
|
||||
* @ensure return.len > 0
|
||||
**/
|
||||
fn String[] String.split(String s, String needle, usz max = 0, Allocator* using = mem::heap())
|
||||
@param [in] s
|
||||
@param [in] needle
|
||||
@param max "Max number of elements, 0 means no limit, defaults to 0"
|
||||
@param skip_empty "True to skip empty elements"
|
||||
@param [&inout] allocator "The allocator to use for the String[]"
|
||||
|
||||
@require needle.len > 0 "The needle must be at least 1 character long"
|
||||
@ensure return.len > 0
|
||||
*>
|
||||
fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = allocator::heap(), bool skip_empty = false)
|
||||
{
|
||||
usz capacity = 16;
|
||||
usz i = 0;
|
||||
String* holder = malloc(String, capacity, .using = using);
|
||||
String* holder = allocator::alloc_array(allocator, String, capacity);
|
||||
bool no_more = false;
|
||||
while (!no_more)
|
||||
{
|
||||
@@ -173,112 +246,219 @@ fn String[] String.split(String s, String needle, usz max = 0, Allocator* using
|
||||
res = s;
|
||||
no_more = true;
|
||||
}
|
||||
if (!res.len && skip_empty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == capacity)
|
||||
{
|
||||
capacity *= 2;
|
||||
holder = realloc(holder, String.sizeof * capacity, .using = using);
|
||||
holder = allocator::realloc(allocator, holder, String.sizeof * capacity);
|
||||
}
|
||||
holder[i++] = res;
|
||||
}
|
||||
return holder[:i];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is identical to String.split, but implicitly uses the
|
||||
* temporary allocator.
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @param max "Max number of elements, 0 means no limit, defaults to 0"
|
||||
**/
|
||||
fn String[] String.tsplit(String s, String needle, usz max = 0)
|
||||
<*
|
||||
Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }, using the heap allocator
|
||||
to store the parts.
|
||||
|
||||
@param [in] s
|
||||
@param [in] needle
|
||||
@param max "Max number of elements, 0 means no limit, defaults to 0"
|
||||
@param skip_empty "True to skip empty elements"
|
||||
@require needle.len > 0 "The needle must be at least 1 character long"
|
||||
@ensure return.len > 0
|
||||
*>
|
||||
fn String[] String.new_split(s, String needle, usz max = 0, bool skip_empty) => s.split(needle, max, allocator::heap(), skip_empty) @inline;
|
||||
|
||||
<*
|
||||
This function is identical to String.split, but implicitly uses the
|
||||
temporary allocator.
|
||||
|
||||
@param [in] s
|
||||
@param [in] needle
|
||||
@param max "Max number of elements, 0 means no limit, defaults to 0"
|
||||
@param skip_empty "True to skip empty elements"
|
||||
*>
|
||||
fn String[] String.tsplit(s, String needle, usz max = 0, bool skip_empty = false) => s.split(needle, max, allocator::temp(), skip_empty) @inline;
|
||||
|
||||
fault SplitResult { BUFFER_EXCEEDED }
|
||||
|
||||
<*
|
||||
Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
|
||||
|
||||
@param [in] s
|
||||
@param [in] needle
|
||||
@param [inout] buffer
|
||||
@param max "Max number of elements, 0 means no limit, defaults to 0"
|
||||
@require needle.len > 0 "The needle must be at least 1 character long"
|
||||
@ensure return.len > 0
|
||||
@return! SplitResult.BUFFER_EXCEEDED `If there are more elements than would fit the buffer`
|
||||
*>
|
||||
fn String[]! String.split_to_buffer(s, String needle, String[] buffer, usz max = 0, bool skip_empty = false)
|
||||
{
|
||||
return s.split(needle, max, mem::temp()) @inline;
|
||||
usz max_capacity = buffer.len;
|
||||
usz i = 0;
|
||||
bool no_more = false;
|
||||
while (!no_more)
|
||||
{
|
||||
usz! index = i == max - 1 ? SearchResult.MISSING? : s.index_of(needle);
|
||||
String res @noinit;
|
||||
if (try index)
|
||||
{
|
||||
res = s[:index];
|
||||
s = s[index + needle.len..];
|
||||
}
|
||||
else
|
||||
{
|
||||
res = s;
|
||||
no_more = true;
|
||||
}
|
||||
if (!res.len && skip_empty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (i == max_capacity)
|
||||
{
|
||||
return SplitResult.BUFFER_EXCEEDED?;
|
||||
}
|
||||
buffer[i++] = res;
|
||||
}
|
||||
return buffer[:i];
|
||||
}
|
||||
|
||||
fn bool String.contains(String s, String needle)
|
||||
<*
|
||||
Check if a substring is found in the string.
|
||||
|
||||
@param [in] s
|
||||
@param [in] needle "The string to look for."
|
||||
@pure
|
||||
@return "true if the string contains the substring, false otherwise"
|
||||
*>
|
||||
fn bool String.contains(s, String needle)
|
||||
{
|
||||
return @ok(s.index_of(needle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the first incidence of a string.
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @pure
|
||||
* @ensure return < s.len
|
||||
* @require needle.len > 0 : "The needle must be len 1 or more"
|
||||
* @return "the index of the needle"
|
||||
* @return! SearchResult.MISSING "if the needle cannot be found"
|
||||
**/
|
||||
fn usz! String.index_of(String s, String needle)
|
||||
<*
|
||||
Find the index of the first incidence of a string.
|
||||
|
||||
@param [in] s
|
||||
@param needle "The character to look for"
|
||||
@pure
|
||||
@ensure return < s.len
|
||||
@return "the index of the needle"
|
||||
@return! SearchResult.MISSING "if the needle cannot be found"
|
||||
*>
|
||||
fn usz! String.index_of_char(s, char needle)
|
||||
{
|
||||
usz match = 0;
|
||||
usz needed = needle.len;
|
||||
usz index_start = 0;
|
||||
char search = needle[0];
|
||||
foreach (usz i, char c : s)
|
||||
foreach (i, c : s)
|
||||
{
|
||||
if (c == search)
|
||||
if (c == needle) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
Find the index of the first incidence of a character.
|
||||
|
||||
@param [in] s
|
||||
@param needle "The character to look for"
|
||||
@param start_index "The index to start with, may exceed max index."
|
||||
@pure
|
||||
@ensure return < s.len
|
||||
@return "the index of the needle"
|
||||
@return! SearchResult.MISSING "if the needle cannot be found starting from the start_index"
|
||||
*>
|
||||
fn usz! String.index_of_char_from(s, char needle, usz start_index)
|
||||
{
|
||||
usz len = s.len;
|
||||
if (len <= start_index) return SearchResult.MISSING?;
|
||||
for (usz i = start_index; i < len; i++)
|
||||
{
|
||||
if (s[i] == needle) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
Find the index of the first incidence of a character starting from the end.
|
||||
|
||||
@param [in] s
|
||||
@param needle "the character to find"
|
||||
@pure
|
||||
@ensure return < s.len
|
||||
@return "the index of the needle"
|
||||
@return! SearchResult.MISSING "if the needle cannot be found"
|
||||
*>
|
||||
fn usz! String.rindex_of_char(s, char needle)
|
||||
{
|
||||
foreach_r (i, c : s)
|
||||
{
|
||||
if (c == needle) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
Find the index of the first incidence of a string.
|
||||
|
||||
@param [in] s
|
||||
@param [in] needle
|
||||
@pure
|
||||
@ensure return < s.len
|
||||
@require needle.len > 0 : "The needle must be len 1 or more"
|
||||
@return "the index of the needle"
|
||||
@return! SearchResult.MISSING "if the needle cannot be found"
|
||||
*>
|
||||
fn usz! String.index_of(s, String needle)
|
||||
{
|
||||
usz needed = needle.len;
|
||||
if (needed > 0 && s.len >= needed)
|
||||
{
|
||||
char first = needle[0];
|
||||
foreach (i, c: s[..^needed])
|
||||
{
|
||||
if (!match) index_start = i;
|
||||
match++;
|
||||
if (match == needed) return index_start;
|
||||
search = needle[match];
|
||||
continue;
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
match = 0;
|
||||
search = needle[0];
|
||||
if (c == first && s[i:needed] == needle) return i;
|
||||
}
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the last incidence of a string.
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @pure
|
||||
* @ensure return < s.len
|
||||
* @require needle.len > 0 "The needle must be len 1 or more"
|
||||
* @return "the index of the needle"
|
||||
* @return! SearchResult.MISSING "if the needle cannot be found"
|
||||
**/
|
||||
fn usz! String.rindex_of(String s, String needle)
|
||||
<*
|
||||
Find the index of the last incidence of a string.
|
||||
|
||||
@param [in] s
|
||||
@param [in] needle
|
||||
@pure
|
||||
@ensure return < s.len
|
||||
@require needle.len > 0 "The needle must be len 1 or more"
|
||||
@return "the index of the needle"
|
||||
@return! SearchResult.MISSING "if the needle cannot be found"
|
||||
*>
|
||||
fn usz! String.rindex_of(s, String needle)
|
||||
{
|
||||
usz match = 0;
|
||||
usz needed = needle.len;
|
||||
usz index_start = 0;
|
||||
char search = needle[^1];
|
||||
foreach_r (usz i, char c : s)
|
||||
if (needed > 0 && s.len >= needed)
|
||||
{
|
||||
if (c == search)
|
||||
char first = needle[0];
|
||||
foreach_r (i, c: s[..^needed])
|
||||
{
|
||||
if (!match) index_start = i;
|
||||
match++;
|
||||
if (match == needed) return index_start - needle.len + 1;
|
||||
search = needle[^(match + 1)];
|
||||
continue;
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
match = 0;
|
||||
search = needle[^1];
|
||||
if (c == first && s[i:needed] == needle) return i;
|
||||
}
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn String ZString.as_str(ZString str)
|
||||
fn String ZString.str_view(str)
|
||||
{
|
||||
return (String)((char*)str)[:str.len()];
|
||||
return (String)(str[:str.len()]);
|
||||
}
|
||||
|
||||
fn usz ZString.char_len(ZString str)
|
||||
fn usz ZString.char_len(str)
|
||||
{
|
||||
usz len = 0;
|
||||
char* ptr = (char*)str;
|
||||
@@ -289,131 +469,209 @@ fn usz ZString.char_len(ZString str)
|
||||
return len;
|
||||
}
|
||||
|
||||
fn usz ZString.len(ZString str)
|
||||
fn usz ZString.len(str)
|
||||
{
|
||||
usz len = 0;
|
||||
char* ptr = (char*)str;
|
||||
while (char c = ptr++[0]) len++;
|
||||
return len;
|
||||
char* ptr = (char*)str;
|
||||
while (char c = ptr++[0]) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
fn ZString String.zstr_copy(String s, Allocator* using = mem::heap())
|
||||
fn ZString String.zstr_copy(s, Allocator allocator = allocator::heap())
|
||||
{
|
||||
usz len = s.len;
|
||||
char* str = malloc(len + 1, .using = using);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (ZString)str;
|
||||
usz len = s.len;
|
||||
char* str = allocator::malloc(allocator, len + 1);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (ZString)str;
|
||||
}
|
||||
|
||||
fn String String.concat(String s1, String s2, Allocator* using = mem::heap())
|
||||
fn String String.concat(s1, String s2, Allocator allocator = allocator::heap())
|
||||
{
|
||||
usz full_len = s1.len + s2.len;
|
||||
char* str = malloc(full_len + 1, .using = using);
|
||||
usz s1_len = s1.len;
|
||||
mem::copy(str, s1.ptr, s1_len);
|
||||
mem::copy(str + s1_len, s2.ptr, s2.len);
|
||||
str[full_len] = 0;
|
||||
return (String)str[:full_len];
|
||||
usz full_len = s1.len + s2.len;
|
||||
char* str = allocator::malloc(allocator, full_len + 1);
|
||||
usz s1_len = s1.len;
|
||||
mem::copy(str, s1.ptr, s1_len);
|
||||
mem::copy(str + s1_len, s2.ptr, s2.len);
|
||||
str[full_len] = 0;
|
||||
return (String)str[:full_len];
|
||||
}
|
||||
|
||||
fn String String.tconcat(String s1, String s2) => s1.concat(s2, mem::temp());
|
||||
fn String String.tconcat(s1, String s2) => s1.concat(s2, allocator::temp());
|
||||
|
||||
|
||||
fn ZString String.zstr_tcopy(String s) => s.zstr_copy(mem::temp()) @inline;
|
||||
fn ZString String.zstr_tcopy(s) => s.zstr_copy(allocator::temp()) @inline;
|
||||
|
||||
fn String String.copy(String s, Allocator* using = mem::heap())
|
||||
fn String String.copy(s, Allocator allocator = allocator::heap())
|
||||
{
|
||||
usz len = s.len;
|
||||
char* str = malloc(len + 1, .using = using);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (String)str[:len];
|
||||
usz len = s.len;
|
||||
char* str = allocator::malloc(allocator, len + 1);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (String)str[:len];
|
||||
}
|
||||
|
||||
fn String String.tcopy(String s) => s.copy(mem::temp()) @inline;
|
||||
fn void String.free(&s, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!s.len) return;
|
||||
allocator::free(allocator, s.ptr);
|
||||
*s = "";
|
||||
}
|
||||
|
||||
fn String ZString.copy(ZString z, Allocator* using = mem::heap()) => z.as_str().copy(using) @inline;
|
||||
fn String ZString.tcopy(ZString z) => z.as_str().copy(mem::temp()) @inline;
|
||||
fn String String.tcopy(s) => s.copy(allocator::temp()) @inline;
|
||||
|
||||
/**
|
||||
* Convert an UTF-8 string to UTF-16
|
||||
* @return "The UTF-16 string as a slice, allocated using the given allocator"
|
||||
* @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
|
||||
* @return! AllocationFailure "If allocation of the string fails"
|
||||
**/
|
||||
fn Char16[]! String.to_utf16(String s, Allocator* using = mem::heap())
|
||||
fn String ZString.copy(z, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return z.str_view().copy(allocator) @inline;
|
||||
}
|
||||
|
||||
fn String ZString.tcopy(z)
|
||||
{
|
||||
return z.str_view().copy(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Convert an UTF-8 string to UTF-16
|
||||
@return "The UTF-16 string as a slice, allocated using the given allocator"
|
||||
@return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
|
||||
@return! AllocationFailure "If allocation of the string fails"
|
||||
*>
|
||||
fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::heap())
|
||||
{
|
||||
usz len16 = conv::utf16len_for_utf8(s);
|
||||
Char16* data = malloc_checked(Char16, len16 + 1, .using = using)!;
|
||||
Char16* data = allocator::alloc_array_try(allocator, Char16, len16 + 1)!;
|
||||
conv::utf8to16_unsafe(s, data)!;
|
||||
data[len16] = 0;
|
||||
return data[:len16];
|
||||
}
|
||||
|
||||
fn Char32[]! String.to_utf32(String s, Allocator* using = mem::heap())
|
||||
<*
|
||||
Convert an UTF-8 string to UTF-16
|
||||
@return "The UTF-16 string as a slice, allocated using the given allocator"
|
||||
@return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
|
||||
@return! AllocationFailure "If allocation of the string fails"
|
||||
*>
|
||||
fn Char16[]! String.to_temp_utf16(s)
|
||||
{
|
||||
return s.to_new_utf16(allocator::temp());
|
||||
}
|
||||
|
||||
fn WString! String.to_wstring(s, Allocator allocator)
|
||||
{
|
||||
return (WString)s.to_new_utf16(allocator).ptr;
|
||||
}
|
||||
|
||||
fn WString! String.to_temp_wstring(s) => s.to_wstring(allocator::temp());
|
||||
fn WString! String.to_new_wstring(s) => s.to_wstring(allocator::heap());
|
||||
|
||||
fn Char32[]! String.to_utf32(s, Allocator allocator)
|
||||
{
|
||||
usz codepoints = conv::utf8_codepoints(s);
|
||||
Char32* data = malloc_checked(Char32, codepoints + 1, .using = using)!;
|
||||
Char32* data = allocator::alloc_array_try(allocator, Char32, codepoints + 1)!;
|
||||
conv::utf8to32_unsafe(s, data)!;
|
||||
data[codepoints] = 0;
|
||||
return data[:codepoints];
|
||||
}
|
||||
|
||||
fn void String.convert_ascii_to_lower(String s)
|
||||
fn Char32[]! String.to_new_utf32(s) => s.to_utf32(allocator::heap()) @inline;
|
||||
fn Char32[]! String.to_temp_utf32(s) => s.to_utf32(allocator::temp()) @inline;
|
||||
|
||||
<*
|
||||
Convert a string to ASCII lower case.
|
||||
|
||||
@param [inout] s
|
||||
@pure
|
||||
*>
|
||||
fn void String.convert_ascii_to_lower(s)
|
||||
{
|
||||
foreach (&c : s) if (*c >= 'A' && *c <= 'Z') *c += 'a' - 'A';
|
||||
foreach (&c : s) if (c.is_upper() @pure) *c += 'a' - 'A';
|
||||
}
|
||||
|
||||
fn String String.ascii_to_lower(String s, Allocator* using = mem::heap())
|
||||
fn String String.new_ascii_to_lower(s, Allocator allocator = allocator::heap())
|
||||
{
|
||||
String copy = s.copy(using);
|
||||
String copy = s.copy(allocator);
|
||||
copy.convert_ascii_to_lower();
|
||||
return copy;
|
||||
}
|
||||
|
||||
fn void String.convert_ascii_to_upper(String s)
|
||||
fn String String.temp_ascii_to_lower(s)
|
||||
{
|
||||
foreach (&c : s) if (*c >= 'a' && *c <= 'z') *c -= 'a' - 'A';
|
||||
return s.new_ascii_to_lower(allocator::temp());
|
||||
}
|
||||
|
||||
fn String String.ascii_to_upper(String s, Allocator* using = mem::heap())
|
||||
<*
|
||||
Convert a string to ASCII upper case.
|
||||
|
||||
@param [inout] s
|
||||
@pure
|
||||
*>
|
||||
fn void String.convert_ascii_to_upper(s)
|
||||
{
|
||||
String copy = s.copy(using);
|
||||
foreach (&c : s) if (c.is_lower() @pure) *c -= 'a' - 'A';
|
||||
}
|
||||
|
||||
<*
|
||||
Returns a string converted to ASCII upper case.
|
||||
|
||||
@param [in] s
|
||||
@param [inout] allocator
|
||||
|
||||
@return `a new String converted to ASCII upper case.`
|
||||
*>
|
||||
fn String String.new_ascii_to_upper(s, Allocator allocator = allocator::heap())
|
||||
{
|
||||
String copy = s.copy(allocator);
|
||||
copy.convert_ascii_to_upper();
|
||||
return copy;
|
||||
}
|
||||
|
||||
fn String! from_utf32(Char32[] utf32, Allocator* using = mem::heap())
|
||||
fn StringIterator String.iterator(s)
|
||||
{
|
||||
return { s, 0 };
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] s
|
||||
@return `a temporary String converted to ASCII upper case.`
|
||||
*>
|
||||
fn String String.temp_ascii_to_upper(s)
|
||||
{
|
||||
return s.new_ascii_to_upper(allocator::temp());
|
||||
}
|
||||
|
||||
fn String! new_from_utf32(Char32[] utf32, Allocator allocator = allocator::heap())
|
||||
{
|
||||
usz len = conv::utf8len_for_utf32(utf32);
|
||||
char* data = malloc_checked(len + 1, .using = using)!;
|
||||
defer catch free(data, .using = using);
|
||||
char* data = allocator::malloc_try(allocator, len + 1)!;
|
||||
defer catch allocator::free(allocator, data);
|
||||
conv::utf32to8_unsafe(utf32, data);
|
||||
data[len] = 0;
|
||||
return (String)data[:len];
|
||||
}
|
||||
|
||||
fn String! from_utf16(Char16[] utf16, Allocator* using = mem::heap())
|
||||
fn String! new_from_utf16(Char16[] utf16, Allocator allocator = allocator::heap())
|
||||
{
|
||||
usz len = conv::utf8len_for_utf16(utf16);
|
||||
char* data = malloc_checked(len + 1, .using = using)!;
|
||||
defer catch free(data, .using = using);
|
||||
char* data = allocator::malloc_try(allocator, len + 1)!;
|
||||
defer catch allocator::free(allocator, data);
|
||||
conv::utf16to8_unsafe(utf16, data)!;
|
||||
data[len] = 0;
|
||||
return (String)data[:len];
|
||||
}
|
||||
|
||||
fn String! from_zutf16(Char16* utf16_pointer, Allocator* using = mem::heap())
|
||||
fn String! new_from_wstring(WString wstring, Allocator allocator = allocator::heap())
|
||||
{
|
||||
usz utf16_len;
|
||||
while (utf16_pointer[utf16_len] != 0) utf16_len++;
|
||||
Char16[] utf16 = utf16_pointer[:utf16_len];
|
||||
return from_utf16(utf16, using);
|
||||
while (wstring[utf16_len] != 0) utf16_len++;
|
||||
Char16[] utf16 = wstring[:utf16_len];
|
||||
return new_from_utf16(utf16, allocator);
|
||||
}
|
||||
|
||||
fn usz String.utf8_codepoints(String s)
|
||||
fn String! temp_from_wstring(WString wstring) => new_from_wstring(wstring, allocator::temp()) @inline;
|
||||
fn String! temp_from_utf16(Char16[] utf16) => new_from_utf16(utf16, allocator::temp()) @inline;
|
||||
|
||||
fn usz String.utf8_codepoints(s)
|
||||
{
|
||||
usz len = 0;
|
||||
foreach (char c : s)
|
||||
@@ -424,7 +682,10 @@ fn usz String.utf8_codepoints(String s)
|
||||
}
|
||||
|
||||
|
||||
macro String.to_integer(String string, $Type)
|
||||
<*
|
||||
@require (base <= 10 && base > 1) || base == 16 : "Unsupported base"
|
||||
*>
|
||||
macro String.to_integer(string, $Type, int base = 10)
|
||||
{
|
||||
usz len = string.len;
|
||||
usz index = 0;
|
||||
@@ -444,8 +705,8 @@ macro String.to_integer(String string, $Type)
|
||||
break;
|
||||
}
|
||||
if (len == index) return NumberConversion.MALFORMED_INTEGER?;
|
||||
$Type base = 10;
|
||||
if (string[index] == '0')
|
||||
$Type base_used = ($Type)base;
|
||||
if (string[index] == '0' && base == 10)
|
||||
{
|
||||
index++;
|
||||
if (index == len) return ($Type)0;
|
||||
@@ -453,15 +714,15 @@ macro String.to_integer(String string, $Type)
|
||||
{
|
||||
case 'x':
|
||||
case 'X':
|
||||
base = 16;
|
||||
base_used = 16;
|
||||
index++;
|
||||
case 'b':
|
||||
case 'B':
|
||||
base = 2;
|
||||
base_used = 2;
|
||||
index++;
|
||||
case 'o':
|
||||
case 'O':
|
||||
base = 8;
|
||||
base_used = 8;
|
||||
index++;
|
||||
default:
|
||||
break;
|
||||
@@ -473,21 +734,21 @@ macro String.to_integer(String string, $Type)
|
||||
{
|
||||
char c = {|
|
||||
char ch = string[index++];
|
||||
if (base != 16 || ch < 'A') return (char)(ch - '0');
|
||||
if (ch <= 'F') return (char)(ch - 'A');
|
||||
if (base_used != 16 || ch < 'A') return (char)(ch - '0');
|
||||
if (ch <= 'F') return (char)(ch - 'A' + 10);
|
||||
if (ch < 'a') return NumberConversion.MALFORMED_INTEGER?;
|
||||
if (ch > 'f') return NumberConversion.MALFORMED_INTEGER?;
|
||||
return (char)(ch - 'a');
|
||||
return (char)(ch - 'a' + 10);
|
||||
|}!;
|
||||
if (c >= base) return NumberConversion.MALFORMED_INTEGER?;
|
||||
if (c >= base_used) return NumberConversion.MALFORMED_INTEGER?;
|
||||
value = {|
|
||||
if (is_negative)
|
||||
{
|
||||
$Type new_value = value * base - c;
|
||||
$Type new_value = value * base_used - c;
|
||||
if (new_value > value) return NumberConversion.INTEGER_OVERFLOW?;
|
||||
return new_value;
|
||||
}
|
||||
$Type new_value = value * base + c;
|
||||
$Type new_value = value * base_used + c;
|
||||
if (new_value < value) return NumberConversion.INTEGER_OVERFLOW?;
|
||||
return new_value;
|
||||
|}!;
|
||||
@@ -495,20 +756,74 @@ macro String.to_integer(String string, $Type)
|
||||
return value;
|
||||
}
|
||||
|
||||
fn int128! String.to_int128(s, int base = 10) => s.to_integer(int128, base);
|
||||
fn long! String.to_long(s, int base = 10) => s.to_integer(long, base);
|
||||
fn int! String.to_int(s, int base = 10) => s.to_integer(int, base);
|
||||
fn short! String.to_short(s, int base = 10) => s.to_integer(short, base);
|
||||
fn ichar! String.to_ichar(s, int base = 10) => s.to_integer(ichar, base);
|
||||
|
||||
fn Char16[]! String.to_temp_utf16(String s) => s.to_utf16(mem::temp());
|
||||
fn uint128! String.to_uint128(s, int base = 10) => s.to_integer(uint128, base);
|
||||
fn ulong! String.to_ulong(s, int base = 10) => s.to_integer(ulong, base);
|
||||
fn uint! String.to_uint(s, int base = 10) => s.to_integer(uint, base);
|
||||
fn ushort! String.to_ushort(s, int base = 10) => s.to_integer(ushort, base);
|
||||
fn char! String.to_uchar(s, int base = 10) => s.to_integer(char, base);
|
||||
|
||||
fn int128! String.to_int128(String s) => s.to_integer(int128);
|
||||
fn long! String.to_long(String s) => s.to_integer(long);
|
||||
fn int! String.to_int(String s) => s.to_integer(int);
|
||||
fn short! String.to_short(String s) => s.to_integer(short);
|
||||
fn ichar! String.to_ichar(String s) => s.to_integer(ichar);
|
||||
fn double! String.to_double(s) => s.to_real(double);
|
||||
fn float! String.to_float(s) => s.to_real(float);
|
||||
|
||||
fn uint128! String.to_uint128(String s) => s.to_integer(uint128);
|
||||
fn ulong! String.to_ulong(String s) => s.to_integer(ulong);
|
||||
fn uint! String.to_uint(String s) => s.to_integer(uint);
|
||||
fn ushort! String.to_ushort(String s) => s.to_integer(ushort);
|
||||
fn char! String.to_uchar(String s) => s.to_integer(char);
|
||||
fn Splitter String.splitter(self, String split)
|
||||
{
|
||||
return { .string = self, .split = split };
|
||||
}
|
||||
|
||||
fn double! String.to_double(String s) => s.to_real(double);
|
||||
fn float! String.to_float(String s) => s.to_real(float);
|
||||
fn Splitter String.tokenize(self, String split)
|
||||
{
|
||||
return { .string = self, .split = split, .tokenize = true };
|
||||
}
|
||||
|
||||
struct Splitter
|
||||
{
|
||||
String string;
|
||||
String split;
|
||||
usz current;
|
||||
bool tokenize;
|
||||
int last_index;
|
||||
}
|
||||
|
||||
fn void Splitter.reset(&self)
|
||||
{
|
||||
self.current = 0;
|
||||
}
|
||||
|
||||
fn String! Splitter.next(&self)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
usz len = self.string.len;
|
||||
usz current = self.current;
|
||||
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
String remaining = self.string[current..];
|
||||
usz! next = remaining.index_of(self.split);
|
||||
if (try next)
|
||||
{
|
||||
self.current = current + next + self.split.len;
|
||||
if (!next && self.tokenize) continue;
|
||||
return remaining[:next];
|
||||
}
|
||||
self.current = len;
|
||||
return remaining;
|
||||
}
|
||||
}
|
||||
|
||||
macro String new_struct_to_str(x, Allocator allocator = allocator::heap())
|
||||
{
|
||||
DString s;
|
||||
@stack_mem(512; Allocator mem)
|
||||
{
|
||||
s.new_init(allocator: mem);
|
||||
io::fprint(&s, x)!!;
|
||||
return s.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
macro String temp_struct_to_str(x) => new_struct_to_str(x, allocator::temp());
|
||||
|
||||
@@ -6,18 +6,44 @@ struct StringIterator
|
||||
usz current;
|
||||
}
|
||||
|
||||
fn void StringIterator.reset(StringIterator* this)
|
||||
fn void StringIterator.reset(&self)
|
||||
{
|
||||
this.current = 0;
|
||||
self.current = 0;
|
||||
}
|
||||
|
||||
fn Char32! StringIterator.next(StringIterator* this)
|
||||
fn Char32! StringIterator.next(&self)
|
||||
{
|
||||
usz len = this.utf8.len;
|
||||
usz current = this.current;
|
||||
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
usz read = (len - current < 4 ? len - current : 4);
|
||||
Char32 res = conv::utf8_to_char32(&this.utf8[current], &read)!;
|
||||
this.current += read;
|
||||
return res;
|
||||
}
|
||||
usz len = self.utf8.len;
|
||||
usz current = self.current;
|
||||
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
usz read = (len - current < 4 ? len - current : 4);
|
||||
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
|
||||
self.current += read;
|
||||
return res;
|
||||
}
|
||||
|
||||
fn Char32! StringIterator.peek(&self)
|
||||
{
|
||||
usz len = self.utf8.len;
|
||||
usz current = self.current;
|
||||
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
usz read = (len - current < 4 ? len - current : 4);
|
||||
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
|
||||
return res;
|
||||
}
|
||||
|
||||
fn bool StringIterator.has_next(&self)
|
||||
{
|
||||
return self.current < self.utf8.len;
|
||||
}
|
||||
|
||||
fn Char32! StringIterator.get(&self)
|
||||
{
|
||||
usz len = self.utf8.len;
|
||||
usz current = self.current;
|
||||
usz read = (len - current < 4 ? len - current : 4);
|
||||
usz index = current > read ? current - read : 0;
|
||||
if (index >= len) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
Char32 res = conv::utf8_to_char32(&self.utf8[index], &read)!;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ const MASK = KMAX - 1;
|
||||
const B1B_DIG = 2;
|
||||
const uint[2] B1B_MAX = { 9007199, 254740991 };
|
||||
|
||||
/**
|
||||
* @require chars.len > 0
|
||||
**/
|
||||
<*
|
||||
@require chars.len > 0
|
||||
*>
|
||||
macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
{
|
||||
uint[KMAX] x;
|
||||
@@ -105,7 +105,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
got_digit = true;
|
||||
default:
|
||||
dc++;
|
||||
if (c != '0') x[KMAX - 4] |= 1;
|
||||
if (c != '0') x[KMAX - 4] |= 1;
|
||||
|
||||
}
|
||||
if (index == last_char) break;
|
||||
@@ -235,7 +235,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
carry = (1000000000 >> sh) * tmp;
|
||||
if (k == a && !x[k])
|
||||
{
|
||||
a = (a + 1) & MASK;
|
||||
a = (a + 1) & MASK;
|
||||
i--;
|
||||
rp -= 9;
|
||||
}
|
||||
@@ -266,7 +266,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
y *= sign;
|
||||
|
||||
bool denormal;
|
||||
/* Limit precision for denormal results */
|
||||
// Limit precision for denormal results
|
||||
uint bits = $bits;
|
||||
if (bits > math::DOUBLE_MANT_DIG + e2 - $emin)
|
||||
{
|
||||
@@ -404,7 +404,7 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
if ((c | 32) == 'p')
|
||||
{
|
||||
long e2val = String.to_long((String)chars[index + 1..]) ?? (NumberConversion.MALFORMED_FLOAT?)!;
|
||||
e2 = e2val;
|
||||
e2 = e2val;
|
||||
}
|
||||
e2 += 4 * rp - 32;
|
||||
if (!x) return sign * 0.0;
|
||||
@@ -449,7 +449,7 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
return math::scalbn(y, (int)e2);
|
||||
}
|
||||
|
||||
macro String.to_real(String chars, $Type) @private
|
||||
macro String.to_real(chars, $Type) @private
|
||||
{
|
||||
int sign = 1;
|
||||
$switch ($Type)
|
||||
@@ -465,18 +465,18 @@ macro String.to_real(String chars, $Type) @private
|
||||
$error "Unexpected type";
|
||||
$endswitch
|
||||
|
||||
while (chars.len && chars[0] == ' ') chars = chars[1..];
|
||||
if (!chars.len) return NumberConversion.MALFORMED_FLOAT?;
|
||||
switch (chars[0])
|
||||
{
|
||||
case '-':
|
||||
sign = -1;
|
||||
nextcase;
|
||||
case '+':
|
||||
chars = chars[1..];
|
||||
}
|
||||
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
|
||||
if (chars == "NAN" || chars == "nan") return $Type.nan;
|
||||
while (chars.len && chars[0] == ' ') chars = chars[1..];
|
||||
if (!chars.len) return NumberConversion.MALFORMED_FLOAT?;
|
||||
switch (chars[0])
|
||||
{
|
||||
case '-':
|
||||
sign = -1;
|
||||
nextcase;
|
||||
case '+':
|
||||
chars = chars[1..];
|
||||
}
|
||||
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
|
||||
if (chars == "NAN" || chars == "nan") return $Type.nan;
|
||||
|
||||
if (chars.len > 2 && chars[0] == '0' && (chars[1] | 32) == 'x')
|
||||
{
|
||||
|
||||
@@ -8,9 +8,9 @@ fault ConversionResult
|
||||
VALUE_OUT_OF_RANGE,
|
||||
VALUE_OUT_OF_UNSIGNED_RANGE,
|
||||
}
|
||||
/**
|
||||
* @require $Type.kindof.is_int() || $Type.kindof == TypeKind.ENUM "Argument was not an integer"
|
||||
**/
|
||||
<*
|
||||
@require $Type.kindof.is_int() || $Type.kindof == TypeKind.ENUM "Argument was not an integer"
|
||||
*>
|
||||
macro any_to_int(any v, $Type)
|
||||
{
|
||||
typeid any_type = v.type;
|
||||
@@ -74,48 +74,44 @@ macro any_to_int(any v, $Type)
|
||||
}
|
||||
}
|
||||
|
||||
fn bool typeid.is_subtype_of(self, typeid other)
|
||||
{
|
||||
while (self != void.typeid)
|
||||
{
|
||||
if (self == other) return true;
|
||||
self = self.parentof;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
macro bool is_subtype_of($Type, $OtherType)
|
||||
{
|
||||
var $typeid = $Type.typeid;
|
||||
$switch ($Type)
|
||||
$case $OtherType: return true;
|
||||
$default: return false;
|
||||
$endswitch
|
||||
}
|
||||
macro bool is_numerical($Type)
|
||||
{
|
||||
var $kind = $Type.kindof;
|
||||
$if $kind == TypeKind.DISTINCT:
|
||||
return is_numerical($Type.inner);
|
||||
return is_numerical($typefrom($Type.inner));
|
||||
$else
|
||||
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|
||||
|| $kind == TypeKind.VECTOR;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn bool TypeKind.is_int(TypeKind kind) @inline
|
||||
fn bool TypeKind.is_int(kind) @inline
|
||||
{
|
||||
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
|
||||
}
|
||||
|
||||
macro bool is_indexable($Type)
|
||||
{
|
||||
return $checks($Type t, int i, t[i]);
|
||||
}
|
||||
|
||||
macro bool is_comparable($Type)
|
||||
{
|
||||
var $kind = $Type.kindof;
|
||||
$if $kind == TypeKind.DISTINCT:
|
||||
return is_comparable($Type.inner);
|
||||
$else
|
||||
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|
||||
|| $kind == TypeKind.VECTOR || $kind == TypeKind.BOOL || $kind == TypeKind.POINTER
|
||||
|| $kind == TypeKind.ENUM;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro bool is_equatable($Type)
|
||||
{
|
||||
return $checks($Type a, a == a);
|
||||
}
|
||||
|
||||
macro bool is_subarray_convertable($Type)
|
||||
macro bool is_slice_convertable($Type)
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$case SUBARRAY:
|
||||
$case SLICE:
|
||||
return true;
|
||||
$case POINTER:
|
||||
return $Type.inner.kindof == TypeKind.ARRAY;
|
||||
@@ -124,10 +120,51 @@ macro bool is_subarray_convertable($Type)
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_bool($Type) => $Type.kindof == TypeKind.BOOL;
|
||||
macro bool is_int($Type) => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT;
|
||||
macro bool is_bool($Type) @const => $Type.kindof == TypeKind.BOOL;
|
||||
macro bool is_int($Type) @const => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT;
|
||||
|
||||
macro bool is_intlike($Type)
|
||||
<*
|
||||
@require is_numerical($Type) "Expected a numerical type"
|
||||
*>
|
||||
macro bool is_signed($Type) @const
|
||||
{
|
||||
$switch (inner_kind($Type))
|
||||
$case SIGNED_INT:
|
||||
$case FLOAT:
|
||||
return true;
|
||||
$case VECTOR:
|
||||
return is_signed($typefrom($Type.inner));
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require is_numerical($Type) "Expected a numerical type"
|
||||
*>
|
||||
macro bool is_unsigned($Type) @const
|
||||
{
|
||||
$switch (inner_kind($Type))
|
||||
$case UNSIGNED_INT:
|
||||
return true;
|
||||
$case VECTOR:
|
||||
return is_unsigned($typefrom($Type.inner));
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_indexable($Type) @const
|
||||
{
|
||||
return $defined($Type{}[0]);
|
||||
}
|
||||
|
||||
macro bool is_ref_indexable($Type) @const
|
||||
{
|
||||
return $defined(&$Type{}[0]);
|
||||
}
|
||||
|
||||
macro bool is_intlike($Type) @const
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$case SIGNED_INT:
|
||||
@@ -140,10 +177,22 @@ macro bool is_intlike($Type)
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_underlying_int($Type) @const
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
return true;
|
||||
$case DISTINCT:
|
||||
return is_underlying_int($typefrom($Type.inner));
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_float($Type) => $Type.kindof == TypeKind.FLOAT;
|
||||
macro bool is_float($Type) @const => $Type.kindof == TypeKind.FLOAT;
|
||||
|
||||
macro bool is_floatlike($Type)
|
||||
macro bool is_floatlike($Type) @const
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$case FLOAT:
|
||||
@@ -155,45 +204,45 @@ macro bool is_floatlike($Type)
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_vector($Type)
|
||||
macro bool is_vector($Type) @const
|
||||
{
|
||||
return $Type.kindof == TypeKind.VECTOR;
|
||||
}
|
||||
|
||||
macro TypeKind inner_kind($Type)
|
||||
macro typeid inner_type($Type) @const
|
||||
{
|
||||
$if $Type.kindof == TypeKind.DISTINCT:
|
||||
return inner_kind($typefrom($Type.inner));
|
||||
return inner_type($typefrom($Type.inner));
|
||||
$else
|
||||
return $Type.kindof;
|
||||
return $Type.typeid;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro bool @convertable(#a, $TypeB) @builtin
|
||||
macro TypeKind inner_kind($Type) @const
|
||||
{
|
||||
return $checks($TypeB x = #a);
|
||||
return inner_type($Type).kindof;
|
||||
}
|
||||
|
||||
macro bool is_same($TypeA, $TypeB)
|
||||
macro bool is_same($TypeA, $TypeB) @const
|
||||
{
|
||||
return $TypeA.typeid == $TypeB.typeid;
|
||||
}
|
||||
|
||||
macro bool @has_same(#a, #b, ...)
|
||||
macro bool @has_same(#a, #b, ...) @const
|
||||
{
|
||||
var $type_a = $typeof(#a).typeid;
|
||||
$if $type_a != $typeof(#b).typeid:
|
||||
return false;
|
||||
var $type_a = @typeid(#a);
|
||||
$if $type_a != @typeid(#b):
|
||||
return false;
|
||||
$endif
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
$if $typeof($vaexpr($i)).typeid != $type_a:
|
||||
$if @typeid($vaexpr[$i]) != $type_a:
|
||||
return false;
|
||||
$endif
|
||||
$endfor
|
||||
return true;
|
||||
}
|
||||
|
||||
macro bool may_load_atomic($Type)
|
||||
macro bool may_load_atomic($Type) @const
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$case SIGNED_INT:
|
||||
@@ -208,10 +257,36 @@ macro bool may_load_atomic($Type)
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_promotable_to_floatlike($Type) => types::is_floatlike($Type) || types::is_int($Type);
|
||||
macro bool is_promotable_to_float($Type) => types::is_float($Type) || types::is_int($Type);
|
||||
macro lower_to_atomic_compatible_type($Type) @const
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
return $Type.typeid;
|
||||
$case DISTINCT:
|
||||
return lower_to_atomic_compatible_type($Type.inner);
|
||||
$case FLOAT:
|
||||
$switch ($Type)
|
||||
$case float16:
|
||||
return ushort.typeid;
|
||||
$case float:
|
||||
return uint.typeid;
|
||||
$case double:
|
||||
return ulong.typeid;
|
||||
$case float128:
|
||||
return uint128.typeid;
|
||||
$default:
|
||||
return void.typeid;
|
||||
$endswitch
|
||||
$default:
|
||||
return void.typeid;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_same_vector_type($Type1, $Type2)
|
||||
macro bool is_promotable_to_floatlike($Type) @const => types::is_floatlike($Type) || types::is_int($Type);
|
||||
macro bool is_promotable_to_float($Type) @const => types::is_float($Type) || types::is_int($Type);
|
||||
|
||||
macro bool is_same_vector_type($Type1, $Type2) @const
|
||||
{
|
||||
$if $Type1.kindof != TypeKind.VECTOR:
|
||||
return $Type2.kindof != TypeKind.VECTOR;
|
||||
@@ -220,51 +295,74 @@ macro bool is_same_vector_type($Type1, $Type2)
|
||||
$endif
|
||||
}
|
||||
|
||||
macro bool is_equatable_type($Type)
|
||||
macro bool is_equatable_type($Type) @const
|
||||
{
|
||||
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):
|
||||
return true;
|
||||
$else
|
||||
return is_equatable($Type);
|
||||
return $Type.is_eq;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro bool is_equatable_value(value)
|
||||
<*
|
||||
Checks if a type implements the copy protocol.
|
||||
*>
|
||||
macro bool implements_copy($Type) @const
|
||||
{
|
||||
return $defined($Type.copy) && $defined($Type.free);
|
||||
}
|
||||
|
||||
macro bool is_equatable_value(value) @deprecated
|
||||
{
|
||||
return is_equatable_type($typeof(value));
|
||||
}
|
||||
|
||||
macro bool is_comparable_value(value)
|
||||
macro bool @equatable_value(#value) @const
|
||||
{
|
||||
return is_equatable_type($typeof(#value));
|
||||
}
|
||||
|
||||
macro bool @comparable_value(#value) @const
|
||||
{
|
||||
$if $defined(#value.less) || $defined(#value.compare_to):
|
||||
return true;
|
||||
$else
|
||||
return $typeof(#value).is_ordered;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro bool is_comparable_value(value) @deprecated
|
||||
{
|
||||
$if $defined(value.less) || $defined(value.compare_to):
|
||||
return true;
|
||||
$else
|
||||
return is_comparable($typeof(value));
|
||||
return $typeof(value).is_ordered;
|
||||
$endif
|
||||
}
|
||||
|
||||
enum TypeKind : char
|
||||
{
|
||||
VOID,
|
||||
BOOL,
|
||||
SIGNED_INT,
|
||||
UNSIGNED_INT,
|
||||
FLOAT,
|
||||
TYPEID,
|
||||
ANYFAULT,
|
||||
ANY,
|
||||
ENUM,
|
||||
FAULT,
|
||||
STRUCT,
|
||||
UNION,
|
||||
BITSTRUCT,
|
||||
FUNC,
|
||||
OPTIONAL,
|
||||
ARRAY,
|
||||
SUBARRAY,
|
||||
VECTOR,
|
||||
DISTINCT,
|
||||
POINTER,
|
||||
VOID,
|
||||
BOOL,
|
||||
SIGNED_INT,
|
||||
UNSIGNED_INT,
|
||||
FLOAT,
|
||||
TYPEID,
|
||||
ANYFAULT,
|
||||
ANY,
|
||||
ENUM,
|
||||
FAULT,
|
||||
STRUCT,
|
||||
UNION,
|
||||
BITSTRUCT,
|
||||
FUNC,
|
||||
OPTIONAL,
|
||||
ARRAY,
|
||||
SLICE,
|
||||
VECTOR,
|
||||
DISTINCT,
|
||||
POINTER,
|
||||
INTERFACE,
|
||||
}
|
||||
|
||||
struct TypeEnum
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
module std::core::values;
|
||||
|
||||
macro TypeKind @typekind(#value) @builtin => $typeof(#value).kindof;
|
||||
macro bool @typeis(#value, $Type) @builtin => $typeof(#value).typeid == $Type.typeid;
|
||||
macro bool @is_bool(#value) => types::is_bool($typeof(#value));
|
||||
macro bool @is_int(#value) => types::is_int($typeof(#value));
|
||||
macro bool @convertable_to(#a, #b) => $checks($typeof(#b) x = #a);
|
||||
macro bool @is_floatlike(#value) => types::is_floatlike($typeof(#value));
|
||||
macro bool @is_float(#value) => types::is_float($typeof(#value));
|
||||
macro bool @is_promotable_to_floatlike(#value) => types::is_promotable_to_floatlike($typeof(#value));
|
||||
macro bool @is_promotable_to_float(#value) => types::is_promotable_to_float($typeof(#value));
|
||||
macro bool @is_same_vector_type(#value1, #value2) => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
|
||||
macro typeid @typeid(#value) @const @builtin => $typeof(#value).typeid;
|
||||
macro TypeKind @typekind(#value) @const @builtin => $typeof(#value).kindof;
|
||||
macro bool @typeis(#value, $Type) @const @builtin => $typeof(#value).typeid == $Type.typeid;
|
||||
<*
|
||||
Return true if two values have the same type before any conversions.
|
||||
*>
|
||||
macro bool @is_same_type(#value1, #value2) @const => $typeof(#value1).typeid == $typeof(#value2).typeid;
|
||||
macro bool @is_bool(#value) @const => types::is_bool($typeof(#value));
|
||||
macro bool @is_int(#value) @const => types::is_int($typeof(#value));
|
||||
macro bool @is_floatlike(#value) @const => types::is_floatlike($typeof(#value));
|
||||
macro bool @is_float(#value) @const => types::is_float($typeof(#value));
|
||||
macro bool @is_promotable_to_floatlike(#value) @const => types::is_promotable_to_floatlike($typeof(#value));
|
||||
macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_float($typeof(#value));
|
||||
macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
|
||||
macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
|
||||
macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2));
|
||||
|
||||
macro promote_int(x)
|
||||
{
|
||||
$if @is_int(x):
|
||||
@@ -19,5 +26,21 @@ macro promote_int(x)
|
||||
$endif
|
||||
}
|
||||
|
||||
macro TypeKind @inner_kind(#value) => types::inner_kind($typeof(#value));
|
||||
macro promote_int_same(x, y)
|
||||
{
|
||||
$if @is_int(x):
|
||||
$switch
|
||||
$case @is_vector(y) &&& $typeof(y).inner == float.typeid:
|
||||
return (float)x;
|
||||
$case $typeof(y).typeid == float.typeid:
|
||||
return (float)x;
|
||||
$default:
|
||||
return (double)x;
|
||||
$endswitch
|
||||
$else
|
||||
return x;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro TypeKind @inner_kind(#value) @const => types::inner_kind($typeof(#value));
|
||||
|
||||
|
||||
@@ -1,2 +1,12 @@
|
||||
module std::crypto;
|
||||
|
||||
fn bool safe_compare(void* data1, void* data2, usz len)
|
||||
{
|
||||
char match = 0;
|
||||
for (usz i = 0; i < len; i++)
|
||||
{
|
||||
match = match | (mem::@volatile_load(((char*)data1)[i]) ^ mem::@volatile_load(((char*)data2)[i]));
|
||||
}
|
||||
return match == 0;
|
||||
}
|
||||
|
||||
|
||||
12
lib/std/crypto/dh.c3
Normal file
12
lib/std/crypto/dh.c3
Normal file
@@ -0,0 +1,12 @@
|
||||
module std::crypto::dh;
|
||||
import std::math::bigint;
|
||||
|
||||
fn BigInt generate_secret(BigInt p, BigInt x, BigInt y)
|
||||
{
|
||||
return y.mod_pow(x, p);
|
||||
}
|
||||
|
||||
fn BigInt public_key(BigInt p, BigInt g, BigInt x)
|
||||
{
|
||||
return g.mod_pow(x, p);
|
||||
}
|
||||
@@ -9,39 +9,49 @@ struct Rc4
|
||||
char[256] state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the RC4 state.
|
||||
*
|
||||
* @param [inout] this "The RC4 state"
|
||||
* @param [in] key "The key to use"
|
||||
* @require key.len > 0 "The key must be at least 1 byte long"
|
||||
**/
|
||||
fn void Rc4.init(Rc4* this, char[] key)
|
||||
<*
|
||||
Initialize the RC4 state.
|
||||
|
||||
@param [in] key "The key to use"
|
||||
@require key.len > 0 "The key must be at least 1 byte long"
|
||||
*>
|
||||
fn void Rc4.init(&self, char[] key)
|
||||
{
|
||||
// Init the state matrix
|
||||
foreach (char i, &c : this.state) *c = i;
|
||||
foreach (char i, &c : self.state) *c = i;
|
||||
for (int i = 0, int j = 0; i < 256; i++)
|
||||
{
|
||||
j = (j + this.state[i] + key[i % key.len]) & 0xFF;
|
||||
@swap(this.state[i], this.state[j]);
|
||||
j = (j + self.state[i] + key[i % key.len]) & 0xFF;
|
||||
@swap(self.state[i], self.state[j]);
|
||||
}
|
||||
this.i = 0;
|
||||
this.j = 0;
|
||||
self.i = 0;
|
||||
self.j = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt or decrypt a sequence of bytes.
|
||||
*
|
||||
* @param [inout] this "The RC4 State"
|
||||
* @param [in] in "The input"
|
||||
* @param [out] out "The output"
|
||||
* @require in.len <= out.len "Output would overflow"
|
||||
**/
|
||||
fn void Rc4.crypt(Rc4* this, char[] in, char[] out)
|
||||
<*
|
||||
Run a single pass of en/decryption using a particular key.
|
||||
@param [in] key
|
||||
@param [inout] data
|
||||
*>
|
||||
fn void crypt(char[] key, char[] data)
|
||||
{
|
||||
uint i = this.i;
|
||||
uint j = this.j;
|
||||
char* state = &this.state;
|
||||
Rc4 rc4;
|
||||
rc4.init(key);
|
||||
rc4.crypt(data, data);
|
||||
}
|
||||
|
||||
<*
|
||||
Encrypt or decrypt a sequence of bytes.
|
||||
|
||||
@param [in] in "The input"
|
||||
@param [out] out "The output"
|
||||
@require in.len <= out.len "Output would overflow"
|
||||
*>
|
||||
fn void Rc4.crypt(&self, char[] in, char[] out)
|
||||
{
|
||||
uint i = self.i;
|
||||
uint j = self.j;
|
||||
char* state = &self.state;
|
||||
isz len = in.len;
|
||||
foreach (idx, c : in)
|
||||
{
|
||||
@@ -50,16 +60,16 @@ fn void Rc4.crypt(Rc4* this, char[] in, char[] out)
|
||||
@swap(state[i], state[j]);
|
||||
out[idx] = in[idx] ^ state[(state[i] + state[j]) & 0xFF];
|
||||
}
|
||||
this.i = i;
|
||||
this.j = j;
|
||||
self.i = i;
|
||||
self.j = j;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the rc4 state.
|
||||
*
|
||||
* @param [out] this "The RC4 State"
|
||||
**/
|
||||
fn void Rc4.destroy(Rc4* this)
|
||||
<*
|
||||
Clear the rc4 state.
|
||||
|
||||
@param [&out] self "The RC4 State"
|
||||
*>
|
||||
fn void Rc4.destroy(&self)
|
||||
{
|
||||
*this = {};
|
||||
*self = {};
|
||||
}
|
||||
|
||||
406
lib/std/encoding/base32.c3
Normal file
406
lib/std/encoding/base32.c3
Normal file
@@ -0,0 +1,406 @@
|
||||
module std::encoding::base32;
|
||||
|
||||
// This module implements base32 encoding according to RFC 4648
|
||||
// (https://www.rfc-editor.org/rfc/rfc4648)
|
||||
|
||||
struct Base32Alphabet
|
||||
{
|
||||
char[32] encoding;
|
||||
char[256] reverse;
|
||||
}
|
||||
|
||||
const char NO_PAD = 0;
|
||||
const char DEFAULT_PAD = '=';
|
||||
|
||||
<*
|
||||
Encode the content of src into a newly allocated string
|
||||
@param [in] src "The input to be encoded."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@return "The encoded string."
|
||||
*>
|
||||
fn String! encode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding));
|
||||
return encode_buffer(src, dst, padding, alphabet);
|
||||
}
|
||||
|
||||
<*
|
||||
Decode the content of src into a newly allocated char array.
|
||||
@param [in] src "The input to be encoded."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@return "The decoded data."
|
||||
*>
|
||||
fn char[]! decode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding));
|
||||
return decode_buffer(src, dst, padding, alphabet);
|
||||
}
|
||||
|
||||
fn String! encode_new(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::heap(), padding, alphabet);
|
||||
fn String! encode_temp(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::temp(), padding, alphabet);
|
||||
fn char[]! decode_new(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::heap(), padding, alphabet);
|
||||
fn char[]! decode_temp(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::temp(), padding, alphabet);
|
||||
|
||||
<*
|
||||
Calculate the length in bytes of the decoded data.
|
||||
@param n "Length in bytes of input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@return "Length in bytes of the decoded data."
|
||||
*>
|
||||
fn usz decode_len(usz n, char padding)
|
||||
{
|
||||
if (padding) return (n / 8) * 5;
|
||||
// no padding
|
||||
usz trailing = n % 8;
|
||||
return n / 8 * 5 + (trailing * 5 ) / 8;
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the length in bytes of the encoded data.
|
||||
@param n "Length in bytes on input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@return "Length in bytes of the encoded data."
|
||||
*>
|
||||
fn usz encode_len(usz n, char padding)
|
||||
{
|
||||
// A character is encoded into 8 x 5-bit blocks.
|
||||
if (padding) return (n + 4) / 5 * 8;
|
||||
|
||||
// no padding
|
||||
usz trailing = n % 5;
|
||||
return n / 5 * 8 + (trailing * 8 + 4) / 5;
|
||||
}
|
||||
|
||||
<*
|
||||
Decode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be decoded."
|
||||
@param dst "The decoded input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@require dst.len >= decode_len(src.len, padding) "Destination buffer too small"
|
||||
@return "The resulting dst buffer"
|
||||
@return! DecodingFailure
|
||||
*>
|
||||
fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
if (src.len == 0) return dst[:0];
|
||||
char* dst_ptr = dst;
|
||||
usz dn = decode_len(src.len, padding);
|
||||
usz n;
|
||||
char[8] buf;
|
||||
while (src.len > 0 && dst.len > 0)
|
||||
{
|
||||
usz i @noinit;
|
||||
// load 8 bytes into buffer
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
if (src.len == 0)
|
||||
{
|
||||
if (padding > 0) return DecodingFailure.INVALID_PADDING?;
|
||||
break;
|
||||
}
|
||||
if (src[0] == padding) break;
|
||||
buf[i] = alphabet.reverse[src[0]];
|
||||
if (buf[i] == INVALID) return DecodingFailure.INVALID_CHARACTER?;
|
||||
src = src[1..];
|
||||
}
|
||||
|
||||
// extract 5-bytes from the buffer which contains 8 x 5 bit chunks
|
||||
switch (i)
|
||||
{
|
||||
case 8:
|
||||
// |66677777| dst[4]
|
||||
// | 77777| buf[7]
|
||||
// |666 | buf[6] << 5
|
||||
dst[4] = buf[7] | buf[6] << 5;
|
||||
n++;
|
||||
nextcase 7;
|
||||
case 7:
|
||||
// |45555566| dst[3]
|
||||
// | 66| buf[6] >> 3
|
||||
// | 55555 | buf[5] << 2
|
||||
// |4 | buf[4] << 7
|
||||
dst[3] = buf[6] >> 3 | buf[5] << 2 | buf[4] << 7;
|
||||
n++;
|
||||
nextcase 5;
|
||||
case 5:
|
||||
// |33334444| dst[2]
|
||||
// | 4444| buf[4] >> 1
|
||||
// |3333 | buf[3] << 4
|
||||
dst[2] = buf[4] >> 1 | buf[3] << 4;
|
||||
n++;
|
||||
nextcase 4;
|
||||
case 4:
|
||||
// |11222223| dst[1]
|
||||
// | 3| buf[3] >> 4
|
||||
// | 22222 | buf[2] << 1
|
||||
// |11 | buf[1] << 6
|
||||
dst[1] = buf[3] >> 4 | buf[2] << 1 | buf[1] << 6;
|
||||
n++;
|
||||
nextcase 2;
|
||||
case 2:
|
||||
// |00000111| dst[0]
|
||||
// | 111| buf[1] >> 2
|
||||
// |00000 | buf[0] << 3
|
||||
dst[0] = buf[1] >> 2 | buf[0] << 3;
|
||||
n++;
|
||||
default:
|
||||
return DecodingFailure.INVALID_CHARACTER?;
|
||||
}
|
||||
if (dst.len < 5) break;
|
||||
dst = dst[5..];
|
||||
}
|
||||
return dst_ptr[:n];
|
||||
}
|
||||
|
||||
<*
|
||||
Encode the content of src into dst, which must be properly sized.
|
||||
@param [in] src "The input to be encoded."
|
||||
@param [inout] dst "The encoded input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@require dst.len >= encode_len(src.len, padding) "Destination buffer too small"
|
||||
@return "The encoded size."
|
||||
*>
|
||||
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
if (src.len == 0) return (String)dst[:0];
|
||||
|
||||
char* dst_ptr = dst;
|
||||
usz n = (src.len / 5) * 5;
|
||||
usz dn = encode_len(src.len, padding);
|
||||
|
||||
uint msb, lsb;
|
||||
for (usz i = 0; i < n; i += 5)
|
||||
{
|
||||
// to fit 40 bits we need two 32-bit uints
|
||||
msb = (uint)src[i] << 24 | (uint)src[i+1] << 16
|
||||
| (uint)src[i+2] << 8 | (uint)src[i+3];
|
||||
lsb = msb << 8 | (uint)src[i+4];
|
||||
|
||||
// now slice them into 5-bit chunks and translate to the
|
||||
// alphabet.
|
||||
dst[0] = alphabet.encoding[(msb >> 27) & MASK];
|
||||
dst[1] = alphabet.encoding[(msb >> 22) & MASK];
|
||||
dst[2] = alphabet.encoding[(msb >> 17) & MASK];
|
||||
dst[3] = alphabet.encoding[(msb >> 12) & MASK];
|
||||
dst[4] = alphabet.encoding[(msb >> 7) & MASK];
|
||||
dst[5] = alphabet.encoding[(msb >> 2) & MASK];
|
||||
dst[6] = alphabet.encoding[(lsb >> 5) & MASK];
|
||||
dst[7] = alphabet.encoding[lsb & MASK];
|
||||
|
||||
dst = dst[8..];
|
||||
}
|
||||
|
||||
usz trailing = src.len - n;
|
||||
if (trailing == 0) return (String)dst_ptr[:dn];
|
||||
|
||||
msb = 0;
|
||||
switch (trailing)
|
||||
{
|
||||
case 4:
|
||||
msb |= (uint)src[n+3];
|
||||
lsb = msb << 8;
|
||||
dst[6] = alphabet.encoding[(lsb >> 5) & MASK];
|
||||
dst[5] = alphabet.encoding[(msb >> 2) & MASK];
|
||||
nextcase 3;
|
||||
case 3:
|
||||
msb |= (uint)src[n+2] << 8;
|
||||
dst[4] = alphabet.encoding[(msb >> 7) & MASK];
|
||||
nextcase 2;
|
||||
case 2:
|
||||
msb |= (uint)src[n+1] << 16;
|
||||
dst[3] = alphabet.encoding[(msb >> 12) & MASK];
|
||||
dst[2] = alphabet.encoding[(msb >> 17) & MASK];
|
||||
nextcase 1;
|
||||
case 1:
|
||||
msb |= (uint)src[n] << 24;
|
||||
dst[1] = alphabet.encoding[(msb >> 22) & MASK];
|
||||
dst[0] = alphabet.encoding[(msb >> 27) & MASK];
|
||||
}
|
||||
|
||||
// add the padding
|
||||
if (padding > 0)
|
||||
{
|
||||
for (usz i = (trailing * 8 / 5) + 1; i < 8; i++)
|
||||
{
|
||||
dst[i] = padding;
|
||||
}
|
||||
}
|
||||
return (String)dst_ptr[:dn];
|
||||
}
|
||||
|
||||
const uint MASK @private = 0b11111;
|
||||
const char INVALID @private = 0xff;
|
||||
|
||||
const int STD_PADDING = '=';
|
||||
const int NO_PADDING = -1;
|
||||
|
||||
fault Base32Error
|
||||
{
|
||||
DUPLICATE_IN_ALPHABET,
|
||||
PADDING_IN_ALPHABET,
|
||||
INVALID_CHARACTER_IN_ALPHABET,
|
||||
DESTINATION_TOO_SMALL,
|
||||
INVALID_PADDING,
|
||||
CORRUPT_INPUT
|
||||
}
|
||||
|
||||
struct Base32Encoder @deprecated
|
||||
{
|
||||
Base32Alphabet alphabet;
|
||||
char padding;
|
||||
}
|
||||
|
||||
<*
|
||||
@param encoder "The 32-character alphabet for encoding."
|
||||
@param padding "Set to a negative value to disable padding."
|
||||
@require padding < 256
|
||||
*>
|
||||
fn void! Base32Encoder.init(&self, Alphabet encoder = STD_ALPHABET, int padding = STD_PADDING)
|
||||
{
|
||||
encoder.validate(padding)!;
|
||||
*self = { .alphabet = { .encoding = (char[32])encoder }, .padding = padding < 0 ? (char)0 : (char)padding};
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the length in bytes of the encoded data.
|
||||
@param n "Length in bytes on input."
|
||||
@return "Length in bytes of the encoded data."
|
||||
*>
|
||||
fn usz Base32Encoder.encode_len(&self, usz n)
|
||||
{
|
||||
return encode_len(n, self.padding);
|
||||
}
|
||||
|
||||
<*
|
||||
Encode the content of src into dst, which must be properly sized.
|
||||
@param [in] src "The input to be encoded."
|
||||
@param [inout] dst "The encoded input."
|
||||
@return "The encoded size."
|
||||
@return! Base32Error.DESTINATION_TOO_SMALL
|
||||
*>
|
||||
fn usz! Base32Encoder.encode(&self, char[] src, char[] dst)
|
||||
{
|
||||
usz dn = self.encode_len(src.len);
|
||||
if (dst.len < dn) return Base32Error.DESTINATION_TOO_SMALL?;
|
||||
return encode_buffer(src, dst, self.padding, &self.alphabet).len;
|
||||
}
|
||||
|
||||
struct Base32Decoder @deprecated
|
||||
{
|
||||
Base32Alphabet alphabet;
|
||||
char padding;
|
||||
}
|
||||
|
||||
<*
|
||||
@param decoder "The alphabet used for decoding."
|
||||
@param padding "Set to a negative value to disable padding."
|
||||
@require padding < 256
|
||||
*>
|
||||
fn void! Base32Decoder.init(&self, Alphabet decoder = STD_ALPHABET, int padding = STD_PADDING)
|
||||
{
|
||||
decoder.validate(padding)!;
|
||||
*self = { .alphabet = { .encoding = (char[32])decoder }, .padding = padding < 0 ? (char)0 : (char)padding };
|
||||
|
||||
self.alphabet.reverse[..] = INVALID;
|
||||
foreach (char i, c : decoder)
|
||||
{
|
||||
self.alphabet.reverse[c] = i;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the length in bytes of the decoded data.
|
||||
@param n "Length in bytes of input."
|
||||
@return "Length in bytes of the decoded data."
|
||||
*>
|
||||
fn usz Base32Decoder.decode_len(&self, usz n)
|
||||
{
|
||||
return decode_len(n, self.padding);
|
||||
}
|
||||
|
||||
<*
|
||||
Decode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be decoded."
|
||||
@param dst "The decoded input."
|
||||
@return "The decoded size."
|
||||
@return! Base32Error.DESTINATION_TOO_SMALL, Base32Error.CORRUPT_INPUT
|
||||
*>
|
||||
fn usz! Base32Decoder.decode(&self, char[] src, char[] dst)
|
||||
{
|
||||
if (src.len == 0) return 0;
|
||||
usz dn = self.decode_len(src.len);
|
||||
if (dst.len < dn) return Base32Error.DESTINATION_TOO_SMALL?;
|
||||
return decode_buffer(src, dst, self.padding, &self.alphabet).len;
|
||||
}
|
||||
|
||||
|
||||
// Validate the 32-character alphabet to make sure that no character occurs
|
||||
// twice and that the padding is not present in the alphabet.
|
||||
fn void! Alphabet.validate(&self, int padding)
|
||||
{
|
||||
bool[256] checked;
|
||||
foreach (c : self)
|
||||
{
|
||||
if (checked[c])
|
||||
{
|
||||
return Base32Error.DUPLICATE_IN_ALPHABET?;
|
||||
}
|
||||
checked[c] = true;
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
return Base32Error.INVALID_CHARACTER_IN_ALPHABET?;
|
||||
}
|
||||
}
|
||||
if (padding >= 0)
|
||||
{
|
||||
char pad = (char)padding;
|
||||
if (pad == '\r' || pad == '\n')
|
||||
{
|
||||
return Base32Error.INVALID_PADDING?;
|
||||
}
|
||||
if (checked[pad])
|
||||
{
|
||||
return Base32Error.PADDING_IN_ALPHABET?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
distinct Alphabet = char[32];
|
||||
// Standard base32 Alphabet
|
||||
const Alphabet STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
// Extended Hex Alphabet
|
||||
const Alphabet HEX_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
|
||||
|
||||
const Base32Alphabet STANDARD = {
|
||||
.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
|
||||
.reverse = x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffff1a1b1c1d1e1fffffffffffffffff
|
||||
ff000102030405060708090a0b0c0d0e0f10111213141516171819ffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
|
||||
};
|
||||
|
||||
const Base32Alphabet HEX = {
|
||||
.encoding = "0123456789ABCDEFGHIJKLMNOPQRSTUV",
|
||||
.reverse = x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff00010203040506070809ffffffffffff
|
||||
ff0a0b0c0d0e0f101112131415161718191a1b1c1d1e1fffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
|
||||
};
|
||||
401
lib/std/encoding/base64.c3
Normal file
401
lib/std/encoding/base64.c3
Normal file
@@ -0,0 +1,401 @@
|
||||
module std::encoding::base64;
|
||||
import std::core::bitorder;
|
||||
|
||||
// The implementation is based on https://www.rfc-editor.org/rfc/rfc4648
|
||||
// Specifically this section:
|
||||
// https://www.rfc-editor.org/rfc/rfc4648#section-4
|
||||
|
||||
const char NO_PAD = 0;
|
||||
const char DEFAULT_PAD = '=';
|
||||
|
||||
struct Base64Alphabet
|
||||
{
|
||||
char[64] encoding;
|
||||
char[256] reverse;
|
||||
}
|
||||
|
||||
const Base64Alphabet STANDARD = {
|
||||
.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
|
||||
.reverse =
|
||||
x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffff3effffff3f3435363738393a3b3c3dffffffffffff
|
||||
ff000102030405060708090a0b0c0d0e0f10111213141516171819ffffffffff
|
||||
ff1a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233ffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
|
||||
};
|
||||
|
||||
const Base64Alphabet URL = {
|
||||
.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
|
||||
.reverse =
|
||||
x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffff3effff3435363738393a3b3c3dffffffffffff
|
||||
ff000102030405060708090a0b0c0d0e0f10111213141516171819ffffffff3f
|
||||
ff1a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233ffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
|
||||
};
|
||||
|
||||
const STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
||||
fn String encode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding));
|
||||
return encode_buffer(src, dst, padding, alphabet);
|
||||
}
|
||||
|
||||
fn char[]! decode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding))!;
|
||||
return decode_buffer(src, dst, padding, alphabet);
|
||||
}
|
||||
|
||||
fn String encode_new(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::heap(), padding, alphabet);
|
||||
fn String encode_temp(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::temp(), padding, alphabet);
|
||||
fn char[]! decode_new(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::heap(), padding, alphabet);
|
||||
fn char[]! decode_temp(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::temp(), padding, alphabet);
|
||||
|
||||
|
||||
<*
|
||||
Calculate the size of the encoded data.
|
||||
@param n "Size of the input to be encoded."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@return "The size of the input once encoded."
|
||||
*>
|
||||
fn usz encode_len(usz n, char padding)
|
||||
{
|
||||
if (padding) return (n + 2) / 3 * 4;
|
||||
usz trailing = n % 3;
|
||||
return n / 3 * 4 + (trailing * 4 + 2) / 3;
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the size of the decoded data.
|
||||
@param n "Size of the input to be decoded."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@return "The size of the input once decoded."
|
||||
@return! DecodingFailure.INVALID_PADDING
|
||||
*>
|
||||
fn usz! decode_len(usz n, char padding)
|
||||
{
|
||||
usz dn = n / 4 * 3;
|
||||
usz trailing = n % 4;
|
||||
if (padding)
|
||||
{
|
||||
if (trailing != 0) return DecodingFailure.INVALID_PADDING?;
|
||||
// source size is multiple of 4
|
||||
return dn;
|
||||
}
|
||||
if (trailing == 1) return DecodingFailure.INVALID_PADDING?;
|
||||
return dn + trailing * 3 / 4;
|
||||
}
|
||||
|
||||
<*
|
||||
Encode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be encoded."
|
||||
@param dst "The encoded input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@return "The encoded size."
|
||||
@return! Base64Error.DESTINATION_TOO_SMALL
|
||||
*>
|
||||
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
if (src.len == 0) return (String)dst[:0];
|
||||
usz dn = encode_len(src.len, padding);
|
||||
char* dst_ptr = dst;
|
||||
assert(dst.len >= dn);
|
||||
usz trailing = src.len % 3;
|
||||
char[] src3 = src[:^trailing];
|
||||
|
||||
while (src3.len > 0)
|
||||
{
|
||||
uint group = (uint)src3[0] << 16 | (uint)src3[1] << 8 | (uint)src3[2];
|
||||
dst[0] = alphabet.encoding[group >> 18 & MASK];
|
||||
dst[1] = alphabet.encoding[group >> 12 & MASK];
|
||||
dst[2] = alphabet.encoding[group >> 6 & MASK];
|
||||
dst[3] = alphabet.encoding[group & MASK];
|
||||
dst = dst[4..];
|
||||
src3 = src3[3..];
|
||||
}
|
||||
|
||||
// Encode the remaining bytes according to:
|
||||
// https://www.rfc-editor.org/rfc/rfc4648#section-3.5
|
||||
switch (trailing)
|
||||
{
|
||||
case 1:
|
||||
uint group = (uint)src[^1] << 16;
|
||||
dst[0] = alphabet.encoding[group >> 18 & MASK];
|
||||
dst[1] = alphabet.encoding[group >> 12 & MASK];
|
||||
if (padding > 0)
|
||||
{
|
||||
dst[2] = padding;
|
||||
dst[3] = padding;
|
||||
}
|
||||
case 2:
|
||||
uint group = (uint)src[^2] << 16 | (uint)src[^1] << 8;
|
||||
dst[0] = alphabet.encoding[group >> 18 & MASK];
|
||||
dst[1] = alphabet.encoding[group >> 12 & MASK];
|
||||
dst[2] = alphabet.encoding[group >> 6 & MASK];
|
||||
if (padding > 0)
|
||||
{
|
||||
dst[3] = padding;
|
||||
}
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
return (String)dst_ptr[:dn];
|
||||
}
|
||||
|
||||
<*
|
||||
Decode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be decoded."
|
||||
@param dst "The decoded input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require (decode_len(src.len, padding) ?? 0) <= dst.len "Destination buffer too small"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@return "The decoded data."
|
||||
@return! DecodingFailure
|
||||
*>
|
||||
fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
if (src.len == 0) return dst[:0];
|
||||
usz dn = decode_len(src.len, padding)!;
|
||||
assert(dst.len >= dn);
|
||||
|
||||
usz trailing = src.len % 4;
|
||||
char* dst_ptr = dst;
|
||||
char[] src4 = src;
|
||||
switch
|
||||
{
|
||||
case !padding:
|
||||
src4 = src[:^trailing];
|
||||
default:
|
||||
// If there is padding, keep the last 4 bytes for later.
|
||||
// NB. src.len >= 4 as decode_len passed
|
||||
trailing = 4;
|
||||
if (src[^1] == padding) src4 = src[:^4];
|
||||
}
|
||||
while (src4.len > 0)
|
||||
{
|
||||
char c0 = alphabet.reverse[src4[0]];
|
||||
char c1 = alphabet.reverse[src4[1]];
|
||||
char c2 = alphabet.reverse[src4[2]];
|
||||
char c3 = alphabet.reverse[src4[3]];
|
||||
switch (0xFF)
|
||||
{
|
||||
case c0:
|
||||
case c1:
|
||||
case c2:
|
||||
case c3:
|
||||
return DecodingFailure.INVALID_CHARACTER?;
|
||||
}
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6 | (uint)c3;
|
||||
dst[0] = (char)(group >> 16);
|
||||
dst[1] = (char)(group >> 8);
|
||||
dst[2] = (char)group;
|
||||
dst = dst[3..];
|
||||
src4 = src4[4..];
|
||||
}
|
||||
|
||||
if (trailing == 0) return dst_ptr[:dn];
|
||||
|
||||
src = src[^trailing..];
|
||||
char c0 = alphabet.reverse[src[0]];
|
||||
char c1 = alphabet.reverse[src[1]];
|
||||
if (c0 == 0xFF || c1 == 0xFF) return DecodingFailure.INVALID_PADDING?;
|
||||
if (!padding)
|
||||
{
|
||||
switch (src.len)
|
||||
{
|
||||
case 2:
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12;
|
||||
dst[0] = (char)(group >> 16);
|
||||
case 3:
|
||||
char c2 = alphabet.reverse[src[2]];
|
||||
if (c2 == 0xFF) return DecodingFailure.INVALID_CHARACTER?;
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
|
||||
dst[0] = (char)(group >> 16);
|
||||
dst[1] = (char)(group >> 8);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Valid paddings are:
|
||||
// 2: xx==
|
||||
// 1: xxx=
|
||||
switch (padding)
|
||||
{
|
||||
case src[2]:
|
||||
if (src[3] != padding) return DecodingFailure.INVALID_PADDING?;
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12;
|
||||
dst[0] = (char)(group >> 16);
|
||||
dn -= 2;
|
||||
case src[3]:
|
||||
char c2 = alphabet.reverse[src[2]];
|
||||
if (c2 == 0xFF) return DecodingFailure.INVALID_CHARACTER?;
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
|
||||
dst[0] = (char)(group >> 16);
|
||||
dst[1] = (char)(group >> 8);
|
||||
dn -= 1;
|
||||
}
|
||||
}
|
||||
return dst_ptr[:dn];
|
||||
}
|
||||
|
||||
const MASK @private = 0b111111;
|
||||
|
||||
struct Base64Encoder @deprecated
|
||||
{
|
||||
char padding;
|
||||
String alphabet;
|
||||
}
|
||||
|
||||
fault Base64Error
|
||||
{
|
||||
DUPLICATE_IN_ALPHABET,
|
||||
PADDING_IN_ALPHABET,
|
||||
DESTINATION_TOO_SMALL,
|
||||
INVALID_PADDING,
|
||||
INVALID_CHARACTER,
|
||||
}
|
||||
|
||||
<*
|
||||
@param alphabet "The alphabet used for encoding."
|
||||
@param padding "Set to a negative value to disable padding."
|
||||
@require alphabet.len == 64
|
||||
@require padding < 256
|
||||
@return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
|
||||
*>
|
||||
fn Base64Encoder*! Base64Encoder.init(&self, String alphabet, int padding = '=')
|
||||
{
|
||||
check_alphabet(alphabet, padding)!;
|
||||
*self = { .padding = padding < 0 ? 0 : (char)padding, .alphabet = alphabet };
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the size of the encoded data.
|
||||
@param n "Size of the input to be encoded."
|
||||
@return "The size of the input once encoded."
|
||||
*>
|
||||
fn usz Base64Encoder.encode_len(&self, usz n)
|
||||
{
|
||||
return encode_len(n, self.padding);
|
||||
}
|
||||
|
||||
<*
|
||||
Encode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be encoded."
|
||||
@param dst "The encoded input."
|
||||
@return "The encoded size."
|
||||
@return! Base64Error.DESTINATION_TOO_SMALL
|
||||
*>
|
||||
fn usz! Base64Encoder.encode(&self, char[] src, char[] dst)
|
||||
{
|
||||
if (src.len == 0) return 0;
|
||||
usz dn = self.encode_len(src.len);
|
||||
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
|
||||
Base64Alphabet a = { .encoding = self.alphabet[:64] };
|
||||
return encode_buffer(src, dst, self.padding, &a).len;
|
||||
}
|
||||
|
||||
struct Base64Decoder @deprecated
|
||||
{
|
||||
char padding;
|
||||
Base64Alphabet encoding;
|
||||
bool init_done;
|
||||
}
|
||||
|
||||
import std;
|
||||
<*
|
||||
@param alphabet "The alphabet used for encoding."
|
||||
@param padding "Set to a negative value to disable padding."
|
||||
@require alphabet.len == 64
|
||||
@require padding < 256
|
||||
@return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
|
||||
*>
|
||||
fn void! Base64Decoder.init(&self, String alphabet, int padding = '=')
|
||||
{
|
||||
self.init_done = true;
|
||||
check_alphabet(alphabet, padding)!;
|
||||
*self = { .padding = padding < 0 ? 0 : (char)padding, .encoding.encoding = alphabet[:64] };
|
||||
|
||||
self.encoding.reverse[..] = 0xFF;
|
||||
|
||||
foreach (i, c : alphabet)
|
||||
{
|
||||
self.encoding.reverse[c] = (char)i;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the size of the decoded data.
|
||||
@param n "Size of the input to be decoded."
|
||||
@return "The size of the input once decoded."
|
||||
@return! Base64Error.INVALID_PADDING
|
||||
*>
|
||||
fn usz! Base64Decoder.decode_len(&self, usz n)
|
||||
{
|
||||
return decode_len(n, self.padding) ?? Base64Error.INVALID_PADDING?;
|
||||
}
|
||||
|
||||
<*
|
||||
Decode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be decoded."
|
||||
@param dst "The decoded input."
|
||||
@return "The decoded size."
|
||||
@return! Base64Error.DESTINATION_TOO_SMALL, Base64Error.INVALID_PADDING, Base64Error.INVALID_CHARACTER
|
||||
*>
|
||||
fn usz! Base64Decoder.decode(&self, char[] src, char[] dst)
|
||||
{
|
||||
if (src.len == 0) return 0;
|
||||
usz dn = self.decode_len(src.len)!;
|
||||
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
|
||||
char[]! decoded = decode_buffer(src, dst, self.padding, &self.encoding);
|
||||
if (catch err = decoded)
|
||||
{
|
||||
case DecodingFailure.INVALID_PADDING:
|
||||
return Base64Error.INVALID_PADDING?;
|
||||
case DecodingFailure.INVALID_CHARACTER:
|
||||
return Base64Error.INVALID_CHARACTER?;
|
||||
default:
|
||||
return err?;
|
||||
}
|
||||
return decoded.len;
|
||||
}
|
||||
|
||||
// Make sure that all bytes in the alphabet are unique and
|
||||
// the padding is not present in the alphabet.
|
||||
fn void! check_alphabet(String alphabet, int padding) @local
|
||||
{
|
||||
bool[256] checked;
|
||||
if (padding < 0)
|
||||
{
|
||||
foreach (c : alphabet)
|
||||
{
|
||||
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
|
||||
checked[c] = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
char pad = (char)padding;
|
||||
foreach (c : alphabet)
|
||||
{
|
||||
if (c == pad) return Base64Error.PADDING_IN_ALPHABET?;
|
||||
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
|
||||
checked[c] = true;
|
||||
}
|
||||
}
|
||||
|
||||
98
lib/std/encoding/csv.c3
Normal file
98
lib/std/encoding/csv.c3
Normal file
@@ -0,0 +1,98 @@
|
||||
module std::encoding::csv;
|
||||
import std::io;
|
||||
|
||||
|
||||
struct CsvReader
|
||||
{
|
||||
InStream stream;
|
||||
String separator;
|
||||
}
|
||||
|
||||
struct CsvRow (Printable)
|
||||
{
|
||||
String[] list;
|
||||
String row;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
fn usz! CsvRow.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
return f.printf("%s", self.list);
|
||||
}
|
||||
|
||||
fn usz CsvRow.len(&self) @operator(len)
|
||||
{
|
||||
return self.list.len;
|
||||
}
|
||||
|
||||
<*
|
||||
@require col < self.list.len
|
||||
*>
|
||||
fn String CsvRow.get_col(&self, usz col) @operator([])
|
||||
{
|
||||
return self.list[col];
|
||||
}
|
||||
|
||||
fn void CsvReader.init(&self, InStream stream, String separator = ",")
|
||||
{
|
||||
self.stream = stream;
|
||||
self.separator = separator;
|
||||
}
|
||||
|
||||
fn CsvRow! CsvReader.read_new_row(self)
|
||||
{
|
||||
return self.read_row(allocator::heap()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator
|
||||
*>
|
||||
fn CsvRow! CsvReader.read_row(self, Allocator allocator)
|
||||
{
|
||||
String row = io::readline(self.stream, allocator: allocator)!;
|
||||
defer catch allocator::free(allocator, row);
|
||||
String[] list = row.split(self.separator, allocator: allocator);
|
||||
return { list, row, allocator };
|
||||
}
|
||||
|
||||
fn CsvRow! CsvReader.read_temp_row(self)
|
||||
{
|
||||
return self.read_row(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.allocator `Row already freed`
|
||||
*>
|
||||
fn void CsvRow.free(&self)
|
||||
{
|
||||
allocator::free(self.allocator, self.list);
|
||||
allocator::free(self.allocator, self.row);
|
||||
self.allocator = null;
|
||||
}
|
||||
|
||||
fn void! CsvReader.skip_row(self) @maydiscard
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
(void)io::treadline(self.stream);
|
||||
};
|
||||
}
|
||||
|
||||
macro CsvReader.@each_row(self, int rows = int.max; @body(String[] row))
|
||||
{
|
||||
InStream stream = self.stream;
|
||||
String sep = self.separator;
|
||||
while (rows--)
|
||||
{
|
||||
@stack_mem(512; Allocator mem)
|
||||
{
|
||||
String! s = io::readline(stream, mem);
|
||||
if (catch err = s)
|
||||
{
|
||||
if (err == IoError.EOF) return;
|
||||
return err?;
|
||||
}
|
||||
@body(s.split(sep, allocator: mem));
|
||||
};
|
||||
}
|
||||
}
|
||||
7
lib/std/encoding/encoding.c3
Normal file
7
lib/std/encoding/encoding.c3
Normal file
@@ -0,0 +1,7 @@
|
||||
module std::encoding;
|
||||
|
||||
fault DecodingFailure
|
||||
{
|
||||
INVALID_CHARACTER,
|
||||
INVALID_PADDING,
|
||||
}
|
||||
109
lib/std/encoding/hex.c3
Normal file
109
lib/std/encoding/hex.c3
Normal file
@@ -0,0 +1,109 @@
|
||||
module std::encoding::hex;
|
||||
import std::encoding @norecurse;
|
||||
|
||||
// The implementation is based on https://www.rfc-editor.org/rfc/rfc4648
|
||||
|
||||
fn String encode_buffer(char[] code, char[] buffer)
|
||||
{
|
||||
return (String)buffer[:encode_bytes(code, buffer)];
|
||||
}
|
||||
|
||||
fn char[]! decode_buffer(char[] code, char[] buffer)
|
||||
{
|
||||
return buffer[:decode_bytes(code, buffer)!];
|
||||
}
|
||||
|
||||
fn String encode(char[] code, Allocator allocator)
|
||||
{
|
||||
char[] data = allocator::alloc_array(allocator, char, encode_len(code.len));
|
||||
return (String)data[:encode_bytes(code, data)];
|
||||
}
|
||||
|
||||
fn char[]! decode(char[] code, Allocator allocator)
|
||||
{
|
||||
char[] data = allocator::alloc_array(allocator, char, decode_len(code.len));
|
||||
return data[:decode_bytes(code, data)!];
|
||||
}
|
||||
|
||||
fn String encode_new(char[] code) @inline => encode(code, allocator::heap());
|
||||
fn String encode_temp(char[] code) @inline => encode(code, allocator::temp());
|
||||
fn char[]! decode_new(char[] code) @inline => decode(code, allocator::heap());
|
||||
fn char[]! decode_temp(char[] code) @inline => decode(code, allocator::temp());
|
||||
|
||||
<*
|
||||
Calculate the size of the encoded data.
|
||||
@param n "Size of the input to be encoded."
|
||||
@return "The size of the input once encoded."
|
||||
*>
|
||||
fn usz encode_len(usz n) => n * 2;
|
||||
|
||||
<*
|
||||
Encode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be encoded."
|
||||
@param dst "The encoded input."
|
||||
@return "The encoded size."
|
||||
@require dst.len >= encode_len(src.len) "Destination array is not large enough"
|
||||
*>
|
||||
fn usz encode_bytes(char[] src, char[] dst)
|
||||
{
|
||||
usz j = 0;
|
||||
foreach (v : src)
|
||||
{
|
||||
dst[j] = HEXALPHABET[v >> 4];
|
||||
dst[j + 1] = HEXALPHABET[v & 0x0f];
|
||||
j = j + 2;
|
||||
}
|
||||
return src.len * 2;
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the size of the decoded data.
|
||||
@param n "Size of the input to be decoded."
|
||||
@return "The size of the input once decoded."
|
||||
*>
|
||||
macro usz decode_len(usz n) => n / 2;
|
||||
|
||||
<*
|
||||
Decodes src into bytes. Returns the actual number of bytes written to dst.
|
||||
|
||||
Expects that src only contains hexadecimal characters and that src has even
|
||||
length.
|
||||
|
||||
@param src "The input to be decoded."
|
||||
@param dst "The decoded input."
|
||||
@require src.len % 2 == 0 "src is not of even length"
|
||||
@require dst.len >= decode_len(src.len) "Destination array is not large enough"
|
||||
@return! DecodingFailure.INVALID_CHARACTER
|
||||
*>
|
||||
fn usz! decode_bytes(char[] src, char[] dst)
|
||||
{
|
||||
usz i;
|
||||
for (usz j = 1; j < src.len; j += 2)
|
||||
{
|
||||
char a = HEXREVERSE[src[j - 1]];
|
||||
char b = HEXREVERSE[src[j]];
|
||||
if (a > 0x0f || b > 0x0f) return DecodingFailure.INVALID_CHARACTER?;
|
||||
dst[i] = (a << 4) | b;
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
const char[*] HEXALPHABET @private = "0123456789abcdef";
|
||||
const char[*] HEXREVERSE @private =
|
||||
x`ffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff
|
||||
00010203040506070809ffffffffffff
|
||||
ff0a0b0c0d0e0fffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff
|
||||
ff0a0b0c0d0e0fffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff`;
|
||||
@@ -6,7 +6,45 @@ import std::io;
|
||||
import std::ascii;
|
||||
import std::collections::object;
|
||||
|
||||
enum JsonTokenType
|
||||
fault JsonParsingError
|
||||
{
|
||||
EOF,
|
||||
UNEXPECTED_CHARACTER,
|
||||
INVALID_ESCAPE_SEQUENCE,
|
||||
DUPLICATE_MEMBERS,
|
||||
INVALID_NUMBER,
|
||||
}
|
||||
|
||||
fn Object*! parse_string(String s, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return parse(ByteReader{}.init(s), allocator);
|
||||
}
|
||||
|
||||
fn Object*! temp_parse_string(String s)
|
||||
{
|
||||
return parse(ByteReader{}.init(s), allocator::temp());
|
||||
}
|
||||
|
||||
fn Object*! parse(InStream s, Allocator allocator = allocator::heap())
|
||||
{
|
||||
@stack_mem(512; Allocator mem)
|
||||
{
|
||||
JsonContext context = { .last_string = dstring::new_with_capacity(64, mem), .stream = s, .allocator = allocator };
|
||||
@pool(allocator)
|
||||
{
|
||||
return parse_any(&context);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
fn Object*! temp_parse(InStream s)
|
||||
{
|
||||
return parse(s, allocator::temp());
|
||||
}
|
||||
|
||||
// -- Implementation follows --
|
||||
|
||||
enum JsonTokenType @local
|
||||
{
|
||||
NO_TOKEN,
|
||||
LBRACE,
|
||||
@@ -23,79 +61,67 @@ enum JsonTokenType
|
||||
EOF,
|
||||
}
|
||||
|
||||
struct JsonParser
|
||||
struct JsonContext @local
|
||||
{
|
||||
uint line;
|
||||
Stream stream;
|
||||
Allocator* allocator;
|
||||
InStream stream;
|
||||
Allocator allocator;
|
||||
JsonTokenType token;
|
||||
DString last_string;
|
||||
double last_number;
|
||||
char current;
|
||||
anyfault current_err;
|
||||
bool skip_comments;
|
||||
bool reached_end;
|
||||
bitstruct : char {
|
||||
bool skip_comments;
|
||||
bool reached_end;
|
||||
bool pushed_back;
|
||||
}
|
||||
}
|
||||
|
||||
fault JsonParsingError
|
||||
{
|
||||
EOF,
|
||||
UNEXPECTED_CHARACTER,
|
||||
INVALID_ESCAPE_SEQUENCE,
|
||||
DUPLICATE_MEMBERS,
|
||||
INVALID_NUMBER,
|
||||
}
|
||||
|
||||
fn void JsonParser.init(JsonParser* parser, Stream s, Allocator* using = mem::heap())
|
||||
{
|
||||
*parser = { .last_string = dstring::new_with_capacity(64, using), .stream = s, .allocator = using };
|
||||
}
|
||||
|
||||
fn Object*! JsonParser.parse_from_token(JsonParser* this, JsonTokenType token)
|
||||
fn Object*! parse_from_token(JsonContext* context, JsonTokenType token) @local
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case NO_TOKEN: unreachable();
|
||||
case LBRACE: return this.parse_map();
|
||||
case LBRACKET: return this.parse_array();
|
||||
case LBRACE: return parse_map(context);
|
||||
case LBRACKET: return parse_array(context);
|
||||
case COMMA:
|
||||
case RBRACE:
|
||||
case RBRACKET:
|
||||
case COLON: return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
case STRING: return object::new_string(this.last_string.str(), this.allocator);
|
||||
case NUMBER: return object::new_float(this.last_number, this.allocator);
|
||||
case STRING: return object::new_string(context.last_string.str_view(), context.allocator);
|
||||
case NUMBER: return object::new_float(context.last_number, context.allocator);
|
||||
case TRUE: return object::new_bool(true);
|
||||
case FALSE: return object::new_bool(false);
|
||||
case NULL: return object::new_null();
|
||||
case EOF: return JsonParsingError.EOF?;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
fn Object*! JsonParser.parse_any(JsonParser* this)
|
||||
fn Object*! parse_any(JsonContext* context) @local
|
||||
{
|
||||
return this.parse_from_token(this.advance());
|
||||
return parse_from_token(context, advance(context));
|
||||
}
|
||||
|
||||
fn JsonTokenType! JsonParser.lex_number(JsonParser* this, char c)
|
||||
fn JsonTokenType! lex_number(JsonContext *context, char c) @local
|
||||
{
|
||||
@pool()
|
||||
@stack_mem(256; Allocator mem)
|
||||
{
|
||||
DString t = dstring::tnew_with_capacity(32);
|
||||
DString t = dstring::new_with_capacity(32, allocator: mem);
|
||||
bool negate = c == '-';
|
||||
if (negate)
|
||||
{
|
||||
t.append(c);
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
}
|
||||
while (c >= '0' && c <= '9')
|
||||
while (c.is_digit())
|
||||
{
|
||||
t.append(c);
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
}
|
||||
if (c == '.')
|
||||
{
|
||||
t.append(c);
|
||||
while (c = this.read_next()!, c >= '0' && c <= '9')
|
||||
while (c = read_next(context)!, c.is_digit())
|
||||
{
|
||||
t.append(c);
|
||||
}
|
||||
@@ -103,113 +129,125 @@ fn JsonTokenType! JsonParser.lex_number(JsonParser* this, char c)
|
||||
if ((c | 32) == 'e')
|
||||
{
|
||||
t.append(c);
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
switch (c)
|
||||
{
|
||||
case '-':
|
||||
case '+':
|
||||
t.append(c);
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
}
|
||||
if (c < '0' || c > '9') return JsonParsingError.INVALID_NUMBER?;
|
||||
while (c >= '0' && c <= '9')
|
||||
if (!c.is_digit()) return JsonParsingError.INVALID_NUMBER?;
|
||||
while (c.is_digit())
|
||||
{
|
||||
t.append(c);
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
}
|
||||
}
|
||||
this.pushback();
|
||||
double! d = t.str().to_double() ?? JsonParsingError.INVALID_NUMBER?;
|
||||
this.last_number = d!;
|
||||
pushback(context, c);
|
||||
double! d = t.str_view().to_double() ?? JsonParsingError.INVALID_NUMBER?;
|
||||
context.last_number = d!;
|
||||
return NUMBER;
|
||||
};
|
||||
}
|
||||
|
||||
fn Object*! JsonParser.parse_map(JsonParser* this)
|
||||
fn Object*! parse_map(JsonContext* context) @local
|
||||
{
|
||||
Object* map = object::new_obj(this.allocator);
|
||||
JsonTokenType token = this.advance()!;
|
||||
Object* map = object::new_obj(context.allocator);
|
||||
defer catch map.free();
|
||||
JsonTokenType token = advance(context)!;
|
||||
|
||||
DString temp_key = dstring::new_with_capacity(32, this.allocator);
|
||||
defer temp_key.free();
|
||||
while (token != JsonTokenType.RBRACE)
|
||||
@stack_mem(256; Allocator mem)
|
||||
{
|
||||
if (token != JsonTokenType.STRING) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
DString string = this.last_string;
|
||||
if (map.has_key(string.str())) return JsonParsingError.DUPLICATE_MEMBERS?;
|
||||
// Copy the key to our temp holder. We do this to work around the issue
|
||||
// if the temp allocator should be used as the default allocator.
|
||||
temp_key.clear();
|
||||
temp_key.append(string);
|
||||
this.parse_expected(COLON)!;
|
||||
Object* element = this.parse_any()!;
|
||||
map.set(temp_key.str(), element);
|
||||
token = this.advance()!;
|
||||
if (token == JsonTokenType.COMMA)
|
||||
{
|
||||
token = this.advance()!;
|
||||
continue;
|
||||
}
|
||||
if (token != JsonTokenType.RBRACE) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
return map;
|
||||
DString temp_key = dstring::new_with_capacity(32, mem);
|
||||
while (token != JsonTokenType.RBRACE)
|
||||
{
|
||||
if (token != JsonTokenType.STRING) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
DString string = context.last_string;
|
||||
if (map.has_key(string.str_view())) return JsonParsingError.DUPLICATE_MEMBERS?;
|
||||
// Copy the key to our temp holder, since our
|
||||
// last_string may be used in parse_any
|
||||
temp_key.clear();
|
||||
temp_key.append(string);
|
||||
parse_expected(context, COLON)!;
|
||||
Object* element = parse_any(context)!;
|
||||
map.set(temp_key.str_view(), element);
|
||||
token = advance(context)!;
|
||||
if (token == JsonTokenType.COMMA)
|
||||
{
|
||||
token = advance(context)!;
|
||||
continue;
|
||||
}
|
||||
if (token != JsonTokenType.RBRACE) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
return map;
|
||||
};
|
||||
}
|
||||
|
||||
fn Object*! JsonParser.parse_array(JsonParser* this)
|
||||
fn Object*! parse_array(JsonContext* context) @local
|
||||
{
|
||||
Object* list = object::new_obj(this.allocator);
|
||||
Object* list = object::new_obj(context.allocator);
|
||||
defer catch list.free();
|
||||
JsonTokenType token = this.advance()!;
|
||||
JsonTokenType token = advance(context)!;
|
||||
while (token != JsonTokenType.RBRACKET)
|
||||
{
|
||||
Object* element = this.parse_from_token(token)!;
|
||||
list.append(element);
|
||||
token = this.advance()!;
|
||||
Object* element = parse_from_token(context, token)!;
|
||||
list.push(element);
|
||||
token = advance(context)!;
|
||||
if (token == JsonTokenType.COMMA)
|
||||
{
|
||||
token = this.advance()!;
|
||||
continue;
|
||||
token = advance(context)!;
|
||||
continue;
|
||||
}
|
||||
if (token != JsonTokenType.RBRACKET) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
fn void JsonParser.pushback(JsonParser* this)
|
||||
fn void pushback(JsonContext* context, char c) @local
|
||||
{
|
||||
if (!this.reached_end) this.stream.pushback_byte()!!;
|
||||
if (!context.reached_end)
|
||||
{
|
||||
assert(!context.pushed_back);
|
||||
context.pushed_back = true;
|
||||
context.current = c;
|
||||
}
|
||||
}
|
||||
|
||||
fn char! JsonParser.read_next(JsonParser* this)
|
||||
fn char! read_next(JsonContext* context) @local
|
||||
{
|
||||
if (this.reached_end) return '\0';
|
||||
char! c = this.stream.read_byte();
|
||||
if (context.reached_end) return '\0';
|
||||
if (context.pushed_back)
|
||||
{
|
||||
context.pushed_back = false;
|
||||
return context.current;
|
||||
}
|
||||
char! c = context.stream.read_byte();
|
||||
if (catch err = c)
|
||||
{
|
||||
case IoError.EOF:
|
||||
this.reached_end = true;
|
||||
context.reached_end = true;
|
||||
return '\0';
|
||||
default:
|
||||
return err?;
|
||||
}
|
||||
if (c == 0)
|
||||
{
|
||||
this.reached_end = true;
|
||||
context.reached_end = true;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
fn JsonTokenType! JsonParser.advance(JsonParser* this)
|
||||
fn JsonTokenType! advance(JsonContext* context) @local
|
||||
{
|
||||
char c;
|
||||
// Skip whitespace
|
||||
while WS: (c = this.read_next()!)
|
||||
while WS: (c = read_next(context)!)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\n':
|
||||
this.line++;
|
||||
context.line++;
|
||||
nextcase;
|
||||
case ' ':
|
||||
case '\t':
|
||||
@@ -217,24 +255,24 @@ fn JsonTokenType! JsonParser.advance(JsonParser* this)
|
||||
case '\v':
|
||||
continue;
|
||||
case '/':
|
||||
if (!this.skip_comments) break;
|
||||
c = this.read_next()!;
|
||||
if (!context.skip_comments) break;
|
||||
c = read_next(context)!;
|
||||
if (c != '*')
|
||||
{
|
||||
this.pushback();
|
||||
pushback(context, c);
|
||||
break WS;
|
||||
}
|
||||
while COMMENT: (1)
|
||||
while COMMENT: (true)
|
||||
{
|
||||
// Skip to */
|
||||
while (c = this.read_next()!)
|
||||
while (c = read_next(context)!)
|
||||
{
|
||||
if (c == '\n') this.line++;
|
||||
if (c == '\n') context.line++;
|
||||
if (c != '*') continue;
|
||||
// Skip through all the '*'
|
||||
while (c = this.read_next()!)
|
||||
while (c = read_next(context)!)
|
||||
{
|
||||
if (c == '\n') this.line++;
|
||||
if (c == '\n') context.line++;
|
||||
if (c != '*') break;
|
||||
}
|
||||
if (c == '/') break COMMENT;
|
||||
@@ -262,44 +300,44 @@ fn JsonTokenType! JsonParser.advance(JsonParser* this)
|
||||
case ',':
|
||||
return COMMA;
|
||||
case '"':
|
||||
return this.lex_string();
|
||||
return lex_string(context);
|
||||
case '-':
|
||||
case '0'..'9':
|
||||
return this.lex_number(c);
|
||||
return lex_number(context, c);
|
||||
case 't':
|
||||
this.match("rue")!;
|
||||
match(context, "rue")!;
|
||||
return TRUE;
|
||||
case 'f':
|
||||
this.match("alse")!;
|
||||
match(context, "alse")!;
|
||||
return FALSE;
|
||||
case 'n':
|
||||
this.match("ull")!;
|
||||
match(context, "ull")!;
|
||||
return NULL;
|
||||
default:
|
||||
return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
}
|
||||
|
||||
fn void! JsonParser.match(JsonParser* this, String str)
|
||||
fn void! match(JsonContext* context, String str) @local
|
||||
{
|
||||
foreach (c : str)
|
||||
{
|
||||
char l = this.read_next()!;
|
||||
char l = read_next(context)!;
|
||||
if (l != c) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
}
|
||||
|
||||
fn void! JsonParser.parse_expected(JsonParser* this, JsonTokenType token) @local
|
||||
fn void! parse_expected(JsonContext* context, JsonTokenType token) @local
|
||||
{
|
||||
if (this.advance()! != token) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
if (advance(context)! != token) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
|
||||
fn JsonTokenType! JsonParser.lex_string(JsonParser *this)
|
||||
fn JsonTokenType! lex_string(JsonContext* context)
|
||||
{
|
||||
this.last_string.clear();
|
||||
while LOOP: (1)
|
||||
context.last_string.clear();
|
||||
while LOOP: (true)
|
||||
{
|
||||
char c = this.read_next()!;
|
||||
char c = read_next(context)!;
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
@@ -311,10 +349,10 @@ fn JsonTokenType! JsonParser.lex_string(JsonParser *this)
|
||||
case '\\':
|
||||
break;
|
||||
default:
|
||||
this.last_string.append(c);
|
||||
context.last_string.append(c);
|
||||
continue;
|
||||
}
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
@@ -339,15 +377,16 @@ fn JsonTokenType! JsonParser.lex_string(JsonParser *this)
|
||||
uint val;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
if (!c.is_xdigit()) return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
|
||||
val = val << 4 + (c > '9' ? (c | 32) - 'a' + 10 : c - '0');
|
||||
}
|
||||
this.last_string.append_char32(val);
|
||||
context.last_string.append_char32(val);
|
||||
continue;
|
||||
default:
|
||||
return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
|
||||
}
|
||||
context.last_string.append(c);
|
||||
}
|
||||
return STRING;
|
||||
}
|
||||
|
||||
@@ -8,46 +8,46 @@ const uint ADLER_CONST @private = 65521;
|
||||
|
||||
struct Adler32
|
||||
{
|
||||
uint a;
|
||||
uint b;
|
||||
uint a;
|
||||
uint b;
|
||||
}
|
||||
|
||||
fn void Adler32.init(Adler32 *this)
|
||||
fn void Adler32.init(&self)
|
||||
{
|
||||
*this = { 1, 0 };
|
||||
*self = { 1, 0 };
|
||||
}
|
||||
|
||||
fn void Adler32.updatec(Adler32* this, char c)
|
||||
fn void Adler32.updatec(&self, char c)
|
||||
{
|
||||
this.a = (this.a + c) % ADLER_CONST;
|
||||
this.b = (this.b + this.a) % ADLER_CONST;
|
||||
self.a = (self.a + c) % ADLER_CONST;
|
||||
self.b = (self.b + self.a) % ADLER_CONST;
|
||||
}
|
||||
|
||||
fn void Adler32.update(Adler32* this, char[] data)
|
||||
fn void Adler32.update(&self, char[] data)
|
||||
{
|
||||
uint a = this.a;
|
||||
uint b = this.b;
|
||||
foreach (char x : data)
|
||||
{
|
||||
a = (a + x) % ADLER_CONST;
|
||||
b = (b + a) % ADLER_CONST;
|
||||
}
|
||||
*this = { a, b };
|
||||
uint a = self.a;
|
||||
uint b = self.b;
|
||||
foreach (char x : data)
|
||||
{
|
||||
a = (a + x) % ADLER_CONST;
|
||||
b = (b + a) % ADLER_CONST;
|
||||
}
|
||||
*self = { a, b };
|
||||
}
|
||||
|
||||
fn uint Adler32.final(Adler32* this)
|
||||
fn uint Adler32.final(&self)
|
||||
{
|
||||
return (this.b << 16) | this.a;
|
||||
return (self.b << 16) | self.a;
|
||||
}
|
||||
|
||||
fn uint encode(char[] data)
|
||||
{
|
||||
uint a = 1;
|
||||
uint b = 0;
|
||||
foreach (char x : data)
|
||||
{
|
||||
a = (a + x) % ADLER_CONST;
|
||||
b = (b + a) % ADLER_CONST;
|
||||
}
|
||||
return (b << 16) | a;
|
||||
uint a = 1;
|
||||
uint b = 0;
|
||||
foreach (char x : data)
|
||||
{
|
||||
a = (a + x) % ADLER_CONST;
|
||||
b = (b + a) % ADLER_CONST;
|
||||
}
|
||||
return (b << 16) | a;
|
||||
}
|
||||
@@ -5,41 +5,41 @@ module std::hash::crc32;
|
||||
|
||||
struct Crc32
|
||||
{
|
||||
uint result;
|
||||
uint result;
|
||||
}
|
||||
|
||||
fn void Crc32.init(Crc32* this, uint seed = 0)
|
||||
fn void Crc32.init(&self, uint seed = 0)
|
||||
{
|
||||
this.result = ~seed;
|
||||
self.result = ~seed;
|
||||
}
|
||||
|
||||
fn void Crc32.updatec(Crc32* this, char c)
|
||||
fn void Crc32.updatec(&self, char c)
|
||||
{
|
||||
this.result = (this.result >> 8) ^ CRC32_TABLE[(this.result ^ c) & 0xFF];
|
||||
self.result = (self.result >> 8) ^ CRC32_TABLE[(self.result ^ c) & 0xFF];
|
||||
}
|
||||
|
||||
fn void Crc32.update(Crc32* this, char[] data)
|
||||
fn void Crc32.update(&self, char[] data)
|
||||
{
|
||||
uint result = this.result;
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
|
||||
}
|
||||
this.result = result;
|
||||
uint result = self.result;
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
|
||||
}
|
||||
self.result = result;
|
||||
}
|
||||
|
||||
fn uint Crc32.final(Crc32* this)
|
||||
fn uint Crc32.final(&self)
|
||||
{
|
||||
return ~this.result;
|
||||
return ~self.result;
|
||||
}
|
||||
|
||||
fn uint encode(char[] data)
|
||||
{
|
||||
uint result = ~(uint)(0);
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
|
||||
}
|
||||
uint result = ~(uint)(0);
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
|
||||
}
|
||||
return ~result;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,107 +5,107 @@ module std::hash::crc64;
|
||||
|
||||
struct Crc64
|
||||
{
|
||||
ulong result;
|
||||
ulong result;
|
||||
}
|
||||
|
||||
fn void Crc64.init(Crc64* this, uint seed = 0)
|
||||
fn void Crc64.init(&self, uint seed = 0)
|
||||
{
|
||||
this.result = seed;
|
||||
self.result = seed;
|
||||
}
|
||||
|
||||
fn void Crc64.updatec(Crc64* this, char c)
|
||||
fn void Crc64.updatec(&self, char c)
|
||||
{
|
||||
this.result = (this.result << 8) ^ CRC64_TABLE[(char)((this.result >> 56) ^ c)];
|
||||
self.result = (self.result << 8) ^ CRC64_TABLE[(char)((self.result >> 56) ^ c)];
|
||||
}
|
||||
|
||||
fn void Crc64.update(Crc64* this, char[] data)
|
||||
fn void Crc64.update(&self, char[] data)
|
||||
{
|
||||
ulong result = this.result;
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
|
||||
}
|
||||
this.result = result;
|
||||
ulong result = self.result;
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
|
||||
}
|
||||
self.result = result;
|
||||
}
|
||||
|
||||
fn ulong Crc64.final(Crc64* this)
|
||||
fn ulong Crc64.final(&self)
|
||||
{
|
||||
return this.result;
|
||||
return self.result;
|
||||
}
|
||||
|
||||
fn ulong encode(char[] data)
|
||||
{
|
||||
ulong result = (ulong)(0);
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
|
||||
}
|
||||
ulong result = (ulong)(0);
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const ulong[256] CRC64_TABLE @private = {
|
||||
0x0000000000000000, 0x42f0e1eba9ea3693, 0x85e1c3d753d46d26, 0xc711223cfa3e5bb5,
|
||||
0x493366450e42ecdf, 0x0bc387aea7a8da4c, 0xccd2a5925d9681f9, 0x8e224479f47cb76a,
|
||||
0x9266cc8a1c85d9be, 0xd0962d61b56fef2d, 0x17870f5d4f51b498, 0x5577eeb6e6bb820b,
|
||||
0xdb55aacf12c73561, 0x99a54b24bb2d03f2, 0x5eb4691841135847, 0x1c4488f3e8f96ed4,
|
||||
0x663d78ff90e185ef, 0x24cd9914390bb37c, 0xe3dcbb28c335e8c9, 0xa12c5ac36adfde5a,
|
||||
0x2f0e1eba9ea36930, 0x6dfeff5137495fa3, 0xaaefdd6dcd770416, 0xe81f3c86649d3285,
|
||||
0xf45bb4758c645c51, 0xb6ab559e258e6ac2, 0x71ba77a2dfb03177, 0x334a9649765a07e4,
|
||||
0xbd68d2308226b08e, 0xff9833db2bcc861d, 0x388911e7d1f2dda8, 0x7a79f00c7818eb3b,
|
||||
0xcc7af1ff21c30bde, 0x8e8a101488293d4d, 0x499b3228721766f8, 0x0b6bd3c3dbfd506b,
|
||||
0x854997ba2f81e701, 0xc7b97651866bd192, 0x00a8546d7c558a27, 0x4258b586d5bfbcb4,
|
||||
0x5e1c3d753d46d260, 0x1cecdc9e94ace4f3, 0xdbfdfea26e92bf46, 0x990d1f49c77889d5,
|
||||
0x172f5b3033043ebf, 0x55dfbadb9aee082c, 0x92ce98e760d05399, 0xd03e790cc93a650a,
|
||||
0xaa478900b1228e31, 0xe8b768eb18c8b8a2, 0x2fa64ad7e2f6e317, 0x6d56ab3c4b1cd584,
|
||||
0xe374ef45bf6062ee, 0xa1840eae168a547d, 0x66952c92ecb40fc8, 0x2465cd79455e395b,
|
||||
0x3821458aada7578f, 0x7ad1a461044d611c, 0xbdc0865dfe733aa9, 0xff3067b657990c3a,
|
||||
0x711223cfa3e5bb50, 0x33e2c2240a0f8dc3, 0xf4f3e018f031d676, 0xb60301f359dbe0e5,
|
||||
0xda050215ea6c212f, 0x98f5e3fe438617bc, 0x5fe4c1c2b9b84c09, 0x1d14202910527a9a,
|
||||
0x93366450e42ecdf0, 0xd1c685bb4dc4fb63, 0x16d7a787b7faa0d6, 0x5427466c1e109645,
|
||||
0x4863ce9ff6e9f891, 0x0a932f745f03ce02, 0xcd820d48a53d95b7, 0x8f72eca30cd7a324,
|
||||
0x0150a8daf8ab144e, 0x43a04931514122dd, 0x84b16b0dab7f7968, 0xc6418ae602954ffb,
|
||||
0xbc387aea7a8da4c0, 0xfec89b01d3679253, 0x39d9b93d2959c9e6, 0x7b2958d680b3ff75,
|
||||
0xf50b1caf74cf481f, 0xb7fbfd44dd257e8c, 0x70eadf78271b2539, 0x321a3e938ef113aa,
|
||||
0x2e5eb66066087d7e, 0x6cae578bcfe24bed, 0xabbf75b735dc1058, 0xe94f945c9c3626cb,
|
||||
0x676dd025684a91a1, 0x259d31cec1a0a732, 0xe28c13f23b9efc87, 0xa07cf2199274ca14,
|
||||
0x167ff3eacbaf2af1, 0x548f120162451c62, 0x939e303d987b47d7, 0xd16ed1d631917144,
|
||||
0x5f4c95afc5edc62e, 0x1dbc74446c07f0bd, 0xdaad56789639ab08, 0x985db7933fd39d9b,
|
||||
0x84193f60d72af34f, 0xc6e9de8b7ec0c5dc, 0x01f8fcb784fe9e69, 0x43081d5c2d14a8fa,
|
||||
0xcd2a5925d9681f90, 0x8fdab8ce70822903, 0x48cb9af28abc72b6, 0x0a3b7b1923564425,
|
||||
0x70428b155b4eaf1e, 0x32b26afef2a4998d, 0xf5a348c2089ac238, 0xb753a929a170f4ab,
|
||||
0x3971ed50550c43c1, 0x7b810cbbfce67552, 0xbc902e8706d82ee7, 0xfe60cf6caf321874,
|
||||
0xe224479f47cb76a0, 0xa0d4a674ee214033, 0x67c58448141f1b86, 0x253565a3bdf52d15,
|
||||
0xab1721da49899a7f, 0xe9e7c031e063acec, 0x2ef6e20d1a5df759, 0x6c0603e6b3b7c1ca,
|
||||
0xf6fae5c07d3274cd, 0xb40a042bd4d8425e, 0x731b26172ee619eb, 0x31ebc7fc870c2f78,
|
||||
0xbfc9838573709812, 0xfd39626eda9aae81, 0x3a28405220a4f534, 0x78d8a1b9894ec3a7,
|
||||
0x649c294a61b7ad73, 0x266cc8a1c85d9be0, 0xe17dea9d3263c055, 0xa38d0b769b89f6c6,
|
||||
0x2daf4f0f6ff541ac, 0x6f5faee4c61f773f, 0xa84e8cd83c212c8a, 0xeabe6d3395cb1a19,
|
||||
0x90c79d3fedd3f122, 0xd2377cd44439c7b1, 0x15265ee8be079c04, 0x57d6bf0317edaa97,
|
||||
0xd9f4fb7ae3911dfd, 0x9b041a914a7b2b6e, 0x5c1538adb04570db, 0x1ee5d94619af4648,
|
||||
0x02a151b5f156289c, 0x4051b05e58bc1e0f, 0x87409262a28245ba, 0xc5b073890b687329,
|
||||
0x4b9237f0ff14c443, 0x0962d61b56fef2d0, 0xce73f427acc0a965, 0x8c8315cc052a9ff6,
|
||||
0x3a80143f5cf17f13, 0x7870f5d4f51b4980, 0xbf61d7e80f251235, 0xfd913603a6cf24a6,
|
||||
0x73b3727a52b393cc, 0x31439391fb59a55f, 0xf652b1ad0167feea, 0xb4a25046a88dc879,
|
||||
0xa8e6d8b54074a6ad, 0xea16395ee99e903e, 0x2d071b6213a0cb8b, 0x6ff7fa89ba4afd18,
|
||||
0xe1d5bef04e364a72, 0xa3255f1be7dc7ce1, 0x64347d271de22754, 0x26c49cccb40811c7,
|
||||
0x5cbd6cc0cc10fafc, 0x1e4d8d2b65facc6f, 0xd95caf179fc497da, 0x9bac4efc362ea149,
|
||||
0x158e0a85c2521623, 0x577eeb6e6bb820b0, 0x906fc95291867b05, 0xd29f28b9386c4d96,
|
||||
0xcedba04ad0952342, 0x8c2b41a1797f15d1, 0x4b3a639d83414e64, 0x09ca82762aab78f7,
|
||||
0x87e8c60fded7cf9d, 0xc51827e4773df90e, 0x020905d88d03a2bb, 0x40f9e43324e99428,
|
||||
0x2cffe7d5975e55e2, 0x6e0f063e3eb46371, 0xa91e2402c48a38c4, 0xebeec5e96d600e57,
|
||||
0x65cc8190991cb93d, 0x273c607b30f68fae, 0xe02d4247cac8d41b, 0xa2dda3ac6322e288,
|
||||
0xbe992b5f8bdb8c5c, 0xfc69cab42231bacf, 0x3b78e888d80fe17a, 0x7988096371e5d7e9,
|
||||
0xf7aa4d1a85996083, 0xb55aacf12c735610, 0x724b8ecdd64d0da5, 0x30bb6f267fa73b36,
|
||||
0x4ac29f2a07bfd00d, 0x08327ec1ae55e69e, 0xcf235cfd546bbd2b, 0x8dd3bd16fd818bb8,
|
||||
0x03f1f96f09fd3cd2, 0x41011884a0170a41, 0x86103ab85a2951f4, 0xc4e0db53f3c36767,
|
||||
0xd8a453a01b3a09b3, 0x9a54b24bb2d03f20, 0x5d45907748ee6495, 0x1fb5719ce1045206,
|
||||
0x919735e51578e56c, 0xd367d40ebc92d3ff, 0x1476f63246ac884a, 0x568617d9ef46bed9,
|
||||
0xe085162ab69d5e3c, 0xa275f7c11f7768af, 0x6564d5fde549331a, 0x279434164ca30589,
|
||||
0xa9b6706fb8dfb2e3, 0xeb46918411358470, 0x2c57b3b8eb0bdfc5, 0x6ea7525342e1e956,
|
||||
0x72e3daa0aa188782, 0x30133b4b03f2b111, 0xf7021977f9cceaa4, 0xb5f2f89c5026dc37,
|
||||
0x3bd0bce5a45a6b5d, 0x79205d0e0db05dce, 0xbe317f32f78e067b, 0xfcc19ed95e6430e8,
|
||||
0x86b86ed5267cdbd3, 0xc4488f3e8f96ed40, 0x0359ad0275a8b6f5, 0x41a94ce9dc428066,
|
||||
0xcf8b0890283e370c, 0x8d7be97b81d4019f, 0x4a6acb477bea5a2a, 0x089a2aacd2006cb9,
|
||||
0x14dea25f3af9026d, 0x562e43b4931334fe, 0x913f6188692d6f4b, 0xd3cf8063c0c759d8,
|
||||
0x5dedc41a34bbeeb2, 0x1f1d25f19d51d821, 0xd80c07cd676f8394, 0x9afce626ce85b507,
|
||||
0x0000000000000000, 0x42f0e1eba9ea3693, 0x85e1c3d753d46d26, 0xc711223cfa3e5bb5,
|
||||
0x493366450e42ecdf, 0x0bc387aea7a8da4c, 0xccd2a5925d9681f9, 0x8e224479f47cb76a,
|
||||
0x9266cc8a1c85d9be, 0xd0962d61b56fef2d, 0x17870f5d4f51b498, 0x5577eeb6e6bb820b,
|
||||
0xdb55aacf12c73561, 0x99a54b24bb2d03f2, 0x5eb4691841135847, 0x1c4488f3e8f96ed4,
|
||||
0x663d78ff90e185ef, 0x24cd9914390bb37c, 0xe3dcbb28c335e8c9, 0xa12c5ac36adfde5a,
|
||||
0x2f0e1eba9ea36930, 0x6dfeff5137495fa3, 0xaaefdd6dcd770416, 0xe81f3c86649d3285,
|
||||
0xf45bb4758c645c51, 0xb6ab559e258e6ac2, 0x71ba77a2dfb03177, 0x334a9649765a07e4,
|
||||
0xbd68d2308226b08e, 0xff9833db2bcc861d, 0x388911e7d1f2dda8, 0x7a79f00c7818eb3b,
|
||||
0xcc7af1ff21c30bde, 0x8e8a101488293d4d, 0x499b3228721766f8, 0x0b6bd3c3dbfd506b,
|
||||
0x854997ba2f81e701, 0xc7b97651866bd192, 0x00a8546d7c558a27, 0x4258b586d5bfbcb4,
|
||||
0x5e1c3d753d46d260, 0x1cecdc9e94ace4f3, 0xdbfdfea26e92bf46, 0x990d1f49c77889d5,
|
||||
0x172f5b3033043ebf, 0x55dfbadb9aee082c, 0x92ce98e760d05399, 0xd03e790cc93a650a,
|
||||
0xaa478900b1228e31, 0xe8b768eb18c8b8a2, 0x2fa64ad7e2f6e317, 0x6d56ab3c4b1cd584,
|
||||
0xe374ef45bf6062ee, 0xa1840eae168a547d, 0x66952c92ecb40fc8, 0x2465cd79455e395b,
|
||||
0x3821458aada7578f, 0x7ad1a461044d611c, 0xbdc0865dfe733aa9, 0xff3067b657990c3a,
|
||||
0x711223cfa3e5bb50, 0x33e2c2240a0f8dc3, 0xf4f3e018f031d676, 0xb60301f359dbe0e5,
|
||||
0xda050215ea6c212f, 0x98f5e3fe438617bc, 0x5fe4c1c2b9b84c09, 0x1d14202910527a9a,
|
||||
0x93366450e42ecdf0, 0xd1c685bb4dc4fb63, 0x16d7a787b7faa0d6, 0x5427466c1e109645,
|
||||
0x4863ce9ff6e9f891, 0x0a932f745f03ce02, 0xcd820d48a53d95b7, 0x8f72eca30cd7a324,
|
||||
0x0150a8daf8ab144e, 0x43a04931514122dd, 0x84b16b0dab7f7968, 0xc6418ae602954ffb,
|
||||
0xbc387aea7a8da4c0, 0xfec89b01d3679253, 0x39d9b93d2959c9e6, 0x7b2958d680b3ff75,
|
||||
0xf50b1caf74cf481f, 0xb7fbfd44dd257e8c, 0x70eadf78271b2539, 0x321a3e938ef113aa,
|
||||
0x2e5eb66066087d7e, 0x6cae578bcfe24bed, 0xabbf75b735dc1058, 0xe94f945c9c3626cb,
|
||||
0x676dd025684a91a1, 0x259d31cec1a0a732, 0xe28c13f23b9efc87, 0xa07cf2199274ca14,
|
||||
0x167ff3eacbaf2af1, 0x548f120162451c62, 0x939e303d987b47d7, 0xd16ed1d631917144,
|
||||
0x5f4c95afc5edc62e, 0x1dbc74446c07f0bd, 0xdaad56789639ab08, 0x985db7933fd39d9b,
|
||||
0x84193f60d72af34f, 0xc6e9de8b7ec0c5dc, 0x01f8fcb784fe9e69, 0x43081d5c2d14a8fa,
|
||||
0xcd2a5925d9681f90, 0x8fdab8ce70822903, 0x48cb9af28abc72b6, 0x0a3b7b1923564425,
|
||||
0x70428b155b4eaf1e, 0x32b26afef2a4998d, 0xf5a348c2089ac238, 0xb753a929a170f4ab,
|
||||
0x3971ed50550c43c1, 0x7b810cbbfce67552, 0xbc902e8706d82ee7, 0xfe60cf6caf321874,
|
||||
0xe224479f47cb76a0, 0xa0d4a674ee214033, 0x67c58448141f1b86, 0x253565a3bdf52d15,
|
||||
0xab1721da49899a7f, 0xe9e7c031e063acec, 0x2ef6e20d1a5df759, 0x6c0603e6b3b7c1ca,
|
||||
0xf6fae5c07d3274cd, 0xb40a042bd4d8425e, 0x731b26172ee619eb, 0x31ebc7fc870c2f78,
|
||||
0xbfc9838573709812, 0xfd39626eda9aae81, 0x3a28405220a4f534, 0x78d8a1b9894ec3a7,
|
||||
0x649c294a61b7ad73, 0x266cc8a1c85d9be0, 0xe17dea9d3263c055, 0xa38d0b769b89f6c6,
|
||||
0x2daf4f0f6ff541ac, 0x6f5faee4c61f773f, 0xa84e8cd83c212c8a, 0xeabe6d3395cb1a19,
|
||||
0x90c79d3fedd3f122, 0xd2377cd44439c7b1, 0x15265ee8be079c04, 0x57d6bf0317edaa97,
|
||||
0xd9f4fb7ae3911dfd, 0x9b041a914a7b2b6e, 0x5c1538adb04570db, 0x1ee5d94619af4648,
|
||||
0x02a151b5f156289c, 0x4051b05e58bc1e0f, 0x87409262a28245ba, 0xc5b073890b687329,
|
||||
0x4b9237f0ff14c443, 0x0962d61b56fef2d0, 0xce73f427acc0a965, 0x8c8315cc052a9ff6,
|
||||
0x3a80143f5cf17f13, 0x7870f5d4f51b4980, 0xbf61d7e80f251235, 0xfd913603a6cf24a6,
|
||||
0x73b3727a52b393cc, 0x31439391fb59a55f, 0xf652b1ad0167feea, 0xb4a25046a88dc879,
|
||||
0xa8e6d8b54074a6ad, 0xea16395ee99e903e, 0x2d071b6213a0cb8b, 0x6ff7fa89ba4afd18,
|
||||
0xe1d5bef04e364a72, 0xa3255f1be7dc7ce1, 0x64347d271de22754, 0x26c49cccb40811c7,
|
||||
0x5cbd6cc0cc10fafc, 0x1e4d8d2b65facc6f, 0xd95caf179fc497da, 0x9bac4efc362ea149,
|
||||
0x158e0a85c2521623, 0x577eeb6e6bb820b0, 0x906fc95291867b05, 0xd29f28b9386c4d96,
|
||||
0xcedba04ad0952342, 0x8c2b41a1797f15d1, 0x4b3a639d83414e64, 0x09ca82762aab78f7,
|
||||
0x87e8c60fded7cf9d, 0xc51827e4773df90e, 0x020905d88d03a2bb, 0x40f9e43324e99428,
|
||||
0x2cffe7d5975e55e2, 0x6e0f063e3eb46371, 0xa91e2402c48a38c4, 0xebeec5e96d600e57,
|
||||
0x65cc8190991cb93d, 0x273c607b30f68fae, 0xe02d4247cac8d41b, 0xa2dda3ac6322e288,
|
||||
0xbe992b5f8bdb8c5c, 0xfc69cab42231bacf, 0x3b78e888d80fe17a, 0x7988096371e5d7e9,
|
||||
0xf7aa4d1a85996083, 0xb55aacf12c735610, 0x724b8ecdd64d0da5, 0x30bb6f267fa73b36,
|
||||
0x4ac29f2a07bfd00d, 0x08327ec1ae55e69e, 0xcf235cfd546bbd2b, 0x8dd3bd16fd818bb8,
|
||||
0x03f1f96f09fd3cd2, 0x41011884a0170a41, 0x86103ab85a2951f4, 0xc4e0db53f3c36767,
|
||||
0xd8a453a01b3a09b3, 0x9a54b24bb2d03f20, 0x5d45907748ee6495, 0x1fb5719ce1045206,
|
||||
0x919735e51578e56c, 0xd367d40ebc92d3ff, 0x1476f63246ac884a, 0x568617d9ef46bed9,
|
||||
0xe085162ab69d5e3c, 0xa275f7c11f7768af, 0x6564d5fde549331a, 0x279434164ca30589,
|
||||
0xa9b6706fb8dfb2e3, 0xeb46918411358470, 0x2c57b3b8eb0bdfc5, 0x6ea7525342e1e956,
|
||||
0x72e3daa0aa188782, 0x30133b4b03f2b111, 0xf7021977f9cceaa4, 0xb5f2f89c5026dc37,
|
||||
0x3bd0bce5a45a6b5d, 0x79205d0e0db05dce, 0xbe317f32f78e067b, 0xfcc19ed95e6430e8,
|
||||
0x86b86ed5267cdbd3, 0xc4488f3e8f96ed40, 0x0359ad0275a8b6f5, 0x41a94ce9dc428066,
|
||||
0xcf8b0890283e370c, 0x8d7be97b81d4019f, 0x4a6acb477bea5a2a, 0x089a2aacd2006cb9,
|
||||
0x14dea25f3af9026d, 0x562e43b4931334fe, 0x913f6188692d6f4b, 0xd3cf8063c0c759d8,
|
||||
0x5dedc41a34bbeeb2, 0x1f1d25f19d51d821, 0xd80c07cd676f8394, 0x9afce626ce85b507,
|
||||
};
|
||||
|
||||
@@ -3,39 +3,39 @@
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::hash::fnv32a;
|
||||
|
||||
def Fnv32a = distinct uint;
|
||||
distinct Fnv32a = uint;
|
||||
|
||||
const FNV32A_START @private = 0x811c9dc5;
|
||||
const FNV32A_MUL @private = 0x01000193;
|
||||
|
||||
macro void @update(uint &h, char x) @private => h = (h * FNV32A_MUL) ^ x;
|
||||
macro void @update(&h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV32A_MUL;
|
||||
|
||||
fn void Fnv32a.init(Fnv32a* this)
|
||||
fn void Fnv32a.init(&self)
|
||||
{
|
||||
*this = FNV32A_START;
|
||||
*self = FNV32A_START;
|
||||
}
|
||||
|
||||
fn void Fnv32a.update(Fnv32a* this, char[] data)
|
||||
fn void Fnv32a.update(&self, char[] data)
|
||||
{
|
||||
uint h = (uint)*this;
|
||||
Fnv32a h = *self;
|
||||
foreach (char x : data)
|
||||
{
|
||||
@update(h, x);
|
||||
}
|
||||
*this = (Fnv32a)h;
|
||||
{
|
||||
@update(h, x);
|
||||
}
|
||||
*self = h;
|
||||
}
|
||||
|
||||
macro void Fnv32a.update_char(Fnv32a* this, char c)
|
||||
macro void Fnv32a.update_char(&self, char c)
|
||||
{
|
||||
@update(*this, x);
|
||||
@update(*self, c);
|
||||
}
|
||||
|
||||
fn uint encode(char[] data)
|
||||
{
|
||||
uint h = FNV32A_START;
|
||||
foreach (char x : data)
|
||||
{
|
||||
{
|
||||
@update(h, x);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
41
lib/std/hash/fnv64a.c3
Normal file
41
lib/std/hash/fnv64a.c3
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::hash::fnv64a;
|
||||
|
||||
distinct Fnv64a = ulong;
|
||||
|
||||
const FNV64A_START @private = 0xcbf29ce484222325;
|
||||
const FNV64A_MUL @private = 0x00000100000001b3;
|
||||
|
||||
macro void @update(&h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV64A_MUL;
|
||||
|
||||
fn void Fnv64a.init(&self)
|
||||
{
|
||||
*self = FNV64A_START;
|
||||
}
|
||||
|
||||
fn void Fnv64a.update(&self, char[] data)
|
||||
{
|
||||
Fnv64a h = *self;
|
||||
foreach (char x : data)
|
||||
{
|
||||
@update(h, x);
|
||||
}
|
||||
*self = h;
|
||||
}
|
||||
|
||||
macro void Fnv64a.update_char(&self, char c)
|
||||
{
|
||||
@update(*self, c);
|
||||
}
|
||||
|
||||
fn ulong encode(char[] data)
|
||||
{
|
||||
ulong h = FNV64A_START;
|
||||
foreach (char x : data)
|
||||
{
|
||||
@update(h, x);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
107
lib/std/hash/hmac.c3
Normal file
107
lib/std/hash/hmac.c3
Normal file
@@ -0,0 +1,107 @@
|
||||
module std::hash::hmac(<HashAlg, HASH_BYTES, BLOCK_BYTES>);
|
||||
import std::crypto;
|
||||
|
||||
struct Hmac
|
||||
{
|
||||
HashAlg a, b;
|
||||
}
|
||||
|
||||
fn char[HASH_BYTES] hash(char[] key, char[] message)
|
||||
{
|
||||
Hmac hmac @noinit;
|
||||
hmac.init(key);
|
||||
hmac.update(message);
|
||||
return hmac.final();
|
||||
}
|
||||
|
||||
<*
|
||||
@require output.len > 0 "Output must be greater than zero"
|
||||
@require output.len < int.max / HASH_BYTES "Output is too large"
|
||||
*>
|
||||
fn void pbkdf2(char[] pw, char[] salt, uint iterations, char[] output)
|
||||
{
|
||||
usz l = output.len / HASH_BYTES;
|
||||
usz r = output.len % HASH_BYTES;
|
||||
|
||||
Hmac hmac;
|
||||
hmac.init(pw);
|
||||
|
||||
char[] dst_curr = output;
|
||||
for (usz i = 1; i <= l; i++)
|
||||
{
|
||||
@derive(&hmac, salt, iterations, i, dst_curr[:HASH_BYTES]);
|
||||
dst_curr = dst_curr[HASH_BYTES..];
|
||||
}
|
||||
|
||||
if (r > 0)
|
||||
{
|
||||
char[HASH_BYTES] tmp;
|
||||
@derive(&hmac, salt, iterations, l + 1, &tmp);
|
||||
dst_curr[..] = tmp[:dst_curr.len];
|
||||
mem::zero_volatile(&tmp);
|
||||
}
|
||||
}
|
||||
|
||||
fn void Hmac.init(&self, char[] key)
|
||||
{
|
||||
char[BLOCK_BYTES] buffer;
|
||||
if (key.len > BLOCK_BYTES)
|
||||
{
|
||||
self.a.init();
|
||||
self.a.update(key);
|
||||
buffer[:HASH_BYTES] = self.a.final()[..];
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[:key.len] = key[..];
|
||||
}
|
||||
|
||||
foreach (&b : buffer) *b ^= IPAD;
|
||||
|
||||
self.a.init();
|
||||
self.a.update(&buffer);
|
||||
|
||||
foreach (&b : buffer) *b ^= IPAD ^ OPAD;
|
||||
|
||||
self.b.init();
|
||||
self.b.update(&buffer);
|
||||
|
||||
mem::zero_volatile(&buffer);
|
||||
}
|
||||
|
||||
fn void Hmac.update(&self, char[] data)
|
||||
{
|
||||
self.a.update(data);
|
||||
}
|
||||
|
||||
fn char[HASH_BYTES] Hmac.final(&self)
|
||||
{
|
||||
self.b.update(&&self.a.final());
|
||||
return self.b.final();
|
||||
}
|
||||
|
||||
const IPAD @private = 0x36;
|
||||
const OPAD @private = 0x5C;
|
||||
|
||||
macro @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[] out)
|
||||
{
|
||||
assert(out.len == HASH_BYTES);
|
||||
char[HASH_BYTES] tmp @noinit;
|
||||
defer mem::zero_volatile(&tmp);
|
||||
Hmac hmac = *hmac_start;
|
||||
hmac.update(salt);
|
||||
UIntBE be = { (uint)index };
|
||||
hmac.update(&&bitcast(be, char[4]));
|
||||
tmp = hmac.final();
|
||||
out[..] = tmp;
|
||||
for (int it = 1; it < iterations; it++)
|
||||
{
|
||||
hmac = *hmac_start;
|
||||
hmac.update(&tmp);
|
||||
tmp = hmac.final();
|
||||
foreach (i, v : tmp)
|
||||
{
|
||||
out[i] ^= v;
|
||||
}
|
||||
}
|
||||
}
|
||||
225
lib/std/hash/md5.c3
Normal file
225
lib/std/hash/md5.c3
Normal file
@@ -0,0 +1,225 @@
|
||||
module std::hash::md5;
|
||||
import std::hash::hmac;
|
||||
import std::bits;
|
||||
|
||||
const BLOCK_BYTES = 64;
|
||||
const HASH_BYTES = 16;
|
||||
|
||||
struct Md5
|
||||
{
|
||||
uint lo, hi;
|
||||
uint a, b, c, d;
|
||||
char[64] buffer;
|
||||
uint[16] block;
|
||||
}
|
||||
|
||||
def HmacMd5 = Hmac(<Md5, HASH_BYTES, BLOCK_BYTES>);
|
||||
def hmac = hmac::hash(<Md5, HASH_BYTES, BLOCK_BYTES>);
|
||||
def pbkdf2 = hmac::pbkdf2(<Md5, HASH_BYTES, BLOCK_BYTES>);
|
||||
|
||||
fn char[HASH_BYTES] hash(char[] data)
|
||||
{
|
||||
Md5 md5;
|
||||
md5.init();
|
||||
md5.update(data);
|
||||
return md5.final();
|
||||
}
|
||||
|
||||
fn void Md5.init(&self)
|
||||
{
|
||||
self.a = 0x67452301;
|
||||
self.b = 0xefcdab89;
|
||||
self.c = 0x98badcfe;
|
||||
self.d = 0x10325476;
|
||||
|
||||
self.lo = 0;
|
||||
self.hi = 0;
|
||||
}
|
||||
|
||||
|
||||
fn void Md5.update(&ctx, char[] data)
|
||||
{
|
||||
uint saved_lo = ctx.lo;
|
||||
if ((ctx.lo = (saved_lo + data.len) & 0x1fffffff) < saved_lo) ctx.hi++;
|
||||
ctx.hi += data.len >> 29;
|
||||
|
||||
usz used = (usz)saved_lo & 0x3f;
|
||||
|
||||
if (used)
|
||||
{
|
||||
usz available = 64 - used;
|
||||
|
||||
if (data.len < available)
|
||||
{
|
||||
ctx.buffer[used:data.len] = data[..];
|
||||
return;
|
||||
}
|
||||
ctx.buffer[used:available] = data[:available];
|
||||
data = data[available..];
|
||||
body(ctx, &ctx.buffer, 64);
|
||||
}
|
||||
|
||||
if (data.len >= 64)
|
||||
{
|
||||
data = body(ctx, data, data.len & ~(usz)0x3f)[:data.len & 0x3f];
|
||||
}
|
||||
ctx.buffer[:data.len] = data[..];
|
||||
}
|
||||
|
||||
fn char[HASH_BYTES] Md5.final(&ctx)
|
||||
{
|
||||
usz used = (usz)ctx.lo & 0x3f;
|
||||
ctx.buffer[used++] = 0x80;
|
||||
|
||||
usz available = 64 - used;
|
||||
|
||||
if (available < 8)
|
||||
{
|
||||
ctx.buffer[used:available] = 0;
|
||||
body(ctx, &ctx.buffer, 64);
|
||||
used = 0;
|
||||
available = 64;
|
||||
}
|
||||
ctx.buffer[used:available - 8] = 0;
|
||||
|
||||
ctx.lo <<= 3;
|
||||
ctx.buffer[56:4] = bitcast(ctx.lo, char[4])[..];
|
||||
ctx.buffer[60:4] = bitcast(ctx.hi, char[4])[..];
|
||||
|
||||
body(ctx, &ctx.buffer, 64);
|
||||
|
||||
char[16] res @noinit;
|
||||
res[0:4] = bitcast(ctx.a, char[4]);
|
||||
res[4:4] = bitcast(ctx.b, char[4]);
|
||||
res[8:4] = bitcast(ctx.c, char[4]);
|
||||
res[12:4] = bitcast(ctx.d, char[4]);
|
||||
*ctx = {};
|
||||
return res;
|
||||
}
|
||||
|
||||
module std::hash::md5 @private;
|
||||
|
||||
// Implementation
|
||||
macro @f(x, y, z) => z ^ (x & (y ^ z));
|
||||
macro @g(x, y, z) => y ^ (z & (x ^ y));
|
||||
macro @h(x, y, z) => (x ^ y) ^ z;
|
||||
macro @h2(x, y, z) => x ^ (y ^ z);
|
||||
macro @i(x, y, z) => y ^ (x | ~z);
|
||||
|
||||
macro @step(#f, &a, b, c, d, ptr, n, t, s)
|
||||
{
|
||||
*a += #f(b, c, d) + *(uint *)&ptr[n * 4] + t;
|
||||
*a = (*a << s) | ((*a & 0xffffffff) >> (32 - s));
|
||||
*a += b;
|
||||
}
|
||||
|
||||
|
||||
fn char* body(Md5* ctx, void* data, usz size)
|
||||
{
|
||||
char* ptr;
|
||||
uint a, b, c, d;
|
||||
uint saved_a, saved_b, saved_c, saved_d;
|
||||
ptr = data;
|
||||
a = ctx.a;
|
||||
b = ctx.b;
|
||||
c = ctx.c;
|
||||
d = ctx.d;
|
||||
|
||||
do
|
||||
{
|
||||
saved_a = a;
|
||||
saved_b = b;
|
||||
saved_c = c;
|
||||
saved_d = d;
|
||||
|
||||
/* Round 1 */
|
||||
@step(@f, a, b, c, d, ptr, 0, 0xd76aa478, 7) ;
|
||||
@step(@f, d, a, b, c, ptr, 1, 0xe8c7b756, 12) ;
|
||||
@step(@f, c, d, a, b, ptr, 2, 0x242070db, 17) ;
|
||||
@step(@f, b, c, d, a, ptr, 3, 0xc1bdceee, 22) ;
|
||||
@step(@f, a, b, c, d, ptr, 4, 0xf57c0faf, 7) ;
|
||||
@step(@f, d, a, b, c, ptr, 5, 0x4787c62a, 12) ;
|
||||
@step(@f, c, d, a, b, ptr, 6, 0xa8304613, 17) ;
|
||||
@step(@f, b, c, d, a, ptr, 7, 0xfd469501, 22) ;
|
||||
@step(@f, a, b, c, d, ptr, 8, 0x698098d8, 7) ;
|
||||
@step(@f, d, a, b, c, ptr, 9, 0x8b44f7af, 12) ;
|
||||
@step(@f, c, d, a, b, ptr, 10, 0xffff5bb1, 17);
|
||||
@step(@f, b, c, d, a, ptr, 11, 0x895cd7be, 22);
|
||||
@step(@f, a, b, c, d, ptr, 12, 0x6b901122, 7) ;
|
||||
@step(@f, d, a, b, c, ptr, 13, 0xfd987193, 12);
|
||||
@step(@f, c, d, a, b, ptr, 14, 0xa679438e, 17);
|
||||
@step(@f, b, c, d, a, ptr, 15, 0x49b40821, 22);
|
||||
|
||||
/* Round 2 */
|
||||
@step(@g, a, b, c, d, ptr, 1, 0xf61e2562, 5) ;
|
||||
@step(@g, d, a, b, c, ptr, 6, 0xc040b340, 9) ;
|
||||
@step(@g, c, d, a, b, ptr, 11, 0x265e5a51, 14);
|
||||
@step(@g, b, c, d, a, ptr, 0, 0xe9b6c7aa, 20) ;
|
||||
@step(@g, a, b, c, d, ptr, 5, 0xd62f105d, 5) ;
|
||||
@step(@g, d, a, b, c, ptr, 10, 0x02441453, 9) ;
|
||||
@step(@g, c, d, a, b, ptr, 15, 0xd8a1e681, 14);
|
||||
@step(@g, b, c, d, a, ptr, 4, 0xe7d3fbc8, 20) ;
|
||||
@step(@g, a, b, c, d, ptr, 9, 0x21e1cde6, 5) ;
|
||||
@step(@g, d, a, b, c, ptr, 14, 0xc33707d6, 9) ;
|
||||
@step(@g, c, d, a, b, ptr, 3, 0xf4d50d87, 14) ;
|
||||
@step(@g, b, c, d, a, ptr, 8, 0x455a14ed, 20) ;
|
||||
@step(@g, a, b, c, d, ptr, 13, 0xa9e3e905, 5) ;
|
||||
@step(@g, d, a, b, c, ptr, 2, 0xfcefa3f8, 9) ;
|
||||
@step(@g, c, d, a, b, ptr, 7, 0x676f02d9, 14) ;
|
||||
@step(@g, b, c, d, a, ptr, 12, 0x8d2a4c8a, 20);
|
||||
|
||||
/* Round 3 */
|
||||
@step(@h, a, b, c, d, ptr, 5, 0xfffa3942, 4);
|
||||
@step(@h2, d, a, b, c, ptr, 8, 0x8771f681, 11);
|
||||
@step(@h, c, d, a, b, ptr, 11, 0x6d9d6122, 16);
|
||||
@step(@h2, b, c, d, a, ptr, 14, 0xfde5380c, 23);
|
||||
@step(@h, a, b, c, d, ptr, 1, 0xa4beea44, 4);
|
||||
@step(@h2, d, a, b, c, ptr, 4, 0x4bdecfa9, 11);
|
||||
@step(@h, c, d, a, b, ptr, 7, 0xf6bb4b60, 16);
|
||||
@step(@h2, b, c, d, a, ptr, 10, 0xbebfbc70, 23);
|
||||
@step(@h, a, b, c, d, ptr, 13, 0x289b7ec6, 4) ;
|
||||
@step(@h2, d, a, b, c, ptr, 0, 0xeaa127fa, 11) ;
|
||||
@step(@h, c, d, a, b, ptr, 3, 0xd4ef3085, 16) ;
|
||||
@step(@h2, b, c, d, a, ptr, 6, 0x04881d05, 23) ;
|
||||
@step(@h, a, b, c, d, ptr, 9, 0xd9d4d039, 4) ;
|
||||
@step(@h2, d, a, b, c, ptr, 12, 0xe6db99e5, 11) ;
|
||||
@step(@h, c, d, a, b, ptr, 15, 0x1fa27cf8, 16) ;
|
||||
@step(@h2, b, c, d, a, ptr, 2, 0xc4ac5665, 23) ;
|
||||
|
||||
/* Round 4 */
|
||||
@step(@i, a, b, c, d, ptr, 0, 0xf4292244, 6) ;
|
||||
@step(@i, d, a, b, c, ptr, 7, 0x432aff97, 10) ;
|
||||
@step(@i, c, d, a, b, ptr, 14, 0xab9423a7, 15) ;
|
||||
@step(@i, b, c, d, a, ptr, 5, 0xfc93a039, 21) ;
|
||||
@step(@i, a, b, c, d, ptr, 12, 0x655b59c3, 6) ;
|
||||
@step(@i, d, a, b, c, ptr, 3, 0x8f0ccc92, 10) ;
|
||||
@step(@i, c, d, a, b, ptr, 10, 0xffeff47d, 15) ;
|
||||
@step(@i, b, c, d, a, ptr, 1, 0x85845dd1, 21) ;
|
||||
@step(@i, a, b, c, d, ptr, 8, 0x6fa87e4f, 6) ;
|
||||
@step(@i, d, a, b, c, ptr, 15, 0xfe2ce6e0, 10) ;
|
||||
@step(@i, c, d, a, b, ptr, 6, 0xa3014314, 15) ;
|
||||
@step(@i, b, c, d, a, ptr, 13, 0x4e0811a1, 21) ;
|
||||
@step(@i, a, b, c, d, ptr, 4, 0xf7537e82, 6) ;
|
||||
@step(@i, d, a, b, c, ptr, 11, 0xbd3af235, 10) ;
|
||||
@step(@i, c, d, a, b, ptr, 2, 0x2ad7d2bb, 15) ;
|
||||
@step(@i, b, c, d, a, ptr, 9, 0xeb86d391, 21) ;
|
||||
|
||||
a += saved_a;
|
||||
b += saved_b;
|
||||
c += saved_c;
|
||||
d += saved_d;
|
||||
|
||||
ptr += 64;
|
||||
|
||||
} while (size -= 64);
|
||||
|
||||
ctx.a = a;
|
||||
ctx.b = b;
|
||||
ctx.c = c;
|
||||
ctx.d = d;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,86 +5,101 @@
|
||||
// Implementation was off Steve Reid's SHA-1 C implementation
|
||||
|
||||
module std::hash::sha1;
|
||||
import std::hash::hmac;
|
||||
import std::bits;
|
||||
|
||||
const BLOCK_BYTES = 64;
|
||||
const HASH_BYTES = 20;
|
||||
|
||||
struct Sha1
|
||||
{
|
||||
uint[5] state;
|
||||
uint[2] count;
|
||||
char[64] buffer;
|
||||
char[BLOCK_BYTES] buffer;
|
||||
}
|
||||
|
||||
fn void Sha1.init(Sha1* this)
|
||||
def HmacSha1 = Hmac(<Sha1, HASH_BYTES, BLOCK_BYTES>);
|
||||
def hmac = hmac::hash(<Sha1, HASH_BYTES, BLOCK_BYTES>);
|
||||
def pbkdf2 = hmac::pbkdf2(<Sha1, HASH_BYTES, BLOCK_BYTES>);
|
||||
|
||||
fn char[HASH_BYTES] hash(char[] data)
|
||||
{
|
||||
// SHA1 initialization constants
|
||||
*this = {
|
||||
.state = {
|
||||
0x67452301,
|
||||
0xEFCDAB89,
|
||||
0x98BADCFE,
|
||||
0x10325476,
|
||||
0xC3D2E1F0
|
||||
}
|
||||
Sha1 sha1 @noinit;
|
||||
sha1.init();
|
||||
sha1.update(data);
|
||||
return sha1.final();
|
||||
}
|
||||
|
||||
fn void Sha1.init(&self)
|
||||
{
|
||||
// SHA1 initialization constants
|
||||
*self = {
|
||||
.state = {
|
||||
0x67452301,
|
||||
0xEFCDAB89,
|
||||
0x98BADCFE,
|
||||
0x10325476,
|
||||
0xC3D2E1F0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] this
|
||||
* @param [in] data
|
||||
* @require data.len <= uint.max
|
||||
**/
|
||||
fn void Sha1.update(Sha1* this, char[] data)
|
||||
<*
|
||||
@param [in] data
|
||||
@require data.len <= uint.max
|
||||
*>
|
||||
fn void Sha1.update(&self, char[] data)
|
||||
{
|
||||
uint j = this.count[0];
|
||||
uint j = self.count[0];
|
||||
uint len = data.len;
|
||||
if ((this.count[0] += len << 3) < j) this.count[1]++;
|
||||
this.count[1] += len >> 29;
|
||||
if ((self.count[0] += len << 3) < j) self.count[1]++;
|
||||
self.count[1] += len >> 29;
|
||||
j = (j >> 3) & 63;
|
||||
uint i;
|
||||
if (j + len > 63)
|
||||
{
|
||||
i = 64 - j;
|
||||
this.buffer[j..] = data[:i];
|
||||
sha1_transform(&this.state, &this.buffer);
|
||||
self.buffer[j..] = data[:i];
|
||||
sha1_transform(&self.state, &self.buffer);
|
||||
for (; i + 63 < len; i += 64)
|
||||
{
|
||||
sha1_transform(&this.state, &data[i]);
|
||||
sha1_transform(&self.state, &data[i]);
|
||||
}
|
||||
j = 0;
|
||||
}
|
||||
this.buffer[j:len - i] = data[i..];
|
||||
self.buffer[j:len - i] = data[i..];
|
||||
}
|
||||
|
||||
|
||||
fn char[20] Sha1.final(Sha1* this)
|
||||
fn char[HASH_BYTES] Sha1.final(&self)
|
||||
{
|
||||
char[8] finalcount;
|
||||
for (uint i = 0; i < 8; i++)
|
||||
{
|
||||
finalcount[i] = (char)((this.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
|
||||
finalcount[i] = (char)((self.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
|
||||
}
|
||||
this.update(char[] { 0o200 });
|
||||
while ((this.count[0] & 504) != 448)
|
||||
self.update(char[] { 0o200 });
|
||||
while ((self.count[0] & 504) != 448)
|
||||
{
|
||||
this.update(char[] { 0 });
|
||||
self.update(char[] { 0 });
|
||||
}
|
||||
|
||||
this.update(&finalcount);
|
||||
char[20] digest;
|
||||
for (uint i = 0; i < 20; i++)
|
||||
self.update(&finalcount);
|
||||
char[HASH_BYTES] digest;
|
||||
for (uint i = 0; i < HASH_BYTES; i++)
|
||||
{
|
||||
digest[i] = (char)((this.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF);
|
||||
digest[i] = (char)((self.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
// Clear mem
|
||||
mem::clear(this, Sha1.sizeof);
|
||||
*self = {};
|
||||
finalcount = {};
|
||||
return digest;
|
||||
}
|
||||
|
||||
union Long16 @local
|
||||
{
|
||||
char[64] c;
|
||||
char[BLOCK_BYTES] c;
|
||||
uint[16] l;
|
||||
}
|
||||
|
||||
@@ -96,48 +111,53 @@ macro @blk(&block, i) @local
|
||||
|
||||
macro @blk0(&block, i) @local
|
||||
{
|
||||
$if env::BIG_ENDIAN:
|
||||
return block.l[i];
|
||||
$else
|
||||
return block.l[i] = (block.l[i].rotl(24) & 0xFF00FF00)
|
||||
| (block.l[i].rotl(8) & 0x00FF00FF);
|
||||
$endif
|
||||
$if env::BIG_ENDIAN:
|
||||
return block.l[i];
|
||||
$else
|
||||
return block.l[i] = (block.l[i].rotl(24) & 0xFF00FF00)
|
||||
| (block.l[i].rotl(8) & 0x00FF00FF);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro @r0(&block, v, &w, x, y, &z, i) @local
|
||||
macro @r0(&block, v, &wref, x, y, &z, i) @local
|
||||
{
|
||||
z += ((w & (x ^ y)) ^ y) + @blk0(block, i) + 0x5A827999 + v.rotl(5);
|
||||
w = w.rotl(30);
|
||||
var w = *wref;
|
||||
*z += ((w & (x ^ y)) ^ y) + @blk0(*block, i) + 0x5A827999 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro @r1(&block, v, &w, x, y, &z, i) @local
|
||||
macro @r1(&block, v, &wref, x, y, &z, i) @local
|
||||
{
|
||||
z += ((w & (x ^ y)) ^ y) + @blk(block, i) + 0x5A827999 + v.rotl(5);
|
||||
w = w.rotl(30);
|
||||
var w = *wref;
|
||||
*z += ((w & (x ^ y)) ^ y) + @blk(*block, i) + 0x5A827999 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro @r2(&block, v, &w, x, y, &z, i) @local
|
||||
macro @r2(&block, v, &wref, x, y, &z, i) @local
|
||||
{
|
||||
z += (w ^ x ^ y) + @blk(block, i) + 0x6ED9EBA1 + v.rotl(5);
|
||||
w = w.rotl(30);
|
||||
var w = *wref;
|
||||
*z += (w ^ x ^ y) + @blk(*block, i) + 0x6ED9EBA1 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro @r3(&block, v, &w, x, y, &z, i) @local
|
||||
macro @r3(&block, v, &wref, x, y, &z, i) @local
|
||||
{
|
||||
z += (((w | x) &y) | (w & x)) + @blk(block, i) + 0x8F1BBCDC + v.rotl(5);
|
||||
w = w.rotl(30);
|
||||
var w = *wref;
|
||||
*z += (((w | x) & y) | (w & x)) + @blk(*block, i) + 0x8F1BBCDC + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro @r4(&block, v, &w, x, y, &z, i) @local
|
||||
macro @r4(&block, v, &wref, x, y, &z, i) @local
|
||||
{
|
||||
z += (w ^ x ^ y) + @blk(block, i) + 0xCA62C1D6 + v.rotl(5);
|
||||
w = w.rotl(30);
|
||||
var w = *wref;
|
||||
*z += (w ^ x ^ y) + @blk(*block, i) + 0xCA62C1D6 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] state
|
||||
* @param [&in] buffer
|
||||
**/
|
||||
<*
|
||||
@param [&inout] state
|
||||
@param [&in] buffer
|
||||
*>
|
||||
fn void sha1_transform(uint* state, char* buffer) @local
|
||||
{
|
||||
Long16 block;
|
||||
|
||||
176
lib/std/hash/sha256.c3
Normal file
176
lib/std/hash/sha256.c3
Normal file
@@ -0,0 +1,176 @@
|
||||
module std::hash::sha256;
|
||||
|
||||
import std::hash::hmac;
|
||||
|
||||
const BLOCK_SIZE = 64;
|
||||
const HASH_SIZE = 32;
|
||||
|
||||
const uint[64] K @local = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||
};
|
||||
|
||||
// Right rotate function
|
||||
macro uint @rotr(uint x, uint n) @local => (((x) >> (n)) | ((x) << (32 - (n))));
|
||||
|
||||
// SHA-256 functions
|
||||
macro uint @ch(uint x, uint y, uint z) @local => (x & y) ^ (~x & z);
|
||||
macro uint @maj(uint x, uint y, uint z) @local => (x & y) ^ (x & z) ^ (y & z);
|
||||
macro uint @_sigma0(uint x) @local => @rotr(x, 2) ^ @rotr(x, 13) ^ @rotr(x, 22);
|
||||
macro uint @_sigma1(uint x) @local => @rotr(x, 6) ^ @rotr(x, 11) ^ @rotr(x, 25);
|
||||
macro uint @sigma0(uint x) @local => @rotr(x, 7) ^ @rotr(x, 18) ^ (x >> 3);
|
||||
macro uint @sigma1(uint x) @local => @rotr(x, 17) ^ @rotr(x, 19) ^ (x >> 10);
|
||||
|
||||
struct Sha256
|
||||
{
|
||||
uint[8] state;
|
||||
ulong bitcount;
|
||||
char[BLOCK_SIZE] buffer;
|
||||
}
|
||||
|
||||
def HmacSha256 = Hmac(<Sha256, HASH_SIZE, BLOCK_SIZE>);
|
||||
def hmac = hmac::hash(<Sha256, HASH_SIZE, BLOCK_SIZE>);
|
||||
def pbkdf2 = hmac::pbkdf2(<Sha256, HASH_SIZE, BLOCK_SIZE>);
|
||||
|
||||
fn char[HASH_SIZE] hash(char[] data)
|
||||
{
|
||||
Sha256 sha256 @noinit;
|
||||
sha256.init();
|
||||
sha256.update(data);
|
||||
return sha256.final();
|
||||
}
|
||||
|
||||
fn void Sha256.init(&self)
|
||||
{
|
||||
// Sha256 initialization constants
|
||||
*self = {
|
||||
.state = {
|
||||
0x6A09E667,
|
||||
0xBB67AE85,
|
||||
0x3C6EF372,
|
||||
0xA54FF53A,
|
||||
0x510E527F,
|
||||
0x9B05688C,
|
||||
0x1F83D9AB,
|
||||
0x5BE0CD19
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] data
|
||||
@require data.len <= uint.max
|
||||
*>
|
||||
fn void Sha256.update(&self, char[] data) {
|
||||
uint i = 0;
|
||||
uint len = data.len;
|
||||
uint buffer_pos = (uint)(self.bitcount / 8) % BLOCK_SIZE;
|
||||
self.bitcount += (ulong)(len * 8);
|
||||
|
||||
while (len--) {
|
||||
self.buffer[buffer_pos++] = data[i++];
|
||||
if (buffer_pos == BLOCK_SIZE) {
|
||||
sha256_transform(&self.state, &self.buffer);
|
||||
buffer_pos = 0; // Reset buffer position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn char[HASH_SIZE] Sha256.final(&self) {
|
||||
char[HASH_SIZE] hash;
|
||||
ulong i = (self.bitcount / 8) % BLOCK_SIZE;
|
||||
|
||||
// Append 0x80 to the buffer
|
||||
self.buffer[i++] = 0x80;
|
||||
|
||||
// Pad the buffer with zeros
|
||||
if (i > BLOCK_SIZE - 8) {
|
||||
while (i < BLOCK_SIZE) {
|
||||
self.buffer[i++] = 0x00;
|
||||
}
|
||||
sha256_transform(&self.state, &self.buffer);
|
||||
i = 0; // Reset buffer index after transformation
|
||||
}
|
||||
|
||||
while (i < BLOCK_SIZE - 8) {
|
||||
self.buffer[i++] = 0x00;
|
||||
}
|
||||
|
||||
// Append the bitcount in big-endian format
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
self.buffer[BLOCK_SIZE - 8 + j] = (char)((self.bitcount >> (56 - j * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
sha256_transform(&self.state, &self.buffer);
|
||||
|
||||
// Convert state to the final hash
|
||||
for (i = 0; i < 8; ++i) {
|
||||
hash[i * 4] = (char)((self.state[i] >> 24) & 0xFF);
|
||||
hash[i * 4 + 1] = (char)((self.state[i] >> 16) & 0xFF);
|
||||
hash[i * 4 + 2] = (char)((self.state[i] >> 8) & 0xFF);
|
||||
hash[i * 4 + 3] = (char)(self.state[i] & 0xFF);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] state
|
||||
@param [&in] buffer
|
||||
*>
|
||||
fn void sha256_transform(uint* state, char* buffer) @local {
|
||||
uint a, b, c, d, e, f, g, h, t1, t2;
|
||||
uint[64] m;
|
||||
int i;
|
||||
|
||||
// Prepare the message schedule
|
||||
for (i = 0; i < 16; ++i) {
|
||||
m[i] = ((uint)buffer[i * 4] << 24) | ((uint)buffer[i * 4 + 1] << 16) |
|
||||
((uint)buffer[i * 4 + 2] << 8) | ((uint)buffer[i * 4 + 3]); // Ensure values are cast to uint for correct shifts
|
||||
}
|
||||
for (i = 16; i < 64; ++i) {
|
||||
m[i] = @sigma1(m[i - 2]) + m[i - 7] + @sigma0(m[i - 15]) + m[i - 16];
|
||||
}
|
||||
|
||||
// Initialize working variables
|
||||
a = state[0];
|
||||
b = state[1];
|
||||
c = state[2];
|
||||
d = state[3];
|
||||
e = state[4];
|
||||
f = state[5];
|
||||
g = state[6];
|
||||
h = state[7];
|
||||
|
||||
// Perform the main SHA-256 compression function
|
||||
for (i = 0; i < 64; ++i) {
|
||||
t1 = h + @_sigma1(e) + @ch(e, f, g) + K[i] + m[i];
|
||||
t2 = @_sigma0(a) + @maj(a, b, c);
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + t1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = t1 + t2;
|
||||
}
|
||||
|
||||
// Update the state
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
state[4] += e;
|
||||
state[5] += f;
|
||||
state[6] += g;
|
||||
state[7] += h;
|
||||
a = b = c = d = e = f = g = h = t1 = t2 = i = 0;
|
||||
m[:64] = buffer[:64] = 0;
|
||||
}
|
||||
94
lib/std/io/bits.c3
Normal file
94
lib/std/io/bits.c3
Normal file
@@ -0,0 +1,94 @@
|
||||
module std::io;
|
||||
|
||||
struct BitReader
|
||||
{
|
||||
InStream reader;
|
||||
uint bits;
|
||||
uint len;
|
||||
}
|
||||
|
||||
fn void BitReader.init(&self, InStream byte_reader)
|
||||
{
|
||||
*self = { .reader = byte_reader };
|
||||
}
|
||||
|
||||
fn void BitReader.clear(&self) @inline
|
||||
{
|
||||
self.len = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
@require nbits <= 8
|
||||
@require self.len + nbits <= uint.sizeof * 8
|
||||
*>
|
||||
fn char! BitReader.read_bits(&self, uint nbits)
|
||||
{
|
||||
uint bits = self.bits;
|
||||
if (self.len < nbits)
|
||||
{
|
||||
// New bits are pushed right.
|
||||
char c = self.reader.read_byte()!;
|
||||
bits <<= 8;
|
||||
bits |= c;
|
||||
self.bits = bits;
|
||||
self.len += 8;
|
||||
}
|
||||
self.len -= nbits;
|
||||
uint mask = (1 << nbits) - 1;
|
||||
return (char)((bits >> self.len) & mask);
|
||||
}
|
||||
|
||||
struct BitWriter
|
||||
{
|
||||
OutStream writer;
|
||||
uint bits;
|
||||
uint len;
|
||||
}
|
||||
|
||||
// c3 doesn't allow to shift more than bit width of a variable,
|
||||
// so use closest byte boundary of 24 instead of 32
|
||||
const int WRITER_BITS = 24;
|
||||
|
||||
fn void BitWriter.init(&self, OutStream byte_writer)
|
||||
{
|
||||
*self = { .writer = byte_writer };
|
||||
}
|
||||
|
||||
fn void! BitWriter.flush(&self)
|
||||
{
|
||||
if (self.len == 0) return;
|
||||
|
||||
int padding = ($sizeof(self.bits) * 8 - self.len);
|
||||
uint bits = self.bits << padding;
|
||||
uint n = (self.len + 7) / 8;
|
||||
char[4] buffer;
|
||||
bitorder::write(bits, &buffer, UIntBE);
|
||||
io::write_all(self.writer, buffer[:n])!;
|
||||
self.len = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
@require nbits <= 32
|
||||
*>
|
||||
fn void! BitWriter.write_bits(&self, uint bits, uint nbits)
|
||||
{
|
||||
if (nbits == 0) return;
|
||||
while (self.len + nbits > WRITER_BITS)
|
||||
{
|
||||
uint to_push = WRITER_BITS - self.len;
|
||||
uint bits_to_push = (bits >> (nbits - to_push)) & ((1 << to_push) - 1);
|
||||
|
||||
self.bits <<= to_push;
|
||||
self.bits |= bits_to_push;
|
||||
self.len += to_push;
|
||||
nbits -= to_push;
|
||||
|
||||
self.flush()!;
|
||||
}
|
||||
|
||||
if (nbits == 0) return;
|
||||
|
||||
self.bits <<= nbits;
|
||||
self.bits |= bits & ((1 << nbits) - 1);
|
||||
self.len += nbits;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module std::io::dir;
|
||||
import std::io::os;
|
||||
|
||||
202
lib/std/io/file.c3
Normal file
202
lib/std/io/file.c3
Normal file
@@ -0,0 +1,202 @@
|
||||
module std::io;
|
||||
import libc;
|
||||
|
||||
struct File (InStream, OutStream)
|
||||
{
|
||||
CFile file;
|
||||
}
|
||||
|
||||
module std::io::file;
|
||||
import libc, std::io::path, std::io::os;
|
||||
|
||||
fn File! open(String filename, String mode)
|
||||
{
|
||||
return from_handle(os::native_fopen(filename, mode));
|
||||
}
|
||||
|
||||
fn File! open_path(Path path, String mode)
|
||||
{
|
||||
return from_handle(os::native_fopen(path.str_view(), mode));
|
||||
}
|
||||
|
||||
fn File from_handle(CFile file)
|
||||
{
|
||||
return { .file = file };
|
||||
}
|
||||
|
||||
fn bool is_file(String path)
|
||||
{
|
||||
return os::native_is_file(path);
|
||||
}
|
||||
|
||||
fn usz! get_size(String path)
|
||||
{
|
||||
return os::native_file_size(path);
|
||||
}
|
||||
|
||||
fn void! delete(String filename) => os::native_remove(filename) @inline;
|
||||
|
||||
|
||||
<*
|
||||
@require self.file != null
|
||||
*>
|
||||
fn void! File.reopen(&self, String filename, String mode)
|
||||
{
|
||||
self.file = os::native_freopen(self.file, filename, mode)!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.file != null
|
||||
*>
|
||||
fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
|
||||
{
|
||||
os::native_fseek(self.file, offset, seek_mode)!;
|
||||
return os::native_ftell(self.file);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Implement later
|
||||
<*
|
||||
@require self.file == null
|
||||
*>
|
||||
fn void! File.memopen(File* file, char[] data, String mode)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
file.file = libc::memopen(data.ptr, data.len, mode.zstr_tcopy(), file.file);
|
||||
// TODO errors
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
<*
|
||||
@require self.file != null
|
||||
*>
|
||||
fn void! File.write_byte(&self, char c) @dynamic
|
||||
{
|
||||
return os::native_fputc(c, self.file);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self
|
||||
*>
|
||||
fn void! File.close(&self) @inline @dynamic
|
||||
{
|
||||
if (self.file && libc::fclose(self.file))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ECONNRESET:
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID?;
|
||||
case errno::EINTR: return IoError.INTERRUPTED?;
|
||||
case errno::EDQUOT:
|
||||
case errno::EFAULT:
|
||||
case errno::EAGAIN:
|
||||
case errno::EFBIG:
|
||||
case errno::ENETDOWN:
|
||||
case errno::ENETUNREACH:
|
||||
case errno::ENOSPC:
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE?;
|
||||
default: return IoError.UNKNOWN_ERROR?;
|
||||
}
|
||||
}
|
||||
self.file = null;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.file
|
||||
*>
|
||||
fn bool File.eof(&self) @inline
|
||||
{
|
||||
return libc::feof(self.file) != 0;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] buffer
|
||||
*>
|
||||
fn usz! File.read(&self, char[] buffer) @dynamic
|
||||
{
|
||||
return os::native_fread(self.file, buffer);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [out] buffer
|
||||
@require self.file `File must be initialized`
|
||||
*>
|
||||
fn usz! File.write(&self, char[] buffer) @dynamic
|
||||
{
|
||||
return os::native_fwrite(self.file, buffer);
|
||||
}
|
||||
|
||||
|
||||
fn char! File.read_byte(&self) @dynamic
|
||||
{
|
||||
int c = libc::fgetc(self.file);
|
||||
if (c == -1) return IoError.EOF?;
|
||||
return (char)c;
|
||||
}
|
||||
|
||||
<*
|
||||
Load up to buffer.len characters. Returns IoError.OVERFLOW if the file is longer
|
||||
than the buffer.
|
||||
|
||||
@param filename "The path to the file to read"
|
||||
@param [in] buffer "The buffer to read to"
|
||||
*>
|
||||
fn char[]! load_buffer(String filename, char[] buffer)
|
||||
{
|
||||
File file = open(filename, "rb")!;
|
||||
defer (void)file.close();
|
||||
usz len = file.seek(0, END)!;
|
||||
if (len > buffer.len) return IoError.OVERFLOW?;
|
||||
file.seek(0, SET)!;
|
||||
usz read = 0;
|
||||
while (read < len)
|
||||
{
|
||||
read += file.read(buffer[read:len - read])!;
|
||||
}
|
||||
return buffer[:len];
|
||||
|
||||
}
|
||||
|
||||
fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
|
||||
{
|
||||
File file = open(filename, "rb")!;
|
||||
defer (void)file.close();
|
||||
usz len = file.seek(0, END)!;
|
||||
file.seek(0, SET)!;
|
||||
char* data = allocator::malloc_try(allocator, len)!;
|
||||
defer catch allocator::free(allocator, data);
|
||||
usz read = 0;
|
||||
while (read < len)
|
||||
{
|
||||
read += file.read(data[read:len - read])!;
|
||||
}
|
||||
return data[:len];
|
||||
}
|
||||
|
||||
fn char[]! load_temp(String filename)
|
||||
{
|
||||
return load_new(filename, allocator::temp());
|
||||
}
|
||||
|
||||
fn void! save(String filename, char[] data)
|
||||
{
|
||||
File file = open(filename, "wb")!;
|
||||
defer (void)file.close();
|
||||
while (data.len)
|
||||
{
|
||||
usz written = file.write(data)!;
|
||||
data = data[written..];
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.file `File must be initialized`
|
||||
*>
|
||||
fn void! File.flush(&self) @dynamic
|
||||
{
|
||||
libc::fflush(self.file);
|
||||
}
|
||||
533
lib/std/io/formatter.c3
Normal file
533
lib/std/io/formatter.c3
Normal file
@@ -0,0 +1,533 @@
|
||||
module std::io;
|
||||
import std::collections::map;
|
||||
import libc;
|
||||
|
||||
const int PRINTF_NTOA_BUFFER_SIZE = 256;
|
||||
|
||||
interface Printable
|
||||
{
|
||||
fn String to_string(Allocator allocator) @optional;
|
||||
fn String to_new_string(Allocator allocator) @optional @deprecated("Use to_string");
|
||||
fn usz! to_format(Formatter* formatter) @optional;
|
||||
}
|
||||
|
||||
fault PrintFault
|
||||
{
|
||||
BUFFER_EXCEEDED,
|
||||
INTERNAL_BUFFER_EXCEEDED,
|
||||
INVALID_FORMAT,
|
||||
NOT_ENOUGH_ARGUMENTS,
|
||||
INVALID_ARGUMENT,
|
||||
}
|
||||
|
||||
def OutputFn = fn void!(void* buffer, char c);
|
||||
def FloatType = double;
|
||||
|
||||
|
||||
macro bool is_struct_with_default_print($Type)
|
||||
{
|
||||
return $Type.kindof == STRUCT
|
||||
&&& !$defined($Type.to_format)
|
||||
&&& !$defined($Type.to_new_string)
|
||||
&&& !$defined($Type.to_string);
|
||||
}
|
||||
|
||||
<*
|
||||
Introspect a struct and print it to a formatter
|
||||
|
||||
@require @typekind(value) == STRUCT `This macro is only valid on macros`
|
||||
*>
|
||||
macro usz! struct_to_format(value, Formatter* f, bool $force_dump)
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
usz total = f.print("{ ")!;
|
||||
$foreach ($i, $member : $Type.membersof)
|
||||
$if $i > 0:
|
||||
total += f.print(", ")!;
|
||||
$endif
|
||||
$if $member.nameof != "":
|
||||
total += f.printf("%s: ", $member.nameof)!;
|
||||
$endif
|
||||
$if ($force_dump &&& $member.typeid.kindof == STRUCT) |||
|
||||
is_struct_with_default_print($typefrom($member.typeid)):
|
||||
total += struct_to_format($member.get(value), f, $force_dump)!;
|
||||
$else
|
||||
total += f.printf("%s", $member.get(value))!;
|
||||
$endif
|
||||
$endforeach
|
||||
return total + f.print(" }");
|
||||
}
|
||||
|
||||
fn usz! ReflectedParam.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
return f.printf("[Parameter '%s']", self.name);
|
||||
}
|
||||
|
||||
fn usz! Formatter.printf(&self, String format, args...)
|
||||
{
|
||||
return self.vprintf(format, args) @inline;
|
||||
}
|
||||
|
||||
struct Formatter
|
||||
{
|
||||
void *data;
|
||||
OutputFn out_fn;
|
||||
struct
|
||||
{
|
||||
PrintFlags flags;
|
||||
uint width;
|
||||
uint prec;
|
||||
usz idx;
|
||||
anyfault first_fault;
|
||||
}
|
||||
}
|
||||
|
||||
bitstruct PrintFlags : uint
|
||||
{
|
||||
bool zeropad;
|
||||
bool left;
|
||||
bool plus;
|
||||
bool space;
|
||||
bool hash;
|
||||
bool uppercase;
|
||||
bool precision;
|
||||
}
|
||||
|
||||
fn void Formatter.init(&self, OutputFn out_fn, void* data = null)
|
||||
{
|
||||
*self = { .data = data, .out_fn = out_fn};
|
||||
}
|
||||
|
||||
fn usz! Formatter.out(&self, char c) @private
|
||||
{
|
||||
if (catch err = self.out_fn(self.data, c))
|
||||
{
|
||||
if (self.first_fault) return self.first_fault?;
|
||||
self.first_fault = err;
|
||||
return err?;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
fn usz! Formatter.print_with_function(&self, Printable arg)
|
||||
{
|
||||
if (&arg.to_format)
|
||||
{
|
||||
PrintFlags old = self.flags;
|
||||
uint old_width = self.width;
|
||||
uint old_prec = self.prec;
|
||||
defer
|
||||
{
|
||||
self.flags = old;
|
||||
self.width = old_width;
|
||||
self.prec = old_prec;
|
||||
}
|
||||
if (!arg) return self.out_substr("(null)");
|
||||
return arg.to_format(self);
|
||||
}
|
||||
if (&arg.to_string)
|
||||
{
|
||||
PrintFlags old = self.flags;
|
||||
uint old_width = self.width;
|
||||
uint old_prec = self.prec;
|
||||
defer
|
||||
{
|
||||
self.flags = old;
|
||||
self.width = old_width;
|
||||
self.prec = old_prec;
|
||||
}
|
||||
if (!arg) return self.out_substr("(null)");
|
||||
@stack_mem(1024; Allocator mem)
|
||||
{
|
||||
return self.out_substr(arg.to_string(mem));
|
||||
};
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
|
||||
fn usz! Formatter.out_str(&self, any arg) @private
|
||||
{
|
||||
switch (arg.type.kindof)
|
||||
{
|
||||
case TYPEID:
|
||||
return self.out_substr("typeid");
|
||||
case VOID:
|
||||
return self.out_substr("void");
|
||||
case ANYFAULT:
|
||||
case FAULT:
|
||||
return self.out_substr((*(anyfault*)arg.ptr).nameof);
|
||||
case INTERFACE:
|
||||
case ANY:
|
||||
return self.out_str(*(any*)arg);
|
||||
case OPTIONAL:
|
||||
unreachable();
|
||||
case SIGNED_INT:
|
||||
case UNSIGNED_INT:
|
||||
PrintFlags flags = self.flags;
|
||||
uint width = self.width;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.flags = {};
|
||||
self.width = 0;
|
||||
return self.ntoa_any(arg, 10) ?? self.out_substr("<INVALID>");
|
||||
case FLOAT:
|
||||
PrintFlags flags = self.flags;
|
||||
uint width = self.width;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.flags = {};
|
||||
self.width = 0;
|
||||
return self.ftoa(float_from_any(arg)) ?? self.out_substr("ERR");
|
||||
case BOOL:
|
||||
return self.out_substr(*(bool*)arg.ptr ? "true" : "false");
|
||||
default:
|
||||
}
|
||||
usz! n = self.print_with_function((Printable)arg);
|
||||
if (try n) return n;
|
||||
if (@catch(n) != SearchResult.MISSING) n!;
|
||||
switch (arg.type.kindof)
|
||||
{
|
||||
case ENUM:
|
||||
usz i = types::any_to_int(arg, usz)!!;
|
||||
assert(i < arg.type.names.len, "Illegal enum value found, numerical value was %d.", i);
|
||||
return self.out_substr(arg.type.names[i]);
|
||||
case STRUCT:
|
||||
return self.out_substr("<struct>");
|
||||
case UNION:
|
||||
return self.out_substr("<union>");
|
||||
case BITSTRUCT:
|
||||
return self.out_substr("<bitstruct>");
|
||||
case FUNC:
|
||||
return self.out_substr("<function>");
|
||||
case DISTINCT:
|
||||
if (arg.type == String.typeid)
|
||||
{
|
||||
return self.out_substr(*(String*)arg);
|
||||
}
|
||||
if (arg.type == ZString.typeid)
|
||||
{
|
||||
return self.out_substr(*(ZString*)arg ? ((ZString*)arg).str_view() : "(null)");
|
||||
}
|
||||
if (arg.type == DString.typeid)
|
||||
{
|
||||
return self.out_substr(*(DString*)arg ? ((DString*)arg).str_view() : "(null)");
|
||||
}
|
||||
return self.out_str(arg.as_inner());
|
||||
case POINTER:
|
||||
typeid inner = arg.type.inner;
|
||||
void** pointer = arg.ptr;
|
||||
if (arg.type.inner != void.typeid)
|
||||
{
|
||||
any deref = any_make(*pointer, inner);
|
||||
n = self.print_with_function((Printable)deref);
|
||||
if (try n) return n;
|
||||
if (@catch(n) != SearchResult.MISSING) n!;
|
||||
}
|
||||
PrintFlags flags = self.flags;
|
||||
uint width = self.width;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.width = 0;
|
||||
self.out_substr("0x")!;
|
||||
return self.ntoa_any(arg, 16);
|
||||
case ARRAY:
|
||||
// this is SomeType[*] so grab the "SomeType"
|
||||
PrintFlags flags = self.flags;
|
||||
uint width = self.width;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.flags = {};
|
||||
self.width = 0;
|
||||
typeid inner = arg.type.inner;
|
||||
usz size = inner.sizeof;
|
||||
usz alen = arg.type.len;
|
||||
// Pretend this is a String
|
||||
void* ptr = (void*)arg.ptr;
|
||||
usz len = self.out('[')!;
|
||||
for (usz i = 0; i < alen; i++)
|
||||
{
|
||||
if (i != 0) len += self.out_substr(", ")!;
|
||||
len += self.out_str(any_make(ptr, inner))!;
|
||||
ptr += size;
|
||||
}
|
||||
len += self.out(']')!;
|
||||
return len;
|
||||
case VECTOR:
|
||||
PrintFlags flags = self.flags;
|
||||
uint width = self.width;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.flags = {};
|
||||
self.width = 0;
|
||||
// this is SomeType[*] so grab the "SomeType"
|
||||
typeid inner = arg.type.inner;
|
||||
usz size = inner.sizeof;
|
||||
usz vlen = arg.type.len;
|
||||
// Pretend this is a String
|
||||
void* ptr = (void*)arg.ptr;
|
||||
usz len = self.out_substr("[<")!;
|
||||
for (usz i = 0; i < vlen; i++)
|
||||
{
|
||||
if (i != 0) len += self.out_substr(", ")!;
|
||||
len += self.out_str(any_make(ptr, inner))!;
|
||||
ptr += size;
|
||||
}
|
||||
len += self.out_substr(">]")!;
|
||||
return len;
|
||||
case SLICE:
|
||||
// this is SomeType[] so grab the "SomeType"
|
||||
typeid inner = arg.type.inner;
|
||||
if (inner == void.typeid) inner = char.typeid;
|
||||
PrintFlags flags = self.flags;
|
||||
uint width = self.width;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.flags = {};
|
||||
self.width = 0;
|
||||
usz size = inner.sizeof;
|
||||
// Pretend this is a String
|
||||
String* temp = (void*)arg.ptr;
|
||||
void* ptr = (void*)temp.ptr;
|
||||
usz slen = temp.len;
|
||||
usz len = self.out('[')!;
|
||||
for (usz i = 0; i < slen; i++)
|
||||
{
|
||||
if (i != 0) len += self.out_substr(", ")!;
|
||||
len += self.out_str(any_make(ptr, inner))!;
|
||||
ptr += size;
|
||||
}
|
||||
len += self.out(']')!;
|
||||
return len;
|
||||
case ANY:
|
||||
case INTERFACE:
|
||||
unreachable("Already handled");
|
||||
default:
|
||||
}
|
||||
return self.out_substr("Invalid type");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
fn void! out_null_fn(void* data @unused, char c @unused) @private
|
||||
{
|
||||
}
|
||||
|
||||
macro usz! @report_fault(Formatter* f, $fault)
|
||||
{
|
||||
(void)f.out_substr($fault);
|
||||
return PrintFault.INVALID_FORMAT?;
|
||||
}
|
||||
|
||||
macro usz! @wrap_bad(Formatter* f, #action)
|
||||
{
|
||||
usz! len = #action;
|
||||
if (catch err = len)
|
||||
{
|
||||
case PrintFault.BUFFER_EXCEEDED:
|
||||
case PrintFault.INTERNAL_BUFFER_EXCEEDED:
|
||||
return f.first_err(err)?;
|
||||
default:
|
||||
err = f.first_err(PrintFault.INVALID_ARGUMENT);
|
||||
f.out_substr("<INVALID>")!;
|
||||
return err?;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
fn usz! Formatter.vprintf(&self, String format, any[] anys)
|
||||
{
|
||||
self.first_fault = {};
|
||||
if (!self.out_fn)
|
||||
{
|
||||
// use null output function
|
||||
self.out_fn = &out_null_fn;
|
||||
}
|
||||
usz total_len;
|
||||
usz format_len = format.len;
|
||||
usz variant_index = 0;
|
||||
for (usz i = 0; i < format_len; i++)
|
||||
{
|
||||
// format specifier? %[flags][width][.precision][length]
|
||||
char c = format[i];
|
||||
if (c != '%')
|
||||
{
|
||||
// no
|
||||
total_len += self.out(c)!;
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
if (i >= format_len) return @report_fault(self, "%ERR");
|
||||
c = format[i];
|
||||
if (c == '%')
|
||||
{
|
||||
total_len += self.out(c)!;
|
||||
continue;
|
||||
}
|
||||
// evaluate flags
|
||||
self.flags = {};
|
||||
while FLAG_EVAL: (true)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '0': self.flags.zeropad = true;
|
||||
case '-': self.flags.left = true;
|
||||
case '+': self.flags.plus = true;
|
||||
case ' ': self.flags.space = true;
|
||||
case '#': self.flags.hash = true;
|
||||
default: break FLAG_EVAL;
|
||||
}
|
||||
if (++i >= format_len) return @report_fault(self, "%ERR");
|
||||
c = format[i];
|
||||
}
|
||||
// evaluate width field
|
||||
int! w = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
|
||||
if (catch w) return @report_fault(self, "%ERR");
|
||||
c = format[i];
|
||||
if (w < 0)
|
||||
{
|
||||
self.flags.left = true;
|
||||
w = -w;
|
||||
}
|
||||
self.width = w;
|
||||
// evaluate precision field
|
||||
self.prec = 0;
|
||||
if (c == '.')
|
||||
{
|
||||
self.flags.precision = true;
|
||||
if (++i >= format_len) return @report_fault(self, "<BAD FORMAT>");
|
||||
int! prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
|
||||
if (catch prec) return @report_fault(self, "<BAD FORMAT>");
|
||||
self.prec = prec < 0 ? 0 : prec;
|
||||
c = format[i];
|
||||
}
|
||||
|
||||
// evaluate specifier
|
||||
uint base = 0;
|
||||
if (variant_index >= anys.len)
|
||||
{
|
||||
self.first_err(PrintFault.NOT_ENOUGH_ARGUMENTS);
|
||||
total_len += self.out_substr("<MISSING>")!;
|
||||
continue;
|
||||
}
|
||||
any current = anys[variant_index++];
|
||||
switch (c)
|
||||
{
|
||||
case 'd':
|
||||
base = 10;
|
||||
self.flags.hash = false;
|
||||
case 'X' :
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'x' :
|
||||
base = 16;
|
||||
case 'O':
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'o' :
|
||||
base = 8;
|
||||
case 'B':
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'b' :
|
||||
base = 2;
|
||||
case 'A':
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'a':
|
||||
total_len += @wrap_bad(self, self.atoa(float_from_any(current)))!;
|
||||
continue;
|
||||
case 'F' :
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'f':
|
||||
total_len += @wrap_bad(self, self.ftoa(float_from_any(current)))!;
|
||||
continue;
|
||||
case 'E':
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'e':
|
||||
total_len += @wrap_bad(self, self.etoa(float_from_any(current)))!;
|
||||
continue;
|
||||
case 'G':
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'g':
|
||||
total_len += @wrap_bad(self, self.gtoa(float_from_any(current)))!;
|
||||
continue;
|
||||
case 'c':
|
||||
total_len += self.out_char(current)!;
|
||||
continue;
|
||||
case 's':
|
||||
if (self.flags.left)
|
||||
{
|
||||
usz len = self.out_str(current)!;
|
||||
total_len += len;
|
||||
total_len += self.pad(' ', self.width, len)!;
|
||||
continue;
|
||||
}
|
||||
OutputFn out_fn = self.out_fn;
|
||||
self.out_fn = (OutputFn)&out_null_fn;
|
||||
usz len = self.out_str(current)!;
|
||||
self.out_fn = out_fn;
|
||||
total_len += self.pad(' ', self.width, len)!;
|
||||
total_len += self.out_str(current)!;
|
||||
continue;
|
||||
case 'p':
|
||||
self.flags.zeropad = true;
|
||||
self.flags.hash = true;
|
||||
base = 16;
|
||||
default:
|
||||
self.first_err(PrintFault.INVALID_FORMAT);
|
||||
total_len += self.out_substr("<BAD FORMAT>")!;
|
||||
continue;
|
||||
}
|
||||
if (base != 10)
|
||||
{
|
||||
self.flags.plus = false;
|
||||
self.flags.space = false;
|
||||
}
|
||||
// ignore '0' flag when precision is given
|
||||
if (self.flags.precision) self.flags.zeropad = false;
|
||||
|
||||
bool is_neg;
|
||||
total_len += @wrap_bad(self, self.ntoa(int_from_any(current, &is_neg), is_neg, base))!;
|
||||
}
|
||||
// termination
|
||||
// out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
|
||||
|
||||
// return written chars without terminating \0
|
||||
if (self.first_fault) return self.first_fault?;
|
||||
return total_len;
|
||||
}
|
||||
|
||||
|
||||
fn usz! Formatter.print(&self, String str)
|
||||
{
|
||||
if (!self.out_fn)
|
||||
{
|
||||
// use null output function
|
||||
self.out_fn = &out_null_fn;
|
||||
}
|
||||
foreach (c : str) self.out(c)!;
|
||||
return self.idx;
|
||||
}
|
||||
@@ -1,32 +1,41 @@
|
||||
module std::io;
|
||||
import std::math;
|
||||
|
||||
const char[16] XDIGITS_H = "0123456789ABCDEF";
|
||||
const char[16] XDIGITS_L = "0123456789abcdef";
|
||||
|
||||
fn void! Formatter.left_adjust(Formatter* this, usz len) @local
|
||||
fault FormattingFault
|
||||
{
|
||||
if (!this.flags.left) return;
|
||||
for (usz l = len; l < this.width; l++) this.out(' ')!;
|
||||
BAD_FORMAT
|
||||
}
|
||||
|
||||
fn void! Formatter.right_adjust(Formatter* this, usz len) @local
|
||||
macro Formatter.first_err(&self, anyfault f)
|
||||
{
|
||||
if (this.flags.left) return;
|
||||
for (usz l = len; l < this.width; l++) this.out(' ')!;
|
||||
if (self.first_fault) return self.first_fault;
|
||||
self.first_fault = f;
|
||||
return f;
|
||||
}
|
||||
|
||||
fn usz! Formatter.adjust(&self, usz len) @local
|
||||
{
|
||||
if (!self.flags.left) return 0;
|
||||
return self.pad(' ', self.width, len);
|
||||
}
|
||||
|
||||
fn uint128! int_from_any(any arg, bool *is_neg) @private
|
||||
{
|
||||
*is_neg = false;
|
||||
if (arg.type.kindof == TypeKind.POINTER)
|
||||
switch (arg.type.kindof)
|
||||
{
|
||||
return (uint128)(uptr)*(void**)arg.ptr;
|
||||
case TypeKind.POINTER:
|
||||
*is_neg = false;
|
||||
return (uint128)(uptr)*(void**)arg.ptr;
|
||||
case TypeKind.DISTINCT:
|
||||
case TypeKind.ENUM:
|
||||
return int_from_any(arg.as_inner(), is_neg);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (arg.type.kindof == TypeKind.DISTINCT)
|
||||
{
|
||||
return int_from_any(any { arg.ptr, arg.type.inner }, is_neg);
|
||||
}
|
||||
*is_neg = false;
|
||||
switch (arg)
|
||||
{
|
||||
case bool:
|
||||
@@ -44,8 +53,8 @@ fn uint128! int_from_any(any arg, bool *is_neg) @private
|
||||
long val = *arg;
|
||||
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
|
||||
case int128:
|
||||
int128 val = *arg;
|
||||
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
|
||||
int128 val = *arg;
|
||||
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
|
||||
case char:
|
||||
return *arg;
|
||||
case ushort:
|
||||
@@ -63,7 +72,7 @@ fn uint128! int_from_any(any arg, bool *is_neg) @private
|
||||
double d = *arg;
|
||||
return (uint128)((*is_neg = d < 0) ? -d : d);
|
||||
default:
|
||||
return PrintFault.INVALID_ARGUMENT_TYPE?;
|
||||
return FormattingFault.BAD_FORMAT?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,12 +81,9 @@ fn FloatType! float_from_any(any arg) @private
|
||||
$if env::F128_SUPPORT:
|
||||
if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr);
|
||||
$endif
|
||||
$if env::F16_SUPPORT:
|
||||
if (arg.type == float16.typeid) return *((float16*)arg.ptr);
|
||||
$endif
|
||||
if (arg.type.kindof == TypeKind.DISTINCT)
|
||||
{
|
||||
return float_from_any(any { arg.ptr, arg.type.inner });
|
||||
return float_from_any(arg.as_inner());
|
||||
}
|
||||
switch (arg)
|
||||
{
|
||||
@@ -92,7 +98,7 @@ fn FloatType! float_from_any(any arg) @private
|
||||
case long:
|
||||
return *arg;
|
||||
case int128:
|
||||
return *arg;
|
||||
return *arg;
|
||||
case char:
|
||||
return *arg;
|
||||
case ushort:
|
||||
@@ -102,46 +108,45 @@ fn FloatType! float_from_any(any arg) @private
|
||||
case ulong:
|
||||
return *arg;
|
||||
case uint128:
|
||||
return *arg;
|
||||
return *arg;
|
||||
case float:
|
||||
return (FloatType)*arg;
|
||||
case double:
|
||||
return (FloatType)*arg;
|
||||
default:
|
||||
return PrintFault.INVALID_ARGUMENT_TYPE?;
|
||||
return FormattingFault.BAD_FORMAT?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a simple integer value, typically for formatting.
|
||||
*
|
||||
* @param [inout] len_ptr "the length remaining."
|
||||
* @param [in] buf "the buf to read from."
|
||||
* @param maxlen "the maximum len that can be read."
|
||||
* @return "The result of the atoi."
|
||||
**/
|
||||
<*
|
||||
Read a simple integer value, typically for formatting.
|
||||
|
||||
@param [inout] len_ptr "the length remaining."
|
||||
@param [in] buf "the buf to read from."
|
||||
@param maxlen "the maximum len that can be read."
|
||||
@return "The result of the atoi."
|
||||
*>
|
||||
fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
|
||||
{
|
||||
uint i = 0;
|
||||
usz len = *len_ptr;
|
||||
while (len < maxlen)
|
||||
{
|
||||
char c = buf[len];
|
||||
if (c < '0' || c > '9') break;
|
||||
i = i * 10 + c - '0';
|
||||
len++;
|
||||
}
|
||||
*len_ptr = len;
|
||||
return i;
|
||||
{
|
||||
char c = buf[len];
|
||||
if (!c.is_digit()) break;
|
||||
i = i * 10 + c - '0';
|
||||
len++;
|
||||
}
|
||||
*len_ptr = len;
|
||||
return i;
|
||||
}
|
||||
|
||||
fn void! Formatter.out_substr(Formatter *this, String str) @private
|
||||
fn usz! Formatter.out_substr(&self, String str) @private
|
||||
{
|
||||
usz l = conv::utf8_codepoints(str);
|
||||
uint prec = this.prec;
|
||||
if (this.flags.precision && l < prec) l = prec;
|
||||
this.right_adjust(' ')!;
|
||||
uint prec = self.prec;
|
||||
if (self.flags.precision && l < prec) l = prec;
|
||||
usz index = 0;
|
||||
usz chars = str.len;
|
||||
char* ptr = str.ptr;
|
||||
@@ -149,16 +154,18 @@ fn void! Formatter.out_substr(Formatter *this, String str) @private
|
||||
{
|
||||
char c = ptr[index];
|
||||
// Break if we have precision set and we ran out...
|
||||
if (c & 0xC0 != 0x80 && this.flags.precision && !prec--) break;
|
||||
this.out(c)!;
|
||||
index++;
|
||||
if (c & 0xC0 != 0x80 && self.flags.precision && !prec--) break;
|
||||
self.out(c)!;
|
||||
index++;
|
||||
}
|
||||
return this.left_adjust(l);
|
||||
return index;
|
||||
}
|
||||
|
||||
fn void! Formatter.pad(Formatter* this, char c, isz width, isz len) @inline
|
||||
fn usz! Formatter.pad(&self, char c, isz width, isz len) @inline
|
||||
{
|
||||
for (isz i = len; i < width; i++) this.out(c)!;
|
||||
isz delta = width - len;
|
||||
for (isz i = 0; i < delta; i++) self.out(c)!;
|
||||
return max(0, delta);
|
||||
}
|
||||
|
||||
fn char* fmt_u(uint128 x, char* s)
|
||||
@@ -168,9 +175,10 @@ fn char* fmt_u(uint128 x, char* s)
|
||||
return s;
|
||||
}
|
||||
|
||||
fn void! Formatter.out_chars(Formatter* this, char[] s)
|
||||
fn usz! Formatter.out_chars(&self, char[] s)
|
||||
{
|
||||
foreach (c : s) this.out(c)!;
|
||||
foreach (c : s) self.out(c)!;
|
||||
return s.len;
|
||||
}
|
||||
|
||||
enum FloatFormatting
|
||||
@@ -181,35 +189,37 @@ enum FloatFormatting
|
||||
HEX
|
||||
}
|
||||
|
||||
fn void! Formatter.etoa(Formatter* this, double y) => this.floatformat(EXPONENTIAL, y);
|
||||
fn void! Formatter.ftoa(Formatter* this, double y) => this.floatformat(FLOAT, y);
|
||||
fn void! Formatter.gtoa(Formatter* this, double y) => this.floatformat(ADAPTIVE, y);
|
||||
fn void! Formatter.atoa(Formatter* this, double y) => this.floatformat(HEX, y);
|
||||
fn usz! Formatter.etoa(&self, double y) => self.floatformat(EXPONENTIAL, y);
|
||||
fn usz! Formatter.ftoa(&self, double y) => self.floatformat(FLOAT, y);
|
||||
fn usz! Formatter.gtoa(&self, double y) => self.floatformat(ADAPTIVE, y);
|
||||
fn usz! Formatter.atoa(&self, double y) => self.floatformat(HEX, y);
|
||||
|
||||
fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, double y) @private
|
||||
fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @private
|
||||
{
|
||||
// This code is heavily based on musl's printf code
|
||||
const BUF_SIZE = (math::DOUBLE_MANT_DIG + 28) / 29 + 1
|
||||
+ (math::DOUBLE_MAX_EXP + math::DOUBLE_MANT_DIG + 28 + 8) / 9;
|
||||
uint[BUF_SIZE] big;
|
||||
bool is_neg = false;
|
||||
if (math::signbit(y))
|
||||
{
|
||||
is_neg = true;
|
||||
y = -y;
|
||||
}
|
||||
int pl = is_neg || this.flags.plus ? 1 : 0;
|
||||
// Print inf/nan
|
||||
bool is_neg = false;
|
||||
if (math::signbit(y))
|
||||
{
|
||||
is_neg = true;
|
||||
y = -y;
|
||||
}
|
||||
isz pl = is_neg || self.flags.plus ? 1 : 0;
|
||||
// Print inf/nan
|
||||
if (!math::is_finite(y))
|
||||
{
|
||||
usz len;
|
||||
// Add padding
|
||||
if (!this.flags.left) this.pad(' ', this.width, 3 + pl)!;
|
||||
String s = this.flags.uppercase ? "INF" : "inf";
|
||||
if (y != y) this.flags.uppercase ? "NAN" : "nan";
|
||||
if (pl) this.out(is_neg ? '-' : '+')!;
|
||||
this.out_chars(s)!;
|
||||
if (this.flags.left) this.pad(' ', this.width, 3 + pl)!;
|
||||
return;
|
||||
if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
|
||||
String s = self.flags.uppercase ? "INF" : "inf";
|
||||
if (math::is_nan(y)) s = self.flags.uppercase ? "NAN" : "nan";
|
||||
len += s.len;
|
||||
if (pl) len += self.out(is_neg ? '-' : '+')!;
|
||||
len += self.out_chars(s)!;
|
||||
if (self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
|
||||
return len;
|
||||
}
|
||||
// Rescale
|
||||
int e2;
|
||||
@@ -220,7 +230,7 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
char* ebuf = 12 + (char*)&ebuf0;
|
||||
char[9 + math::DOUBLE_MANT_DIG / 4] buf_array;
|
||||
char* buf = &buf_array;
|
||||
isz p = this.flags.precision ? this.prec : -1;
|
||||
isz p = self.flags.precision ? self.prec : -1;
|
||||
if (formatting == HEX)
|
||||
{
|
||||
double round = 8.0;
|
||||
@@ -229,50 +239,51 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
if (p > 0 && p < math::DOUBLE_MANT_DIG / 4 - 1)
|
||||
{
|
||||
int re = math::DOUBLE_MANT_DIG / 4 - 1 - (int)p;
|
||||
round *= 1 << (math::DOUBLE_MANT_DIG % 4);
|
||||
while (re--) round *= 16;
|
||||
if (is_neg)
|
||||
{
|
||||
y = -y;
|
||||
round *= 1 << (math::DOUBLE_MANT_DIG % 4);
|
||||
while (re--) round *= 16;
|
||||
if (is_neg)
|
||||
{
|
||||
y = -y;
|
||||
y -= round;
|
||||
y += round;
|
||||
y = -y;
|
||||
}
|
||||
else
|
||||
{
|
||||
y += round;
|
||||
y -= round;
|
||||
}
|
||||
y += round;
|
||||
y = -y;
|
||||
}
|
||||
else
|
||||
{
|
||||
y += round;
|
||||
y -= round;
|
||||
}
|
||||
}
|
||||
// Reverse print
|
||||
char* estr = fmt_u(e2 < 0 ? (int128)-e2 : (int128)e2, ebuf);
|
||||
if (estr == ebuf) *--estr = '0';
|
||||
*--estr = (e2 < 0 ? '-' : '+');
|
||||
*--estr = this.flags.uppercase ? 'P' : 'p';
|
||||
*--estr = self.flags.uppercase ? 'P' : 'p';
|
||||
char* s = buf;
|
||||
char* xdigits = this.flags.uppercase ? &XDIGITS_H : &XDIGITS_L;
|
||||
char* xdigits = self.flags.uppercase ? &XDIGITS_H : &XDIGITS_L;
|
||||
do
|
||||
{
|
||||
int x = (int)y;
|
||||
*s++ = xdigits[x];
|
||||
y = 16 * (y - x);
|
||||
if (s - buf == 1 && (y || p > 0 || this.flags.hash)) *s++ = '.';
|
||||
if (s - buf == 1 && (y || p > 0 || self.flags.hash)) *s++ = '.';
|
||||
} while (y);
|
||||
isz outlen = s - buf;
|
||||
isz explen = ebuf - estr;
|
||||
if (p > int.max - 2 - explen - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
usz len;
|
||||
usz l = p && outlen - 2 < p
|
||||
? p + 2 + explen
|
||||
: outlen + explen;
|
||||
if (!this.flags.left && !this.flags.zeropad) this.pad(' ', this.width, pl + l)!;
|
||||
if (is_neg || this.flags.plus) this.out(is_neg ? '-' : '+')!;
|
||||
this.out_chars(this.flags.uppercase ? "0X" : "0x")!;
|
||||
if (this.flags.zeropad) this.pad('0', this.width, pl + l)!;
|
||||
this.out_chars(buf[:outlen])!;
|
||||
this.pad('0', l - outlen - explen, 0)!;
|
||||
this.out_chars(estr[:explen])!;
|
||||
if (this.flags.left) this.pad(' ', this.width, pl + l)!;
|
||||
return;
|
||||
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
|
||||
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
|
||||
len += self.out_chars(self.flags.uppercase ? "0X" : "0x")!;
|
||||
if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
|
||||
len += self.out_chars(buf[:outlen])!;
|
||||
len += self.pad('0', l - outlen - explen, 0)!;
|
||||
len += self.out_chars(estr[:explen])!;
|
||||
if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;
|
||||
return len;
|
||||
}
|
||||
if (p < 0) p = 6;
|
||||
if (y)
|
||||
@@ -405,7 +416,7 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
formatting = EXPONENTIAL;
|
||||
p--;
|
||||
}
|
||||
if (!this.flags.hash)
|
||||
if (!self.flags.hash)
|
||||
{
|
||||
// Count trailing zeros in last place
|
||||
if (z > a && z[-1])
|
||||
@@ -427,8 +438,8 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
}
|
||||
}
|
||||
}
|
||||
if (p > int.max - 1 - (isz)(p || this.flags.hash)) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
int l = (int)(1 + p + (isz)(p || this.flags.hash));
|
||||
if (p > int.max - 1 - (isz)(p || self.flags.hash)) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
int l = (int)(1 + p + (isz)(p || self.flags.hash));
|
||||
char* estr @noinit;
|
||||
if (formatting == FLOAT)
|
||||
{
|
||||
@@ -440,14 +451,15 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
estr = fmt_u((uint128)(e < 0 ? -e : e), ebuf);
|
||||
while (ebuf - estr < 2) (--estr)[0] = '0';
|
||||
*--estr = (e < 0 ? '-' : '+');
|
||||
*--estr = this.flags.uppercase ? 'E' : 'e';
|
||||
*--estr = self.flags.uppercase ? 'E' : 'e';
|
||||
if (ebuf - estr > (isz)int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
l += (int)(ebuf - estr);
|
||||
}
|
||||
if (l > int.max - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
if (!this.flags.left && !this.flags.zeropad) this.pad(' ', this.width, pl + l)!;
|
||||
if (is_neg || this.flags.plus) this.out(is_neg ? '-' : '+')!;
|
||||
if (this.flags.zeropad) this.pad('0', this.width, pl + l)!;
|
||||
usz len;
|
||||
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
|
||||
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
|
||||
if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
|
||||
if (formatting == FLOAT)
|
||||
{
|
||||
if (a > r) a = r;
|
||||
@@ -462,57 +474,57 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
case s == buf + 9:
|
||||
*--s = '0';
|
||||
}
|
||||
this.out_chars(s[:buf + 9 - s])!;
|
||||
len += self.out_chars(s[:buf + 9 - s])!;
|
||||
}
|
||||
if (p || this.flags.hash) this.out('.')!;
|
||||
if (p || self.flags.hash) len += self.out('.')!;
|
||||
for (; d < z && p > 0; d++, p -= 9)
|
||||
{
|
||||
char* s = fmt_u(*d, buf + 9);
|
||||
while (s > buf) *--s = '0';
|
||||
this.out_chars(s[:math::min((isz)9, p)])!;
|
||||
len += self.out_chars(s[:math::min((isz)9, p)])!;
|
||||
}
|
||||
this.pad('0', p + 9, 9)!;
|
||||
len += self.pad('0', p + 9, 9)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (z <= a) z = a + 1;
|
||||
for (uint* d = a; d < z && p >= 0; d++)
|
||||
{
|
||||
for (uint* d = a; d < z && p >= 0; d++)
|
||||
{
|
||||
char* s = fmt_u(*d, buf + 9);
|
||||
if (s == buf + 9) (--s)[0] = '0';
|
||||
if (d != a)
|
||||
{
|
||||
while (s > buf) (--s)[0] = '0';
|
||||
}
|
||||
else
|
||||
{
|
||||
this.out(s++[0])!;
|
||||
if (p > 0 || this.flags.hash) this.out('.')!;
|
||||
}
|
||||
this.out_chars(s[:math::min(buf + 9 - s, p)])!;
|
||||
p -= buf + 9 - s;
|
||||
}
|
||||
this.pad('0', p + 18, 18)!;
|
||||
this.out_chars(estr[:ebuf - estr])!;
|
||||
if (d != a)
|
||||
{
|
||||
while (s > buf) (--s)[0] = '0';
|
||||
}
|
||||
else
|
||||
{
|
||||
len += self.out(s++[0])!;
|
||||
if (p > 0 || self.flags.hash) len += self.out('.')!;
|
||||
}
|
||||
len += self.out_chars(s[:math::min(buf + 9 - s, p)])!;
|
||||
p -= buf + 9 - s;
|
||||
}
|
||||
len += self.pad('0', p + 18, 18)!;
|
||||
len += self.out_chars(estr[:ebuf - estr])!;
|
||||
}
|
||||
|
||||
if (this.flags.left) this.pad(' ', this.width, pl + l)!;
|
||||
if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;
|
||||
|
||||
return;
|
||||
return len;
|
||||
}
|
||||
|
||||
fn void! Formatter.ntoa(Formatter* this, uint128 value, bool negative, uint base) @private
|
||||
fn usz! Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
|
||||
{
|
||||
char[PRINTF_NTOA_BUFFER_SIZE] buf @noinit;
|
||||
usz len = 0;
|
||||
usz len;
|
||||
|
||||
// no hash for 0 values
|
||||
if (!value) this.flags.hash = false;
|
||||
if (!value) self.flags.hash = false;
|
||||
|
||||
// write if precision != 0 or value is != 0
|
||||
if (!this.flags.precision || value)
|
||||
if (!self.flags.precision || value)
|
||||
{
|
||||
char past_10 = (this.flags.uppercase ? 'A' : 'a') - 10;
|
||||
char past_10 = (self.flags.uppercase ? 'A' : 'a') - 10;
|
||||
do
|
||||
{
|
||||
if (len >= PRINTF_NTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
@@ -522,21 +534,21 @@ fn void! Formatter.ntoa(Formatter* this, uint128 value, bool negative, uint base
|
||||
}
|
||||
while (value);
|
||||
}
|
||||
return this.ntoa_format((String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
|
||||
return self.ntoa_format((String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
|
||||
}
|
||||
|
||||
fn void! Formatter.ntoa_format(Formatter* this, String buf, usz len, bool negative, uint base) @private
|
||||
fn usz! Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint base) @private
|
||||
{
|
||||
// pad leading zeros
|
||||
if (!this.flags.left)
|
||||
if (!self.flags.left)
|
||||
{
|
||||
if (this.width && this.flags.zeropad && (negative || this.flags.plus || this.flags.space)) this.width--;
|
||||
while (len < this.prec)
|
||||
if (self.width && self.flags.zeropad && (negative || self.flags.plus || self.flags.space)) self.width--;
|
||||
while (len < self.prec)
|
||||
{
|
||||
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
buf[len++] = '0';
|
||||
}
|
||||
while (this.flags.zeropad && len < this.width)
|
||||
while (self.flags.zeropad && len < self.width)
|
||||
{
|
||||
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
buf[len++] = '0';
|
||||
@@ -544,9 +556,9 @@ fn void! Formatter.ntoa_format(Formatter* this, String buf, usz len, bool negati
|
||||
}
|
||||
|
||||
// handle hash
|
||||
if (this.flags.hash && base != 10)
|
||||
if (self.flags.hash && base != 10)
|
||||
{
|
||||
if (!this.flags.precision && len && len == this.prec && len == this.width)
|
||||
if (!self.flags.precision && len && len == self.prec && len == self.width)
|
||||
{
|
||||
len--;
|
||||
if (len) len--;
|
||||
@@ -557,11 +569,11 @@ fn void! Formatter.ntoa_format(Formatter* this, String buf, usz len, bool negati
|
||||
switch (base)
|
||||
{
|
||||
case 16:
|
||||
buf[len++] = this.flags.uppercase ? 'X' : 'x';
|
||||
buf[len++] = self.flags.uppercase ? 'X' : 'x';
|
||||
case 8:
|
||||
buf[len++] = this.flags.uppercase ? 'O' : 'o';
|
||||
buf[len++] = self.flags.uppercase ? 'O' : 'o';
|
||||
case 2:
|
||||
buf[len++] = this.flags.uppercase ? 'B' : 'b';
|
||||
buf[len++] = self.flags.uppercase ? 'B' : 'b';
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
@@ -574,94 +586,85 @@ fn void! Formatter.ntoa_format(Formatter* this, String buf, usz len, bool negati
|
||||
case negative:
|
||||
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
buf[len++] = '-';
|
||||
case this.flags.plus:
|
||||
case self.flags.plus:
|
||||
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
buf[len++] = '+';
|
||||
case this.flags.space:
|
||||
case self.flags.space:
|
||||
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
buf[len++] = ' ';
|
||||
}
|
||||
if (!len) return;
|
||||
return this.out_reverse(buf[:len]);
|
||||
if (len) self.out_reverse(buf[:len])!;
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
fn void! Formatter.ntoa_any(Formatter* this, any arg, uint base) @private
|
||||
fn usz! Formatter.ntoa_any(&self, any arg, uint base) @private
|
||||
{
|
||||
bool is_neg;
|
||||
uint128 val = int_from_any(arg, &is_neg)!!;
|
||||
return this.ntoa(val, is_neg, base) @inline;
|
||||
return self.ntoa(int_from_any(arg, &is_neg)!!, is_neg, base) @inline;
|
||||
}
|
||||
|
||||
fn void! Formatter.out_char(Formatter* this, any arg) @private
|
||||
fn usz! Formatter.out_char(&self, any arg) @private
|
||||
{
|
||||
usz len = 1;
|
||||
uint l = 1;
|
||||
// pre padding
|
||||
this.right_adjust(l)!;
|
||||
// char output
|
||||
Char32 c = types::any_to_int(arg, uint) ?? 0xFFFD;
|
||||
len += self.adjust(l)!;
|
||||
// char output
|
||||
Char32 c = types::any_to_int(arg, uint) ?? 0xFFFD;
|
||||
switch (true)
|
||||
{
|
||||
case c < 0x7f:
|
||||
this.out((char)c)!;
|
||||
self.out((char)c)!;
|
||||
case c < 0x7ff:
|
||||
this.out((char)(0xC0 | c >> 6))!;
|
||||
this.out((char)(0x80 | (c & 0x3F)))!;
|
||||
case c < 0xffff:
|
||||
this.out((char)(0xE0 | c >> 12))!;
|
||||
this.out((char)(0x80 | (c >> 6 & 0x3F)))!;
|
||||
this.out((char)(0x80 | (c & 0x3F)))!;
|
||||
default:
|
||||
this.out((char)(0xF0 | c >> 18))!;
|
||||
this.out((char)(0x80 | (c >> 12 & 0x3F)))!;
|
||||
this.out((char)(0x80 | (c >> 6 & 0x3F)))!;
|
||||
this.out((char)(0x80 | (c & 0x3F)))!;
|
||||
self.out((char)(0xC0 | c >> 6))!;
|
||||
self.out((char)(0x80 | (c & 0x3F)))!;
|
||||
case c < 0xffff:
|
||||
self.out((char)(0xE0 | c >> 12))!;
|
||||
self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
|
||||
self.out((char)(0x80 | (c & 0x3F)))!;
|
||||
default:
|
||||
self.out((char)(0xF0 | c >> 18))!;
|
||||
self.out((char)(0x80 | (c >> 12 & 0x3F)))!;
|
||||
self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
|
||||
self.out((char)(0x80 | (c & 0x3F)))!;
|
||||
}
|
||||
return this.left_adjust(l);
|
||||
len += self.adjust(l)!;
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
fn void! Formatter.out_reverse(Formatter* this, char[] buf) @private
|
||||
fn usz! Formatter.out_reverse(&self, char[] buf) @private
|
||||
{
|
||||
usz buffer_start_idx = this.idx;
|
||||
usz n;
|
||||
usz buffer_start_idx = self.idx;
|
||||
usz len = buf.len;
|
||||
// pad spaces up to given width
|
||||
if (!this.flags.left && !this.flags.zeropad)
|
||||
{
|
||||
for (usz i = len; i < this.width; i++)
|
||||
{
|
||||
this.out(' ')!;
|
||||
}
|
||||
}
|
||||
// reverse string
|
||||
while (len) this.out(buf[--len])!;
|
||||
// pad spaces up to given width
|
||||
if (!self.flags.zeropad && !self.flags.left)
|
||||
{
|
||||
n += self.pad(' ', self.width, len)!;
|
||||
}
|
||||
// reverse string
|
||||
while (len) n += self.out(buf[--len])!;
|
||||
|
||||
// append pad spaces up to given width
|
||||
return this.left_adjust(this.idx - buffer_start_idx);
|
||||
n += self.adjust(n)!;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn void! printf_advance_format(usz format_len, usz *index_ptr) @inline @private
|
||||
{
|
||||
usz val = ++(*index_ptr);
|
||||
if (val >= format_len) return FormattingFault.UNTERMINATED_FORMAT?;
|
||||
}
|
||||
|
||||
fn any! next_any(any* args_ptr, usz args_len, usz* arg_index_ptr) @inline @private
|
||||
{
|
||||
if (*arg_index_ptr >= args_len) return FormattingFault.MISSING_ARG?;
|
||||
return args_ptr[(*arg_index_ptr)++];
|
||||
}
|
||||
|
||||
fn int! printf_parse_format_field(
|
||||
any* args_ptr, usz args_len, usz* args_index_ptr,
|
||||
char* format_ptr, usz format_len, usz* index_ptr) @inline @private
|
||||
{
|
||||
char c = format_ptr[*index_ptr];
|
||||
if (c >= '0' && c <= '9') return simple_atoi(format_ptr, format_len, index_ptr);
|
||||
if (c.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr);
|
||||
if (c != '*') return 0;
|
||||
printf_advance_format(format_len, index_ptr)!;
|
||||
any val = next_any(args_ptr, args_len, args_index_ptr)!;
|
||||
if (!val.type.kindof.is_int()) return FormattingFault.INVALID_WIDTH_ARG?;
|
||||
uint! intval = types::any_to_int(val, int);
|
||||
return intval ?? FormattingFault.INVALID_WIDTH_ARG?;
|
||||
usz len = ++(*index_ptr);
|
||||
if (len >= format_len) return FormattingFault.BAD_FORMAT?;
|
||||
if (*args_index_ptr >= args_len) return FormattingFault.BAD_FORMAT?;
|
||||
any val = args_ptr[(*args_index_ptr)++];
|
||||
if (!val.type.kindof.is_int()) return FormattingFault.BAD_FORMAT?;
|
||||
uint! intval = types::any_to_int(val, int);
|
||||
return intval ?? FormattingFault.BAD_FORMAT?;
|
||||
}
|
||||
491
lib/std/io/io.c3
491
lib/std/io/io.c3
@@ -4,132 +4,407 @@
|
||||
module std::io;
|
||||
import libc;
|
||||
|
||||
struct File
|
||||
{
|
||||
CFile file;
|
||||
}
|
||||
|
||||
enum Seek
|
||||
{
|
||||
SET,
|
||||
CURSOR,
|
||||
END
|
||||
SET,
|
||||
CURSOR,
|
||||
END
|
||||
}
|
||||
|
||||
fault IoError
|
||||
{
|
||||
FILE_NOT_FOUND,
|
||||
FILE_NOT_VALID,
|
||||
INVALID_POSITION,
|
||||
OVERFLOW,
|
||||
FILE_IS_PIPE,
|
||||
FILE_EOF,
|
||||
INCOMPLETE_WRITE,
|
||||
BUSY,
|
||||
NO_PERMISSION,
|
||||
OUT_OF_SPACE,
|
||||
INVALID_PUSHBACK,
|
||||
EOF,
|
||||
CANNOT_READ_DIR,
|
||||
TOO_MANY_DESCRIPTORS,
|
||||
FILE_IS_DIR,
|
||||
READ_ONLY,
|
||||
FILE_NOT_DIR,
|
||||
SYMLINK_FAILED,
|
||||
ALREADY_EXISTS,
|
||||
NOT_SEEKABLE,
|
||||
NAME_TOO_LONG,
|
||||
WOULD_BLOCK,
|
||||
DIR_NOT_EMPTY,
|
||||
INTERRUPTED,
|
||||
GENERAL_ERROR,
|
||||
UNKNOWN_ERROR,
|
||||
UNSUPPORTED_OPERATION,
|
||||
ALREADY_EXISTS,
|
||||
BUSY,
|
||||
CANNOT_READ_DIR,
|
||||
DIR_NOT_EMPTY,
|
||||
EOF,
|
||||
FILE_CANNOT_DELETE,
|
||||
FILE_IS_DIR,
|
||||
FILE_IS_PIPE,
|
||||
FILE_NOT_DIR,
|
||||
FILE_NOT_FOUND,
|
||||
FILE_NOT_VALID,
|
||||
GENERAL_ERROR,
|
||||
ILLEGAL_ARGUMENT,
|
||||
INCOMPLETE_WRITE,
|
||||
INTERRUPTED,
|
||||
INVALID_POSITION,
|
||||
INVALID_PUSHBACK,
|
||||
NAME_TOO_LONG,
|
||||
NOT_SEEKABLE,
|
||||
NO_PERMISSION,
|
||||
OUT_OF_SPACE,
|
||||
OVERFLOW,
|
||||
READ_ONLY,
|
||||
SYMLINK_FAILED,
|
||||
TOO_MANY_DESCRIPTORS,
|
||||
UNEXPECTED_EOF,
|
||||
UNKNOWN_ERROR,
|
||||
UNSUPPORTED_OPERATION,
|
||||
WOULD_BLOCK,
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Read from a stream (default is stdin) to the next "\n"
|
||||
or to the end of the stream, whatever comes first.
|
||||
"\r" will be filtered from the String.
|
||||
|
||||
@param stream `The stream to read from.`
|
||||
@require @is_instream(stream) `The stream must implement InStream.`
|
||||
@param [inout] allocator `the allocator to use.`
|
||||
@return `The string containing the data read.`
|
||||
*>
|
||||
macro String! readline(stream = io::stdin(), Allocator allocator = allocator::heap())
|
||||
{
|
||||
bool $is_stream = @typeis(stream, InStream);
|
||||
$if $is_stream:
|
||||
$typeof(&stream.read_byte) func = &stream.read_byte;
|
||||
char val = func((void*)stream)!;
|
||||
$else
|
||||
char val = stream.read_byte()!;
|
||||
$endif
|
||||
|
||||
if (val == '\n') return "";
|
||||
@pool(allocator)
|
||||
{
|
||||
DString str = dstring::temp_with_capacity(256);
|
||||
if (val != '\r') str.append(val);
|
||||
while (1)
|
||||
{
|
||||
$if $is_stream:
|
||||
char! c = func((void*)stream);
|
||||
$else
|
||||
char! c = stream.read_byte();
|
||||
$endif
|
||||
if (catch err = c)
|
||||
{
|
||||
if (err == IoError.EOF) break;
|
||||
return err?;
|
||||
}
|
||||
if (c == '\r') continue;
|
||||
if (c == '\n') break;
|
||||
str.append_char(c);
|
||||
}
|
||||
return str.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Reads a string, see `readline`, except the it is allocated
|
||||
on the temporary allocator and does not need to be freed.
|
||||
|
||||
@param stream `The stream to read from.`
|
||||
@require @is_instream(stream) `The stream must implement InStream.`
|
||||
@return `The temporary string containing the data read.`
|
||||
*>
|
||||
macro String! treadline(stream = io::stdin())
|
||||
{
|
||||
return readline(stream, allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Print a value to a stream.
|
||||
|
||||
@param out `the stream to print to`
|
||||
@param x `the value to print`
|
||||
@require @is_outstream(out) `The output must implement OutStream.`
|
||||
@return `the number of bytes printed.`
|
||||
*>
|
||||
macro usz! fprint(out, x)
|
||||
{
|
||||
var $Type = $typeof(x);
|
||||
$switch ($Type)
|
||||
$case String: return out.write(x);
|
||||
$case ZString: return out.write(x.str_view());
|
||||
$case DString: return out.write(x.str_view());
|
||||
$default:
|
||||
$if $assignable(x, String):
|
||||
return out.write((String)x);
|
||||
$else
|
||||
$if is_struct_with_default_print($Type):
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putstream_fn, &&(OutStream)out);
|
||||
return struct_to_format(x, &formatter, false);
|
||||
$else
|
||||
return fprintf(out, "%s", x);
|
||||
$endif
|
||||
$endif
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Prints using a 'printf'-style formatting string.
|
||||
See `printf` for details on formatting.
|
||||
|
||||
@param [inout] out `The OutStream to print to`
|
||||
@param [in] format `The printf-style format string`
|
||||
@return `the number of characters printed`
|
||||
*>
|
||||
fn usz! fprintf(OutStream out, String format, args...)
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putstream_fn, &out);
|
||||
return formatter.vprintf(format, args);
|
||||
}
|
||||
|
||||
<*
|
||||
Prints using a 'printf'-style formatting string,
|
||||
appending '\n' at the end. See `printf`.
|
||||
|
||||
@param [inout] out `The OutStream to print to`
|
||||
@param [in] format `The printf-style format string`
|
||||
@return `the number of characters printed`
|
||||
*>
|
||||
fn usz! fprintfn(OutStream out, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putstream_fn, &out);
|
||||
usz len = formatter.vprintf(format, args)!;
|
||||
out.write_byte('\n')!;
|
||||
if (&out.flush) out.flush()!;
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_outstream(out) "The output must implement OutStream"
|
||||
*>
|
||||
macro usz! fprintn(out, x = "")
|
||||
{
|
||||
usz len = fprint(out, x)!;
|
||||
out.write_byte('\n')!;
|
||||
$switch
|
||||
$case @typeid(out) == OutStream.typeid:
|
||||
if (&out.flush) out.flush()!;
|
||||
$case $defined(out.flush):
|
||||
out.flush()!;
|
||||
$endswitch
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
<*
|
||||
Print any value to stdout.
|
||||
*>
|
||||
macro void print(x)
|
||||
{
|
||||
(void)fprint(io::stdout(), x);
|
||||
}
|
||||
|
||||
<*
|
||||
Print any value to stdout, appending an '\n’ after.
|
||||
|
||||
@param x "The value to print"
|
||||
*>
|
||||
macro void printn(x = "")
|
||||
{
|
||||
(void)fprintn(io::stdout(), x);
|
||||
}
|
||||
|
||||
<*
|
||||
Print any value to stderr.
|
||||
*>
|
||||
macro void eprint(x)
|
||||
{
|
||||
(void)fprint(io::stderr(), x);
|
||||
}
|
||||
|
||||
<*
|
||||
Print any value to stderr, appending an '\n’ after.
|
||||
|
||||
@param x "The value to print"
|
||||
*>
|
||||
macro void eprintn(x)
|
||||
{
|
||||
(void)fprintn(io::stderr(), x);
|
||||
}
|
||||
|
||||
|
||||
fn void! out_putstream_fn(void* data, char c) @private
|
||||
{
|
||||
OutStream* stream = data;
|
||||
return (*stream).write_byte(c);
|
||||
}
|
||||
|
||||
fn void! out_putchar_fn(void* data @unused, char c) @private
|
||||
{
|
||||
libc::putchar(c);
|
||||
}
|
||||
|
||||
<*
|
||||
Prints using a 'printf'-style formatting string.
|
||||
To print integer numbers, use "%d" or "%x"/"%X,
|
||||
the latter gives the hexadecimal representation.
|
||||
|
||||
All types can be printed using "%s" which gives
|
||||
the default representation of the value.
|
||||
|
||||
To create a custom output for a type, implement
|
||||
the Printable interface.
|
||||
|
||||
@param [in] format `The printf-style format string`
|
||||
@return `the number of characters printed`
|
||||
*>
|
||||
fn usz! printf(String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putchar_fn);
|
||||
return formatter.vprintf(format, args);
|
||||
}
|
||||
|
||||
<*
|
||||
Prints using a 'printf'-style formatting string,
|
||||
appending '\n' at the end. See `printf`.
|
||||
|
||||
@param [in] format `The printf-style format string`
|
||||
@return `the number of characters printed`
|
||||
*>
|
||||
fn usz! printfn(String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putchar_fn);
|
||||
usz! len = formatter.vprintf(format, args);
|
||||
putchar('\n');
|
||||
io::stdout().flush()!;
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
<*
|
||||
Prints using a 'printf'-style formatting string
|
||||
to stderr.
|
||||
|
||||
@param [in] format `The printf-style format string`
|
||||
@return `the number of characters printed`
|
||||
*>
|
||||
fn usz! eprintf(String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
OutStream stream = stderr();
|
||||
formatter.init(&out_putstream_fn, &stream);
|
||||
return formatter.vprintf(format, args);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Prints using a 'printf'-style formatting string,
|
||||
to stderr appending '\n' at the end. See `printf`.
|
||||
|
||||
@param [in] format `The printf-style format string`
|
||||
@return `the number of characters printed`
|
||||
*>
|
||||
fn usz! eprintfn(String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
OutStream stream = stderr();
|
||||
formatter.init(&out_putstream_fn, &stream);
|
||||
usz! len = formatter.vprintf(format, args);
|
||||
stderr().write_byte('\n')!;
|
||||
stderr().flush()!;
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
<*
|
||||
Prints using a 'printf'-style formatting string,
|
||||
to a string buffer. See `printf`.
|
||||
|
||||
@param [inout] buffer `The buffer to print to`
|
||||
@param [in] format `The printf-style format string`
|
||||
@return `a slice formed from the "buffer" with the resulting length.`
|
||||
*>
|
||||
fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
BufferData data = { .buffer = buffer };
|
||||
formatter.init(&out_buffer_fn, &data);
|
||||
usz size = formatter.vprintf(format, args)!;
|
||||
return buffer[:data.written];
|
||||
}
|
||||
|
||||
// Used to print to a buffer.
|
||||
fn void! out_buffer_fn(void *data, char c) @private
|
||||
{
|
||||
BufferData *buffer_data = data;
|
||||
if (buffer_data.written >= buffer_data.buffer.len) return PrintFault.BUFFER_EXCEEDED?;
|
||||
buffer_data.buffer[buffer_data.written++] = c;
|
||||
}
|
||||
|
||||
// Used for buffer printing
|
||||
struct BufferData @private
|
||||
{
|
||||
char[] buffer;
|
||||
usz written;
|
||||
}
|
||||
|
||||
// Only available with LIBC
|
||||
module std::io @if (env::LIBC);
|
||||
import libc;
|
||||
|
||||
<*
|
||||
Libc `putchar`, prints a single character to stdout.
|
||||
*>
|
||||
fn void putchar(char c) @inline
|
||||
{
|
||||
libc::putchar(c);
|
||||
}
|
||||
|
||||
<*
|
||||
Get standard out.
|
||||
|
||||
@return `stdout as a File`
|
||||
*>
|
||||
fn File* stdout()
|
||||
{
|
||||
static File file;
|
||||
if (!file.file) file = file::from_handle(libc::stdout());
|
||||
return &file;
|
||||
}
|
||||
|
||||
<*
|
||||
Get standard err.
|
||||
|
||||
@return `stderr as a File`
|
||||
*>
|
||||
fn File* stderr()
|
||||
{
|
||||
static File file;
|
||||
if (!file.file) file = file::from_handle(libc::stderr());
|
||||
return &file;
|
||||
}
|
||||
|
||||
<*
|
||||
Get standard in.
|
||||
|
||||
@return `stdin as a File`
|
||||
*>
|
||||
fn File* stdin()
|
||||
{
|
||||
static File file;
|
||||
if (!file.file) file = file::from_handle(libc::stdin());
|
||||
return &file;
|
||||
}
|
||||
|
||||
module std::io @if(!env::LIBC);
|
||||
|
||||
File stdin_file;
|
||||
File stdout_file;
|
||||
File stderr_file;
|
||||
|
||||
fn void putchar(char c) @inline
|
||||
{
|
||||
libc::putchar(c);
|
||||
(void)stdout_file.write_byte(c);
|
||||
}
|
||||
|
||||
macro void print(x)
|
||||
fn File* stdout()
|
||||
{
|
||||
var $Type = $typeof(x);
|
||||
$switch ($Type)
|
||||
$case String:
|
||||
(void)stdout().print(x);
|
||||
$case ZString:
|
||||
(void)stdout().print(x.as_str());
|
||||
$case DString:
|
||||
(void)stdout().print(x.str());
|
||||
$default:
|
||||
$if @convertible(x, String):
|
||||
(void)stdout().print((String)x);
|
||||
$else
|
||||
(void)stdout().printf("%s", x);
|
||||
$endif
|
||||
$endswitch
|
||||
return &stdout_file;
|
||||
}
|
||||
|
||||
macro void printn(x = "")
|
||||
fn File* stderr()
|
||||
{
|
||||
var $Type = $typeof(x);
|
||||
$switch ($Type)
|
||||
$case String:
|
||||
(void)stdout().printn(x);
|
||||
$case ZString:
|
||||
(void)stdout().printn(x.as_str());
|
||||
$case DString:
|
||||
(void)stdout().printn(x.str());
|
||||
$default:
|
||||
$if @convertible(x, String):
|
||||
(void)stdout().printn((String)x);
|
||||
$else
|
||||
(void)stdout().printfn("%s", x);
|
||||
$endif
|
||||
$endswitch
|
||||
return &stderr_file;
|
||||
}
|
||||
|
||||
fn File stdout()
|
||||
fn File* stdin()
|
||||
{
|
||||
return { libc::stdout() };
|
||||
return &stdin_file;
|
||||
}
|
||||
|
||||
fn File stderr()
|
||||
{
|
||||
return { libc::stderr() };
|
||||
}
|
||||
|
||||
fn File stdin()
|
||||
{
|
||||
return { libc::stdin() };
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
error FileError
|
||||
{
|
||||
ulong errno;
|
||||
}
|
||||
|
||||
fn FileError errorFromErrno()
|
||||
{
|
||||
return FileError { };
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
pubic fn void! File.clearerr(File *file) @inline
|
||||
{
|
||||
clearerr(file->file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn void File.error(File *file) @inline
|
||||
{
|
||||
int err = ferror
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
module std::io::file;
|
||||
import libc;
|
||||
|
||||
fn File! open(String filename, String mode)
|
||||
{
|
||||
return { .file = os::native_fopen(filename, mode) };
|
||||
}
|
||||
|
||||
fn File! open_path(Path path, String mode)
|
||||
{
|
||||
return { .file = os::native_fopen(path.as_str(), mode) };
|
||||
}
|
||||
|
||||
/**
|
||||
* @require file.file != null
|
||||
**/
|
||||
fn void! File.reopen(File* file, String filename, String mode)
|
||||
{
|
||||
file.file = os::native_freopen(file.file, filename, mode)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require file.file != null
|
||||
**/
|
||||
fn usz! File.seek(File file, isz offset, Seek seek_mode = Seek.SET)
|
||||
{
|
||||
os::native_fseek(file.file, offset, seek_mode)!;
|
||||
return os::native_ftell(file.file);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Implement later
|
||||
/**
|
||||
* @require file.file == null
|
||||
**/
|
||||
fn void! File.memopen(File* file, char[] data, String mode)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
file.file = libc::memopen(data.ptr, data.len, mode.zstr_tcopy(), file.file);
|
||||
// TODO errors
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @require file && file.file != null
|
||||
*/
|
||||
fn void! File.putc(File *file, char c)
|
||||
{
|
||||
if (!libc::fputc(c, file.file)) return IoError.FILE_EOF?;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require file != null
|
||||
*/
|
||||
fn void! File.close(File *file) @inline
|
||||
{
|
||||
if (file.file && libc::fclose(file.file))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ECONNRESET:
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID?;
|
||||
case errno::EINTR: return IoError.INTERRUPTED?;
|
||||
case errno::EDQUOT:
|
||||
case errno::EFAULT:
|
||||
case errno::EAGAIN:
|
||||
case errno::EFBIG:
|
||||
case errno::ENETDOWN:
|
||||
case errno::ENETUNREACH:
|
||||
case errno::ENOSPC:
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE?;
|
||||
default: return IoError.UNKNOWN_ERROR?;
|
||||
}
|
||||
}
|
||||
file.file = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require file && file.file
|
||||
*/
|
||||
fn bool File.eof(File* file) @inline
|
||||
{
|
||||
return libc::feof(file.file) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [in] buffer
|
||||
*/
|
||||
fn usz! File.read(File* file, char[] buffer)
|
||||
{
|
||||
return os::native_fread(file.file, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @param [&out] buffer
|
||||
* @require file.file `File must be initialized`
|
||||
*/
|
||||
fn usz! File.write(File file, char[] buffer)
|
||||
{
|
||||
return os::native_fwrite(file.file, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @require file.file `File must be initialized`
|
||||
*/
|
||||
fn usz! File.printn(File file, String string = "")
|
||||
{
|
||||
usz len = file.print(string)!;
|
||||
if (!libc::putc('\n', file.file)) return IoError.UNKNOWN_ERROR?;
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @require file.file `File must be initialized`
|
||||
*/
|
||||
fn usz! File.print(File file, String string)
|
||||
{
|
||||
usz len = string.len;
|
||||
if (len != file.write((char[])string)!) return IoError.UNKNOWN_ERROR?;
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @require file.file `File must be initialized`
|
||||
*/
|
||||
fn DString File.getline(File* file, Allocator* using = mem::heap())
|
||||
{
|
||||
DString s = dstring::new_with_capacity(120, using);
|
||||
while (!file.eof())
|
||||
{
|
||||
int c = libc::fgetc(file.file);
|
||||
if (c == -1) break;
|
||||
if (c == '\n') break;
|
||||
s.append_char((char)c);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @require file.file `File must be initialized`
|
||||
* @return "a zero terminated String (the pointer may be safely cast into a ZString)"
|
||||
*/
|
||||
fn String File.tgetline(File* file)
|
||||
{
|
||||
return file.getline(mem::temp()).zstr().as_str();
|
||||
}
|
||||
|
||||
fn char! File.getc(File* file)
|
||||
{
|
||||
int c = libc::fgetc(file.file);
|
||||
if (c == -1) return IoError.FILE_EOF?;
|
||||
return (char)c;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @require file.file `File must be initialized`
|
||||
*/
|
||||
fn void File.flush(File* file)
|
||||
{
|
||||
libc::fflush(file.file);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
module std::io::file;
|
||||
import libc;
|
||||
|
||||
|
||||
|
||||
fn bool is_file(String path)
|
||||
{
|
||||
return os::native_is_file(path);
|
||||
}
|
||||
|
||||
fn usz! get_size(String path)
|
||||
{
|
||||
return os::native_file_size(path);
|
||||
}
|
||||
@@ -1,427 +0,0 @@
|
||||
module std::io;
|
||||
import std::collections::map;
|
||||
import libc;
|
||||
|
||||
const int PRINTF_NTOA_BUFFER_SIZE = 256;
|
||||
|
||||
fault PrintFault
|
||||
{
|
||||
BUFFER_EXCEEDED,
|
||||
INTERNAL_BUFFER_EXCEEDED,
|
||||
INVALID_FORMAT_STRING,
|
||||
MISSING_ARG,
|
||||
INVALID_ARGUMENT_TYPE,
|
||||
}
|
||||
|
||||
fault FormattingFault
|
||||
{
|
||||
UNTERMINATED_FORMAT,
|
||||
MISSING_ARG,
|
||||
INVALID_WIDTH_ARG,
|
||||
INVALID_FORMAT_TYPE,
|
||||
}
|
||||
|
||||
def OutputFn = fn void!(char c, void* buffer);
|
||||
def FloatType = double;
|
||||
|
||||
fn String any.to_string(void* value, Allocator *using) @interface;
|
||||
fn void! any.to_format(void* value, Formatter* formatter) @interface;
|
||||
|
||||
fn usz! printf(String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putchar_fn);
|
||||
return formatter.vprintf(format, args);
|
||||
}
|
||||
|
||||
fn usz! printfn(String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putchar_fn);
|
||||
usz len = formatter.vprintf(format, args)!;
|
||||
putchar('\n');
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
BufferData data = { .buffer = buffer };
|
||||
formatter.init(&out_buffer_fn, &data);
|
||||
usz size = formatter.vprintf(format, args)!;
|
||||
return buffer[:data.written];
|
||||
}
|
||||
|
||||
fn usz! File.printf(File file, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_fputchar_fn, &file);
|
||||
return formatter.vprintf(format, args)!;
|
||||
}
|
||||
|
||||
fn usz! File.printfn(File file, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_fputchar_fn, &file);
|
||||
usz len = formatter.vprintf(format, args)!;
|
||||
file.putc('\n')!;
|
||||
file.flush();
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
fn usz! Formatter.printf(Formatter* this, String format, args...)
|
||||
{
|
||||
return this.vprintf(format, args) @inline;
|
||||
}
|
||||
|
||||
struct Formatter
|
||||
{
|
||||
void *data;
|
||||
OutputFn out_fn;
|
||||
struct
|
||||
{
|
||||
PrintFlags flags;
|
||||
uint width;
|
||||
uint prec;
|
||||
usz idx;
|
||||
}
|
||||
}
|
||||
|
||||
bitstruct PrintFlags : uint
|
||||
{
|
||||
bool zeropad : 0;
|
||||
bool left : 1;
|
||||
bool plus : 2;
|
||||
bool space : 3;
|
||||
bool hash : 4;
|
||||
bool uppercase : 5;
|
||||
bool precision : 6;
|
||||
}
|
||||
|
||||
fn void Formatter.init(Formatter* this, OutputFn out_fn, void* data = null)
|
||||
{
|
||||
*this = { .data = data, .out_fn = out_fn};
|
||||
}
|
||||
|
||||
fn void! Formatter.out(Formatter* this, char c) @private
|
||||
{
|
||||
this.out_fn(c, this.data)!;
|
||||
}
|
||||
|
||||
macro bool! Formatter.print_with_function(Formatter* this, any arg)
|
||||
{
|
||||
if (&arg.to_format)
|
||||
{
|
||||
PrintFlags old = this.flags;
|
||||
uint old_width = this.width;
|
||||
uint old_prec = this.prec;
|
||||
defer
|
||||
{
|
||||
this.flags = old;
|
||||
this.width = old_width;
|
||||
this.prec = old_prec;
|
||||
}
|
||||
arg.to_format(this)!;
|
||||
return true;
|
||||
}
|
||||
if (&arg.to_string)
|
||||
{
|
||||
PrintFlags old = this.flags;
|
||||
uint old_width = this.width;
|
||||
uint old_prec = this.prec;
|
||||
defer
|
||||
{
|
||||
this.flags = old;
|
||||
this.width = old_width;
|
||||
this.prec = old_prec;
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
this.out_substr(arg.to_string(mem::temp()))!;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void! Formatter.out_str(Formatter* this, any arg) @private
|
||||
{
|
||||
switch (arg.type.kindof)
|
||||
{
|
||||
case TYPEID:
|
||||
return this.out_substr("typeid");
|
||||
case VOID:
|
||||
return this.out_substr("void");
|
||||
case ANYFAULT:
|
||||
case FAULT:
|
||||
return this.out_substr((*(anyfault*)arg.ptr).nameof);
|
||||
case ANY:
|
||||
return this.out_str(*(any*)arg);
|
||||
case ENUM:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr(arg.type.names[types::any_to_int(arg, usz)!!]);
|
||||
case STRUCT:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr("<struct>");
|
||||
case UNION:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr("<union>");
|
||||
case BITSTRUCT:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr("<bitstruct>");
|
||||
case FUNC:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr("<function>");
|
||||
case OPTIONAL:
|
||||
unreachable();
|
||||
case DISTINCT:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
if (arg.type == DString.typeid)
|
||||
{
|
||||
return this.out_substr(((DString*)arg).str());
|
||||
}
|
||||
return this.out_str(any { arg.ptr, arg.type.inner });
|
||||
case POINTER:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.ntoa_any(arg, 16);
|
||||
case SIGNED_INT:
|
||||
case UNSIGNED_INT:
|
||||
return this.ntoa_any(arg, 10);
|
||||
case FLOAT:
|
||||
return this.ftoa(float_from_any(arg)!!);
|
||||
case ARRAY:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
// this is SomeType[*] so grab the "SomeType"
|
||||
typeid inner = arg.type.inner;
|
||||
usz size = inner.sizeof;
|
||||
usz len = arg.type.len;
|
||||
// Pretend this is a String
|
||||
void* ptr = (void*)arg.ptr;
|
||||
this.out('[')!;
|
||||
for (usz i = 0; i < len; i++)
|
||||
{
|
||||
if (i != 0) this.out_substr(", ")!;
|
||||
this.out_str(any { ptr, inner })!;
|
||||
ptr += size;
|
||||
}
|
||||
return this.out(']');
|
||||
case VECTOR:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
// this is SomeType[*] so grab the "SomeType"
|
||||
typeid inner = arg.type.inner;
|
||||
usz size = inner.sizeof;
|
||||
usz len = arg.type.len;
|
||||
// Pretend this is a String
|
||||
void* ptr = (void*)arg.ptr;
|
||||
this.out_substr("[<")!;
|
||||
for (usz i = 0; i < len; i++)
|
||||
{
|
||||
if (i != 0) this.out_substr(", ")!;
|
||||
this.out_str(any { ptr, inner })!;
|
||||
ptr += size;
|
||||
}
|
||||
return this.out_substr(">]");
|
||||
case SUBARRAY:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
// this is SomeType[] so grab the "SomeType"
|
||||
typeid inner = arg.type.inner;
|
||||
if (inner == char.typeid)
|
||||
{
|
||||
return this.out_substr(*(String*)arg);
|
||||
}
|
||||
usz size = inner.sizeof;
|
||||
// Pretend this is a String
|
||||
String* temp = (void*)arg.ptr;
|
||||
void* ptr = (void*)temp.ptr;
|
||||
usz len = temp.len;
|
||||
this.out('[')!;
|
||||
for (usz i = 0; i < len; i++)
|
||||
{
|
||||
if (i != 0) this.out_substr(", ")!;
|
||||
this.out_str(any { ptr, inner })!;
|
||||
ptr += size;
|
||||
}
|
||||
this.out(']')!;
|
||||
case BOOL:
|
||||
return this.out_substr(*(bool*)arg.ptr ? "true" : "false");
|
||||
default:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr("Invalid type");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn void! out_buffer_fn(char c, void *data) @private
|
||||
{
|
||||
BufferData *buffer_data = data;
|
||||
if (buffer_data.written >= buffer_data.buffer.len) return PrintFault.BUFFER_EXCEEDED?;
|
||||
buffer_data.buffer[buffer_data.written++] = c;
|
||||
}
|
||||
|
||||
fn void! out_null_fn(char c @unused, void* data @unused) @private
|
||||
{
|
||||
}
|
||||
|
||||
fn void! out_putchar_fn(char c, void* data @unused) @private
|
||||
{
|
||||
libc::putchar(c);
|
||||
}
|
||||
|
||||
fn void! out_fputchar_fn(char c, void* data) @private
|
||||
{
|
||||
File* f = data;
|
||||
f.putc(c)!;
|
||||
}
|
||||
|
||||
struct BufferData @private
|
||||
{
|
||||
char[] buffer;
|
||||
usz written;
|
||||
}
|
||||
|
||||
|
||||
fn usz! Formatter.vprintf(Formatter* this, String format, any[] anys)
|
||||
{
|
||||
if (!this.out_fn)
|
||||
{
|
||||
// use null output function
|
||||
this.out_fn = &out_null_fn;
|
||||
}
|
||||
usz format_len = format.len;
|
||||
usz variant_index = 0;
|
||||
for (usz i = 0; i < format_len; i++)
|
||||
{
|
||||
// format specifier? %[flags][width][.precision][length]
|
||||
char c = format[i];
|
||||
if (c != '%')
|
||||
{
|
||||
// no
|
||||
this.out(c)!;
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
if (i >= format_len) return PrintFault.INVALID_FORMAT_STRING?;
|
||||
c = format[i];
|
||||
if (c == '%')
|
||||
{
|
||||
this.out(c)!;
|
||||
continue;
|
||||
}
|
||||
// evaluate flags
|
||||
this.flags = {};
|
||||
while FLAG_EVAL: (true)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '0': this.flags.zeropad = true;
|
||||
case '-': this.flags.left = true;
|
||||
case '+': this.flags.plus = true;
|
||||
case ' ': this.flags.space = true;
|
||||
case '#': this.flags.hash = true;
|
||||
default: break FLAG_EVAL;
|
||||
}
|
||||
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING?;
|
||||
c = format[i];
|
||||
}
|
||||
// evaluate width field
|
||||
int w = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i)!;
|
||||
c = format[i];
|
||||
if (w < 0)
|
||||
{
|
||||
this.flags.left = true;
|
||||
w = -w;
|
||||
}
|
||||
this.width = w;
|
||||
// evaluate precision field
|
||||
this.prec = 0;
|
||||
if (c == '.')
|
||||
{
|
||||
this.flags.precision = true;
|
||||
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING?;
|
||||
int prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i)!;
|
||||
this.prec = prec < 0 ? 0 : prec;
|
||||
c = format[i];
|
||||
}
|
||||
|
||||
// evaluate specifier
|
||||
uint base = 0;
|
||||
if (variant_index >= anys.len) return PrintFault.MISSING_ARG?;
|
||||
any current = anys[variant_index++];
|
||||
switch (c)
|
||||
{
|
||||
case 'd':
|
||||
base = 10;
|
||||
this.flags.hash = false;
|
||||
case 'X' :
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'x' :
|
||||
base = 16;
|
||||
case 'O':
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'o' :
|
||||
base = 8;
|
||||
case 'B':
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'b' :
|
||||
base = 2;
|
||||
case 'A':
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'a':
|
||||
this.atoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'F' :
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'f':
|
||||
this.ftoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'E':
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'e':
|
||||
this.etoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'G':
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'g':
|
||||
this.gtoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'c':
|
||||
this.out_char(current)!;
|
||||
continue;
|
||||
case 's':
|
||||
this.out_str(current)!;
|
||||
continue;
|
||||
case 'p':
|
||||
this.flags.zeropad = true;
|
||||
this.flags.hash = true;
|
||||
base = 16;
|
||||
default:
|
||||
return PrintFault.INVALID_FORMAT_STRING?;
|
||||
}
|
||||
if (base != 10)
|
||||
{
|
||||
this.flags.plus = false;
|
||||
this.flags.space = false;
|
||||
}
|
||||
// ignore '0' flag when precision is given
|
||||
if (this.flags.precision) this.flags.zeropad = false;
|
||||
|
||||
bool is_neg;
|
||||
uint128 v = int_from_any(current, &is_neg)!!;
|
||||
|
||||
this.ntoa(v, is_neg, base)!;
|
||||
}
|
||||
// termination
|
||||
// out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
|
||||
|
||||
// return written chars without terminating \0
|
||||
return this.idx;
|
||||
}
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
module std::io;
|
||||
|
||||
def CloseStreamFn = fn void!(Stream*);
|
||||
def FlushStreamFn = fn void!(Stream*);
|
||||
def SeekStreamFn = fn usz!(Stream*, isz offset, Seek seek);
|
||||
def LenStreamFn = fn usz(Stream*);
|
||||
def AvailableStreamFn = fn usz(Stream*);
|
||||
def ReadStreamFn = fn usz!(Stream*, char[] bytes);
|
||||
def ReadFromStreamFn = fn usz!(Stream*, Stream*);
|
||||
def ReadByteStreamFn = fn char!(Stream*);
|
||||
def PushbackByteStreamFn = fn void!(Stream*);
|
||||
def WriteStreamFn = fn usz!(Stream*, char[] bytes);
|
||||
def WriteToStreamFn = fn usz!(Stream*, Stream* out);
|
||||
def WriteByteStreamFn = fn void!(Stream*, char c);
|
||||
def DestroyStreamFn = fn void!(Stream*);
|
||||
|
||||
struct StreamInterface
|
||||
{
|
||||
CloseStreamFn close_fn;
|
||||
FlushStreamFn flush_fn;
|
||||
SeekStreamFn seek_fn;
|
||||
LenStreamFn len_fn;
|
||||
AvailableStreamFn available_fn;
|
||||
ReadStreamFn read_fn;
|
||||
ReadFromStreamFn read_stream_fn;
|
||||
ReadByteStreamFn read_byte_fn;
|
||||
PushbackByteStreamFn pushback_byte_fn;
|
||||
WriteStreamFn write_fn;
|
||||
WriteToStreamFn write_stream_fn;
|
||||
WriteByteStreamFn write_byte_fn;
|
||||
DestroyStreamFn destroy_fn;
|
||||
}
|
||||
|
||||
struct Stream
|
||||
{
|
||||
StreamInterface *fns;
|
||||
void* data;
|
||||
}
|
||||
|
||||
fn bool Stream.supports_seek(Stream* s) @inline => (bool)s.fns.seek_fn;
|
||||
fn bool Stream.supports_available(Stream* s) @inline => s.fns.available_fn || s.fns.seek_fn;
|
||||
fn bool Stream.supports_len(Stream* s) @inline => s.fns.len_fn || s.fns.seek_fn;
|
||||
fn bool Stream.supports_read(Stream* s) @inline => s.fns.read_fn || s.fns.read_byte_fn;
|
||||
fn bool Stream.supports_read_from(Stream* s) @inline => (bool)s.fns.read_stream_fn;
|
||||
fn bool Stream.supports_write_to(Stream* s) @inline => (bool)s.fns.write_stream_fn;
|
||||
fn bool Stream.supports_pushback_byte(Stream* s) @inline => s.fns.pushback_byte_fn || s.fns.seek_fn;
|
||||
fn bool Stream.supports_write(Stream* s) @inline => s.fns.write_fn || s.fns.write_byte_fn;
|
||||
|
||||
fn void! Stream.destroy(Stream* s) @inline @maydiscard
|
||||
{
|
||||
if (s.fns.destroy_fn) return s.fns.destroy_fn(s);
|
||||
return s.close();
|
||||
}
|
||||
|
||||
fn void! Stream.close(Stream* s) @inline @maydiscard
|
||||
{
|
||||
if (CloseStreamFn func = s.fns.close_fn) return func(s);
|
||||
}
|
||||
|
||||
fn usz! Stream.seek(Stream* s, isz offset, Seek seek) @inline
|
||||
{
|
||||
if (SeekStreamFn func = s.fns.seek_fn) return func(s, offset, seek);
|
||||
return IoError.NOT_SEEKABLE?;
|
||||
}
|
||||
|
||||
fn usz! Stream.available(Stream* s) @inline
|
||||
{
|
||||
if (AvailableStreamFn func = s.fns.available_fn) return func(s);
|
||||
if (SeekStreamFn func = s.fns.seek_fn)
|
||||
{
|
||||
usz curr = func(s, 0, Seek.CURSOR)!;
|
||||
usz len = func(s, 0, Seek.END)!;
|
||||
func(s, curr, Seek.SET)!;
|
||||
return len - curr;
|
||||
}
|
||||
return IoError.NOT_SEEKABLE?;
|
||||
}
|
||||
|
||||
fn usz! Stream.read(Stream* s, char[] buffer)
|
||||
{
|
||||
if (ReadStreamFn func = s.fns.read_fn) return func(s, buffer);
|
||||
if (ReadByteStreamFn func = s.fns.read_byte_fn)
|
||||
{
|
||||
usz len = 0;
|
||||
foreach (&cptr : buffer)
|
||||
{
|
||||
char! c = func(s);
|
||||
if (catch err = c)
|
||||
{
|
||||
case IoError.EOF: return len;
|
||||
default: return err?;
|
||||
}
|
||||
*cptr = c;
|
||||
len++;
|
||||
}
|
||||
}
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn char! Stream.read_byte(Stream* s) @inline
|
||||
{
|
||||
if (ReadByteStreamFn func = s.fns.read_byte_fn) return func(s);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! Stream.write(Stream* s, char[] bytes) @inline
|
||||
{
|
||||
if (WriteStreamFn func = s.fns.write_fn) return func(s, bytes);
|
||||
if (WriteByteStreamFn func = s.fns.write_byte_fn)
|
||||
{
|
||||
foreach (c : bytes) func(s, c)!;
|
||||
return bytes.len;
|
||||
}
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn void! Stream.write_byte(Stream* s, char b) @inline
|
||||
{
|
||||
if (WriteByteStreamFn func = s.fns.write_byte_fn) return func(s, b);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! Stream.write_to(Stream* s, Stream* to) @inline
|
||||
{
|
||||
if (WriteToStreamFn func = s.fns.write_stream_fn) return func(s, to);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! Stream.read_from(Stream* s, Stream* from) @inline
|
||||
{
|
||||
if (ReadFromStreamFn func = s.fns.read_stream_fn) return func(s, from);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn void! Stream.flush(Stream* s) @inline @maydiscard
|
||||
{
|
||||
if (FlushStreamFn func = s.fns.flush_fn) return func(s);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! Stream.len(Stream* s) @inline
|
||||
{
|
||||
if (LenStreamFn func = s.fns.len_fn) return func(s);
|
||||
if (SeekStreamFn func = s.fns.seek_fn)
|
||||
{
|
||||
usz curr = func(s, 0, Seek.CURSOR)!;
|
||||
usz len = func(s, 0, Seek.END)!;
|
||||
func(s, curr, Seek.SET)!;
|
||||
return len;
|
||||
}
|
||||
return IoError.NOT_SEEKABLE?;
|
||||
}
|
||||
|
||||
fn void! Stream.pushback_byte(Stream* s) @inline
|
||||
{
|
||||
if (PushbackByteStreamFn func = s.fns.pushback_byte_fn) return func(s);
|
||||
if (SeekStreamFn func = s.fns.seek_fn)
|
||||
{
|
||||
func(s, -1, CURSOR)!;
|
||||
return;
|
||||
}
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn void! Stream.write_string(Stream* s, String str) @inline => (void)(s.write((char[])str)!);
|
||||
|
||||
fn usz! Stream.copy_to(Stream* s, Stream* dst, char[] buffer = {})
|
||||
{
|
||||
if (buffer.len) return copy_through_buffer(s, dst, buffer);
|
||||
if (WriteToStreamFn func = s.fns.write_stream_fn) return func(s, dst);
|
||||
if (ReadFromStreamFn func = dst.fns.read_stream_fn) return func(dst, s);
|
||||
$switch (env::MEMORY_ENV)
|
||||
$case NORMAL:
|
||||
@pool()
|
||||
{
|
||||
return copy_through_buffer(s, dst, tmalloc(char, 4096));
|
||||
};
|
||||
$case SMALL:
|
||||
@pool()
|
||||
{
|
||||
return copy_through_buffer(s, dst, tmalloc(char, 1024));
|
||||
};
|
||||
$case TINY:
|
||||
$case NONE:
|
||||
return copy_through_buffer(s, dst, &&(char[256]{}));
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro usz! copy_through_buffer(Stream* s, Stream* dst, char[] buffer) @local
|
||||
{
|
||||
usz total_copied;
|
||||
while (true)
|
||||
{
|
||||
usz! len = s.read(buffer);
|
||||
if (catch err = len)
|
||||
{
|
||||
case IoError.EOF: return total_copied;
|
||||
default: return err?;
|
||||
}
|
||||
if (!len) return total_copied;
|
||||
usz written = dst.write(buffer[:len])!;
|
||||
total_copied += len;
|
||||
if (written != len) return IoError.INCOMPLETE_WRITE?;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,30 @@
|
||||
module std::io::os;
|
||||
import libc;
|
||||
|
||||
|
||||
$switch
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_posix():
|
||||
|
||||
macro void! native_chdir(Path p)
|
||||
{
|
||||
if (posix::chdir(p.as_zstr()))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES: return IoError.NO_PERMISSION?;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
|
||||
case errno::ENOTDIR: return IoError.FILE_NOT_DIR?;
|
||||
case errno::ENOENT: return IoError.FILE_NOT_FOUND?;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
|
||||
default: return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_win32():
|
||||
import std::io::path, libc, std::os;
|
||||
|
||||
macro void! native_chdir(Path path)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
// TODO improve with better error handling.
|
||||
if (win32::win32_SetCurrentDirectoryW(path.as_str().to_temp_utf16()!!)) return;
|
||||
};
|
||||
return IoError.GENERAL_ERROR?;
|
||||
$switch
|
||||
$case env::POSIX:
|
||||
if (posix::chdir(path.as_zstr()))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES: return IoError.NO_PERMISSION?;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
|
||||
case errno::ENOTDIR: return IoError.FILE_NOT_DIR?;
|
||||
case errno::ENOENT: return IoError.FILE_NOT_FOUND?;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
|
||||
default: return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
}
|
||||
$case env::WIN32:
|
||||
@pool()
|
||||
{
|
||||
// TODO improve with better error handling.
|
||||
if (win32::setCurrentDirectoryW(path.str_view().to_temp_utf16()!!)) return;
|
||||
};
|
||||
return IoError.GENERAL_ERROR?;
|
||||
$default:
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
$default:
|
||||
|
||||
fn void! native_chdir(Path path)
|
||||
{
|
||||
unreachable("'getcwd' not available");
|
||||
}
|
||||
|
||||
$endswitch
|
||||
@@ -1,187 +0,0 @@
|
||||
module std::io::os;
|
||||
import libc;
|
||||
|
||||
def FopenFn = fn void*!(String, String);
|
||||
def FreopenFn = fn void*!(void*, String, String);
|
||||
def FcloseFn = fn void!(void*);
|
||||
def FseekFn = fn void!(void*, isz, Seek);
|
||||
def FtellFn = fn usz!(void*);
|
||||
def FwriteFn = fn usz!(void*, char[] buffer);
|
||||
def FreadFn = fn usz!(void*, char[] buffer);
|
||||
|
||||
$if !$defined(native_fopen_fn):
|
||||
FopenFn native_fopen_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_fclose_fn):
|
||||
FcloseFn native_fclose_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_freopen_fn):
|
||||
FreopenFn native_freopen_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_fseek_fn):
|
||||
FseekFn native_fseek_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_ftell_fn):
|
||||
FtellFn native_ftell_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_fwrite_fn):
|
||||
FwriteFn native_fwrite_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_fread_fn):
|
||||
FreadFn native_fread_fn @weak;
|
||||
$endif
|
||||
|
||||
|
||||
/**
|
||||
* @require mode.len > 0
|
||||
* @require filename.len > 0
|
||||
**/
|
||||
fn void*! native_fopen(String filename, String mode) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_fopen_fn) return native_fopen_fn(filename, mode);
|
||||
unreachable("Tried to call fopen without support.");
|
||||
$else
|
||||
@pool()
|
||||
{
|
||||
$if env::os_is_win32():
|
||||
void* file = (CFile)_wfopen(filename.to_temp_utf16(), filename.to_temp_utf16())!;
|
||||
$else
|
||||
void* file = libc::fopen(filename.zstr_tcopy(), mode.zstr_tcopy());
|
||||
$endif
|
||||
return file ?: file_open_errno()?;
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @require mode.len > 0
|
||||
* @require filename.len > 0
|
||||
**/
|
||||
fn void*! native_freopen(void* file, String filename, String mode) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_freopen_fn) return native_freopen_fn(file, filename, mode);
|
||||
unreachable("Tried to call freopen without support.");
|
||||
$else
|
||||
@pool()
|
||||
{
|
||||
$if env::os_is_win32():
|
||||
file = _wfreopen(filename.to_temp_utf16(), mode.to_temp_utf16(), file)!;
|
||||
$else
|
||||
file = libc::freopen(filename.zstr_tcopy(), mode.zstr_tcopy(), file);
|
||||
$endif
|
||||
return file ?: file_open_errno()?;
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_fseek_fn) return native_fseek_fn(file, offset, seek_mode);
|
||||
unreachable("Tried to call fseek without support.");
|
||||
$else
|
||||
$if env::os_is_win32():
|
||||
bool success = _fseeki64(file, (long)offset, (int)seek_mode) == 0;
|
||||
$else
|
||||
bool success = libc::fseek(file, (SeekIndex)offset, (CInt)seek_mode) == 0;
|
||||
$endif
|
||||
if (!success) return file_seek_errno()?;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn usz! native_ftell(CFile file) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_ftell_fn) return native_ftell_fn(file);
|
||||
unreachable("Tried to call ftell without support.");
|
||||
$else
|
||||
$if env::os_is_win32():
|
||||
long index = _ftelli64(file);
|
||||
return index >= 0 ? index : file_seek_errno()?;
|
||||
$else
|
||||
SeekIndex index = libc::ftell(file);
|
||||
return index >= 0 ? index : file_seek_errno()?;
|
||||
$endif
|
||||
$endif
|
||||
}
|
||||
|
||||
fn usz! native_fwrite(CFile file, char[] buffer) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_fwrite_fn) return native_fwrite_fn(file, buffer);
|
||||
unreachable("Tried to call fwrite without support.");
|
||||
$else
|
||||
return libc::fwrite(buffer.ptr, 1, buffer.len, file);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn usz! native_fread(CFile file, char[] buffer) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_fread_fn) return native_fread_fn(file, buffer);
|
||||
unreachable("Tried to call fread without support.");
|
||||
$else
|
||||
return libc::fread(buffer.ptr, 1, buffer.len, file);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro anyfault file_open_errno() @local
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES: return IoError.NO_PERMISSION;
|
||||
case errno::EDQUOT: return IoError.OUT_OF_SPACE;
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID;
|
||||
case errno::EEXIST: return IoError.ALREADY_EXISTS;
|
||||
case errno::EINTR: return IoError.INTERRUPTED;
|
||||
case errno::EFAULT: return IoError.GENERAL_ERROR;
|
||||
case errno::EISDIR: return IoError.FILE_IS_DIR;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED;
|
||||
case errno::EMFILE: return IoError.TOO_MANY_DESCRIPTORS;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG;
|
||||
case errno::ENFILE: return IoError.OUT_OF_SPACE;
|
||||
case errno::ENOTDIR: return IoError.FILE_NOT_DIR;
|
||||
case errno::ENOENT: return IoError.FILE_NOT_FOUND;
|
||||
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
|
||||
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
|
||||
case errno::EOVERFLOW: return IoError.OVERFLOW;
|
||||
case errno::EROFS: return IoError.READ_ONLY;
|
||||
case errno::EOPNOTSUPP: return IoError.UNSUPPORTED_OPERATION;
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE;
|
||||
case errno::EWOULDBLOCK: return IoError.WOULD_BLOCK;
|
||||
default: return IoError.UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
macro anyfault file_seek_errno() @local
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ESPIPE: return IoError.FILE_IS_PIPE;
|
||||
case errno::EPIPE: return IoError.FILE_IS_PIPE;
|
||||
case errno::EOVERFLOW: return IoError.OVERFLOW;
|
||||
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
|
||||
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE;
|
||||
case errno::EINVAL: return IoError.INVALID_POSITION;
|
||||
case errno::EINTR: return IoError.INTERRUPTED;
|
||||
case errno::EFBIG: return IoError.OUT_OF_SPACE;
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID;
|
||||
case errno::EAGAIN: return IoError.WOULD_BLOCK;
|
||||
default: return IoError.UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// Win functions
|
||||
$if env::os_is_win32():
|
||||
extern fn void* _wfopen(Char16*, Char16*) @local;
|
||||
extern fn void* _wfreopen(Char16*, Char16*, CFile) @local;
|
||||
extern fn int _fseeki64(CFile, long, int) @local;
|
||||
extern fn long _ftelli64(CFile) @local;
|
||||
$endif
|
||||
|
||||
$if env::os_is_posix():
|
||||
extern fn CInt access(ZString path, CInt mode);
|
||||
$endif
|
||||
134
lib/std/io/os/file_libc.c3
Normal file
134
lib/std/io/os/file_libc.c3
Normal file
@@ -0,0 +1,134 @@
|
||||
module std::io::os @if(env::LIBC);
|
||||
import libc;
|
||||
|
||||
<*
|
||||
@require mode.len > 0
|
||||
@require filename.len > 0
|
||||
*>
|
||||
fn void*! native_fopen(String filename, String mode) @inline
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
$if env::WIN32:
|
||||
void* file = libc::_wfopen(filename.to_temp_wstring(), mode.to_temp_wstring())!;
|
||||
$else
|
||||
void* file = libc::fopen(filename.zstr_tcopy(), mode.zstr_tcopy());
|
||||
$endif
|
||||
return file ?: file_open_errno()?;
|
||||
};
|
||||
}
|
||||
|
||||
fn void! native_remove(String filename)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
$if env::WIN32:
|
||||
CInt result = libc::_wremove(filename.to_temp_wstring())!;
|
||||
$else
|
||||
CInt result = libc::remove(filename.zstr_tcopy());
|
||||
$endif
|
||||
if (result)
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ENOENT:
|
||||
return IoError.FILE_NOT_FOUND?;
|
||||
case errno::EACCES:
|
||||
default:
|
||||
return IoError.FILE_CANNOT_DELETE?;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@require mode.len > 0
|
||||
@require filename.len > 0
|
||||
*>
|
||||
fn void*! native_freopen(void* file, String filename, String mode) @inline
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
$if env::WIN32:
|
||||
file = libc::_wfreopen(filename.to_temp_wstring(), mode.to_temp_wstring(), file)!;
|
||||
$else
|
||||
file = libc::freopen(filename.zstr_tcopy(), mode.zstr_tcopy(), file);
|
||||
$endif
|
||||
return file ?: file_open_errno()?;
|
||||
};
|
||||
}
|
||||
|
||||
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
|
||||
{
|
||||
if (libc::fseek(file, (SeekIndex)offset, seek_mode.ordinal)) return file_seek_errno()?;
|
||||
}
|
||||
|
||||
|
||||
fn usz! native_ftell(CFile file) @inline
|
||||
{
|
||||
long index = libc::ftell(file);
|
||||
return index >= 0 ? (usz)index : file_seek_errno()?;
|
||||
}
|
||||
|
||||
fn usz! native_fwrite(CFile file, char[] buffer) @inline
|
||||
{
|
||||
return libc::fwrite(buffer.ptr, 1, buffer.len, file);
|
||||
}
|
||||
|
||||
fn void! native_fputc(CInt c, CFile stream) @inline
|
||||
{
|
||||
if (!libc::fputc(c, stream)) return IoError.EOF?;
|
||||
}
|
||||
|
||||
fn usz! native_fread(CFile file, char[] buffer) @inline
|
||||
{
|
||||
return libc::fread(buffer.ptr, 1, buffer.len, file);
|
||||
}
|
||||
|
||||
macro anyfault file_open_errno() @local
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES: return IoError.NO_PERMISSION;
|
||||
case errno::EDQUOT: return IoError.OUT_OF_SPACE;
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID;
|
||||
case errno::EEXIST: return IoError.ALREADY_EXISTS;
|
||||
case errno::EINTR: return IoError.INTERRUPTED;
|
||||
case errno::EFAULT: return IoError.GENERAL_ERROR;
|
||||
case errno::EISDIR: return IoError.FILE_IS_DIR;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED;
|
||||
case errno::EMFILE: return IoError.TOO_MANY_DESCRIPTORS;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG;
|
||||
case errno::ENFILE: return IoError.OUT_OF_SPACE;
|
||||
case errno::ENOTDIR: return IoError.FILE_NOT_DIR;
|
||||
case errno::ENOENT: return IoError.FILE_NOT_FOUND;
|
||||
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
|
||||
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
|
||||
case errno::EOVERFLOW: return IoError.OVERFLOW;
|
||||
case errno::EROFS: return IoError.READ_ONLY;
|
||||
case errno::EOPNOTSUPP: return IoError.UNSUPPORTED_OPERATION;
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE;
|
||||
case errno::EWOULDBLOCK: return IoError.WOULD_BLOCK;
|
||||
default: return IoError.UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
macro anyfault file_seek_errno() @local
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ESPIPE: return IoError.FILE_IS_PIPE;
|
||||
case errno::EPIPE: return IoError.FILE_IS_PIPE;
|
||||
case errno::EOVERFLOW: return IoError.OVERFLOW;
|
||||
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
|
||||
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE;
|
||||
case errno::EINVAL: return IoError.INVALID_POSITION;
|
||||
case errno::EINTR: return IoError.INTERRUPTED;
|
||||
case errno::EFBIG: return IoError.OUT_OF_SPACE;
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID;
|
||||
case errno::EAGAIN: return IoError.WOULD_BLOCK;
|
||||
default: return IoError.UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
83
lib/std/io/os/file_nolibc.c3
Normal file
83
lib/std/io/os/file_nolibc.c3
Normal file
@@ -0,0 +1,83 @@
|
||||
module std::io::os @if(env::NO_LIBC);
|
||||
import libc;
|
||||
|
||||
def FopenFn = fn void*!(String, String);
|
||||
def FreopenFn = fn void*!(void*, String, String);
|
||||
def FcloseFn = fn void!(void*);
|
||||
def FseekFn = fn void!(void*, isz, Seek);
|
||||
def FtellFn = fn usz!(void*);
|
||||
def FwriteFn = fn usz!(void*, char[] buffer);
|
||||
def FreadFn = fn usz!(void*, char[] buffer);
|
||||
def RemoveFn = fn void!(String);
|
||||
def FputcFn = fn void!(int, void*);
|
||||
|
||||
FopenFn native_fopen_fn @weak @if(!$defined(native_fopen_fn));
|
||||
FcloseFn native_fclose_fn @weak @if(!$defined(native_fclose_fn));
|
||||
FreopenFn native_freopen_fn @weak @if(!$defined(native_freopen_fn));
|
||||
FseekFn native_fseek_fn @weak @if(!$defined(native_fseek_fn));
|
||||
FtellFn native_ftell_fn @weak @if(!$defined(native_ftell_fn));
|
||||
FwriteFn native_fwrite_fn @weak @if(!$defined(native_fwrite_fn));
|
||||
FreadFn native_fread_fn @weak @if(!$defined(native_fread_fn));
|
||||
RemoveFn native_remove_fn @weak @if(!$defined(native_remove_fn));
|
||||
FputcFn native_fputc_fn @weak @if(!$defined(native_fputc_fn));
|
||||
|
||||
<*
|
||||
@require mode.len > 0
|
||||
@require filename.len > 0
|
||||
*>
|
||||
fn void*! native_fopen(String filename, String mode) @inline
|
||||
{
|
||||
if (native_fopen_fn) return native_fopen_fn(filename, mode);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
<*
|
||||
Delete a file.
|
||||
|
||||
@require filename.len > 0
|
||||
*>
|
||||
fn void! native_remove(String filename) @inline
|
||||
{
|
||||
if (native_remove_fn) return native_remove_fn(filename);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
<*
|
||||
@require mode.len > 0
|
||||
@require filename.len > 0
|
||||
*>
|
||||
fn void*! native_freopen(void* file, String filename, String mode) @inline
|
||||
{
|
||||
if (native_freopen_fn) return native_freopen_fn(file, filename, mode);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
|
||||
{
|
||||
if (native_fseek_fn) return native_fseek_fn(file, offset, seek_mode);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! native_ftell(CFile file) @inline
|
||||
{
|
||||
if (native_ftell_fn) return native_ftell_fn(file);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! native_fwrite(CFile file, char[] buffer) @inline
|
||||
{
|
||||
if (native_fwrite_fn) return native_fwrite_fn(file, buffer);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! native_fread(CFile file, char[] buffer) @inline
|
||||
{
|
||||
if (native_fread_fn) return native_fread_fn(file, buffer);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn void! native_fputc(CInt c, CFile stream) @inline
|
||||
{
|
||||
if (native_fputc_fn) return native_fputc_fn(c, stream);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
122
lib/std/io/os/fileinfo.c3
Normal file
122
lib/std/io/os/fileinfo.c3
Normal file
@@ -0,0 +1,122 @@
|
||||
module std::io::os;
|
||||
import libc, std::os, std::io;
|
||||
|
||||
fn void! native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX || env::BSD_FAMILY)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
$if env::DARWIN || env::LINUX || env::BSD_FAMILY:
|
||||
int res = libc::stat(path.zstr_tcopy(), stat);
|
||||
$else
|
||||
unreachable("Stat unimplemented");
|
||||
int res = 0;
|
||||
$endif
|
||||
if (res != 0)
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EBADF:
|
||||
return IoError.FILE_NOT_VALID?;
|
||||
case errno::EFAULT:
|
||||
unreachable("Invalid stat");
|
||||
case errno::EIO:
|
||||
return IoError.GENERAL_ERROR?;
|
||||
case errno::EACCES:
|
||||
return IoError.NO_PERMISSION?;
|
||||
case errno::ELOOP:
|
||||
return IoError.NO_PERMISSION?;
|
||||
case errno::ENAMETOOLONG:
|
||||
return IoError.NAME_TOO_LONG?;
|
||||
case errno::ENOENT:
|
||||
return IoError.FILE_NOT_FOUND?;
|
||||
case errno::ENOTDIR:
|
||||
return IoError.FILE_NOT_DIR?;
|
||||
case errno::EOVERFLOW:
|
||||
return IoError.GENERAL_ERROR?;
|
||||
default:
|
||||
return IoError.UNKNOWN_ERROR?;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn usz! native_file_size(String path) @if(env::WIN32)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
Win32_FILE_ATTRIBUTE_DATA data;
|
||||
win32::getFileAttributesExW(path.to_temp_wstring()!, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data);
|
||||
Win32_LARGE_INTEGER size;
|
||||
size.lowPart = data.nFileSizeLow;
|
||||
size.highPart = data.nFileSizeHigh;
|
||||
return (usz)size.quadPart;
|
||||
};
|
||||
}
|
||||
|
||||
fn usz! native_file_size(String path) @if(!env::WIN32 && !env::DARWIN)
|
||||
{
|
||||
File f = file::open(path, "r")!;
|
||||
defer (void)f.close();
|
||||
return f.seek(0, Seek.END)!;
|
||||
}
|
||||
|
||||
fn usz! native_file_size(String path) @if(env::DARWIN)
|
||||
{
|
||||
Stat stat;
|
||||
native_stat(&stat, path)!;
|
||||
return stat.st_size;
|
||||
}
|
||||
|
||||
fn bool native_file_or_dir_exists(String path)
|
||||
{
|
||||
$switch
|
||||
$case env::DARWIN:
|
||||
$case env::FREEBSD:
|
||||
$case env::NETBSD:
|
||||
$case env::OPENBSD:
|
||||
$case env::LINUX:
|
||||
Stat stat;
|
||||
return @ok(native_stat(&stat, path));
|
||||
$case env::WIN32:
|
||||
@pool()
|
||||
{
|
||||
return (bool)win32::pathFileExistsW(path.to_temp_utf16()) ?? false;
|
||||
};
|
||||
$case env::POSIX:
|
||||
@pool()
|
||||
{
|
||||
return posix::access(path.zstr_tcopy(), 0 /* F_OK */) != -1;
|
||||
};
|
||||
$default:
|
||||
unreachable("Not supported");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn bool native_is_file(String path)
|
||||
{
|
||||
$switch
|
||||
$case env::DARWIN:
|
||||
$case env::FREEBSD:
|
||||
$case env::NETBSD:
|
||||
$case env::OPENBSD:
|
||||
$case env::LINUX:
|
||||
Stat stat;
|
||||
return @ok(native_stat(&stat, path)) && libc_S_ISTYPE(stat.st_mode, libc::S_IFREG);
|
||||
$default:
|
||||
File! f = file::open(path, "r");
|
||||
defer (void)f.close();
|
||||
return @ok(f);
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn bool native_is_dir(String path)
|
||||
{
|
||||
$if env::DARWIN || env::LINUX || env::BSD_FAMILY:
|
||||
Stat stat;
|
||||
return @ok(native_stat(&stat, path)) && libc_S_ISTYPE(stat.st_mode, libc::S_IFDIR);
|
||||
$else
|
||||
return native_file_or_dir_exists(path) && !native_is_file(path);
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user