mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Compare commits
1102 Commits
latest-0.7
...
latest-pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
808ce6d605 | ||
|
|
09606a2998 | ||
|
|
0a21323652 | ||
|
|
c990b5aaf1 | ||
|
|
98e0985a10 | ||
|
|
5974bf6cd0 | ||
|
|
992890065a | ||
|
|
d4dbe27072 | ||
|
|
56a11ae3ca | ||
|
|
f1d03c194f | ||
|
|
0085fe73ad | ||
|
|
a238b62483 | ||
|
|
58022e7cca | ||
|
|
d2f046780d | ||
|
|
ec65c5761e | ||
|
|
0ed917cdc2 | ||
|
|
8373ab0509 | ||
|
|
8f7610345d | ||
|
|
8bd963ecaf | ||
|
|
dc52478c09 | ||
|
|
e1ec4b1235 | ||
|
|
7c68396c0d | ||
|
|
eae7d0c4a1 | ||
|
|
5055e86518 | ||
|
|
4b13ad692a | ||
|
|
7c81bb35ca | ||
|
|
5a82f672b5 | ||
|
|
0387d7666d | ||
|
|
392c78860d | ||
|
|
585c66100d | ||
|
|
8bb974829d | ||
|
|
6b3139940c | ||
|
|
9efb9b90d1 | ||
|
|
152558f5bc | ||
|
|
7ae4c5a1ab | ||
|
|
9fbf6bc213 | ||
|
|
143fa70f87 | ||
|
|
3ac701be0e | ||
|
|
8cb23cff29 | ||
|
|
ec6ba8e7ca | ||
|
|
df030ac51c | ||
|
|
4b03a84b00 | ||
|
|
5d0c41da6b | ||
|
|
990e9685d2 | ||
|
|
a7309b217e | ||
|
|
d7cf8fa9ab | ||
|
|
a78a169a17 | ||
|
|
d80a8629a6 | ||
|
|
14779bd467 | ||
|
|
eb80776988 | ||
|
|
fac9054f1b | ||
|
|
768d24d580 | ||
|
|
e299a4b630 | ||
|
|
bbf89815d6 | ||
|
|
cc6d552e3d | ||
|
|
27ceded331 | ||
|
|
3f279b2f1c | ||
|
|
acc4a900f5 | ||
|
|
f079fa82b2 | ||
|
|
7665720264 | ||
|
|
83705ab340 | ||
|
|
adea3dd83f | ||
|
|
9b52be9ba6 | ||
|
|
202349d88f | ||
|
|
18ab18958b | ||
|
|
2237d3b836 | ||
|
|
e2f17a770b | ||
|
|
40e6a2c4a3 | ||
|
|
a80e40a798 | ||
|
|
de8a733c77 | ||
|
|
c5d7a03859 | ||
|
|
0b82537700 | ||
|
|
7b8299c8dd | ||
|
|
dcbd0f8f2d | ||
|
|
402c00e230 | ||
|
|
0542c05b88 | ||
|
|
67e224c62f | ||
|
|
565b08846f | ||
|
|
80ad0e02ad | ||
|
|
b8ee5a4150 | ||
|
|
445dd155b7 | ||
|
|
abb3efca00 | ||
|
|
de0b317461 | ||
|
|
21758476d4 | ||
|
|
b69f78be30 | ||
|
|
89b9f52f1e | ||
|
|
02a67254cc | ||
|
|
120f56ac5f | ||
|
|
5b93212f43 | ||
|
|
c57415ea78 | ||
|
|
36dfbdff45 | ||
|
|
d07e454dfe | ||
|
|
40aa4d4dcd | ||
|
|
0be291e0d7 | ||
|
|
35d84390bd | ||
|
|
124efb2684 | ||
|
|
5c158e481b | ||
|
|
42b79d19c1 | ||
|
|
4521a25284 | ||
|
|
1933d47ba1 | ||
|
|
bb9e9b54cf | ||
|
|
eddbfb8ba3 | ||
|
|
2c55d6e220 | ||
|
|
472c49de25 | ||
|
|
016254d38c | ||
|
|
054fcd2deb | ||
|
|
d71aa10f62 | ||
|
|
1d0aef4522 | ||
|
|
71b673d241 | ||
|
|
dcf695c726 | ||
|
|
5e656603a5 | ||
|
|
b4426c095b | ||
|
|
5f32c97094 | ||
|
|
1c8cb7fa11 | ||
|
|
6bc606a9b1 | ||
|
|
d509b4caa1 | ||
|
|
12975d07ac | ||
|
|
3d512abaf7 | ||
|
|
a9d93c93d5 | ||
|
|
1b7601fdbb | ||
|
|
373013046d | ||
|
|
10d369d766 | ||
|
|
161280ce7d | ||
|
|
4e129d4ae2 | ||
|
|
2b6f1c061d | ||
|
|
cb19c7d9e7 | ||
|
|
224b120745 | ||
|
|
534dd42472 | ||
|
|
163976f85f | ||
|
|
d79a8808d7 | ||
|
|
1cba5a0774 | ||
|
|
6c76a7ce4e | ||
|
|
084d5cbc94 | ||
|
|
32b1df0f86 | ||
|
|
50718cb905 | ||
|
|
414c0c9438 | ||
|
|
362d5680e4 | ||
|
|
fed7a74a75 | ||
|
|
d276d3767f | ||
|
|
a07660f957 | ||
|
|
4d02ce4414 | ||
|
|
c1e3cfaacc | ||
|
|
c11385cf49 | ||
|
|
0b5064683f | ||
|
|
f3ede27f60 | ||
|
|
4fbb42833e | ||
|
|
3e76b7ff1c | ||
|
|
e901a3de55 | ||
|
|
4899ee14e2 | ||
|
|
3c04a326f4 | ||
|
|
a1ff3b05ed | ||
|
|
74e228688a | ||
|
|
75d454b6a6 | ||
|
|
6cffb888ea | ||
|
|
cf03215564 | ||
|
|
5b90743120 | ||
|
|
0fb91265b6 | ||
|
|
3cb7c489ee | ||
|
|
c65c378b7f | ||
|
|
cf9784afee | ||
|
|
8bd942c1b8 | ||
|
|
109e15b5a0 | ||
|
|
0fdd6bdc81 | ||
|
|
378b35265b | ||
|
|
b5e25e3857 | ||
|
|
9b2fc04959 | ||
|
|
ce8167a102 | ||
|
|
397d065a74 | ||
|
|
5e23817a3d | ||
|
|
396263f5c3 | ||
|
|
26d0760c0d | ||
|
|
11f090116f | ||
|
|
5e1c343be4 | ||
|
|
0e69432e3d | ||
|
|
94a26d9483 | ||
|
|
efa5bdc6df | ||
|
|
459969ddb2 | ||
|
|
ae5047b73f | ||
|
|
17f3db835c | ||
|
|
1845a515ca | ||
|
|
bf50178eb3 | ||
|
|
32675161c4 | ||
|
|
e257500e03 | ||
|
|
0add42b0a0 | ||
|
|
b14053df41 | ||
|
|
6a78864b6c | ||
|
|
b1fea45cd1 | ||
|
|
cef48482f1 | ||
|
|
a126a25d66 | ||
|
|
61c939059d | ||
|
|
5fa820a462 | ||
|
|
472124dab3 | ||
|
|
1e11b6c442 | ||
|
|
34a86852c6 | ||
|
|
52afbdbde9 | ||
|
|
888657cc97 | ||
|
|
d7bfddf35e | ||
|
|
9c435352b9 | ||
|
|
3fe55b5e51 | ||
|
|
cdabe8fd9e | ||
|
|
5390ca6250 | ||
|
|
61e84e4d34 | ||
|
|
1b82b7d2f2 | ||
|
|
49f59d82c9 | ||
|
|
e84e23f7ec | ||
|
|
0b9b49673e | ||
|
|
4512c6446d | ||
|
|
0fea6c6056 | ||
|
|
dd8449576f | ||
|
|
c3b2694834 | ||
|
|
d3ebd4a130 | ||
|
|
a326e31e57 | ||
|
|
945a3f3fc0 | ||
|
|
2a8fbb8fec | ||
|
|
c0f1b02d0b | ||
|
|
f2c557c3b7 | ||
|
|
d00a93f195 | ||
|
|
aa07969fc2 | ||
|
|
6dcd91c5ef | ||
|
|
c2e603ddd8 | ||
|
|
70c4b24519 | ||
|
|
51364a9d1b | ||
|
|
d9f4eb46d9 | ||
|
|
cd2d1a04d8 | ||
|
|
3f7a547d8a | ||
|
|
f254c27966 | ||
|
|
8a1c02c840 | ||
|
|
7f297a9d27 | ||
|
|
64ef33f09b | ||
|
|
cd4c586c3f | ||
|
|
0a9d4e398d | ||
|
|
d61beef7e0 | ||
|
|
7136b05019 | ||
|
|
87fa253059 | ||
|
|
04eb6fc451 | ||
|
|
15fc435d92 | ||
|
|
9d54e9e3c4 | ||
|
|
c73c7cb2a3 | ||
|
|
827ad18ef4 | ||
|
|
3fc562af6f | ||
|
|
87c42f1cd3 | ||
|
|
e93f22fbda | ||
|
|
8fa59cbb43 | ||
|
|
527766310f | ||
|
|
a02b0a1f4c | ||
|
|
4d93db51ee | ||
|
|
fa2e6e8189 | ||
|
|
0fc1871ddf | ||
|
|
197f82d829 | ||
|
|
4ad11724d1 | ||
|
|
6d53fd6f6e | ||
|
|
db47b0555d | ||
|
|
ebd7b7243a | ||
|
|
e0771beabc | ||
|
|
824d064710 | ||
|
|
336ddb67c8 | ||
|
|
9ad98beda7 | ||
|
|
702f836b40 | ||
|
|
d820a2356a | ||
|
|
fa1951642b | ||
|
|
42dc2d541a | ||
|
|
fdbbe5c1aa | ||
|
|
292bf1cbbc | ||
|
|
82769669ec | ||
|
|
29b211eefc | ||
|
|
e4965ab408 | ||
|
|
067a4f7cb1 | ||
|
|
739e91efa4 | ||
|
|
26d733ef59 | ||
|
|
32d6025e29 | ||
|
|
04706f2dcd | ||
|
|
d8a7c57b56 | ||
|
|
ad8769580a | ||
|
|
d51dd09a1f | ||
|
|
5ed4f9519f | ||
|
|
493a084745 | ||
|
|
9bd04526e8 | ||
|
|
90f0486334 | ||
|
|
e4f1b57bd0 | ||
|
|
c949bd3108 | ||
|
|
56f8008d85 | ||
|
|
c6a96ad7f3 | ||
|
|
328e6f518c | ||
|
|
8ec4a2cada | ||
|
|
9b318ec233 | ||
|
|
d96624c578 | ||
|
|
bf1d401566 | ||
|
|
a2c886a2d9 | ||
|
|
e76278cfd7 | ||
|
|
1028f85daa | ||
|
|
e706c914a8 | ||
|
|
f3b71ed7eb | ||
|
|
18b246c577 | ||
|
|
c205719563 | ||
|
|
02a5270c5a | ||
|
|
2ba244dd9c | ||
|
|
d33d0a232b | ||
|
|
5c4197debd | ||
|
|
41261d40b2 | ||
|
|
48ecceab54 | ||
|
|
0d9547a388 | ||
|
|
d2f59c5b3f | ||
|
|
85166bc706 | ||
|
|
bf26898645 | ||
|
|
1f2bcd5462 | ||
|
|
4e6cd4283c | ||
|
|
9aec5de105 | ||
|
|
dec49b05b8 | ||
|
|
29bcd2c96e | ||
|
|
97a9cab218 | ||
|
|
436af4dbca | ||
|
|
b08a2a2c0f | ||
|
|
b43dac24c4 | ||
|
|
bae0f0f579 | ||
|
|
8055c340f6 | ||
|
|
00c1210625 | ||
|
|
070b13c81c | ||
|
|
415c9639e7 | ||
|
|
996f8a6a4d | ||
|
|
e49e3e32e7 | ||
|
|
86a05ba6c0 | ||
|
|
651735f9a0 | ||
|
|
759389066f | ||
|
|
3577a2d6b8 | ||
|
|
b63886b879 | ||
|
|
466d3bc1b6 | ||
|
|
2a2c0f5d91 | ||
|
|
0c0d0ace4d | ||
|
|
a0ab10c23e | ||
|
|
0685260454 | ||
|
|
15662bd32f | ||
|
|
475816251b | ||
|
|
bf62d11a73 | ||
|
|
37d42d555d | ||
|
|
1fc522ec9c | ||
|
|
3a8a6aa429 | ||
|
|
e15ee23925 | ||
|
|
85657c9200 | ||
|
|
3f20e5af1d | ||
|
|
18e2838772 | ||
|
|
f023db8638 | ||
|
|
cb0b94c064 | ||
|
|
7087665ccc | ||
|
|
1793dd7f0b | ||
|
|
a61fd6d280 | ||
|
|
034a048c8a | ||
|
|
608fc4df9f | ||
|
|
5961bcaf86 | ||
|
|
fd5489458a | ||
|
|
cf5dd5496a | ||
|
|
ccffa03de2 | ||
|
|
ce0ab62c78 | ||
|
|
0bc5cbca74 | ||
|
|
a2aa9fae6b | ||
|
|
9d79c3f33d | ||
|
|
a26055c932 | ||
|
|
b296875c05 | ||
|
|
a54658d37f | ||
|
|
0a0e097bdf | ||
|
|
2df51bfe07 | ||
|
|
cb065152ea | ||
|
|
07fa14f00b | ||
|
|
ff1b17d18b | ||
|
|
c3d5778ae0 | ||
|
|
373ad1a399 | ||
|
|
6deed2d4a4 | ||
|
|
6324d78c32 | ||
|
|
9e14338b77 | ||
|
|
6e4614b6a4 | ||
|
|
0b52819090 | ||
|
|
404a78943d | ||
|
|
7215a9fa12 | ||
|
|
463c6957fc | ||
|
|
8ec3a52ef7 | ||
|
|
ab1efdda73 | ||
|
|
4f3b6f922d | ||
|
|
869a1d93cb | ||
|
|
5d468ccbf0 | ||
|
|
887ed5b9e9 | ||
|
|
5c1a6d7623 | ||
|
|
1b49ebf855 | ||
|
|
98155b61f1 | ||
|
|
60cdea5292 | ||
|
|
49e836b1ab | ||
|
|
5b83108dd1 | ||
|
|
a50de26c5d | ||
|
|
7b50c87858 | ||
|
|
a816a78e98 | ||
|
|
39694e65c0 | ||
|
|
2a41fa6281 | ||
|
|
49b8cfe267 | ||
|
|
20dfdf5c5d | ||
|
|
1e543dc286 | ||
|
|
06884720e5 | ||
|
|
1ea181524e | ||
|
|
b16ee3119d | ||
|
|
4e66693065 | ||
|
|
5f96b8e4c6 | ||
|
|
748a2f6530 | ||
|
|
6360ddbc77 | ||
|
|
eccc6700dc | ||
|
|
52ececba37 | ||
|
|
ffc65bcbf4 | ||
|
|
0da6bf4455 | ||
|
|
7063e684ba | ||
|
|
07363c6ecd | ||
|
|
5070840da9 | ||
|
|
4a25bcc5ee | ||
|
|
d43d7100af | ||
|
|
791cbbfb62 | ||
|
|
9b05dfdef1 | ||
|
|
b072e88bb3 | ||
|
|
af33d2b1cc | ||
|
|
d438d7510e | ||
|
|
1673aef74f | ||
|
|
b3bce10699 | ||
|
|
3ff922e12b | ||
|
|
3b718335ec | ||
|
|
f25ad512a7 | ||
|
|
5a3c484ceb | ||
|
|
331a77c1c2 | ||
|
|
045053f6bf | ||
|
|
4809979898 | ||
|
|
a5b2636b2e | ||
|
|
c483c3b75f | ||
|
|
c10d449e43 | ||
|
|
54b110a367 | ||
|
|
ee8dc3d681 | ||
|
|
a38a627a1d | ||
|
|
8aaf54e8b1 | ||
|
|
423152202f | ||
|
|
f37e7460aa | ||
|
|
8f5d5a0bb5 | ||
|
|
883052a6bb | ||
|
|
9cf271f5fb | ||
|
|
5d8cad91b1 | ||
|
|
614c6989d8 | ||
|
|
03ad72afbb | ||
|
|
b924ede71a | ||
|
|
a81f857d8c | ||
|
|
6169d7acdf | ||
|
|
4af31da7ea | ||
|
|
0bd2c81757 | ||
|
|
5ed1281451 | ||
|
|
7b649314ec | ||
|
|
e37343fbe3 | ||
|
|
7b02907830 | ||
|
|
6eee760239 | ||
|
|
ae33d1a206 | ||
|
|
3430240c2a | ||
|
|
6f11260a5c | ||
|
|
df67b7dddd | ||
|
|
f3b7df2ab0 | ||
|
|
a000ae560a | ||
|
|
0d85caf21c | ||
|
|
e34a26422f | ||
|
|
fe70f10bcc | ||
|
|
d6be1cbf65 | ||
|
|
04cd079d4e | ||
|
|
b4b14674b4 | ||
|
|
5a1831c989 | ||
|
|
e9ec421b3b | ||
|
|
872f63eecc | ||
|
|
1eb8c0ced1 | ||
|
|
b5ae2485a7 | ||
|
|
fe6817f90d | ||
|
|
98a72007f8 | ||
|
|
87c1e09a7a | ||
|
|
e0fbe31f00 | ||
|
|
7d6c844b99 | ||
|
|
a03446a26d | ||
|
|
a7e77fec78 | ||
|
|
05c3fa1afd | ||
|
|
30c8435669 | ||
|
|
94497c968b | ||
|
|
281d4af464 | ||
|
|
cb2d0e798e | ||
|
|
da67cd4eb0 | ||
|
|
7d06ca6d35 | ||
|
|
6d45450130 | ||
|
|
27bbeaf79c | ||
|
|
3af5a537da | ||
|
|
6287e8dfbf | ||
|
|
1f49a5448e | ||
|
|
ece4a2b6fb | ||
|
|
e68bd0c57f | ||
|
|
eaeafb7299 | ||
|
|
44d736a537 | ||
|
|
122dbb3668 | ||
|
|
c2abbe2e2f | ||
|
|
3ccabd625c | ||
|
|
cfe6534c15 | ||
|
|
f5090eb158 | ||
|
|
d3db91536c | ||
|
|
9c42919e5a | ||
|
|
a6d33ec4af | ||
|
|
b03ae8bb17 | ||
|
|
59fd777198 | ||
|
|
d8286fa2a5 | ||
|
|
3345e70c63 | ||
|
|
12eea4a98d | ||
|
|
fdc20dc642 | ||
|
|
c5e3a1b2da | ||
|
|
35270fb0bf | ||
|
|
d782dad149 | ||
|
|
92aefb15f8 | ||
|
|
8342ac80d3 | ||
|
|
c71444e7a0 | ||
|
|
06e10bb69f | ||
|
|
fabd96552f | ||
|
|
2e99ae5ab9 | ||
|
|
8fea6ee8ab | ||
|
|
e6b10ee00c | ||
|
|
6aff6d66de | ||
|
|
8035991ac3 | ||
|
|
c0bd14cee7 | ||
|
|
3ba0beee96 | ||
|
|
8e6535f13c | ||
|
|
0d8f9520e9 | ||
|
|
3caaf0a3e8 | ||
|
|
a2206f1bcd | ||
|
|
7b5277d52c | ||
|
|
9f55a74d2e | ||
|
|
3eb8f68ded | ||
|
|
bd9bc118db | ||
|
|
95375a2591 | ||
|
|
b7115e9c70 | ||
|
|
078d9dc0b7 | ||
|
|
79c0c8e082 | ||
|
|
69b3263a00 | ||
|
|
cbd415881b | ||
|
|
6dbd81a6f9 | ||
|
|
e605a21fd3 | ||
|
|
d1349c9cfb | ||
|
|
c375aef9a3 | ||
|
|
3c1f692d49 | ||
|
|
29e20ee1be | ||
|
|
cf14787552 | ||
|
|
10241df23c | ||
|
|
8795ffc4f1 | ||
|
|
e25812a071 | ||
|
|
14a929588a | ||
|
|
02d1486af9 | ||
|
|
bab317282c | ||
|
|
a3a6319bcf | ||
|
|
17dfbb377e | ||
|
|
ff39f14dd1 | ||
|
|
af4309b286 | ||
|
|
176fb47c23 | ||
|
|
3a69c9f1fe | ||
|
|
944cc00d34 | ||
|
|
a751177a3e | ||
|
|
d291a40f69 | ||
|
|
cb006dd715 | ||
|
|
c7f09f2879 | ||
|
|
c0387221af | ||
|
|
0c7c5fbd7b | ||
|
|
fafcf3d0a9 | ||
|
|
b757f1447b | ||
|
|
bc3d9d761f | ||
|
|
10bc68fb39 | ||
|
|
de8aed9d96 | ||
|
|
1080303768 | ||
|
|
0503e15e31 | ||
|
|
ca2fabc9f9 | ||
|
|
0178a44b3c | ||
|
|
8f3cb9c6e9 | ||
|
|
c339278ff7 | ||
|
|
47316dac59 | ||
|
|
90d3f429aa | ||
|
|
239d249f01 | ||
|
|
7312c10b9e | ||
|
|
3c6e6f1965 | ||
|
|
28b9be64ee | ||
|
|
d2cae909e1 | ||
|
|
e194081e21 | ||
|
|
04cc34f12e | ||
|
|
f7143c1852 | ||
|
|
1781e97f02 | ||
|
|
c17cb7d0ca | ||
|
|
21343baa75 | ||
|
|
58c59361ea | ||
|
|
cb17cfff7d | ||
|
|
1634217fc4 | ||
|
|
bc9b0900a5 | ||
|
|
f43a7540c5 | ||
|
|
410a25f334 | ||
|
|
35c04cdc36 | ||
|
|
3e641ab82b | ||
|
|
7972397c65 | ||
|
|
9bf933ae31 | ||
|
|
a69ee59b82 | ||
|
|
961aa0ef61 | ||
|
|
48318c3ad4 | ||
|
|
a004cd3d03 | ||
|
|
768ce6092d | ||
|
|
e4e499edd2 | ||
|
|
f36e9fea48 | ||
|
|
d5eec296a0 | ||
|
|
e2e2ca1d7f | ||
|
|
6ab7198f2f | ||
|
|
2e1f7c95ce | ||
|
|
a2ef63f5b6 | ||
|
|
b7c9a4e2e9 | ||
|
|
28ffb864a3 | ||
|
|
18b4ce4e7d | ||
|
|
551ce34b9b | ||
|
|
de09a19a48 | ||
|
|
ba55946c9a | ||
|
|
33ab18033a | ||
|
|
96127d4ff3 | ||
|
|
43163fe2a0 | ||
|
|
a52b30c951 | ||
|
|
7d6a864d56 | ||
|
|
eeab73df4e | ||
|
|
db45abdfc7 | ||
|
|
cb32441533 | ||
|
|
643aa47e99 | ||
|
|
5e1bf75621 | ||
|
|
7c8e3dd4fd | ||
|
|
ad02fad167 | ||
|
|
261184b5c1 | ||
|
|
d07da2804e | ||
|
|
702b63ddb7 | ||
|
|
e35dbd29fb | ||
|
|
b52ab886d2 | ||
|
|
4b95d6be4c | ||
|
|
34b0b6f8f9 | ||
|
|
4fea202e6d | ||
|
|
8cfdb76869 | ||
|
|
858f8d2405 | ||
|
|
67aa18c1aa | ||
|
|
31b15c775e | ||
|
|
bf7e7e2397 | ||
|
|
eb8fb8871f | ||
|
|
85dc9c45ab | ||
|
|
076ef187cb | ||
|
|
c8d39251a9 | ||
|
|
f5e6b697b8 | ||
|
|
e8e88c1920 | ||
|
|
82a58e1c66 | ||
|
|
8e8d0436ad | ||
|
|
db99de9717 | ||
|
|
fd9fbe26a1 | ||
|
|
582453cb45 | ||
|
|
b73a44ec7d | ||
|
|
be98a01ed8 | ||
|
|
625a6d987d | ||
|
|
2597f6217e | ||
|
|
0470f3be8e | ||
|
|
1d25197bfd | ||
|
|
aae873c044 | ||
|
|
6471728ee5 | ||
|
|
29bae1fbd6 | ||
|
|
9c770f360e | ||
|
|
3b6d68ef21 | ||
|
|
24c03f9800 | ||
|
|
ed61b51489 | ||
|
|
0205ee8688 | ||
|
|
abd3585c44 | ||
|
|
aa910a1c44 | ||
|
|
00b88a8027 | ||
|
|
229fdd6193 | ||
|
|
5292e08cd6 | ||
|
|
90990ed2f3 | ||
|
|
c99284103d | ||
|
|
b463358add | ||
|
|
0e10b71cbf | ||
|
|
cb2d8133e0 | ||
|
|
f2d27229d2 | ||
|
|
604661b12c | ||
|
|
440df8415e | ||
|
|
c31c423386 | ||
|
|
8358af2240 | ||
|
|
4625b457fb | ||
|
|
151a28a92a | ||
|
|
9fe6c77d28 | ||
|
|
1c4f7a4b61 | ||
|
|
f23bbb342c | ||
|
|
91b866c967 | ||
|
|
483fe62750 | ||
|
|
2a47cc2ca9 | ||
|
|
e707190539 | ||
|
|
cb62554a26 | ||
|
|
9c58db99af | ||
|
|
90c339ebdb | ||
|
|
e3a8a3ec02 | ||
|
|
bdbe81fedd | ||
|
|
f0142e3b1a | ||
|
|
2c6ff00261 | ||
|
|
61a21203f4 | ||
|
|
d7affc5028 | ||
|
|
eb9549a818 | ||
|
|
6d9906db0a | ||
|
|
334ee975b9 | ||
|
|
d600d0898c | ||
|
|
44f4efa5aa | ||
|
|
8151305701 | ||
|
|
3ac9bfc387 | ||
|
|
51b9cb85bc | ||
|
|
d805ff9782 | ||
|
|
fa9ba3f607 | ||
|
|
9d2be2851b | ||
|
|
2639338426 | ||
|
|
d8daa4ac83 | ||
|
|
6641155892 | ||
|
|
86034353ec | ||
|
|
944ef0fc8d | ||
|
|
194b7c4772 | ||
|
|
6963e143a1 | ||
|
|
4977bd1d78 | ||
|
|
a21e641748 | ||
|
|
208b0f6d0e | ||
|
|
0bc546595d | ||
|
|
a673b4ad66 | ||
|
|
9a68a5c063 | ||
|
|
e790df539d | ||
|
|
fbeb779335 | ||
|
|
625bfa5713 | ||
|
|
943a294900 | ||
|
|
3400dd5e42 | ||
|
|
e8b3c44de3 | ||
|
|
9575698fa4 | ||
|
|
5f0a7dd63e | ||
|
|
38bc11b7b8 | ||
|
|
04528aee1f | ||
|
|
5c82747970 | ||
|
|
b45c337515 | ||
|
|
9dc6b0e660 | ||
|
|
428165590e | ||
|
|
53051e04a3 | ||
|
|
869bcf8b2b | ||
|
|
382a65abcd | ||
|
|
908d705669 | ||
|
|
d422fb699f | ||
|
|
506e63284b | ||
|
|
ed92476916 | ||
|
|
1218afd51f | ||
|
|
b88722b4a6 | ||
|
|
694d297eb8 | ||
|
|
2053f2767b | ||
|
|
448176b0b7 | ||
|
|
b1a22b5002 | ||
|
|
e9aee55714 | ||
|
|
2acf3c57c7 | ||
|
|
f2babb6063 | ||
|
|
b8d07474fe | ||
|
|
cf913b41c6 | ||
|
|
adb3df05c6 | ||
|
|
34bded30eb | ||
|
|
774a375ce7 | ||
|
|
ee35001732 | ||
|
|
da4105ffb1 | ||
|
|
5c5692ae98 | ||
|
|
379d16abe7 | ||
|
|
078ce38c57 | ||
|
|
f99b903d78 | ||
|
|
3650b81970 | ||
|
|
bb6fcdfa6f | ||
|
|
8df112e157 | ||
|
|
af91f35017 | ||
|
|
3cce90bba1 | ||
|
|
1a351bdb6d | ||
|
|
efaac43248 | ||
|
|
f082cac762 | ||
|
|
2bd289ebd6 | ||
|
|
aba9baf207 | ||
|
|
e755c36ea2 | ||
|
|
6c7dc2a28e | ||
|
|
cdd530d807 | ||
|
|
02c0db7b8b | ||
|
|
8a62c12089 | ||
|
|
988549599d | ||
|
|
70159c00cc | ||
|
|
2dfbdea889 | ||
|
|
299d1f530f | ||
|
|
bf0ff8abbc | ||
|
|
123b1c8f44 | ||
|
|
a314e05826 | ||
|
|
83fd24faa2 | ||
|
|
1d4ad5f1d5 | ||
|
|
26d5cc694a | ||
|
|
a2122e0153 | ||
|
|
10fc94aaa7 | ||
|
|
0835bada39 | ||
|
|
277af1a2b6 | ||
|
|
5b835bec3e | ||
|
|
dc23cef59a | ||
|
|
098079d317 | ||
|
|
1ab57ecf20 | ||
|
|
808ab56545 | ||
|
|
19acdc7a19 | ||
|
|
e15fdc709f | ||
|
|
457244e3de | ||
|
|
d5559ecafd | ||
|
|
802fbfcf1e | ||
|
|
a20e74c401 | ||
|
|
7cdb1ce9eb | ||
|
|
0d170a70b6 | ||
|
|
b19cd0b87d | ||
|
|
50efc95c83 | ||
|
|
fa50268b4e | ||
|
|
ae1d51d089 | ||
|
|
1b8355ff07 | ||
|
|
f32afb70b8 | ||
|
|
60d96ca7b7 | ||
|
|
014f734260 | ||
|
|
de4963ef95 | ||
|
|
e7d3e60ebd | ||
|
|
a46f73ad24 | ||
|
|
759bc1d909 | ||
|
|
c79c9dac8d | ||
|
|
635d4babc4 | ||
|
|
9b3b4ae8be | ||
|
|
b3e7f074e9 | ||
|
|
ee1ed73fc5 | ||
|
|
10e11fb742 | ||
|
|
8b47317ec7 | ||
|
|
04626b72cd | ||
|
|
2151cd0929 | ||
|
|
93ded9c1e0 | ||
|
|
20964b43ce | ||
|
|
af192354fd | ||
|
|
ad48637cbb | ||
|
|
89507bd335 | ||
|
|
21533ffee4 | ||
|
|
4502a9286c | ||
|
|
ba11511c69 | ||
|
|
965ef19a5b | ||
|
|
8f86b331c1 | ||
|
|
59a1590955 | ||
|
|
fad87b294b | ||
|
|
13bb2b6690 | ||
|
|
4a803ed0cf | ||
|
|
9e80f1b26c | ||
|
|
9299c78747 | ||
|
|
e1a125e326 | ||
|
|
a13eb99962 | ||
|
|
d46733e11a | ||
|
|
ce569462f6 | ||
|
|
9285dfefad | ||
|
|
5246ef83e7 | ||
|
|
0448e50b3d | ||
|
|
2d535aaa25 | ||
|
|
dc1e5323ab | ||
|
|
63abf1c2f8 | ||
|
|
df8904909b | ||
|
|
e986e3a8c0 | ||
|
|
f67da4f315 | ||
|
|
8a4e7b6ce8 | ||
|
|
2b0fb52f65 | ||
|
|
faf073885f | ||
|
|
11b8a9808d | ||
|
|
1ef9c73342 | ||
|
|
92d56b7a35 | ||
|
|
a894adbdd6 | ||
|
|
605a7c4091 | ||
|
|
adabae2a24 | ||
|
|
affb722b23 | ||
|
|
1b2f5989e1 | ||
|
|
e0b6c83a62 | ||
|
|
839f835845 | ||
|
|
2636a855c4 | ||
|
|
0d147a48b2 | ||
|
|
aff3a3f746 | ||
|
|
c95204c3f7 | ||
|
|
794e8371c8 | ||
|
|
2bbc6cbbca | ||
|
|
07bd37da43 | ||
|
|
a0497e9274 | ||
|
|
fa730e7ec2 | ||
|
|
b4a6e3704f | ||
|
|
dd80e8b799 | ||
|
|
5efc721b0c | ||
|
|
20c13c0bb4 | ||
|
|
cd3e924d1e | ||
|
|
2e42868467 | ||
|
|
8d698b5e40 | ||
|
|
2f45beecbe | ||
|
|
1b4b9bca94 | ||
|
|
40ae9d2e55 | ||
|
|
0df538d0e2 | ||
|
|
aa425a0886 | ||
|
|
842788e59d | ||
|
|
2b97d7d59c | ||
|
|
75f78551cf | ||
|
|
01ef53a090 | ||
|
|
a55f56a88f | ||
|
|
eb75d8f82a | ||
|
|
f07bd3cbc6 | ||
|
|
93640699be | ||
|
|
99e29bff8d | ||
|
|
95137db64b | ||
|
|
e7ce79e731 | ||
|
|
779f548a00 | ||
|
|
f0bd93d1f0 | ||
|
|
3ce15bd7af | ||
|
|
07eee04e94 | ||
|
|
1f7b62b248 | ||
|
|
b2c994618f | ||
|
|
2afa544d7d | ||
|
|
dda2d2ecbe | ||
|
|
f79f6d4001 | ||
|
|
cf167c9446 | ||
|
|
f0201f971e | ||
|
|
a3abea1a33 | ||
|
|
5f6f52838c | ||
|
|
e0237096d6 | ||
|
|
82491a6f85 | ||
|
|
1aacb1fa60 | ||
|
|
bbd9f6dc96 | ||
|
|
496d23e93f | ||
|
|
e936b999d2 | ||
|
|
becda6ea1d | ||
|
|
2ad17a04d4 | ||
|
|
1617792a35 | ||
|
|
c7b3ae0cf9 | ||
|
|
1dcd40aa5f | ||
|
|
40554192b1 | ||
|
|
9bc5e259d2 | ||
|
|
f66cadccd2 | ||
|
|
be511b26cd | ||
|
|
4cfa5441d2 | ||
|
|
5e45c34f21 | ||
|
|
d7a11903c7 | ||
|
|
b893697a87 | ||
|
|
f2daf2e11e | ||
|
|
9baeca3a8e | ||
|
|
ef649050c4 | ||
|
|
d6d0e08906 | ||
|
|
c9d9127da6 | ||
|
|
7f85534414 | ||
|
|
ba1332dc2a | ||
|
|
45a0895c39 | ||
|
|
72cc8e430a | ||
|
|
9645bd3289 | ||
|
|
8fc01d4e1a | ||
|
|
a48e2274e5 | ||
|
|
786e47408a | ||
|
|
6e348d1e71 | ||
|
|
d697b910ba | ||
|
|
4d848f1707 | ||
|
|
6377f0573d | ||
|
|
c3d2b2824c | ||
|
|
18e408ead4 | ||
|
|
08c63108a1 | ||
|
|
da25a411f9 | ||
|
|
e685414829 | ||
|
|
ae5a74bc41 | ||
|
|
76374d31c4 | ||
|
|
ffd7a5e483 | ||
|
|
d143ec227c | ||
|
|
f2703508f2 | ||
|
|
bb96dc931e | ||
|
|
a5a2b00ec8 | ||
|
|
00f1206f3c | ||
|
|
349d9ef3cf | ||
|
|
9f30b56e13 | ||
|
|
83d6b35afe | ||
|
|
f4b9f375e0 | ||
|
|
be3f9007c9 | ||
|
|
b665e2cbe5 | ||
|
|
0ed68f94cf | ||
|
|
966e8107f8 | ||
|
|
61a4dcc807 | ||
|
|
52541a03eb | ||
|
|
972c84b65b | ||
|
|
f668b96cc9 | ||
|
|
9461873b4c | ||
|
|
8d563eba7a | ||
|
|
fe98225f0a | ||
|
|
bae3e59217 | ||
|
|
b5ddc36d7f | ||
|
|
c2c0ecded8 | ||
|
|
9d5b31dad5 | ||
|
|
6c0e94cad9 | ||
|
|
84aee6a25b | ||
|
|
71a765c66e | ||
|
|
5c3b637cf6 | ||
|
|
bd1de1e7dc | ||
|
|
3cd2267b0a | ||
|
|
7fcc91edc8 | ||
|
|
9052f07c19 | ||
|
|
c7f0d54328 | ||
|
|
498803e9ba | ||
|
|
082457c5fb | ||
|
|
23897bc9a4 | ||
|
|
8ada2a70d9 | ||
|
|
a91330b7d1 | ||
|
|
2f3954a7d9 | ||
|
|
b7ae5dce8b | ||
|
|
91db6ceeda | ||
|
|
fc2f718d9e | ||
|
|
64ef3fc756 | ||
|
|
93dd432b62 | ||
|
|
6c822e5aa3 | ||
|
|
8c741c617c | ||
|
|
b83e57b952 | ||
|
|
24ebe975d8 | ||
|
|
511ae0da00 | ||
|
|
36eb650228 | ||
|
|
50b4d7aa35 | ||
|
|
abe4727c3a | ||
|
|
c528f53d58 | ||
|
|
83955ea5b5 | ||
|
|
fc5c70a628 | ||
|
|
5287640140 | ||
|
|
634438eb82 | ||
|
|
164c901ae6 | ||
|
|
54e70cae0f | ||
|
|
30ec200492 | ||
|
|
584a8a2e60 | ||
|
|
3f07d1c7b8 | ||
|
|
125436d23e | ||
|
|
900365c25e | ||
|
|
d313afa487 | ||
|
|
a411f20762 | ||
|
|
8a0907cb70 | ||
|
|
8a09b2e5f7 | ||
|
|
bfccc303d1 | ||
|
|
0d3299f267 | ||
|
|
11bb8b49da | ||
|
|
f0d2b0eff0 | ||
|
|
005cc08118 | ||
|
|
c5494a23ce | ||
|
|
5dcc67aa1b | ||
|
|
335f53fb64 | ||
|
|
3636898ac0 | ||
|
|
5ba24e05d0 | ||
|
|
0ada5504af | ||
|
|
8ac02a28cc | ||
|
|
246957b8bd | ||
|
|
0595270d9a | ||
|
|
8b86d1461d | ||
|
|
0129308bf3 | ||
|
|
05094b4f47 | ||
|
|
9a59cd164d | ||
|
|
3eecaf9e29 | ||
|
|
8b47673524 | ||
|
|
8b29e4780d | ||
|
|
fd2a81afb1 | ||
|
|
a0d4df2272 | ||
|
|
e39c7cae8d | ||
|
|
8a2907806b | ||
|
|
f778e75757 | ||
|
|
6ab7953706 | ||
|
|
42e4370994 | ||
|
|
9a1fdbbca0 | ||
|
|
434a0e8e4b | ||
|
|
946c167bf1 | ||
|
|
ba10c8953d | ||
|
|
72d7813c20 | ||
|
|
1083de1f81 | ||
|
|
b4b6cba301 | ||
|
|
3244898610 | ||
|
|
6454856fdb | ||
|
|
5cf48ad730 | ||
|
|
b5d0739de0 | ||
|
|
f6e130ad3c | ||
|
|
f9e62b80ea | ||
|
|
debbae594c | ||
|
|
37ffd92f7b | ||
|
|
a44e932806 | ||
|
|
668175851b | ||
|
|
e7c9ec0938 | ||
|
|
d6fa9cd50b | ||
|
|
41e173d255 | ||
|
|
fde2bb2a7e | ||
|
|
0a9bb2e8e0 | ||
|
|
b64dcde21d | ||
|
|
eade5fa57a | ||
|
|
f85198e3ee | ||
|
|
dca805bd8a | ||
|
|
3888fcb182 | ||
|
|
de73265d28 | ||
|
|
cb895754c8 | ||
|
|
6e42bfef3b | ||
|
|
01357ef6d7 | ||
|
|
89d205258e | ||
|
|
0f2d425297 | ||
|
|
28fc03c376 | ||
|
|
1290906d66 | ||
|
|
25d416aca1 | ||
|
|
8cce7f6836 | ||
|
|
4c26adb376 | ||
|
|
94b8330ac5 | ||
|
|
3cb5df5639 | ||
|
|
ded5fde2d5 | ||
|
|
65fb977e89 | ||
|
|
e3f3b6f5f1 | ||
|
|
ab4ed9472a | ||
|
|
1668999f90 | ||
|
|
e828d9a05a | ||
|
|
47447dc069 | ||
|
|
39a59c929f | ||
|
|
f355738dda | ||
|
|
87e254e4b1 | ||
|
|
561a683230 | ||
|
|
63e5aa58c5 | ||
|
|
2be3071bdb | ||
|
|
d3e81b193a | ||
|
|
586d191585 |
15
.clang-format
Normal file
15
.clang-format
Normal file
@@ -0,0 +1,15 @@
|
||||
IndentWidth: 4
|
||||
UseCRLF: false
|
||||
IndentCaseLabels: true
|
||||
UseTab: ForIndentation
|
||||
TabWidth: 4
|
||||
BreakBeforeBraces: Allman
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeParens: ControlStatementsExceptControlMacros
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
44
.clang-tidy
44
.clang-tidy
@@ -1,21 +1,7 @@
|
||||
---
|
||||
# Configure clang-tidy for this project.
|
||||
|
||||
IndentWidth: 4
|
||||
UseCRLF: false
|
||||
IndentCaseLabels: true
|
||||
UseTab: UT_ForIndentation
|
||||
TabWidth: 4
|
||||
BreakBeforeBraces: Allman
|
||||
AllowShortBlocksOnASingleLine: SBS_Empty
|
||||
AllowShortIfStatementsOnASingleLine: SIS_WithoutElse
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeParens: SBPO_ControlStatementsExceptControlMacros
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
|
||||
|
||||
# Disabled:
|
||||
# -google-readability-namespace-comments the *_CLIENT_NS is a macro, and
|
||||
@@ -40,17 +26,19 @@ Checks: >
|
||||
|
||||
# Turn all the warnings from the checks above into errors.
|
||||
WarningsAsErrors: "*"
|
||||
|
||||
CheckOptions:
|
||||
- { key: readability-function-cognitive-complexity.Threshold, value: 100 }
|
||||
- { key: readability-identifier-naming.StructCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.FunctionCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.ConstexprVariablePrefix, value: k }
|
||||
- { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.GlobalConstantPrefix, value: k }
|
||||
- { key: readability-identifier-naming.StaticConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.StaticConstantPrefix, value: k }
|
||||
- { key: readability-function-cognitive-complexity.Threshold, value: 100 }
|
||||
- { key: readability-identifier-naming.StructCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.FunctionCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
||||
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.ConstexprVariablePrefix, value: k }
|
||||
- { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.GlobalConstantPrefix, value: k }
|
||||
- { key: readability-identifier-naming.StaticConstantCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.StaticConstantPrefix, value: k }
|
||||
- { key: readability-identifier-naming.MinimumParameterNameLength, value: 0 }
|
||||
|
||||
MinimumParameterNameLength: 0
|
||||
|
||||
1132
.github/workflows/main.yml
vendored
1132
.github/workflows/main.yml
vendored
File diff suppressed because it is too large
Load Diff
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,5 +1,7 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
testrun
|
||||
benchmarkrun
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
@@ -7,6 +9,8 @@
|
||||
*.obj
|
||||
*.elf
|
||||
*.ll
|
||||
*.wasm
|
||||
*.s
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
@@ -69,20 +73,34 @@ out/
|
||||
/cmake-build-debug/
|
||||
/cmake-build-release/
|
||||
|
||||
# Emacs files
|
||||
CMakeFiles/cmake.check_cache
|
||||
CMakeCache.txt
|
||||
|
||||
# etags(Emacs), ctags, gtags
|
||||
TAGS
|
||||
GPATH
|
||||
GRTAGS
|
||||
GTAGS
|
||||
tags
|
||||
|
||||
# Clangd LSP files
|
||||
/.cache/
|
||||
/compile_commands.json
|
||||
|
||||
# 'nix build' resulting symlink
|
||||
# Nix
|
||||
result
|
||||
/.envrc
|
||||
/.direnv/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# tests
|
||||
/test/tmp/*
|
||||
/test/testrun
|
||||
/test/test_suite_runner
|
||||
testrun
|
||||
test_suite_runner
|
||||
|
||||
# patches, originals and rejects
|
||||
*.patch
|
||||
*.rej
|
||||
*.orig
|
||||
|
||||
491
CMakeLists.txt
491
CMakeLists.txt
@@ -1,5 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
set(C3_LLVM_MIN_VERSION 17)
|
||||
set(C3_LLVM_MAX_VERSION 22)
|
||||
set(C3_LLVM_DEFAULT_VERSION 21)
|
||||
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
|
||||
message(FATAL_ERROR "In-tree build detected, please build in a separate directory")
|
||||
endif()
|
||||
|
||||
# Grab the version
|
||||
file(READ "src/version.h" ver)
|
||||
if (NOT ${ver} MATCHES "COMPILER_VERSION \"([0-9]+.[0-9]+.[0-9]+)\"")
|
||||
@@ -7,8 +15,20 @@ if (NOT ${ver} MATCHES "COMPILER_VERSION \"([0-9]+.[0-9]+.[0-9]+)\"")
|
||||
endif()
|
||||
|
||||
# Set the project and version
|
||||
project(c3c VERSION ${CMAKE_MATCH_1})
|
||||
message("C3C version: ${CMAKE_PROJECT_VERSION}")
|
||||
project(c3c VERSION ${CMAKE_MATCH_1} LANGUAGES C CXX)
|
||||
message("Configuring C3C ${CMAKE_PROJECT_VERSION} for ${CMAKE_SYSTEM_NAME}")
|
||||
|
||||
# Helper functions
|
||||
function(c3_print_variables)
|
||||
set(msg "")
|
||||
foreach(var ${ARGN})
|
||||
if(msg)
|
||||
string(APPEND msg " ; ")
|
||||
endif()
|
||||
string(APPEND msg "${c3_print_prefix}${var}=\"${${var}}\"")
|
||||
endforeach()
|
||||
message(STATUS "${msg}")
|
||||
endfunction()
|
||||
|
||||
# Avoid warning for FetchContent
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
||||
@@ -16,7 +36,7 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
|
||||
if (MSVC)
|
||||
if (WIN32)
|
||||
set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib")
|
||||
set(CMAKE_INSTALL_BINDIR "c:\\c3c")
|
||||
else ()
|
||||
@@ -38,34 +58,63 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
if(MSVC)
|
||||
message(STATUS "MSVC version ${MSVC_VERSION}")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc /utf-8")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc /utf-8")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
|
||||
else()
|
||||
if (true)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O0 -fno-exceptions")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O0 -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")
|
||||
add_compile_options(/utf-8)
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
FetchContent_GetProperties(LLVM_Windows)
|
||||
if(NOT LLVM_Windows_URL MATCHES "msvcrt")
|
||||
set(MSVC_CRT_SUFFIX "")
|
||||
message(STATUS "Detected STATIC LLVM (libcmt)")
|
||||
else()
|
||||
set(MSVC_CRT_SUFFIX "DLL")
|
||||
message(STATUS "Detected DYNAMIC LLVM (msvcrt)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Force the Runtime to Release (/MT or /MD) even in Debug
|
||||
# This is required to match our RelWithDebInfo LLVM builds
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
set_property(GLOBAL PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
|
||||
add_compile_definitions(_ITERATOR_DEBUG_LEVEL=0)
|
||||
|
||||
# Suppresses the LNK4098 mismatch warning in Debug builds
|
||||
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:libcmtd /NODEFAULTLIB:msvcrtd")
|
||||
else()
|
||||
add_compile_options(-gdwarf-3 -fno-exceptions)
|
||||
|
||||
#add_compile_options(-fsanitize=address,undefined)
|
||||
#add_link_options(-fsanitize=address,undefined)
|
||||
endif()
|
||||
|
||||
option(C3_LINK_DYNAMIC "link dynamically with LLVM/LLD libs")
|
||||
# Options
|
||||
set(C3_LINK_DYNAMIC OFF CACHE BOOL "Link dynamically with LLVM/LLD libs")
|
||||
set(C3_WITH_LLVM ON CACHE BOOL "Build with LLVM")
|
||||
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
|
||||
set(C3_USE_MIMALLOC OFF CACHE BOOL "Use built-in mimalloc")
|
||||
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
|
||||
set(C3_USE_TB OFF CACHE BOOL "Use TB")
|
||||
set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory")
|
||||
set(C3_LLD_INCLUDE_DIR "" CACHE STRING "Use custom LLD include directory")
|
||||
set(C3_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation")
|
||||
set(C3_FETCH_LLVM OFF CACHE BOOL "Automatically download LLVM artifacts")
|
||||
set(C3_LLVM_TAG "llvm_21.x" CACHE STRING "Tag/Branch to download LLVM from")
|
||||
set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
|
||||
set(TCC_LIB_PATH "/usr/lib/tcc/libtcc1.a" CACHE STRING "Use custom libtcc1.a path")
|
||||
|
||||
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" "")
|
||||
option(LLVM_CRT_LIBRARY_DIR "Use custom llvm's compiler-rt directory" "")
|
||||
set(C3_OPTIONS
|
||||
C3_LINK_DYNAMIC
|
||||
C3_WITH_LLVM
|
||||
C3_FETCH_LLVM
|
||||
C3_LLVM_TAG
|
||||
C3_LLVM_VERSION
|
||||
C3_USE_MIMALLOC
|
||||
C3_MIMALLOC_TAG
|
||||
C3_USE_TB
|
||||
C3_LLD_DIR
|
||||
C3_ENABLE_CLANGD_LSP
|
||||
LLVM_CRT_LIBRARY_DIR
|
||||
)
|
||||
|
||||
set(C3_USE_MIMALLOC OFF)
|
||||
if(C3_USE_MIMALLOC)
|
||||
@@ -80,16 +129,6 @@ if(C3_USE_MIMALLOC)
|
||||
)
|
||||
FetchContent_MakeAvailable(mimalloc)
|
||||
endif()
|
||||
if (NOT WIN32)
|
||||
find_package(CURL)
|
||||
endif()
|
||||
if(C3_WITH_LLVM)
|
||||
if (NOT C3_LLVM_VERSION STREQUAL "auto")
|
||||
if (${C3_LLVM_VERSION} VERSION_LESS 17 OR ${C3_LLVM_VERSION} VERSION_GREATER 21)
|
||||
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(Git QUIET)
|
||||
if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
@@ -107,7 +146,6 @@ if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
endif()
|
||||
|
||||
# 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(
|
||||
@@ -118,37 +156,84 @@ if(C3_ENABLE_CLANGD_LSP)
|
||||
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")
|
||||
if(C3_FETCH_LLVM)
|
||||
# 1. Determine local platform ID
|
||||
if (WIN32)
|
||||
set(C3_LLVM_PLATFORM "windows-amd64")
|
||||
elseif (APPLE)
|
||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64")
|
||||
set(C3_LLVM_PLATFORM "darwin-aarch64")
|
||||
else()
|
||||
set(C3_LLVM_PLATFORM "darwin-amd64")
|
||||
endif()
|
||||
else() # Linux
|
||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "aarch64")
|
||||
set(C3_LLVM_PLATFORM "linux-aarch64")
|
||||
elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "riscv64")
|
||||
set(C3_LLVM_PLATFORM "linux-riscv64")
|
||||
else()
|
||||
set(C3_LLVM_PLATFORM "linux-amd64")
|
||||
endif()
|
||||
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)
|
||||
# 2. Determine if we want Debug or Release
|
||||
set(C3_LLVM_SUFFIX "")
|
||||
set(C3_LLVM_TYPE "Release")
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(C3_LLVM_SUFFIX "-dbg")
|
||||
set(C3_LLVM_TYPE "Debug")
|
||||
endif()
|
||||
|
||||
# 3. Construct URL
|
||||
set(C3_LLVM_ARTIFACT_NAME "llvm-${C3_LLVM_PLATFORM}${C3_LLVM_SUFFIX}")
|
||||
#set(C3_LLVM_URL "https://github.com/c3lang/c3-llvm/releases/download/${C3_LLVM_TAG}/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
|
||||
#set(C3_LLVM_URL "https://github.com//ManuLinares/llvm-custom-builds/releases/download/${C3_LLVM_TAG}/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
|
||||
|
||||
# We could also just set "latest" here to always fetch the latest release
|
||||
set(C3_LLVM_URL "https://github.com/c3lang/llvm-for-c3/releases/latest/download/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
|
||||
|
||||
message(STATUS "Fetching ${C3_LLVM_TYPE} LLVM artifact for ${C3_LLVM_PLATFORM}...")
|
||||
message(STATUS "URL: ${C3_LLVM_URL}")
|
||||
|
||||
FetchContent_Declare(
|
||||
LLVM_Artifact
|
||||
URL ${C3_LLVM_URL}
|
||||
)
|
||||
FetchContent_MakeAvailable(LLVM_Artifact)
|
||||
|
||||
# 4. Point CMake to the fetched location
|
||||
set(llvm_dir ${llvm_artifact_SOURCE_DIR})
|
||||
set(CMAKE_PREFIX_PATH ${llvm_dir} ${CMAKE_PREFIX_PATH})
|
||||
set(LLVM_DIR "${llvm_dir}/lib/cmake/llvm")
|
||||
set(LLD_DIR "${llvm_dir}/lib/cmake/lld")
|
||||
|
||||
# TEST: For Windows, we might need to add the bin dir to prefix path for find_package to work well
|
||||
if (WIN32)
|
||||
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH})
|
||||
endif()
|
||||
|
||||
find_package(LLVM REQUIRED CONFIG NO_DEFAULT_PATH)
|
||||
find_package(LLD REQUIRED CONFIG NO_DEFAULT_PATH)
|
||||
else()
|
||||
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
|
||||
# Legacy MSVC path if someone explicitly disabled fetching but is on MSVC
|
||||
find_package(LLVM REQUIRED CONFIG)
|
||||
find_package(LLD REQUIRED CONFIG)
|
||||
else()
|
||||
# Default system search
|
||||
file (GLOB LLVM_CMAKE_PATHS "/usr/lib/llvm/*/lib/cmake/llvm/")
|
||||
list (APPEND CMAKE_PREFIX_PATH ${LLVM_CMAKE_PATHS} "/usr/lib/")
|
||||
|
||||
if (NOT C3_LLVM_VERSION STREQUAL "auto")
|
||||
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
|
||||
else()
|
||||
find_package(LLVM REQUIRED CONFIG)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT C3_FETCH_LLVM AND EXISTS /opt/homebrew/lib)
|
||||
list(APPEND LLVM_LIBRARY_DIRS /opt/homebrew/lib)
|
||||
endif()
|
||||
|
||||
if (EXISTS /usr/lib)
|
||||
@@ -158,12 +243,15 @@ if(C3_WITH_LLVM)
|
||||
list(APPEND LLVM_LIBRARY_DIRS /usr/lib)
|
||||
endif()
|
||||
|
||||
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
|
||||
|
||||
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
|
||||
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
|
||||
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
|
||||
message(STATUS "LLVM 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.")
|
||||
if (${LLVM_PACKAGE_VERSION} VERSION_LESS C3_LLVM_MIN_VERSION OR
|
||||
${LLVM_PACKAGE_VERSION} VERSION_GREATER C3_LLVM_MAX_VERSION)
|
||||
message(FATAL_ERROR "LLVM ${LLVM_PACKAGE_VERSION} is not supported! LLVM version between ${C3_LLVM_MIN_VERSION} and ${C3_LLVM_MAX_VERSION} is required.")
|
||||
endif()
|
||||
|
||||
if(LLVM_ENABLE_RTTI)
|
||||
@@ -213,44 +301,48 @@ if(C3_WITH_LLVM)
|
||||
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(APPEND LLVM_LIBRARY_DIRS ${C3_LLD_DIR})
|
||||
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
|
||||
endif()
|
||||
|
||||
message(STATUS "Looking for static lld libraries in ${LLVM_LIBRARY_DIRS}")
|
||||
|
||||
# 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)
|
||||
find_library(LLD_COFF NAMES lldCOFF.a liblldCOFF.a liblldCOFF.dylib lldCOFF.lib liblldCOFF.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_COMMON NAMES lldCommon.a liblldCommon.a liblldCommon.dylib lldCommon.lib liblldCommon.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_ELF NAMES lldELF.a liblldELF.a liblldELF.dylib lldELF.lib liblldELF.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
||||
find_library(LLD_MACHO NAMES lldMachO.a liblldMachO.a liblldMachO.dylib lldMachO.lib liblldMachO.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
else()
|
||||
set(LLD_MACHO "")
|
||||
endif()
|
||||
find_library(LLD_MINGW NAMES lldMinGW.a liblldMinGW.a liblldMinGW.dylib lldMinGW.lib liblldMinGW.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_WASM NAMES lldWasm.a liblldWasm.a liblldWasm.dylib lldWasm.lib liblldWasm.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
else()
|
||||
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
message(STATUS "Looking for shared lld libraries in ${LLVM_LIBRARY_DIRS}")
|
||||
|
||||
#find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
if(UNIX AND NOT WIN32)
|
||||
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} REQUIRED)
|
||||
else()
|
||||
find_library(LLVM NAMES libLLVM.a LLVM.lib PATHS ${LLVM_LIBRARY_DIRS} REQUIRED)
|
||||
endif()
|
||||
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)
|
||||
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
||||
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
else()
|
||||
set(LLD_MACHO "")
|
||||
endif()
|
||||
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
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()
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
# 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_WASM}
|
||||
@@ -262,21 +354,40 @@ if(C3_WITH_LLVM)
|
||||
|
||||
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" ${LLVM_CRT_LIBRARY_DIR})
|
||||
find_file(RT_TSAN_DYNAMIC NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
|
||||
find_file(RT_UBSAN_DYNAMIC NAMES libclang_rt.ubsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
|
||||
find_file(RT_LSAN_DYNAMIC NAMES libclang_rt.lsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
|
||||
set(sanitizer_runtime_libraries
|
||||
${RT_ASAN_DYNAMIC}
|
||||
${RT_TSAN_DYNAMIC}
|
||||
# Unused
|
||||
# ${RT_UBSAN_DYNAMIC}
|
||||
# ${RT_LSAN_DYNAMIC}
|
||||
)
|
||||
if (llvm_dir)
|
||||
file(GLOB_RECURSE RT_ASAN_DYNAMIC "${llvm_dir}/*libclang_rt.asan_osx_dynamic.dylib")
|
||||
file(GLOB_RECURSE RT_TSAN_DYNAMIC "${llvm_dir}/*libclang_rt.tsan_osx_dynamic.dylib")
|
||||
endif()
|
||||
|
||||
if (NOT RT_ASAN_DYNAMIC OR NOT RT_TSAN_DYNAMIC)
|
||||
# Fallback to searching in LLVM_LIBRARY_DIRS (for non-fetched LLVM)
|
||||
find_file(RT_ASAN_DYNAMIC_PATH NAMES libclang_rt.asan_osx_dynamic.dylib PATHS ${LLVM_LIBRARY_DIRS} PATH_SUFFIXES "clang/${LLVM_MAJOR_VERSION}/lib/darwin" "clang/${LLVM_PACKAGE_VERSION}/lib/darwin")
|
||||
find_file(RT_TSAN_DYNAMIC_PATH NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS ${LLVM_LIBRARY_DIRS} PATH_SUFFIXES "clang/${LLVM_MAJOR_VERSION}/lib/darwin" "clang/${LLVM_PACKAGE_VERSION}/lib/darwin")
|
||||
if (RT_ASAN_DYNAMIC_PATH)
|
||||
set(RT_ASAN_DYNAMIC ${RT_ASAN_DYNAMIC_PATH})
|
||||
endif()
|
||||
if (RT_TSAN_DYNAMIC_PATH)
|
||||
set(RT_TSAN_DYNAMIC ${RT_TSAN_DYNAMIC_PATH})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (RT_ASAN_DYNAMIC)
|
||||
list(GET RT_ASAN_DYNAMIC 0 RT_ASAN_DYNAMIC)
|
||||
endif()
|
||||
if (RT_TSAN_DYNAMIC)
|
||||
list(GET RT_TSAN_DYNAMIC 0 RT_TSAN_DYNAMIC)
|
||||
endif()
|
||||
|
||||
if (RT_ASAN_DYNAMIC AND RT_TSAN_DYNAMIC)
|
||||
set(sanitizer_runtime_libraries
|
||||
${RT_ASAN_DYNAMIC}
|
||||
${RT_TSAN_DYNAMIC}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS "linking to llvm libs ${lld_libs}")
|
||||
message(STATUS "Found lld libs ${lld_libs}")
|
||||
message(STATUS "Linking to llvm libs ${llvm_libs}")
|
||||
message(STATUS "Linking to lld libs ${lld_libs}")
|
||||
endif()
|
||||
|
||||
add_library(miniz STATIC dependencies/miniz/miniz.c)
|
||||
@@ -350,9 +461,12 @@ add_executable(c3c
|
||||
src/utils/whereami.c
|
||||
src/utils/cpus.c
|
||||
src/utils/unzipper.c
|
||||
src/utils/msi.c
|
||||
src/compiler/c_codegen.c
|
||||
src/compiler/decltable.c
|
||||
src/compiler/methodtable.c
|
||||
src/compiler/mac_support.c
|
||||
src/utils/fetch_msvc.c
|
||||
src/compiler/windows_support.c
|
||||
src/compiler/codegen_asm.c
|
||||
src/compiler/asm_target.c
|
||||
@@ -394,9 +508,23 @@ if(C3_WITH_LLVM)
|
||||
|
||||
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=1)
|
||||
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
|
||||
if (MSVC)
|
||||
# Use the same detected CRT for the wrapper
|
||||
set_target_properties(c3c_wrappers PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
set_target_properties(miniz PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
|
||||
|
||||
target_compile_options(c3c PRIVATE
|
||||
"$<$<CONFIG:Debug>:/EHa>"
|
||||
"$<$<CONFIG:Release>:/EHsc>")
|
||||
endif()
|
||||
|
||||
if(C3_LLD_INCLUDE_DIR)
|
||||
target_include_directories(c3c_wrappers PRIVATE ${C3_LLD_INCLUDE_DIR})
|
||||
endif()
|
||||
else()
|
||||
target_sources(c3c PRIVATE src/utils/hostinfo.c)
|
||||
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0)
|
||||
target_link_libraries(c3c m)
|
||||
endif()
|
||||
|
||||
target_include_directories(c3c PRIVATE
|
||||
@@ -446,7 +574,7 @@ else()
|
||||
endif()
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
target_link_libraries(c3c ${llvm_libs} miniz c3c_wrappers ${lld_libs})
|
||||
target_link_libraries(c3c miniz c3c_wrappers)
|
||||
|
||||
target_include_directories(c3c PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/wrapper/include/")
|
||||
@@ -454,11 +582,11 @@ if(C3_WITH_LLVM)
|
||||
target_include_directories(c3c_wrappers PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/wrapper/include/")
|
||||
|
||||
target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
|
||||
target_link_libraries(c3c_wrappers PUBLIC ${lld_libs} ${llvm_libs})
|
||||
|
||||
else()
|
||||
|
||||
target_link_libraries(c3c ${llvm_libs} miniz ${lld_libs})
|
||||
target_link_libraries(c3c miniz ${lld_libs} ${llvm_libs})
|
||||
|
||||
endif()
|
||||
|
||||
@@ -475,60 +603,79 @@ if(MINGW)
|
||||
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})
|
||||
target_compile_definitions(c3c PUBLIC CURL_FOUND=1)
|
||||
else()
|
||||
target_compile_definitions(c3c PUBLIC CURL_FOUND=0)
|
||||
if (NOT WIN32)
|
||||
# For dlopen support
|
||||
if (CMAKE_DL_LIBS)
|
||||
target_link_libraries(c3c ${CMAKE_DL_LIBS})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
if(MSVC)
|
||||
message("Adding MSVC options")
|
||||
target_compile_options(c3c PRIVATE /wd4068 /wd4090 /WX /Wv:18)
|
||||
target_compile_options(c3c PRIVATE
|
||||
/wd4068
|
||||
/wd4090
|
||||
/WX
|
||||
/Wv:18
|
||||
)
|
||||
|
||||
if(C3_WITH_LLVM)
|
||||
target_compile_options(c3c_wrappers PUBLIC /wd4624 /wd4267 /wd4244 /WX /Wv:18)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
# The the sanitizer libs are in the folder "lib/clang/21/lib/windows/" but use the find anyway
|
||||
file(GLOB_RECURSE FOUND_ASAN_LIB "${llvm_dir}/*clang_rt.asan_dynamic-x86_64.lib")
|
||||
if(FOUND_ASAN_LIB)
|
||||
list(GET FOUND_ASAN_LIB 0 _asan_path)
|
||||
get_filename_component(_asan_dir "${_asan_path}" DIRECTORY)
|
||||
set(sanitizer_runtime_libraries
|
||||
${_asan_dir}/clang_rt.asan_dynamic-x86_64.lib
|
||||
${_asan_dir}/clang_rt.asan_dynamic-x86_64.dll
|
||||
${_asan_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
|
||||
message(STATUS "Found Sanitizer binaries at: ${_asan_dir}")
|
||||
else()
|
||||
message(WARNING "Could not find sanitizer runtime libraries in ${llvm_dir}")
|
||||
endif()
|
||||
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)
|
||||
target_compile_options(c3c PRIVATE
|
||||
-pthread
|
||||
-Wall
|
||||
-Werror
|
||||
-Wno-unknown-pragmas
|
||||
-Wno-unused-result
|
||||
-Wno-unused-function
|
||||
-Wno-unused-variable
|
||||
-Wno-unused-parameter
|
||||
-Wno-char-subscripts
|
||||
)
|
||||
target_link_options(c3c PRIVATE -pthread)
|
||||
endif()
|
||||
|
||||
if(CMAKE_C_COMPILER_ID STREQUAL "TinyCC")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-eh-frame-hdr -z noexecstack")
|
||||
|
||||
# Link the static tcc runtime archive if it exists
|
||||
if(EXISTS "${TCC_LIB_PATH}")
|
||||
target_link_libraries(c3c "${TCC_LIB_PATH}")
|
||||
else()
|
||||
message(FATAL_ERROR "TCC runtime not found at ${TCC_LIB_PATH}; Ensure the path is correct.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
install(TARGETS c3c DESTINATION bin)
|
||||
@@ -539,6 +686,12 @@ if (NOT WIN32)
|
||||
install(FILES c3c.1 DESTINATION "share/man/man1")
|
||||
endif()
|
||||
|
||||
# Copy stdlib
|
||||
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()
|
||||
|
||||
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
|
||||
@@ -549,8 +702,10 @@ if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
|
||||
|
||||
if (APPLE)
|
||||
# Change LC_ID_DYLIB to be rpath-based instead of having an absolute path
|
||||
# We set DYLD_LIBRARY_PATH so the tools (which might be dynamic) can find libLLVM.dylib in the artifact
|
||||
string(REPLACE ";" ":" _dyld_path "${LLVM_LIBRARY_DIRS}")
|
||||
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>
|
||||
COMMAND ${CMAKE_COMMAND} -E env "DYLD_LIBRARY_PATH=${_dyld_path}:$ENV{DYLD_LIBRARY_PATH}" 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()
|
||||
|
||||
@@ -558,3 +713,35 @@ if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
|
||||
endif()
|
||||
|
||||
feature_summary(WHAT ALL)
|
||||
|
||||
message(STATUS "Building ${CMAKE_PROJECT_NAME} with the following configuration:")
|
||||
|
||||
set(c3_print_prefix " ")
|
||||
|
||||
foreach(option IN LISTS C3_OPTIONS)
|
||||
if (DEFINED ${option})
|
||||
c3_print_variables(${option})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(flag_var
|
||||
CMAKE_BUILD_TYPE
|
||||
CMAKE_C_COMPILER
|
||||
CMAKE_CXX_COMPILER
|
||||
CMAKE_LINKER
|
||||
CMAKE_OBJCOPY
|
||||
CMAKE_STRIP
|
||||
CMAKE_DLLTOOL)
|
||||
c3_print_variables(${flag_var})
|
||||
endforeach()
|
||||
|
||||
message(STATUS "Build flags:")
|
||||
foreach(flag_var
|
||||
CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
|
||||
CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO
|
||||
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
|
||||
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
|
||||
c3_print_variables(${flag_var})
|
||||
endforeach()
|
||||
|
||||
message(STATUS "Output to: \"${CMAKE_BINARY_DIR}\"")
|
||||
|
||||
57
CMakePresets.json
Normal file
57
CMakePresets.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"version": 3,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "windows-base",
|
||||
"hidden": true,
|
||||
"architecture": {
|
||||
"value": "x64"
|
||||
},
|
||||
"toolset": {
|
||||
"value": "host=x64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-vs-2022-release",
|
||||
"generator": "Visual Studio 17 2022",
|
||||
"displayName": "Windows x64 Visual Studio 17 2022",
|
||||
"inherits": "windows-base",
|
||||
"binaryDir": "build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CONFIGURATION_TYPES": "Release;RelWithDebInfo",
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-vs-2022-debug",
|
||||
"generator": "Visual Studio 17 2022",
|
||||
"displayName": "Windows x64 Visual Studio 17 2022 (Debug)",
|
||||
"inherits": "windows-base",
|
||||
"binaryDir": "build-debug",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CONFIGURATION_TYPES": "Debug",
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "windows-vs-2022-debug",
|
||||
"displayName": "Debug",
|
||||
"configurePreset": "windows-vs-2022-debug",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "windows-vs-2022-release",
|
||||
"displayName": "Release",
|
||||
"configurePreset": "windows-vs-2022-release",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "windows-vs-2022-release-with-debug-info",
|
||||
"displayName": "RelWithDebInfo",
|
||||
"configurePreset": "windows-vs-2022-release",
|
||||
"configuration": "RelWithDebInfo"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -151,8 +151,9 @@ one with related functions when working on temporary strings.
|
||||
|
||||
# C3 Standard library style guide.
|
||||
|
||||
When contributing to the standard librairy please to your best to follow the following style requirements
|
||||
as to ensure a consistent style in the stdlib and also make accepting PRs more quickly.
|
||||
When contributing to the standard library please try your best to adhere to the
|
||||
following style requirements to ensure a consistent style in the stdlib and to
|
||||
facilitate accepting PRs more quickly.
|
||||
|
||||
### Braces are placed on the next line
|
||||
|
||||
@@ -256,4 +257,4 @@ argument.
|
||||
## Add tests to your changes
|
||||
|
||||
If you add or fix things, then there should always be tests in `test/unit/stdlib` to verify
|
||||
the functionality.
|
||||
the functionality.
|
||||
|
||||
@@ -59,7 +59,7 @@ further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at . All
|
||||
reported by contacting the project team at info@c3-lang.org. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
70
CONTRIBUTING.md
Normal file
70
CONTRIBUTING.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# How to contribute to C3
|
||||
|
||||
The C3 project consists of
|
||||
|
||||
1. The C3 language itself.
|
||||
2. The C3 compiler, called c3c.
|
||||
3. The C3 standard library
|
||||
4. Various tools, such as the editor plugins
|
||||
|
||||
## 1. How to contribute to the C3 language
|
||||
|
||||
The C3 language is essentially the language specification. You can contribute to the language by:
|
||||
|
||||
1. Filing enhancement requests for changes to the language.
|
||||
2. Offering feedback on existing features, on Discord or by filing issues.
|
||||
3. Help working on the language specification.
|
||||
4. Help working on the grammar.
|
||||
|
||||
## 2. How to contribute to the C3 compiler
|
||||
|
||||
The C3 compiler consists for the compiler itself + test suites for testing the compiler.
|
||||
You can contribute by:
|
||||
|
||||
1. File bugs (by far the most important thing).
|
||||
2. Suggest improved diagnostics / error messages.
|
||||
3. Refactoring existing code (needs deep understanding of the compiler).
|
||||
4. Add support for more architectures.
|
||||
5. Add support for more backends.
|
||||
|
||||
## 3. How to contribute to the standard library
|
||||
|
||||
The standard library is the library itself + test suites for testing the standard library.
|
||||
You can contribute by:
|
||||
|
||||
1. Filing bugs on the standard library.
|
||||
2. Write additional unit tests.
|
||||
3. Suggest new functionality by filing an issue.
|
||||
4. Work on stdlib additions.
|
||||
5. Fix bugs in the stdlib
|
||||
6. Maintain a section of the standard library
|
||||
|
||||
### How to work on small stdlib additions
|
||||
|
||||
If there is just a matter of adding a function or two to an existing module, a pull request
|
||||
is sufficient. However, please make sure that:
|
||||
|
||||
1. It follows the guidelines for the code to ensure a uniform experience (naming standard, indentation, braces etc).
|
||||
2. Add a line in the release notes about the change.
|
||||
3. Make sure it has unit tests.
|
||||
|
||||
### How to work on non-trivial additions to the stdlib
|
||||
|
||||
Regardless whether an addition is approved for inclusion or not, it needs to incubate:
|
||||
|
||||
1. First implement it standalone, showing that it’s working well and has a solid design. This has the advantage of people being able to contribute or even create competing implementations
|
||||
2. Once it is considered finished it can be proposed for inclusion.
|
||||
|
||||
This will greatly help improving the quality of additions.
|
||||
|
||||
Note that any new addition needs a full set of unit tests before being included into the standard library.
|
||||
|
||||
### Maintain a part of the standard library
|
||||
|
||||
A single maintainer is insufficient for a standard library, instead we need one or more maintainer
|
||||
for each module. The maintainer(s) will review pull requests and actively work on making the module
|
||||
pristine with the highest possible quality.
|
||||
|
||||
## 4. How to contribute to various tools
|
||||
|
||||
In general, file a pull request. Depending on who maintains it, rules may differ.
|
||||
179
LICENSE
179
LICENSE
@@ -1,165 +1,20 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
Copyright (c) 2022-2025 Christoffer Lernö and contributors
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.
|
||||
165
LICENSE_SRC
Normal file
165
LICENSE_SRC
Normal file
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -1,20 +0,0 @@
|
||||
Copyright (c) 2022 Christoffer Lernö and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.
|
||||
269
README.md
269
README.md
@@ -8,16 +8,19 @@ for programmers who like C.
|
||||
|
||||
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).
|
||||
- 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-macos-with-precompiled-binaries).
|
||||
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
|
||||
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
|
||||
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
|
||||
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
|
||||
- OpenBSD x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz), [install instructions](#installing-on-openbsd-with-precompiled-binaries).
|
||||
|
||||
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
|
||||
|
||||

|
||||
|
||||
Thanks to full ABI compatibility with C, it's possible to mix C and C3 in the same project with no effort. As a demonstration, vkQuake was compiled with a small portion of the code converted to C3 and compiled with the c3c compiler. (The fork can be found at https://github.com/c3lang/vkQuake)
|
||||
Thanks to full ABI compatibility with C, it's possible to mix C and C3 in the same project with no effort. As a demonstration, vkQuake was compiled with a small portion of the code converted to C3 and compiled with the c3c compiler. (The aging fork can be found at https://github.com/c3lang/vkQuake)
|
||||
|
||||
A non-curated list of user written projects and other resources can be found [here](https://github.com/c3lang/c3-showcase).
|
||||
|
||||
### Design Principles
|
||||
- Procedural "get things done"-type of language.
|
||||
@@ -33,10 +36,10 @@ whole new language.
|
||||
|
||||
### Example code
|
||||
|
||||
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/).
|
||||
The following code shows [generics](https://c3-lang.org/generic-programming/generics/) (more examples can be found at https://c3-lang.org/language-overview/examples/).
|
||||
|
||||
```cpp
|
||||
module stack {Type};
|
||||
```c3
|
||||
module stack <Type>;
|
||||
// Above: the parameterized type is applied to the entire module.
|
||||
|
||||
struct Stack
|
||||
@@ -75,7 +78,7 @@ fn bool Stack.empty(Stack* this)
|
||||
|
||||
Testing it out:
|
||||
|
||||
```cpp
|
||||
```c3
|
||||
import stack;
|
||||
|
||||
// Define our new types, the first will implicitly create
|
||||
@@ -124,6 +127,7 @@ fn void main()
|
||||
- New semantic macro system
|
||||
- Module based name spacing
|
||||
- Slices
|
||||
- Operator overloading
|
||||
- Compile time reflection
|
||||
- Enhanced compile time execution
|
||||
- Generics based on generic modules
|
||||
@@ -138,15 +142,16 @@ fn void main()
|
||||
|
||||
### Current status
|
||||
|
||||
The current stable version of the compiler is **version 0.6.8**.
|
||||
The current stable version of the compiler is **version 0.7.10**.
|
||||
|
||||
The the next version is 0.7.0 which will be a breaking release.
|
||||
The upcoming 0.7.x releases will focus on expanding the standard library,
|
||||
fixing bugs and improving compile time analysis.
|
||||
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)
|
||||
or discuss C3 on its dedicated Discord: [https://discord.gg/qN76R87](https://discord.gg/qN76R87).
|
||||
|
||||
The compiler is currently verified to compile on Linux, Windows and MacOS.
|
||||
The compiler is currently verified to compile on Linux, OpenBSD, Windows and MacOS.
|
||||
|
||||
**Support matrix**
|
||||
|
||||
@@ -157,6 +162,8 @@ The compiler is currently verified to compile on Linux, Windows and MacOS.
|
||||
| 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* |
|
||||
| Android Aarch64 | No | Untested | Untested | Untested | Untested | Yes* |
|
||||
| Android x64 | No | Untested | Untested | Untested | Untested | 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* |
|
||||
@@ -167,17 +174,21 @@ The compiler is currently verified to compile on Linux, Windows and MacOS.
|
||||
| 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 |
|
||||
| ELF freestanding Xtensa* | 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* |
|
||||
| OpenBSD x64 | Yes* | Yes | Yes* | 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*
|
||||
*\* Inline asm is still a work in progress*<br>
|
||||
*\* OpenBSD 7.7 is the only tested version*<br>
|
||||
*\* OpenBSD has limited stacktrace, needs to be tested further*<br>
|
||||
*\* Xtensa support is enabled by compiling with `-DXTENSA_ENABLE`. The [espressif llvm fork](https://github.com/espressif/llvm-project) is recommended for best compatibility*
|
||||
|
||||
More platforms will be supported in the future.
|
||||
|
||||
@@ -186,41 +197,82 @@ More platforms will be supported in the future.
|
||||
- If you wish to contribute with ideas, please file issues or discuss on Discord.
|
||||
- Interested in contributing to the stdlib? Please get in touch on Discord.
|
||||
- Compilation instructions for other Linux and Unix variants are appreciated.
|
||||
- Would you like to contribute bindings to some library? It would be nice to have support for SDL, Raylib and more.
|
||||
- Would you like to contribute bindings to some library? It would be nice to have support for SDL3 and more. If you have created some bindings, please submit them to https://github.com/c3lang/vendor.
|
||||
- Build something with C3 and show it off and give feedback. The language is still open for significant tweaks.
|
||||
- Start work on the C -> C3 converter which takes C code and does a "best effort" to translate it to C3. The first version only needs to work on C headers.
|
||||
- Do you have some specific area you have deep knowledge of and could help make C3 even better at doing? File or comment on issues.
|
||||
|
||||
### Installing
|
||||
|
||||
This installs the latest prerelease build, as opposed to the latest released version.
|
||||
|
||||
#### Installing on Windows with precompiled binaries
|
||||
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-windows-debug.zip))
|
||||
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows-debug.zip))
|
||||
2. Unzip exe and standard lib.
|
||||
3. If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
|
||||
4. Run `c3c.exe`.
|
||||
|
||||
#### Installing on Windows with the install script
|
||||
|
||||
Open a PowerShell terminal (you may need to run it as an administrator) and run the following command:
|
||||
```bash
|
||||
iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex
|
||||
```
|
||||
The script will inform you once the installation is successful and add the `~/.c3` directory to your PATH, which will allow you to run the c3c command from any location.
|
||||
|
||||
You can choose another version with option `C3_VERSION`.
|
||||
For example, you can force the installation of the 0.7.4 version:
|
||||
```bash
|
||||
$env:C3_VERSION='0.7.4'; powershell -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex"
|
||||
```
|
||||
|
||||
If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
|
||||
|
||||
|
||||
#### Installing on Debian with precompiled binaries
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-linux-debug.tar.gz))
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux-debug.tar.gz))
|
||||
2. Unpack executable and standard lib.
|
||||
3. Run `./c3c`.
|
||||
|
||||
#### Installing on Debian with the install script
|
||||
|
||||
Open a terminal and run the following command:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | bash
|
||||
```
|
||||
The C3 compiler will be installed, and the script will also update your ~/.bashrc to include `~/.c3` in your PATH, allowing you to invoke the c3c command from anywhere. You might need to restart your terminal or source your shell for the changes to take effect.
|
||||
|
||||
You can choose another version with option `C3_VERSION`.
|
||||
For example, you can force the installation of the 0.7.4 version:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | C3_VERSION=0.7.4 bash
|
||||
```
|
||||
|
||||
#### 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))
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/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))
|
||||
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos.zip)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/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 OpenBSD with precompiled binaries
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd-debug.tar.gz))
|
||||
2. Unpack executable and standard lib.
|
||||
3. Run `./c3c`.
|
||||
|
||||
(*Note that this is specifically for OpenBSD 7.7, running it on any other version is prone to ABI breaks)
|
||||
|
||||
#### Installing on Arch Linux
|
||||
Arch includes c3c in the official 'extra' repo. It can be easily installed the usual way:
|
||||
|
||||
@@ -247,6 +299,60 @@ cd c3c-git
|
||||
makepkg -si
|
||||
```
|
||||
|
||||
#### Installing via Nix
|
||||
|
||||
You can access `c3c` via [flake.nix](./flake.nix), which will contain the latest commit of the compiler. To add `c3c` to your `flake.nix`, do the following:
|
||||
```nix
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
c3c.url = "github:c3lang/c3c";
|
||||
# Those are desired if you don't want to copy extra nixpkgs
|
||||
c3c.inputs = {
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import inputs.nixpkgs { inherit system; };
|
||||
c3c = inputs.c3c.packages.${system}.c3c;
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.c3c
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Installing on Gentoo
|
||||
|
||||
`c3c` is available in the [Gentoo GURU overlay](https://wiki.gentoo.org/wiki/Project:GURU).
|
||||
|
||||
Enable and sync the GURU repository (if not already done):
|
||||
|
||||
```sh
|
||||
sudo eselect repository enable guru
|
||||
sudo emaint sync -r guru
|
||||
```
|
||||
|
||||
Install `c3c` with:
|
||||
|
||||
```sh
|
||||
sudo emerge -av dev-lang/c3c
|
||||
```
|
||||
|
||||
* The compiler binary is installed to `/usr/bin/c3c`.
|
||||
* The standard library is installed to `/usr/lib/c3`.
|
||||
|
||||
For Gentoo-specific issues, please use the [Gentoo Bugzilla](https://bugs.gentoo.org/) (Product: *GURU*).
|
||||
|
||||
#### Building via Docker
|
||||
|
||||
You can build `c3c` using an Ubuntu container. By default, the script will build through Ubuntu 22.04. You can specify the version by passing the `UBUNTU_VERSION` environment variable.
|
||||
@@ -259,14 +365,15 @@ See the `build-with-docker.sh` script for more information on other configurable
|
||||
|
||||
#### Installing on OS X using Homebrew
|
||||
|
||||
2. Install CMake: `brew install cmake`
|
||||
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`
|
||||
7. Change directory to the build directory `cd build`
|
||||
8. Set up CMake build for debug: `cmake ..`
|
||||
9. Build: `cmake --build .`
|
||||
1. Install [Homebrew](https://brew.sh/)
|
||||
2. Install LLVM 17+: `brew install llvm`
|
||||
3. Install lld: `brew install lld`
|
||||
4. Install CMake: `brew install cmake`
|
||||
5. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
6. Enter the C3C directory `cd c3c`.
|
||||
7. Set up CMake build for debug: `cmake -B build -S .`
|
||||
8. Build: `cmake --build build`
|
||||
9. Change directory to the build directory `cd build`
|
||||
|
||||
#### Installing on Windows using Scoop
|
||||
|
||||
@@ -279,7 +386,7 @@ scoop install c3
|
||||
#### Getting started with a "hello world"
|
||||
|
||||
Create a `main.c3` file with:
|
||||
```c++
|
||||
```c3
|
||||
module hello_world;
|
||||
import std::io;
|
||||
|
||||
@@ -307,28 +414,35 @@ called `hello_world` or `hello_world.exe`depending on platform.
|
||||
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`.
|
||||
5. Set up the CMake build `cmake -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release`
|
||||
6. Build: `cmake --build build --config Release`
|
||||
7. You should now have the c3c.exe
|
||||
4. Enter the C3C directory: `cd c3c`.
|
||||
5. Set up the CMake build: `cmake --preset windows-vs-2022-release`
|
||||
6. Build: `cmake --build --preset windows-vs-2022-release`
|
||||
|
||||
You should now have a `c3c` executable.
|
||||
You should now have a `c3c` executable in `build\Release`.
|
||||
|
||||
You can try it out by running some sample code: `c3c.exe compile ../resources/examples/hash.c3`
|
||||
You can try it out by running some sample code: `c3c.exe compile ../../resources/examples/hash.c3`
|
||||
|
||||
Building `c3c` using Visual Studio Code is also supported when using the `CMake Tools` extension. Simply select the `Windows x64 Visual Studio 17 2022` configure preset and build.
|
||||
|
||||
*Note that if you run into linking issues when building, make sure that you are using the latest version of VS17.*
|
||||
|
||||
#### Compiling on Windows (Debug)
|
||||
|
||||
Debug build requires a different set of LLVM libraries to be loaded for which a separate CMake configuration is used to avoid conflicts.
|
||||
1. Configure: `cmake --preset windows-vs-2022-debug`
|
||||
2. Build: `cmake --build --preset windows-vs-2022-debug`
|
||||
|
||||
You should now have a `c3c` executable in `build-debug\Debug`.
|
||||
|
||||
#### 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 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`
|
||||
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`. If you're using Ubuntu 25.04, also install `libpolly-20-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 .`
|
||||
5. Set up CMake build: `cmake -B build -S .`
|
||||
6. Build: `cmake --build build`
|
||||
7. Change directory to the build directory `cd build`
|
||||
|
||||
You should now have a `c3c` executable.
|
||||
|
||||
@@ -341,13 +455,12 @@ You can try it out by running some sample code: `./c3c compile ../resources/exam
|
||||
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 .`
|
||||
4. Create the CMake build cache: `cmake -B build -S .`
|
||||
5. Build: `cmake --build build`
|
||||
6. Enter the build directory: `cd 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 .`
|
||||
For a system-wide installation, run the following as root: `cmake --install .`
|
||||
|
||||
|
||||
#### Compiling on Fedora
|
||||
@@ -357,12 +470,37 @@ For a sytem-wide installation, run the following as root: `cmake --install .`
|
||||
3. 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: `git clone https://github.com/c3lang/c3c.git --depth=1`
|
||||
4. Enter the C3C directory: `cd c3c`
|
||||
5. Create a build directory and navigate into it: `mkdir build && cd build`
|
||||
6. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake .. -DC3_LINK_DYNAMIC=1`
|
||||
7. Build the project: `cmake --build .`
|
||||
5. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake -B build -S . -DC3_LINK_DYNAMIC=1`
|
||||
6. Build the project: `cmake --build build`
|
||||
7. Enter the build directory: `cd build`
|
||||
|
||||
The c3c binary should be created in the build directory. You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
|
||||
|
||||
#### Compiling on Arch Linux
|
||||
|
||||
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm libxml2`
|
||||
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: `git clone https://github.com/c3lang/c3c.git --depth=1`
|
||||
3. Enter the C3C directory: `cd c3c`
|
||||
4. Create the CMake build cache:
|
||||
```bash
|
||||
cmake -B build \
|
||||
-D C3_LINK_DYNAMIC=ON \
|
||||
-D CMAKE_BUILD_TYPE=Release
|
||||
```
|
||||
5. Build the project: `cmake --build build`.
|
||||
|
||||
After compilation, the `c3c` binary will be located in the `build` directory. You can test it by compiling an example: `./build/c3c compile resources/examples/ls.c3`.
|
||||
|
||||
6. To install the compiler globally: `sudo cmake --install build`
|
||||
|
||||
#### Compiling on NixOS
|
||||
|
||||
1. Enter nix shell, by typing `nix develop` in root directory
|
||||
2. Configure cmake via `cmake . -Bbuild $=C3_CMAKE_FLAGS`. Note: passing `C3_CMAKE_FLAGS` is needed in due to generate `compile_commands.json` and find missing libs.
|
||||
4. Build it `cmake --build build`
|
||||
5. Test it out: `./build/c3c -V`
|
||||
6. If you use `clangd` lsp server for your editor, it is recommended to make a symbolic link to `compile_command.json` in the root: `ln -s ./build/compile_commands.json compile_commands.json`
|
||||
|
||||
#### Compiling on other Linux / Unix variants
|
||||
|
||||
@@ -370,20 +508,24 @@ The c3c binary should be created in the build directory. You can try it out by r
|
||||
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`
|
||||
6. Change directory to the build directory `cd build`
|
||||
7. Set up CMake build for debug: `cmake ..`. At this point you may need to manually
|
||||
provide the link path to the LLVM CMake directories, e.g. `cmake -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/ ..`
|
||||
8. Build: `cmake --build .`
|
||||
5. Set up CMake build for debug: `cmake -B build -S .`. At this point you may need to manually
|
||||
provide the link path to the LLVM CMake directories, e.g. `cmake -B build -S . -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/`
|
||||
6. Build: `cmake --build build`
|
||||
7. Change directory to the build directory `cd build`
|
||||
|
||||
*A note on compiling for Linux/Unix/MacOS: to be able to fetch vendor libraries
|
||||
libcurl is needed. The CMake script should detect it if it is available. Note that
|
||||
this functionality is non-essential and it is perfectly fine to user the compiler without it.*
|
||||
this functionality is non-essential and it is perfectly fine to use the compiler without it.*
|
||||
|
||||
#### Licensing
|
||||
|
||||
The C3 compiler is licensed under LGPL 3.0, the standard library itself is
|
||||
MIT licensed.
|
||||
Unless specified otherwise, the code in this repository is MIT licensed.
|
||||
The exception is the compiler source code (the source code under `src`),
|
||||
which is licensed under LGPL 3.0.
|
||||
|
||||
This means you are free to use all parts of standard library,
|
||||
tests, benchmarks, grammar, examples and so on under the MIT license, including
|
||||
using those libraries and tests if you build your own C3 compiler.
|
||||
|
||||
#### Editor plugins
|
||||
|
||||
@@ -402,8 +544,13 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
|
||||
|
||||
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.
|
||||
A special thank you to sponsors [Zack Puhl](https://github.com/NotsoanoNimus) and [konimarti](https://github.com/konimarti) for going the extra mile.
|
||||
|
||||
And honorable mention goes to past sponsors:
|
||||
[Ygor Pontelo](https://github.com/ygorpontelo), [Simone Raimondi](https://github.com/SRaimondi),
|
||||
[Jan Válek](https://github.com/jan-valek), [Pierre Curto](https://github.com/pierrec),
|
||||
[Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad)
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#c3lang/c3c&Date)
|
||||
[](https://www.star-history.com/#c3lang/c3c&Date)
|
||||
|
||||
219
benchmarks/stdlib/collections/hashmap.c3
Normal file
219
benchmarks/stdlib/collections/hashmap.c3
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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.
|
||||
//
|
||||
// Some benchmark test ideas are sourced from this article on C++ hashmap benchmarking:
|
||||
// https://martin.ankerl.com/2022/08/27/hashmap-bench-01/
|
||||
//
|
||||
module hashmap_benchmarks;
|
||||
|
||||
import std::collections::map;
|
||||
import std::math::random;
|
||||
|
||||
|
||||
const DEFAULT_ITERATIONS = 16384;
|
||||
|
||||
Lcg64Random rand;
|
||||
|
||||
HashMap { int, int } modifying_numbers_random;
|
||||
|
||||
fn void bench_setup() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(DEFAULT_ITERATIONS);
|
||||
|
||||
// TODO: Cannot take the address of a @benchmark function. If we could, we could pass &insert_erase as a fn ptr and use the $qnameof CT eval internally.
|
||||
set_benchmark_func_iterations($qnameof(insert_erase), 32);
|
||||
set_benchmark_func_iterations($qnameof(random_access), 1024);
|
||||
|
||||
random::seed(&rand, 0x4528_21e6_38d0_1377);
|
||||
|
||||
for (usz i = 0; i < 1_000; ++i) modifying_numbers_random.set(rand.next_int(), rand.next_int());
|
||||
}
|
||||
|
||||
|
||||
// ==============================================================================================
|
||||
module hashmap_benchmarks @benchmark;
|
||||
|
||||
import std::collections::map;
|
||||
|
||||
import std::math::random;
|
||||
import std::encoding::base64;
|
||||
|
||||
|
||||
fn void generic_hash_speeds()
|
||||
{
|
||||
(char){}.hash();
|
||||
(char[<100>]){}.hash();
|
||||
(char[100]){}.hash();
|
||||
(ichar){}.hash();
|
||||
(ichar[<100>]){}.hash();
|
||||
(ichar[100]){}.hash();
|
||||
(short){}.hash();
|
||||
(short[<100>]){}.hash();
|
||||
(short[100]){}.hash();
|
||||
(ushort){}.hash();
|
||||
(ushort[<100>]){}.hash();
|
||||
(ushort[100]){}.hash();
|
||||
(int){}.hash();
|
||||
(int[<100>]){}.hash();
|
||||
(int[100]){}.hash();
|
||||
(uint){}.hash();
|
||||
(uint[<100>]){}.hash();
|
||||
(uint[100]){}.hash();
|
||||
(long){}.hash();
|
||||
(long[<20>]){}.hash();
|
||||
(long[100]){}.hash();
|
||||
(ulong){}.hash();
|
||||
(ulong[<20>]){}.hash();
|
||||
(ulong[100]){}.hash();
|
||||
(int128){}.hash();
|
||||
(int128[<20>]){}.hash();
|
||||
(int128[100]){}.hash();
|
||||
(uint128){}.hash();
|
||||
(uint128[<20>]){}.hash();
|
||||
(uint128[100]){}.hash();
|
||||
(bool){}.hash();
|
||||
(bool[<100>]){}.hash();
|
||||
(bool[100]){}.hash();
|
||||
String x = "abc";
|
||||
char[] y = "abc";
|
||||
assert(x.hash() == y.hash());
|
||||
String z1 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
|
||||
char[] z2 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
|
||||
assert(z1.hash() == z2.hash());
|
||||
assert(int.typeid.hash());
|
||||
}
|
||||
|
||||
|
||||
fn void hash_speeds_of_many_random_values() => @pool()
|
||||
{
|
||||
var $arrsz = 10_000;
|
||||
uint fake_checksum;
|
||||
|
||||
char[] chars = allocator::new_array(tmem, char, $arrsz)[:$arrsz];
|
||||
foreach (&v : chars) *v = (char)random::next(&rand, uint.max);
|
||||
|
||||
ushort[] shorts = allocator::new_array(tmem, ushort, $arrsz)[:$arrsz];
|
||||
foreach (&v : shorts) *v = (ushort)random::next(&rand, uint.max);
|
||||
|
||||
uint[] ints = allocator::new_array(tmem, uint, $arrsz)[:$arrsz];
|
||||
foreach (&v : ints) *v = random::next(&rand, uint.max);
|
||||
|
||||
ulong[] longs = allocator::new_array(tmem, ulong, $arrsz)[:$arrsz];
|
||||
foreach (&v : longs) *v = (ulong)random::next(&rand, uint.max);
|
||||
|
||||
uint128[] vwideints = allocator::new_array(tmem, uint128, $arrsz)[:$arrsz];
|
||||
foreach (&v : vwideints) *v = (uint128)random::next(&rand, uint.max);
|
||||
|
||||
char[48][] zstrs = allocator::new_array(tmem, char[48], $arrsz)[:$arrsz];
|
||||
|
||||
String[] strs = mem::temp_array(String, $arrsz);
|
||||
foreach (x, &v : zstrs)
|
||||
{
|
||||
foreach (&c : (*v)[:random::next(&rand, 48)]) *c = (char)random::next(&rand, char.max);
|
||||
strs[x] = ((ZString)&v[0]).str_view();
|
||||
}
|
||||
|
||||
runtime::@start_benchmark();
|
||||
foreach (v : chars) fake_checksum += v.hash();
|
||||
foreach (v : shorts) fake_checksum += v.hash();
|
||||
foreach (v : ints) fake_checksum += v.hash();
|
||||
foreach (v : longs) fake_checksum += v.hash();
|
||||
foreach (v : vwideints) fake_checksum += v.hash();
|
||||
foreach (v : strs) fake_checksum += v.hash();
|
||||
runtime::@end_benchmark();
|
||||
}
|
||||
|
||||
|
||||
fn void modifying_numbers_init_from_map() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit_from_map(&modifying_numbers_random);
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void insert_erase() => @pool()
|
||||
{
|
||||
uint iters = 1_000_000;
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (int i = 0; i < iters; ++i) v[i] = i;
|
||||
for (int i = 0; i < iters; ++i) v.remove(i);
|
||||
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
uint bound = 10_000;
|
||||
usz pseudo_checksum = 0;
|
||||
|
||||
for (uint i = 0; i < bound; ++i) v[i] = i;
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (uint i = 0; i < 1_000_000; ++i) pseudo_checksum += (v[i.hash() % bound] ?? 0);
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access_erase() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
uint bound = 10_000;
|
||||
|
||||
for (uint i = 0; i < bound; ++i) v[i] = i;
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (uint i = 0; i < bound; ++i)
|
||||
{
|
||||
v[i.hash() % bound] = i; // supplant an entry
|
||||
|
||||
v.remove(random::next(&rand, bound)); // remove a random entry
|
||||
}
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access_string_keys() => @pool()
|
||||
{
|
||||
HashMap { String, ulong } v;
|
||||
v.tinit();
|
||||
|
||||
usz pseudo_checksum = 0;
|
||||
String[] saved = mem::temp_array(String, 5_000);
|
||||
|
||||
for (usz i = 0; i < saved.len; ++i)
|
||||
{
|
||||
ulong hash = i.hash();
|
||||
String b64key = base64::tencode(@as_char_view(hash));
|
||||
|
||||
v[b64key] = hash;
|
||||
|
||||
if (i < saved.len) saved[i] = b64key;
|
||||
}
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (usz i = 0; i < saved.len; ++i)
|
||||
{
|
||||
pseudo_checksum += v[ saved[random::next(&rand, saved.len)] ]!! % 512;
|
||||
}
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
38
benchmarks/stdlib/collections/linkedlist.c3
Normal file
38
benchmarks/stdlib/collections/linkedlist.c3
Normal file
@@ -0,0 +1,38 @@
|
||||
module linkedlist_benchmarks;
|
||||
|
||||
import std::collections::linkedlist;
|
||||
|
||||
|
||||
LinkedList{int} long_list;
|
||||
const HAY = 2;
|
||||
const NEEDLE = 1000;
|
||||
|
||||
fn void bench_setup() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(4096);
|
||||
|
||||
int[*] haystack = { [0..999] = HAY };
|
||||
long_list = linkedlist::@new{int}(mem, haystack[..]);
|
||||
long_list.push(NEEDLE);
|
||||
long_list.push_all(haystack[..]);
|
||||
}
|
||||
|
||||
|
||||
// ==============================================================================================
|
||||
module linkedlist_benchmarks @benchmark;
|
||||
|
||||
String die_str = "Failed to find the value `1`. Is something broken?";
|
||||
|
||||
|
||||
fn void foreach_iterator()
|
||||
{
|
||||
foreach (v : long_list.array_view()) if (v == NEEDLE) return;
|
||||
runtime::@kill_benchmark(die_str);
|
||||
}
|
||||
|
||||
fn void foreach_r_iterator()
|
||||
{
|
||||
foreach_r (v : long_list.array_view()) if (v == NEEDLE) return;
|
||||
runtime::@kill_benchmark(die_str);
|
||||
}
|
||||
51
benchmarks/stdlib/compression/deflate.c3
Normal file
51
benchmarks/stdlib/compression/deflate.c3
Normal file
@@ -0,0 +1,51 @@
|
||||
module deflate_benchmarks;
|
||||
import std::compression::deflate;
|
||||
|
||||
const uint SMALL_ITERATIONS = 50000;
|
||||
const uint LARGE_ITERATIONS = 100;
|
||||
|
||||
// Data to compress
|
||||
const char[] SMALL_DATA = { [0..1023] = 'A' };
|
||||
const char[] LARGE_DATA = { [0..1048575] = 'B' };
|
||||
|
||||
char[] small_compressed;
|
||||
char[] large_compressed;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
small_compressed = deflate::compress(mem, SMALL_DATA)!!;
|
||||
large_compressed = deflate::compress(mem, LARGE_DATA)!!;
|
||||
set_benchmark_warmup_iterations(2);
|
||||
set_benchmark_max_iterations(10);
|
||||
|
||||
set_benchmark_func_iterations($qnameof(deflate_compress_small), SMALL_ITERATIONS);
|
||||
set_benchmark_func_iterations($qnameof(deflate_decompress_small), SMALL_ITERATIONS);
|
||||
set_benchmark_func_iterations($qnameof(deflate_compress_large), LARGE_ITERATIONS);
|
||||
set_benchmark_func_iterations($qnameof(deflate_decompress_large), LARGE_ITERATIONS);
|
||||
}
|
||||
|
||||
// =======================================================================================
|
||||
module deflate_benchmarks @benchmark;
|
||||
|
||||
import std::compression::deflate;
|
||||
import std::core::mem;
|
||||
|
||||
fn void deflate_compress_small() => @pool()
|
||||
{
|
||||
char[]? compressed = deflate::compress(tmem, SMALL_DATA);
|
||||
}
|
||||
|
||||
fn void deflate_decompress_small() => @pool()
|
||||
{
|
||||
char[]? decompressed = deflate::decompress(tmem, small_compressed);
|
||||
}
|
||||
|
||||
fn void deflate_compress_large() => @pool()
|
||||
{
|
||||
char[]? compressed = deflate::compress(tmem, LARGE_DATA);
|
||||
}
|
||||
|
||||
fn void deflate_decompress_large() => @pool()
|
||||
{
|
||||
char[]? decompressed = deflate::decompress(tmem, large_compressed);
|
||||
}
|
||||
46
benchmarks/stdlib/core/string_trim.c3
Normal file
46
benchmarks/stdlib/core/string_trim.c3
Normal file
@@ -0,0 +1,46 @@
|
||||
module string_trim_wars;
|
||||
|
||||
const String WHITESPACE_TARGET = " \n\t\r\f\va \tbcde\v\f\r\t\n ";
|
||||
const String WHITESPACE_NUMERIC_TARGET = " 25290 0969 99a \tbcde12332 34 43 0000";
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(64);
|
||||
set_benchmark_max_iterations(1 << 24);
|
||||
}
|
||||
|
||||
macro void trim_bench($trim_str, String $target = WHITESPACE_TARGET) => @pool()
|
||||
{
|
||||
String s1;
|
||||
String s2 = $target.tcopy();
|
||||
|
||||
runtime::@start_benchmark();
|
||||
|
||||
$switch:
|
||||
$case $typeof($trim_str) == String:
|
||||
s1 = s2.trim($trim_str);
|
||||
$case $typeof($trim_str) == AsciiCharset:
|
||||
s1 = s2.trim_charset($trim_str);
|
||||
$default: $error "Unable to determine the right String `trim` operation to use.";
|
||||
$endswitch
|
||||
|
||||
@volatile_load(s1);
|
||||
|
||||
runtime::@end_benchmark();
|
||||
}
|
||||
|
||||
|
||||
module string_trim_wars @benchmark;
|
||||
|
||||
fn void trim_control() => trim_bench(" "); // only spaces
|
||||
|
||||
fn void trim_whitespace_default() => trim_bench("\t\n\r "); // default set
|
||||
fn void trim_whitespace_default_ordered() => trim_bench(" \n\t\r"); // default \w set, but ordered by expected freq
|
||||
|
||||
fn void trim_whitespace_bad() => trim_bench("\f\v\n\t\r "); // bad-perf ordering, all \w
|
||||
|
||||
fn void trim_whitespace_ordered_extended() => trim_bench(" \n\t\r\f\v"); // proposed ordering, all \w
|
||||
fn void trim_charset_whitespace() => trim_bench(ascii::WHITESPACE_SET); // use charset, all \w
|
||||
|
||||
fn void trim_many() => trim_bench(" \n\t\r\f\v0123456789", WHITESPACE_NUMERIC_TARGET); // ordered, all \w + num
|
||||
fn void trim_charset_many() => trim_bench(ascii::WHITESPACE_SET | ascii::NUMBER_SET, WHITESPACE_NUMERIC_TARGET); // set, all \w + num
|
||||
31
benchmarks/stdlib/crypto/aes_bench.c3
Normal file
31
benchmarks/stdlib/crypto/aes_bench.c3
Normal file
@@ -0,0 +1,31 @@
|
||||
module std::crypto::aes_bench;
|
||||
|
||||
import std::crypto::aes;
|
||||
|
||||
fn void init() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(5);
|
||||
set_benchmark_max_iterations(10_000);
|
||||
}
|
||||
|
||||
|
||||
AesType aes = AES256;
|
||||
char[] key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4";
|
||||
char[] text = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710";
|
||||
char[] cipher = x"601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6";
|
||||
char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
|
||||
|
||||
fn void bench_ctr_xcrypt() @benchmark
|
||||
{
|
||||
char[64] out;
|
||||
Aes ctx;
|
||||
|
||||
// encrypt
|
||||
ctx.init(aes, key, iv);
|
||||
ctx.encrypt_buffer(text, &out);
|
||||
|
||||
// decrypt
|
||||
ctx.init(aes, key, iv);
|
||||
ctx.decrypt_buffer(cipher, &out);
|
||||
}
|
||||
|
||||
39
benchmarks/stdlib/crypto/chacha20.c3
Normal file
39
benchmarks/stdlib/crypto/chacha20.c3
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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 chacha20_benchmarks;
|
||||
|
||||
import std::crypto::chacha20;
|
||||
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(1024);
|
||||
}
|
||||
|
||||
const char[] KEY = x'98bef1469be7269837a45bfbc92a5a6ac762507cf96443bf33b96b1bd4c6f8f6';
|
||||
const char[] NONCE = x'44e792d63335abb1582e9253';
|
||||
const uint COUNTER = 42;
|
||||
|
||||
char[] one_mb @align(ulong.sizeof) = { [0..1024*1024] = 0xA5 };
|
||||
|
||||
// This doesn't test both encryption + decryption, because it's a symmetric operation that shares
|
||||
// a single common data transformation. Testing one limb is enough.
|
||||
fn void gogo_chacha20() @benchmark
|
||||
{
|
||||
chacha20::encrypt_mut(one_mb[..], KEY, NONCE, COUNTER);
|
||||
}
|
||||
|
||||
// Check what the speed of an unligned buffer looks like.
|
||||
fn void gogo_chacha20_unaligned() @benchmark => @pool()
|
||||
{
|
||||
char[] copy = mem::talloc_array(char, one_mb.len + 3);
|
||||
char[] im_off_slightly = copy[3..];
|
||||
copy[3..] = one_mb[..];
|
||||
assert((usz)im_off_slightly.ptr % usz.sizeof > 0);
|
||||
|
||||
runtime::@start_benchmark();
|
||||
chacha20::encrypt_mut(im_off_slightly, KEY, NONCE, COUNTER);
|
||||
runtime::@end_benchmark();
|
||||
}
|
||||
113
benchmarks/stdlib/crypto/crypto_shootout.c3
Normal file
113
benchmarks/stdlib/crypto/crypto_shootout.c3
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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 crypto_hash_benchmarks;
|
||||
|
||||
import std::collections::pair;
|
||||
|
||||
|
||||
const usz COMMON_ITERATIONS = 1 << 17;
|
||||
|
||||
char* common_1mib_ptr;
|
||||
char[] common_16;
|
||||
char[] common_256;
|
||||
char[] common_4kib;
|
||||
char[] common_1mib;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(COMMON_ITERATIONS + 3);
|
||||
|
||||
common_1mib_ptr = mem::alloc_array(char, 1024*1024);
|
||||
common_1mib = common_1mib_ptr[:1024*1024];
|
||||
common_1mib[..] = 0xA5;
|
||||
|
||||
common_16 = common_1mib[:16];
|
||||
common_256 = common_1mib[:256];
|
||||
common_4kib = common_1mib[:4096];
|
||||
|
||||
static String[] function_prefixes = {
|
||||
$qnameof(md5_16)[..^4],
|
||||
$qnameof(sha1_16)[..^4],
|
||||
$qnameof(sha2_256_16)[..^4],
|
||||
$qnameof(sha2_512_16)[..^4],
|
||||
//$qnameof(blake2s_256_16)[..^4],
|
||||
//$qnameof(blake2b_256_16)[..^4],
|
||||
$qnameof(blake3_16)[..^4],
|
||||
$qnameof(ripemd_160_16)[..^4],
|
||||
$qnameof(whirlpool_16)[..^4],
|
||||
$qnameof(streebog_256_16)[..^4],
|
||||
$qnameof(streebog_512_16)[..^4],
|
||||
};
|
||||
|
||||
static Pair{ String, uint }[] to_iters = {
|
||||
{ "_4kib", 1 << 15 },
|
||||
{ "_1mib", 1024 },
|
||||
};
|
||||
|
||||
foreach (p : to_iters)
|
||||
{
|
||||
foreach (name : function_prefixes) set_benchmark_func_iterations(name.tconcat(p.first), p.second);
|
||||
}
|
||||
}
|
||||
|
||||
fn void teardown_bench() @finalizer
|
||||
{
|
||||
mem::free(common_1mib_ptr);
|
||||
}
|
||||
|
||||
|
||||
// =======================================================================================
|
||||
module crypto_hash_benchmarks @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
|
||||
fn void md5_16() => md5::hash(common_16);
|
||||
fn void sha1_16() => sha1::hash(common_16);
|
||||
fn void sha2_256_16() => sha256::hash(common_16);
|
||||
fn void sha2_512_16() => sha512::hash(common_16);
|
||||
//fn void blake2s_256_16() => blake2::s(256, common_16);
|
||||
//fn void blake2b_256_16() => blake2::b(256, common_16);
|
||||
fn void blake3_16() => blake3::hash(common_16);
|
||||
fn void ripemd_160_16() => ripemd::hash{160}(common_16);
|
||||
fn void whirlpool_16() => whirlpool::hash(common_16);
|
||||
fn void streebog_256_16() => streebog::hash_256(common_16);
|
||||
fn void streebog_512_16() => streebog::hash_512(common_16);
|
||||
|
||||
fn void md5_256() => md5::hash(common_256);
|
||||
fn void sha1_256() => sha1::hash(common_256);
|
||||
fn void sha2_256_256() => sha256::hash(common_256);
|
||||
fn void sha2_512_256() => sha512::hash(common_256);
|
||||
//fn void blake2s_256_256() => blake2::s(256, common_256);
|
||||
//fn void blake2b_256_256() => blake2::b(256, common_256);
|
||||
fn void blake3_256() => blake3::hash(common_256);
|
||||
fn void ripemd_160_256() => ripemd::hash{160}(common_256);
|
||||
fn void whirlpool_256() => whirlpool::hash(common_256);
|
||||
fn void streebog_256_256() => streebog::hash_256(common_256);
|
||||
fn void streebog_512_256() => streebog::hash_512(common_256);
|
||||
|
||||
fn void md5_4kib() => md5::hash(common_4kib);
|
||||
fn void sha1_4kib() => sha1::hash(common_4kib);
|
||||
fn void sha2_256_4kib() => sha256::hash(common_4kib);
|
||||
fn void sha2_512_4kib() => sha512::hash(common_4kib);
|
||||
//fn void blake2s_256_4kib() => blake2::s(256, common_4kib);
|
||||
//fn void blake2b_256_4kib() => blake2::b(256, common_4kib);
|
||||
fn void blake3_4kib() => blake3::hash(common_4kib);
|
||||
fn void ripemd_160_4kib() => ripemd::hash{160}(common_4kib);
|
||||
fn void whirlpool_4kib() => whirlpool::hash(common_4kib);
|
||||
fn void streebog_256_4kib() => streebog::hash_256(common_4kib);
|
||||
fn void streebog_512_4kib() => streebog::hash_512(common_4kib);
|
||||
|
||||
fn void md5_1mib() => md5::hash(common_1mib);
|
||||
fn void sha1_1mib() => sha1::hash(common_1mib);
|
||||
fn void sha2_256_1mib() => sha256::hash(common_1mib);
|
||||
fn void sha2_512_1mib() => sha512::hash(common_1mib);
|
||||
//fn void blake2s_256_1mib() => blake2::s(256, common_1mib);
|
||||
//fn void blake2b_256_1mib() => blake2::b(256, common_1mib);
|
||||
fn void blake3_1mib() => blake3::hash(common_1mib);
|
||||
fn void ripemd_160_1mib() => ripemd::hash{160}(common_1mib);
|
||||
fn void whirlpool_1mib() => whirlpool::hash(common_1mib);
|
||||
fn void streebog_256_1mib() => streebog::hash_256(common_1mib);
|
||||
fn void streebog_512_1mib() => streebog::hash_512(common_1mib);
|
||||
57
benchmarks/stdlib/hash/blake3.c3
Normal file
57
benchmarks/stdlib/hash/blake3.c3
Normal file
@@ -0,0 +1,57 @@
|
||||
module blake3_bench;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(128);
|
||||
|
||||
input = mem::alloc_array(char, BUFSZ);
|
||||
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
|
||||
input_slice = input[:BUFSZ];
|
||||
}
|
||||
|
||||
fn void teardown_bench() @finalizer
|
||||
{
|
||||
mem::free(input);
|
||||
input = null;
|
||||
}
|
||||
|
||||
char* input;
|
||||
char[] input_slice;
|
||||
const usz BUFSZ = 1024 * 1024;
|
||||
|
||||
module blake3_bench @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
fn void blake3_hash()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = blake3::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_sha256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = sha256::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_sha512()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = sha512::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_whirlpool()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = whirlpool::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
41
benchmarks/stdlib/hash/md5.c3
Normal file
41
benchmarks/stdlib/hash/md5.c3
Normal file
@@ -0,0 +1,41 @@
|
||||
module md5_bench;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(128);
|
||||
|
||||
input = mem::alloc_array(char, BUFSZ);
|
||||
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
|
||||
input_slice = input[:BUFSZ];
|
||||
}
|
||||
|
||||
fn void teardown_bench() @finalizer
|
||||
{
|
||||
mem::free(input);
|
||||
input = null;
|
||||
}
|
||||
|
||||
char* input;
|
||||
char[] input_slice;
|
||||
const usz BUFSZ = 1024 * 1024;
|
||||
|
||||
module md5_bench @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
fn void md5_hash()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = md5::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_sha256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = sha256::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
94
benchmarks/stdlib/hash/non_crypto_shootout.c3
Normal file
94
benchmarks/stdlib/hash/non_crypto_shootout.c3
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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 non_crypto_benchmarks;
|
||||
|
||||
|
||||
const usz COMMON_ITERATIONS = 1 << 18;
|
||||
|
||||
const char[] COMMON_1 = { 0xA5 };
|
||||
const char[] COMMON_4 = { 0xA5, 0xA5, 0xA5, 0xA5, };
|
||||
const char[] COMMON_8 = { [0..7] = 0xA5 };
|
||||
const char[] COMMON_16 = { [0..15] = 0xA5 };
|
||||
const char[] COMMON_32 = { [0..31] = 0xA5 };
|
||||
const char[] COMMON_64 = { [0..63] = 0xA5 };
|
||||
const char[] COMMON_128 = { [0..127] = 0xA5 };
|
||||
const char[] COMMON_1024 = { [0..1023] = 0xA5 };
|
||||
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(COMMON_ITERATIONS + 3);
|
||||
}
|
||||
|
||||
|
||||
// =======================================================================================
|
||||
module non_crypto_benchmarks @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
|
||||
fn void fnv64a_1() => fnv64a::hash(COMMON_1);
|
||||
fn void fnv32a_1() => fnv32a::hash(COMMON_1);
|
||||
fn void wyhash2_1() => wyhash2::hash(COMMON_1);
|
||||
fn void metro64_1() => metro64::hash(COMMON_1);
|
||||
fn void metro128_1() => metro128::hash(COMMON_1);
|
||||
fn void a5hash_1() => a5hash::hash(COMMON_1);
|
||||
fn void komi_1() => komi::hash(COMMON_1);
|
||||
|
||||
fn void fnv64a_4() => fnv64a::hash(COMMON_4);
|
||||
fn void fnv32a_4() => fnv32a::hash(COMMON_4);
|
||||
fn void wyhash2_4() => wyhash2::hash(COMMON_4);
|
||||
fn void metro64_4() => metro64::hash(COMMON_4);
|
||||
fn void metro128_4() => metro128::hash(COMMON_4);
|
||||
fn void a5hash_4() => a5hash::hash(COMMON_4);
|
||||
fn void komi_4() => komi::hash(COMMON_4);
|
||||
|
||||
fn void fnv64a_8() => fnv64a::hash(COMMON_8);
|
||||
fn void fnv32a_8() => fnv32a::hash(COMMON_8);
|
||||
fn void wyhash2_8() => wyhash2::hash(COMMON_8);
|
||||
fn void metro64_8() => metro64::hash(COMMON_8);
|
||||
fn void metro128_8() => metro128::hash(COMMON_8);
|
||||
fn void a5hash_8() => a5hash::hash(COMMON_8);
|
||||
fn void komi_8() => komi::hash(COMMON_8);
|
||||
|
||||
fn void fnv64a_16() => fnv64a::hash(COMMON_16);
|
||||
fn void fnv32a_16() => fnv32a::hash(COMMON_16);
|
||||
fn void wyhash2_16() => wyhash2::hash(COMMON_16);
|
||||
fn void metro64_16() => metro64::hash(COMMON_16);
|
||||
fn void metro128_16() => metro128::hash(COMMON_16);
|
||||
fn void a5hash_16() => a5hash::hash(COMMON_16);
|
||||
fn void komi_16() => komi::hash(COMMON_16);
|
||||
|
||||
fn void fnv64a_32() => fnv64a::hash(COMMON_32);
|
||||
fn void fnv32a_32() => fnv32a::hash(COMMON_32);
|
||||
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
|
||||
fn void metro64_32() => metro64::hash(COMMON_32);
|
||||
fn void metro128_32() => metro128::hash(COMMON_32);
|
||||
fn void a5hash_32() => a5hash::hash(COMMON_32);
|
||||
fn void komi_32() => komi::hash(COMMON_32);
|
||||
|
||||
fn void fnv64a_64() => fnv64a::hash(COMMON_64);
|
||||
fn void fnv32a_64() => fnv32a::hash(COMMON_64);
|
||||
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
|
||||
fn void metro64_64() => metro64::hash(COMMON_64);
|
||||
fn void metro128_64() => metro128::hash(COMMON_64);
|
||||
fn void a5hash_64() => a5hash::hash(COMMON_64);
|
||||
fn void komi_64() => komi::hash(COMMON_64);
|
||||
|
||||
fn void fnv64a_128() => fnv64a::hash(COMMON_128);
|
||||
fn void fnv32a_128() => fnv32a::hash(COMMON_128);
|
||||
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
|
||||
fn void metro64_128() => metro64::hash(COMMON_128);
|
||||
fn void metro128_128() => metro128::hash(COMMON_128);
|
||||
fn void a5hash_128() => a5hash::hash(COMMON_128);
|
||||
fn void komi_128() => komi::hash(COMMON_128);
|
||||
|
||||
fn void fnv64a_1024() => fnv64a::hash(COMMON_1024);
|
||||
fn void fnv32a_1024() => fnv32a::hash(COMMON_1024);
|
||||
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
|
||||
fn void metro64_1024() => metro64::hash(COMMON_1024);
|
||||
fn void metro128_1024() => metro128::hash(COMMON_1024);
|
||||
fn void a5hash_1024() => a5hash::hash(COMMON_1024);
|
||||
fn void komi_1024() => komi::hash(COMMON_1024);
|
||||
76
benchmarks/stdlib/hash/ripemd.c3
Normal file
76
benchmarks/stdlib/hash/ripemd.c3
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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 ripemd_bench;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(256);
|
||||
|
||||
input = mem::alloc_array(char, BUFSZ);
|
||||
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
|
||||
input_slice = input[:BUFSZ];
|
||||
}
|
||||
|
||||
fn void teardown_bench() @finalizer
|
||||
{
|
||||
mem::free(input);
|
||||
input = null;
|
||||
}
|
||||
|
||||
char* input;
|
||||
char[] input_slice;
|
||||
const usz BUFSZ = 1024 * 1024;
|
||||
|
||||
module ripemd_bench @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
fn void ripemd_128()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{128}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void ripemd_160()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{160}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void ripemd_256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{256}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void ripemd_320()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = ripemd::hash{320}(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_sha256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = sha256::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_whirlpool()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = whirlpool::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
51
benchmarks/stdlib/hash/streebog.c3
Normal file
51
benchmarks/stdlib/hash/streebog.c3
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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 streebog_bench;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(256);
|
||||
}
|
||||
|
||||
const char[] INPUT = { [0..1024*1024] = 0xA5 };
|
||||
const char[*] EXPECTED_256 = x'694676905b7cf099755db1cc186f741f0fd1877aaaa4badcbfb305537f986971';
|
||||
const char[*] EXPECTED_512 = x'fe21d08857ea97e79035d1e5c9ba5130786e8d1875bc74d628349560d94d6bdff0b0dcd2f6347eb8b3f0239b6cca76b5028c0ff45f631fcdf77b1d551dd079f3';
|
||||
|
||||
|
||||
module streebog_bench @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
fn void get_in_the_bog_256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = streebog::hash_256(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void get_in_the_bog_512()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = streebog::hash_512(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_sha256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = sha256::hash(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_whirlpool()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = whirlpool::hash(INPUT);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
57
benchmarks/stdlib/hash/whirlpool.c3
Normal file
57
benchmarks/stdlib/hash/whirlpool.c3
Normal file
@@ -0,0 +1,57 @@
|
||||
module whirlpool_bench;
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(128);
|
||||
|
||||
input = mem::alloc_array(char, BUFSZ);
|
||||
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
|
||||
input_slice = input[:BUFSZ];
|
||||
}
|
||||
|
||||
fn void teardown_bench() @finalizer
|
||||
{
|
||||
mem::free(input);
|
||||
input = null;
|
||||
}
|
||||
|
||||
char* input;
|
||||
char[] input_slice;
|
||||
const usz BUFSZ = 1024 * 1024;
|
||||
|
||||
module whirlpool_bench @benchmark;
|
||||
|
||||
import std::hash;
|
||||
|
||||
fn void whirlpool_hash()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = whirlpool::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_sha256()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = sha256::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_sha512()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = sha512::hash(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
|
||||
fn void compared_with_streebog_512()
|
||||
{
|
||||
runtime::@start_benchmark();
|
||||
char[*] myset = streebog::hash_512(input_slice);
|
||||
runtime::@end_benchmark();
|
||||
mem::zero_volatile(myset[..]);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
: ${DOCKER:=docker}
|
||||
: ${IMAGE:="c3c-builder"}
|
||||
@@ -30,7 +30,8 @@ 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 \
|
||||
"git config --global --add safe.directory /home/c3c/source && \
|
||||
cmake -S . -B build \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
|
||||
-DCMAKE_C_COMPILER=clang-$LLVM_VERSION \
|
||||
@@ -41,4 +42,4 @@ exec $DOCKER run -i --rm \
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-$LLVM_VERSION \
|
||||
-DC3_LLVM_VERSION=auto && \
|
||||
cmake --build build && \
|
||||
cp -r build/c3c build/lib bin"
|
||||
cp -r build/c3c build/lib bin"
|
||||
|
||||
@@ -2,48 +2,44 @@ ARG UBUNTU_VERSION=22.04
|
||||
FROM ubuntu:${UBUNTU_VERSION}
|
||||
|
||||
ARG LLVM_VERSION=18
|
||||
ENV LLVM_DEV_VERSION=20
|
||||
ARG CMAKE_VERSION=3.20.0
|
||||
|
||||
ARG CMAKE_VERSION=3.20
|
||||
# Prevent interactive prompts during apt install
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
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 && \
|
||||
RUN for i in 1 2 3; do apt-get update && break || sleep 2; done && \
|
||||
apt-get install -y --fix-missing \
|
||||
wget gnupg software-properties-common lsb-release \
|
||||
zlib1g zlib1g-dev python3 ninja-build curl g++ libcurl4-openssl-dev git && \
|
||||
CODENAME=$(lsb_release -cs) && \
|
||||
ARCH=$(uname -m) && \
|
||||
wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${ARCH}.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
|
||||
sh cmake-${CMAKE_VERSION}-linux-${ARCH}.sh --prefix=/opt/cmake --skip-license && \
|
||||
ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake && \
|
||||
rm cmake-${CMAKE_VERSION}-linux-${ARCH}.sh
|
||||
|
||||
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; \
|
||||
RUN CODENAME=$(lsb_release -cs) && \
|
||||
for i in 1 2; do wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key && break || sleep 2; done | apt-key add - && \
|
||||
if [ "${LLVM_VERSION}" -ge 16 ]; then \
|
||||
add-apt-repository "deb http://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-${LLVM_VERSION} main"; \
|
||||
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; \
|
||||
add-apt-repository "deb http://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME} main"; \
|
||||
fi && \
|
||||
for i in 1 2 3; do apt-get update && break || sleep 2; done && \
|
||||
apt-get install -y --fix-missing \
|
||||
clang-${LLVM_VERSION} \
|
||||
clang++-${LLVM_VERSION} \
|
||||
llvm-${LLVM_VERSION} \
|
||||
llvm-${LLVM_VERSION}-dev \
|
||||
lld-${LLVM_VERSION} \
|
||||
liblld-${LLVM_VERSION}-dev \
|
||||
libpolly-${LLVM_VERSION}-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
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
|
||||
18
flake.nix
18
flake.nix
@@ -6,29 +6,27 @@
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem
|
||||
outputs = { self, ... }@inputs: inputs.flake-utils.lib.eachDefaultSystem
|
||||
(system:
|
||||
let pkgs = import inputs.nixpkgs { inherit system; };
|
||||
call = set: pkgs.callPackage ./nix/default.nix (
|
||||
set // {
|
||||
rev = self.rev or "unknown";
|
||||
}
|
||||
);
|
||||
c3cBuild = set: pkgs.callPackage ./nix/default.nix (set // {
|
||||
rev = self.rev or "unknown";
|
||||
});
|
||||
in {
|
||||
packages = {
|
||||
default = self.packages.${system}.c3c;
|
||||
|
||||
c3c = call {};
|
||||
c3c = c3cBuild {};
|
||||
|
||||
c3c-checks = pkgs.callPackage ./nix/default.nix {
|
||||
c3c-checks = c3cBuild {
|
||||
checks = true;
|
||||
};
|
||||
|
||||
c3c-debug = pkgs.callPackage ./nix/default.nix {
|
||||
c3c-debug = c3cBuild {
|
||||
debug = true;
|
||||
};
|
||||
|
||||
c3c-debug-checks = pkgs.callPackage ./nix/default.nix {
|
||||
c3c-debug-checks = c3cBuild {
|
||||
debug = true;
|
||||
checks = true;
|
||||
};
|
||||
|
||||
189
install/install.ps1
Normal file
189
install/install.ps1
Normal file
@@ -0,0 +1,189 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
C3 install script.
|
||||
.DESCRIPTION
|
||||
This script installs C3 on Windows from the command line.
|
||||
.PARAMETER C3Version
|
||||
Specifies the version of C3 to install.
|
||||
Default is 'latest'. Can also be set via environment variable 'C3_VERSION'.
|
||||
.PARAMETER C3Home
|
||||
Specifies C3's installation directory.
|
||||
Default is '$Env:USERPROFILE\.c3'. Can also be set via environment variable 'C3_HOME'.
|
||||
.PARAMETER NoPathUpdate
|
||||
If specified, the script will not modify the PATH environment variable.
|
||||
.PARAMETER C3Repourl
|
||||
Specifies the repository URL of C3.
|
||||
Default is 'https://github.com/c3lang/c3c'. Can also be set via environment variable 'C3_REPOURL'.
|
||||
.LINK
|
||||
https://c3-lang.org/
|
||||
.LINK
|
||||
https://github.com/c3lang/c3c
|
||||
#>
|
||||
|
||||
# Script parameters with defaults
|
||||
param (
|
||||
[string] $C3Version = 'latest',
|
||||
[string] $C3Home = "$Env:USERPROFILE\.c3",
|
||||
[switch] $NoPathUpdate,
|
||||
[string] $C3Repourl = 'https://github.com/c3lang/c3c'
|
||||
)
|
||||
|
||||
# Enable strict mode for better error handling
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
# Function to broadcast environment variable changes to Windows system
|
||||
function Publish-Env {
|
||||
# Add P/Invoke type if it does not exist
|
||||
if (-not ("Win32.NativeMethods" -as [Type])) {
|
||||
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr SendMessageTimeout(
|
||||
IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
|
||||
uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
|
||||
"@
|
||||
}
|
||||
|
||||
# Constants for broadcasting environment changes
|
||||
$HWND_BROADCAST = [IntPtr] 0xffff
|
||||
$WM_SETTINGCHANGE = 0x1a
|
||||
$result = [UIntPtr]::Zero
|
||||
|
||||
# Broadcast the message to all windows
|
||||
[Win32.Nativemethods]::SendMessageTimeout($HWND_BROADCAST,
|
||||
$WM_SETTINGCHANGE,
|
||||
[UIntPtr]::Zero,
|
||||
"Environment",
|
||||
2,
|
||||
5000,
|
||||
[ref] $result
|
||||
) | Out-Null
|
||||
}
|
||||
|
||||
# Function to write or update an environment variable in the registry
|
||||
function Write-Env {
|
||||
param(
|
||||
[String] $name,
|
||||
[String] $val,
|
||||
[Switch] $global
|
||||
)
|
||||
|
||||
# Determine the registry key based on scope (user or system)
|
||||
$RegisterKey = if ($global) {
|
||||
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
|
||||
} else {
|
||||
Get-Item -Path 'HKCU:'
|
||||
}
|
||||
|
||||
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment', $true)
|
||||
|
||||
# If value is null, delete the variable
|
||||
if ($null -eq $val) {
|
||||
$EnvRegisterKey.DeleteValue($name)
|
||||
} else {
|
||||
# Determine the correct registry value type
|
||||
$RegistryValueKind = if ($val.Contains('%')) {
|
||||
[Microsoft.Win32.RegistryValueKind]::ExpandString
|
||||
} elseif ($EnvRegisterKey.GetValue($name)) {
|
||||
$EnvRegisterKey.GetValueKind($name)
|
||||
} else {
|
||||
[Microsoft.Win32.RegistryValueKind]::String
|
||||
}
|
||||
$EnvRegisterKey.SetValue($name, $val, $RegistryValueKind)
|
||||
}
|
||||
|
||||
# Broadcast the change to the system
|
||||
Publish-Env
|
||||
}
|
||||
|
||||
# Function to get an environment variable from the registry
|
||||
function Get-Env {
|
||||
param(
|
||||
[String] $name,
|
||||
[Switch] $global
|
||||
)
|
||||
|
||||
# Determine registry key based on scope
|
||||
$RegisterKey = if ($global) {
|
||||
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
|
||||
} else {
|
||||
Get-Item -Path 'HKCU:'
|
||||
}
|
||||
|
||||
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment')
|
||||
$RegistryValueOption = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
|
||||
|
||||
# Retrieve the value without expanding environment variables
|
||||
$EnvRegisterKey.GetValue($name, $null, $RegistryValueOption)
|
||||
}
|
||||
|
||||
# Override defaults if environment variables exist
|
||||
if ($Env:C3_VERSION) { $C3Version = $Env:C3_VERSION }
|
||||
if ($Env:C3_HOME) { $C3Home = $Env:C3_HOME }
|
||||
if ($Env:C3_NO_PATH_UPDATE) { $NoPathUpdate = $true }
|
||||
if ($Env:C3_REPOURL) { $C3Repourl = $Env:C3_REPOURL -replace '/$', '' }
|
||||
|
||||
# Set binary name
|
||||
$BINARY = "c3-windows"
|
||||
|
||||
# Determine the download URL based on version
|
||||
if ($C3Version -eq 'latest') {
|
||||
$DOWNLOAD_URL = "$C3Repourl/releases/latest/download/$BINARY.zip"
|
||||
} else {
|
||||
# Ensure version starts with 'v'
|
||||
$C3Version = "v" + ($C3Version -replace '^v', '')
|
||||
$DOWNLOAD_URL = "$C3Repourl/releases/download/$C3Version/$BINARY.zip"
|
||||
}
|
||||
|
||||
$BinDir = $C3Home
|
||||
|
||||
Write-Host "This script will automatically download and install C3 ($C3Version) for you."
|
||||
Write-Host "Getting it from this url: $DOWNLOAD_URL"
|
||||
Write-Host "The binary will be installed into '$BinDir'"
|
||||
|
||||
# Create temporary file for download
|
||||
$TEMP_FILE = [System.IO.Path]::GetTempFileName()
|
||||
|
||||
try {
|
||||
# Download the binary
|
||||
Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $TEMP_FILE
|
||||
|
||||
# Remove previous installation if it exists
|
||||
if (Test-Path -Path $BinDir) {
|
||||
Remove-Item -Path $BinDir -Recurse -Force | Out-Null
|
||||
}
|
||||
|
||||
# Rename temp file to .zip
|
||||
$ZIP_FILE = $TEMP_FILE + ".zip"
|
||||
Rename-Item -Path $TEMP_FILE -NewName $ZIP_FILE
|
||||
|
||||
# Extract downloaded zip
|
||||
Expand-Archive -Path $ZIP_FILE -DestinationPath $Env:USERPROFILE -Force
|
||||
|
||||
# Rename extracted folder to target installation directory
|
||||
Rename-Item -Path "$Env:USERPROFILE/c3-windows-Release" -NewName $BinDir
|
||||
} catch {
|
||||
Write-Host "Error: '$DOWNLOAD_URL' is not available or failed to download"
|
||||
exit 1
|
||||
} finally {
|
||||
# Cleanup temporary zip file
|
||||
Remove-Item -Path $ZIP_FILE
|
||||
}
|
||||
|
||||
# Update PATH environment variable if requested
|
||||
if (!$NoPathUpdate) {
|
||||
$PATH = Get-Env 'PATH'
|
||||
if ($PATH -notlike "*$BinDir*") {
|
||||
Write-Output "Adding $BinDir to PATH"
|
||||
|
||||
# Persist PATH for future sessions
|
||||
Write-Env -name 'PATH' -val "$BinDir;$PATH"
|
||||
|
||||
# Update PATH for current session
|
||||
$Env:PATH = "$BinDir;$PATH"
|
||||
Write-Output "You may need to restart your shell"
|
||||
} else {
|
||||
Write-Output "$BinDir is already in PATH"
|
||||
}
|
||||
} else {
|
||||
Write-Output "You may need to update your PATH manually to use c3"
|
||||
}
|
||||
137
install/install.sh
Normal file
137
install/install.sh
Normal file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail # Exit on error, unset variables, and fail pipelines on any error
|
||||
|
||||
__wrap__() {
|
||||
# Version of C3 to install (default: latest)
|
||||
VERSION="${C3_VERSION:-latest}"
|
||||
# Installation directory (default: ~/.c3)
|
||||
C3_HOME="${C3_HOME:-$HOME/.c3}"
|
||||
# Expand '~' if present
|
||||
C3_HOME="${C3_HOME/#\~/$HOME}"
|
||||
BIN_DIR="$C3_HOME"
|
||||
# C3 compiler repository URL
|
||||
REPO="c3lang/c3c"
|
||||
REPOURL="${C3_REPOURL:-https://github.com/$REPO}"
|
||||
|
||||
detect_platform() {
|
||||
# Detects the operating system
|
||||
local os_type
|
||||
os_type="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
case "$os_type" in
|
||||
darwin) # macOS
|
||||
echo "macos"
|
||||
;;
|
||||
msys*|mingw*|cygwin*) # Windows (Git Bash / MSYS / Cygwin)
|
||||
IS_MSYS=true
|
||||
echo "windows"
|
||||
;;
|
||||
*)
|
||||
echo $os_type
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Determine platform string
|
||||
PLATFORM="$(detect_platform)"
|
||||
|
||||
# File extension for the archive (ZIP for Windows, TAR.GZ for others)
|
||||
EXT=".tar.gz"
|
||||
BINARY="c3-${PLATFORM}"
|
||||
if [[ "${IS_MSYS:-false}" == true ]]; then
|
||||
EXT=".zip"
|
||||
fi
|
||||
|
||||
# Determine the download URL (latest release or specific version)
|
||||
if [[ "$VERSION" == "latest" ]]; then
|
||||
URL="${REPOURL%/}/releases/latest/download/${BINARY}${EXT}"
|
||||
else
|
||||
URL="${REPOURL%/}/releases/download/v${VERSION#v}/${BINARY}${EXT}"
|
||||
fi
|
||||
|
||||
# Temporary file for the downloaded archive
|
||||
TEMP_FILE="$(mktemp "${TMPDIR:-/tmp}/.C3_install.XXXXXXXX")"
|
||||
trap 'rm -f "$TEMP_FILE"' EXIT # Ensure temp file is deleted on exit
|
||||
|
||||
download_file() {
|
||||
# Download the archive using curl or wget
|
||||
# Check that the curl version is not 8.8.0, which is broken for --write-out
|
||||
# https://github.com/curl/curl/issues/13845
|
||||
if command -v curl >/dev/null && [[ "$(curl --version | awk 'NR==1{print $2}')" != "8.8.0" ]]; then
|
||||
curl -SL "$URL" -o "$TEMP_FILE"
|
||||
elif command -v wget >/dev/null; then
|
||||
wget -O "$TEMP_FILE" "$URL"
|
||||
else
|
||||
echo "Error: curl or wget is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Downloading C3 ($VERSION) from $URL..."
|
||||
download_file
|
||||
|
||||
# Remove existing installation and extract the new one
|
||||
rm -rf "$BIN_DIR"
|
||||
if [[ "$EXT" == ".zip" ]]; then
|
||||
unzip "$TEMP_FILE" -d "$HOME"
|
||||
else
|
||||
tar -xzf "$TEMP_FILE" -C "$HOME"
|
||||
fi
|
||||
|
||||
# Move extracted folder to installation directory
|
||||
mv "$HOME/c3" "$BIN_DIR"
|
||||
chmod +x "$BIN_DIR/c3c" # Ensure compiler binary is executable
|
||||
echo "✅ Installation completed in $BIN_DIR"
|
||||
|
||||
# Update PATH unless suppressed by environment variable
|
||||
if [ -n "${C3_NO_PATH_UPDATE:-}" ]; then
|
||||
echo "No path update because C3_NO_PATH_UPDATE is set"
|
||||
else
|
||||
update_shell() {
|
||||
FILE="$1"
|
||||
LINE="$2"
|
||||
|
||||
# Create shell config file if missing
|
||||
if [ ! -f "$FILE" ]; then
|
||||
touch "$FILE"
|
||||
fi
|
||||
|
||||
# Add the PATH line if not already present
|
||||
if ! grep -Fxq "$LINE" "$FILE"; then
|
||||
echo "Updating '${FILE}'"
|
||||
echo "$LINE" >>"$FILE"
|
||||
echo "Please restart or source your shell."
|
||||
fi
|
||||
}
|
||||
|
||||
# Detect the current shell and add C3 to its PATH
|
||||
case "$(basename "${SHELL-}")" in
|
||||
bash)
|
||||
# Default to bashrc as that is used in non login shells instead of the profile.
|
||||
LINE="export PATH=\"${BIN_DIR}:\$PATH\""
|
||||
update_shell ~/.bashrc "$LINE"
|
||||
;;
|
||||
fish)
|
||||
LINE="fish_add_path ${BIN_DIR}"
|
||||
update_shell ~/.config/fish/config.fish "$LINE"
|
||||
;;
|
||||
zsh)
|
||||
LINE="export PATH=\"${BIN_DIR}:\$PATH\""
|
||||
update_shell ~/.zshrc "$LINE"
|
||||
;;
|
||||
tcsh)
|
||||
LINE="set path = ( ${BIN_DIR} \$path )"
|
||||
update_shell ~/.tcshrc "$LINE"
|
||||
;;
|
||||
'')
|
||||
echo "warn: Could not detect shell type." >&2
|
||||
echo " Please permanently add '${BIN_DIR}' to your \$PATH to enable the 'c3c' command." >&2
|
||||
;;
|
||||
*)
|
||||
echo "warn: Could not update shell $(basename "$SHELL")" >&2
|
||||
echo " Please permanently add '${BIN_DIR}' to your \$PATH to enable the 'c3c' command." >&2
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
__wrap__
|
||||
@@ -1,77 +1,42 @@
|
||||
<* This module is scheduled for removal, use std::core::ascii *>
|
||||
module std::ascii;
|
||||
|
||||
macro bool in_range_m(c, start, len) => (uint)(c - start) < len;
|
||||
macro bool is_lower_m(c) => in_range_m(c, 0x61, 26);
|
||||
macro bool is_upper_m(c) => in_range_m(c, 0x41, 26);
|
||||
macro bool is_digit_m(c) => in_range_m(c, 0x30, 10);
|
||||
macro bool is_lower_m(c) => in_range_m(c, 0x61, 26);
|
||||
macro bool is_upper_m(c) => in_range_m(c, 0x41, 26);
|
||||
macro bool is_digit_m(c) => in_range_m(c, 0x30, 10);
|
||||
macro bool is_bdigit_m(c) => in_range_m(c, 0x30, 2);
|
||||
macro bool is_odigit_m(c) => in_range_m(c, 0x30, 8);
|
||||
macro bool is_xdigit_m(c) => in_range_m(c | 32, 0x61, 6) || is_digit_m(c);
|
||||
macro bool is_alpha_m(c) => in_range_m(c | 32, 0x61, 26);
|
||||
macro bool is_print_m(c) => in_range_m(c, 0x20, 95);
|
||||
macro bool is_graph_m(c) => in_range_m(c, 0x21, 94);
|
||||
macro bool is_space_m(c) => in_range_m(c, 0x9, 5) || c == 0x20;
|
||||
macro bool is_alnum_m(c) => is_alpha_m(c) || is_digit_m(c);
|
||||
macro bool is_punct_m(c) => !is_alnum_m(c) && is_graph_m(c);
|
||||
macro bool is_blank_m(c) => c == 0x20 || c == 0x9;
|
||||
macro bool is_cntrl_m(c) => c < 0x20 || c == 0x7f;
|
||||
macro bool is_alpha_m(c) => in_range_m(c | 32, 0x61, 26);
|
||||
macro bool is_print_m(c) => in_range_m(c, 0x20, 95);
|
||||
macro bool is_graph_m(c) => in_range_m(c, 0x21, 94);
|
||||
macro bool is_space_m(c) => in_range_m(c, 0x9, 5) || c == 0x20;
|
||||
macro bool is_alnum_m(c) => is_alpha_m(c) || is_digit_m(c);
|
||||
macro bool is_punct_m(c) => !is_alnum_m(c) && is_graph_m(c);
|
||||
macro bool is_blank_m(c) => c == 0x20 || c == 0x9;
|
||||
macro bool is_cntrl_m(c) => c < 0x20 || c == 0x7f;
|
||||
macro to_lower_m(c) => is_upper_m(c) ? c + 0x20 : c;
|
||||
macro to_upper_m(c) => is_lower_m(c) ? c - 0x20 : c;
|
||||
|
||||
fn bool in_range(char c, char start, char len) => in_range_m(c, start, len);
|
||||
fn bool is_lower(char c) => is_lower_m(c);
|
||||
fn bool is_upper(char c) => is_upper_m(c);
|
||||
fn bool is_digit(char c) => is_digit_m(c);
|
||||
fn bool is_bdigit(char c) => is_bdigit_m(c);
|
||||
fn bool is_odigit(char c) => is_odigit_m(c);
|
||||
fn bool is_xdigit(char c) => is_xdigit_m(c);
|
||||
fn bool is_alpha(char c) => is_alpha_m(c);
|
||||
fn bool is_print(char c) => is_print_m(c);
|
||||
fn bool is_graph(char c) => is_graph_m(c);
|
||||
fn bool is_space(char c) => is_space_m(c);
|
||||
fn bool is_alnum(char c) => is_alnum_m(c);
|
||||
fn bool is_punct(char c) => is_punct_m(c);
|
||||
fn bool is_blank(char c) => is_blank_m(c);
|
||||
fn bool is_cntrl(char c) => is_cntrl_m(c);
|
||||
fn char to_lower(char c) => (char)to_lower_m(c);
|
||||
fn char to_upper(char c) => (char)to_upper_m(c);
|
||||
|
||||
fn bool char.in_range(char c, char start, char len) => in_range_m(c, start, len);
|
||||
fn bool char.is_lower(char c) => is_lower_m(c);
|
||||
fn bool char.is_upper(char c) => is_upper_m(c);
|
||||
fn bool char.is_digit(char c) => is_digit_m(c);
|
||||
fn bool char.is_bdigit(char c) => is_bdigit_m(c);
|
||||
fn bool char.is_odigit(char c) => is_odigit_m(c);
|
||||
fn bool char.is_xdigit(char c) => is_xdigit_m(c);
|
||||
fn bool char.is_alpha(char c) => is_alpha_m(c);
|
||||
fn bool char.is_print(char c) => is_print_m(c);
|
||||
fn bool char.is_graph(char c) => is_graph_m(c);
|
||||
fn bool char.is_space(char c) => is_space_m(c);
|
||||
fn bool char.is_alnum(char c) => is_alnum_m(c);
|
||||
fn bool char.is_punct(char c) => is_punct_m(c);
|
||||
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);
|
||||
fn bool uint.is_upper(uint c) => is_upper_m(c);
|
||||
fn bool uint.is_digit(uint c) => is_digit_m(c);
|
||||
fn bool uint.is_bdigit(uint c) => is_bdigit_m(c);
|
||||
fn bool uint.is_odigit(uint c) => is_odigit_m(c);
|
||||
fn bool uint.is_xdigit(uint c) => is_xdigit_m(c);
|
||||
fn bool uint.is_alpha(uint c) => is_alpha_m(c);
|
||||
fn bool uint.is_print(uint c) => is_print_m(c);
|
||||
fn bool uint.is_graph(uint c) => is_graph_m(c);
|
||||
fn bool uint.is_space(uint c) => is_space_m(c);
|
||||
fn bool uint.is_alnum(uint c) => is_alnum_m(c);
|
||||
fn bool uint.is_punct(uint c) => is_punct_m(c);
|
||||
fn bool uint.is_blank(uint c) => is_blank_m(c);
|
||||
fn bool uint.is_cntrl(uint c) => is_cntrl_m(c);
|
||||
fn uint uint.to_lower(uint c) => (uint)to_lower_m(c);
|
||||
fn uint uint.to_upper(uint c) => (uint)to_upper_m(c);
|
||||
fn bool uint.is_lower(uint c) @deprecated => is_lower_m(c);
|
||||
fn bool uint.is_upper(uint c) @deprecated => is_upper_m(c);
|
||||
fn bool uint.is_digit(uint c) @deprecated => is_digit_m(c);
|
||||
fn bool uint.is_bdigit(uint c) @deprecated => is_bdigit_m(c);
|
||||
fn bool uint.is_odigit(uint c) @deprecated => is_odigit_m(c);
|
||||
fn bool uint.is_xdigit(uint c) @deprecated => is_xdigit_m(c);
|
||||
fn bool uint.is_alpha(uint c) @deprecated => is_alpha_m(c);
|
||||
fn bool uint.is_print(uint c) @deprecated => is_print_m(c);
|
||||
fn bool uint.is_graph(uint c) @deprecated => is_graph_m(c);
|
||||
fn bool uint.is_space(uint c) @deprecated => is_space_m(c);
|
||||
fn bool uint.is_alnum(uint c) @deprecated => is_alnum_m(c);
|
||||
fn bool uint.is_punct(uint c) @deprecated => is_punct_m(c);
|
||||
fn bool uint.is_blank(uint c) @deprecated => is_blank_m(c);
|
||||
fn bool uint.is_cntrl(uint c) @deprecated => is_cntrl_m(c);
|
||||
fn uint uint.to_lower(uint c) @deprecated => (uint)to_lower_m(c);
|
||||
fn uint uint.to_upper(uint c) @deprecated => (uint)to_upper_m(c);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
|
||||
// Copyright (c) 2023-2025 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::types{Type};
|
||||
module std::atomic::types;
|
||||
|
||||
struct Atomic
|
||||
struct Atomic <Type>
|
||||
{
|
||||
Type data;
|
||||
}
|
||||
@@ -11,147 +11,87 @@ struct Atomic
|
||||
<*
|
||||
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"
|
||||
@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)
|
||||
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.");
|
||||
}
|
||||
return $$atomic_load(&self.data, false, $ordering.ordinal);
|
||||
}
|
||||
<*
|
||||
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"
|
||||
@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)
|
||||
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.");
|
||||
}
|
||||
$$atomic_store(&self.data, value, false, $ordering.ordinal);
|
||||
}
|
||||
|
||||
macro Type Atomic.add(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
macro Type Atomic.add(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_add, data, value, ordering);
|
||||
return atomic::fetch_add(&self.data, value, $ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.sub(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
macro Type Atomic.sub(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_sub, data, value, ordering);
|
||||
return atomic::fetch_sub(&self.data, value, $ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.mul(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
macro Type Atomic.mul(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_mul, data, value, ordering);
|
||||
return atomic::fetch_mul(&self.data, value, $ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
macro Type Atomic.div(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_div, data, value, ordering);
|
||||
return atomic::fetch_div(&self.data, value, $ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
macro Type Atomic.max(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_max, data, value, ordering);
|
||||
return atomic::fetch_max(&self.data, value, $ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
macro Type Atomic.min(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_min, data, value, ordering);
|
||||
return atomic::fetch_min(&self.data, value, $ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.or(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
macro Type Atomic.or(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_or, data, value, ordering);
|
||||
return atomic::fetch_or(&self.data, value, $ordering);
|
||||
}
|
||||
|
||||
fn Type Atomic.xor(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
macro Type Atomic.xor(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
|
||||
return atomic::fetch_xor(&self.data, value, $ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.and(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
macro Type Atomic.and(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_and, data, value, ordering);
|
||||
return atomic::fetch_and(&self.data, value, $ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.shr(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
macro Type Atomic.shr(&self, Type amount, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
|
||||
return atomic::fetch_shift_right(&self.data, amount, $ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.shl(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
macro Type Atomic.shl(&self, Type amount, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
|
||||
return atomic::fetch_shift_left(&self.data, amount, $ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.set(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
|
||||
macro Type Atomic.set(&self, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec_no_arg(atomic::flag_set, data, ordering);
|
||||
return atomic::flag_set(&self.data, $ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.clear(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
|
||||
macro Type Atomic.clear(&self, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
|
||||
return @atomic_exec_no_arg(atomic::flag_clear, data, 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.");
|
||||
}
|
||||
}
|
||||
|
||||
macro @atomic_exec_no_arg(#func, data, ordering) @local
|
||||
{
|
||||
switch(ordering)
|
||||
{
|
||||
case RELAXED: return #func(data, RELAXED);
|
||||
case ACQUIRE: return #func(data, ACQUIRE);
|
||||
case RELEASE: return #func(data, RELEASE);
|
||||
case ACQUIRE_RELEASE: return #func(data, ACQUIRE_RELEASE);
|
||||
case SEQ_CONSISTENT: return #func(data, SEQ_CONSISTENT);
|
||||
default: unreachable("Ordering may not be non-atomic or unordered.");
|
||||
}
|
||||
return atomic::flag_clear(&self.data, $ordering);
|
||||
}
|
||||
|
||||
module std::atomic;
|
||||
@@ -171,11 +111,13 @@ macro bool is_native_atomic_type($Type)
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
$case POINTER:
|
||||
$case FUNC:
|
||||
$case FLOAT:
|
||||
$case BOOL:
|
||||
return true;
|
||||
$case DISTINCT:
|
||||
return is_native_atomic_type($typefrom($Type.inner));
|
||||
$case TYPEDEF:
|
||||
$case CONSTDEF:
|
||||
return is_native_atomic_type($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
@@ -183,7 +125,7 @@ macro bool is_native_atomic_type($Type)
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [&inout] 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"
|
||||
@@ -192,7 +134,7 @@ macro bool is_native_atomic_type($Type)
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require $defined(*ptr + y) : "+ must be defined between the values."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
@@ -203,7 +145,7 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [in] y : "the value to be subtracted from ptr."
|
||||
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
@@ -212,7 +154,7 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require $defined(*ptr - y) : "- must be defined between the values."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
@@ -223,7 +165,7 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [in] y : "the value to be multiplied with ptr."
|
||||
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
@@ -231,16 +173,16 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require $defined(*ptr * y) : "* must be defined between the values."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != 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;
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
@@ -263,24 +205,24 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [in] y : "the value to divide ptr by."
|
||||
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require $defined(*ptr * y) : "/ must be defined between the values."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $defined(*ptr / y) : "/ must be defined between the values."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != 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;
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
@@ -303,7 +245,7 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [in] y : "the value to perform a bitwise or with."
|
||||
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
@@ -312,7 +254,7 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require $defined(*ptr | y) : "| must be defined between the values."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
@@ -320,7 +262,7 @@ macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [in] y : "the value to perform a bitwise xor with."
|
||||
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
@@ -329,7 +271,7 @@ macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require $defined(*ptr ^ y) : "^ must be defined between the values."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
@@ -337,7 +279,7 @@ macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [in] y : "the value to perform a bitwise and with."
|
||||
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
@@ -346,7 +288,7 @@ macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require $defined(*ptr ^ y) : "& must be defined between the values."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
@@ -354,7 +296,7 @@ macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [in] y : "the value to shift ptr by."
|
||||
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
@@ -363,16 +305,16 @@ macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) : "The value for shift right must be an integer"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != 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;
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
@@ -396,7 +338,7 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [in] y : "the value to shift ptr by."
|
||||
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
@@ -405,16 +347,16 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) : "The value for shift left must be an integer"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != 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;
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
@@ -438,7 +380,7 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [&inout] 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"
|
||||
|
||||
@@ -446,7 +388,7 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
|
||||
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
@@ -454,7 +396,7 @@ macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
$typeof(*ptr) new_value = true;
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
do
|
||||
{
|
||||
@@ -466,14 +408,14 @@ macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [&inout] 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 $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
@@ -481,7 +423,7 @@ macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
$typeof(*ptr) new_value = false;
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
do
|
||||
{
|
||||
@@ -501,7 +443,7 @@ macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require $defined(*ptr > y) : "Only values that are comparable with > may be used"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
@@ -520,7 +462,7 @@ macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
|
||||
@require $defined(*ptr > y) : "Only values that are comparable with > may be used"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
|
||||
@@ -58,7 +58,7 @@ fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired,
|
||||
nextcase;
|
||||
$endif
|
||||
default:
|
||||
unreachable("Unsuported size (%d) for atomic_compare_exchange", size);
|
||||
unreachable("Unsupported size (%d) for atomic_compare_exchange", size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,301 +2,101 @@
|
||||
// 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;
|
||||
|
||||
alias AnyPredicate = fn bool(any value);
|
||||
alias AnyTest = fn bool(any type, any context);
|
||||
|
||||
struct AnyList (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
any* entries;
|
||||
}
|
||||
import std::collections::interfacelist;
|
||||
|
||||
alias AnyPredicate = InterfacePredicate {any};
|
||||
alias AnyTest = InterfaceTest {any};
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param initial_capacity : "The initial capacity to reserve"
|
||||
The AnyList contains a heterogenous set of types. Anything placed in the
|
||||
list will shallowly copied in order to be stored as an `any`. This means
|
||||
that the list will copy and free its elements.
|
||||
|
||||
However, because we're getting `any` values back when we pop, those operations
|
||||
need to take an allocator, as we can only copy then pop then return the copy.
|
||||
|
||||
If we're not doing pop, then things are easier, since we can just hand over
|
||||
the existing any.
|
||||
*>
|
||||
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
typedef AnyList = inline InterfaceList {any};
|
||||
|
||||
<*
|
||||
Return the first element by value, assuming it is the given type.
|
||||
|
||||
@param $Type : "The type of the first element"
|
||||
@return "The first element"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.first(&self, $Type)
|
||||
{
|
||||
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;
|
||||
return *anycast(self.first_any(), $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
Return the first element
|
||||
|
||||
@param initial_capacity : "The initial capacity to reserve"
|
||||
@return "The first element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
}
|
||||
fn any? AnyList.first_any(&self) @inline => InterfaceList {any}.first(self);
|
||||
|
||||
fn bool AnyList.is_initialized(&self) @inline => self.allocator != null;
|
||||
<*
|
||||
Return the last element by value, assuming it is the given type.
|
||||
|
||||
fn usz? AnyList.to_format(&self, Formatter* formatter) @dynamic
|
||||
@param $Type : "The type of the last element"
|
||||
@return "The last element"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.last(&self, $Type)
|
||||
{
|
||||
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;
|
||||
}
|
||||
return *anycast(self.last_any(), $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Push an element on the list by cloning it.
|
||||
*>
|
||||
macro void AnyList.push(&self, element)
|
||||
{
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
self.append_internal(allocator::clone(self.allocator, element));
|
||||
}
|
||||
Return the last element
|
||||
|
||||
fn void AnyList.append_internal(&self, any element) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
@return "The last element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.last_any(&self) @inline => InterfaceList {any}.last(self);
|
||||
|
||||
<*
|
||||
Free a retained element removed using *_retained.
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@param $Type : "The type we assume the value has"
|
||||
@return "The last value as the type given"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
fn void AnyList.free_element(&self, any element) @inline
|
||||
macro AnyList.pop(&self, $Type)
|
||||
{
|
||||
allocator::free(self.allocator, element.ptr);
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return *anycast(self.entries[--self.size], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@param $Type : "The type we assume the value has"
|
||||
@return "The first value as the type given"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.pop(&self, $Type)
|
||||
{
|
||||
if (!self.size) return 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? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.copy_pop(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return 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 temp allocator
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.tcopy_pop(&self) => self.copy_pop(tmem);
|
||||
|
||||
<*
|
||||
Pop the last value. It must later be released using list.free_element()
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.pop_retained(&self)
|
||||
{
|
||||
if (!self.size) return 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 NO_MORE_ELEMENT?;
|
||||
if (!self.size) return 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 NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
Return an element in the list by value, assuming it is the given type.
|
||||
|
||||
|
||||
<*
|
||||
Same as copy_pop() but pops the first value instead.
|
||||
*>
|
||||
fn any? AnyList.copy_pop_first(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return 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(tmem);
|
||||
|
||||
<*
|
||||
@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] : 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] : NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
fn bool AnyList.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
fn usz AnyList.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
@param index : "The index of the element to retrieve"
|
||||
@param $Type : "The type of the element"
|
||||
@return "The element at the index"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
@require index < self.size : "Index out of range"
|
||||
*>
|
||||
macro AnyList.get(&self, usz index, $Type)
|
||||
@@ -305,148 +105,18 @@ macro AnyList.get(&self, usz index, $Type)
|
||||
}
|
||||
|
||||
<*
|
||||
Return an element in the list.
|
||||
|
||||
@param index : "The index of the element to retrieve"
|
||||
@return "The element at the index"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
@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;
|
||||
}
|
||||
fn any AnyList.get_any(&self, usz index) @inline @operator([]) => InterfaceList {any}.get(self, index);
|
||||
|
||||
<*
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
Return the length of the list.
|
||||
|
||||
@return "The number of elements in the list"
|
||||
*>
|
||||
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 = tmem;
|
||||
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);
|
||||
}
|
||||
fn usz AnyList.len(&self) @operator(len) @inline => InterfaceList {any}.len(self);
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
// Copyright (c) 2023-2025 C3 team. 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 SIZE > 0
|
||||
@require SIZE > 0 : "The size of the bitset in bits must be at least 1"
|
||||
*>
|
||||
module std::collections::bitset{SIZE};
|
||||
module std::collections::bitset <SIZE>;
|
||||
|
||||
alias Type = uint;
|
||||
|
||||
const BITS = Type.sizeof * 8;
|
||||
const BITS = uint.sizeof * 8;
|
||||
const SZ = (SIZE + BITS - 1) / BITS;
|
||||
|
||||
struct BitSet
|
||||
{
|
||||
Type[SZ] data;
|
||||
uint[SZ] data;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "The number of bits set"
|
||||
*>
|
||||
fn usz BitSet.cardinality(&self)
|
||||
{
|
||||
usz n;
|
||||
@@ -24,7 +28,11 @@ fn usz BitSet.cardinality(&self)
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
Set a bit in the bitset.
|
||||
|
||||
@param i : "The index to set"
|
||||
|
||||
@require i < SIZE : "Index was out of range"
|
||||
*>
|
||||
fn void BitSet.set(&self, usz i)
|
||||
{
|
||||
@@ -34,7 +42,86 @@ fn void BitSet.set(&self, usz i)
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
Perform xor over all bits, mutating itself
|
||||
|
||||
@param set : "The bit set to xor with"
|
||||
@return "The resulting bit set"
|
||||
*>
|
||||
macro BitSet BitSet.xor_self(&self, BitSet set) @operator(^=)
|
||||
{
|
||||
foreach (i, &x : self.data) *x ^= set.data[i];
|
||||
return *self;
|
||||
}
|
||||
|
||||
<*
|
||||
Perform xor over all bits, returning a new bit set.
|
||||
|
||||
@param set : "The bit set to xor with"
|
||||
@return "The resulting bit set"
|
||||
*>
|
||||
fn BitSet BitSet.xor(&self, BitSet set) @operator(^)
|
||||
{
|
||||
BitSet new_set @noinit;
|
||||
foreach (i, x : self.data) new_set.data[i] = x ^ set.data[i];
|
||||
return new_set;
|
||||
}
|
||||
|
||||
<*
|
||||
Perform or over all bits, returning a new bit set.
|
||||
|
||||
@param set : "The bit set to xor with"
|
||||
@return "The resulting bit set"
|
||||
*>
|
||||
fn BitSet BitSet.or(&self, BitSet set) @operator(|)
|
||||
{
|
||||
BitSet new_set @noinit;
|
||||
foreach (i, x : self.data) new_set.data[i] = x | set.data[i];
|
||||
return new_set;
|
||||
}
|
||||
|
||||
<*
|
||||
Perform or over all bits, mutating itself
|
||||
|
||||
@param set : "The bit set to xor with"
|
||||
@return "The resulting bit set"
|
||||
*>
|
||||
macro BitSet BitSet.or_self(&self, BitSet set) @operator(|=)
|
||||
{
|
||||
foreach (i, &x : self.data) *x |= set.data[i];
|
||||
return *self;
|
||||
}
|
||||
|
||||
<*
|
||||
Perform & over all bits, returning a new bit set.
|
||||
|
||||
@param set : "The bit set to xor with"
|
||||
@return "The resulting bit set"
|
||||
*>
|
||||
fn BitSet BitSet.and(&self, BitSet set) @operator(&)
|
||||
{
|
||||
BitSet new_set @noinit;
|
||||
foreach (i, x : self.data) new_set.data[i] = x & set.data[i];
|
||||
return new_set;
|
||||
}
|
||||
|
||||
<*
|
||||
Perform & over all bits, mutating itself.
|
||||
|
||||
@param set : "The bit set to xor with"
|
||||
@return "The resulting bit set"
|
||||
*>
|
||||
macro BitSet BitSet.and_self(&self, BitSet set) @operator(&=)
|
||||
{
|
||||
foreach (i, &x : self.data) *x &= set.data[i];
|
||||
return *self;
|
||||
}
|
||||
|
||||
<*
|
||||
Unset (clear) a bit in the bitset.
|
||||
|
||||
@param i : "The index to set"
|
||||
|
||||
@require i < SIZE : "Index was out of range"
|
||||
*>
|
||||
fn void BitSet.unset(&self, usz i)
|
||||
{
|
||||
@@ -44,7 +131,12 @@ fn void BitSet.unset(&self, usz i)
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
Get a particular bit in the bitset
|
||||
|
||||
@param i : "The index of the bit"
|
||||
|
||||
@require i < SIZE : "Index was out of range"
|
||||
@pure
|
||||
*>
|
||||
fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
{
|
||||
@@ -53,13 +145,23 @@ fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
return self.data[q] & (1 << r) != 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the number of bits.
|
||||
|
||||
@pure
|
||||
*>
|
||||
fn usz BitSet.len(&self) @operator(len) @inline
|
||||
{
|
||||
return SZ * BITS;
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < SIZE
|
||||
Change a particular bit in the bitset
|
||||
|
||||
@param i : "The index of the bit"
|
||||
@param value : "The value to set the bit to"
|
||||
|
||||
@require i < SIZE : "Index was out of range"
|
||||
*>
|
||||
fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
|
||||
{
|
||||
@@ -70,7 +172,7 @@ fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
|
||||
<*
|
||||
@require Type.kindof == UNSIGNED_INT
|
||||
*>
|
||||
module std::collections::growablebitset{Type};
|
||||
module std::collections::growablebitset <Type>;
|
||||
import std::collections::list;
|
||||
|
||||
const BITS = Type.sizeof * 8;
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
<*
|
||||
@require MAX_SIZE >= 1 : `The size must be at least 1 element big.`
|
||||
*>
|
||||
module std::collections::elastic_array {Type, MAX_SIZE};
|
||||
module std::collections::elastic_array <Type, MAX_SIZE>;
|
||||
import std::io, std::math, std::collections::list_common;
|
||||
|
||||
alias ElementPredicate = fn bool(Type *type);
|
||||
alias 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;
|
||||
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct ElasticArray (Printable)
|
||||
{
|
||||
@@ -46,7 +46,7 @@ fn String ElasticArray.to_tstring(&self)
|
||||
|
||||
fn void? ElasticArray.push_try(&self, Type element) @inline
|
||||
{
|
||||
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
|
||||
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY~;
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ fn void ElasticArray.push(&self, Type element) @inline
|
||||
|
||||
fn Type? ElasticArray.pop(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ fn void ElasticArray.clear(&self)
|
||||
*>
|
||||
fn Type? ElasticArray.pop_first(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
@@ -121,7 +121,24 @@ fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
|
||||
|
||||
@param [in] array
|
||||
*>
|
||||
fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
|
||||
fn usz ElasticArray.add_array_to_limit(&self, Type[] array) @deprecated("Use push_all_to_limit")
|
||||
{
|
||||
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 as many values from this array as possible, returning the
|
||||
number of elements that didn't fit.
|
||||
|
||||
@param [in] array
|
||||
*>
|
||||
fn usz ElasticArray.push_all_to_limit(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return 0;
|
||||
foreach (i, &value : array)
|
||||
@@ -139,7 +156,7 @@ fn usz ElasticArray.add_array_to_limit(&self, Type[] 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)
|
||||
fn void ElasticArray.add_array(&self, Type[] array) @deprecated("Use push_all")
|
||||
{
|
||||
if (!array.len) return;
|
||||
foreach (&value : array)
|
||||
@@ -148,6 +165,21 @@ fn void ElasticArray.add_array(&self, Type[] array)
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
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.push_all(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return;
|
||||
foreach (&value : array)
|
||||
{
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@@ -209,7 +241,7 @@ fn void? ElasticArray.push_front_try(&self, Type type) @inline
|
||||
*>
|
||||
fn void? ElasticArray.insert_at_try(&self, usz index, Type value)
|
||||
{
|
||||
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
|
||||
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY~;
|
||||
self.insert_at(index, value);
|
||||
}
|
||||
|
||||
@@ -237,25 +269,25 @@ fn void ElasticArray.set_at(&self, usz index, Type type)
|
||||
|
||||
fn void? ElasticArray.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
self.size--;
|
||||
}
|
||||
|
||||
fn void? ElasticArray.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type? ElasticArray.first(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
fn Type? ElasticArray.last(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
@@ -336,7 +368,7 @@ fn usz? ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -345,7 +377,7 @@ fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -426,4 +458,4 @@ fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER)
|
||||
fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
return list_common::list_compact(self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<*
|
||||
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap"
|
||||
@require Enum.values.len > 0 : "Only non-empty enums may be used with enummap"
|
||||
*>
|
||||
module std::collections::enummap{Enum, ValueType};
|
||||
module std::collections::enummap <Enum, ValueType>;
|
||||
import std::io;
|
||||
|
||||
struct EnumMap (Printable)
|
||||
{
|
||||
ValueType[Enum.len] values;
|
||||
ValueType[Enum.values.len] values;
|
||||
}
|
||||
|
||||
fn void EnumMap.init(&self, ValueType init_value)
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
<*
|
||||
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enumset"
|
||||
*>
|
||||
module std::collections::enumset{Enum};
|
||||
module std::collections::enumset <Enum>;
|
||||
import std::io;
|
||||
|
||||
alias EnumSetType @private = $typefrom(type_for_enum_elements(Enum.elements));
|
||||
const ENUM_COUNT @private = Enum.values.len;
|
||||
alias EnumSetType @private = $typefrom(type_for_enum_elements(ENUM_COUNT));
|
||||
|
||||
const IS_CHAR_ARRAY = Enum.elements > 128;
|
||||
const IS_CHAR_ARRAY = ENUM_COUNT > 128;
|
||||
typedef EnumSet (Printable) = EnumSetType;
|
||||
|
||||
fn void EnumSet.add(&self, Enum v)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<*
|
||||
@require $defined((Key){}.hash()) : `No .hash function found on the key`
|
||||
*>
|
||||
module std::collections::map{Key, Value};
|
||||
module std::collections::map <Key, Value>;
|
||||
import std::math;
|
||||
import std::io @norecurse;
|
||||
|
||||
@@ -30,8 +30,10 @@ struct HashMap (Printable)
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator allocator;
|
||||
uint count; // Number of elements
|
||||
uint threshold; // Resize limit
|
||||
<* Last inserted LinkedEntry *>
|
||||
uint count;
|
||||
<* Resize limit *>
|
||||
uint threshold;
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
@@ -90,7 +92,7 @@ macro HashMap* HashMap.init_with_key_values(&self, Allocator allocator, ..., uin
|
||||
*>
|
||||
macro HashMap* HashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.tinit_with_key_values(tmem, capacity, load_factor);
|
||||
return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -148,7 +150,7 @@ fn bool HashMap.is_initialized(&map)
|
||||
fn HashMap* HashMap.init_from_map(&self, Allocator allocator, HashMap* other_map)
|
||||
{
|
||||
self.init(allocator, other_map.table.len, other_map.load_factor);
|
||||
self.put_all_for_create(other_map);
|
||||
hashmap_put_all_for_create(self, other_map);
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -173,29 +175,48 @@ fn usz HashMap.len(&map) @inline
|
||||
|
||||
fn Value*? HashMap.get_ref(&map, Key key)
|
||||
{
|
||||
if (!map.count) return NOT_FOUND?;
|
||||
if (!map.count) return NOT_FOUND~;
|
||||
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 NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn Value* HashMap.get_or_create_ref(&map, Key key) @operator(&[])
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
if (map.count)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
map.set(key, {});
|
||||
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;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
fn Entry*? HashMap.get_entry(&map, Key key)
|
||||
{
|
||||
if (!map.count) return NOT_FOUND?;
|
||||
if (!map.count) return NOT_FOUND~;
|
||||
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 NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value or update and
|
||||
@require $assignable(#expr, Value)
|
||||
Get the value or set it to the value
|
||||
|
||||
@require $defined(Value val = #expr)
|
||||
*>
|
||||
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
{
|
||||
@@ -212,7 +233,7 @@ macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
if (e.hash == hash && equals(key, e.key)) return e.value;
|
||||
}
|
||||
Value val = #expr;
|
||||
map.add_entry(hash, key, val, index);
|
||||
hashmap_add_entry(map, hash, key, val, index);
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -229,15 +250,15 @@ fn bool HashMap.has_key(&map, Key key)
|
||||
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
|
||||
{
|
||||
// If the map isn't initialized, use the defaults to initialize it.
|
||||
switch (map.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
map.init(mem);
|
||||
case null:
|
||||
map.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (map.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
map.init(mem);
|
||||
case null:
|
||||
map.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[index]; e != null; e = e.next)
|
||||
@@ -248,13 +269,13 @@ fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
map.add_entry(hash, key, value, index);
|
||||
hashmap_add_entry(map, hash, key, value, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void? HashMap.remove(&map, Key key) @maydiscard
|
||||
{
|
||||
if (!map.remove_entry_for_key(key)) return NOT_FOUND?;
|
||||
if (!hashmap_remove_entry_for_key(map, key)) return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn void HashMap.clear(&map)
|
||||
@@ -269,9 +290,9 @@ fn void HashMap.clear(&map)
|
||||
{
|
||||
Entry *to_delete = next;
|
||||
next = next.next;
|
||||
map.free_entry(to_delete);
|
||||
hashmap_free_entry(map, to_delete);
|
||||
}
|
||||
map.free_entry(entry);
|
||||
hashmap_free_entry(map, entry);
|
||||
*entry_ref = null;
|
||||
}
|
||||
map.count = 0;
|
||||
@@ -281,7 +302,7 @@ fn void HashMap.free(&map)
|
||||
{
|
||||
if (!map.is_initialized()) return;
|
||||
map.clear();
|
||||
map.free_internal(map.table.ptr);
|
||||
hashmap_free_internal(map, map.table.ptr);
|
||||
map.table = {};
|
||||
}
|
||||
|
||||
@@ -332,10 +353,7 @@ macro HashMap.@each_entry(map; @body(entry))
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] HashMap.tvalues(&map)
|
||||
{
|
||||
return map.values(tmem) @inline;
|
||||
}
|
||||
fn Value[] HashMap.tvalues(&self) => self.values(tmem) @inline;
|
||||
|
||||
fn Value[] HashMap.values(&self, Allocator allocator)
|
||||
{
|
||||
@@ -384,7 +402,7 @@ fn HashMapKeyIterator HashMap.key_iter(&self)
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
|
||||
fn void hashmap_add_entry(HashMap* map, uint hash, Key key, Value value, uint bucket_index) @private
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
@@ -393,11 +411,11 @@ fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_ind
|
||||
map.table[bucket_index] = entry;
|
||||
if (map.count++ >= map.threshold)
|
||||
{
|
||||
map.resize(map.table.len * 2);
|
||||
hashmap_resize(map, map.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.resize(&map, uint new_capacity) @private
|
||||
fn void hashmap_resize(HashMap* map, uint new_capacity) @private
|
||||
{
|
||||
Entry*[] old_table = map.table;
|
||||
uint old_capacity = old_table.len;
|
||||
@@ -407,9 +425,9 @@ fn void HashMap.resize(&map, uint new_capacity) @private
|
||||
return;
|
||||
}
|
||||
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
|
||||
map.transfer(new_table);
|
||||
hashmap_transfer(map, new_table);
|
||||
map.table = new_table;
|
||||
map.free_internal(old_table.ptr);
|
||||
hashmap_free_internal(map, old_table.ptr);
|
||||
map.threshold = (uint)(new_capacity * map.load_factor);
|
||||
}
|
||||
|
||||
@@ -421,11 +439,11 @@ fn usz? HashMap.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s: %s", entry.key, entry.value)!;
|
||||
};
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
fn void HashMap.transfer(&map, Entry*[] new_table) @private
|
||||
fn void hashmap_transfer(HashMap* map, Entry*[] new_table) @private
|
||||
{
|
||||
Entry*[] src = map.table;
|
||||
uint new_capacity = new_table.len;
|
||||
@@ -444,20 +462,20 @@ fn void HashMap.transfer(&map, Entry*[] new_table) @private
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
|
||||
fn void hashmap_put_all_for_create(HashMap* 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);
|
||||
hashmap_put_for_create(map, e.key, e.value);
|
||||
e = e.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.put_for_create(&map, Key key, Value value) @private
|
||||
fn void hashmap_put_for_create(HashMap* map, Key key, Value value) @private
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
@@ -469,15 +487,15 @@ fn void HashMap.put_for_create(&map, Key key, Value value) @private
|
||||
return;
|
||||
}
|
||||
}
|
||||
map.create_entry(hash, key, value, i);
|
||||
hashmap_create_entry(map, hash, key, value, i);
|
||||
}
|
||||
|
||||
fn void HashMap.free_internal(&map, void* ptr) @inline @private
|
||||
fn void hashmap_free_internal(HashMap* map, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(map.allocator, ptr);
|
||||
}
|
||||
|
||||
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
|
||||
fn bool hashmap_remove_entry_for_key(HashMap* map, Key key) @private
|
||||
{
|
||||
if (!map.count) return false;
|
||||
uint hash = rehash(key.hash());
|
||||
@@ -498,7 +516,7 @@ fn bool HashMap.remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
map.free_entry(e);
|
||||
hashmap_free_entry(map, e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
@@ -507,7 +525,7 @@ fn bool HashMap.remove_entry_for_key(&map, Key key) @private
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
|
||||
fn void hashmap_create_entry(HashMap* map, uint hash, Key key, Value value, int bucket_index) @private
|
||||
{
|
||||
Entry *e = map.table[bucket_index];
|
||||
$if COPY_KEYS:
|
||||
@@ -518,12 +536,12 @@ fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_i
|
||||
map.count++;
|
||||
}
|
||||
|
||||
fn void HashMap.free_entry(&self, Entry *entry) @local
|
||||
fn void hashmap_free_entry(HashMap* map, Entry *entry) @local
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
allocator::free(self.allocator, entry.key);
|
||||
allocator::free(map.allocator, entry.key);
|
||||
$endif
|
||||
self.free_internal(entry);
|
||||
hashmap_free_internal(map, entry);
|
||||
}
|
||||
|
||||
|
||||
@@ -589,4 +607,4 @@ macro uint index_for(uint hash, uint capacity) @private
|
||||
return hash & (capacity - 1);
|
||||
}
|
||||
|
||||
int dummy @local;
|
||||
int dummy @local;
|
||||
|
||||
654
lib/std/collections/hashset.c3
Normal file
654
lib/std/collections/hashset.c3
Normal file
@@ -0,0 +1,654 @@
|
||||
<*
|
||||
@require $defined((Value){}.hash()) : `No .hash function found on the value`
|
||||
*>
|
||||
module std::collections::set <Value>;
|
||||
import std::math;
|
||||
import std::io @norecurse;
|
||||
|
||||
const uint DEFAULT_INITIAL_CAPACITY = 16;
|
||||
const uint MAXIMUM_CAPACITY = 1u << 31;
|
||||
const float DEFAULT_LOAD_FACTOR = 0.75;
|
||||
|
||||
const Allocator SET_HEAP_ALLOCATOR = (Allocator)&dummy;
|
||||
|
||||
<* Copy the ONHEAP allocator to initialize to a set that is heap allocated *>
|
||||
const HashSet ONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
|
||||
|
||||
struct Entry
|
||||
{
|
||||
uint hash;
|
||||
Value value;
|
||||
Entry* next;
|
||||
}
|
||||
|
||||
struct HashSet (Printable)
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator allocator;
|
||||
<* Number of elements *>
|
||||
usz count;
|
||||
<* Resize limit *>
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
fn usz HashSet.len(&self) @operator(len) => self.count;
|
||||
|
||||
<*
|
||||
@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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
self.allocator = allocator;
|
||||
self.threshold = (usz) (capacity * load_factor);
|
||||
self.load_factor = 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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init(tmem, capacity, load_factor) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashSet* HashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
self.add($vaarg[$i]);
|
||||
$endfor
|
||||
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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashSet* HashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the HashSet"
|
||||
@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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
foreach (v : values) self.add(v);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the HashSet entries"
|
||||
@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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_from_values(tmem, values, capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
Has this hash set been initialized yet?
|
||||
|
||||
@param [&in] set : "The hash set we are testing"
|
||||
@return "Returns true if it has been initialized, false otherwise"
|
||||
*>
|
||||
fn bool HashSet.is_initialized(&set)
|
||||
{
|
||||
return set.allocator && set.allocator.ptr != &dummy;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn HashSet* HashSet.init_from_set(&self, Allocator allocator, HashSet* other_set)
|
||||
{
|
||||
self.init(allocator, other_set.table.len, other_set.load_factor);
|
||||
hashset_put_all_for_create(self, other_set);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !set.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn HashSet* HashSet.tinit_from_set(&set, HashSet* other_set)
|
||||
{
|
||||
return set.init_from_set(tmem, other_set) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set is empty
|
||||
|
||||
@return "true if it is empty"
|
||||
@pure
|
||||
*>
|
||||
fn bool HashSet.is_empty(&set) @inline
|
||||
{
|
||||
return !set.count;
|
||||
}
|
||||
|
||||
<*
|
||||
Add all elements in the slice to the set.
|
||||
|
||||
@param [in] list
|
||||
@return "The number of new elements added"
|
||||
@ensure total <= list.len
|
||||
*>
|
||||
fn usz HashSet.add_all(&set, Value[] list)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : list)
|
||||
{
|
||||
if (set.add(v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other
|
||||
@return "The number of new elements added"
|
||||
@ensure return <= other.count
|
||||
*>
|
||||
fn usz HashSet.add_all_from(&set, HashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (set.add(value)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to add"
|
||||
@return "true if the value didn't exist in the set"
|
||||
*>
|
||||
fn bool HashSet.add(&set, Value value)
|
||||
{
|
||||
// If the set isn't initialized, use the defaults to initialize it.
|
||||
switch (set.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
set.init(mem);
|
||||
case null:
|
||||
set.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint hash = rehash(value.hash());
|
||||
uint index = index_for(hash, set.table.len);
|
||||
for (Entry *e = set.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return false;
|
||||
}
|
||||
hashset_add_entry(set, hash, value, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Iterate over all the values in the set
|
||||
*>
|
||||
macro HashSet.@each(set; @body(value))
|
||||
{
|
||||
if (!set.count) return;
|
||||
foreach (Entry* entry : set.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@body(entry.value);
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set contains the given value.
|
||||
|
||||
@param value : "The value to check"
|
||||
@return "true if it exists in the set"
|
||||
*>
|
||||
fn bool HashSet.contains(&set, Value value)
|
||||
{
|
||||
if (!set.count) return false;
|
||||
uint hash = rehash(value.hash());
|
||||
for (Entry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
Remove a single value from the set.
|
||||
|
||||
@param value : "The value to remove"
|
||||
@return? NOT_FOUND : "If the entry is not found"
|
||||
*>
|
||||
fn void? HashSet.remove(&set, Value value) @maydiscard
|
||||
{
|
||||
if (!hashset_remove_entry_for_value(set, value)) return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz HashSet.remove_all(&set, Value[] values)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : values)
|
||||
{
|
||||
if (hashset_remove_entry_for_value(set, v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other : "Other set"
|
||||
*>
|
||||
fn usz HashSet.remove_all_from(&set, HashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value val)
|
||||
{
|
||||
if (hashset_remove_entry_for_value(set, val)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
Free all memory allocated by the hash set.
|
||||
*>
|
||||
fn void HashSet.free(&set)
|
||||
{
|
||||
if (!set.is_initialized()) return;
|
||||
set.clear();
|
||||
hashset_free_internal(set, set.table.ptr);
|
||||
*set = {};
|
||||
}
|
||||
|
||||
<*
|
||||
Clear all elements from the set while keeping the underlying storage
|
||||
|
||||
@ensure set.count == 0
|
||||
*>
|
||||
fn void HashSet.clear(&set)
|
||||
{
|
||||
if (!set.count) return;
|
||||
|
||||
foreach (Entry** &entry_ref : set.table)
|
||||
{
|
||||
Entry* entry = *entry_ref;
|
||||
if (!entry) continue;
|
||||
|
||||
Entry *next = entry.next;
|
||||
while (next)
|
||||
{
|
||||
Entry *to_delete = next;
|
||||
next = next.next;
|
||||
hashset_free_entry(set, to_delete);
|
||||
}
|
||||
|
||||
hashset_free_entry(set, entry);
|
||||
*entry_ref = null;
|
||||
}
|
||||
set.count = 0;
|
||||
}
|
||||
|
||||
fn void HashSet.reserve(&set, usz capacity)
|
||||
{
|
||||
if (capacity > set.threshold)
|
||||
{
|
||||
hashset_resize(set, math::next_power_of_2(capacity));
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] HashSet.tvalues(&self) => self.values(tmem) @inline;
|
||||
|
||||
fn Value[] HashSet.values(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, self.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : self.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
list[index++] = entry.value;
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// --- Set Operations ---
|
||||
|
||||
<*
|
||||
Returns the union of two sets (A | B)
|
||||
|
||||
@param [&in] other : "The other set to union with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the union of both sets"
|
||||
*>
|
||||
fn HashSet HashSet.set_union(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
usz new_capacity = math::next_power_of_2(self.count + other.count);
|
||||
HashSet result;
|
||||
result.init(allocator, new_capacity, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
result.add_all_from(other);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tset_union(&self, HashSet* other) => self.set_union(tmem, other);
|
||||
|
||||
<*
|
||||
Returns the intersection of the two sets (A & B)
|
||||
|
||||
@param [&in] other : "The other set to intersect with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the intersection of both sets"
|
||||
*>
|
||||
fn HashSet HashSet.intersection(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
HashSet result;
|
||||
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
|
||||
|
||||
// Iterate through the smaller set for efficiency
|
||||
HashSet* smaller = self.count <= other.count ? self : other;
|
||||
HashSet* larger = self.count > other.count ? self : other;
|
||||
|
||||
smaller.@each(;Value value)
|
||||
{
|
||||
if (larger.contains(value)) result.add(value);
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tintersection(&self, HashSet* other) => self.intersection(tmem, other);
|
||||
|
||||
<*
|
||||
Return this set - other, so (A & ~B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set but not in the other"
|
||||
*>
|
||||
fn HashSet HashSet.difference(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
HashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
self.@each(;Value value)
|
||||
{
|
||||
if (!other.contains(value))
|
||||
{
|
||||
result.add(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tdifference(&self, HashSet* other) => self.difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Return (A ^ B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set or the other, but not both"
|
||||
*>
|
||||
fn HashSet HashSet.symmetric_difference(&self, Allocator allocator, HashSet* other)
|
||||
{
|
||||
HashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (!result.add(value))
|
||||
{
|
||||
result.remove(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn HashSet HashSet.tsymmetric_difference(&self, HashSet* other) => self.symmetric_difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Check if this hash set is a subset of another set.
|
||||
|
||||
@param [&in] other : "The other set to check against"
|
||||
@return "True if all elements of this set are in the other set"
|
||||
*>
|
||||
fn bool HashSet.is_subset(&self, HashSet* other)
|
||||
{
|
||||
if (self.count == 0) return true;
|
||||
if (self.count > other.count) return false;
|
||||
|
||||
self.@each(;Value value)
|
||||
{
|
||||
if (!other.contains(value)) return false;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void hashset_add_entry(HashSet* set, uint hash, Value value, uint bucket_index) @private
|
||||
{
|
||||
Entry* entry = allocator::new(set.allocator, Entry, { .hash = hash, .value = value, .next = set.table[bucket_index] });
|
||||
set.table[bucket_index] = entry;
|
||||
if (set.count++ >= set.threshold)
|
||||
{
|
||||
hashset_resize(set, set.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void hashset_resize(HashSet* self, usz new_capacity) @private
|
||||
{
|
||||
Entry*[] old_table = self.table;
|
||||
usz old_capacity = old_table.len;
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
self.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
Entry*[] new_table = allocator::new_array(self.allocator, Entry*, new_capacity);
|
||||
hashset_transfer(self, new_table);
|
||||
self.table = new_table;
|
||||
hashset_free_internal(self, old_table.ptr);
|
||||
self.threshold = (uint)(new_capacity * self.load_factor);
|
||||
}
|
||||
|
||||
fn usz? HashSet.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len;
|
||||
len += f.print("{ ")!;
|
||||
self.@each(; Value value)
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s", value)!;
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
fn void hashset_transfer(HashSet* self, Entry*[] new_table) @private
|
||||
{
|
||||
Entry*[] src = self.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 hashset_put_all_for_create(HashSet* set, HashSet* other_set) @private
|
||||
{
|
||||
if (!other_set.count) return;
|
||||
foreach (Entry *e : other_set.table)
|
||||
{
|
||||
while (e)
|
||||
{
|
||||
hashset_put_for_create(set, e.value);
|
||||
e = e.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void hashset_put_for_create(HashSet* set, Value value) @private
|
||||
{
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
for (Entry *e = set.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
// Value already exists, no need to do anything
|
||||
return;
|
||||
}
|
||||
}
|
||||
hashset_create_entry(set, hash, value, i);
|
||||
}
|
||||
|
||||
fn void hashset_free_internal(HashSet* self, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(self.allocator, ptr);
|
||||
}
|
||||
|
||||
fn void hashset_create_entry(HashSet* set, uint hash, Value value, int bucket_index) @private
|
||||
{
|
||||
Entry* entry = allocator::new(set.allocator, Entry, {
|
||||
.hash = hash,
|
||||
.value = value,
|
||||
.next = set.table[bucket_index]
|
||||
});
|
||||
set.table[bucket_index] = entry;
|
||||
set.count++;
|
||||
}
|
||||
|
||||
<*
|
||||
Removes the entry for the specified value if present
|
||||
@return "true if found and removed, false otherwise"
|
||||
*>
|
||||
fn bool hashset_remove_entry_for_value(HashSet* set, Value value) @private
|
||||
{
|
||||
if (!set.count) return false;
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
Entry* prev = set.table[i];
|
||||
Entry* e = prev;
|
||||
while (e)
|
||||
{
|
||||
Entry *next = e.next;
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
set.count--;
|
||||
if (prev == e)
|
||||
{
|
||||
set.table[i] = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
hashset_free_entry(set, e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void hashset_free_entry(HashSet* set, Entry *entry) @private
|
||||
{
|
||||
allocator::free(set.allocator, entry);
|
||||
}
|
||||
|
||||
struct HashSetIterator
|
||||
{
|
||||
HashSet* set;
|
||||
usz bucket_index;
|
||||
Entry* current;
|
||||
}
|
||||
|
||||
fn HashSetIterator HashSet.iter(&set) => { .set = set, .bucket_index = 0, .current = null };
|
||||
|
||||
fn Value? HashSetIterator.next(&self)
|
||||
{
|
||||
if (self.current)
|
||||
{
|
||||
Value value = self.current.value;
|
||||
self.current = self.current.next;
|
||||
return value;
|
||||
}
|
||||
|
||||
while (self.bucket_index < self.set.table.len)
|
||||
{
|
||||
self.current = self.set.table[self.bucket_index++];
|
||||
if (self.current)
|
||||
{
|
||||
Value value = self.current.value;
|
||||
self.current = self.current.next;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz HashSetIterator.len(&self) @operator(len)
|
||||
{
|
||||
return self.set.count;
|
||||
}
|
||||
|
||||
<* @pure *>
|
||||
fn uint rehash(uint hash) @inline @private
|
||||
{
|
||||
hash ^= (hash >> 20) ^ (hash >> 12);
|
||||
return hash ^ ((hash >> 7) ^ (hash >> 4));
|
||||
}
|
||||
|
||||
macro uint index_for(uint hash, uint capacity) @private => hash & (capacity - 1);
|
||||
|
||||
int dummy @local;
|
||||
539
lib/std/collections/interfacelist.c3
Normal file
539
lib/std/collections/interfacelist.c3
Normal file
@@ -0,0 +1,539 @@
|
||||
// Copyright (c) 2024-2025 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 Type.kindof == INTERFACE || Type.kindof == ANY : "The kind of an interfacelist must be an interface or `any`"
|
||||
*>
|
||||
module std::collections::interfacelist <Type>;
|
||||
import std::io,std::math;
|
||||
|
||||
alias InterfacePredicate = fn bool(Type value);
|
||||
alias InterfaceTest = fn bool(Type type, Type context);
|
||||
|
||||
<*
|
||||
The InterfaceList contains a heterogenous set of types implementing an interface. anything placed in the
|
||||
list will shallowly copied in order to be stored as the interface. This means
|
||||
that the list will copy and free its elements.
|
||||
|
||||
However, because we're getting interface values back when we pop, those operations
|
||||
need to take an allocator, as we can only copy then pop then return the copy.
|
||||
|
||||
If we're not doing pop, then things are easier, since we can just hand over
|
||||
the existing value.
|
||||
*>
|
||||
struct InterfaceList (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
Type* entries;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Initialize the list. If not initialized then it will use the temp allocator
|
||||
when something is pushed to it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param initial_capacity : "The initial capacity to reserve, defaults to 16"
|
||||
*>
|
||||
fn InterfaceList* InterfaceList.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, Type, 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 InterfaceList* InterfaceList.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn bool InterfaceList.is_initialized(&self) @inline => self.allocator != null;
|
||||
|
||||
<*
|
||||
Push an element on the list by cloning it.
|
||||
@require $defined(Type t = &element) : "Element must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.push(&self, element)
|
||||
{
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
interfacelist_append(self, allocator::clone(self.allocator, element));
|
||||
}
|
||||
|
||||
<*
|
||||
Free a retained element removed using *_retained.
|
||||
*>
|
||||
fn void InterfaceList.free_element(&self, Type element) @inline
|
||||
{
|
||||
allocator::free(self.allocator, element.ptr);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the last value, pop it and return the copy of it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use for copying"
|
||||
@return "A copy of the last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.copy_pop(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return (Type)allocator::clone_any(allocator, self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the last value, pop it and return the copy of it.
|
||||
|
||||
@return "A temp copy of the last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.tcopy_pop(&self) => self.copy_pop(tmem);
|
||||
|
||||
<*
|
||||
Pop the last value. It must later be released using `list.free_element()`.
|
||||
|
||||
@return "The last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.pop_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Remove all elements in the list.
|
||||
*>
|
||||
fn void InterfaceList.clear(&self)
|
||||
{
|
||||
for (usz i = 0; i < self.size; i++)
|
||||
{
|
||||
self.free_element(self.entries[i]);
|
||||
}
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the first value. It must later be released using `list.free_element()`.
|
||||
|
||||
@return "The first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.pop_first_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the first value, pop it and return the copy of it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use for copying"
|
||||
@return "A copy of the first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.copy_pop_first(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
defer self.remove_at(0);
|
||||
return (Type)allocator::clone_any(allocator, self.entries[0]);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the first value, pop it and return the temp copy of it.
|
||||
|
||||
@return "A temp copy of the first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.tcopy_pop_first(&self) => self.copy_pop_first(tmem);
|
||||
|
||||
<*
|
||||
Remove the element at the particular index.
|
||||
|
||||
@param index : "The index of the element to remove"
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void InterfaceList.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];
|
||||
}
|
||||
|
||||
<*
|
||||
Add all the elements in another InterfaceList.
|
||||
|
||||
@param [&in] other_list : "The list to add"
|
||||
*>
|
||||
fn void InterfaceList.add_all(&self, InterfaceList* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
self.reserve(other_list.size);
|
||||
foreach (value : other_list)
|
||||
{
|
||||
self.entries[self.size++] = (Type)allocator::clone_any(self.allocator, value);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the order of the elements in the list.
|
||||
*>
|
||||
fn void InterfaceList.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);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Return a view of the data as a slice.
|
||||
|
||||
@return "The slice view"
|
||||
*>
|
||||
fn Type[] InterfaceList.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Push an element to the front of the list.
|
||||
|
||||
@param value : "The value to push to the list"
|
||||
@require $defined(Type t = &value) : "Value must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.push_front(&self, value)
|
||||
{
|
||||
self.insert_at(0, value);
|
||||
}
|
||||
|
||||
<*
|
||||
Insert an element at a particular index.
|
||||
|
||||
@param index : "the index where the element should be inserted"
|
||||
@param type : "the value to insert"
|
||||
@require index <= self.size : "The index is out of bounds"
|
||||
@require $defined(Type t = &type) : "Type must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.insert_at(&self, usz index, type)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(type);
|
||||
return;
|
||||
}
|
||||
Type value = allocator::clone(self.allocator, type);
|
||||
self._insert_at(self, index, value);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove the last element in the list. The list may not be empty.
|
||||
|
||||
@require self.size > 0 : "The list was already empty"
|
||||
*>
|
||||
fn void InterfaceList.remove_last(&self)
|
||||
{
|
||||
self.free_element(self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove the first element in the list, the list may not be empty.
|
||||
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn void InterfaceList.remove_first(&self)
|
||||
{
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
<*
|
||||
Return the first element
|
||||
|
||||
@return "The first element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.first(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[0] : NO_MORE_ELEMENT~;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the last element
|
||||
|
||||
@return "The last element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.last(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT~;
|
||||
}
|
||||
|
||||
<*
|
||||
Return whether the list is empty.
|
||||
|
||||
@return "True if the list is empty"
|
||||
*>
|
||||
fn bool InterfaceList.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the length of the list.
|
||||
|
||||
@return "The number of elements in the list"
|
||||
*>
|
||||
fn usz InterfaceList.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Return an element in the list.
|
||||
|
||||
@param index : "The index of the element to retrieve"
|
||||
@return "The element at the index"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
@require index < self.size : "Index out of range"
|
||||
*>
|
||||
fn Type InterfaceList.get(&self, usz index) @inline @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
<*
|
||||
Completely free and clear a list.
|
||||
*>
|
||||
fn void InterfaceList.free(&self)
|
||||
{
|
||||
if (!self.allocator) return;
|
||||
self.clear();
|
||||
allocator::free(self.allocator, self.entries);
|
||||
self.capacity = 0;
|
||||
self.entries = null;
|
||||
}
|
||||
|
||||
<*
|
||||
Swap two elements in a list.
|
||||
|
||||
@param i : "Index of one of the elements"
|
||||
@param j : "Index of the other element"
|
||||
@require i < self.size : "The first index is out of range"
|
||||
@require j < self.size : "The second index is out of range"
|
||||
*>
|
||||
fn void InterfaceList.swap(&self, usz i, usz j)
|
||||
{
|
||||
Type temp = self.entries[i];
|
||||
self.entries[i] = self.entries[j];
|
||||
self.entries[j] = temp;
|
||||
}
|
||||
|
||||
<*
|
||||
Print the list to a formatter.
|
||||
*>
|
||||
fn usz? InterfaceList.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;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Remove Type elements matching the predicate.
|
||||
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.remove_if(&self, InterfacePredicate filter)
|
||||
{
|
||||
return interfacelist_remove_if(self, filter, false);
|
||||
}
|
||||
|
||||
<*
|
||||
Retain the elements matching the predicate.
|
||||
|
||||
@param selection : "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.retain_if(&self, InterfacePredicate selection)
|
||||
{
|
||||
return interfacelist_remove_if(self, selection, true);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove Type elements matching the predicate.
|
||||
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@param context : "The context to the function"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.remove_using_test(&self, InterfaceTest filter, Type context)
|
||||
{
|
||||
return interfacelist_remove_using_test(self, filter, false, context);
|
||||
}
|
||||
|
||||
<*
|
||||
Retain Type elements matching the predicate.
|
||||
|
||||
@param selection : "The function to determine if it should be retained or not"
|
||||
@param context : "The context to the function"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.retain_using_test(&self, InterfaceTest selection, Type context)
|
||||
{
|
||||
return interfacelist_remove_using_test(self, selection, true, context);
|
||||
}
|
||||
|
||||
<*
|
||||
Reserve memory so that at least the `min_capacity` exists.
|
||||
|
||||
@param min_capacity : "The min capacity to hold"
|
||||
*>
|
||||
fn void InterfaceList.reserve(&self, usz min_capacity)
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity);
|
||||
self.capacity = min_capacity;
|
||||
}
|
||||
|
||||
<*
|
||||
Set the element at Type index.
|
||||
|
||||
@param index : "The index where to set the value."
|
||||
@param value : "The value to set"
|
||||
@require index <= self.size : "Index out of range"
|
||||
@require $defined(Type t = &value) : "Value must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.set(&self, usz index, value)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(value);
|
||||
return;
|
||||
}
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index] = allocator::clone(self.allocator, value);
|
||||
}
|
||||
|
||||
// -- private
|
||||
|
||||
fn void interfacelist_ensure_capacity(InterfaceList* 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);
|
||||
}
|
||||
|
||||
fn void interfacelist_append(InterfaceList* self, Type element) @local
|
||||
{
|
||||
interfacelist_ensure_capacity(self);
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void interfacelist_insert_at(InterfaceList* self, usz index, Type value) @local
|
||||
{
|
||||
interfacelist_ensure_capacity(self);
|
||||
for (usz i = self.size; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.size++;
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
macro usz interfacelist_remove_using_test(InterfaceList* self, InterfaceTest 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;
|
||||
}
|
||||
|
||||
macro usz interfacelist_remove_if(InterfaceList* self, InterfacePredicate 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;
|
||||
}
|
||||
333
lib/std/collections/linked_blockingqueue.c3
Normal file
333
lib/std/collections/linked_blockingqueue.c3
Normal file
@@ -0,0 +1,333 @@
|
||||
module std::collections::blockingqueue <Value>;
|
||||
import std::thread, std::time;
|
||||
|
||||
|
||||
const INITIAL_CAPACITY = 16;
|
||||
|
||||
struct QueueEntry
|
||||
{
|
||||
Value value;
|
||||
<* Next in queue order *>
|
||||
QueueEntry* next;
|
||||
<* Previous in queue order *>
|
||||
QueueEntry* prev;
|
||||
}
|
||||
|
||||
struct LinkedBlockingQueue
|
||||
{
|
||||
<* First element in queue *>
|
||||
QueueEntry* head;
|
||||
<* Last element in queue *>
|
||||
QueueEntry* tail;
|
||||
<* Current number of elements *>
|
||||
usz count;
|
||||
<* Maximum capacity (0 for unbounded) *>
|
||||
usz capacity;
|
||||
Mutex lock;
|
||||
ConditionVariable not_empty;
|
||||
ConditionVariable not_full;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param capacity : "Maximum capacity (0 for unbounded)"
|
||||
@require !self.is_initialized() : "Queue was already initialized"
|
||||
*>
|
||||
fn LinkedBlockingQueue* LinkedBlockingQueue.init(&self, Allocator allocator, usz capacity = 0)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.capacity = capacity;
|
||||
self.count = 0;
|
||||
self.head = null;
|
||||
self.tail = null;
|
||||
|
||||
self.lock.init()!!;
|
||||
self.not_empty.init()!!;
|
||||
self.not_full.init()!!;
|
||||
return self;
|
||||
}
|
||||
|
||||
fn LinkedBlockingQueue* LinkedBlockingQueue.tinit(&self, usz capacity = 0)
|
||||
{
|
||||
return self.init(tmem, capacity) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
*>
|
||||
fn void LinkedBlockingQueue.free(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
// Free all remaining entries
|
||||
QueueEntry* entry = self.head;
|
||||
while (entry != null)
|
||||
{
|
||||
QueueEntry* next = entry.next;
|
||||
allocator::free(self.allocator, entry);
|
||||
entry = next;
|
||||
}
|
||||
};
|
||||
|
||||
self.lock.destroy();
|
||||
self.not_empty.destroy();
|
||||
self.not_full.destroy();
|
||||
}
|
||||
|
||||
fn void linkedblockingqueue_link_entry(LinkedBlockingQueue* self, QueueEntry* entry) @private
|
||||
{
|
||||
entry.next = null;
|
||||
entry.prev = self.tail;
|
||||
|
||||
if (self.tail == null)
|
||||
{
|
||||
// First element in queue
|
||||
self.head = entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append to tail
|
||||
self.tail.next = entry;
|
||||
}
|
||||
self.tail = entry;
|
||||
self.count++;
|
||||
}
|
||||
|
||||
|
||||
fn QueueEntry* linkedblockingqueue_unlink_head(LinkedBlockingQueue* self) @private
|
||||
{
|
||||
if (self.head == null) return null;
|
||||
|
||||
QueueEntry* entry = self.head;
|
||||
self.head = entry.next;
|
||||
|
||||
if (self.head != null)
|
||||
{
|
||||
self.head.prev = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queue is now empty
|
||||
self.tail = null;
|
||||
}
|
||||
|
||||
self.count--;
|
||||
return entry;
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "Value to add to the queue"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
*>
|
||||
fn void LinkedBlockingQueue.push(&self, Value value)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
while (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
self.not_full.wait(&self.lock);
|
||||
}
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
linkedblockingqueue_link_entry(self, entry);
|
||||
|
||||
// Signal that queue is no longer empty
|
||||
self.not_empty.signal();
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Get a value from the queue, blocking if there is no element in the queue.
|
||||
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value"
|
||||
*>
|
||||
fn Value LinkedBlockingQueue.poll(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
while (self.count == 0)
|
||||
{
|
||||
self.not_empty.wait(&self.lock);
|
||||
}
|
||||
|
||||
QueueEntry* entry = linkedblockingqueue_unlink_head(self);
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Pop an element from the queue, fail is it is empty.
|
||||
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value"
|
||||
@return? NO_MORE_ELEMENT : "If the queue is empty"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.pop(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.count == 0) return NO_MORE_ELEMENT~;
|
||||
|
||||
QueueEntry* entry = linkedblockingqueue_unlink_head(self);
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Poll with a timeout.
|
||||
|
||||
@param timeout : "Timeout in microseconds"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value or null if timeout occurred"
|
||||
@return? NO_MORE_ELEMENT : "If we reached the timeout"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.poll_timeout(&self, Duration timeout)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
// Use while loop to handle spurious wakeups
|
||||
if (!self.count)
|
||||
{
|
||||
Time start = time::now();
|
||||
Time end = start + timeout;
|
||||
while (!self.count)
|
||||
{
|
||||
if (end <= time::now()) break;
|
||||
if (catch self.not_empty.wait_until(&self.lock, end)) break;
|
||||
}
|
||||
if (!self.count) return NO_MORE_ELEMENT~;
|
||||
}
|
||||
|
||||
QueueEntry* entry = linkedblockingqueue_unlink_head(self);
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
|
||||
// Must signal not_full after removing an item
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "Current size of the queue"
|
||||
*>
|
||||
fn usz LinkedBlockingQueue.size(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return self.count;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "True if queue is empty"
|
||||
*>
|
||||
fn bool LinkedBlockingQueue.is_empty(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return self.count == 0;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Try to push, return CAPACITY_EXCEEDED if the queue is full.
|
||||
|
||||
@param value : "Value to add to the queue"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return? CAPACITY_EXCEEDED : "If the queue is full"
|
||||
*>
|
||||
fn void? LinkedBlockingQueue.try_push(&self, Value value)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED~;
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
linkedblockingqueue_link_entry(self, entry);
|
||||
self.not_empty.signal();
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Try to push, return CAPACITY_EXCEEDED if the queue is still full after timeout is reached.
|
||||
|
||||
@param value : "Value to add to the queue"
|
||||
@param timeout : "Timeout in microseconds"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return? CAPACITY_EXCEEDED : "If the queue is full"
|
||||
*>
|
||||
fn void? LinkedBlockingQueue.push_timeout(&self, Value value, Duration timeout)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
Time start = time::now();
|
||||
Time end = start + timeout;
|
||||
while (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
if (end <= time::now()) break;
|
||||
if (catch self.not_empty.wait_until(&self.lock, end)) break;
|
||||
}
|
||||
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED~;
|
||||
}
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
linkedblockingqueue_link_entry(self, entry);
|
||||
self.not_empty.signal();
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The head value or NO_MORE_ELEMENT~ if queue is empty"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.peek(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return (self.head != null) ? self.head.value : NO_MORE_ELEMENT~;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@return "True if queue is initialized"
|
||||
*>
|
||||
fn bool LinkedBlockingQueue.is_initialized(&self)
|
||||
{
|
||||
return self.allocator && self.lock.initialized;
|
||||
}
|
||||
651
lib/std/collections/linked_hashmap.c3
Normal file
651
lib/std/collections/linked_hashmap.c3
Normal file
@@ -0,0 +1,651 @@
|
||||
// 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;
|
||||
import std::io @norecurse;
|
||||
|
||||
const LinkedHashMap LINKEDONHEAP = { .allocator = MAP_HEAP_ALLOCATOR };
|
||||
|
||||
struct LinkedEntry
|
||||
{
|
||||
uint hash;
|
||||
Key key;
|
||||
Value value;
|
||||
<* For bucket chain *>
|
||||
LinkedEntry* next;
|
||||
<* Previous in insertion order *>
|
||||
LinkedEntry* before;
|
||||
<* Next in insertion order *>
|
||||
LinkedEntry* after;
|
||||
}
|
||||
|
||||
struct LinkedHashMap (Printable)
|
||||
{
|
||||
LinkedEntry*[] table;
|
||||
Allocator allocator;
|
||||
usz count;
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
<* First inserted LinkedEntry *>
|
||||
LinkedEntry* head;
|
||||
<* Last inserted LinkedEntry *>
|
||||
LinkedEntry* tail;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@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.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.init(&self, Allocator allocator, usz 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 = (usz)(capacity * load_factor);
|
||||
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
|
||||
self.head = null;
|
||||
self.tail = null;
|
||||
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.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init(tmem, 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.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro LinkedHashMap* LinkedHashMap.init_with_key_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
$for var $i = 0; $i < $vacount; $i += 2:
|
||||
self.set($vaarg[$i], $vaarg[$i + 1]);
|
||||
$endfor
|
||||
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.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro LinkedHashMap* LinkedHashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys : "The keys for the LinkedHashMap entries"
|
||||
@param [in] values : "The values for the LinkedHashMap 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.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
assert(keys.len == values.len);
|
||||
self.init(allocator, capacity, load_factor);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
self.set(keys[i], values[i]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys : "The keys for the LinkedHashMap entries"
|
||||
@param [in] values : "The values for the LinkedHashMap entries"
|
||||
@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.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_from_keys_and_values(tmem, keys, values, capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
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 LinkedHashMap.is_initialized(&map)
|
||||
{
|
||||
return map.allocator && map.allocator.ptr != &dummy;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param [&in] other_map : "The map to copy from."
|
||||
@require !self.is_initialized() : "Map was already initialized"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.init_from_map(&self, Allocator allocator, LinkedHashMap* other_map)
|
||||
{
|
||||
self.init(allocator, other_map.table.len, other_map.load_factor);
|
||||
linkedhashmap_put_all_for_create(self, other_map);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_map : "The map to copy from."
|
||||
@require !map.is_initialized() : "Map was already initialized"
|
||||
*>
|
||||
fn LinkedHashMap* LinkedHashMap.tinit_from_map(&map, LinkedHashMap* other_map)
|
||||
{
|
||||
return map.init_from_map(tmem, other_map) @inline;
|
||||
}
|
||||
|
||||
fn bool LinkedHashMap.is_empty(&map) @inline
|
||||
{
|
||||
return !map.count;
|
||||
}
|
||||
|
||||
fn usz LinkedHashMap.len(&map) @inline => map.count;
|
||||
|
||||
fn Value*? LinkedHashMap.get_ref(&map, Key key)
|
||||
{
|
||||
if (!map.count) return NOT_FOUND~;
|
||||
uint hash = rehash(key.hash());
|
||||
for (LinkedEntry *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 NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn LinkedEntry*? LinkedHashMap.get_entry(&map, Key key)
|
||||
{
|
||||
if (!map.count) return NOT_FOUND~;
|
||||
uint hash = rehash(key.hash());
|
||||
for (LinkedEntry *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 NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value or set it to the value
|
||||
|
||||
@require $defined(Value val = #expr)
|
||||
*>
|
||||
macro Value LinkedHashMap.@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 (LinkedEntry *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? LinkedHashMap.get(&map, Key key) @operator([]) => *map.get_ref(key) @inline;
|
||||
|
||||
fn bool LinkedHashMap.has_key(&map, Key key) => @ok(map.get_ref(key));
|
||||
|
||||
fn bool LinkedHashMap.set(&map, Key key, Value value) @operator([]=)
|
||||
{
|
||||
// If the map isn't initialized, use the defaults to initialize it.
|
||||
switch (map.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
map.init(mem);
|
||||
case null:
|
||||
map.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (LinkedEntry *e = map.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
e.value = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
linkedhashmap_add_entry(map, hash, key, value, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void? LinkedHashMap.remove(&map, Key key) @maydiscard
|
||||
{
|
||||
if (!linkedhashmap_remove_entry_for_key(map, key)) return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.clear(&map)
|
||||
{
|
||||
if (!map.count) return;
|
||||
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
LinkedEntry* next = entry.after;
|
||||
linkedhashmap_free_entry(map, entry);
|
||||
entry = next;
|
||||
}
|
||||
|
||||
foreach (LinkedEntry** &bucket : map.table)
|
||||
{
|
||||
*bucket = null;
|
||||
}
|
||||
|
||||
map.count = 0;
|
||||
map.head = null;
|
||||
map.tail = null;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.free(&map)
|
||||
{
|
||||
if (!map.is_initialized()) return;
|
||||
map.clear();
|
||||
linkedhashmap_free_internal(map, map.table.ptr);
|
||||
map.table = {};
|
||||
}
|
||||
|
||||
fn Key[] LinkedHashMap.tkeys(&self)
|
||||
{
|
||||
return self.keys(tmem) @inline;
|
||||
}
|
||||
|
||||
fn Key[] LinkedHashMap.keys(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
|
||||
Key[] list = allocator::alloc_array(allocator, Key, self.count);
|
||||
usz index = 0;
|
||||
|
||||
LinkedEntry* entry = self.head;
|
||||
while (entry)
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
list[index++] = entry.key.copy(allocator);
|
||||
$else
|
||||
list[index++] = entry.key;
|
||||
$endif
|
||||
entry = entry.after;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
macro LinkedHashMap.@each(map; @body(key, value))
|
||||
{
|
||||
map.@each_entry(; LinkedEntry* entry)
|
||||
{
|
||||
@body(entry.key, entry.value);
|
||||
};
|
||||
}
|
||||
|
||||
macro LinkedHashMap.@each_entry(map; @body(entry))
|
||||
{
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
@body(entry);
|
||||
entry = entry.after;
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] LinkedHashMap.tvalues(&map) => map.values(tmem) @inline;
|
||||
|
||||
fn Value[] LinkedHashMap.values(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, self.count);
|
||||
usz index = 0;
|
||||
LinkedEntry* entry = self.head;
|
||||
while (entry)
|
||||
{
|
||||
list[index++] = entry.value;
|
||||
entry = entry.after;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
fn bool LinkedHashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
|
||||
{
|
||||
if (!map.count) return false;
|
||||
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
if (equals(v, entry.value)) return true;
|
||||
entry = entry.after;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn LinkedHashMapIterator LinkedHashMap.iter(&self) => { .map = self, .current = self.head, .started = false };
|
||||
|
||||
fn LinkedHashMapValueIterator LinkedHashMap.value_iter(&self) => { .map = self, .current = self.head, .started = false };
|
||||
|
||||
fn LinkedHashMapKeyIterator LinkedHashMap.key_iter(&self) => { .map = self, .current = self.head, .started = false };
|
||||
|
||||
fn bool LinkedHashMapIterator.next(&self)
|
||||
{
|
||||
if (!self.started)
|
||||
{
|
||||
self.current = self.map.head;
|
||||
self.started = true;
|
||||
}
|
||||
else if (self.current)
|
||||
{
|
||||
self.current = self.current.after;
|
||||
}
|
||||
return self.current != null;
|
||||
}
|
||||
|
||||
fn LinkedEntry*? LinkedHashMapIterator.get(&self)
|
||||
{
|
||||
return self.current ? self.current : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn Value*? LinkedHashMapValueIterator.get(&self)
|
||||
{
|
||||
return self.current ? &self.current.value : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn Key*? LinkedHashMapKeyIterator.get(&self)
|
||||
{
|
||||
return self.current ? &self.current.key : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool LinkedHashMapIterator.has_next(&self)
|
||||
{
|
||||
if (!self.started) return self.map.head != null;
|
||||
return self.current && self.current.after != null;
|
||||
}
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void linkedhashmap_add_entry(LinkedHashMap* map, uint hash, Key key, Value value, uint bucket_index) @private
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
|
||||
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.key = key,
|
||||
.value = value,
|
||||
.next = map.table[bucket_index],
|
||||
.before = map.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
// Update bucket chain
|
||||
map.table[bucket_index] = entry;
|
||||
|
||||
// Update linked list
|
||||
if (map.tail)
|
||||
{
|
||||
map.tail.after = entry;
|
||||
entry.before = map.tail;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.head = entry;
|
||||
}
|
||||
map.tail = entry;
|
||||
|
||||
if (map.count++ >= map.threshold)
|
||||
{
|
||||
linkedhashmap_resize(map, map.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void linkedhashmap_resize(LinkedHashMap* map, uint new_capacity) @private
|
||||
{
|
||||
LinkedEntry*[] old_table = map.table;
|
||||
uint old_capacity = old_table.len;
|
||||
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
map.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedEntry*[] new_table = allocator::new_array(map.allocator, LinkedEntry*, new_capacity);
|
||||
map.table = new_table;
|
||||
map.threshold = (uint)(new_capacity * map.load_factor);
|
||||
|
||||
// Rehash all entries - linked list order remains unchanged
|
||||
foreach (uint i, LinkedEntry *e : old_table)
|
||||
{
|
||||
if (!e) continue;
|
||||
|
||||
// Split the bucket chain into two chains based on new bit
|
||||
LinkedEntry* lo_head = null;
|
||||
LinkedEntry* lo_tail = null;
|
||||
LinkedEntry* hi_head = null;
|
||||
LinkedEntry* hi_tail = null;
|
||||
|
||||
do
|
||||
{
|
||||
LinkedEntry* next = e.next;
|
||||
if ((e.hash & old_capacity) == 0)
|
||||
{
|
||||
if (!lo_tail)
|
||||
{
|
||||
lo_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
lo_tail.next = e;
|
||||
}
|
||||
lo_tail = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!hi_tail)
|
||||
{
|
||||
hi_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
hi_tail.next = e;
|
||||
}
|
||||
hi_tail = e;
|
||||
}
|
||||
e.next = null;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
|
||||
if (lo_tail)
|
||||
{
|
||||
lo_tail.next = null;
|
||||
new_table[i] = lo_head;
|
||||
}
|
||||
if (hi_tail)
|
||||
{
|
||||
hi_tail.next = null;
|
||||
new_table[i + old_capacity] = hi_head;
|
||||
}
|
||||
}
|
||||
|
||||
linkedhashmap_free_internal(map, old_table.ptr);
|
||||
}
|
||||
|
||||
fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len;
|
||||
len += f.print("{ ")!;
|
||||
self.@each_entry(; LinkedEntry* entry)
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s: %s", entry.key, entry.value)!;
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
fn void linkedhashmap_transfer(LinkedHashMap* map, LinkedEntry*[] new_table) @private
|
||||
{
|
||||
LinkedEntry*[] src = map.table;
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, LinkedEntry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
LinkedEntry* 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 linkedhashmap_put_all_for_create(LinkedHashMap* map, LinkedHashMap* other_map) @private
|
||||
{
|
||||
if (!other_map.count) return;
|
||||
other_map.@each(; Key key, Value value) {
|
||||
map.set(key, value);
|
||||
};
|
||||
}
|
||||
|
||||
fn void linkedhashmap_put_for_create(LinkedHashMap* map, Key key, Value value) @private
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
for (LinkedEntry *e = map.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
e.value = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
linkedhashmap_create_entry(map, hash, key, value, i);
|
||||
}
|
||||
|
||||
fn void linkedhashmap_free_internal(LinkedHashMap* map, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(map.allocator, ptr);
|
||||
}
|
||||
|
||||
fn bool linkedhashmap_remove_entry_for_key(LinkedHashMap* map, Key key) @private
|
||||
{
|
||||
if (!map.count) return false;
|
||||
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
LinkedEntry* prev = null;
|
||||
LinkedEntry* e = map.table[i];
|
||||
|
||||
while (e)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
if (prev)
|
||||
{
|
||||
prev.next = e.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.table[i] = e.next;
|
||||
}
|
||||
|
||||
if (e.before)
|
||||
{
|
||||
e.before.after = e.after;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.head = e.after;
|
||||
}
|
||||
|
||||
if (e.after)
|
||||
{
|
||||
e.after.before = e.before;
|
||||
}
|
||||
else
|
||||
{
|
||||
map.tail = e.before;
|
||||
}
|
||||
|
||||
map.count--;
|
||||
linkedhashmap_free_entry(map, e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = e.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void linkedhashmap_create_entry(LinkedHashMap* map, uint hash, Key key, Value value, int bucket_index) @private
|
||||
{
|
||||
LinkedEntry *e = map.table[bucket_index];
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
|
||||
map.table[bucket_index] = entry;
|
||||
map.count++;
|
||||
}
|
||||
|
||||
fn void linkedhashmap_free_entry(LinkedHashMap* self, LinkedEntry *entry) @local
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
allocator::free(self.allocator, entry.key);
|
||||
$endif
|
||||
linkedhashmap_free_internal(self, entry);
|
||||
}
|
||||
|
||||
|
||||
struct LinkedHashMapIterator
|
||||
{
|
||||
LinkedHashMap* map;
|
||||
LinkedEntry* current;
|
||||
bool started;
|
||||
}
|
||||
|
||||
typedef LinkedHashMapValueIterator = inline LinkedHashMapIterator;
|
||||
typedef LinkedHashMapKeyIterator = inline LinkedHashMapIterator;
|
||||
|
||||
fn usz LinkedHashMapValueIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz LinkedHashMapKeyIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz LinkedHashMapIterator.len(self) @operator(len) => self.map.count;
|
||||
|
||||
int dummy @local;
|
||||
730
lib/std/collections/linked_hashset.c3
Normal file
730
lib/std/collections/linked_hashset.c3
Normal file
@@ -0,0 +1,730 @@
|
||||
<*
|
||||
@require $defined((Value){}.hash()) : `No .hash function found on the value`
|
||||
*>
|
||||
module std::collections::set <Value>;
|
||||
import std::math;
|
||||
import std::io @norecurse;
|
||||
|
||||
const LinkedHashSet LINKEDONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
|
||||
|
||||
struct LinkedEntry
|
||||
{
|
||||
uint hash;
|
||||
Value value;
|
||||
<* For bucket chain *>
|
||||
LinkedEntry* next;
|
||||
<* Previous in insertion order *>
|
||||
LinkedEntry* before;
|
||||
<* Next in insertion order *>
|
||||
LinkedEntry* after;
|
||||
}
|
||||
|
||||
struct LinkedHashSet (Printable)
|
||||
{
|
||||
LinkedEntry*[] table;
|
||||
Allocator allocator;
|
||||
<* Number of elements *>
|
||||
usz count;
|
||||
<* Resize limit *>
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
<* Resize limit *>
|
||||
LinkedEntry* head;
|
||||
<* First inserted LinkedEntry *>
|
||||
LinkedEntry* tail;
|
||||
}
|
||||
|
||||
fn usz LinkedHashSet.len(&self) @operator(len) => self.count;
|
||||
|
||||
<*
|
||||
@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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
self.allocator = allocator;
|
||||
self.threshold = (usz)(capacity * load_factor);
|
||||
self.load_factor = load_factor;
|
||||
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
|
||||
|
||||
self.head = null;
|
||||
self.tail = null;
|
||||
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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init(tmem, capacity, load_factor) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro LinkedHashSet* LinkedHashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
self.add($vaarg[$i]);
|
||||
$endfor
|
||||
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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro LinkedHashSet* LinkedHashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the LinkedHashSet"
|
||||
@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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.init(allocator, capacity, load_factor);
|
||||
foreach (v : values) self.add(v);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] values : "The values for the LinkedHashSet entries"
|
||||
@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.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init_from_values(tmem, values, capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
Has this linked hash set been initialized yet?
|
||||
|
||||
@param [&in] set : "The linked hash set we are testing"
|
||||
@return "Returns true if it has been initialized, false otherwise"
|
||||
*>
|
||||
fn bool LinkedHashSet.is_initialized(&set)
|
||||
{
|
||||
return set.allocator && set.allocator.ptr != &dummy;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.init_from_set(&self, Allocator allocator, LinkedHashSet* other_set)
|
||||
{
|
||||
self.init(allocator, other_set.table.len, other_set.load_factor);
|
||||
LinkedEntry* entry = other_set.head;
|
||||
while (entry) // Save insertion order
|
||||
{
|
||||
linkedhashset_put_for_create(self, entry.value);
|
||||
entry = entry.after;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_set : "The set to copy from."
|
||||
@require !set.is_initialized() : "Set was already initialized"
|
||||
*>
|
||||
fn LinkedHashSet* LinkedHashSet.tinit_from_set(&set, LinkedHashSet* other_set)
|
||||
{
|
||||
return set.init_from_set(tmem, other_set) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set is empty
|
||||
|
||||
@return "true if it is empty"
|
||||
@pure
|
||||
*>
|
||||
fn bool LinkedHashSet.is_empty(&set) @inline
|
||||
{
|
||||
return !set.count;
|
||||
}
|
||||
|
||||
<*
|
||||
Add all elements in the slice to the set.
|
||||
|
||||
@param [in] list
|
||||
@return "The number of new elements added"
|
||||
@ensure total <= list.len
|
||||
*>
|
||||
fn usz LinkedHashSet.add_all(&set, Value[] list)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : list)
|
||||
{
|
||||
if (set.add(v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other
|
||||
@return "The number of new elements added"
|
||||
@ensure return <= other.count
|
||||
*>
|
||||
fn usz LinkedHashSet.add_all_from(&set, LinkedHashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (set.add(value)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to add"
|
||||
@return "true if the value didn't exist in the set"
|
||||
*>
|
||||
fn bool LinkedHashSet.add(&set, Value value)
|
||||
{
|
||||
// If the set isn't initialized, use the defaults to initialize it.
|
||||
switch (set.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
set.init(mem);
|
||||
case null:
|
||||
set.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint hash = rehash(value.hash());
|
||||
uint index = index_for(hash, set.table.len);
|
||||
for (LinkedEntry *e = set.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return false;
|
||||
}
|
||||
linkedhashset_add_entry(set, hash, value, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Iterate over all the values in the set
|
||||
*>
|
||||
macro LinkedHashSet.@each(set; @body(value))
|
||||
{
|
||||
if (!set.count) return;
|
||||
LinkedEntry* entry = set.head;
|
||||
while (entry)
|
||||
{
|
||||
@body(entry.value);
|
||||
entry = entry.after;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the set contains the given value.
|
||||
|
||||
@param value : "The value to check"
|
||||
@return "true if it exists in the set"
|
||||
*>
|
||||
fn bool LinkedHashSet.contains(&set, Value value)
|
||||
{
|
||||
if (!set.count) return false;
|
||||
uint hash = rehash(value.hash());
|
||||
for (LinkedEntry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
Remove a single value from the set.
|
||||
|
||||
@param value : "The value to remove"
|
||||
@return? NOT_FOUND : "If the entry is not found"
|
||||
*>
|
||||
fn void? LinkedHashSet.remove(&set, Value value) @maydiscard
|
||||
{
|
||||
if (!linkedhashset_remove_entry_for_value(set, value)) return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz LinkedHashSet.remove_all(&set, Value[] values)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : values)
|
||||
{
|
||||
if (linkedhashset_remove_entry_for_value(set, v)) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other : "Other set"
|
||||
*>
|
||||
fn usz LinkedHashSet.remove_all_from(&set, LinkedHashSet* other)
|
||||
{
|
||||
usz total;
|
||||
other.@each(;Value val)
|
||||
{
|
||||
if (linkedhashset_remove_entry_for_value(set, val)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
Free all memory allocated by the hash set.
|
||||
*>
|
||||
fn void LinkedHashSet.free(&set)
|
||||
{
|
||||
if (!set.is_initialized()) return;
|
||||
set.clear();
|
||||
linkedhashset_free_internal(set, set.table.ptr);
|
||||
set.table = {};
|
||||
}
|
||||
|
||||
<*
|
||||
Clear all elements from the set while keeping the underlying storage
|
||||
|
||||
@ensure set.count == 0
|
||||
*>
|
||||
fn void LinkedHashSet.clear(&set)
|
||||
{
|
||||
if (!set.count) return;
|
||||
|
||||
LinkedEntry* entry = set.head;
|
||||
while (entry)
|
||||
{
|
||||
LinkedEntry* next = entry.after;
|
||||
linkedhashset_free_entry(set, entry);
|
||||
entry = next;
|
||||
}
|
||||
|
||||
foreach (LinkedEntry** &bucket : set.table)
|
||||
{
|
||||
*bucket = null;
|
||||
}
|
||||
|
||||
set.count = 0;
|
||||
set.head = null;
|
||||
set.tail = null;
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.reserve(&set, usz capacity)
|
||||
{
|
||||
if (capacity > set.threshold)
|
||||
{
|
||||
linkedhashset_resize(set, math::next_power_of_2(capacity));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Set Operations ---
|
||||
|
||||
<*
|
||||
Returns the union of two sets (A | B)
|
||||
|
||||
@param [&in] other : "The other set to union with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the union of both sets"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.set_union(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
usz new_capacity = math::next_power_of_2(self.count + other.count);
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, new_capacity, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
result.add_all_from(other);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tset_union(&self, LinkedHashSet* other) => self.set_union(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Returns the intersection of the two sets (A & B)
|
||||
|
||||
@param [&in] other : "The other set to intersect with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing the intersection of both sets"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.intersection(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
|
||||
|
||||
// Iterate through the smaller set for efficiency
|
||||
LinkedHashSet* smaller = self.count <= other.count ? self : other;
|
||||
LinkedHashSet* larger = self.count > other.count ? self : other;
|
||||
|
||||
smaller.@each(;Value value)
|
||||
{
|
||||
if (larger.contains(value)) result.add(value);
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tintersection(&self, LinkedHashSet* other) => self.intersection(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Return this set - other, so (A & ~B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set but not in the other"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.difference(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
self.@each(;Value value)
|
||||
{
|
||||
if (!other.contains(value))
|
||||
{
|
||||
result.add(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tdifference(&self, LinkedHashSet* other) => self.difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Return (A ^ B)
|
||||
|
||||
@param [&in] other : "The other set to compare with"
|
||||
@param [&inout] allocator : "Allocator for the new set"
|
||||
@return "A new set containing elements in this set or the other, but not both"
|
||||
*>
|
||||
fn LinkedHashSet LinkedHashSet.symmetric_difference(&self, Allocator allocator, LinkedHashSet* other)
|
||||
{
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, self.table.len, self.load_factor);
|
||||
result.add_all_from(self);
|
||||
other.@each(;Value value)
|
||||
{
|
||||
if (!result.add(value))
|
||||
{
|
||||
result.remove(value);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn LinkedHashSet LinkedHashSet.tsymmetric_difference(&self, LinkedHashSet* other) => self.symmetric_difference(tmem, other) @inline;
|
||||
|
||||
<*
|
||||
Check if this hash set is a subset of another set.
|
||||
|
||||
@param [&in] other : "The other set to check against"
|
||||
@return "True if all elements of this set are in the other set"
|
||||
*>
|
||||
fn bool LinkedHashSet.is_subset(&self, LinkedHashSet* other)
|
||||
{
|
||||
if (self.count == 0) return true;
|
||||
if (self.count > other.count) return false;
|
||||
|
||||
self.@each(; Value value) {
|
||||
if (!other.contains(value)) return false;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void linkedhashset_add_entry(LinkedHashSet* set, uint hash, Value value, uint bucket_index) @private
|
||||
{
|
||||
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.value = value,
|
||||
.next = set.table[bucket_index],
|
||||
.before = set.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
// Update bucket chain
|
||||
set.table[bucket_index] = entry;
|
||||
|
||||
// Update linked list
|
||||
if (set.tail)
|
||||
{
|
||||
set.tail.after = entry;
|
||||
entry.before = set.tail;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.head = entry;
|
||||
}
|
||||
set.tail = entry;
|
||||
|
||||
if (set.count++ >= set.threshold)
|
||||
{
|
||||
linkedhashset_resize(set, set.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void linkedhashset_resize(LinkedHashSet* set, usz new_capacity) @private
|
||||
{
|
||||
LinkedEntry*[] old_table = set.table;
|
||||
usz old_capacity = old_table.len;
|
||||
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
set.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedEntry*[] new_table = allocator::new_array(set.allocator, LinkedEntry*, new_capacity);
|
||||
set.table = new_table;
|
||||
set.threshold = (uint)(new_capacity * set.load_factor);
|
||||
|
||||
// Rehash all entries - linked list order remains unchanged
|
||||
foreach (uint i, LinkedEntry *e : old_table)
|
||||
{
|
||||
if (!e) continue;
|
||||
|
||||
// Split the bucket chain into two chains based on new bit
|
||||
LinkedEntry* lo_head = null;
|
||||
LinkedEntry* lo_tail = null;
|
||||
LinkedEntry* hi_head = null;
|
||||
LinkedEntry* hi_tail = null;
|
||||
|
||||
do
|
||||
{
|
||||
LinkedEntry* next = e.next;
|
||||
if ((e.hash & old_capacity) == 0)
|
||||
{
|
||||
if (!lo_tail)
|
||||
{
|
||||
lo_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
lo_tail.next = e;
|
||||
}
|
||||
lo_tail = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!hi_tail)
|
||||
{
|
||||
hi_head = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
hi_tail.next = e;
|
||||
}
|
||||
hi_tail = e;
|
||||
}
|
||||
e.next = null;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
|
||||
if (lo_tail)
|
||||
{
|
||||
lo_tail.next = null;
|
||||
new_table[i] = lo_head;
|
||||
}
|
||||
if (hi_tail)
|
||||
{
|
||||
hi_tail.next = null;
|
||||
new_table[i + old_capacity] = hi_head;
|
||||
}
|
||||
}
|
||||
|
||||
linkedhashset_free_internal(set, old_table.ptr);
|
||||
}
|
||||
|
||||
fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len;
|
||||
len += f.print("{ ")!;
|
||||
self.@each(; Value value)
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s", value)!;
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
fn void linked_hashset_transfer(LinkedHashSet* set, LinkedEntry*[] new_table) @private
|
||||
{
|
||||
LinkedEntry*[] src = set.table;
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, LinkedEntry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
LinkedEntry* 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 linkedhashset_put_for_create(LinkedHashSet* set, Value value) @private
|
||||
{
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
for (LinkedEntry *e = set.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
// Value already exists, no need to do anything
|
||||
return;
|
||||
}
|
||||
}
|
||||
linkedhashset_create_entry(set, hash, value, i);
|
||||
}
|
||||
|
||||
fn void linkedhashset_free_internal(LinkedHashSet* set, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(set.allocator, ptr);
|
||||
}
|
||||
|
||||
fn void linkedhashset_create_entry(LinkedHashSet* set, uint hash, Value value, int bucket_index) @private
|
||||
{
|
||||
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.value = value,
|
||||
.next = set.table[bucket_index],
|
||||
.before = set.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
set.table[bucket_index] = entry;
|
||||
|
||||
// Update linked list
|
||||
if (set.tail)
|
||||
{
|
||||
set.tail.after = entry;
|
||||
entry.before = set.tail;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.head = entry;
|
||||
}
|
||||
set.tail = entry;
|
||||
|
||||
set.count++;
|
||||
}
|
||||
|
||||
fn bool linkedhashset_remove_entry_for_value(LinkedHashSet* set, Value value) @private
|
||||
{
|
||||
if (!set.count) return false;
|
||||
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
LinkedEntry* prev = null;
|
||||
LinkedEntry* e = set.table[i];
|
||||
|
||||
while (e)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
{
|
||||
if (prev)
|
||||
{
|
||||
prev.next = e.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.table[i] = e.next;
|
||||
}
|
||||
|
||||
if (e.before)
|
||||
{
|
||||
e.before.after = e.after;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.head = e.after;
|
||||
}
|
||||
|
||||
if (e.after)
|
||||
{
|
||||
e.after.before = e.before;
|
||||
}
|
||||
else
|
||||
{
|
||||
set.tail = e.before;
|
||||
}
|
||||
|
||||
set.count--;
|
||||
linkedhashset_free_entry(set, e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = e.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void linkedhashset_free_entry(LinkedHashSet* set, LinkedEntry *entry) @private
|
||||
{
|
||||
allocator::free(set.allocator, entry);
|
||||
}
|
||||
|
||||
struct LinkedHashSetIterator
|
||||
{
|
||||
LinkedHashSet* set;
|
||||
LinkedEntry* current;
|
||||
bool started;
|
||||
}
|
||||
|
||||
fn LinkedHashSetIterator LinkedHashSet.iter(&set) => { .set = set, .current = set.head, .started = false };
|
||||
|
||||
fn bool LinkedHashSetIterator.next(&self)
|
||||
{
|
||||
if (!self.started)
|
||||
{
|
||||
self.current = self.set.head;
|
||||
self.started = true;
|
||||
}
|
||||
else if (self.current)
|
||||
{
|
||||
self.current = self.current.after;
|
||||
}
|
||||
return self.current != null;
|
||||
}
|
||||
|
||||
fn Value*? LinkedHashSetIterator.get(&self)
|
||||
{
|
||||
return self.current ? &self.current.value : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool LinkedHashSetIterator.has_next(&self)
|
||||
{
|
||||
if (!self.started) return self.set.head != null;
|
||||
return self.current && self.current.after != null;
|
||||
}
|
||||
|
||||
fn usz LinkedHashSetIterator.len(&self) @operator(len)
|
||||
{
|
||||
return self.set.count;
|
||||
}
|
||||
|
||||
int dummy @local;
|
||||
@@ -1,14 +1,15 @@
|
||||
// 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>;
|
||||
import std::io;
|
||||
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
|
||||
struct Node @private
|
||||
struct Node
|
||||
{
|
||||
Node *next;
|
||||
Node *prev;
|
||||
Node* next;
|
||||
Node* prev;
|
||||
Type value;
|
||||
}
|
||||
|
||||
@@ -16,8 +17,31 @@ struct LinkedList
|
||||
{
|
||||
Allocator allocator;
|
||||
usz size;
|
||||
Node *_first;
|
||||
Node *_last;
|
||||
Node* _first;
|
||||
Node* _last;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len = f.print("{ ")!;
|
||||
for (Node* node = self._first; node != null; node = node.next)
|
||||
{
|
||||
len += f.printf(node.next ? "%s, " : "%s", node.value)!;
|
||||
}
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
macro LinkedList @new(Allocator allocator, Type[] #default_values = {})
|
||||
{
|
||||
LinkedList new_list;
|
||||
new_list.init(allocator);
|
||||
new_list.push_all(#default_values);
|
||||
return new_list;
|
||||
}
|
||||
|
||||
macro LinkedList @tnew(Type[] #default_values = {})
|
||||
{
|
||||
return @new(tmem, #default_values);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -68,6 +92,11 @@ fn void LinkedList.push_front(&self, Type value)
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn void LinkedList.push_front_all(&self, Type[] value)
|
||||
{
|
||||
foreach_r (v : value) self.push_front(v);
|
||||
}
|
||||
|
||||
fn void LinkedList.push(&self, Type value)
|
||||
{
|
||||
Node *last = self._last;
|
||||
@@ -85,18 +114,23 @@ fn void LinkedList.push(&self, Type value)
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn void LinkedList.push_all(&self, Type[] value)
|
||||
{
|
||||
foreach (v : value) self.push(v);
|
||||
}
|
||||
|
||||
fn Type? LinkedList.peek(&self) => self.first() @inline;
|
||||
fn Type? LinkedList.peek_last(&self) => self.last() @inline;
|
||||
|
||||
fn Type? LinkedList.first(&self)
|
||||
{
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
if (!self._first) return NO_MORE_ELEMENT~;
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn Type? LinkedList.last(&self)
|
||||
{
|
||||
if (!self._last) return NO_MORE_ELEMENT?;
|
||||
if (!self._last) return NO_MORE_ELEMENT~;
|
||||
return self._last.value;
|
||||
}
|
||||
|
||||
@@ -133,6 +167,7 @@ macro Node* LinkedList.node_at_index(&self, usz index)
|
||||
while (index--) node = node.next;
|
||||
return node;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
@@ -141,6 +176,14 @@ fn Type LinkedList.get(&self, usz index)
|
||||
return self.node_at_index(index).value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn Type* LinkedList.get_ref(&self, usz index)
|
||||
{
|
||||
return &self.node_at_index(index).value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
@@ -149,12 +192,32 @@ fn void LinkedList.set(&self, usz index, Type element)
|
||||
self.node_at_index(index).value = element;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.index_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = self._first, usz i = 0; node != null; node = node.next, ++i)
|
||||
{
|
||||
if (node.value == t) return i;
|
||||
}
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.rindex_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = self._last, usz i = self.size - 1; node != null; node = node.prev, --i)
|
||||
{
|
||||
if (node.value == t) return i;
|
||||
if (i == 0) break;
|
||||
}
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void LinkedList.remove_at(&self, usz index)
|
||||
{
|
||||
self.unlink(self.node_at_index(index));
|
||||
linked_list_unlink(self, self.node_at_index(index));
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -169,47 +232,47 @@ fn void LinkedList.insert_at(&self, usz index, Type element)
|
||||
case self.size:
|
||||
self.push(element);
|
||||
default:
|
||||
self.link_before(self.node_at_index(index), element);
|
||||
linked_list_link_before(self, self.node_at_index(index), element);
|
||||
}
|
||||
}
|
||||
<*
|
||||
@require succ != null
|
||||
*>
|
||||
fn void LinkedList.link_before(&self, Node *succ, Type value) @private
|
||||
fn void linked_list_link_before(LinkedList* l, Node *succ, Type value) @private
|
||||
{
|
||||
Node* pred = succ.prev;
|
||||
Node* new_node = self.alloc_node();
|
||||
Node* new_node = l.alloc_node();
|
||||
*new_node = { .prev = pred, .next = succ, .value = value };
|
||||
succ.prev = new_node;
|
||||
if (!pred)
|
||||
{
|
||||
self._first = new_node;
|
||||
l._first = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
pred.next = new_node;
|
||||
}
|
||||
self.size++;
|
||||
l.size++;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self._first != null
|
||||
@require l._first != null
|
||||
*>
|
||||
fn void LinkedList.unlink_first(&self) @private
|
||||
fn void linked_list_unlink_first(LinkedList* l) @private
|
||||
{
|
||||
Node* f = self._first;
|
||||
Node* f = l._first;
|
||||
Node* next = f.next;
|
||||
self.free_node(f);
|
||||
self._first = next;
|
||||
l.free_node(f);
|
||||
l._first = next;
|
||||
if (!next)
|
||||
{
|
||||
self._last = null;
|
||||
l._last = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
next.prev = null;
|
||||
}
|
||||
self.size--;
|
||||
l.size--;
|
||||
}
|
||||
|
||||
fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -222,7 +285,7 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
case equals(node.value, t):
|
||||
Node* next = node.next;
|
||||
self.unlink(node);
|
||||
linked_list_unlink(self, node);
|
||||
node = next;
|
||||
default:
|
||||
node = node.next;
|
||||
@@ -233,8 +296,8 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
|
||||
fn Type? LinkedList.pop(&self)
|
||||
{
|
||||
if (!self._last) return NO_MORE_ELEMENT?;
|
||||
defer self.unlink_last();
|
||||
if (!self._last) return NO_MORE_ELEMENT~;
|
||||
defer linked_list_unlink_last(self);
|
||||
return self._last.value;
|
||||
}
|
||||
|
||||
@@ -245,21 +308,21 @@ fn bool LinkedList.is_empty(&self)
|
||||
|
||||
fn Type? LinkedList.pop_front(&self)
|
||||
{
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
defer self.unlink_first();
|
||||
if (!self._first) return NO_MORE_ELEMENT~;
|
||||
defer linked_list_unlink_first(self);
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn void? LinkedList.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
self.unlink_last();
|
||||
if (!self._first) return NO_MORE_ELEMENT~;
|
||||
linked_list_unlink_last(self);
|
||||
}
|
||||
|
||||
fn void? LinkedList.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
self.unlink_first();
|
||||
if (!self._first) return NO_MORE_ELEMENT~;
|
||||
linked_list_unlink_first(self);
|
||||
}
|
||||
|
||||
|
||||
@@ -269,7 +332,7 @@ fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
self.unlink(node);
|
||||
linked_list_unlink(self, node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -282,7 +345,7 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
self.unlink(node);
|
||||
linked_list_unlink(self, node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -291,7 +354,7 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
<*
|
||||
@require self._last != null
|
||||
*>
|
||||
fn void LinkedList.unlink_last(&self) @inline @private
|
||||
fn void linked_list_unlink_last(LinkedList* self) @inline @private
|
||||
{
|
||||
Node* l = self._last;
|
||||
Node* prev = l.prev;
|
||||
@@ -311,7 +374,7 @@ fn void LinkedList.unlink_last(&self) @inline @private
|
||||
<*
|
||||
@require x != null
|
||||
*>
|
||||
fn void LinkedList.unlink(&self, Node* x) @private
|
||||
fn void linked_list_unlink(LinkedList* self, Node* x) @private
|
||||
{
|
||||
Node* next = x.next;
|
||||
Node* prev = x.prev;
|
||||
@@ -334,3 +397,69 @@ fn void LinkedList.unlink(&self, Node* x) @private
|
||||
self.free_node(x);
|
||||
self.size--;
|
||||
}
|
||||
|
||||
|
||||
macro bool LinkedList.eq(&self, other) @operator(==) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
Node* node1 = self._first;
|
||||
Node* node2 = other._first;
|
||||
while (true)
|
||||
{
|
||||
if (!node1) return node2 == null;
|
||||
if (!node2) return false;
|
||||
if (node1.value != node2.value) return false;
|
||||
node1 = node1.next;
|
||||
node2 = node2.next;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn LinkedListArrayView LinkedList.array_view(&self)
|
||||
{
|
||||
return { .list = self, .current_node = self._first };
|
||||
}
|
||||
|
||||
|
||||
struct LinkedListArrayView
|
||||
{
|
||||
LinkedList* list;
|
||||
Node* current_node;
|
||||
usz current_index;
|
||||
}
|
||||
|
||||
fn usz LinkedListArrayView.len(&self) @operator(len) => self.list.size;
|
||||
|
||||
<*
|
||||
@require index < self.list.size
|
||||
*>
|
||||
fn Type LinkedListArrayView.get(&self, usz index) @operator([])
|
||||
{
|
||||
return *self.get_ref(index);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.list.size
|
||||
*>
|
||||
fn Type* LinkedListArrayView.get_ref(&self, usz index) @operator(&[])
|
||||
{
|
||||
if (index == self.list.size - 1)
|
||||
{
|
||||
self.current_node = self.list._last;
|
||||
self.current_index = index;
|
||||
}
|
||||
|
||||
while (self.current_index != index)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case index < self.current_index: // reverse iteration
|
||||
self.current_node = self.current_node.prev;
|
||||
self.current_index--;
|
||||
case index > self.current_index:
|
||||
self.current_node = self.current_node.next;
|
||||
self.current_index++;
|
||||
}
|
||||
}
|
||||
|
||||
return &self.current_node.value;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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};
|
||||
module std::collections::list <Type>;
|
||||
import std::io, std::math, std::collections::list_common;
|
||||
|
||||
alias ElementPredicate = fn bool(Type *type);
|
||||
@@ -13,7 +13,7 @@ const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
|
||||
|
||||
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR };
|
||||
|
||||
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct List (Printable)
|
||||
{
|
||||
@@ -57,7 +57,7 @@ fn List* List.tinit(&self, usz initial_capacity = 16)
|
||||
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
|
||||
{
|
||||
self.init(allocator, values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
self.push_all(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
|
||||
fn List* List.tinit_with_array(&self, Type[] values)
|
||||
{
|
||||
self.tinit(values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
self.push_all(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ fn void List.init_wrapping_array(&self, Allocator allocator, Type[] types)
|
||||
self.allocator = allocator;
|
||||
self.capacity = types.len;
|
||||
self.entries = types.ptr;
|
||||
self.set_size(types.len);
|
||||
list_set_size(self, types.len);
|
||||
}
|
||||
|
||||
fn bool List.is_initialized(&self) @inline => self.allocator != null && self.allocator != (Allocator)&dummy;
|
||||
@@ -110,24 +110,24 @@ fn usz? List.to_format(&self, Formatter* formatter) @dynamic
|
||||
fn void List.push(&self, Type element) @inline
|
||||
{
|
||||
self.reserve(1);
|
||||
self.entries[self.set_size(self.size + 1)] = element;
|
||||
self.entries[list_set_size(self, self.size + 1)] = element;
|
||||
}
|
||||
|
||||
fn Type? List.pop(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.set_size(self.size - 1);
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer list_set_size(self, self.size - 1);
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
fn void List.clear(&self)
|
||||
{
|
||||
self.set_size(0);
|
||||
list_set_size(self, 0);
|
||||
}
|
||||
|
||||
fn Type? List.pop_first(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
@@ -137,16 +137,17 @@ fn Type? List.pop_first(&self)
|
||||
*>
|
||||
fn void List.remove_at(&self, usz index)
|
||||
{
|
||||
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];
|
||||
usz new_size = self.size - 1;
|
||||
defer list_set_size(self, new_size);
|
||||
if (!new_size || index == new_size) return;
|
||||
self.entries[index .. new_size - 1] = self.entries[index + 1 .. new_size];
|
||||
}
|
||||
|
||||
fn void List.add_all(&self, List* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
self.reserve(other_list.size);
|
||||
usz index = self.set_size(self.size + other_list.size);
|
||||
usz index = list_set_size(self, self.size + other_list.size);
|
||||
foreach (&value : other_list)
|
||||
{
|
||||
self.entries[index++] = *value;
|
||||
@@ -198,11 +199,25 @@ fn Type[] List.array_view(&self)
|
||||
@param [in] array
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void List.add_array(&self, Type[] array)
|
||||
fn void List.add_array(&self, Type[] array) @deprecated("Use push_all")
|
||||
{
|
||||
if (!array.len) return;
|
||||
self.reserve(array.len);
|
||||
usz index = self.set_size(self.size + array.len);
|
||||
usz index = list_set_size(self, self.size + array.len);
|
||||
self.entries[index : array.len] = array[..];
|
||||
}
|
||||
|
||||
<*
|
||||
Add the values of an array to this list.
|
||||
|
||||
@param [in] array
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void List.push_all(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return;
|
||||
self.reserve(array.len);
|
||||
usz index = list_set_size(self, self.size + array.len);
|
||||
self.entries[index : array.len] = array[..];
|
||||
}
|
||||
|
||||
@@ -217,7 +232,7 @@ fn void List.push_front(&self, Type type) @inline
|
||||
fn void List.insert_at(&self, usz index, Type type)
|
||||
{
|
||||
self.reserve(1);
|
||||
self.set_size(self.size + 1);
|
||||
list_set_size(self, self.size + 1);
|
||||
for (isz i = self.size - 1; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
@@ -235,25 +250,25 @@ fn void List.set_at(&self, usz index, Type type)
|
||||
|
||||
fn void? List.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
self.set_size(self.size - 1);
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
list_set_size(self, self.size - 1);
|
||||
}
|
||||
|
||||
fn void? List.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type? List.first(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
fn Type? List.last(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT~;
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
@@ -284,7 +299,7 @@ fn void List.free(&self)
|
||||
{
|
||||
if (!self.allocator || self.allocator.ptr == &dummy || !self.capacity) return;
|
||||
|
||||
self.pre_free(); // Remove sanitizer annotation
|
||||
list_pre_free(self); // Remove sanitizer annotation
|
||||
|
||||
$if type_is_overaligned():
|
||||
allocator::free_aligned(self.allocator, self.entries);
|
||||
@@ -343,7 +358,7 @@ fn usz List.retain_using_test(&self, ElementTest filter, any context)
|
||||
return list_common::list_remove_using_test(self, filter, true, context);
|
||||
}
|
||||
|
||||
fn void List.ensure_capacity(&self, usz min_capacity) @local
|
||||
fn void list_ensure_capacity(List* self, usz min_capacity) @local
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (self.capacity >= min_capacity) return;
|
||||
@@ -359,7 +374,7 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
|
||||
break;
|
||||
}
|
||||
|
||||
self.pre_free(); // Remove sanitizer annotation
|
||||
list_pre_free(self); // Remove sanitizer annotation
|
||||
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
$if type_is_overaligned():
|
||||
@@ -369,7 +384,7 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
|
||||
$endif;
|
||||
self.capacity = min_capacity;
|
||||
|
||||
self.post_alloc(); // Add sanitizer annotation
|
||||
list_post_alloc(self); // Add sanitizer annotation
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -404,7 +419,7 @@ fn void List.reserve(&self, usz added)
|
||||
assert(new_size < usz.max / 2U);
|
||||
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
|
||||
while (new_capacity < new_size) new_capacity *= 2U;
|
||||
self.ensure_capacity(new_capacity);
|
||||
list_ensure_capacity(self, new_capacity);
|
||||
}
|
||||
|
||||
fn void List._update_size_change(&self,usz old_size, usz new_size)
|
||||
@@ -421,7 +436,7 @@ fn void List._update_size_change(&self,usz old_size, usz new_size)
|
||||
<*
|
||||
@require new_size == 0 || self.capacity != 0
|
||||
*>
|
||||
fn usz List.set_size(&self, usz new_size) @inline @private
|
||||
fn usz list_set_size(List* self, usz new_size) @inline @private
|
||||
{
|
||||
usz old_size = self.size;
|
||||
self._update_size_change(old_size, new_size);
|
||||
@@ -429,7 +444,7 @@ fn usz List.set_size(&self, usz new_size) @inline @private
|
||||
return old_size;
|
||||
}
|
||||
|
||||
macro void List.pre_free(&self) @private
|
||||
macro void list_pre_free(List* self) @private
|
||||
{
|
||||
if (!self.capacity) return;
|
||||
self._update_size_change(self.size, self.capacity);
|
||||
@@ -438,7 +453,7 @@ macro void List.pre_free(&self) @private
|
||||
<*
|
||||
@require self.capacity > 0
|
||||
*>
|
||||
macro void List.post_alloc(&self) @private
|
||||
macro void list_post_alloc(List* self) @private
|
||||
{
|
||||
self._update_size_change(self.capacity, self.size);
|
||||
}
|
||||
@@ -446,22 +461,22 @@ macro void List.post_alloc(&self) @private
|
||||
// Functions for equatable types
|
||||
|
||||
|
||||
fn usz? List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
fn usz? List.index_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn usz? List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
fn usz? List.rindex_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach_r (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -517,7 +532,8 @@ fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
usz old_size = self.size;
|
||||
defer {
|
||||
defer
|
||||
{
|
||||
if (old_size != self.size) self._update_size_change(old_size, self.size);
|
||||
}
|
||||
return list_common::list_remove_item(self, value);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module std::collections::maybe{Type};
|
||||
module std::collections::maybe <Type>;
|
||||
import std::io;
|
||||
|
||||
struct Maybe (Printable)
|
||||
@@ -32,5 +32,14 @@ const Maybe EMPTY = { };
|
||||
|
||||
macro Type? Maybe.get(self)
|
||||
{
|
||||
return self.has_value ? self.value : NOT_FOUND?;
|
||||
return self.has_value ? self.value : NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool Maybe.equals(self, Maybe other) @operator(==) @if(types::is_equatable_type(Type))
|
||||
{
|
||||
if (self.has_value)
|
||||
{
|
||||
return other.has_value && equals(self.value, other.value);
|
||||
}
|
||||
return !other.has_value;
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array();
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn void Object.init_map_if_needed(&self) @private
|
||||
fn void object_init_map_if_needed(Object* self) @private
|
||||
{
|
||||
if (self.is_empty())
|
||||
{
|
||||
@@ -163,7 +163,7 @@ fn void Object.init_map_if_needed(&self) @private
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn void Object.init_array_if_needed(&self) @private
|
||||
fn void object_init_array_if_needed(Object* self) @private
|
||||
{
|
||||
if (self.is_empty())
|
||||
{
|
||||
@@ -175,17 +175,18 @@ fn void Object.init_array_if_needed(&self) @private
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn void Object.set_object(&self, String key, Object* new_object) @private
|
||||
fn void object_set_object(Object* self, String key, Object* new_object) @private
|
||||
{
|
||||
self.init_map_if_needed();
|
||||
ObjectInternalMapEntry*? entry = self.map.get_entry(key);
|
||||
defer
|
||||
{
|
||||
(void)entry.value.free();
|
||||
}
|
||||
object_init_map_if_needed(self);
|
||||
Object*? val = self.map.get_entry(key).value;
|
||||
defer (void)val.free();
|
||||
self.map.set(key, new_object);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.allocator != null : "This object is not properly initialized, was it really created using 'new'"
|
||||
@require $typeof(value) != void* ||| value == null : "void pointers cannot be stored in an object"
|
||||
*>
|
||||
macro Object* Object.object_from_value(&self, value) @private
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
@@ -201,9 +202,8 @@ macro Object* Object.object_from_value(&self, value) @private
|
||||
$case $Type.typeid == Object*.typeid:
|
||||
return value;
|
||||
$case $Type.typeid == void*.typeid:
|
||||
if (value != null) return TYPE_MISMATCH?;
|
||||
return &NULL_OBJECT;
|
||||
$case $assignable(value, String):
|
||||
$case $defined(String x = value):
|
||||
return new_string(value, self.allocator);
|
||||
$default:
|
||||
$error "Unsupported object type.";
|
||||
@@ -214,7 +214,7 @@ macro Object* Object.object_from_value(&self, value) @private
|
||||
macro Object* Object.set(&self, String key, value)
|
||||
{
|
||||
Object* val = self.object_from_value(value);
|
||||
self.set_object(key, val);
|
||||
object_set_object(self, key, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -242,8 +242,7 @@ macro Object* Object.push(&self, value)
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn Object*? Object.get(&self, String key) => self.is_empty() ? NOT_FOUND? : self.map.get(key);
|
||||
|
||||
fn Object*? Object.get(&self, String key) => self.is_empty() ? NOT_FOUND~ : self.map.get(key);
|
||||
|
||||
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
|
||||
|
||||
@@ -268,7 +267,7 @@ fn usz Object.get_len(&self)
|
||||
*>
|
||||
fn void Object.push_object(&self, Object* to_append)
|
||||
{
|
||||
self.init_array_if_needed();
|
||||
object_init_array_if_needed(self);
|
||||
self.array.push(to_append);
|
||||
}
|
||||
|
||||
@@ -277,7 +276,7 @@ fn void Object.push_object(&self, Object* to_append)
|
||||
*>
|
||||
fn void Object.set_object_at(&self, usz index, Object* to_set)
|
||||
{
|
||||
self.init_array_if_needed();
|
||||
object_init_array_if_needed(self);
|
||||
while (self.array.len() < index)
|
||||
{
|
||||
self.array.push(&NULL_OBJECT);
|
||||
@@ -308,7 +307,7 @@ macro get_integer_value(Object* value, $Type)
|
||||
return ($Type)value.s.to_uint128();
|
||||
$endif
|
||||
}
|
||||
if (!value.is_int()) return string::MALFORMED_INTEGER?;
|
||||
if (!value.is_int()) return string::MALFORMED_INTEGER~;
|
||||
return ($Type)value.i;
|
||||
}
|
||||
|
||||
@@ -361,7 +360,7 @@ fn uint128? Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint1
|
||||
fn String? Object.get_string(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_string()) return TYPE_MISMATCH?;
|
||||
if (!value.is_string()) return TYPE_MISMATCH~;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
@@ -371,7 +370,7 @@ fn String? Object.get_string(&self, String key)
|
||||
fn String? Object.get_string_at(&self, usz index)
|
||||
{
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_string()) return TYPE_MISMATCH?;
|
||||
if (!value.is_string()) return TYPE_MISMATCH~;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
@@ -381,7 +380,7 @@ fn String? Object.get_string_at(&self, usz index)
|
||||
macro String? Object.get_enum(&self, $EnumType, String key)
|
||||
{
|
||||
Object value = self.get(key)!;
|
||||
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
|
||||
if ($EnumType.typeid != value.type) return TYPE_MISMATCH~;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
@@ -391,7 +390,7 @@ macro String? Object.get_enum(&self, $EnumType, String key)
|
||||
macro String? Object.get_enum_at(&self, $EnumType, usz index)
|
||||
{
|
||||
Object value = self.get_at(index);
|
||||
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
|
||||
if ($EnumType.typeid != value.type) return TYPE_MISMATCH~;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
@@ -401,7 +400,7 @@ macro String? Object.get_enum_at(&self, $EnumType, usz index)
|
||||
fn bool? Object.get_bool(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_bool()) return TYPE_MISMATCH?;
|
||||
if (!value.is_bool()) return TYPE_MISMATCH~;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
@@ -412,7 +411,7 @@ fn bool? Object.get_bool(&self, String key)
|
||||
fn bool? Object.get_bool_at(&self, usz index)
|
||||
{
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_bool()) return TYPE_MISMATCH?;
|
||||
if (!value.is_bool()) return TYPE_MISMATCH~;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
@@ -431,7 +430,7 @@ fn double? Object.get_float(&self, String key)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
return TYPE_MISMATCH?;
|
||||
return TYPE_MISMATCH~;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +449,7 @@ fn double? Object.get_float_at(&self, usz index)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
return TYPE_MISMATCH?;
|
||||
return TYPE_MISMATCH~;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,16 +20,13 @@
|
||||
// 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::priorityqueue::private;
|
||||
|
||||
typedef PriorityQueue = inline PrivatePriorityQueue{Type, false};
|
||||
typedef PriorityQueueMax = inline PrivatePriorityQueue{Type, true};
|
||||
|
||||
module std::collections::priorityqueue::private{Type, MAX};
|
||||
module std::collections::priorityqueue;
|
||||
import std::collections::list, std::io;
|
||||
|
||||
struct PrivatePriorityQueue (Printable)
|
||||
typedef PriorityQueue <Type> = inline PrivatePriorityQueue{Type, false};
|
||||
typedef PriorityQueueMax <Type> = inline PrivatePriorityQueue{Type, true};
|
||||
|
||||
struct PrivatePriorityQueue (Printable) <Type, MAX>
|
||||
{
|
||||
List{Type} heap;
|
||||
}
|
||||
@@ -86,7 +83,7 @@ fn Type? PrivatePriorityQueue.pop(&self)
|
||||
{
|
||||
usz i = 0;
|
||||
usz len = self.heap.len();
|
||||
if (!len) return NO_MORE_ELEMENT?;
|
||||
if (!len) return NO_MORE_ELEMENT~;
|
||||
usz new_count = len - 1;
|
||||
self.heap.swap(0, new_count);
|
||||
while OUTER: ((2 * i + 1) < new_count)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<*
|
||||
@require Type.is_ordered : "The type must be ordered"
|
||||
*>
|
||||
module std::collections::range{Type};
|
||||
module std::collections::range <Type>;
|
||||
import std::io;
|
||||
|
||||
struct Range (Printable)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<*
|
||||
@require Type.kindof == ARRAY : "Required an array type"
|
||||
*>
|
||||
module std::collections::ringbuffer{Type};
|
||||
module std::collections::ringbuffer <Type>;
|
||||
import std::io;
|
||||
|
||||
alias Element = $typeof((Type){}[0]);
|
||||
@@ -48,7 +48,7 @@ fn Element? RingBuffer.pop(&self)
|
||||
switch
|
||||
{
|
||||
case self.written == 0:
|
||||
return NO_MORE_ELEMENT?;
|
||||
return NO_MORE_ELEMENT~;
|
||||
case self.written < self.buf.len:
|
||||
self.written--;
|
||||
return self.buf[self.written];
|
||||
|
||||
@@ -1,16 +1,72 @@
|
||||
module std::collections::tuple{Type1, Type2};
|
||||
module std::collections::pair <Type1, Type2>;
|
||||
import std::io;
|
||||
|
||||
struct Tuple
|
||||
struct Pair (Printable)
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
}
|
||||
|
||||
module std::collections::triple{Type1, Type2, Type3};
|
||||
fn usz? Pair.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
return f.printf("{ %s, %s }", self.first, self.second);
|
||||
}
|
||||
|
||||
struct Triple
|
||||
<*
|
||||
@param [&out] a
|
||||
@param [&out] b
|
||||
@require $defined(*a = self.first) : "You cannot assign the first value to a"
|
||||
@require $defined(*b = self.second) : "You cannot assign the second value to b"
|
||||
*>
|
||||
macro void Pair.unpack(&self, a, b)
|
||||
{
|
||||
*a = self.first;
|
||||
*b = self.second;
|
||||
}
|
||||
|
||||
fn bool Pair.equal(self, Pair other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2))
|
||||
{
|
||||
return self.first == other.first && self.second == other.second;
|
||||
}
|
||||
|
||||
module std::collections::triple <Type1, Type2, Type3>;
|
||||
import std::io;
|
||||
|
||||
struct Triple (Printable)
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
Type3 third;
|
||||
}
|
||||
|
||||
fn usz? Triple.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
return f.printf("{ %s, %s, %s }", self.first, self.second, self.third);
|
||||
}
|
||||
<*
|
||||
@param [&out] a
|
||||
@param [&out] b
|
||||
@param [&out] c
|
||||
@require $defined(*a = self.first) : "You cannot assign the first value to a"
|
||||
@require $defined(*b = self.second) : "You cannot assign the second value to b"
|
||||
@require $defined(*c = self.third) : "You cannot assign the second value to c"
|
||||
*>
|
||||
macro void Triple.unpack(&self, a, b, c)
|
||||
{
|
||||
*a = self.first;
|
||||
*b = self.second;
|
||||
*c = self.third;
|
||||
}
|
||||
|
||||
fn bool Triple.equal(self, Triple other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2) &&& types::has_equals(Type3))
|
||||
{
|
||||
return self.first == other.first && self.second == other.second && self.third == other.third;
|
||||
}
|
||||
|
||||
module std::collections::tuple <Type1, Type2>;
|
||||
|
||||
struct Tuple @deprecated("Use 'Pair' instead")
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
}
|
||||
1108
lib/std/compression/deflate.c3
Normal file
1108
lib/std/compression/deflate.c3
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,10 +7,12 @@ const uint PIXELS_MAX = 400000000;
|
||||
Purely informative. It will be saved to the file header,
|
||||
but does not affect how chunks are en-/decoded.
|
||||
*>
|
||||
enum QOIColorspace : char (char id)
|
||||
constdef QOIColorspace : char
|
||||
{
|
||||
SRGB = 0, // sRGB with linear alpha
|
||||
LINEAR = 1 // all channels linear
|
||||
<* sRGB with linear alpha *>
|
||||
SRGB = 0,
|
||||
<* all channels linear *>
|
||||
LINEAR = 1
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -19,7 +21,7 @@ enum QOIColorspace : char (char id)
|
||||
AUTO can be used when decoding to automatically determine
|
||||
the channels from the file's header.
|
||||
*>
|
||||
enum QOIChannels : char (char id)
|
||||
constdef QOIChannels : inline char
|
||||
{
|
||||
AUTO = 0,
|
||||
RGB = 3,
|
||||
@@ -98,7 +100,7 @@ fn usz? write(String filename, char[] input, QOIDesc* desc) => @pool()
|
||||
fn char[]? read(Allocator allocator, String filename, QOIDesc* desc, QOIChannels channels = AUTO) => @pool()
|
||||
{
|
||||
// read file
|
||||
char[] data = file::load_temp(filename) ?? FILE_OPEN_FAILED?!;
|
||||
char[] data = file::load_temp(filename) ?? FILE_OPEN_FAILED~!;
|
||||
// pass data to decode function
|
||||
return decode(allocator, data, desc, channels);
|
||||
}
|
||||
@@ -126,14 +128,14 @@ import std::bits;
|
||||
fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
{
|
||||
// check info in desc
|
||||
if (desc.width == 0 || desc.height == 0) return INVALID_PARAMETERS?;
|
||||
if (desc.channels == AUTO) return INVALID_PARAMETERS?;
|
||||
if (desc.width == 0 || desc.height == 0) return INVALID_PARAMETERS~;
|
||||
if (desc.channels == AUTO) return INVALID_PARAMETERS~;
|
||||
uint pixels = desc.width * desc.height;
|
||||
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
|
||||
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS~;
|
||||
|
||||
// check input data size
|
||||
uint image_size = pixels * desc.channels.id;
|
||||
if (image_size != input.len) return INVALID_DATA?;
|
||||
uint image_size = pixels * desc.channels;
|
||||
if (image_size != input.len) return INVALID_DATA~;
|
||||
|
||||
// allocate memory for encoded data (output)
|
||||
// header + chunk tag and RGB(A) data for each pixel + end of stream
|
||||
@@ -146,13 +148,13 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
.be_magic = bswap('qoif'),
|
||||
.be_width = bswap(desc.width),
|
||||
.be_height = bswap(desc.height),
|
||||
.channels = desc.channels.id,
|
||||
.colorspace = desc.colorspace.id
|
||||
.channels = desc.channels,
|
||||
.colorspace = desc.colorspace
|
||||
};
|
||||
|
||||
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
|
||||
uint loc_end = image_size - desc.channels; // End of image data
|
||||
char run_length = 0; // Length of the current run
|
||||
|
||||
Pixel[64] palette; // Zero-initialized by default
|
||||
@@ -163,7 +165,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
ichar[<3>] luma; // ...and luma
|
||||
|
||||
// write chunks
|
||||
for (loc = 0; loc < image_size; loc += desc.channels.id)
|
||||
for (loc = 0; loc < image_size; loc += desc.channels)
|
||||
{
|
||||
// set previous pixel
|
||||
prev = p;
|
||||
@@ -248,7 +250,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
}
|
||||
|
||||
// write end of stream
|
||||
output[pos:END_OF_STREAM.len] = END_OF_STREAM;
|
||||
output[pos:END_OF_STREAM.len] = END_OF_STREAM[..];
|
||||
pos += END_OF_STREAM.len;
|
||||
|
||||
return output[:pos];
|
||||
@@ -281,27 +283,27 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels channels = AUTO) @nodiscard
|
||||
{
|
||||
// check input data
|
||||
if (data.len < Header.sizeof + END_OF_STREAM.len) return INVALID_DATA?;
|
||||
if (data.len < Header.sizeof + END_OF_STREAM.len) return INVALID_DATA~;
|
||||
|
||||
// get header
|
||||
Header* header = (Header*)data.ptr;
|
||||
|
||||
// check magic bytes (FourCC)
|
||||
if (bswap(header.be_magic) != 'qoif') return INVALID_DATA?;
|
||||
if (bswap(header.be_magic) != 'qoif') return 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 INVALID_DATA?; // Channels must be specified in the header
|
||||
uint width = desc.width = bswap(header.be_width);
|
||||
uint height = desc.height = bswap(header.be_height);
|
||||
QOIChannels desc_channels = desc.channels = header.channels;
|
||||
desc.colorspace = header.colorspace;
|
||||
if (desc_channels == AUTO) return INVALID_DATA~; // Channels must be specified in the header
|
||||
|
||||
// check width and height
|
||||
if (desc.width == 0 || desc.height == 0) return INVALID_DATA?;
|
||||
if (width == 0 || height == 0) return INVALID_DATA~;
|
||||
|
||||
// check pixel count
|
||||
ulong pixels = (ulong)desc.width * (ulong)desc.height;
|
||||
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
|
||||
ulong pixels = (ulong)width * (ulong)height;
|
||||
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS~;
|
||||
|
||||
uint pos = Header.sizeof; // Current position in data
|
||||
uint loc; // Current position in image (top-left corner)
|
||||
@@ -311,14 +313,14 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c
|
||||
Pixel[64] palette; // Zero-initialized by default
|
||||
Pixel p = { 0, 0, 0, 255 };
|
||||
|
||||
if (channels == AUTO) channels = desc.channels;
|
||||
if (channels == AUTO) channels = desc_channels;
|
||||
|
||||
// allocate memory for image data
|
||||
usz image_size = (usz)pixels * channels.id;
|
||||
usz image_size = (usz)pixels * channels;
|
||||
char[] image = allocator::alloc_array(allocator, char, image_size);
|
||||
defer catch allocator::free(allocator, image);
|
||||
|
||||
for (loc = 0; loc < image_size; loc += channels.id)
|
||||
for (loc = 0; loc < image_size; loc += channels)
|
||||
{
|
||||
// get chunk tag
|
||||
tag = data[pos];
|
||||
@@ -364,7 +366,7 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c
|
||||
}
|
||||
|
||||
// draw the pixel
|
||||
if (channels == RGBA) { image[loc:4] = p.rgba; } else { image[loc:3] = p.rgb; }
|
||||
if (channels == RGBA) { image[loc:4] = p.rgba[..]; } else { image[loc:3] = p.rgb[..]; }
|
||||
}
|
||||
|
||||
return image;
|
||||
@@ -391,31 +393,22 @@ 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)
|
||||
<* magic bytes "qoif" *>
|
||||
uint be_magic;
|
||||
<* image width in pixels (BE) *>
|
||||
uint be_width;
|
||||
<* image height in pixels (BE) *>
|
||||
uint be_height;
|
||||
|
||||
// informative fields
|
||||
char channels; // 3 = RGB, 4 = RGB
|
||||
char colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
|
||||
<* 3 = RGB, 4 = RGB *>
|
||||
QOIChannels channels;
|
||||
<* 0 = sRGB with linear alpha, 1 = all channels linear *>
|
||||
QOIColorspace colorspace;
|
||||
}
|
||||
|
||||
const char[*] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1};
|
||||
|
||||
// inefficient, but it's only run once at a time
|
||||
|
||||
<*
|
||||
@return? INVALID_DATA
|
||||
*>
|
||||
macro @enumcast($Type, raw)
|
||||
{
|
||||
foreach (value : $Type.values)
|
||||
{
|
||||
if (value.id == raw) return value;
|
||||
}
|
||||
return INVALID_DATA?;
|
||||
}
|
||||
|
||||
typedef Pixel = inline char[<4>];
|
||||
macro char Pixel.hash(Pixel p)
|
||||
{
|
||||
|
||||
1215
lib/std/compression/zip.c3
Normal file
1215
lib/std/compression/zip.c3
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,13 @@
|
||||
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2023-2025 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;
|
||||
|
||||
// The arena allocator allocates up to its maximum data
|
||||
// and then fails to allocate more, returning out of memory.
|
||||
// It supports mark and reset to mark.
|
||||
|
||||
struct ArenaAllocator (Allocator)
|
||||
{
|
||||
char[] data;
|
||||
@@ -12,6 +16,8 @@ struct ArenaAllocator (Allocator)
|
||||
|
||||
<*
|
||||
Initialize a memory arena for use using the provided bytes.
|
||||
|
||||
@param [inout] data : "The memory to use for the arena."
|
||||
*>
|
||||
fn ArenaAllocator* ArenaAllocator.init(&self, char[] data)
|
||||
{
|
||||
@@ -20,23 +26,44 @@ fn ArenaAllocator* ArenaAllocator.init(&self, char[] data)
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Reset the usage completely.
|
||||
*>
|
||||
fn void ArenaAllocator.clear(&self)
|
||||
{
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
struct ArenaAllocatorHeader @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
<*
|
||||
Given some memory, create an arena allocator on the stack for it.
|
||||
|
||||
@param [inout] bytes : `The bytes to use, may be empty.`
|
||||
|
||||
@return `An arena allocator using the bytes`
|
||||
*>
|
||||
macro ArenaAllocator* wrap(char[] bytes)
|
||||
{
|
||||
return (ArenaAllocator){}.init(bytes);
|
||||
}
|
||||
|
||||
<*
|
||||
"Mark" the current state of the arena allocator by returning the use count.
|
||||
|
||||
@return `The value to pass to 'reset' in order to reset to the current use.`
|
||||
*>
|
||||
fn usz ArenaAllocator.mark(&self) => self.used;
|
||||
|
||||
<*
|
||||
Reset to a previous mark.
|
||||
|
||||
@param mark : `The previous mark.`
|
||||
@require mark <= self.used : "Invalid mark - out of range"
|
||||
*>
|
||||
fn void ArenaAllocator.reset(&self, usz mark) => self.used = mark;
|
||||
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require ptr != null
|
||||
*>
|
||||
fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
@@ -50,10 +77,10 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
}
|
||||
}
|
||||
|
||||
fn usz ArenaAllocator.mark(&self) => self.used;
|
||||
fn void ArenaAllocator.reset(&self, usz mark) => self.used = mark;
|
||||
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require size > 0
|
||||
@@ -63,12 +90,12 @@ fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz a
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
|
||||
if (size > total_len) return mem::INVALID_ALLOC_SIZE~;
|
||||
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 mem::OUT_OF_MEMORY?;
|
||||
if (end > total_len) return mem::OUT_OF_MEMORY~;
|
||||
self.used = end;
|
||||
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
|
||||
header.size = size;
|
||||
@@ -77,6 +104,8 @@ fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz a
|
||||
}
|
||||
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require old_pointer != null
|
||||
@@ -88,7 +117,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
|
||||
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 mem::INVALID_ALLOC_SIZE?;
|
||||
if (size > total_len) return mem::INVALID_ALLOC_SIZE~;
|
||||
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
// Do last allocation and alignment match?
|
||||
@@ -101,7 +130,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
|
||||
else
|
||||
{
|
||||
usz new_used = self.used + size - old_size;
|
||||
if (new_used > total_len) return mem::OUT_OF_MEMORY?;
|
||||
if (new_used > total_len) return mem::OUT_OF_MEMORY~;
|
||||
self.used = new_used;
|
||||
}
|
||||
header.size = size;
|
||||
@@ -109,6 +138,14 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
|
||||
}
|
||||
// Otherwise just allocate new memory.
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
mem::copy(mem, old_pointer, math::min(size, old_size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal data
|
||||
|
||||
struct ArenaAllocatorHeader @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::io, std::math;
|
||||
|
||||
struct AllocChunk @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
<*
|
||||
The backed arena allocator provides an allocator that will allocate from a pre-allocated chunk of memory
|
||||
provided by it's backing allocator. The allocator supports mark / reset operations, so it can be used
|
||||
as a stack (push-pop) allocator. If the initial memory is used up, it will fall back to regular allocations,
|
||||
that will be safely freed on `reset`.
|
||||
|
||||
While this allocator is similar to the dynamic arena, it supports multiple "save points", which the dynamic arena
|
||||
doesn't.
|
||||
*>
|
||||
struct BackedArenaAllocator (Allocator)
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
@@ -16,6 +19,12 @@ struct BackedArenaAllocator (Allocator)
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
struct AllocChunk @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
|
||||
|
||||
struct ExtraPage @local
|
||||
@@ -47,7 +56,7 @@ fn BackedArenaAllocator*? new_backed_allocator(usz size, Allocator allocator)
|
||||
fn void BackedArenaAllocator.destroy(&self)
|
||||
{
|
||||
self.reset(0);
|
||||
if (self.last_page) (void)self._free_page(self.last_page);
|
||||
if (self.last_page) (void)_free_page(self, self.last_page);
|
||||
allocator::free(self.backing_allocator, self);
|
||||
}
|
||||
|
||||
@@ -70,7 +79,7 @@ fn void BackedArenaAllocator.reset(&self, usz mark)
|
||||
self.used = last_page.mark;
|
||||
ExtraPage *to_free = last_page;
|
||||
last_page = last_page.prev_page;
|
||||
self._free_page(to_free)!!;
|
||||
_free_page(self, to_free)!!;
|
||||
}
|
||||
self.last_page = last_page;
|
||||
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
|
||||
@@ -89,13 +98,13 @@ fn void BackedArenaAllocator.reset(&self, usz mark)
|
||||
self.used = mark;
|
||||
}
|
||||
|
||||
fn void? BackedArenaAllocator._free_page(&self, ExtraPage* page) @inline @local
|
||||
fn void? _free_page(BackedArenaAllocator* self, ExtraPage* page) @inline @local
|
||||
{
|
||||
void* mem = page.start;
|
||||
return self.backing_allocator.release(mem, page.is_aligned());
|
||||
}
|
||||
|
||||
fn void*? BackedArenaAllocator._realloc_page(&self, ExtraPage* page, usz size, usz alignment) @inline @local
|
||||
fn void*? _realloc_page(BackedArenaAllocator* self, ExtraPage* page, usz size, usz alignment) @inline @local
|
||||
{
|
||||
// Then the actual start pointer:
|
||||
void* real_pointer = page.start;
|
||||
@@ -124,11 +133,11 @@ fn void*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignm
|
||||
assert(self.last_page, "Realloc of unrelated pointer");
|
||||
// First grab the page
|
||||
ExtraPage *page = pointer - ExtraPage.sizeof;
|
||||
return self._realloc_page(page, size, alignment);
|
||||
return _realloc_page(self, page, size, alignment);
|
||||
}
|
||||
|
||||
AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
mem::copy(data, pointer, math::min(size, chunk.size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,17 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
<*
|
||||
The dynamic arena allocator is an arena allocator that can grow by adding additional arena "pages".
|
||||
It only supports reset, at which point all pages except the first one is released to the backing
|
||||
allocator.
|
||||
|
||||
If you want multiple save points, use the BackedArenaAllocator instead.
|
||||
|
||||
The advantage over the BackedArenaAllocator, is that when allocating beyond the first "page", it will
|
||||
retain the characteristics of an arena allocator (allocating a large piece of memory then handing off
|
||||
memory from that memory), whereas the BackedArenaAllocator will have heap allocator characteristics.
|
||||
*>
|
||||
struct DynamicArenaAllocator (Allocator)
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
@@ -106,7 +117,7 @@ fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a
|
||||
return old_pointer;
|
||||
}
|
||||
void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
mem::copy(new_mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return new_mem;
|
||||
}
|
||||
|
||||
@@ -126,36 +137,7 @@ fn void DynamicArenaAllocator.reset(&self)
|
||||
self.page = page;
|
||||
}
|
||||
|
||||
<*
|
||||
@require math::is_power_of_2(alignment)
|
||||
@require size > 0
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
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(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 = allocator::malloc_try(self.backing_allocator, page_size)!;
|
||||
DynamicArenaPage*? page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
|
||||
if (catch err = page)
|
||||
{
|
||||
allocator::free(self.backing_allocator, mem);
|
||||
return err?;
|
||||
}
|
||||
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;
|
||||
self.page = page;
|
||||
page.current_stack_ptr = mem_start;
|
||||
return mem_start;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require size > 0 : `acquire expects size > 0`
|
||||
@@ -178,7 +160,7 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
|
||||
}
|
||||
if (!page)
|
||||
{
|
||||
ptr = self._alloc_new(size, alignment)!;
|
||||
ptr = _alloc_new(self, size, alignment)!;
|
||||
break SET_DONE;
|
||||
}
|
||||
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
|
||||
@@ -197,7 +179,7 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
|
||||
break ALLOCATE_NEW;
|
||||
}
|
||||
}
|
||||
ptr = self._alloc_new(size, alignment)!;
|
||||
ptr = _alloc_new(self, size, alignment)!;
|
||||
break SET_DONE;
|
||||
}
|
||||
page.used = new_used;
|
||||
@@ -209,3 +191,34 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
|
||||
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
<*
|
||||
@require math::is_power_of_2(alignment)
|
||||
@require size > 0
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*? _alloc_new(DynamicArenaAllocator* self, usz size, usz alignment) @local
|
||||
{
|
||||
// First, make sure that we can align it, extending the page size if needed.
|
||||
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 = allocator::malloc_try(self.backing_allocator, page_size)!;
|
||||
DynamicArenaPage*? page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
|
||||
if (catch err = page)
|
||||
{
|
||||
allocator::free(self.backing_allocator, mem);
|
||||
return err~;
|
||||
}
|
||||
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;
|
||||
self.page = page;
|
||||
page.current_stack_ptr = mem_start;
|
||||
return mem_start;
|
||||
}
|
||||
@@ -5,6 +5,13 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
<*
|
||||
The SimpleHeapAllocator implements a simple heap allocator on top of an allocator function.
|
||||
|
||||
It uses the given allocator function to allocate memory from some source, but never frees it.
|
||||
This allocator is intended to be used in environments where there isn't any native libc malloc,
|
||||
and it has to be emulated from a memory region, or wrapping linear memory as is the case for plain WASM.
|
||||
*>
|
||||
struct SimpleHeapAllocator (Allocator)
|
||||
{
|
||||
MemoryAllocFn alloc_fn;
|
||||
@@ -25,58 +32,58 @@ fn void*? SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type,
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
return alignment > 0 ? @aligned_alloc(self._calloc, size, alignment) : self._calloc(size);
|
||||
return alignment > 0 ? @aligned_alloc_fn(self, simple_alloc_calloc, size, alignment) : simple_alloc_calloc(self, size);
|
||||
}
|
||||
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size);
|
||||
return alignment > 0 ? @aligned_alloc_fn(self, simple_alloc_alloc, size, alignment) : simple_alloc_alloc(self, 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);
|
||||
? @aligned_realloc_fn(self, simple_alloc_calloc, simple_alloc_free, old_pointer, size, alignment)
|
||||
: simple_alloc_realloc(self, old_pointer, size);
|
||||
}
|
||||
|
||||
fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
|
||||
{
|
||||
if (aligned)
|
||||
{
|
||||
@aligned_free(self._free, old_pointer)!!;
|
||||
@aligned_free_fn(self, simple_alloc_free, old_pointer)!!;
|
||||
}
|
||||
else
|
||||
{
|
||||
self._free(old_pointer);
|
||||
simple_alloc_free(self, old_pointer);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require old_pointer && bytes > 0
|
||||
*>
|
||||
fn void*? SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
|
||||
fn void*? simple_alloc_realloc(SimpleHeapAllocator* 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 = self._alloc(bytes)!;
|
||||
void* new = simple_alloc_alloc(self, bytes)!;
|
||||
usz max_to_copy = math::min(block.size, bytes);
|
||||
mem::copy(new, old_pointer, max_to_copy);
|
||||
self._free(old_pointer);
|
||||
simple_alloc_free(self, old_pointer);
|
||||
return new;
|
||||
}
|
||||
|
||||
fn void*? SimpleHeapAllocator._calloc(&self, usz bytes) @local
|
||||
fn void*? simple_alloc_calloc(SimpleHeapAllocator* self, usz bytes) @local
|
||||
{
|
||||
void* data = self._alloc(bytes)!;
|
||||
void* data = simple_alloc_alloc(self, bytes)!;
|
||||
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
|
||||
fn void*? simple_alloc_alloc(SimpleHeapAllocator* self, usz bytes) @local
|
||||
{
|
||||
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
if (!self.free_list)
|
||||
{
|
||||
self.add_block(aligned_bytes)!;
|
||||
simple_alloc_add_block(self, aligned_bytes)!;
|
||||
}
|
||||
|
||||
Header* current = self.free_list;
|
||||
@@ -116,22 +123,22 @@ fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
self.add_block(aligned_bytes)!;
|
||||
return self._alloc(aligned_bytes);
|
||||
simple_alloc_add_block(self, aligned_bytes)!;
|
||||
return simple_alloc_alloc(self, aligned_bytes);
|
||||
}
|
||||
|
||||
fn void? SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
|
||||
fn void? simple_alloc_add_block(SimpleHeapAllocator* self, usz aligned_bytes) @local
|
||||
{
|
||||
assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes);
|
||||
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;
|
||||
self._free(new_block + 1);
|
||||
simple_alloc_free(self, new_block + 1);
|
||||
}
|
||||
|
||||
|
||||
fn void SimpleHeapAllocator._free(&self, void* ptr) @local
|
||||
fn void simple_alloc_free(SimpleHeapAllocator* self, void* ptr) @local
|
||||
{
|
||||
// Empty ptr -> do nothing.
|
||||
if (!ptr) return;
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021-2025 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;
|
||||
|
||||
<*
|
||||
The LibcAllocator is a wrapper around malloc to conform to the Allocator interface.
|
||||
*>
|
||||
typedef LibcAllocator (Allocator) = uptr;
|
||||
const LibcAllocator LIBC_ALLOCATOR = {};
|
||||
typedef 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)
|
||||
@@ -24,22 +22,22 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
|
||||
void* data @noinit;
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY~;
|
||||
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
}
|
||||
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
|
||||
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
else
|
||||
{
|
||||
void* data @noinit;
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(data = libc::malloc(bytes))) return mem::OUT_OF_MEMORY?;
|
||||
if (!(data = libc::malloc(bytes))) return mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
@@ -50,25 +48,32 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
|
||||
|
||||
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) ?: mem::OUT_OF_MEMORY?;
|
||||
void* new_ptr;
|
||||
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return mem::OUT_OF_MEMORY?;
|
||||
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: mem::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
|
||||
// Try realloc, even though it might be unaligned.
|
||||
void* new_ptr = libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~!;
|
||||
|
||||
// If it's aligned then we're done!
|
||||
uptr ptr_val = (uptr)new_ptr;
|
||||
if (ptr_val & (alignment - 1) == 0) return new_ptr;
|
||||
|
||||
// We failed, so we need to use memalign
|
||||
// We will free new_ptr before we exit.
|
||||
defer libc::free(new_ptr);
|
||||
|
||||
// Create a pointer which is sure to be aligned.
|
||||
void* aligned_ptr;
|
||||
if (posix::posix_memalign(&aligned_ptr, alignment, new_bytes))
|
||||
{
|
||||
return mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
// Now it is safe to copy the full range of data.
|
||||
mem::copy(aligned_ptr, new_ptr, new_bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return aligned_ptr;
|
||||
|
||||
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);
|
||||
@@ -84,12 +89,12 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
|
||||
{
|
||||
if (alignment > 0)
|
||||
{
|
||||
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY?;
|
||||
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
|
||||
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
|
||||
if (!data) return mem::OUT_OF_MEMORY?;
|
||||
if (!data) return mem::OUT_OF_MEMORY~;
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
@@ -100,9 +105,9 @@ fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignmen
|
||||
{
|
||||
if (alignment)
|
||||
{
|
||||
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY?;
|
||||
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
|
||||
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
|
||||
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
@@ -123,12 +128,12 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
|
||||
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 ?: mem::OUT_OF_MEMORYY?;
|
||||
return data ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
else
|
||||
{
|
||||
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
|
||||
if (!data) return mem::OUT_OF_MEMORY?;
|
||||
if (!data) return mem::OUT_OF_MEMORY~;
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
@@ -142,9 +147,9 @@ fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignmen
|
||||
if (alignment)
|
||||
{
|
||||
void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!;
|
||||
return data ?: mem::OUT_OF_MEMORY?;
|
||||
return data ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
|
||||
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
<*
|
||||
The OnStackAllocator is similar to the ArenaAllocator: it allocates from a chunk of memory
|
||||
given to it.
|
||||
|
||||
The difference is that when it runs out of memory it will go directly to its backing allocator
|
||||
rather than failing.
|
||||
|
||||
It is utilized by the @stack_mem macro as an alternative to the temp allocator.
|
||||
*>
|
||||
struct OnStackAllocator (Allocator)
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
@@ -8,7 +17,6 @@ struct OnStackAllocator (Allocator)
|
||||
OnStackAllocatorExtraChunk* chunk;
|
||||
}
|
||||
|
||||
|
||||
struct OnStackAllocatorExtraChunk @local
|
||||
{
|
||||
bool is_aligned;
|
||||
@@ -116,7 +124,7 @@ fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignm
|
||||
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
mem::copy(mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
module std::core::mem::allocator;
|
||||
module std::core::mem::allocator @if(!(env::POSIX || env::WIN32) || !$feature(VMEM_TEMP));
|
||||
import std::io, std::math;
|
||||
import std::core::sanitizer::asan;
|
||||
|
||||
|
||||
struct TempAllocatorChunk @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
// This implements the temp allocator.
|
||||
// The temp allocator is a specialized allocator only intended for use where
|
||||
// the allocation is strictly stack-like.
|
||||
//
|
||||
// It is *not* thread-safe: you cannot safely use the
|
||||
// temp allocator on a thread and pass it to another thread.
|
||||
//
|
||||
// Fundamentally the temp allocator is a thread local arena allocator
|
||||
// but the stack-like behaviour puts additional constraints to it.
|
||||
//
|
||||
// It works great for allocating temporary data in a scope then dropping
|
||||
// that data, however you should not be storing temporary data in globals
|
||||
// or locals that have a lifetime outside of the current temp allocator scope.
|
||||
//
|
||||
// Furthermore, note that the temp allocator is bounded, with additional
|
||||
// allocations on top of that causing heap allocations. Such heap allocations
|
||||
// will be cleaned up but is undesirable from a performance standpoint.
|
||||
//
|
||||
// If you want customizable behaviour similar to the temp allocator, consider
|
||||
// the ArenaAllocator, BackedArenaAllocator or the DynamicArenaAllocator.
|
||||
//
|
||||
// Experimenting with the temp allocator to make it work outside of its
|
||||
// standard usage patterns will invariably end in tears and frustrated
|
||||
// hair pulling.
|
||||
//
|
||||
// Use one of the ArenaAllocators instead.
|
||||
|
||||
struct TempAllocator (Allocator)
|
||||
{
|
||||
@@ -15,14 +34,22 @@ struct TempAllocator (Allocator)
|
||||
TempAllocatorPage* last_page;
|
||||
TempAllocator* derived;
|
||||
bool allocated;
|
||||
usz reserve_size;
|
||||
usz realloc_size;
|
||||
usz min_size;
|
||||
usz used;
|
||||
usz capacity;
|
||||
usz original_capacity;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
|
||||
struct TempAllocatorChunk @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
|
||||
|
||||
struct TempAllocatorPage
|
||||
{
|
||||
@@ -37,15 +64,20 @@ 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
|
||||
@require size >= 64
|
||||
@require realloc_size >= 64
|
||||
@require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator."
|
||||
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
|
||||
*>
|
||||
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size)
|
||||
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve = temp_allocator_reserve_size, usz min_size = temp_allocator_min_size, usz realloc_size = temp_allocator_realloc_size)
|
||||
{
|
||||
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
|
||||
temp.last_page = null;
|
||||
temp.backing_allocator = allocator;
|
||||
temp.used = 0;
|
||||
temp.min_size = min_size;
|
||||
temp.realloc_size = realloc_size;
|
||||
temp.reserve_size = reserve;
|
||||
temp.allocated = true;
|
||||
temp.derived = null;
|
||||
temp.original_capacity = temp.capacity = size;
|
||||
@@ -53,19 +85,18 @@ fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size)
|
||||
}
|
||||
<*
|
||||
@require !self.derived
|
||||
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
|
||||
@require mult > 0 : "The multiple can never be zero"
|
||||
*>
|
||||
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz min_size, usz buffer, usz mult)
|
||||
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0)
|
||||
{
|
||||
if (!reserve) reserve = self.reserve_size;
|
||||
usz remaining = self.capacity - self.used;
|
||||
void* mem @noinit;
|
||||
usz size @noinit;
|
||||
if (min_size + buffer > remaining)
|
||||
if (self.min_size + reserve > remaining)
|
||||
{
|
||||
return self.derived = new_temp_allocator(self.backing_allocator, min_size * mult)!;
|
||||
return self.derived = new_temp_allocator(self.backing_allocator, self.realloc_size, self.reserve_size, self.min_size, self.realloc_size)!;
|
||||
}
|
||||
usz start = mem::aligned_offset(self.used + buffer, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
usz start = mem::aligned_offset(self.used + reserve, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
void* ptr = &self.data[start];
|
||||
TempAllocator* temp = (TempAllocator*)ptr;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
@@ -74,6 +105,9 @@ fn TempAllocator*? TempAllocator.derive_allocator(&self, usz min_size, usz buffe
|
||||
temp.last_page = null;
|
||||
temp.backing_allocator = self.backing_allocator;
|
||||
temp.used = 0;
|
||||
temp.min_size = self.min_size;
|
||||
temp.reserve_size = self.reserve_size;
|
||||
temp.realloc_size = self.realloc_size;
|
||||
temp.allocated = false;
|
||||
temp.derived = null;
|
||||
temp.original_capacity = temp.capacity = self.capacity - start - TempAllocator.sizeof;
|
||||
@@ -82,6 +116,9 @@ fn TempAllocator*? TempAllocator.derive_allocator(&self, usz min_size, usz buffe
|
||||
return temp;
|
||||
}
|
||||
|
||||
<*
|
||||
Reset the entire temp allocator, which will merge all the children into it.
|
||||
*>
|
||||
fn void TempAllocator.reset(&self)
|
||||
{
|
||||
TempAllocator* child = self.derived;
|
||||
@@ -90,7 +127,7 @@ fn void TempAllocator.reset(&self)
|
||||
{
|
||||
TempAllocator* old = child;
|
||||
child = old.derived;
|
||||
old.destroy();
|
||||
temp_allocator_destroy(old);
|
||||
}
|
||||
self.capacity = self.original_capacity;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
@@ -105,17 +142,17 @@ fn void TempAllocator.reset(&self)
|
||||
fn void TempAllocator.free(&self)
|
||||
{
|
||||
self.reset();
|
||||
self.destroy();
|
||||
temp_allocator_destroy(self);
|
||||
}
|
||||
|
||||
fn void TempAllocator.destroy(&self) @local
|
||||
fn void temp_allocator_destroy(TempAllocator* self)
|
||||
{
|
||||
TempAllocatorPage *last_page = self.last_page;
|
||||
while (last_page)
|
||||
{
|
||||
TempAllocatorPage *to_free = last_page;
|
||||
last_page = last_page.prev_page;
|
||||
self._free_page(to_free)!!;
|
||||
_free_page(self, to_free)!!;
|
||||
}
|
||||
if (self.allocated)
|
||||
{
|
||||
@@ -142,33 +179,6 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
|
||||
}
|
||||
|
||||
|
||||
fn void? TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
|
||||
{
|
||||
void* mem = page.start;
|
||||
return self.backing_allocator.release(mem, page.is_aligned());
|
||||
}
|
||||
|
||||
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 = &self.last_page;
|
||||
// Remove the page from the list
|
||||
while (*pointer_to_prev != page)
|
||||
{
|
||||
pointer_to_prev = &((*pointer_to_prev).prev_page);
|
||||
}
|
||||
*pointer_to_prev = page.prev_page;
|
||||
usz page_size = page.pagesize();
|
||||
// Clear on size > original size.
|
||||
void* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
if (page_size > size) page_size = size;
|
||||
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
self.backing_allocator.release(real_pointer, page.is_aligned());
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
@@ -178,7 +188,7 @@ fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d
|
||||
assert(self.last_page, "Realloc of non temp pointer");
|
||||
// First grab the page
|
||||
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
|
||||
return self._realloc_page(page, size, alignment);
|
||||
return _realloc_page(self, page, size, alignment);
|
||||
}
|
||||
bool is_realloc_of_last = chunk.size + pointer == &self.data[self.used];
|
||||
if (is_realloc_of_last)
|
||||
@@ -289,3 +299,111 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
|
||||
return &page.data[0];
|
||||
}
|
||||
|
||||
|
||||
fn void? _free_page(TempAllocator* self, TempAllocatorPage* page) @inline @local
|
||||
{
|
||||
void* mem = page.start;
|
||||
return self.backing_allocator.release(mem, page.is_aligned());
|
||||
}
|
||||
|
||||
fn void*? _realloc_page(TempAllocator* 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 = &self.last_page;
|
||||
// Remove the page from the list
|
||||
while (*pointer_to_prev != page)
|
||||
{
|
||||
pointer_to_prev = &((*pointer_to_prev).prev_page);
|
||||
}
|
||||
*pointer_to_prev = page.prev_page;
|
||||
usz page_size = page.pagesize();
|
||||
// Clear on size > original size.
|
||||
void* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
if (page_size > size) page_size = size;
|
||||
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
self.backing_allocator.release(real_pointer, page.is_aligned());
|
||||
return data;
|
||||
}
|
||||
|
||||
module std::core::mem::allocator @if((env::POSIX || env::WIN32) && $feature(VMEM_TEMP));
|
||||
import std::math;
|
||||
|
||||
|
||||
tlocal VmemOptions temp_allocator_default_options = {
|
||||
.shrink_on_reset = env::MEMORY_ENV != NORMAL,
|
||||
.protect_unused_pages = env::COMPILER_OPT_LEVEL <= O1 || env::COMPILER_SAFE_MODE,
|
||||
.scratch_released_data = env::COMPILER_SAFE_MODE
|
||||
};
|
||||
|
||||
|
||||
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve = temp_allocator_reserve_size, usz min_size = temp_allocator_min_size, usz realloc_size = temp_allocator_realloc_size)
|
||||
{
|
||||
Vmem mem;
|
||||
TempAllocator* t = allocator::new(allocator, TempAllocator);
|
||||
defer catch allocator::free(allocator, t);
|
||||
t.vmem.init(preferred_size: isz.sizeof > 4 ? 4 * mem::GB : 512 * mem::MB,
|
||||
reserve_page_size: isz.sizeof > 4 ? 256 * mem::KB : 0,
|
||||
options: temp_allocator_default_options)!;
|
||||
t.allocator = allocator;
|
||||
return t;
|
||||
}
|
||||
|
||||
struct TempAllocator (Allocator)
|
||||
{
|
||||
Vmem vmem;
|
||||
TempAllocator* derived;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
<*
|
||||
@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
|
||||
{
|
||||
return self.vmem.acquire(size, init_type, alignment) @inline;
|
||||
}
|
||||
|
||||
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0)
|
||||
{
|
||||
if (self.derived) return self.derived;
|
||||
return self.derived = new_temp_allocator(self.allocator, 0)!;
|
||||
}
|
||||
|
||||
<*
|
||||
Reset the entire temp allocator, destroying all children
|
||||
*>
|
||||
fn void TempAllocator.reset(&self)
|
||||
{
|
||||
TempAllocator* child = self.derived;
|
||||
if (!child) return;
|
||||
child.reset();
|
||||
child.vmem.reset(0);
|
||||
}
|
||||
fn void TempAllocator.free(&self)
|
||||
{
|
||||
_destroy(self);
|
||||
}
|
||||
|
||||
fn void _destroy(TempAllocator* self) @local
|
||||
{
|
||||
TempAllocator* child = self.derived;
|
||||
if (!child) return;
|
||||
child.destroy();
|
||||
self.vmem.free() @inline;
|
||||
allocator::free(self.allocator, self) @inline;
|
||||
}
|
||||
|
||||
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
return self.vmem.resize(pointer, size, alignment) @inline;
|
||||
}
|
||||
|
||||
fn void TempAllocator.release(&self, void* old_pointer, bool b) @dynamic
|
||||
{
|
||||
self.vmem.release(old_pointer, b) @inline;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021-2025 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.
|
||||
|
||||
@@ -18,12 +18,18 @@ alias AllocMap = HashMap { uptr, Allocation };
|
||||
// A simple tracking allocator.
|
||||
// It tracks allocations using a hash map but
|
||||
// is not compatible with allocators that uses mark()
|
||||
//
|
||||
// It is also embarrassingly single-threaded, so
|
||||
// do not use it to track allocations that cross threads.
|
||||
|
||||
struct TrackingAllocator (Allocator)
|
||||
{
|
||||
Allocator inner_allocator;
|
||||
AllocMap map;
|
||||
usz mem_total;
|
||||
usz allocs_total;
|
||||
usz usage;
|
||||
usz max_usage;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -66,6 +72,11 @@ fn usz TrackingAllocator.total_allocated(&self) => self.mem_total;
|
||||
*>
|
||||
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
|
||||
|
||||
<*
|
||||
@return "the maximum amount of memory allocated"
|
||||
*>
|
||||
fn usz TrackingAllocator.max_allocated(&self) => self.max_usage;
|
||||
|
||||
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
|
||||
{
|
||||
return self.map.tvalues();
|
||||
@@ -84,27 +95,34 @@ fn void*? TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, us
|
||||
backtrace::capture_current(&bt);
|
||||
self.map.set((uptr)data, { data, size, bt });
|
||||
self.mem_total += size;
|
||||
self.usage += size;
|
||||
if (self.usage > self.max_usage) self.max_usage = self.usage;
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*? TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
void* data = self.inner_allocator.resize(old_pointer, size, alignment)!;
|
||||
self.usage -= self.map[(uptr)old_pointer]!!.size;
|
||||
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.usage += size;
|
||||
if (self.usage > self.max_usage) self.max_usage = self.usage;
|
||||
self.allocs_total++;
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
|
||||
{
|
||||
usz? old_size = self.map[(uptr)old_pointer].size;
|
||||
if (catch self.map.remove((uptr)old_pointer))
|
||||
{
|
||||
unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
|
||||
}
|
||||
self.usage -= old_size!!;
|
||||
self.inner_allocator.release(old_pointer, is_aligned);
|
||||
}
|
||||
|
||||
@@ -173,6 +191,7 @@ fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
|
||||
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)!;
|
||||
io::fprintfn(out, "- Maximum memory usage: %d", self.max_usage)!;
|
||||
if (leaks)
|
||||
{
|
||||
io::fprintn(out)!;
|
||||
@@ -212,4 +231,4 @@ fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
255
lib/std/core/allocators/vmem.c3
Normal file
255
lib/std/core/allocators/vmem.c3
Normal file
@@ -0,0 +1,255 @@
|
||||
module std::core::mem::allocator @if(env::POSIX || env::WIN32);
|
||||
import std::math, std::os::posix, libc, std::bits;
|
||||
import std::core::mem;
|
||||
import std::core::env;
|
||||
|
||||
|
||||
|
||||
// Virtual Memory allocator
|
||||
|
||||
faultdef VMEM_RESERVE_FAILED;
|
||||
|
||||
struct Vmem (Allocator)
|
||||
{
|
||||
VirtualMemory memory;
|
||||
usz allocated;
|
||||
usz pagesize;
|
||||
usz page_pot;
|
||||
usz last_page;
|
||||
usz high_water;
|
||||
VmemOptions options;
|
||||
}
|
||||
|
||||
bitstruct VmemOptions : int
|
||||
{
|
||||
<* Release memory on reset *>
|
||||
bool shrink_on_reset;
|
||||
<* Protect unused pages on reset *>
|
||||
bool protect_unused_pages;
|
||||
<* Overwrite released data with 0xAA *>
|
||||
bool scratch_released_data;
|
||||
}
|
||||
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require !reserve_page_size || math::is_power_of_2(reserve_page_size)
|
||||
@require reserve_page_size <= preferred_size : "The min reserve_page_size size must be less or equal to the preferred size"
|
||||
@require preferred_size >= 1 * mem::KB : "The preferred size must exceed 1 KB"
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY, VMEM_RESERVE_FAILED
|
||||
*>
|
||||
fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOptions options = { true, true, env::COMPILER_SAFE_MODE }, usz min_size = 0)
|
||||
{
|
||||
static usz page_size = 0;
|
||||
if (!page_size) page_size = mem::os_pagesize();
|
||||
if (page_size < reserve_page_size) page_size = reserve_page_size;
|
||||
preferred_size = mem::aligned_offset(preferred_size, page_size);
|
||||
if (!min_size) min_size = max(preferred_size / 1024, 1);
|
||||
VirtualMemory? memory = mem::OUT_OF_MEMORY~;
|
||||
while (preferred_size >= min_size)
|
||||
{
|
||||
memory = vm::virtual_alloc(preferred_size, PROTECTED);
|
||||
// It worked?
|
||||
if (try memory) break;
|
||||
switch (@catch(memory))
|
||||
{
|
||||
case mem::OUT_OF_MEMORY:
|
||||
case vm::RANGE_OVERFLOW:
|
||||
// Try a smaller size.
|
||||
preferred_size /= 2;
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (catch memory) return VMEM_RESERVE_FAILED~;
|
||||
if (page_size > preferred_size) page_size = preferred_size;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(memory.ptr, memory.size);
|
||||
$endif
|
||||
*self = { .memory = memory,
|
||||
.high_water = 0,
|
||||
.pagesize = page_size,
|
||||
.page_pot = page_size.ctz(),
|
||||
.options = options,
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require size > 0
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*? Vmem.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = self.memory.size;
|
||||
if (size > total_len) return mem::INVALID_ALLOC_SIZE~;
|
||||
void* start_mem = self.memory.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.allocated + VmemHeader.sizeof;
|
||||
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz after = (usz)(mem - start_mem) + size;
|
||||
if (after > total_len) return mem::OUT_OF_MEMORY~;
|
||||
if (init_type == ZERO && self.high_water <= self.allocated)
|
||||
{
|
||||
init_type = NO_ZERO;
|
||||
}
|
||||
protect(self, after)!;
|
||||
VmemHeader* header = mem - VmemHeader.sizeof;
|
||||
header.size = size;
|
||||
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
fn bool Vmem.owns_pointer(&self, void* ptr) @inline
|
||||
{
|
||||
return (uptr)ptr >= (uptr)self.memory.ptr && (uptr)ptr < (uptr)self.memory.ptr + self.memory.size;
|
||||
}
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require old_pointer != null
|
||||
@require size > 0
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
if (size > self.memory.size) return mem::INVALID_ALLOC_SIZE~;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
assert(self.owns_pointer(old_pointer), "Pointer originates from a different allocator: %p, not in %p - %p", old_pointer, self.memory.ptr, self.memory.ptr + self.allocated);
|
||||
VmemHeader* header = old_pointer - VmemHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
if (old_size == size) return old_pointer;
|
||||
// Do last allocation and alignment match?
|
||||
if (self.memory.ptr + self.allocated == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
if (old_size > size)
|
||||
{
|
||||
unprotect(self, self.allocated + size - old_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
usz allocated = self.allocated + size - old_size;
|
||||
if (allocated > self.memory.size) return mem::OUT_OF_MEMORY~;
|
||||
protect(self, allocated)!;
|
||||
}
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
}
|
||||
if (old_size > size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(old_pointer + size, old_size - size);
|
||||
$endif
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
}
|
||||
// Otherwise just allocate new memory.
|
||||
void* mem = self.acquire(size, NO_ZERO, alignment)!;
|
||||
assert(size > old_size);
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
<*
|
||||
Implements the Allocator interface method.
|
||||
|
||||
@require ptr != null
|
||||
*>
|
||||
fn void Vmem.release(&self, void* ptr, bool) @dynamic
|
||||
{
|
||||
assert(self.owns_pointer(ptr), "Pointer originates from a different allocator %p.", ptr);
|
||||
VmemHeader* header = ptr - VmemHeader.sizeof;
|
||||
// Reclaim memory if it's the last element.
|
||||
if (ptr + header.size == self.memory.ptr + self.allocated)
|
||||
{
|
||||
unprotect(self, self.allocated - header.size - VmemHeader.sizeof);
|
||||
}
|
||||
}
|
||||
|
||||
fn usz Vmem.mark(&self)
|
||||
{
|
||||
return self.allocated;
|
||||
}
|
||||
|
||||
<*
|
||||
@require mark <= self.allocated : "Invalid mark"
|
||||
*>
|
||||
fn void Vmem.reset(&self, usz mark)
|
||||
{
|
||||
if (mark == self.allocated) return;
|
||||
unprotect(self, mark);
|
||||
}
|
||||
|
||||
fn void Vmem.free(&self)
|
||||
{
|
||||
if (!self.memory.ptr) return;
|
||||
$switch:
|
||||
$case env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(self.memory.ptr, self.memory.size);
|
||||
$case env::COMPILER_SAFE_MODE:
|
||||
((char*)self.memory.ptr)[0:self.allocated] = 0xAA;
|
||||
$endswitch
|
||||
(void)self.memory.destroy();
|
||||
*self = {};
|
||||
}
|
||||
|
||||
// Internal data
|
||||
|
||||
struct VmemHeader @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
macro void? protect(Vmem* mem, usz after) @local
|
||||
{
|
||||
usz shift = mem.page_pot;
|
||||
usz page_after = (after + mem.pagesize - 1) >> shift;
|
||||
usz last_page = mem.last_page;
|
||||
bool over_high_water = mem.high_water < after;
|
||||
if (page_after > last_page)
|
||||
{
|
||||
usz page_start = last_page << shift;
|
||||
usz page_len = (page_after - last_page) << shift;
|
||||
mem.memory.commit(page_start, page_len)!;
|
||||
if (mem.options.protect_unused_pages || over_high_water)
|
||||
{
|
||||
mem.memory.protect(page_start, page_len, READWRITE)!;
|
||||
}
|
||||
mem.last_page = page_after;
|
||||
}
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(mem.memory.ptr + mem.allocated, after - mem.allocated);
|
||||
$endif
|
||||
mem.allocated = after;
|
||||
if (over_high_water) mem.high_water = after;
|
||||
}
|
||||
|
||||
macro void unprotect(Vmem* mem, usz after) @local
|
||||
{
|
||||
usz shift = mem.page_pot;
|
||||
usz last_page = mem.last_page;
|
||||
usz page_after = mem.last_page = (after + mem.pagesize - 1) >> shift;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(mem.memory.ptr + after, mem.allocated - after);
|
||||
$else
|
||||
if (mem.options.scratch_released_data)
|
||||
{
|
||||
mem::set(mem.memory.ptr + after, 0xAA, mem.allocated - after);
|
||||
}
|
||||
$endif
|
||||
if ((mem.options.shrink_on_reset || mem.options.protect_unused_pages) && page_after < last_page)
|
||||
{
|
||||
usz start = page_after << shift;
|
||||
usz len = (last_page - page_after) << shift;
|
||||
if (mem.options.shrink_on_reset) (void)mem.memory.decommit(start, len, false);
|
||||
if (mem.options.protect_unused_pages) (void)mem.memory.protect(start, len, PROTECTED);
|
||||
}
|
||||
mem.allocated = after;
|
||||
}
|
||||
165
lib/std/core/ansi.c3
Normal file
165
lib/std/core/ansi.c3
Normal file
@@ -0,0 +1,165 @@
|
||||
module std::core::string::ansi;
|
||||
import std::io;
|
||||
|
||||
constdef Ansi : inline String
|
||||
{
|
||||
RESET = "\e[0m",
|
||||
BOLD = "\e[1m",
|
||||
DIM = "\e[2m",
|
||||
ITALIC = "\e[3m",
|
||||
UNDERLINE = "\e[4m",
|
||||
BLINK = "\e[5m",
|
||||
BLINK_FAST = "\e[6m",
|
||||
INVERT = "\e[7m",
|
||||
HIDDEN = "\e[8m",
|
||||
STRIKETHROUGH = "\e[9m",
|
||||
DOUBLE_UNDER = "\e[21m",
|
||||
NO_DIM = "\e[22m",
|
||||
NO_ITALIC = "\e[23m",
|
||||
NO_UNDERLINE = "\e[24m",
|
||||
NO_BLINK = "\e[25m",
|
||||
NO_INVERT = "\e[27m",
|
||||
NO_HIDDEN = "\e[28m",
|
||||
NO_STRIKETHROUGH = "\e[29m",
|
||||
BLACK = "\e[30m",
|
||||
RED = "\e[31m",
|
||||
GREEN = "\e[32m",
|
||||
YELLOW = "\e[33m",
|
||||
BLUE = "\e[34m",
|
||||
MAGENTA = "\e[35m",
|
||||
CYAN = "\e[36m",
|
||||
WHITE = "\e[37m",
|
||||
DEFAULT = "\e[39m",
|
||||
BRIGHT_BLACK = "\e[90m",
|
||||
BRIGHT_RED = "\e[91m",
|
||||
BRIGHT_GREEN = "\e[92m",
|
||||
BRIGHT_YELLOW = "\e[93m",
|
||||
BRIGHT_BLUE = "\e[94m",
|
||||
BRIGHT_MAGENTA = "\e[95m",
|
||||
BRIGHT_CYAN = "\e[96m",
|
||||
BRIGHT_WHITE = "\e[97m",
|
||||
BG_BLACK = "\e[40m",
|
||||
BG_RED = "\e[41m",
|
||||
BG_GREEN = "\e[42m",
|
||||
BG_YELLOW = "\e[43m",
|
||||
BG_BLUE = "\e[44m",
|
||||
BG_MAGENTA = "\e[45m",
|
||||
BG_CYAN = "\e[46m",
|
||||
BG_WHITE = "\e[47m",
|
||||
BG_DEFAULT = "\e[49m",
|
||||
BG_BRIGHT_BLACK = "\e[100m",
|
||||
BG_BRIGHT_RED = "\e[101m",
|
||||
BG_BRIGHT_GREEN = "\e[102m",
|
||||
BG_BRIGHT_YELLOW = "\e[103m",
|
||||
BG_BRIGHT_BLUE = "\e[104m",
|
||||
BG_BRIGHT_MAGENTA = "\e[105m",
|
||||
BG_BRIGHT_CYAN = "\e[106m",
|
||||
BG_BRIGHT_WHITE = "\e[107m",
|
||||
}
|
||||
|
||||
struct AnsiColor (Printable)
|
||||
{
|
||||
char r, g, b;
|
||||
bool bg;
|
||||
}
|
||||
|
||||
fn usz? AnsiColor.to_format(&self, Formatter* fmt) @dynamic
|
||||
{
|
||||
return fmt.printf("\e[%s8;2;%s;%s;%sm", self.bg ? 4 : 3, self.r, self.g, self.b);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code
|
||||
|
||||
@return `A struct that, when formatted with '%s', sets the foreground or background colour to the specified rgb value`
|
||||
*>
|
||||
fn AnsiColor get_color_rgb(char r, char g, char b, bool bg = false)
|
||||
{
|
||||
return {r, g, b, bg};
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code
|
||||
|
||||
@return `A struct that, when formatted with '%s', sets the foreground or background colour of printed text to the specified rgb value`
|
||||
*>
|
||||
fn AnsiColor get_color(uint rgb, bool bg = false)
|
||||
{
|
||||
return {(char)(rgb >> 16), (char)((rgb & 0x00FF00) >> 8), (char)rgb, bg};
|
||||
}
|
||||
|
||||
<*
|
||||
8-bit color code
|
||||
|
||||
@return `the formatting char for the given background color`
|
||||
*>
|
||||
macro String color_8bit(char $index, bool $bg = false) @const
|
||||
{
|
||||
int $mode = $bg ? 4 : 3;
|
||||
return @sprintf("\e[%s8;5;%sm", $mode, $index);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code
|
||||
|
||||
@return `the string for the given foreground color`
|
||||
*>
|
||||
macro String color_rgb(char $r, char $g, char $b, bool $bg = false) @const
|
||||
{
|
||||
int $mode = $bg ? 4 : 3;
|
||||
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $r, $g, $b);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@require $rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
macro String color(uint $rgb, bool $bg = false) @const
|
||||
{
|
||||
int $mode = $bg ? 4 : 3;
|
||||
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $rgb >> 16, ($rgb & 0xFF00) >> 8, $rgb & 0xFF);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_color(Allocator mem, uint rgb, bool bg = false) @deprecated("use get_color instead")
|
||||
{
|
||||
return make_color_rgb(mem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_tcolor(uint rgb, bool bg = false) @deprecated("use get_color instead")
|
||||
{
|
||||
return make_color_rgb(tmem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_color_rgb(Allocator mem, char r, char g, char b, bool bg = false) @deprecated("use get_color_rgb instead")
|
||||
{
|
||||
return string::format(mem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
|
||||
}
|
||||
|
||||
<*
|
||||
24-bit color code rgb
|
||||
|
||||
@return `the string char for the given foreground color`
|
||||
*>
|
||||
fn String make_tcolor_rgb(char r, char g, char b, bool bg = false) @deprecated("use get_color_rgb instead")
|
||||
{
|
||||
return string::format(tmem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
|
||||
}
|
||||
@@ -1,59 +1,93 @@
|
||||
module std::core::array;
|
||||
import std::core::array::slice;
|
||||
import std::collections::pair, std::io;
|
||||
|
||||
<*
|
||||
Returns true if the array contains at least one element, else false
|
||||
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@require $kindof(array) == SLICE || $kindof(array) == ARRAY
|
||||
@require @typematch(array[0], element) : "array and element must have the same type"
|
||||
*>
|
||||
macro bool contains(array, element)
|
||||
{
|
||||
foreach (&item : array)
|
||||
{
|
||||
if (*item == element) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Return the first index of element found in the array, searching from the start.
|
||||
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@require $kindof(array) == SLICE || $kindof(array) == ARRAY
|
||||
@require @typematch(array[0], element) : "array and element must have the same type"
|
||||
@return "the first index of the element"
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro index_of(array, element)
|
||||
macro usz? index_of(array, element)
|
||||
{
|
||||
foreach (i, &e : array)
|
||||
{
|
||||
if (*e == element) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@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
|
||||
Slice a 2d array and create a Slice2d from it.
|
||||
|
||||
@param array_ptr : "the pointer to create a slice from"
|
||||
@param x : "The starting position of the slice x, optional"
|
||||
@param y : "The starting position of the slice y, optional"
|
||||
@param xlen : "The length of the slice in x, defaults to the length of the array"
|
||||
@param ylen : "The length of the slice in y, defaults to the length of the array"
|
||||
@return "A Slice2d from the array"
|
||||
@require $kindof(array_ptr) == POINTER
|
||||
@require $kindof(*array_ptr) == VECTOR || $kindof(*array_ptr) == ARRAY
|
||||
@require $kindof((*array_ptr)[0]) == VECTOR || $kindof((*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 };
|
||||
return (Slice2d{$ElementType}) { ($ElementType*)array_ptr, $typeof((*array_ptr)[0]).len, y, ylen, x, xlen };
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Return the first index of element found in the array, searching in reverse from the end.
|
||||
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@return "the last index of the element"
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro rindex_of(array, element)
|
||||
macro usz? rindex_of(array, element)
|
||||
{
|
||||
foreach_r (i, &e : array)
|
||||
{
|
||||
if (*e == element) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
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"
|
||||
@require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY
|
||||
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY
|
||||
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type"
|
||||
@ensure result.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro concat(Allocator allocator, arr1, arr2) @nodiscard
|
||||
@@ -70,108 +104,516 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
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"
|
||||
@require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY
|
||||
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY
|
||||
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type"
|
||||
@ensure return.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
|
||||
|
||||
module std::core::array::slice{Type};
|
||||
|
||||
struct Slice2d
|
||||
{
|
||||
Type* ptr;
|
||||
usz inner_len;
|
||||
usz ystart;
|
||||
usz ylen;
|
||||
usz xstart;
|
||||
usz xlen;
|
||||
}
|
||||
<*
|
||||
Apply a reduction/folding operation to an iterable type. This walks along the input array
|
||||
and applies an `#operation` to each value, returning it to the `identity` (or "accumulator")
|
||||
base value.
|
||||
|
||||
fn usz Slice2d.len(&self) @operator(len)
|
||||
{
|
||||
return self.ylen;
|
||||
}
|
||||
For example:
|
||||
```c3
|
||||
int[] my_slice = { 1, 8, 12 };
|
||||
int folded = array::@reduce(my_slice, 2, fn (i, e) => i * e);
|
||||
assert(folded == (2 * 1 * 8 * 12));
|
||||
```
|
||||
|
||||
fn usz Slice2d.count(&self)
|
||||
{
|
||||
return self.ylen * self.xlen;
|
||||
}
|
||||
Notice how the given `identity` value started the multiplication chain at 2. When enumerating
|
||||
`my_slice`, each element is accumulated onto the `identity` value with each sequential iteration.
|
||||
```
|
||||
i = 2; // identity value
|
||||
i *= 1; // my_slice[0]
|
||||
i *= 8; // my_slice[1]
|
||||
i *= 12; // my_slice[2]
|
||||
```
|
||||
|
||||
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
|
||||
{
|
||||
foreach (y, line : *self)
|
||||
{
|
||||
foreach (x, val : line)
|
||||
{
|
||||
@body({ x, y }, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@param [in] array
|
||||
@param identity
|
||||
@param #operation : "The reduction/folding lambda function or function pointer to apply."
|
||||
|
||||
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@reduce_fn(array, identity)) $func = #operation) : "Invalid lambda or function pointer type"
|
||||
*>
|
||||
macro @reduce(array, identity, #operation)
|
||||
{
|
||||
foreach (y, line : *self)
|
||||
{
|
||||
foreach (x, &val : line)
|
||||
{
|
||||
@body({ x, y }, val);
|
||||
}
|
||||
}
|
||||
$typefrom(@reduce_fn(array, identity)) $func = #operation;
|
||||
foreach (index, element : array) identity = $func(identity, element, index);
|
||||
return identity;
|
||||
}
|
||||
|
||||
<*
|
||||
@require idy >= 0 && idy < self.ylen
|
||||
Apply a summation operator (+) to an identity value across a span of array elements
|
||||
and return the final accumulated result.
|
||||
|
||||
@pure
|
||||
|
||||
@param [in] array
|
||||
@param identity_value : "The base accumulator value to use for the sum"
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined(array[0] + array[0]) : "Array element type must implement the '+' operator"
|
||||
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
|
||||
*>
|
||||
macro Type[] Slice2d.get_row(self, usz idy) @operator([])
|
||||
macro @sum(array, identity_value = 0)
|
||||
{
|
||||
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;
|
||||
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc + e);
|
||||
}
|
||||
|
||||
<*
|
||||
@require y >= 0 && y < self.ylen
|
||||
@require x >= 0 && x < self.xlen
|
||||
Apply a product operator (*) to an identity value across a span of array elements
|
||||
and return the final accumulated result.
|
||||
|
||||
@pure
|
||||
|
||||
@param [in] array
|
||||
@param identity_value : "The base accumulator value to use for the product"
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined(array[0] * array[0]) : "Array element type must implement the '*' operator"
|
||||
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
|
||||
*>
|
||||
fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0)
|
||||
macro @product(array, identity_value = 1)
|
||||
{
|
||||
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 };
|
||||
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc * e);
|
||||
}
|
||||
|
||||
<*
|
||||
Applies a given predicate function to each element of an array and returns a new
|
||||
array of `usz` values, each element representing an index within the original array
|
||||
where the predicate returned `true`.
|
||||
|
||||
The `.len` value of the returned array can also be used to quickly identify how many
|
||||
input array elements matched the predicate.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
int[] arr = { 0, 20, 4, 30 };
|
||||
int[] matched_indices = array::@indices_of(mem, arr, fn (u, a) => a > 10);
|
||||
```
|
||||
|
||||
The `matched_indices` variable should contain a dynamically-allocated array of `[1, 3]`,
|
||||
and thus its count indicates that 2 of the 4 elements matched the predicate condition.
|
||||
|
||||
@param [&inout] allocator
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro usz[] @indices_of(Allocator allocator, array, #predicate)
|
||||
{
|
||||
usz[] results = allocator::new_array(allocator, usz, lengthof(array));
|
||||
usz matches;
|
||||
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array)
|
||||
{
|
||||
if ($predicate(element, index)) results[matches++] = index;
|
||||
}
|
||||
|
||||
return results[:matches];
|
||||
}
|
||||
|
||||
<*
|
||||
Array `@indices_of` using the temp allocator.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro usz[] @tindices_of(array, #predicate)
|
||||
{
|
||||
return @indices_of(tmem, array, #predicate);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Applies a predicate function to each element of an input array and returns a new array
|
||||
containing shallow copies of _only_ the elements for which the predicate function returned
|
||||
a `true` value.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
int[] my_arr = { 1, 2, 4, 10, 11, 45 };
|
||||
int[] evens = array::@filter(mem, my_arr, fn (e, u) => !(e % 2));
|
||||
assert(evens == (int[]){2, 4, 10 });
|
||||
```
|
||||
|
||||
@param [&inout] allocator
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro @filter(Allocator allocator, array, #predicate) @nodiscard
|
||||
{
|
||||
var $InnerType = $typeof(array[0]);
|
||||
|
||||
usz[] matched_indices = @indices_of(allocator, array, #predicate);
|
||||
defer allocator::free(allocator, matched_indices.ptr); // can free this upon leaving this call
|
||||
|
||||
if (!matched_indices.len) return ($InnerType[]){};
|
||||
|
||||
$InnerType[] result = allocator::new_array(allocator, $InnerType, matched_indices.len);
|
||||
|
||||
foreach (i, index : matched_indices) result[i] = array[index];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Array `@filter` using the temp allocator.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro @tfilter(array, #predicate) @nodiscard
|
||||
{
|
||||
return @filter(tmem, array, #predicate);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns `true` if _any_ element of the input array returns `true` when
|
||||
the `#predicate` function is applied.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro bool @any(array, #predicate)
|
||||
{
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array) if ($predicate(element, index)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns `true` if _all_ elements of the input array return `true` when
|
||||
the `#predicate` function is applied.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro bool @all(array, #predicate)
|
||||
{
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array) if (!$predicate(element, index)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Extract a copy of all even-index elements from an input array.
|
||||
|
||||
@param [&inout] allocator : "The allocator used to create the return array."
|
||||
@param [in] array : "The array from which to extract all even elements."
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined(array[:lengthof(array)]) : "Expected a sliceable list"
|
||||
*>
|
||||
macro even(Allocator allocator, array)
|
||||
{
|
||||
return unlace_impl{$typeof(array[0])}(allocator, array[:lengthof(array)]);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Extract a copy of all odd-index elements from an input array.
|
||||
|
||||
@param [&inout] allocator : "The allocator used to create the return array."
|
||||
@param [in] array : "The array from which to extract all odd elements."
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined(array[:lengthof(array)]) : "Expected a sliceable list"
|
||||
*>
|
||||
macro odd(Allocator allocator, array)
|
||||
{
|
||||
return unlace_impl{$typeof(array[0])}(allocator, lengthof(array) > 1 ? array[1..] : ($typeof(array[0])[]){});
|
||||
}
|
||||
|
||||
<*
|
||||
Private implementation of `even` and `odd` macros, expecting a slice and returning one as well.
|
||||
This function always extracts the even elements of the input slice.
|
||||
|
||||
@param [&inout] allocator : "The allocator used to create the return array."
|
||||
@param [in] array : "The array from which to extract all odd elements."
|
||||
*>
|
||||
fn Type[] unlace_impl(Allocator allocator, Type[] array) <Type> @private
|
||||
{
|
||||
usz new_len = array.len / 2 + (array.len % 2 == 0 ? 0 : 1);
|
||||
if (new_len == 0) return (Type[]){};
|
||||
Type[] new_array = allocator::new_array(allocator, Type, new_len);
|
||||
foreach (x, &new : new_array) *new = types::implements_copy(Type) ??? array[x * 2].copy(allocator) : array[x * 2];
|
||||
return new_array[:new_len];
|
||||
}
|
||||
|
||||
<*
|
||||
Unlace or partition an input list into its component parts such that `[a, b, c, d, e]` becomes
|
||||
`[a, c, e]` and `[b, d]`. Returned arrays are allocated by the given allocator and are returned
|
||||
via two `out` parameters, `left` and `right`.
|
||||
|
||||
@param [&inout] allocator : "The allocator used to create the returned arrays."
|
||||
@param [in] array : "The input array to unlace."
|
||||
@param [out] left : "Stores a copy of all even-index array elements."
|
||||
@param [out] right : "Stores a copy of all odd-index array elements."
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $typeof(left) == $typeof(array[0])[]*
|
||||
@require $typeof(right) == $typeof(array[0])[]*
|
||||
*>
|
||||
macro unlace(Allocator allocator, array, left, right)
|
||||
{
|
||||
if (left) *left = even(allocator, array);
|
||||
if (right) *right = odd(allocator, array);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Zip together two separate arrays/slices into a single array of Pairs or return values. Values will
|
||||
be collected up to the length of the shorter array if `fill_with` is left undefined; otherwise, they
|
||||
will be collected up to the length of the LONGER array, with missing values in the shorter array being
|
||||
assigned to the value of `fill_with`. Return array elements do not have to be of the same type.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
uint[] chosen_session_ids = server::get_random_sessions(instance)[:128];
|
||||
String[200] refreshed_session_keys = prng::new_keys_batch();
|
||||
|
||||
Pair { uint, String }[] sessions_meta = array::zip(mem, chosen_session_ids, refreshed_session_keys);
|
||||
// The resulting Pair{}[] slice is then length of the shortest of the two arrays, so 128.
|
||||
|
||||
foreach (i, &sess : sessions:meta) {
|
||||
// distribute new session keys to associated instance IDs
|
||||
}
|
||||
```
|
||||
|
||||
Or:
|
||||
```c3
|
||||
String[] client_names = server::online_usernames(instance);
|
||||
uint128[] session_ids = server::user_keys();
|
||||
|
||||
// in this example, we 'know' ahead of time that 'session_ids' can only ever be SHORTER
|
||||
// than 'client_names', but never longer, because it's possible new users have logged
|
||||
// in without getting whatever this 'session ID' is delegated to them.
|
||||
Pair { String, uint128 }[] zipped = array::tzip(client_names, session_ids, fill_with: uint128.max);
|
||||
|
||||
server::refresh_session_keys_by_pair(zipped)!;
|
||||
```
|
||||
|
||||
### When an `operation` is supplied...
|
||||
Apply an operation to each element of two slices or arrays and return the results of
|
||||
each operation into a newly allocated array.
|
||||
|
||||
This essentially combines Iterable1 with Iterable2 using the `operation` functor.
|
||||
|
||||
See the functional `zipWith` construct, which has a more appropriate name than, e.g., `map`;
|
||||
a la: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#v:zipWith
|
||||
|
||||
Similar to "normal" `zip`, this macro pads the shorter input array with a given `fill_with`, or
|
||||
an empty value if one isn't supplied. This `fill_with` is supplied to the `operation` functor
|
||||
_BEFORE_ calculating its result while zipping.
|
||||
|
||||
For example: a functor of `fn char (char a, char b) => a + b` with a `fill_with` of 7,
|
||||
where the `left` array is the shorter iterable, will put 7 into that lambda in each place
|
||||
where `left` is being filled in during the zip operation.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use; default is the heap allocator."
|
||||
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
|
||||
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
|
||||
|
||||
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
|
||||
@require @is_valid_operation(left, right, ...#operation) : "The operator must take two parameters matching the elements of the left and right side"
|
||||
@require @is_valid_fill(left, right, ...fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
|
||||
*>
|
||||
macro @zip(Allocator allocator, left, right, #operation = ..., fill_with = ...) @nodiscard
|
||||
{
|
||||
var $LeftType = $typeof(left[0]);
|
||||
var $RightType = $typeof(right[0]);
|
||||
|
||||
var $Type = Pair { $LeftType, $RightType };
|
||||
bool $is_op = $defined(#operation);
|
||||
$if $is_op:
|
||||
$Type = $typeof(#operation).returns;
|
||||
$endif
|
||||
|
||||
usz left_len = lengthof(left);
|
||||
usz right_len = lengthof(right);
|
||||
|
||||
$LeftType left_fill;
|
||||
$RightType right_fill;
|
||||
usz result_len = min(left_len, right_len);
|
||||
$if $defined(fill_with):
|
||||
switch
|
||||
{
|
||||
case left_len > right_len:
|
||||
$if !$defined(($RightType)fill_with):
|
||||
unreachable();
|
||||
$else
|
||||
right_fill = ($RightType)fill_with;
|
||||
result_len = left_len;
|
||||
$endif
|
||||
case left_len < right_len:
|
||||
$if !$defined(($LeftType)fill_with):
|
||||
unreachable();
|
||||
$else
|
||||
left_fill = ($LeftType)fill_with;
|
||||
result_len = right_len;
|
||||
$endif
|
||||
}
|
||||
$endif
|
||||
|
||||
if (result_len == 0) return ($Type[]){};
|
||||
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, result_len);
|
||||
|
||||
foreach (idx, &item : result)
|
||||
{
|
||||
$if $is_op:
|
||||
var $LambdaType = $typeof(fn $Type ($LeftType a, $RightType b) => ($Type){});
|
||||
$LambdaType $operation = ($LambdaType)#operation;
|
||||
$LeftType lval = idx >= left_len ? left_fill : left[idx];
|
||||
$RightType rval = idx >= right_len ? right_fill : right[idx];
|
||||
*item = $operation(lval, rval);
|
||||
$else
|
||||
*item = {
|
||||
idx >= left_len ? left_fill : left[idx],
|
||||
idx >= right_len ? right_fill : right[idx]
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Array 'zip' using the temp allocator.
|
||||
|
||||
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
|
||||
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
|
||||
|
||||
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
|
||||
@require @is_valid_operation(left, right, ...#operation) : "The operator must take two parameters matching the elements of the left and right side"
|
||||
@require @is_valid_fill(left, right, ...fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
|
||||
*>
|
||||
macro @tzip(left, right, #operation = ..., fill_with = ...) @nodiscard
|
||||
{
|
||||
return @zip(tmem, left, right, #operation: ...#operation, fill_with: ...fill_with);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Apply an operation to each element of two slices or arrays and store the results of
|
||||
each operation into the 'left' value.
|
||||
|
||||
This is useful because no memory allocations are required in order to perform the operation.
|
||||
|
||||
A good example of using this might be using algorithmic transformations on data in-place:
|
||||
```
|
||||
char[] partial_cipher = get_next_plaintext_block();
|
||||
|
||||
array::@zip_into(
|
||||
partial_cipher[ENCRYPT_OFFSET:BASE_KEY.len],
|
||||
BASE_KEY,
|
||||
fn char (char a, char b) => a ^ (b * 5) % 37
|
||||
);
|
||||
```
|
||||
|
||||
This parameterizes the lambda function with left (`partial_cipher`) and right (`BASE_KEY`) slice
|
||||
elements and stores the end result in-place within the left slice. This is in contrast to a
|
||||
regular `zip_with` which will create a cloned final result and return it.
|
||||
|
||||
@param [inout] left : `Slice to store results of applied functor/operation.`
|
||||
@param [in] right : `Slice to apply in the functor/operation.`
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
|
||||
@require @is_valid_list(left) : "Expected a valid list"
|
||||
@require @is_valid_list(right) : "Expected a valid list"
|
||||
@require lengthof(right) >= lengthof(left) : `Right side length must be >= the destination (left) side length; consider using a sub-array of data for the assignment.`
|
||||
@require $defined($typefrom(@zip_into_fn(left, right)) x = #operation) : "The functor must use the same types as the `left` and `right` inputs, and return a value of the `left` type."
|
||||
*>
|
||||
macro @zip_into(left, right, #operation)
|
||||
{
|
||||
$typefrom(@zip_into_fn(left, right)) $operation = #operation;
|
||||
|
||||
foreach (i, &v : left) *v = $operation(left[i], right[i]);
|
||||
}
|
||||
|
||||
|
||||
// --- helper functions
|
||||
module std::core::array @private;
|
||||
|
||||
|
||||
macro typeid @predicate_fn(#array) @const
|
||||
{
|
||||
return $typeof(fn bool ($typeof(#array[0]) a, usz index = 0) => true).typeid;
|
||||
}
|
||||
|
||||
macro typeid @reduce_fn(#array, #identity) @const
|
||||
{
|
||||
return @typeid(fn $typeof(#identity) ($typeof(#identity) i, $typeof(#array[0]) a, usz index = 0) => i);
|
||||
}
|
||||
|
||||
macro typeid @zip_into_fn(#left, #right) @const
|
||||
{
|
||||
return @typeid(fn $typeof(#left[0]) ($typeof(#left[0]) l, $typeof(#right[0]) r) => l);
|
||||
}
|
||||
|
||||
macro bool @is_valid_operation(#left, #right, #operation = ...) @const
|
||||
{
|
||||
$switch:
|
||||
$case !$defined(#operation):
|
||||
return true;
|
||||
$case $kindof(#operation) != FUNC:
|
||||
return false;
|
||||
$default:
|
||||
return $defined(#operation(#left[0], #right[0]));
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool @is_valid_list(#expr) @const
|
||||
{
|
||||
return $defined(#expr[0], lengthof(#expr));
|
||||
}
|
||||
|
||||
macro bool @is_valid_fill(left, right, fill_with = ...)
|
||||
{
|
||||
$if !$defined(fill_with):
|
||||
return true;
|
||||
$else
|
||||
usz left_len = lengthof(left);
|
||||
usz right_len = lengthof(right);
|
||||
if (left_len == right_len) return true;
|
||||
return left_len > right_len ? $defined(($typeof(right[0]))fill_with) : $defined(($typeof(left[0]))fill_with);
|
||||
$endif
|
||||
}
|
||||
|
||||
156
lib/std/core/ascii.c3
Normal file
156
lib/std/core/ascii.c3
Normal file
@@ -0,0 +1,156 @@
|
||||
<*
|
||||
This module contains utils for handling ASCII characters. They only operate on
|
||||
characters corresponding to 0-127.
|
||||
*>
|
||||
module std::core::ascii;
|
||||
|
||||
macro bool @is_lower(c) => ASCII_LOOKUP[c].lower; // Is a-z
|
||||
macro bool @is_upper(c) => ASCII_LOOKUP[c].upper; // Is A-Z
|
||||
macro bool @is_digit(c) => ASCII_LOOKUP[c].digit; // Is 0-9
|
||||
macro bool @is_bdigit(c) => ASCII_LOOKUP[c].bin_digit; // Is 0-1
|
||||
macro bool @is_odigit(c) => ASCII_LOOKUP[c].oct_digit; // Is 0-7
|
||||
macro bool @is_xdigit(c) => ASCII_LOOKUP[c].hex_digit; // Is 0-9 or a-f or A-F
|
||||
macro bool @is_alpha(c) => ASCII_LOOKUP[c].alpha; // Is a-z or A-Z
|
||||
macro bool @is_print(c) => ASCII_LOOKUP[c].printable; // Is a printable character (space or higher and < 127
|
||||
macro bool @is_graph(c) => ASCII_LOOKUP[c].graph; // Does it show any graphics (printable but not space)
|
||||
macro bool @is_space(c) => ASCII_LOOKUP[c].space; // Is it a space character: space, tab, linefeed etc
|
||||
macro bool @is_alnum(c) => ASCII_LOOKUP[c].alphanum; // Is it alpha or digit
|
||||
macro bool @is_punct(c) => ASCII_LOOKUP[c].punct; // Is it "graph" but not digit or letter
|
||||
macro bool @is_blank(c) => ASCII_LOOKUP[c].blank; // Is it a blank space: space or tab
|
||||
macro bool @is_cntrl(c) => ASCII_LOOKUP[c].control; // Is it a control character: before space or 127
|
||||
macro char @to_lower(c) => c + TO_LOWER[c]; // Convert A-Z to a-z if found
|
||||
macro char @to_upper(c) => c - TO_UPPER[c]; // Convert a-z to A-Z if found
|
||||
|
||||
fn bool is_lower(char c) => @is_lower(c); // Is a-z
|
||||
fn bool is_upper(char c) => @is_upper(c); // Is A-Z
|
||||
fn bool is_digit(char c) => @is_digit(c); // Is 0-9
|
||||
fn bool is_bdigit(char c) => @is_bdigit(c); // Is 0-1
|
||||
fn bool is_odigit(char c) => @is_odigit(c); // Is 0-7
|
||||
fn bool is_xdigit(char c) => @is_xdigit(c); // Is 0-9 or a-f or A-F
|
||||
fn bool is_alpha(char c) => @is_alpha(c); // Is a-z or A-Z
|
||||
fn bool is_print(char c) => @is_print(c); // Is a printable character (space or higher and < 127
|
||||
fn bool is_graph(char c) => @is_graph(c); // Does it show any graphics (printable but not space)
|
||||
fn bool is_space(char c) => @is_space(c); // Is it a space character: space, tab, linefeed etc
|
||||
fn bool is_alnum(char c) => @is_alnum(c); // Is it alpha or digit
|
||||
fn bool is_punct(char c) => @is_punct(c); // Is it "graph" but not digit or letter
|
||||
fn bool is_blank(char c) => @is_blank(c); // Is it a blank space: space or tab
|
||||
fn bool is_cntrl(char c) => @is_cntrl(c); // Is it a control character: before space or 127
|
||||
fn char to_lower(char c) => @to_lower(c); // Convert A-Z to a-z if found
|
||||
fn char to_upper(char c) => @to_upper(c); // Convert a-z to A-Z if found
|
||||
|
||||
// The following methods are macro methods for the same functions
|
||||
macro bool char.is_lower(char c) => @is_lower(c);
|
||||
macro bool char.is_upper(char c) => @is_upper(c);
|
||||
macro bool char.is_digit(char c) => @is_digit(c);
|
||||
macro bool char.is_bdigit(char c) => @is_bdigit(c);
|
||||
macro bool char.is_odigit(char c) => @is_odigit(c);
|
||||
macro bool char.is_xdigit(char c) => @is_xdigit(c);
|
||||
macro bool char.is_alpha(char c) => @is_alpha(c);
|
||||
macro bool char.is_print(char c) => @is_print(c);
|
||||
macro bool char.is_graph(char c) => @is_graph(c);
|
||||
macro bool char.is_space(char c) => @is_space(c);
|
||||
macro bool char.is_alnum(char c) => @is_alnum(c);
|
||||
macro bool char.is_punct(char c) => @is_punct(c);
|
||||
macro bool char.is_blank(char c) => @is_blank(c);
|
||||
macro bool char.is_cntrl(char c) => @is_cntrl(c);
|
||||
macro char char.to_lower(char c) => @to_lower(c);
|
||||
macro char char.to_upper(char c) => @to_upper(c);
|
||||
|
||||
<*
|
||||
Convert a-f/A-F/0-9 to the appropriate hex value.
|
||||
|
||||
@require c.is_xdigit()
|
||||
@ensure return >= 0 && return <= 15
|
||||
*>
|
||||
macro char char.from_hex(char c) => HEX_VALUE[c];
|
||||
|
||||
<*
|
||||
Bitstruct containing the different properties of a character
|
||||
*>
|
||||
bitstruct CharType : ushort @private
|
||||
{
|
||||
bool lower;
|
||||
bool upper;
|
||||
bool digit;
|
||||
bool bin_digit;
|
||||
bool hex_digit;
|
||||
bool oct_digit;
|
||||
bool alpha;
|
||||
bool alphanum;
|
||||
bool space;
|
||||
bool printable;
|
||||
bool blank;
|
||||
bool punct;
|
||||
bool control;
|
||||
bool graph;
|
||||
}
|
||||
|
||||
const CharType[256] ASCII_LOOKUP @private = {
|
||||
[0..31] = { .control },
|
||||
[9..13] = { .control, .space },
|
||||
['\t'] = { .control, .space, .blank },
|
||||
[' '] = { .space, .printable, .blank },
|
||||
[33..126] = { .printable, .graph, .punct },
|
||||
['0'..'9'] = { .printable, .graph, .alphanum, .hex_digit, .digit },
|
||||
['2'..'7'] = { .printable, .graph, .alphanum, .hex_digit, .digit, .oct_digit },
|
||||
['0'..'1'] = { .printable, .graph, .alphanum, .hex_digit, .digit, .oct_digit, .bin_digit },
|
||||
['A'..'Z'] = { .printable, .graph, .alphanum, .alpha, .upper },
|
||||
['A'..'F'] = { .printable, .graph, .alphanum, .alpha, .upper, .hex_digit },
|
||||
['a'..'z'] = { .printable, .graph, .alphanum, .alpha, .lower },
|
||||
['a'..'f'] = { .printable, .graph, .alphanum, .alpha, .lower, .hex_digit },
|
||||
[127] = { .control },
|
||||
};
|
||||
|
||||
const char[256] HEX_VALUE = {
|
||||
['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4,
|
||||
['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9,
|
||||
['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14,
|
||||
['F'] = 15, ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13,
|
||||
['e'] = 14, ['f'] = 15
|
||||
};
|
||||
|
||||
const char[256] TO_UPPER @private = { ['a'..'z'] = 'a' - 'A' };
|
||||
const char[256] TO_LOWER @private = { ['A'..'Z'] = 'a' - 'A' };
|
||||
|
||||
typedef AsciiCharset = uint128;
|
||||
|
||||
macro AsciiCharset @create_set(String $string) @const
|
||||
{
|
||||
AsciiCharset $set;
|
||||
$foreach $c : $string:
|
||||
$set |= 1ULL << $c;
|
||||
$endforeach
|
||||
return $set;
|
||||
}
|
||||
|
||||
fn AsciiCharset create_set(String string)
|
||||
{
|
||||
AsciiCharset set;
|
||||
foreach (c : string) set |= (AsciiCharset)1ULL << c;
|
||||
return set;
|
||||
}
|
||||
|
||||
macro bool AsciiCharset.@contains($set, char $c) @const => !!($c < 128) & !!($set & (AsciiCharset)(1ULL << $c));
|
||||
|
||||
macro AsciiCharset @combine_sets(AsciiCharset $first, AsciiCharset... $sets) @const
|
||||
{
|
||||
var $res = $first;
|
||||
$foreach $c : $sets:
|
||||
$res |= $c;
|
||||
$endforeach
|
||||
return $res;
|
||||
}
|
||||
fn AsciiCharset combine_sets(AsciiCharset first, AsciiCharset... sets)
|
||||
{
|
||||
foreach (c : sets) first |= c;
|
||||
return first;
|
||||
}
|
||||
|
||||
macro bool AsciiCharset.contains(set, char c) => !!(c < 128) & !!(set & (AsciiCharset)(1ULL << c));
|
||||
|
||||
const AsciiCharset WHITESPACE_SET = @create_set("\t\n\v\f\r ");
|
||||
const AsciiCharset NUMBER_SET = @create_set("0123456789");
|
||||
const AsciiCharset ALPHA_UPPER_SET = @create_set("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
const AsciiCharset ALPHA_LOWER_SET = @create_set("abcdefghijklmnopqrstuvwxyz");
|
||||
const AsciiCharset ALPHA_SET = @combine_sets(ALPHA_UPPER_SET, ALPHA_LOWER_SET);
|
||||
const AsciiCharset ALPHANUMERIC_SET = @combine_sets(ALPHA_SET, NUMBER_SET);
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright (c) 2023 Christoffer Lerno and contributors. All rights reserved.
|
||||
// Copyright (c) 2023-2025 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::bitorder;
|
||||
|
||||
import std::bits;
|
||||
// This module contains types of different endianness.
|
||||
// *BE types represent big-endian types
|
||||
// *LE types represent little-endian types.
|
||||
@@ -88,93 +88,149 @@ bitstruct UInt128LE : uint128 @littleendian
|
||||
}
|
||||
|
||||
<*
|
||||
@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"
|
||||
@require $defined(*bytes) : "Pointer must be possible to dereference"
|
||||
@require types::is_intlike($typeof(*bytes)) : "Type must be an integer or int vector"
|
||||
*>
|
||||
macro read(bytes, $Type)
|
||||
macro load_be(bytes)
|
||||
{
|
||||
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;
|
||||
$if env::BIG_ENDIAN:
|
||||
return mem::load(bytes, $align: 1);
|
||||
$else
|
||||
return bswap(mem::load(bytes, $align: 1));
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require is_arrayptr_or_slice_of_char(bytes) : "argument must be a pointer to an array or a slice of char"
|
||||
@require $defined(*bytes) : "Pointer must be possible to dereference"
|
||||
@require types::is_intlike($typeof(*bytes)) : "Type must be an integer or int vector"
|
||||
*>
|
||||
macro load_le(bytes)
|
||||
{
|
||||
$if env::BIG_ENDIAN:
|
||||
return bswap(mem::load(bytes, $align: 1));
|
||||
$else
|
||||
return mem::load(bytes, $align: 1);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::is_intlike($typeof(value)) : "Type must be an integer or int vector"
|
||||
*>
|
||||
macro void store_be(void* dst, value)
|
||||
{
|
||||
$if env::BIG_ENDIAN:
|
||||
mem::store(($typeof(value)*)dst, value, $align: 1);
|
||||
$else
|
||||
mem::store(($typeof(value)*)dst, bswap(value), $align: 1);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::is_intlike($typeof(value)) : "Type must be an integer or int vector"
|
||||
*>
|
||||
macro void store_le(void* dst, value)
|
||||
{
|
||||
$if env::BIG_ENDIAN:
|
||||
mem::store(($typeof(value)*)dst, bswap(value), $align: 1);
|
||||
$else
|
||||
mem::store(($typeof(value)*)dst, value, $align: 1);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@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"
|
||||
@require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is too short to contain value"
|
||||
*>
|
||||
macro read(bytes, $Type)
|
||||
{
|
||||
char *ptr;
|
||||
$switch $kindof(bytes):
|
||||
$case POINTER:
|
||||
ptr = bytes;
|
||||
$default:
|
||||
ptr = bytes[..].ptr;
|
||||
$endswitch
|
||||
return bitcast(mem::load((char[$Type.sizeof]*)ptr, $align: 1), $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"
|
||||
@require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is not sufficent to hold value"
|
||||
*>
|
||||
macro write(x, bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch @typekind(bytes):
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
char *ptr;
|
||||
$switch $kindof(bytes):
|
||||
$case POINTER:
|
||||
ptr = bytes;
|
||||
$default:
|
||||
ptr = bytes[..].ptr;
|
||||
$endswitch
|
||||
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
|
||||
mem::store(($typeof(x)*)ptr, bitcast(x, $Type).val, 1);
|
||||
}
|
||||
|
||||
macro is_bitorder($Type)
|
||||
macro bool 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;
|
||||
$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)
|
||||
macro bool is_array_or_slice_of_char(bytes) @deprecated("Use @is_array_or_slice_of_char")
|
||||
{
|
||||
$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;
|
||||
return @is_array_or_slice_of_char(bytes);
|
||||
}
|
||||
|
||||
macro bool @is_array_or_slice_of_char(#bytes) @const
|
||||
{
|
||||
var $Type = $typeof(#bytes);
|
||||
$switch $Type.kindof:
|
||||
$case POINTER:
|
||||
typeid $inner = $Type.inner;
|
||||
return $inner.kindof == ARRAY &&& $inner.inner == char.typeid;
|
||||
$case ARRAY:
|
||||
$case SLICE:
|
||||
return $Type.inner == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_arrayptr_or_slice_of_char(bytes)
|
||||
macro bool is_arrayptr_or_slice_of_char(bytes) @deprecated("Use @is_arrayptr_or_slice_of_char")
|
||||
{
|
||||
$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;
|
||||
return @is_arrayptr_or_slice_of_char(bytes);
|
||||
}
|
||||
|
||||
macro bool @is_arrayptr_or_slice_of_char(#bytes) @const
|
||||
{
|
||||
var $Type = $typeof(#bytes);
|
||||
$switch $Type.kindof:
|
||||
$case POINTER:
|
||||
typeid $inner = $Type.inner;
|
||||
return $inner.kindof == ARRAY &&& $inner.inner == char.typeid;
|
||||
$case SLICE:
|
||||
return $Type.inner == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import libc, std::hash, std::io, std::os::backtrace;
|
||||
|
||||
<*
|
||||
EMPTY_MACRO_SLOT is a value used for implementing optional arguments for macros in an efficient
|
||||
way. It relies on the fact that distinct types are not implicitly convertable.
|
||||
way. It relies on the fact that distinct types are not implicitly convertible.
|
||||
|
||||
You can use `@is_empty_macro_slot()` and `@is_valid_macro_slot()` to figure out whether
|
||||
the argument has been used or not.
|
||||
@@ -18,33 +18,53 @@ import libc, std::hash, std::io, std::os::backtrace;
|
||||
macro foo(a, #b = EMPTY_MACRO_SLOT)
|
||||
{
|
||||
$if @is_valid_macro_slot(#b):
|
||||
return invoke_foo2(a, #b);
|
||||
$else
|
||||
return invoke_foo1(a);
|
||||
$endif
|
||||
return invoke_foo2(a, #b);
|
||||
$else
|
||||
return invoke_foo1(a);
|
||||
$endif
|
||||
}
|
||||
*>
|
||||
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
|
||||
const EmptySlot EMPTY_MACRO_SLOT @builtin @deprecated("Use `#arg = ...` instead.") = null;
|
||||
|
||||
typedef EmptySlot = void*;
|
||||
macro @is_empty_macro_slot(#arg) @const @builtin => @typeis(#arg, EmptySlot);
|
||||
macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
|
||||
typedef EmptySlot @constinit = void*;
|
||||
macro bool @is_empty_macro_slot(#arg) @const @builtin
|
||||
@deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.")
|
||||
=> $typeof(#arg) == EmptySlot;
|
||||
macro bool @is_valid_macro_slot(#arg) @const @builtin
|
||||
@deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.")
|
||||
=> $typeof(#arg) != EmptySlot;
|
||||
|
||||
<*
|
||||
Returns a random value at compile time.
|
||||
|
||||
@ensure return >= 0.0 && return < 1.0
|
||||
@return "A compile time random"
|
||||
*>
|
||||
macro @rnd() @const @builtin => $$rnd();
|
||||
|
||||
/*
|
||||
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
|
||||
Use `NO_MORE_ELEMENT` when reading the end of an iterator, or accessing a result out of bounds.
|
||||
*/
|
||||
faultdef NO_MORE_ELEMENT @builtin;
|
||||
|
||||
/*
|
||||
Use `SearchResult` when trying to return a value from some collection but the element is missing.
|
||||
Use `NOT_FOUND` when trying to return a value from some collection but the element is missing.
|
||||
*/
|
||||
faultdef NOT_FOUND @builtin;
|
||||
|
||||
/*
|
||||
Use `CastResult` when an attempt at conversion fails.
|
||||
Use `TYPE_MISMATCH` when an attempt at conversion fails.
|
||||
*/
|
||||
faultdef TYPE_MISMATCH @builtin;
|
||||
|
||||
/*
|
||||
Use `CAPACITY_EXCEEDED` when trying to add to a bounded list or similar.
|
||||
*/
|
||||
faultdef CAPACITY_EXCEEDED @builtin;
|
||||
/*
|
||||
Use `NOT_IMPLEMENTED` when something is conditionally available.
|
||||
*/
|
||||
faultdef NOT_IMPLEMENTED @builtin;
|
||||
|
||||
alias VoidFn = fn void();
|
||||
|
||||
@@ -53,7 +73,7 @@ alias VoidFn = fn void();
|
||||
macro scope.
|
||||
|
||||
@param #variable : `the variable to store and restore`
|
||||
@require values::@is_lvalue(#variable)
|
||||
@require $defined(#variable = #variable) : `Expected an actual variable`
|
||||
*>
|
||||
macro void @scope(#variable; @body) @builtin
|
||||
{
|
||||
@@ -73,94 +93,160 @@ macro void @swap(#a, #b) @builtin
|
||||
#b = temp;
|
||||
}
|
||||
|
||||
macro usz bitsizeof($Type) @builtin @const => $Type.sizeof * 8u;
|
||||
|
||||
macro usz @bitsizeof(#expr) @builtin @const => $sizeof(#expr) * 8u;
|
||||
|
||||
<*
|
||||
Compile-time check for whether a set of constants contains a certain expression.
|
||||
|
||||
@param #needle : "The expression whose value should be located."
|
||||
*>
|
||||
macro bool @in(#needle, ...) @builtin @const
|
||||
{
|
||||
$for var $x = 0; $x < $vacount; $x++:
|
||||
$assert $defined(#needle == $vaconst[$x])
|
||||
: "Index %s: types '%s' (needle) and '%s' are not equatable", $x, $typeof(#needle), $typeof($vaconst[$x]);
|
||||
$if #needle == $vaconst[$x]: return true; $endif
|
||||
$endfor
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
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*)
|
||||
@ensure $typeof(return) == $Type*
|
||||
@return? TYPE_MISMATCH
|
||||
*>
|
||||
macro anycast(any v, $Type) @builtin
|
||||
{
|
||||
if (v.type != $Type.typeid) return TYPE_MISMATCH?;
|
||||
if (v.type != $Type.typeid) return TYPE_MISMATCH~;
|
||||
return ($Type*)v.ptr;
|
||||
}
|
||||
|
||||
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
|
||||
<*
|
||||
@return "The value in the pointer"
|
||||
@return? TYPE_MISMATCH
|
||||
*>
|
||||
macro any.to(self, $Type)
|
||||
{
|
||||
if (self.type != $Type.typeid) return TYPE_MISMATCH~;
|
||||
return *($Type*)self.ptr;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.type == $Type : "The 'any' contained an unexpected type."
|
||||
@return "The value in the pointer"
|
||||
*>
|
||||
macro any.as(self, $Type)
|
||||
{
|
||||
return *($Type*)self.ptr;
|
||||
}
|
||||
|
||||
macro bool @assignable_to(#foo, $Type) @const @builtin @deprecated("use '$defined($Type x = #foo)'") => $defined(*&&($Type){} = #foo);
|
||||
|
||||
macro @addr(#val) @builtin
|
||||
{
|
||||
$if $defined(&#val):
|
||||
return &#val;
|
||||
$else
|
||||
return &&#val;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro typeid @typeid(#value) @const @builtin
|
||||
{
|
||||
return $typeof(#value).typeid;
|
||||
}
|
||||
|
||||
macro TypeKind @typekind(#value) @const @builtin @deprecated("Use `$kindof(#value)`.")
|
||||
{
|
||||
return $kindof(#value);
|
||||
}
|
||||
|
||||
macro bool @typeis(#value, $Type) @const @builtin @deprecated("Use `$typeof(#value) == $Type` instead.")
|
||||
{
|
||||
return $typeof(#value).typeid == $Type.typeid;
|
||||
}
|
||||
|
||||
|
||||
fn bool print_backtrace(String message, int backtraces_to_ignore, void *added_backtrace = null) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
|
||||
{
|
||||
Allocator t = allocator::current_temp;
|
||||
TempAllocator* new_t = allocator::new_temp_allocator(smem, 0x1000)!!;
|
||||
allocator::current_temp = new_t;
|
||||
defer
|
||||
{
|
||||
allocator::current_temp = t;
|
||||
new_t.free();
|
||||
}
|
||||
void*[256] buffer;
|
||||
void*[] backtraces = backtrace::capture_current(&buffer);
|
||||
backtraces_to_ignore++;
|
||||
BacktraceList? backtrace = backtrace::symbolize_backtrace(tmem, backtraces);
|
||||
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 (added_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);
|
||||
backtraces[++backtraces_to_ignore] = added_backtrace;
|
||||
}
|
||||
@stack_mem(4096; Allocator mem)
|
||||
{
|
||||
BacktraceList? backtrace = backtrace::symbolize_backtrace(mem, backtraces);
|
||||
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);
|
||||
}
|
||||
in_panic = true;
|
||||
$if $defined(io::stderr) && env::PANIC_MSG:
|
||||
if (!print_backtrace(message, 2))
|
||||
{
|
||||
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
|
||||
}
|
||||
$endif
|
||||
$$trap();
|
||||
|
||||
}
|
||||
|
||||
macro void abort(String string = "Unrecoverable error reached", ...) @builtin @noreturn
|
||||
macro void abort(String string = "Unrecoverable error reached", ...) @format(0) @builtin @noreturn
|
||||
{
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
|
||||
$$trap();
|
||||
}
|
||||
|
||||
bool in_panic @local = false;
|
||||
bool in_panic @private = false;
|
||||
|
||||
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
|
||||
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);
|
||||
$if $defined(io::stderr) && env::PANIC_MSG:
|
||||
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;
|
||||
$endif
|
||||
in_panic = false;
|
||||
$$trap();
|
||||
}
|
||||
|
||||
@@ -170,21 +256,23 @@ PanicFn panic = &default_panic;
|
||||
|
||||
fn void panicf(String fmt, String file, String function, uint line, args...)
|
||||
{
|
||||
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(allocator);
|
||||
s.appendf(fmt, ...args);
|
||||
in_panic = false;
|
||||
panic(s.str_view(), file, function, line);
|
||||
};
|
||||
$if $defined(io::stderr) && env::PANIC_MSG:
|
||||
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(allocator);
|
||||
s.appendf(fmt, ...args);
|
||||
in_panic = false;
|
||||
panic(s.str_view(), file, function, line);
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -196,8 +284,9 @@ macro void unreachable(String string = "Unreachable statement reached.", ...) @b
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE:
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
|
||||
$endif;
|
||||
$else
|
||||
$$unreachable();
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -238,7 +327,7 @@ macro any.as_inner(&self)
|
||||
@param $Type : "the type to cast to"
|
||||
|
||||
@require $sizeof(expr) == $Type.sizeof : "Cannot bitcast between types of different size."
|
||||
@ensure @typeis(return, $Type)
|
||||
@ensure $typeof(return) == $Type*
|
||||
*>
|
||||
macro bitcast(expr, $Type) @builtin
|
||||
{
|
||||
@@ -255,7 +344,7 @@ macro bitcast(expr, $Type) @builtin
|
||||
@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)
|
||||
@ensure $typeof(return) == $Type*
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro enum_by_name($Type, String enum_name) @builtin
|
||||
@@ -265,25 +354,24 @@ macro enum_by_name($Type, String enum_name) @builtin
|
||||
{
|
||||
if (name == enum_name) return $Type.from_ordinal(i);
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@param $Type : `The type of the enum`
|
||||
@require $Type.kindof == ENUM : `Only enums may be used`
|
||||
@require $defined($Type.#value) : `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)
|
||||
@require $defined($typeof(($Type){}.#value) v = value) : `Expected the value to match the type of the associated value`
|
||||
@ensure $typeof(return) == $Type*
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro @enum_from_value($Type, #value, value) @builtin
|
||||
macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.lookup_field and Enum.lookup")
|
||||
{
|
||||
usz elements = $Type.elements;
|
||||
foreach (e : $Type.values)
|
||||
{
|
||||
if (e.#value == value) return e;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -326,7 +414,7 @@ macro bool @unlikely(bool #value, $probability = 1.0) @builtin
|
||||
|
||||
<*
|
||||
@require values::@is_int(#value) || values::@is_bool(#value)
|
||||
@require $assignable(expected, $typeof(#value))
|
||||
@require $defined($typeof(#value) v = expected)
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro @expect(#value, expected, $probability = 1.0) @builtin
|
||||
@@ -367,20 +455,62 @@ macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write =
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Shuffle a vector by its index
|
||||
|
||||
int[<4>] a = { 1, 2, 3, 4 };
|
||||
assert(swizzle(a, 0, 1, 1, 3) == (int[<4>]) { 1, 2, 2, 4 });
|
||||
*>
|
||||
macro swizzle(v, ...) @builtin
|
||||
{
|
||||
return $$swizzle(v, $vasplat);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Shuffle two vectors by a common index from arranging the vectors sequentially in memory
|
||||
|
||||
int[<4>] a = { 1, 2, 3, 4 };
|
||||
int[<4>] b = { 100, 1000, 10000, 100000 };
|
||||
assert(swizzle2(a, b, 0, 1, 4, 6, 2) == (int[<5>]) { 1, 2, 100, 10000, 3 });
|
||||
*>
|
||||
macro swizzle2(v, v2, ...) @builtin
|
||||
{
|
||||
return $$swizzle2(v, v2, $vasplat);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns the count of leading zero bits from an integer at compile-time.
|
||||
|
||||
@require types::is_int($typeof($value)) : "Input value must be an integer"
|
||||
@require $sizeof($value) * 8 <= 128 : "Input value must be 128 bits wide or lower"
|
||||
*>
|
||||
macro uint @clz($value) @builtin @const
|
||||
{
|
||||
$if $value == 0:
|
||||
return $sizeof($value) * 8; // it's all leading zeroes
|
||||
$endif
|
||||
|
||||
usz $n = 0;
|
||||
uint128 $x = (uint128)$value;
|
||||
|
||||
$if $x <= 0x0000_0000_0000_0000_FFFF_FFFF_FFFF_FFFF: $n += 64; $x <<= 64; $endif
|
||||
$if $x <= 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 32; $x <<= 32; $endif
|
||||
$if $x <= 0x0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 16; $x <<= 16; $endif
|
||||
$if $x <= 0x00FF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 8; $x <<= 8; $endif
|
||||
$if $x <= 0x0FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 4; $x <<= 4; $endif
|
||||
$if $x <= 0x3FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 2; $x <<= 2; $endif
|
||||
$if $x <= 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 1; $endif
|
||||
|
||||
return $n % ($sizeof($value) * 8); // mod by the bitsize of the input value to go back from uint128 -> it's-type
|
||||
}
|
||||
|
||||
<*
|
||||
Return the excuse in the Optional if it is Empty, otherwise
|
||||
return a null fault.
|
||||
|
||||
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
|
||||
@require $kindof(#expr) == OPTIONAL : `@catch expects an Optional value`
|
||||
*>
|
||||
macro fault @catch(#expr) @builtin
|
||||
{
|
||||
@@ -392,7 +522,7 @@ macro fault @catch(#expr) @builtin
|
||||
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`
|
||||
@require $kindof(#expr) == OPTIONAL : `@ok expects an Optional value`
|
||||
*>
|
||||
macro bool @ok(#expr) @builtin
|
||||
{
|
||||
@@ -400,6 +530,67 @@ macro bool @ok(#expr) @builtin
|
||||
return true;
|
||||
}
|
||||
|
||||
<*
|
||||
Check if an Optional expression evaluates to a fault. If so, return it;
|
||||
else, assign the result to an expression.
|
||||
|
||||
@require $defined(#v = #v) : "#v must be a variable"
|
||||
@require $defined(#expr!) : "Expected an optional expression"
|
||||
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
|
||||
*>
|
||||
macro void? @try(#v, #expr) @builtin @maydiscard
|
||||
{
|
||||
var res = #expr;
|
||||
if (catch err = res) return err~;
|
||||
#v = res;
|
||||
}
|
||||
|
||||
<*
|
||||
Check if an Optional expression evaluates to a fault. If so, return true if it is the
|
||||
expected fault, the optional if it is unexpected, or false if there was no fault and
|
||||
the assign happened.
|
||||
|
||||
This can be used in like this:
|
||||
|
||||
while (true)
|
||||
{
|
||||
char[] data;
|
||||
// Read until end of file
|
||||
if (@try_catch(data, load_line(), io::EOF)!) break;
|
||||
.. use data ..
|
||||
}
|
||||
|
||||
In this example we read until we reach an EOF, which is expected. However, if we encounter some other
|
||||
fault, we rethrow is. Without this macro, the code is instead written like:
|
||||
|
||||
while (true)
|
||||
{
|
||||
char[]? data;
|
||||
data = load_line();
|
||||
if (catch err = data)
|
||||
{
|
||||
if (err = io::EOF) break;
|
||||
return err?
|
||||
}
|
||||
.. use data ..
|
||||
}
|
||||
|
||||
@require $defined(#v = #v) : "#v must be a variable"
|
||||
@require $defined(#expr!) : "Expected an optional expression"
|
||||
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
|
||||
@return "True if it was the expected fault, false if the variable was assigned, otherwise returns an optional."
|
||||
*>
|
||||
macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin
|
||||
{
|
||||
var res = #expr;
|
||||
if (catch err = res)
|
||||
{
|
||||
return err == expected_fault ? true : err~;
|
||||
}
|
||||
#v = res;
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(&#value, (char*)&#value) : "This must be a value that can be viewed as a char array"
|
||||
*>
|
||||
@@ -412,6 +603,27 @@ macro isz @str_find(String $string, String $needle) @builtin => $$str_find($stri
|
||||
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 String @str_pascalcase(String $str) @builtin => $$str_pascalcase($str);
|
||||
macro String @str_snakecase(String $str) @builtin => $$str_snakecase($str);
|
||||
macro String @str_camelcase(String $str) @builtin => @str_capitalize($$str_pascalcase($str));
|
||||
macro String @str_constantcase(String $str) @builtin => @str_upper($$str_snakecase($str));
|
||||
macro String @str_replace(String $str, String $pattern, String $replace, uint $limit = 0) @builtin => $$str_replace($str, $pattern, $replace, $limit);
|
||||
macro String @str_capitalize(String $str) @builtin
|
||||
{
|
||||
$switch $str.len:
|
||||
$case 0: return $str;
|
||||
$case 1: return $$str_upper($str);
|
||||
$default: return $$str_upper($str[0:1]) +++ $str[1..];
|
||||
$endswitch
|
||||
}
|
||||
macro String @str_uncapitalize(String $str) @builtin
|
||||
{
|
||||
$switch $str.len:
|
||||
$case 0: return $str;
|
||||
$case 1: return $$str_lower($str);
|
||||
$default: return $$str_lower($str[0:1]) +++ $str[1..];
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro @generic_hash_core(h, value)
|
||||
{
|
||||
@@ -421,7 +633,7 @@ macro @generic_hash_core(h, value)
|
||||
return h;
|
||||
}
|
||||
|
||||
macro @generic_hash(value)
|
||||
macro uint @generic_hash(value)
|
||||
{
|
||||
uint h = @generic_hash_core((uint)0x3efd4391, value);
|
||||
$for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4:
|
||||
@@ -431,22 +643,75 @@ macro @generic_hash(value)
|
||||
return h;
|
||||
}
|
||||
|
||||
macro uint int.hash(int i) => @generic_hash(i);
|
||||
macro uint uint.hash(uint i) => @generic_hash(i);
|
||||
macro uint short.hash(short s) => @generic_hash(s);
|
||||
macro uint ushort.hash(ushort s) => @generic_hash(s);
|
||||
macro uint char.hash(char c) => @generic_hash(c);
|
||||
macro uint ichar.hash(ichar c) => @generic_hash(c);
|
||||
macro uint long.hash(long i) => @generic_hash(i);
|
||||
macro uint ulong.hash(ulong i) => @generic_hash(i);
|
||||
macro uint int128.hash(int128 i) => @generic_hash(i);
|
||||
macro uint uint128.hash(uint128 i) => @generic_hash(i);
|
||||
macro uint bool.hash(bool b) => @generic_hash(b);
|
||||
|
||||
macro uint int128.hash(self) => @generic_hash(self);
|
||||
macro uint uint128.hash(self) => @generic_hash(self);
|
||||
macro uint long.hash(self) => @generic_hash(self);
|
||||
macro uint ulong.hash(self) => @generic_hash(self);
|
||||
macro uint int.hash(self) => @generic_hash(self);
|
||||
macro uint uint.hash(self) => @generic_hash(self);
|
||||
macro uint short.hash(self) => @generic_hash(self);
|
||||
macro uint ushort.hash(self) => @generic_hash(self);
|
||||
macro uint ichar.hash(self) => @generic_hash(self);
|
||||
macro uint char.hash(self) => @generic_hash(self);
|
||||
macro uint bool.hash(self) => @generic_hash(self);
|
||||
|
||||
macro uint int128[*].hash(&self) => hash_array(self);
|
||||
macro uint uint128[*].hash(&self) => hash_array(self);
|
||||
macro uint long[*].hash(&self) => hash_array(self);
|
||||
macro uint ulong[*].hash(&self) => hash_array(self);
|
||||
macro uint int[*].hash(&self) => hash_array(self);
|
||||
macro uint uint[*].hash(&self) => hash_array(self);
|
||||
macro uint short[*].hash(&self) => hash_array(self);
|
||||
macro uint ushort[*].hash(&self) => hash_array(self);
|
||||
macro uint char[*].hash(&self) => hash_array(self);
|
||||
macro uint ichar[*].hash(&self) => hash_array(self);
|
||||
macro uint bool[*].hash(&self) => hash_array(self);
|
||||
|
||||
macro uint int128[<*>].hash(self) => hash_vec(self);
|
||||
macro uint uint128[<*>].hash(self) => hash_vec(self);
|
||||
macro uint long[<*>].hash(self) => hash_vec(self);
|
||||
macro uint ulong[<*>].hash(self) => hash_vec(self);
|
||||
macro uint int[<*>].hash(self) => hash_vec(self);
|
||||
macro uint uint[<*>].hash(self) => hash_vec(self);
|
||||
macro uint short[<*>].hash(self) => hash_vec(self);
|
||||
macro uint ushort[<*>].hash(self) => hash_vec(self);
|
||||
macro uint char[<*>].hash(self) => hash_vec(self);
|
||||
macro uint ichar[<*>].hash(self) => hash_vec(self);
|
||||
macro uint bool[<*>].hash(self) => hash_vec(self);
|
||||
|
||||
macro uint typeid.hash(typeid t) => @generic_hash(((ulong)(uptr)t));
|
||||
macro uint String.hash(String c) => (uint)fnv32a::hash(c);
|
||||
macro uint char[].hash(char[] c) => (uint)fnv32a::hash(c);
|
||||
macro uint String.hash(String c) => (uint)a5hash::hash(c);
|
||||
macro uint char[].hash(char[] c) => (uint)a5hash::hash(c);
|
||||
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
|
||||
|
||||
<*
|
||||
@require $kindof(array_ptr) == POINTER &&& $kindof(*array_ptr) == ARRAY
|
||||
*>
|
||||
macro uint hash_array(array_ptr) @local
|
||||
{
|
||||
var $len = $sizeof(*array_ptr);
|
||||
|
||||
$if $len > 16:
|
||||
return (uint)komi::hash(((char*)array_ptr)[:$len]);
|
||||
$else
|
||||
return (uint)wyhash2::hash(((char*)array_ptr)[:$len]);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $kindof(vec) == VECTOR
|
||||
*>
|
||||
macro uint hash_vec(vec) @local
|
||||
{
|
||||
var $len = $sizeof(vec);
|
||||
|
||||
$if $len > 16:
|
||||
return (uint)komi::hash(((char*)&&vec)[:$len]);
|
||||
$else
|
||||
return (uint)wyhash2::hash(((char*)&&vec)[:$len]);
|
||||
$endif
|
||||
}
|
||||
|
||||
const MAX_FRAMEADDRESS = 128;
|
||||
<*
|
||||
@@ -731,58 +996,74 @@ macro void* get_returnaddress(int n)
|
||||
}
|
||||
}
|
||||
|
||||
module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
|
||||
import libc, std::io;
|
||||
module std::core::builtin @if((env::LINUX || env::ANDROID || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
|
||||
import libc, std::io, std::os::posix;
|
||||
|
||||
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)
|
||||
fn void sig_bus_error(CInt i, void* info, void* context)
|
||||
{
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Illegal memory access.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Illegal memory access.", 1))
|
||||
if (!print_backtrace("Illegal memory access.", 2, posix::stack_instruction(context)))
|
||||
{
|
||||
io::eprintn("\nERROR: 'Illegal memory access'.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
$$trap();
|
||||
os::fastexit(128 + i);
|
||||
}
|
||||
|
||||
fn void sig_segmentation_fault(CInt i)
|
||||
fn void sig_segmentation_fault(CInt i, void* p1, void* context)
|
||||
{
|
||||
$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))
|
||||
if (!print_backtrace("Out of bounds memory access.", 2, posix::stack_instruction(context)))
|
||||
{
|
||||
io::eprintn("\nERROR: Memory error without backtrace, possible stack overflow.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
$$trap();
|
||||
os::fastexit(128 + i);
|
||||
}
|
||||
|
||||
fn void install_signal_handler(CInt signal, SignalFunction func) @local
|
||||
fn void sig_illegal_instruction(CInt i, void* p1, void* context)
|
||||
{
|
||||
SignalFunction old = libc::signal(signal, func);
|
||||
// Restore
|
||||
if ((iptr)old > 1024) libc::signal(signal, old);
|
||||
if (in_panic) os::fastexit(128 + i);
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Illegal instruction.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Illegal instruction.", 2, posix::stack_instruction(context)))
|
||||
{
|
||||
io::eprintn("\nERROR: Illegal instruction.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
os::fastexit(128 + i);
|
||||
}
|
||||
|
||||
char[64 * 1024] sig_stack @local @if(env::BACKTRACE && env::LINUX);
|
||||
|
||||
// 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);
|
||||
}
|
||||
$if env::LINUX:
|
||||
Stack_t ss = {
|
||||
.ss_sp = &sig_stack,
|
||||
.ss_size = sig_stack.len
|
||||
};
|
||||
libc::sigaltstack(&ss, null);
|
||||
$endif
|
||||
|
||||
posix::install_signal_handler(libc::SIGBUS, &sig_bus_error);
|
||||
posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
|
||||
posix::install_signal_handler(libc::SIGILL, &sig_illegal_instruction);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ module std::core::builtin;
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro less(a, b) @builtin
|
||||
macro bool less(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -21,7 +21,7 @@ macro less(a, b) @builtin
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro less_eq(a, b) @builtin
|
||||
macro bool less_eq(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -36,7 +36,7 @@ macro less_eq(a, b) @builtin
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro greater(a, b) @builtin
|
||||
macro bool greater(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -65,7 +65,7 @@ macro int compare_to(a, b) @builtin
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro greater_eq(a, b) @builtin
|
||||
macro bool greater_eq(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -126,3 +126,36 @@ macro max(x, ...) @builtin
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require types::is_numerical($typeof($a))
|
||||
*>
|
||||
macro @max($a, ...) @builtin @const
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return $a > $vaconst[0] ? $a : $vaconst[0];
|
||||
$else
|
||||
var $result = $a;
|
||||
$for var $x = 0; $x < $vacount; ++$x:
|
||||
$if $vaconst[$x] > $result: $result = $vaconst[$x]; $endif
|
||||
$endfor
|
||||
return $result;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::is_numerical($typeof($a))
|
||||
*>
|
||||
macro @min($a, ...) @builtin @const
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return $a < $vaconst[0] ? $a : $vaconst[0];
|
||||
$else
|
||||
var $result = $a;
|
||||
$for var $x = 0; $x < $vacount; ++$x:
|
||||
$if $vaconst[$x] < $result: $result = $vaconst[$x]; $endif
|
||||
$endfor
|
||||
return $result;
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// 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;
|
||||
import std::core::env;
|
||||
|
||||
|
||||
const C_INT_SIZE = $$C_INT_SIZE;
|
||||
const C_LONG_SIZE = $$C_LONG_SIZE;
|
||||
@@ -59,3 +61,57 @@ macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
|
||||
$default: $error("Invalid bitsize");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
const USE_STACK_VALIST = env::ARCH_32_BIT || env::WIN32 || (env::DARWIN && env::AARCH64);
|
||||
module std::core::cinterop @if(USE_STACK_VALIST);
|
||||
|
||||
typedef CVaList = void*;
|
||||
macro CVaList.next(&self, $Type)
|
||||
{
|
||||
void *ptr = mem::aligned_pointer((void*)*self, max($Type.alignof, 8));
|
||||
defer *self = (CVaList)(ptr + 1);
|
||||
return *($Type*)ptr;
|
||||
}
|
||||
|
||||
module std::core::cinterop @if(env::X86_64 && !env::WIN32);
|
||||
|
||||
struct CVaListData
|
||||
{
|
||||
uint gp_offset;
|
||||
uint fp_offset;
|
||||
void *overflow_arg_area;
|
||||
void *reg_save_area;
|
||||
}
|
||||
|
||||
typedef CVaList = CVaListData*;
|
||||
|
||||
macro CVaList.next(self, $Type)
|
||||
{
|
||||
CVaListData* data = (CVaListData*)self;
|
||||
$switch:
|
||||
$case $Type.kindof == FLOAT ||| ($Type.kindof == VECTOR && $Type.sizeof <= 16):
|
||||
var $LoadType = $Type.sizeof < 8 ? double : $Type;
|
||||
if (data.fp_offset < 6 * 8 + 8 * 16 )
|
||||
{
|
||||
defer data.fp_offset += (uint)mem::aligned_offset($Type.sizeof, 16);
|
||||
return ($Type)*($LoadType*)(data.reg_save_area + data.fp_offset);
|
||||
}
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return ($Type)*($LoadType*)ptr;
|
||||
$case $Type.kindof == SIGNED_INT || $Type.kindof == UNSIGNED_INT:
|
||||
var $LoadType = $Type.sizeof < 4 ? int : $Type;
|
||||
if (data.gp_offset < 6 * 8 && $Type.sizeof <= 8)
|
||||
{
|
||||
defer data.gp_offset += (uint)mem::aligned_offset($Type.sizeof, 8);
|
||||
return ($Type)*($LoadType*)(data.reg_save_area + data.gp_offset);
|
||||
}
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return ($Type)*($LoadType*)ptr;
|
||||
$default:
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return *($Type*)ptr;
|
||||
$endswitch
|
||||
}
|
||||
@@ -16,25 +16,25 @@ const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
|
||||
*>
|
||||
fn usz? char32_to_utf8(Char32 c, char[] output)
|
||||
{
|
||||
if (!output.len) return string::CONVERSION_FAILED?;
|
||||
if (!output.len) return string::CONVERSION_FAILED~;
|
||||
switch (true)
|
||||
{
|
||||
case c <= 0x7f:
|
||||
output[0] = (char)c;
|
||||
return 1;
|
||||
case c <= 0x7ff:
|
||||
if (output.len < 2) return string::CONVERSION_FAILED?;
|
||||
if (output.len < 2) return string::CONVERSION_FAILED~;
|
||||
output[0] = (char)(0xC0 | c >> 6);
|
||||
output[1] = (char)(0x80 | (c & 0x3F));
|
||||
return 2;
|
||||
case c <= 0xffff:
|
||||
if (output.len < 3) return string::CONVERSION_FAILED?;
|
||||
if (output.len < 3) return string::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 string::CONVERSION_FAILED?;
|
||||
if (output.len < 4) return string::CONVERSION_FAILED~;
|
||||
output[0] = (char)(0xF0 | c >> 18);
|
||||
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
@@ -42,7 +42,7 @@ fn usz? char32_to_utf8(Char32 c, char[] output)
|
||||
return 4;
|
||||
default:
|
||||
// 0x10FFFF and above is not defined.
|
||||
return string::CONVERSION_FAILED?;
|
||||
return string::CONVERSION_FAILED~;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,15 +84,15 @@ fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
return;
|
||||
}
|
||||
// Low surrogate first is an error
|
||||
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return string::INVALID_UTF16?;
|
||||
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return string::INVALID_UTF16~;
|
||||
|
||||
// Unmatched high surrogate is an error
|
||||
if (*available == 1) return string::INVALID_UTF16?;
|
||||
if (*available == 1) return string::INVALID_UTF16~;
|
||||
|
||||
Char16 low = ptr[1];
|
||||
|
||||
// Unmatched high surrogate, invalid
|
||||
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return string::INVALID_UTF16?;
|
||||
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return string::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
|
||||
@@ -138,7 +138,7 @@ fn usz char32_to_utf8_unsafe(Char32 c, char** output)
|
||||
fn Char32? utf8_to_char32(char* ptr, usz* size)
|
||||
{
|
||||
usz max_size = *size;
|
||||
if (max_size < 1) return string::INVALID_UTF8?;
|
||||
if (max_size < 1) return string::INVALID_UTF8~;
|
||||
char c = (ptr++)[0];
|
||||
|
||||
if ((c & 0x80) == 0)
|
||||
@@ -148,40 +148,40 @@ fn Char32? utf8_to_char32(char* ptr, usz* size)
|
||||
}
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
if (max_size < 2) return string::INVALID_UTF8?;
|
||||
if (max_size < 2) return string::INVALID_UTF8~;
|
||||
*size = 2;
|
||||
Char32 uc = (c & 0x1F) << 6;
|
||||
c = *ptr;
|
||||
// Overlong sequence or invalid second.
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
if (max_size < 3) return string::INVALID_UTF8?;
|
||||
if (max_size < 3) return string::INVALID_UTF8~;
|
||||
*size = 3;
|
||||
Char32 uc = (c & 0x0F) << 12;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
uc += (c & 0x3F) << 6;
|
||||
c = ptr++[0];
|
||||
// Overlong sequence or invalid last
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if (max_size < 4) return string::INVALID_UTF8?;
|
||||
if ((c & 0xF8) != 0xF0) return string::INVALID_UTF8?;
|
||||
if (max_size < 4) return string::INVALID_UTF8~;
|
||||
if ((c & 0xF8) != 0xF0) return string::INVALID_UTF8~;
|
||||
*size = 4;
|
||||
Char32 uc = (c & 0x07) << 18;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
uc += (c & 0x3F) << 12;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
uc += (c & 0x3F) << 6;
|
||||
c = ptr++[0];
|
||||
// Overlong sequence or invalid last
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
usz buf_len = utf32_buffer.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
if (len32 == buf_len) return string::CONVERSION_FAILED?;
|
||||
if (len32 == buf_len) return string::CONVERSION_FAILED~;
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
module std::core::dstring;
|
||||
import std::io;
|
||||
import std::io, std::math;
|
||||
|
||||
<*
|
||||
The DString offers a dynamic string builder.
|
||||
*>
|
||||
typedef DString (OutStream) = DStringOpaque*;
|
||||
typedef DStringOpaque = void;
|
||||
|
||||
const usz MIN_CAPACITY @private = 16;
|
||||
|
||||
<*
|
||||
Initialize the DString with a particular allocator.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param capacity : "Starting capacity, defaults to MIN_CAPACITY and cannot be smaller"
|
||||
@return "Return the DString itself"
|
||||
@require !self.data() : "String already initialized"
|
||||
*>
|
||||
fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY)
|
||||
@@ -20,6 +28,11 @@ fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY)
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize the DString with the temp allocator. Note that if the dstring is never
|
||||
initialized, this is the allocator it will default to.
|
||||
|
||||
@param capacity : "Starting capacity, defaults to MIN_CAPACITY and cannot be smaller"
|
||||
@return "Return the DString itself"
|
||||
@require !self.data() : "String already initialized"
|
||||
*>
|
||||
fn DString DString.tinit(&self, usz capacity = MIN_CAPACITY)
|
||||
@@ -82,7 +95,7 @@ fn void DString.replace(&self, String needle, String replacement)
|
||||
match++;
|
||||
if (match == needle_len)
|
||||
{
|
||||
self.append_chars(replacement);
|
||||
self.append_string(replacement);
|
||||
match = 0;
|
||||
continue;
|
||||
}
|
||||
@@ -90,16 +103,16 @@ fn void DString.replace(&self, String needle, String replacement)
|
||||
}
|
||||
if (match > 0)
|
||||
{
|
||||
self.append_chars(str[i - match:match]);
|
||||
self.append_string(str[i - match:match]);
|
||||
match = 0;
|
||||
}
|
||||
self.append_char(c);
|
||||
}
|
||||
if (match > 0) self.append_chars(str[^match:match]);
|
||||
if (match > 0) self.append_string(str[^match:match]);
|
||||
};
|
||||
}
|
||||
|
||||
fn DString DString.concat(self, Allocator allocator, DString b)
|
||||
fn DString DString.concat(self, Allocator allocator, DString b) @nodiscard
|
||||
{
|
||||
DString string;
|
||||
string.init(allocator, self.len() + b.len());
|
||||
@@ -220,7 +233,7 @@ fn usz DString.append_char32(&self, Char32 c)
|
||||
|
||||
fn DString DString.tcopy(&self) => self.copy(tmem);
|
||||
|
||||
fn DString DString.copy(self, Allocator allocator)
|
||||
fn DString DString.copy(self, Allocator allocator) @nodiscard
|
||||
{
|
||||
if (!self) return new(allocator);
|
||||
StringData* data = self.data();
|
||||
@@ -229,7 +242,7 @@ fn DString DString.copy(self, Allocator allocator)
|
||||
return new_string;
|
||||
}
|
||||
|
||||
fn ZString DString.copy_zstr(self, Allocator allocator)
|
||||
fn ZString DString.copy_zstr(self, Allocator allocator) @nodiscard
|
||||
{
|
||||
usz str_len = self.len();
|
||||
if (!str_len)
|
||||
@@ -243,12 +256,12 @@ fn ZString DString.copy_zstr(self, Allocator allocator)
|
||||
return (ZString)zstr;
|
||||
}
|
||||
|
||||
fn String DString.copy_str(self, Allocator allocator)
|
||||
fn String DString.copy_str(self, Allocator allocator) @nodiscard
|
||||
{
|
||||
return (String)self.copy_zstr(allocator)[:self.len()];
|
||||
}
|
||||
|
||||
fn String DString.tcopy_str(self) => self.copy_str(tmem) @inline;
|
||||
fn String DString.tcopy_str(self) @nodiscard => self.copy_str(tmem) @inline;
|
||||
|
||||
fn bool DString.equals(self, DString other_string)
|
||||
{
|
||||
@@ -292,18 +305,23 @@ fn bool DString.less(self, DString other_string)
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void DString.append_chars(&self, String str)
|
||||
fn void DString.append_chars(&self, String str) @deprecated("Use append_string")
|
||||
{
|
||||
usz other_len = str.len;
|
||||
self.append_bytes(str);
|
||||
}
|
||||
|
||||
fn void DString.append_bytes(&self, char[] bytes)
|
||||
{
|
||||
usz other_len = bytes.len;
|
||||
if (!other_len) return;
|
||||
if (!*self)
|
||||
{
|
||||
*self = temp(str);
|
||||
*self = temp((String)bytes);
|
||||
return;
|
||||
}
|
||||
self.reserve(other_len);
|
||||
StringData* data = self.data();
|
||||
mem::copy(&data.chars[data.len], str.ptr, other_len);
|
||||
mem::copy(&data.chars[data.len], bytes.ptr, other_len);
|
||||
data.len += other_len;
|
||||
}
|
||||
|
||||
@@ -312,7 +330,24 @@ fn Char32[] DString.copy_utf32(&self, Allocator allocator)
|
||||
return self.str_view().to_utf32(allocator) @inline!!;
|
||||
}
|
||||
|
||||
fn void DString.append_string(&self, DString str)
|
||||
<*
|
||||
@require $defined(String s = str) ||| $typeof(str) == DString : "Expected string or DString"
|
||||
*>
|
||||
macro void DString.append_string(&self, str)
|
||||
{
|
||||
$if $typeof(str) == DString:
|
||||
self.append_string_deprecated(str);
|
||||
$else
|
||||
self.append_bytes((String)str);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void DString.append_string_deprecated(&self, DString str) @deprecated("Use .append_dstring()")
|
||||
{
|
||||
self.append_dstring(str);
|
||||
}
|
||||
|
||||
fn void DString.append_dstring(&self, DString str)
|
||||
{
|
||||
StringData* other = str.data();
|
||||
if (!other) return;
|
||||
@@ -327,7 +362,7 @@ fn void DString.clear(self)
|
||||
|
||||
fn usz? DString.write(&self, char[] buffer) @dynamic
|
||||
{
|
||||
self.append_chars((String)buffer);
|
||||
self.append_bytes(buffer);
|
||||
return buffer.len;
|
||||
}
|
||||
|
||||
@@ -387,9 +422,9 @@ macro void DString.append(&self, value)
|
||||
$case ichar:
|
||||
self.append_char(value);
|
||||
$case DString:
|
||||
self.append_string(value);
|
||||
self.append_dstring(value);
|
||||
$case String:
|
||||
self.append_chars(value);
|
||||
self.append_string(value);
|
||||
$case Char32:
|
||||
self.append_char32(value);
|
||||
$default:
|
||||
@@ -397,7 +432,7 @@ macro void DString.append(&self, value)
|
||||
$case $defined((Char32)value):
|
||||
self.append_char32((Char32)value);
|
||||
$case $defined((String)value):
|
||||
self.append_chars((String)value);
|
||||
self.append_string((String)value);
|
||||
$default:
|
||||
$error "Unsupported type for append – use appendf instead.";
|
||||
$endswitch
|
||||
@@ -558,7 +593,7 @@ fn usz? DString.appendfn(&self, String format, args...) @maydiscard
|
||||
};
|
||||
}
|
||||
|
||||
fn DString join(Allocator allocator, String[] s, String joiner)
|
||||
fn DString join(Allocator allocator, String[] s, String joiner) @nodiscard
|
||||
{
|
||||
if (!s.len) return new(allocator);
|
||||
usz total_size = joiner.len * s.len;
|
||||
@@ -568,10 +603,10 @@ fn DString join(Allocator allocator, String[] s, String joiner)
|
||||
}
|
||||
DString res = new_with_capacity(allocator, total_size);
|
||||
res.append(s[0]);
|
||||
foreach (String* &str : s[1..])
|
||||
foreach (String str : s[1..])
|
||||
{
|
||||
res.append(joiner);
|
||||
res.append(*str);
|
||||
res.append(str);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -596,7 +631,7 @@ fn void DString.reverse(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn StringData* DString.data(self) @inline @private
|
||||
fn StringData* DString.data(self) @inline
|
||||
{
|
||||
return (StringData*)self;
|
||||
}
|
||||
@@ -613,7 +648,7 @@ fn void DString.reserve(&self, usz addition)
|
||||
if (data.capacity >= len) return;
|
||||
usz new_capacity = data.capacity * 2;
|
||||
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
|
||||
while (new_capacity < len) new_capacity *= 2;
|
||||
if (new_capacity < len) new_capacity = math::next_power_of_2(len);
|
||||
data.capacity = new_capacity;
|
||||
*self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity);
|
||||
}
|
||||
@@ -623,9 +658,10 @@ fn usz? DString.read_from_stream(&self, InStream reader)
|
||||
if (&reader.available)
|
||||
{
|
||||
usz total_read = 0;
|
||||
while (usz available = reader.available()!)
|
||||
while (ulong available = reader.available()!)
|
||||
{
|
||||
self.reserve(available);
|
||||
if (available > isz.max) available = (ulong)isz.max;
|
||||
self.reserve((usz)available);
|
||||
StringData* data = self.data();
|
||||
usz len = reader.read(data.chars[data.len..(data.capacity - 1)])!;
|
||||
total_read += len;
|
||||
|
||||
@@ -120,10 +120,13 @@ 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 usz MAX_VECTOR_SIZE = $$MAX_VECTOR_SIZE;
|
||||
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 bool NO_LIBC = !LIBC && !CUSTOM_LIBC;
|
||||
const bool CUSTOM_LIBC = $$CUSTOM_LIBC;
|
||||
const bool OLD_IO = $feature(OLD_IO);
|
||||
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;
|
||||
@@ -136,12 +139,13 @@ const bool BACKTRACE = $$BACKTRACE;
|
||||
const usz LLVM_VERSION = $$LLVM_VERSION;
|
||||
const bool BENCHMARKING = $$BENCHMARKING;
|
||||
const bool TESTING = $$TESTING;
|
||||
const bool PANIC_MSG = $$PANIC_MSG;
|
||||
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 NATIVE_STACKTRACE = LINUX || DARWIN || OPENBSD || WIN32;
|
||||
const bool LINUX = LIBC && OS_TYPE == LINUX;
|
||||
const bool DARWIN = LIBC && os_is_darwin();
|
||||
const bool WIN32 = LIBC && OS_TYPE == WIN32;
|
||||
@@ -151,13 +155,20 @@ 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 = ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool ANDROID = LIBC && OS_TYPE == ANDROID;
|
||||
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool WASM_NOLIBC @builtin @deprecated("Use 'FREESTANDING_WASM' instead") = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool FREESTANDING_PE32 = NO_LIBC && OS_TYPE == WIN32;
|
||||
const bool FREESTANDING_MACHO = NO_LIBC && OS_TYPE == MACOS;
|
||||
const bool FREESTANDING_ELF = NO_LIBC && !env::FREESTANDING_PE32 && !env::FREESTANDING_MACHO && !env::FREESTANDING_WASM;
|
||||
const bool FREESTANDING_WASM = NO_LIBC && WASM;
|
||||
const bool FREESTANDING = env::FREESTANDING_PE32 || env::FREESTANDING_MACHO || env::FREESTANDING_ELF || env::FREESTANDING_WASM;
|
||||
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;
|
||||
const int LANGUAGE_DEV_VERSION = $$LANGUAGE_DEV_VERSION;
|
||||
const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::OPENBSD || env::DARWIN || env::WIN32 || env::NETBSD;
|
||||
|
||||
macro bool os_is_darwin() @const
|
||||
{
|
||||
@@ -196,6 +207,8 @@ macro bool os_is_posix() @const
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
const String[] AUTHORS = $$AUTHORS;
|
||||
const String[] AUTHOR_EMAILS = $$AUTHOR_EMAILS;
|
||||
const String PROJECT_VERSION = $$PROJECT_VERSION;
|
||||
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
|
||||
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);
|
||||
|
||||
240
lib/std/core/logging.c3
Normal file
240
lib/std/core/logging.c3
Normal file
@@ -0,0 +1,240 @@
|
||||
module std::core::log;
|
||||
import std::io, std::thread, std::time, std::math::random;
|
||||
|
||||
const FULL_LOG = env::COMPILER_SAFE_MODE || $feature(FULL_LOG);
|
||||
|
||||
typedef LogCategory = inline char;
|
||||
typedef LogTag = char[12];
|
||||
|
||||
const LogCategory CATEGORY_APPLICATION = (LogCategory)0;
|
||||
const LogCategory CATEGORY_SYSTEM = (LogCategory)1;
|
||||
const LogCategory CATEGORY_KERNEL = (LogCategory)2;
|
||||
const LogCategory CATEGORY_AUDIO = (LogCategory)3;
|
||||
const LogCategory CATEGORY_VIDEO = (LogCategory)4;
|
||||
const LogCategory CATEGORY_RENDER = (LogCategory)5;
|
||||
const LogCategory CATEGORY_INPUT = (LogCategory)6;
|
||||
const LogCategory CATEGORY_NETWORK = (LogCategory)7;
|
||||
const LogCategory CATEGORY_SOCKET = (LogCategory)8;
|
||||
const LogCategory CATEGORY_SECURITY = (LogCategory)9;
|
||||
const LogCategory CATEGORY_TEST = (LogCategory)10;
|
||||
const LogCategory CATEGORY_ERROR = (LogCategory)11;
|
||||
const LogCategory CATEGORY_ASSERT = (LogCategory)12;
|
||||
const LogCategory CATEGORY_CRASH = (LogCategory)13;
|
||||
const LogCategory CATEGORY_STATS = (LogCategory)14;
|
||||
const LogCategory CATEGORY_CUSTOM_START = (LogCategory)100;
|
||||
|
||||
tlocal LogCategory default_category = CATEGORY_APPLICATION;
|
||||
tlocal LogTag current_tag;
|
||||
|
||||
|
||||
enum LogPriority : int
|
||||
{
|
||||
VERBOSE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR,
|
||||
CRITICAL,
|
||||
}
|
||||
|
||||
interface Logger
|
||||
{
|
||||
fn void log(LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
|
||||
}
|
||||
|
||||
macro void verbose(String fmt, ..., LogCategory category = default_category) => call_log(VERBOSE, category, fmt, $vasplat);
|
||||
macro void debug(String fmt, ..., LogCategory category = default_category) => call_log(DEBUG, category, fmt, $vasplat);
|
||||
macro void info(String fmt, ..., LogCategory category = default_category) => call_log(INFO, category, fmt, $vasplat);
|
||||
macro void warn(String fmt, ..., LogCategory category = default_category) => call_log(WARN, category, fmt, $vasplat);
|
||||
macro void error(String fmt, ..., LogCategory category = default_category) => call_log(ERROR, category, fmt, $vasplat);
|
||||
macro void critical(String fmt, ..., LogCategory category = default_category) => call_log(CRITICAL, category, fmt, $vasplat);
|
||||
|
||||
macro void @category_scope(LogCategory new_category; @body)
|
||||
{
|
||||
LogCategory old = default_category;
|
||||
default_category = new_category;
|
||||
defer default_category = old;
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
macro void @tag_scope(String tag_prefix = ""; @body)
|
||||
{
|
||||
LogTag old = current_tag;
|
||||
push_tag(tag_prefix);
|
||||
defer current_tag = old;
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
macro void push_tag(String tag_prefix = "")
|
||||
{
|
||||
current_tag = create_tag(tag_prefix);
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
fn LogTag create_tag(String tag_prefix)
|
||||
{
|
||||
LogTag tag @noinit;
|
||||
int start = 0;
|
||||
foreach (int i, c : tag_prefix)
|
||||
{
|
||||
if (c == 0) break;
|
||||
tag[start++] = c;
|
||||
}
|
||||
if (start > 0) tag[start++] = '_';
|
||||
for (int i = start; i < tag.len; i++)
|
||||
{
|
||||
tag[i] = (char)rand_in_range('a', 'z');
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
fn void set_priority_for_category(LogCategory category, LogPriority new_priority)
|
||||
{
|
||||
@atomic_store(config_priorities[category], new_priority, UNORDERED);
|
||||
}
|
||||
|
||||
fn LogPriority get_priority_for_category(LogCategory category)
|
||||
{
|
||||
return @atomic_load(config_priorities[category], UNORDERED);
|
||||
}
|
||||
|
||||
fn void set_priority_all(LogPriority new_priority)
|
||||
{
|
||||
for (int i = 0; i < config_priorities.len; i++)
|
||||
{
|
||||
@atomic_store(config_priorities[i], new_priority, UNORDERED);
|
||||
}
|
||||
}
|
||||
fn void set_logger(Logger logger)
|
||||
{
|
||||
init();
|
||||
if (!logger_mutex.is_initialized())
|
||||
{
|
||||
current_logger = logger;
|
||||
current_logfn = &logger.log;
|
||||
return;
|
||||
}
|
||||
logger_mutex.@in_lock()
|
||||
{
|
||||
current_logger = logger;
|
||||
current_logfn = &logger.log;
|
||||
};
|
||||
}
|
||||
|
||||
macro void init()
|
||||
{
|
||||
log_init.call(fn () => (void)logger_mutex.init());
|
||||
}
|
||||
|
||||
macro void call_log(LogPriority prio, LogCategory category, String fmt, args...)
|
||||
{
|
||||
$if FULL_LOG:
|
||||
call_log_internal(prio, category, $$FILE, $$FUNC, $$LINE, fmt, args);
|
||||
$else
|
||||
call_log_internal(prio, category, "", "", 0, fmt, args);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void call_log_internal(LogPriority prio, LogCategory category, String file, String func, int line, String fmt, any[] args)
|
||||
{
|
||||
LogPriority priority = mem::@atomic_load(config_priorities[category], UNORDERED);
|
||||
if (priority > prio) return;
|
||||
init();
|
||||
bool locked = logger_mutex.is_initialized();
|
||||
if (locked) logger_mutex.lock();
|
||||
Logger logger = current_logger;
|
||||
LogFn logfn = current_logfn;
|
||||
defer if (locked) logger_mutex.unlock();
|
||||
logfn(logger.ptr, prio, category, current_tag, file, func, line, fmt, args);
|
||||
}
|
||||
|
||||
fn String? get_category_name(LogCategory category)
|
||||
{
|
||||
String val = category_names[category];
|
||||
return val ?: NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn void set_category_name(LogCategory category, String name)
|
||||
{
|
||||
category_names[category] = name;
|
||||
}
|
||||
|
||||
struct NullLogger (Logger)
|
||||
{
|
||||
void* dummy;
|
||||
}
|
||||
|
||||
fn void NullLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
|
||||
{}
|
||||
|
||||
struct MultiLogger (Logger)
|
||||
{
|
||||
Logger[] loggers;
|
||||
}
|
||||
|
||||
fn void MultiLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
|
||||
{
|
||||
foreach (logger : self.loggers)
|
||||
{
|
||||
logger.log(priority, category, tag, file, function, line, fmt, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module std::core::log @private;
|
||||
import std::io, std::thread, std::time;
|
||||
|
||||
struct StderrLogger (Logger) @if(env::LIBC)
|
||||
{
|
||||
void* dummy;
|
||||
}
|
||||
|
||||
fn void StderrLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic @if(env::LIBC)
|
||||
{
|
||||
@stack_mem(256 + 64; Allocator mem)
|
||||
{
|
||||
DString str;
|
||||
str.init(mem, 256);
|
||||
str.appendf(fmt, ...args);
|
||||
TzDateTime time = datetime::now().to_local();
|
||||
$if FULL_LOG:
|
||||
io::eprintfn("[%02d:%02d:%02d:%04d] %s:%d [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), file, line, priority, str);
|
||||
$else
|
||||
io::eprintfn("[%02d:%02d:%02d:%04d] [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), priority, str);
|
||||
$endif
|
||||
};
|
||||
}
|
||||
|
||||
alias LogFn = fn void(void*, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
|
||||
LogFn current_logfn = env::LIBC ??? (LogFn)&StderrLogger.log : (LogFn)&NullLogger.log;
|
||||
OnceFlag log_init;
|
||||
Mutex logger_mutex;
|
||||
Logger current_logger = env::LIBC ??? &stderr_logger : &null_logger;
|
||||
StderrLogger stderr_logger @if (env::LIBC);
|
||||
NullLogger null_logger;
|
||||
LogPriority[256] config_priorities = { [0..255] = ERROR, [CATEGORY_APPLICATION] = INFO, [CATEGORY_TEST] = VERBOSE, [CATEGORY_ASSERT] = WARN};
|
||||
String[256] category_names = {
|
||||
[CATEGORY_APPLICATION] = "APP",
|
||||
[CATEGORY_SYSTEM] = "SYSTEM",
|
||||
[CATEGORY_KERNEL] = "KERNEL",
|
||||
[CATEGORY_AUDIO] = "AUDIO",
|
||||
[CATEGORY_VIDEO] = "VIDEO",
|
||||
[CATEGORY_RENDER] = "RENDER",
|
||||
[CATEGORY_INPUT] = "INPUT",
|
||||
[CATEGORY_NETWORK] = "NETWORD",
|
||||
[CATEGORY_SOCKET] = "SOCKET",
|
||||
[CATEGORY_SECURITY] = "SECURITY",
|
||||
[CATEGORY_TEST] = "TEST",
|
||||
[CATEGORY_ERROR] = "ERROR",
|
||||
[CATEGORY_ASSERT] = "ASSERT",
|
||||
[CATEGORY_CRASH] = "CRASH",
|
||||
[CATEGORY_STATS] = "STATS"
|
||||
};
|
||||
@@ -3,10 +3,15 @@
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::mem;
|
||||
import std::core::mem::allocator @public;
|
||||
import std::os::posix, std::os::win32;
|
||||
import std::math;
|
||||
|
||||
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
|
||||
const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
|
||||
const DEFAULT_MEM_ALIGNMENT = env::WASM ? 16 : (void*.alignof) * 2;
|
||||
const ulong KB = 1024;
|
||||
const ulong MB = KB * 1024;
|
||||
const ulong GB = MB * 1024;
|
||||
const ulong TB = GB * 1024;
|
||||
|
||||
faultdef OUT_OF_MEMORY, INVALID_ALLOC_SIZE;
|
||||
|
||||
@@ -15,14 +20,35 @@ macro bool @constant_is_power_of_2($x) @const @private
|
||||
return $x != 0 && ($x & ($x - 1)) == 0;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "The os page size."
|
||||
*>
|
||||
fn usz os_pagesize()
|
||||
{
|
||||
$switch:
|
||||
$case env::POSIX:
|
||||
static usz pagesize;
|
||||
if (pagesize) return pagesize;
|
||||
return pagesize = posix::getpagesize();
|
||||
$case env::WIN32:
|
||||
static usz pagesize;
|
||||
if (pagesize) return pagesize;
|
||||
Win32_SYSTEM_INFO info;
|
||||
win32::getSystemInfo(&info);
|
||||
return pagesize = info.dwPageSize;
|
||||
$default:
|
||||
return 4096;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Load a vector from memory according to a mask assuming default alignment.
|
||||
|
||||
@param ptr : "The pointer address to load from."
|
||||
@param mask : "The mask for the load"
|
||||
@param passthru : "The value to use for non masked values"
|
||||
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
|
||||
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptr = passthru) : "Pointer and passthru must match"
|
||||
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
|
||||
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
@@ -40,12 +66,13 @@ macro masked_load(ptr, bool[<*>] mask, passthru)
|
||||
@param passthru : "The value to use for non masked values"
|
||||
@param $alignment : "The alignment to assume for the pointer"
|
||||
|
||||
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
|
||||
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptr = passthru) : "Pointer and passthru must match"
|
||||
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
|
||||
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
@ensure $typeof(return) == $typeof(*ptr)
|
||||
*>
|
||||
macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
|
||||
{
|
||||
@@ -59,9 +86,9 @@ macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
|
||||
@param mask : "The mask for the load"
|
||||
@param passthru : "The value to use for non masked values"
|
||||
|
||||
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
|
||||
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptrvec[0] = passthru[0]) : "Pointer and passthru must match"
|
||||
@require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
|
||||
@@ -81,9 +108,9 @@ macro gather(ptrvec, bool[<*>] mask, passthru)
|
||||
@param passthru : "The value to use for non masked values"
|
||||
@param $alignment : "The alignment to assume for the pointers"
|
||||
|
||||
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
|
||||
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptrvec[0] = passthru[0]) : "Pointer and passthru must match"
|
||||
@require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
@@ -103,8 +130,8 @@ macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
|
||||
@param value : "The value to store masked"
|
||||
@param mask : "The mask for the store"
|
||||
|
||||
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
|
||||
@require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptr = value) : "Pointer and value must match"
|
||||
@require $kindof(value) == VECTOR : "Expected value to be a vector"
|
||||
@require value.len == mask.len : "Mask and value must have the same length"
|
||||
*>
|
||||
macro masked_store(ptr, value, bool[<*>] mask)
|
||||
@@ -118,8 +145,8 @@ macro masked_store(ptr, value, bool[<*>] mask)
|
||||
@param mask : "The mask for the store"
|
||||
@param $alignment : "The alignment of the pointer"
|
||||
|
||||
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
|
||||
@require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptr = value) : "Pointer and value must match"
|
||||
@require $kindof(value) == VECTOR : "Expected value to be a vector"
|
||||
@require value.len == mask.len : "Mask and value must have the same length"
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
|
||||
@@ -133,9 +160,9 @@ macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
|
||||
@param ptrvec : "The vector pointer containing the addresses to store to."
|
||||
@param value : "The value to store masked"
|
||||
@param mask : "The mask for the store"
|
||||
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
|
||||
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptrvec[0] = value[0]) : "Pointer and value must match"
|
||||
@require value.len == mask.len : "Mask and value must have the same length"
|
||||
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
|
||||
@@ -151,9 +178,9 @@ macro scatter(ptrvec, value, bool[<*>] mask)
|
||||
@param mask : "The mask for the store"
|
||||
@param $alignment : "The alignment of the load"
|
||||
|
||||
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
|
||||
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptrvec[0] = value[0]) : "Pointer and value must match"
|
||||
@require value.len == mask.len : "Mask and value must have the same length"
|
||||
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
@@ -173,7 +200,7 @@ macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
|
||||
*>
|
||||
macro @unaligned_load(#x, usz $alignment) @builtin
|
||||
{
|
||||
return $$unaligned_load(&#x, $alignment);
|
||||
return $$unaligned_load(&#x, $alignment, false);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -188,9 +215,10 @@ macro @unaligned_load(#x, usz $alignment) @builtin
|
||||
*>
|
||||
macro @unaligned_store(#x, value, usz $alignment) @builtin
|
||||
{
|
||||
return $$unaligned_store(&#x, ($typeof(#x))value, $alignment);
|
||||
return $$unaligned_store(&#x, ($typeof(#x))value, $alignment, false);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param #x : "The variable or dereferenced pointer to load."
|
||||
@return "The value of the variable"
|
||||
@@ -215,6 +243,39 @@ macro @volatile_store(#x, value) @builtin
|
||||
return $$volatile_store(&#x, ($typeof(#x))value);
|
||||
}
|
||||
|
||||
<*
|
||||
@param ptr : "The pointer to load from"
|
||||
@param $align : "The alignment to assume for the load"
|
||||
@param $volatile : "Whether the load is volatile or not, defaults to false"
|
||||
@return "The value of the variable"
|
||||
|
||||
@require $defined(*ptr) : "This must be a typed pointer"
|
||||
@require @constant_is_power_of_2($align) : "The alignment must be a power of two"
|
||||
*>
|
||||
macro load(ptr, usz $align, bool $volatile = false)
|
||||
{
|
||||
return $$unaligned_load(ptr, $align, $volatile);
|
||||
}
|
||||
|
||||
<*
|
||||
@param ptr : "The pointer to store to."
|
||||
@param value : "The value to store."
|
||||
@param $align : "The alignment to assume for the store"
|
||||
@param $volatile : "Whether the store is volatile, defaults to false"
|
||||
@return "The value stored"
|
||||
|
||||
@require $defined(*ptr) : "This must be a typed pointer"
|
||||
@require $defined(*ptr = value) : "The value doesn't match the variable"
|
||||
@require @constant_is_power_of_2($align) : "The alignment must be a power of two"
|
||||
*>
|
||||
macro store(ptr, value, usz $align, bool $volatile = false)
|
||||
{
|
||||
return $$unaligned_store(ptr, ($typeof(*ptr))value, $align, $volatile);
|
||||
}
|
||||
|
||||
<*
|
||||
All possible atomic orderings
|
||||
*>
|
||||
enum AtomicOrdering : int
|
||||
{
|
||||
NOT_ATOMIC, // Not atomic
|
||||
@@ -282,7 +343,7 @@ macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = S
|
||||
*>
|
||||
fn usz aligned_offset(usz offset, usz alignment)
|
||||
{
|
||||
return alignment * ((offset + alignment - 1) / alignment);
|
||||
return (offset + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
macro void* aligned_pointer(void* ptr, usz alignment)
|
||||
@@ -298,6 +359,11 @@ fn bool ptr_is_aligned(void* ptr, usz alignment) @inline
|
||||
return (uptr)ptr & ((uptr)alignment - 1) == 0;
|
||||
}
|
||||
|
||||
fn bool ptr_is_page_aligned(void* ptr) @inline
|
||||
{
|
||||
return (uptr)ptr & ((uptr)os_pagesize() - 1) == 0;
|
||||
}
|
||||
|
||||
macro void zero_volatile(char[] data)
|
||||
{
|
||||
$$memset(data.ptr, (char)0, data.len, true, (usz)1);
|
||||
@@ -342,7 +408,7 @@ macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig
|
||||
@param $src_align : "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
|
||||
|
||||
@require src != null || len == 0 : "Copying a null with non-zero length is invalid"
|
||||
@require src != null || $len == 0 : "Copying a null with non-zero length is invalid"
|
||||
@require $len == 0 || dst + $len <= src || src + $len <= dst : "Ranges may not overlap"
|
||||
*>
|
||||
macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
|
||||
@@ -405,7 +471,7 @@ macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $i
|
||||
@require values::@inner_kind(b) == TypeKind.SLICE || values::@inner_kind(b) == TypeKind.POINTER
|
||||
@require values::@inner_kind(a) != TypeKind.SLICE || len == -1
|
||||
@require values::@inner_kind(a) != TypeKind.POINTER || len > -1
|
||||
@require values::@assign_to(a, b) && values::@assign_to(b, a)
|
||||
@require $defined(a = b, b = a)
|
||||
*>
|
||||
macro bool equals(a, b, isz len = -1, usz $align = 0)
|
||||
{
|
||||
@@ -576,11 +642,20 @@ fn void temp_pop(PoolState old_state)
|
||||
allocator::pop_pool(old_state) @inline;
|
||||
}
|
||||
|
||||
macro void @pool_init(Allocator allocator, usz pool_size, usz buffer_size; @body) @builtin
|
||||
<*
|
||||
@require pool_size >= 64
|
||||
@require realloc_size >= 64
|
||||
@require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator."
|
||||
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
|
||||
*>
|
||||
macro void @pool_init(Allocator allocator, usz pool_size,
|
||||
usz reserve_size = allocator::temp_allocator_reserve_size,
|
||||
usz min_size = allocator::temp_allocator_min_size,
|
||||
usz realloc_size = allocator::temp_allocator_realloc_size; @body) @builtin
|
||||
{
|
||||
Allocator current = allocator::current_temp;
|
||||
TempAllocator* top = allocator::top_temp;
|
||||
allocator::create_temp_allocator(allocator, pool_size, buffer_size);
|
||||
allocator::create_temp_allocator(allocator, pool_size, reserve_size, min_size, realloc_size);
|
||||
defer
|
||||
{
|
||||
allocator::destroy_temp_allocators();
|
||||
@@ -589,9 +664,19 @@ macro void @pool_init(Allocator allocator, usz pool_size, usz buffer_size; @body
|
||||
}
|
||||
@body();
|
||||
}
|
||||
macro void @pool(;@body) @builtin
|
||||
|
||||
<*
|
||||
Create a new temporary allocator.
|
||||
|
||||
The `reserve` parameter allows you to determine how many bytes should be reserved for
|
||||
allocations on the current temporary allocator, if allocations are made inside of the pool scope.
|
||||
It is made available for optimization, and can usually be ignored.
|
||||
|
||||
@param reserve : "The amount of bytes to reserve for out-of-order allocations, 0 gives the default."
|
||||
*>
|
||||
macro void @pool(usz reserve = 0; @body) @builtin
|
||||
{
|
||||
PoolState state = allocator::push_pool() @inline;
|
||||
PoolState state = allocator::push_pool(reserve) @inline;
|
||||
defer
|
||||
{
|
||||
allocator::pop_pool(state) @inline;
|
||||
@@ -599,7 +684,7 @@ macro void @pool(;@body) @builtin
|
||||
@body();
|
||||
}
|
||||
|
||||
module std::core::mem @if(WASM_NOLIBC);
|
||||
module std::core::mem @if(env::FREESTANDING_WASM);
|
||||
import std::core::mem::allocator @public;
|
||||
SimpleHeapAllocator wasm_allocator @private;
|
||||
extern int __heap_base;
|
||||
@@ -627,16 +712,50 @@ macro TrackingEnv* get_tracking_env()
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value"
|
||||
@require $alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'clone_aligned' instead"
|
||||
*>
|
||||
macro @clone(value) @builtin @nodiscard
|
||||
{
|
||||
return allocator::clone(mem, value);
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value"
|
||||
*>
|
||||
macro @clone_slice(value) @builtin @nodiscard => allocator::clone_slice(mem, value);
|
||||
|
||||
<*
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value, which must be released using free_aligned"
|
||||
*>
|
||||
macro @clone_aligned(value) @builtin @nodiscard
|
||||
{
|
||||
return allocator::clone_aligned(mem, value);
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value"
|
||||
*>
|
||||
macro @tclone(value) @builtin @nodiscard
|
||||
{
|
||||
return tnew($typeof(value), value);
|
||||
$if $alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT:
|
||||
return tnew($typeof(value), value);
|
||||
$else
|
||||
return allocator::clone_aligned(tmem, value);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value"
|
||||
*>
|
||||
macro @tclone_slice(value) @builtin @nodiscard => allocator::clone_slice(tmem, value);
|
||||
|
||||
fn void* malloc(usz size) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::malloc(mem, size);
|
||||
@@ -658,56 +777,70 @@ fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@param $Type : "The type to allocate"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@return "A pointer to data of type $Type."
|
||||
*>
|
||||
macro new($Type, ...) @nodiscard
|
||||
macro new($Type, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc($Type.sizeof);
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = malloc($Type.sizeof);
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)calloc($Type.sizeof);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@param $Type : "The type to allocate"
|
||||
@param padding : "The padding to add after the allocation"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to data of type $Type."
|
||||
*>
|
||||
macro new_with_padding($Type, usz padding, ...) @nodiscard
|
||||
macro new_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc($Type.sizeof + padding);
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = malloc($Type.sizeof + padding);
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)calloc($Type.sizeof + padding);
|
||||
$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"
|
||||
|
||||
@param $Type : "The type to allocate"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to data of type $Type with the proper alignment"
|
||||
*>
|
||||
macro new_aligned($Type, ...) @nodiscard
|
||||
macro new_aligned($Type, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param $Type : "The type to allocate"
|
||||
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@return "A pointer to uninitialized data for the type $Type"
|
||||
*>
|
||||
macro alloc($Type) @nodiscard
|
||||
{
|
||||
@@ -715,7 +848,12 @@ macro alloc($Type) @nodiscard
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@param $Type : "The type to allocate"
|
||||
@param padding : "The padding to add after the allocation"
|
||||
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@return "A pointer to uninitialized data for the type $Type"
|
||||
*>
|
||||
macro alloc_with_padding($Type, usz padding) @nodiscard
|
||||
{
|
||||
@@ -723,8 +861,13 @@ macro alloc_with_padding($Type, usz padding) @nodiscard
|
||||
}
|
||||
|
||||
<*
|
||||
|
||||
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.
|
||||
|
||||
@param $Type : "The type to allocate"
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@return "A pointer to uninitialized data for the type $Type with the proper alignment"
|
||||
*>
|
||||
macro alloc_aligned($Type) @nodiscard
|
||||
{
|
||||
@@ -732,46 +875,62 @@ macro alloc_aligned($Type) @nodiscard
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@param $Type : "The type to allocate"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to temporary data of type $Type."
|
||||
*>
|
||||
macro tnew($Type, ...) @nodiscard
|
||||
macro tnew($Type, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)tcalloc($Type.sizeof) @inline;
|
||||
$else
|
||||
$Type* val = tmalloc($Type.sizeof) @inline;
|
||||
*val = $vaexpr[0];
|
||||
$if $defined(#init):
|
||||
$Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@param $Type : "The type to allocate"
|
||||
@param padding : "The padding to add after the allocation"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to temporary data of type $Type with added padding at the end."
|
||||
*>
|
||||
macro temp_with_padding($Type, usz padding, ...) @nodiscard
|
||||
macro temp_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)tcalloc($Type.sizeof + padding) @inline;
|
||||
$else
|
||||
$Type* val = tmalloc($Type.sizeof + padding) @inline;
|
||||
*val = $vaexpr[0];
|
||||
$if $defined(#init):
|
||||
$Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline;
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
*>
|
||||
macro talloc($Type) @nodiscard
|
||||
{
|
||||
return tmalloc($Type.sizeof);
|
||||
}
|
||||
|
||||
macro talloc_with_padding($Type, usz padding) @nodiscard
|
||||
{
|
||||
return tmalloc($Type.sizeof + padding);
|
||||
return tmalloc($Type.sizeof, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
*>
|
||||
macro talloc_with_padding($Type, usz padding) @nodiscard
|
||||
{
|
||||
return tmalloc($Type.sizeof + padding, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
|
||||
*>
|
||||
macro new_array($Type, usz elements) @nodiscard
|
||||
@@ -782,6 +941,8 @@ macro new_array($Type, usz elements) @nodiscard
|
||||
<*
|
||||
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 $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
*>
|
||||
macro new_array_aligned($Type, usz elements) @nodiscard
|
||||
{
|
||||
@@ -789,6 +950,7 @@ macro new_array_aligned($Type, usz elements) @nodiscard
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
|
||||
*>
|
||||
macro alloc_array($Type, usz elements) @nodiscard
|
||||
@@ -799,6 +961,8 @@ macro alloc_array($Type, usz elements) @nodiscard
|
||||
<*
|
||||
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 $Type.kindof != OPTIONAL : "Expected a non-optional type"
|
||||
*>
|
||||
macro alloc_array_aligned($Type, usz elements) @nodiscard
|
||||
{
|
||||
@@ -862,6 +1026,18 @@ fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMEN
|
||||
return tmem.resize(ptr, size, alignment)!!;
|
||||
}
|
||||
|
||||
<*
|
||||
Takes the address of a possibly unaligned variable or member,
|
||||
and offers safe access to that member, by constructing an UnalignedRef.
|
||||
|
||||
@require $defined(&#arg) : "It must be possible to take the address of the argument."
|
||||
@return "An 'UnalignedRef' with the proper type and alignment, with a pointer to argument"
|
||||
*>
|
||||
macro @unaligned_addr(#arg) @builtin
|
||||
{
|
||||
return (UnalignedRef{$typeof(#arg), $alignof(#arg)})&#arg;
|
||||
}
|
||||
|
||||
module std::core::mem @if(env::NO_LIBC);
|
||||
|
||||
fn CInt __memcmp(void* s1, void* s2, usz n) @weak @export("memcmp")
|
||||
@@ -899,3 +1075,39 @@ fn void* __memcpy(void* dst, void* src, usz n) @weak @export("memcpy")
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
module std::core::mem::volatile <Type>;
|
||||
|
||||
typedef Volatile @structlike = Type;
|
||||
|
||||
macro Type Volatile.get(&self)
|
||||
{
|
||||
return @volatile_load(*(Type*)self);
|
||||
}
|
||||
|
||||
macro Type Volatile.set(&self, Type val)
|
||||
{
|
||||
return @volatile_store(*(Type*)self, val);
|
||||
}
|
||||
|
||||
<*
|
||||
@require mem::@constant_is_power_of_2(ALIGNMENT) : "The alignment must be a power of 2"
|
||||
*>
|
||||
module std::core::mem::alignment <Type, ALIGNMENT>;
|
||||
import std::core::mem @public;
|
||||
|
||||
<*
|
||||
An UnalignedRef offers correctly aligned access to addresses that may be unaligned or overaligned.
|
||||
*>
|
||||
typedef UnalignedRef = Type*;
|
||||
|
||||
macro Type UnalignedRef.get(self)
|
||||
{
|
||||
return @unaligned_load(*(Type*)self, ALIGNMENT, false);
|
||||
}
|
||||
|
||||
macro Type UnalignedRef.set(&self, Type val)
|
||||
{
|
||||
return @unaligned_store(*(Type*)self, val, ALIGNMENT, false);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::math;
|
||||
|
||||
// C3 has several different allocators available:
|
||||
//
|
||||
// Name Arena Uses buffer OOM Fallback? Mark? Reset?
|
||||
// ArenaAllocator Yes Yes No Yes Yes
|
||||
// BackedArenaAllocator Yes No Yes Yes Yes
|
||||
// DynamicArenaAllocator Yes No Yes No Yes
|
||||
// HeapAllocator No No No No No *Note: Not for normal use
|
||||
// LibcAllocator No No No No No *Note: Wraps malloc
|
||||
// OnStackAllocator Yes Yes Yes No No *Note: Used by @stack_mem
|
||||
// TempAllocator Yes No Yes No* No* *Note: Mark/reset using @pool
|
||||
// TrackingAllocator No No N/A No No *Note: Wraps other heap allocator
|
||||
// Vmem Yes No No Yes Yes *Note: Can be set to huge sizes
|
||||
|
||||
const DEFAULT_SIZE_PREFIX = usz.sizeof;
|
||||
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
|
||||
|
||||
@@ -20,13 +33,18 @@ enum AllocInitType
|
||||
interface Allocator
|
||||
{
|
||||
<*
|
||||
Acquire memory from the allocator, with the given alignment and initialization type.
|
||||
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require size > 0
|
||||
@require size > 0 : "The size must be 1 or more"
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*? acquire(usz size, AllocInitType init_type, usz alignment = 0);
|
||||
|
||||
<*
|
||||
Resize acquired memory from the allocator, with the given new size and alignment.
|
||||
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require ptr != null
|
||||
@@ -34,8 +52,11 @@ interface Allocator
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*? resize(void* ptr, usz new_size, usz alignment = 0);
|
||||
|
||||
<*
|
||||
@require ptr != null
|
||||
Release memory acquired using `acquire` or `resize`.
|
||||
|
||||
@require ptr != null : "Empty pointers should never be released"
|
||||
*>
|
||||
fn void release(void* ptr, bool aligned);
|
||||
}
|
||||
@@ -44,7 +65,7 @@ alias MemoryAllocFn = fn char[]?(usz);
|
||||
|
||||
|
||||
|
||||
fn usz alignment_for_allocation(usz alignment) @inline @private
|
||||
fn usz alignment_for_allocation(usz alignment) @inline
|
||||
{
|
||||
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
|
||||
}
|
||||
@@ -146,7 +167,7 @@ macro void free_aligned(Allocator allocator, void* ptr)
|
||||
<*
|
||||
@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"
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
@@ -162,7 +183,7 @@ macro new(Allocator allocator, $Type, ...) @nodiscard
|
||||
<*
|
||||
@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"
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_try(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
@@ -179,7 +200,7 @@ macro new_try(Allocator allocator, $Type, ...) @nodiscard
|
||||
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"
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
@@ -283,11 +304,75 @@ macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
|
||||
*>
|
||||
macro realloc_array(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return realloc_array_try(allocator, ptr, $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 realloc_array_aligned(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)realloc_aligned(allocator, ptr, $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 realloc_array_try(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)realloc_try(allocator, ptr, $Type.sizeof * elements))[:elements];
|
||||
}
|
||||
|
||||
<*
|
||||
Clone a value.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use to clone"
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value"
|
||||
@require $alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'clone_aligned' instead"
|
||||
*>
|
||||
macro clone(Allocator allocator, value) @nodiscard
|
||||
{
|
||||
return new(allocator, $typeof(value), value);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator used to clone"
|
||||
@param slice : "The slice to clone"
|
||||
@return "A pointer to the cloned slice"
|
||||
|
||||
@require $kindof(slice) == SLICE || $kindof(slice) == ARRAY
|
||||
*>
|
||||
macro clone_slice(Allocator allocator, slice) @nodiscard
|
||||
{
|
||||
if (!lengthof(slice)) return {};
|
||||
|
||||
var $Type = $typeof(slice[0]);
|
||||
|
||||
$Type[] new_arr = new_array(allocator, $Type, slice.len);
|
||||
mem::copy(new_arr.ptr, &slice[0], slice.len * $Type.sizeof);
|
||||
|
||||
return new_arr;
|
||||
}
|
||||
|
||||
<*
|
||||
Clone overaligned values. Must be released using free_aligned.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use to clone"
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value"
|
||||
*>
|
||||
macro clone_aligned(Allocator allocator, value) @nodiscard
|
||||
{
|
||||
return new_aligned(allocator, $typeof(value), value)!!;
|
||||
}
|
||||
|
||||
fn any clone_any(Allocator allocator, any value) @nodiscard
|
||||
{
|
||||
usz size = value.type.sizeof;
|
||||
@@ -307,7 +392,7 @@ 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:
|
||||
$if $kindof(#alloc_fn(bytes)) == OPTIONAL:
|
||||
void* data = #alloc_fn(alignsize)!;
|
||||
$else
|
||||
void* data = #alloc_fn(alignsize);
|
||||
@@ -319,6 +404,28 @@ macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
|
||||
return mem;
|
||||
}
|
||||
|
||||
<*
|
||||
@require bytes > 0
|
||||
@require alignment > 0
|
||||
@require bytes <= isz.max
|
||||
*>
|
||||
macro void*? @aligned_alloc_fn(context, #alloc_fn, usz bytes, usz alignment)
|
||||
{
|
||||
if (alignment < void*.alignof) alignment = void*.alignof;
|
||||
usz header = AlignedBlock.sizeof + alignment;
|
||||
usz alignsize = bytes + header;
|
||||
$if $kindof(#alloc_fn(context, bytes)) == OPTIONAL:
|
||||
void* data = #alloc_fn(context, alignsize)!;
|
||||
$else
|
||||
void* data = #alloc_fn(context, 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;
|
||||
@@ -328,13 +435,23 @@ struct AlignedBlock
|
||||
macro void? @aligned_free(#free_fn, void* old_pointer)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
|
||||
$if $kindof(#free_fn(desc.start)) == OPTIONAL:
|
||||
#free_fn(desc.start)!;
|
||||
$else
|
||||
#free_fn(desc.start);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void? @aligned_free_fn(context, #free_fn, void* old_pointer)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if $kindof(#free_fn(context, desc.start)) == OPTIONAL:
|
||||
#free_fn(context, desc.start)!;
|
||||
$else
|
||||
#free_fn(context, desc.start);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require bytes > 0
|
||||
@require alignment > 0
|
||||
@@ -345,7 +462,7 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
|
||||
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:
|
||||
$if $kindof(#free_fn(data_start)) == OPTIONAL:
|
||||
#free_fn(data_start)!;
|
||||
$else
|
||||
#free_fn(data_start);
|
||||
@@ -353,6 +470,23 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
|
||||
return new_data;
|
||||
}
|
||||
|
||||
<*
|
||||
@require bytes > 0
|
||||
@require alignment > 0
|
||||
*>
|
||||
macro void*? @aligned_realloc_fn(context, #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_fn(context, #calloc_fn, bytes, alignment)!;
|
||||
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
|
||||
$if $kindof(#free_fn(context, data_start)) == OPTIONAL:
|
||||
#free_fn(context, data_start)!;
|
||||
$else
|
||||
#free_fn(context, data_start);
|
||||
$endif
|
||||
return new_data;
|
||||
}
|
||||
|
||||
// All allocators
|
||||
alias mem @builtin = thread_allocator ;
|
||||
@@ -367,13 +501,13 @@ tlocal TempAllocator* top_temp;
|
||||
tlocal bool auto_create_temp = false;
|
||||
|
||||
usz temp_allocator_min_size = temp_allocator_default_min_size();
|
||||
usz temp_allocator_buffer_size = temp_allocator_default_buffer_size();
|
||||
usz temp_allocator_new_mult = 4;
|
||||
usz temp_allocator_reserve_size = temp_allocator_default_reserve_size();
|
||||
usz temp_allocator_realloc_size = temp_allocator_default_min_size() * 4;
|
||||
|
||||
fn PoolState push_pool()
|
||||
fn PoolState push_pool(usz reserve = 0)
|
||||
{
|
||||
Allocator old = top_temp ? current_temp : create_temp_allocator_on_demand();
|
||||
current_temp = ((TempAllocator*)old).derive_allocator(temp_allocator_min_size, temp_allocator_buffer_size, temp_allocator_new_mult)!!;
|
||||
current_temp = ((TempAllocator*)old).derive_allocator(reserve)!!;
|
||||
return (PoolState)old.ptr;
|
||||
}
|
||||
|
||||
@@ -413,7 +547,7 @@ macro usz temp_allocator_default_min_size() @local
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro usz temp_allocator_default_buffer_size() @local
|
||||
macro usz temp_allocator_default_reserve_size() @local
|
||||
{
|
||||
$switch env::MEMORY_ENV:
|
||||
$case NORMAL: return 1024;
|
||||
@@ -423,7 +557,7 @@ macro usz temp_allocator_default_buffer_size() @local
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro Allocator heap() => thread_allocator;
|
||||
macro Allocator heap() @deprecated("Use 'mem' instead.") => thread_allocator;
|
||||
|
||||
<*
|
||||
@require !top_temp : "This should never be called when temp already exists"
|
||||
@@ -435,24 +569,25 @@ fn Allocator create_temp_allocator_on_demand() @private
|
||||
auto_create_temp = true;
|
||||
abort("Use '@pool_init()' to enable the temp allocator on a new thread. A temp allocator is only implicitly created on the main thread.");
|
||||
}
|
||||
return create_temp_allocator(base_allocator(), temp_allocator_size());
|
||||
return create_temp_allocator(temp_base_allocator, temp_allocator_size(), temp_allocator_reserve_size, temp_allocator_min_size, temp_allocator_realloc_size);
|
||||
}
|
||||
|
||||
<*
|
||||
@require !top_temp : "This should never be called when temp already exists"
|
||||
*>
|
||||
fn Allocator create_temp_allocator(Allocator allocator, usz size, usz buffer = temp_allocator_default_buffer_size()) @private
|
||||
fn Allocator create_temp_allocator(Allocator allocator, usz size, usz reserve, usz min_size, usz realloc_size) @private
|
||||
{
|
||||
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size)!!;
|
||||
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size, reserve, min_size, realloc_size)!!;
|
||||
}
|
||||
|
||||
macro Allocator temp()
|
||||
macro Allocator temp() @deprecated("Use 'tmem' instead")
|
||||
{
|
||||
return current_temp;
|
||||
}
|
||||
|
||||
alias tmem @builtin = current_temp;
|
||||
|
||||
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC)
|
||||
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::FREESTANDING_WASM)
|
||||
{
|
||||
auto_create_temp = true;
|
||||
}
|
||||
@@ -497,12 +632,12 @@ typedef NullAllocator (Allocator) = uptr;
|
||||
|
||||
fn void*? NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
return mem::OUT_OF_MEMORY?;
|
||||
return mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
|
||||
fn void*? NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
return mem::OUT_OF_MEMORY?;
|
||||
return mem::OUT_OF_MEMORY~;
|
||||
}
|
||||
|
||||
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
|
||||
252
lib/std/core/mem_mempool.c3
Normal file
252
lib/std/core/mem_mempool.c3
Normal file
@@ -0,0 +1,252 @@
|
||||
module std::core::mem::mempool;
|
||||
import std::core::mem, std::core::mem::allocator, std::math;
|
||||
import std::core::sanitizer::asan;
|
||||
|
||||
const INITIAL_CAPACITY = 0;
|
||||
|
||||
struct FixedBlockPoolNode
|
||||
{
|
||||
void* buffer;
|
||||
FixedBlockPoolNode *next;
|
||||
usz capacity;
|
||||
}
|
||||
|
||||
struct FixedBlockPoolEntry
|
||||
{
|
||||
void *previous;
|
||||
}
|
||||
|
||||
<*
|
||||
Fixed blocks pool pre-allocating blocks backed by an Allocator which are then reserved for the user,
|
||||
blocks deallocated by the user are later re-used by future blocks allocations
|
||||
|
||||
`grow_capacity` can be changed in order to affect how many blocks will be allocated by next pool allocation,
|
||||
it has to be greater than 0
|
||||
`allocated` number of allocated blocks
|
||||
`used` number of used blocks by the user
|
||||
*>
|
||||
struct FixedBlockPool
|
||||
{
|
||||
Allocator allocator;
|
||||
FixedBlockPoolNode head;
|
||||
FixedBlockPoolNode *tail;
|
||||
void *next_free;
|
||||
void *freelist;
|
||||
usz block_size;
|
||||
usz grow_capacity;
|
||||
usz allocated;
|
||||
usz page_size;
|
||||
usz alignment;
|
||||
usz used;
|
||||
bool initialized;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool
|
||||
|
||||
@param [in] allocator : "The allocator to use"
|
||||
@param block_size : "The block size to use"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@param alignment : "The alignment of the buffer"
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
@require block_size > 0 : "Block size must be non zero"
|
||||
@require calculate_actual_capacity(capacity, block_size) * block_size >= block_size
|
||||
: "Total memory would overflow"
|
||||
*>
|
||||
fn FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_size, usz capacity = INITIAL_CAPACITY, usz alignment = 0)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.tail = &self.head;
|
||||
self.head.next = null;
|
||||
self.block_size = math::max(block_size, FixedBlockPoolEntry.sizeof);
|
||||
capacity = calculate_actual_capacity(capacity, self.block_size);
|
||||
self.alignment = allocator::alignment_for_allocation(alignment);
|
||||
self.page_size = capacity * self.block_size;
|
||||
assert(self.page_size >= self.block_size, "Total memory would overflow %d %d", block_size, capacity);
|
||||
self.head.buffer = fixedblockpool_allocate_page(self);
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(self.head.buffer, self.page_size);
|
||||
$endif
|
||||
self.head.capacity = capacity;
|
||||
self.next_free = self.head.buffer;
|
||||
self.freelist = null;
|
||||
self.grow_capacity = capacity;
|
||||
self.initialized = true;
|
||||
self.allocated = capacity;
|
||||
self.used = 0;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool
|
||||
|
||||
@param [in] allocator : "The allocator to use"
|
||||
@param $Type : "The type used for setting the block size"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.init_for_type(&self, Allocator allocator, $Type, usz capacity = INITIAL_CAPACITY)
|
||||
{
|
||||
return self.init(allocator, $Type.sizeof, capacity, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool using Temporary allocator
|
||||
|
||||
@param $Type : "The type used for setting the block size"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.tinit_for_type(&self, $Type, usz capacity = INITIAL_CAPACITY) => self.init_for_type(tmem, $Type, capacity);
|
||||
|
||||
<*
|
||||
Initialize an block pool using Temporary allocator
|
||||
|
||||
@param block_size : "The block size to use"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.tinit(&self, usz block_size, usz capacity = INITIAL_CAPACITY) => self.init(tmem, block_size, capacity);
|
||||
|
||||
<*
|
||||
Free up the entire block pool
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn void FixedBlockPool.free(&self)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(self.head.buffer, self.page_size);
|
||||
$endif
|
||||
fixedblockpool_free_page(self, self.head.buffer);
|
||||
FixedBlockPoolNode* iter = self.head.next;
|
||||
|
||||
while (iter)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(iter.buffer, self.page_size);
|
||||
$endif
|
||||
fixedblockpool_free_page(self, iter.buffer);
|
||||
FixedBlockPoolNode* current = iter;
|
||||
iter = iter.next;
|
||||
allocator::free(self.allocator, current);
|
||||
}
|
||||
self.initialized = false;
|
||||
self.allocated = 0;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate an block on the block pool, re-uses previously deallocated blocks
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn void* FixedBlockPool.alloc(&self)
|
||||
{
|
||||
defer self.used++;
|
||||
|
||||
if (self.freelist)
|
||||
{
|
||||
FixedBlockPoolEntry* entry = self.freelist;
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(entry, self.block_size);
|
||||
$endif
|
||||
self.freelist = entry.previous;
|
||||
mem::clear(entry, self.block_size);
|
||||
return entry;
|
||||
}
|
||||
|
||||
void* end = self.tail.buffer + (self.tail.capacity * self.block_size);
|
||||
if (self.next_free >= end) fixedblockpool_new_node(self);
|
||||
void* ptr = self.next_free;
|
||||
self.next_free += self.block_size;
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(ptr, self.block_size);
|
||||
$endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
<*
|
||||
Deallocate a block from the block pool
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
@require fixedblockpool_check_ptr(self, ptr) : "The pointer should be part of the pool"
|
||||
*>
|
||||
fn void FixedBlockPool.dealloc(&self, void* ptr)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
|
||||
mem::set(ptr, 0xAA, self.block_size);
|
||||
$endif
|
||||
|
||||
FixedBlockPoolEntry* entry = ptr;
|
||||
entry.previous = self.freelist;
|
||||
self.freelist = entry;
|
||||
self.used--;
|
||||
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(ptr, self.block_size);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn bool fixedblockpool_check_ptr(FixedBlockPool* self, void *ptr) @local
|
||||
{
|
||||
FixedBlockPoolNode* iter = &self.head;
|
||||
|
||||
while (iter)
|
||||
{
|
||||
void* end = iter.buffer + (iter.capacity * self.block_size);
|
||||
if (ptr >= iter.buffer && ptr < end) return true;
|
||||
iter = iter.next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.grow_capacity > 0 : "How many blocks will it store"
|
||||
*>
|
||||
fn void fixedblockpool_new_node(FixedBlockPool* self) @local
|
||||
{
|
||||
FixedBlockPoolNode* node = allocator::new(self.allocator, FixedBlockPoolNode);
|
||||
node.buffer = fixedblockpool_allocate_page(self);
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(node.buffer, self.page_size);
|
||||
$endif
|
||||
node.capacity = self.grow_capacity;
|
||||
self.tail.next = node;
|
||||
self.tail = node;
|
||||
self.next_free = node.buffer;
|
||||
self.allocated += node.capacity;
|
||||
}
|
||||
|
||||
macro void* fixedblockpool_allocate_page(FixedBlockPool* self) @private
|
||||
{
|
||||
return self.alignment > mem::DEFAULT_MEM_ALIGNMENT
|
||||
? allocator::calloc_aligned(self.allocator, self.page_size, self.alignment)!!
|
||||
: allocator::calloc(self.allocator, self.page_size);
|
||||
}
|
||||
|
||||
macro void fixedblockpool_free_page(FixedBlockPool* self, void* page) @private
|
||||
{
|
||||
if (self.alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
allocator::free_aligned(self.allocator, page);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator::free(self.allocator, page);
|
||||
}
|
||||
}
|
||||
|
||||
macro usz calculate_actual_capacity(usz capacity, usz block_size) @private
|
||||
{
|
||||
// Assume some overhead
|
||||
if (capacity) return capacity;
|
||||
capacity = (mem::os_pagesize() - 128) / block_size;
|
||||
return capacity ?: 1;
|
||||
}
|
||||
353
lib/std/core/os/mem_vm.c3
Normal file
353
lib/std/core/os/mem_vm.c3
Normal file
@@ -0,0 +1,353 @@
|
||||
<*
|
||||
The VM module holds code for working with virtual memory on supported platforms (currently Win32 and Posix)
|
||||
*>
|
||||
module std::core::mem::vm;
|
||||
import std::io, std::os::win32, std::os::posix, libc;
|
||||
|
||||
<*
|
||||
VirtualMemory is an abstraction for working with an allocated virtual memory area. It will invoke vm:: functions
|
||||
but will perform more checks and track its size (required to unmap the memory on Posix)
|
||||
*>
|
||||
struct VirtualMemory
|
||||
{
|
||||
void* ptr;
|
||||
usz size;
|
||||
VirtualMemoryAccess default_access;
|
||||
}
|
||||
|
||||
faultdef RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, UNMAPPED_ACCESS, UNALIGNED_ADDRESS, RELEASE_FAILED, UPDATE_FAILED, INVALID_ARGS;
|
||||
|
||||
enum VirtualMemoryAccess
|
||||
{
|
||||
PROTECTED,
|
||||
READ,
|
||||
WRITE,
|
||||
READWRITE,
|
||||
EXEC,
|
||||
EXECREAD,
|
||||
EXECWRITE,
|
||||
ANY
|
||||
}
|
||||
|
||||
fn usz aligned_alloc_size(usz size)
|
||||
{
|
||||
$if env::WIN32:
|
||||
return size > 0 ? mem::aligned_offset(size, win32::allocation_granularity()) : win32::allocation_granularity();
|
||||
$else
|
||||
return size > 0 ? mem::aligned_offset(size, mem::os_pagesize()) : mem::os_pagesize();
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate virtual memory, size is rounded up to platform granularity (Win32) / page size (Posix).
|
||||
|
||||
@param size : "The size of the memory to allocate, will be rounded up"
|
||||
@param access : "The initial access permissions."
|
||||
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
|
||||
@return "Pointer to the allocated memory, page aligned"
|
||||
*>
|
||||
fn void*? alloc(usz size, VirtualMemoryAccess access)
|
||||
{
|
||||
$switch:
|
||||
$case env::POSIX:
|
||||
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), posix::MAP_PRIVATE | posix::MAP_ANONYMOUS, -1, 0);
|
||||
if (ptr != posix::MAP_FAILED) return ptr;
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ENOMEM: return mem::OUT_OF_MEMORY~;
|
||||
case errno::EOVERFLOW: return RANGE_OVERFLOW~;
|
||||
case errno::EPERM: return ACCESS_DENIED~;
|
||||
case errno::EINVAL: return INVALID_ARGS~;
|
||||
default: return UNKNOWN_ERROR~;
|
||||
}
|
||||
$case env::WIN32:
|
||||
void* ptr = win32::virtualAlloc(null, aligned_alloc_size(size), MEM_RESERVE, access.to_win32());
|
||||
if (ptr) return ptr;
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_NOT_ENOUGH_MEMORY:
|
||||
case win32::ERROR_COMMITMENT_LIMIT: return mem::OUT_OF_MEMORY~;
|
||||
default: return UNKNOWN_ERROR~;
|
||||
}
|
||||
$default:
|
||||
unsupported("Virtual alloc only available on Win32 and Posix");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Release memory allocated with "alloc".
|
||||
|
||||
@param [&inout] ptr : "Pointer to page to release, should be allocated using vm::alloc"
|
||||
@param size : "The size of the allocated pointer"
|
||||
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
|
||||
*>
|
||||
fn void? release(void* ptr, usz size)
|
||||
{
|
||||
$switch:
|
||||
$case env::POSIX:
|
||||
if (posix::munmap(ptr, aligned_alloc_size(size)))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EINVAL: return INVALID_ARGS~; // Not a valid mapping or size
|
||||
case errno::ENOMEM: return UNMAPPED_ACCESS~; // Address not mapped
|
||||
default: return RELEASE_FAILED~;
|
||||
}
|
||||
}
|
||||
$case env::WIN32:
|
||||
if (win32::virtualFree(ptr, 0, MEM_RELEASE)) return;
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_INVALID_ADDRESS: return INVALID_ARGS~;
|
||||
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY~;
|
||||
default: return RELEASE_FAILED~;
|
||||
}
|
||||
$default:
|
||||
unsupported("Virtual free only available on Win32 and Posix");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Change the access protection of a region in memory. The region must be page aligned.
|
||||
|
||||
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
|
||||
@param len : "To what len to update, must be page aligned"
|
||||
@param access : "The new access"
|
||||
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
|
||||
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
|
||||
*>
|
||||
fn void? protect(void* ptr, usz len, VirtualMemoryAccess access)
|
||||
{
|
||||
$switch:
|
||||
$case env::POSIX:
|
||||
if (!posix::mprotect(ptr, len, access.to_posix())) return;
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES: return ACCESS_DENIED~;
|
||||
case errno::EINVAL: return UNALIGNED_ADDRESS~;
|
||||
case errno::EOVERFLOW: return RANGE_OVERFLOW~;
|
||||
case errno::ENOMEM: return UNMAPPED_ACCESS~;
|
||||
default: return UPDATE_FAILED~;
|
||||
}
|
||||
$case env::WIN32:
|
||||
Win32_Protect old;
|
||||
if (win32::virtualProtect(ptr, len, access.to_win32(), &old)) return;
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~;
|
||||
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~;
|
||||
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~;
|
||||
default: return UPDATE_FAILED~;
|
||||
}
|
||||
$default:
|
||||
unsupported("'virtual_protect' is only available on Win32 and Posix.");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Makes a region of memory available that was previously retrieved using 'alloc'. This is necessary on Win32,
|
||||
but optional on Posix.
|
||||
|
||||
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
|
||||
@param len : "To what len to commit, must be page aligned"
|
||||
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
|
||||
@return? UNKNOWN_ERROR, mem::OUT_OF_MEMORY, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
|
||||
*>
|
||||
fn void? commit(void* ptr, usz len, VirtualMemoryAccess access = READWRITE)
|
||||
{
|
||||
$switch:
|
||||
$case env::POSIX:
|
||||
return protect(ptr, len, READWRITE) @inline;
|
||||
$case env::WIN32:
|
||||
void* result = win32::virtualAlloc(ptr, len, MEM_COMMIT, access.to_win32());
|
||||
if (result) return;
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~;
|
||||
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~;
|
||||
case win32::ERROR_COMMITMENT_LIMIT:
|
||||
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY~;
|
||||
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~;
|
||||
default: return UNKNOWN_ERROR~;
|
||||
}
|
||||
$default:
|
||||
unsupported("'virtual_commit' is only available on Win32 and Posix.");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Notifies that the memory in the region can be released back to the OS. On Win32 this decommits the region,
|
||||
whereas on Posix it tells the system that it may be reused using madvise. The "block" parameter is only
|
||||
respected on Posix, and protects the region from read/write/exec. On Win32 this always happens.
|
||||
|
||||
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
|
||||
@param len : "To what len to commit, must be page aligned"
|
||||
@param block : "Set the released memory to protected"
|
||||
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
|
||||
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
|
||||
*>
|
||||
fn void? decommit(void* ptr, usz len, bool block = true)
|
||||
{
|
||||
$switch:
|
||||
$case env::POSIX:
|
||||
if (posix::madvise(ptr, len, posix::MADV_DONTNEED))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EINVAL: return UNALIGNED_ADDRESS~;
|
||||
case errno::ENOMEM: return UNMAPPED_ACCESS~;
|
||||
default: return UPDATE_FAILED~;
|
||||
}
|
||||
}
|
||||
if (block) (void)protect(ptr, len, PROTECTED) @inline;
|
||||
$case env::WIN32:
|
||||
if (!win32::virtualFree(ptr, len, MEM_DECOMMIT))
|
||||
{
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~;
|
||||
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~;
|
||||
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~;
|
||||
default: return UPDATE_FAILED~;
|
||||
}
|
||||
}
|
||||
$default:
|
||||
unsupported("'virtual_decommit' is only available on Win32 and Posix.");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Map a portion of an already-opened file into memory.
|
||||
|
||||
@param fd : "File descriptor"
|
||||
@param size : "Number of bytes to map, will be rounded up to page size"
|
||||
@param offset : "Byte offset in file, must be page size aligned"
|
||||
@param access : "The initial access permissions"
|
||||
@param shared : "if True then MAP_SHARED else MAP_PRIVATE"
|
||||
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
|
||||
@return "Pointer to the mapped region"
|
||||
*>
|
||||
fn void*? mmap_file(Fd fd, usz size, usz offset = 0, VirtualMemoryAccess access = READ, bool shared = false) @if (env::POSIX)
|
||||
{
|
||||
CInt flags = shared ? posix::MAP_SHARED : posix::MAP_PRIVATE;
|
||||
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), flags, fd, offset);
|
||||
if (ptr != posix::MAP_FAILED) return ptr;
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ENOMEM: return mem::OUT_OF_MEMORY~;
|
||||
case errno::EOVERFLOW: return RANGE_OVERFLOW~;
|
||||
case errno::EPERM: return ACCESS_DENIED~;
|
||||
case errno::EINVAL: return INVALID_ARGS~;
|
||||
case errno::EACCES: return io::NO_PERMISSION~;
|
||||
case errno::EBADF: return io::FILE_NOT_VALID~;
|
||||
case errno::EAGAIN: return io::WOULD_BLOCK~;
|
||||
case errno::ENXIO: return io::FILE_NOT_FOUND~;
|
||||
default: return UNKNOWN_ERROR~;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Create a VirtualMemory using
|
||||
|
||||
@param size : "The size of the memory to allocate."
|
||||
@require size > 0 : "The size must be non-zero"
|
||||
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
|
||||
*>
|
||||
fn VirtualMemory? virtual_alloc(usz size, VirtualMemoryAccess access = PROTECTED)
|
||||
{
|
||||
size = aligned_alloc_size(size);
|
||||
void* ptr = alloc(size, access)!;
|
||||
return { ptr, size, access };
|
||||
}
|
||||
|
||||
<*
|
||||
Commits memory, using vm::commit
|
||||
|
||||
@param offset : "Starting from what offset to commit"
|
||||
@param len : "To what len to commit"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
|
||||
@require offset < self.size : "Offset out of range"
|
||||
@require offset + len <= self.size : "Length out of range"
|
||||
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
|
||||
*>
|
||||
macro void? VirtualMemory.commit(self, usz offset, usz len)
|
||||
{
|
||||
return commit(self.ptr + offset, len, self.default_access);
|
||||
}
|
||||
|
||||
<*
|
||||
Changes protection of a part of memory using vm::protect
|
||||
|
||||
@param offset : "Starting from what offset to update"
|
||||
@param len : "To what len to update"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
|
||||
@require offset < self.size : "Offset out of range"
|
||||
@require offset + len < self.size : "Length out of range"
|
||||
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
|
||||
*>
|
||||
macro void? VirtualMemory.protect(self, usz offset, usz len, VirtualMemoryAccess access)
|
||||
{
|
||||
return protect(self.ptr + offset, len, access);
|
||||
}
|
||||
<*
|
||||
Decommits a part of memory using vm::decommit
|
||||
|
||||
@param offset : "Starting from what offset to decommit"
|
||||
@param len : "To what len to decommit"
|
||||
@param block : "Should the memory be blocked from access after decommit"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
|
||||
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
|
||||
@require offset < self.size : "Offset out of range"
|
||||
@require offset + len < self.size : "Length out of range"
|
||||
@return? UPDATE_FAILED
|
||||
*>
|
||||
fn void? VirtualMemory.decommit(self, usz offset, usz len, bool block = true)
|
||||
{
|
||||
return decommit(self.ptr + offset, len, block);
|
||||
}
|
||||
|
||||
<*
|
||||
Releases the memory region
|
||||
|
||||
@require self.ptr != null : "Virtual memory must be initialized to call destroy"
|
||||
*>
|
||||
fn void? VirtualMemory.destroy(&self)
|
||||
{
|
||||
return release(self.ptr, self.size);
|
||||
}
|
||||
|
||||
fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX)
|
||||
{
|
||||
switch (self)
|
||||
{
|
||||
case PROTECTED: return posix::PROT_NONE;
|
||||
case READ: return posix::PROT_READ;
|
||||
case WRITE: return posix::PROT_WRITE;
|
||||
case EXEC: return posix::PROT_EXEC;
|
||||
case READWRITE: return posix::PROT_READ | posix::PROT_WRITE;
|
||||
case EXECREAD: return posix::PROT_READ | posix::PROT_EXEC;
|
||||
case EXECWRITE: return posix::PROT_WRITE | posix::PROT_EXEC;
|
||||
case ANY: return posix::PROT_WRITE | posix::PROT_READ | posix::PROT_EXEC;
|
||||
}
|
||||
}
|
||||
|
||||
fn Win32_Protect VirtualMemoryAccess.to_win32(self) @if(env::WIN32)
|
||||
{
|
||||
switch (self)
|
||||
{
|
||||
case PROTECTED: return PAGE_NOACCESS;
|
||||
case READ: return PAGE_READONLY;
|
||||
case WRITE: return PAGE_READWRITE;
|
||||
case EXEC: return PAGE_EXECUTE;
|
||||
case READWRITE: return PAGE_READWRITE;
|
||||
case EXECWRITE: return PAGE_EXECUTE_READWRITE;
|
||||
case EXECREAD: return PAGE_EXECUTE_READ;
|
||||
case ANY: return PAGE_EXECUTE_READWRITE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ fn char[]? WasmMemory.allocate_block(&self, usz bytes)
|
||||
}
|
||||
|
||||
usz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE;
|
||||
if ($$wasm_memory_grow(0, blocks_required) == -1) return mem::OUT_OF_MEMORY?;
|
||||
if ($$wasm_memory_grow(0, blocks_required) == -1) return mem::OUT_OF_MEMORY~;
|
||||
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
defer self.use += bytes;
|
||||
return ((char*)self.use)[:bytes];
|
||||
@@ -143,7 +143,7 @@ uint128 x86_features;
|
||||
|
||||
fn void add_feature_if_bit(X86Feature feature, uint register, int bit)
|
||||
{
|
||||
if (register & 1U << bit) x86_features |= 1u128 << feature.ordinal;
|
||||
if (register & 1U << bit) x86_features |= 1ULL << feature.ordinal;
|
||||
}
|
||||
|
||||
fn void x86_initialize_cpu_features()
|
||||
|
||||
@@ -79,7 +79,7 @@ fn SegmentCommand64*? find_segment(MachHeader* header, char* segname)
|
||||
}
|
||||
command = (void*)command + command.cmdsize;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
fn Section64*? find_section(SegmentCommand64* command, char* sectname)
|
||||
{
|
||||
@@ -89,7 +89,7 @@ fn Section64*? find_section(SegmentCommand64* command, char* sectname)
|
||||
if (name_cmp(sectname, §ion.sectname)) return section;
|
||||
section++;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type)
|
||||
|
||||
@@ -7,16 +7,26 @@ macro usz _strlen(ptr) @private
|
||||
return len;
|
||||
}
|
||||
|
||||
macro int @main_to_err_main(#m, int, char**)
|
||||
macro int @main_no_args(#m, int, char**)
|
||||
{
|
||||
if (catch #m()) return 1;
|
||||
return 0;
|
||||
$if $typeof(#m()) == void:
|
||||
#m();
|
||||
return 0;
|
||||
$else
|
||||
return #m();
|
||||
$endif
|
||||
}
|
||||
macro int @main_to_int_main(#m, int, char**) => #m();
|
||||
macro int @main_to_void_main(#m, int, char**)
|
||||
|
||||
macro int @main_args(#m, int argc, char** argv)
|
||||
{
|
||||
#m();
|
||||
return 0;
|
||||
String[] list = args_to_strings(argc, argv);
|
||||
defer free(list.ptr);
|
||||
$if $typeof(#m(list)) == void:
|
||||
#m(list);
|
||||
return 0;
|
||||
$else
|
||||
return #m(list);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro String[] args_to_strings(int argc, char** argv) @private
|
||||
@@ -32,21 +42,6 @@ macro String[] args_to_strings(int argc, char** argv) @private
|
||||
}
|
||||
|
||||
|
||||
macro int @main_to_err_main_args(#m, int argc, char** argv)
|
||||
{
|
||||
String[] list = args_to_strings(argc, argv);
|
||||
defer free(list.ptr);
|
||||
if (catch #m(list)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @main_to_int_main_args(#m, int argc, char** argv)
|
||||
{
|
||||
String[] list = args_to_strings(argc, argv);
|
||||
defer free(list.ptr);
|
||||
return #m(list);
|
||||
}
|
||||
|
||||
macro int @_main_runner(#m, int argc, char** argv)
|
||||
{
|
||||
String[] list = args_to_strings(argc, argv);
|
||||
@@ -54,17 +49,20 @@ macro int @_main_runner(#m, int argc, char** argv)
|
||||
return #m(list) ? 0 : 1;
|
||||
}
|
||||
|
||||
macro int @main_to_void_main_args(#m, int argc, char** argv)
|
||||
module std::core::main_stub @if(env::WIN32);
|
||||
import std::os::win32;
|
||||
|
||||
macro win32_set_utf8_codepage() @local
|
||||
{
|
||||
String[] list = args_to_strings(argc, argv);
|
||||
defer free(list.ptr);
|
||||
#m(list);
|
||||
return 0;
|
||||
// By default windows uses an OEM codepage that differs based on locale
|
||||
// and does not support printing utf-8 characters. This allows both
|
||||
// printing utf-8 characters from strings and reading them from stdin.
|
||||
win32::setConsoleCP(UTF8);
|
||||
win32::setConsoleOutputCP(UTF8);
|
||||
}
|
||||
|
||||
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");
|
||||
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @cname("CommandLineToArgvW");
|
||||
|
||||
macro String[] win_command_line_to_strings(ushort* cmd_line) @private
|
||||
{
|
||||
@@ -91,90 +89,72 @@ macro void release_wargs(String[] list) @private
|
||||
free(list.ptr);
|
||||
}
|
||||
|
||||
macro int @win_to_err_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_main_no_args(#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, 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;
|
||||
win32_set_utf8_codepage();
|
||||
$if $typeof(#m()) == void:
|
||||
#m();
|
||||
return 0;
|
||||
$else
|
||||
return #m();
|
||||
$endif
|
||||
}
|
||||
|
||||
macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(args)) return 1;
|
||||
return 0;
|
||||
$if $typeof(#m(args)) == void:
|
||||
#m(args);
|
||||
return 0;
|
||||
$else
|
||||
return #m(args);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro int @win_to_int_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
macro int @win_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = win_command_line_to_strings(cmd_line);
|
||||
defer release_wargs(args);
|
||||
return #m(args);
|
||||
$if $typeof(#m(handle, prev_handle, args, show_cmd)) == void:
|
||||
#m(handle, prev_handle, args, show_cmd);
|
||||
return 0;
|
||||
$else
|
||||
return #m(handle, prev_handle, args, show_cmd);
|
||||
$endif
|
||||
}
|
||||
|
||||
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);
|
||||
#m(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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, prev_handle, args, show_cmd)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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, prev_handle, args, 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, prev_handle, args, show_cmd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro int @wmain_to_err_main_args(#m, int argc, Char16** argv)
|
||||
macro int @wmain_main(#m, int argc, Char16** argv)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
if (catch #m(args)) return 1;
|
||||
return 1;
|
||||
$if $typeof(#m(args)) == void:
|
||||
#m(args);
|
||||
return 0;
|
||||
$else
|
||||
return #m(args);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
|
||||
macro int @wmain_main_no_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
return #m(args);
|
||||
win32_set_utf8_codepage();
|
||||
$if $typeof(#m()) == void:
|
||||
#m();
|
||||
return 0;
|
||||
$else
|
||||
return #m();
|
||||
$endif
|
||||
}
|
||||
|
||||
macro int @_wmain_runner(#m, int argc, Char16** argv)
|
||||
{
|
||||
win32_set_utf8_codepage();
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
return #m(args) ? 0 : 1;
|
||||
}
|
||||
|
||||
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
#m(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
132
lib/std/core/refcount.c3
Normal file
132
lib/std/core/refcount.c3
Normal file
@@ -0,0 +1,132 @@
|
||||
<*
|
||||
Ref provides a general *external* ref counted wrapper for a pointer. For convenience, a ref count of 0
|
||||
means the reference is still valid.
|
||||
|
||||
When the rc drops to -1, it will first run the dealloc function on the underlying pointer (if it exists),
|
||||
then free the pointer and the atomic variable assuming that they are allocated using the Allocator in the Ref.
|
||||
|
||||
@require !$defined(Type.dealloc) ||| $defined(Type.dealloc(&&(Type){})) : "'dealloc' must only take a pointer to the underlying type"
|
||||
@require !$defined(Type.dealloc) ||| $typeof((Type){}.dealloc()) == void : "'dealloc' must return 'void'"
|
||||
*>
|
||||
module std::core::mem::ref <Type>;
|
||||
import std::thread, std::atomic;
|
||||
|
||||
const OVERALIGNED @private = Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
alias DeallocFn = fn void(void*);
|
||||
|
||||
fn Ref wrap(Type* ptr, Allocator allocator = mem)
|
||||
{
|
||||
return { .refcount = allocator::new(allocator, Atomic{int}), .ptr = ptr, .allocator = allocator };
|
||||
}
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $defined(Type a = $vaexpr[0]) : "The first argument must be an initializer for the type"
|
||||
*>
|
||||
macro Ref new(..., Allocator allocator = mem)
|
||||
{
|
||||
|
||||
$switch:
|
||||
$case OVERALIGNED && !$vacount:
|
||||
Type* ptr = allocator::calloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
|
||||
$case OVERALIGNED:
|
||||
Type* ptr = allocator::malloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
|
||||
*ptr = $vaexpr[0];
|
||||
$case !$vacount:
|
||||
Type* ptr = allocator::calloc(allocator, Type.sizeof);
|
||||
$default:
|
||||
Type* ptr = allocator::malloc(allocator, Type.sizeof);
|
||||
*ptr = $vaexpr[0];
|
||||
$endswitch
|
||||
return { .refcount = allocator::new(allocator, Atomic{int}),
|
||||
.ptr = ptr,
|
||||
.allocator = allocator };
|
||||
}
|
||||
|
||||
struct Ref
|
||||
{
|
||||
Atomic{int}* refcount;
|
||||
Type* ptr;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
fn Ref* Ref.retain(&self)
|
||||
{
|
||||
assert(self.refcount != null, "Reference already released");
|
||||
assert(self.refcount.load(RELAXED) >= 0, "Retaining zombie");
|
||||
self.refcount.add(1, RELAXED);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn void Ref.release(&self)
|
||||
{
|
||||
assert(self.refcount != null, "Reference already released");
|
||||
assert(self.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
|
||||
if (self.refcount.sub(1, RELAXED) == 0)
|
||||
{
|
||||
thread::fence(ACQUIRE);
|
||||
$if $defined(Type.dealloc):
|
||||
self.ptr.dealloc();
|
||||
$endif
|
||||
$if OVERALIGNED:
|
||||
allocator::free_aligned(self.allocator, self.ptr);
|
||||
$else
|
||||
allocator::free(self.allocator, self.ptr);
|
||||
$endif
|
||||
allocator::free(self.allocator, self.refcount);
|
||||
*self = {};
|
||||
}
|
||||
}
|
||||
|
||||
module std::core::mem::rc;
|
||||
import std::thread, std::atomic;
|
||||
|
||||
<*
|
||||
A RefCounted struct should be an inline base of a struct.
|
||||
If a `dealloc` is defined, then it will be called rather than `free`
|
||||
|
||||
For convenience, a ref count of 0 is still valid, and the struct is
|
||||
only freed when when ref count drops to -1.
|
||||
|
||||
The macros rc::retain and rc::release must be used on the full pointer,
|
||||
not on the RefCounted substruct.
|
||||
|
||||
So `Foo* f = ...; RefCounted* rc = f; rc::release(rc);` will not do the right thing.
|
||||
*>
|
||||
struct RefCounted
|
||||
{
|
||||
Atomic{int} refcount;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
|
||||
*>
|
||||
macro retain(refcounted)
|
||||
{
|
||||
if (refcounted)
|
||||
{
|
||||
assert(refcounted.refcount.load(RELAXED) >= 0, "Retaining zombie");
|
||||
refcounted.refcount.add(1, RELAXED);
|
||||
}
|
||||
return refcounted;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
|
||||
@require !$defined(refcounted.dealloc()) ||| $typeof(refcounted.dealloc()) == void
|
||||
: "Expected refcounted type to have a valid dealloc"
|
||||
*>
|
||||
macro void release(refcounted)
|
||||
{
|
||||
if (!refcounted) return;
|
||||
assert(refcounted.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
|
||||
if (refcounted.refcount.sub(1, RELAXED) == 0)
|
||||
{
|
||||
thread::fence(ACQUIRE);
|
||||
$if $defined(refcounted.dealloc):
|
||||
refcounted.dealloc();
|
||||
$else
|
||||
free(refcounted);
|
||||
$endif
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,27 @@ struct SliceRaw
|
||||
usz len;
|
||||
}
|
||||
|
||||
macro @enum_lookup($Type, #value, value)
|
||||
{
|
||||
$foreach $val : $Type.values:
|
||||
if ($val.#value == value) return $val;
|
||||
$endforeach
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
module std::core::runtime @if(WASM_NOLIBC);
|
||||
macro @enum_lookup_new($Type, $name, value)
|
||||
{
|
||||
$foreach $val : $Type.values:
|
||||
if ($val.$eval($name) == value) return $val;
|
||||
$endforeach
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
|
||||
module std::core::runtime @if(env::FREESTANDING_WASM);
|
||||
|
||||
extern fn void __wasm_call_ctors();
|
||||
fn void wasm_initialize() @extern("_initialize") @wasm
|
||||
fn void wasm_initialize() @cname("_initialize") @wasm
|
||||
{
|
||||
// The linker synthesizes this to call constructors.
|
||||
__wasm_call_ctors();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
module std::core::runtime;
|
||||
import libc, std::time, std::io, std::sort;
|
||||
import libc, std::time, std::io, std::sort, std::math, std::collections::map;
|
||||
|
||||
alias BenchmarkFn = fn void();
|
||||
alias BenchmarkFn = fn void ();
|
||||
|
||||
HashMap { String, uint } bench_fn_iters @local;
|
||||
|
||||
struct BenchmarkUnit
|
||||
{
|
||||
@@ -17,6 +19,7 @@ fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator)
|
||||
foreach (i, benchmark : fns)
|
||||
{
|
||||
benchmarks[i] = { names[i], fns[i] };
|
||||
if (!bench_fn_iters.has_key(names[i])) bench_fn_iters[names[i]] = benchmark_max_iterations;
|
||||
}
|
||||
return benchmarks;
|
||||
}
|
||||
@@ -36,6 +39,49 @@ fn void set_benchmark_max_iterations(uint value) @builtin
|
||||
{
|
||||
assert(value > 0);
|
||||
benchmark_max_iterations = value;
|
||||
foreach (k : bench_fn_iters.key_iter()) bench_fn_iters[k] = value;
|
||||
}
|
||||
|
||||
fn void set_benchmark_func_iterations(String func, uint value) @builtin
|
||||
{
|
||||
assert(value > 0);
|
||||
bench_fn_iters[func] = value;
|
||||
}
|
||||
|
||||
|
||||
Clock benchmark_clock @local;
|
||||
NanoDuration benchmark_nano_seconds @local;
|
||||
long cycle_start @local;
|
||||
long cycle_stop @local;
|
||||
DString benchmark_log @local;
|
||||
bool benchmark_warming @local;
|
||||
uint this_iteration @local;
|
||||
bool benchmark_stop @local;
|
||||
|
||||
macro void @start_benchmark()
|
||||
{
|
||||
benchmark_clock = clock::now();
|
||||
cycle_start = $$sysclock();
|
||||
}
|
||||
|
||||
macro void @end_benchmark()
|
||||
{
|
||||
benchmark_nano_seconds = benchmark_clock.mark();
|
||||
cycle_stop = $$sysclock();
|
||||
}
|
||||
|
||||
macro void @kill_benchmark(String format, ...)
|
||||
{
|
||||
@log_benchmark(format, $vasplat);
|
||||
benchmark_stop = true;
|
||||
}
|
||||
|
||||
macro void @log_benchmark(msg, args...) => @pool()
|
||||
{
|
||||
if (benchmark_warming) return;
|
||||
|
||||
benchmark_log.appendf("%s [%d]: ", $$FUNC, this_iteration);
|
||||
benchmark_log.appendfn(msg, ...args);
|
||||
}
|
||||
|
||||
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
|
||||
@@ -58,36 +104,70 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
|
||||
|
||||
name.clear();
|
||||
|
||||
long sys_clock_started;
|
||||
long sys_clock_finished;
|
||||
long sys_clocks;
|
||||
Clock clock;
|
||||
|
||||
foreach(unit : benchmarks)
|
||||
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());
|
||||
|
||||
benchmark_warming = true;
|
||||
for (uint i = 0; i < benchmark_warmup_iterations; i++)
|
||||
{
|
||||
unit.func() @inline;
|
||||
}
|
||||
benchmark_warming = false;
|
||||
|
||||
clock = std::time::clock::now();
|
||||
sys_clock_started = $$sysclock();
|
||||
NanoDuration running_timer;
|
||||
long total_clocks;
|
||||
|
||||
for (uint i = 0; i < benchmark_max_iterations; i++)
|
||||
uint current_benchmark_iterations = bench_fn_iters[unit.name] ?? benchmark_max_iterations;
|
||||
char[] perc_str = { [0..19] = ' ', [20] = 0 };
|
||||
int perc = 0;
|
||||
uint print_step = current_benchmark_iterations / 100;
|
||||
if (print_step == 0) print_step = 1;
|
||||
|
||||
for (this_iteration = 0; this_iteration < current_benchmark_iterations; ++this_iteration, benchmark_nano_seconds = {})
|
||||
{
|
||||
if (this_iteration % print_step == 0) // only print right about when the % will update
|
||||
{
|
||||
perc_str[0..(uint)math::floor((this_iteration / (float)current_benchmark_iterations) * 20)] = '#';
|
||||
perc = (uint)math::ceil(100 * (this_iteration / (float)current_benchmark_iterations));
|
||||
|
||||
io::printf("\r%s [%s] %d / %d (%d%%)", name.str_view(), (ZString)perc_str, this_iteration, current_benchmark_iterations, perc);
|
||||
io::stdout().flush()!!;
|
||||
}
|
||||
|
||||
@start_benchmark(); // can be overridden by calls inside the unit's func
|
||||
|
||||
unit.func() @inline;
|
||||
if (benchmark_stop) return false;
|
||||
|
||||
if (benchmark_nano_seconds == (NanoDuration){}) @end_benchmark(); // only mark when it wasn't already by the unit.func
|
||||
|
||||
total_clocks += cycle_stop - cycle_start;
|
||||
running_timer += benchmark_nano_seconds;
|
||||
}
|
||||
|
||||
sys_clock_finished = $$sysclock();
|
||||
NanoDuration nano_seconds = clock.mark();
|
||||
sys_clocks = sys_clock_finished - sys_clock_started;
|
||||
float clock_cycles = (float)total_clocks / current_benchmark_iterations;
|
||||
float measurement = (float)running_timer / current_benchmark_iterations;
|
||||
String[] units = { "nanoseconds", "microseconds", "milliseconds", "seconds" };
|
||||
|
||||
io::printfn("[COMPLETE] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
|
||||
float adjusted_measurement = measurement;
|
||||
while (adjusted_measurement > 1_000) adjusted_measurement /= 1_000;
|
||||
float adjusted_runtime_total = (float)running_timer;
|
||||
while (adjusted_runtime_total > 1_000) adjusted_runtime_total /= 1_000;
|
||||
|
||||
io::printf("\r%s ", name.str_view());
|
||||
io::printfn(
|
||||
"[COMPLETE] %.2f %s, %.2f CPU clocks, %d iterations (runtime %.2f %s)",
|
||||
adjusted_measurement,
|
||||
units[math::min(3, (int)math::floor(math::log(measurement, 1_000)))],
|
||||
clock_cycles,
|
||||
current_benchmark_iterations,
|
||||
adjusted_runtime_total,
|
||||
units[math::min(3, (int)math::floor(math::log((float)running_timer, 1_000)))],
|
||||
);
|
||||
}
|
||||
|
||||
io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : "");
|
||||
@@ -96,5 +176,12 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
|
||||
|
||||
fn bool default_benchmark_runner(String[] args) => @pool()
|
||||
{
|
||||
benchmark_log.init(mem);
|
||||
defer
|
||||
{
|
||||
if (benchmark_log.len()) io::printfn("\n---------- BENCHMARK LOG ----------\n%s\n", benchmark_log.str_view());
|
||||
benchmark_log.free();
|
||||
}
|
||||
|
||||
return run_benchmarks(benchmark_collection_create(tmem));
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
module std::core::runtime;
|
||||
import std::core::test @public;
|
||||
import std::core::mem::allocator @public;
|
||||
import libc, std::time, std::io, std::sort;
|
||||
import std::os::env;
|
||||
import libc, std::time, std::io, std::sort, std::os;
|
||||
|
||||
|
||||
alias TestFn = fn void();
|
||||
|
||||
@@ -14,10 +14,12 @@ TestContext* test_context @private;
|
||||
struct TestContext
|
||||
{
|
||||
JmpBuf buf;
|
||||
// Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add'
|
||||
<* Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add' *>
|
||||
String test_filter;
|
||||
// Triggers debugger breakpoint when assert or test:: checks failed
|
||||
<* Triggers debugger breakpoint when assert or test:: checks failed *>
|
||||
bool breakpoint_on_assert;
|
||||
<* Controls level of printed logs *>
|
||||
LogPriority log_level;
|
||||
|
||||
// internal state
|
||||
bool assert_print_backtrace;
|
||||
@@ -25,6 +27,8 @@ struct TestContext
|
||||
bool is_in_panic;
|
||||
bool is_quiet_mode;
|
||||
bool is_no_capture;
|
||||
bool sort;
|
||||
bool check_leaks;
|
||||
String current_test_name;
|
||||
TestFn setup_fn;
|
||||
TestFn teardown_fn;
|
||||
@@ -86,7 +90,21 @@ fn bool terminal_has_ansi_codes() @local => @pool()
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void sig_bus_error(CInt i, void*, void* context) @local @if(env::POSIX)
|
||||
{
|
||||
panic_test("Bus error", "Unknown", "Unknown", 1, posix::stack_instruction(context));
|
||||
}
|
||||
|
||||
fn void sig_segmentation_fault(CInt i, void*, void* context) @local @if(env::POSIX)
|
||||
{
|
||||
panic_test("Segmentation fault", "Unknown", "Unknown", 1, posix::stack_instruction(context));
|
||||
}
|
||||
|
||||
fn void test_panic(String message, String file, String function, uint line) @local
|
||||
{
|
||||
panic_test(message, file, function, line);
|
||||
}
|
||||
fn void panic_test(String message, String file, String function, uint line, void* extra_trace = null) @local
|
||||
{
|
||||
if (test_context.is_in_panic) return;
|
||||
test_context.is_in_panic = true;
|
||||
@@ -96,7 +114,7 @@ fn void test_panic(String message, String file, String function, uint line) @loc
|
||||
if (test_context.assert_print_backtrace)
|
||||
{
|
||||
$if env::NATIVE_STACKTRACE:
|
||||
builtin::print_backtrace(message, 0);
|
||||
builtin::print_backtrace(message, extra_trace ? 3 : 0, extra_trace);
|
||||
$endif
|
||||
}
|
||||
io::printf("\nTest failed ^^^ ( %s:%s ) %s\n", file, line, message);
|
||||
@@ -124,7 +142,7 @@ fn void mute_output() @local
|
||||
File* stderr = io::stderr();
|
||||
*stderr = test_context.fake_stdout;
|
||||
*stdout = test_context.fake_stdout;
|
||||
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
|
||||
(void)test_context.fake_stdout.set_cursor(0)!!;
|
||||
}
|
||||
|
||||
fn void unmute_output(bool has_error) @local
|
||||
@@ -137,10 +155,9 @@ fn void unmute_output(bool has_error) @local
|
||||
*stderr = test_context.stored.stderr;
|
||||
*stdout = test_context.stored.stdout;
|
||||
|
||||
usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
|
||||
ulong log_size = test_context.fake_stdout.cursor()!!;
|
||||
if (has_error)
|
||||
{
|
||||
io::printf("\nTesting %s ", test_context.current_test_name);
|
||||
io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
|
||||
}
|
||||
|
||||
@@ -148,7 +165,7 @@ fn void unmute_output(bool has_error) @local
|
||||
{
|
||||
test_context.fake_stdout.write_byte('\n')!!;
|
||||
test_context.fake_stdout.write_byte('\0')!!;
|
||||
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
|
||||
test_context.fake_stdout.set_cursor(0)!!;
|
||||
|
||||
io::printfn("\n========== TEST LOG ============");
|
||||
io::printfn("%s\n", test_context.current_test_name);
|
||||
@@ -166,11 +183,19 @@ fn void unmute_output(bool has_error) @local
|
||||
(void)stdout.flush();
|
||||
}
|
||||
|
||||
|
||||
fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
{
|
||||
usz max_name;
|
||||
bool sort_tests = true;
|
||||
bool check_leaks = true;
|
||||
if (!tests.len)
|
||||
{
|
||||
io::printn("There are no test units to run.");
|
||||
return true; // no tests == technically a pass
|
||||
}
|
||||
$if !env::NO_LIBC && env::POSIX:
|
||||
posix::install_signal_handler(libc::SIGBUS, &sig_bus_error);
|
||||
posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
|
||||
$endif
|
||||
foreach (&unit : tests)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
@@ -179,6 +204,9 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
{
|
||||
.assert_print_backtrace = true,
|
||||
.breakpoint_on_assert = false,
|
||||
.sort = true,
|
||||
.check_leaks = true,
|
||||
.log_level = LogPriority.ERROR,
|
||||
.test_filter = "",
|
||||
.has_ansi_codes = terminal_has_ansi_codes(),
|
||||
.stored.allocator = mem,
|
||||
@@ -192,10 +220,11 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
case "--test-breakpoint":
|
||||
context.breakpoint_on_assert = true;
|
||||
case "--test-nosort":
|
||||
sort_tests = false;
|
||||
context.sort = false;
|
||||
case "--test-noleak":
|
||||
check_leaks = false;
|
||||
context.check_leaks = false;
|
||||
case "--test-nocapture":
|
||||
case "--test-show-output":
|
||||
context.is_no_capture = true;
|
||||
case "--noansi":
|
||||
context.has_ansi_codes = false;
|
||||
@@ -211,13 +240,30 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
}
|
||||
context.test_filter = args[i + 1];
|
||||
i++;
|
||||
case "--test-log-level":
|
||||
if (i == args.len - 1)
|
||||
{
|
||||
io::printn("Missing log level for argument `--test-log-level`.");
|
||||
return false;
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
String upper = args[i + 1].to_upper_copy(tmem);
|
||||
if (catch @try(context.log_level, enum_by_name(LogPriority, upper)))
|
||||
{
|
||||
io::printn("Log level given to `--test-log-level` is not one of verbose, debug, info, warn, error or critical.");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
i++;
|
||||
default:
|
||||
io::printfn("Unknown argument: %s", args[i]);
|
||||
}
|
||||
}
|
||||
test_context = &context;
|
||||
log::set_priority_all(test_context.log_level);
|
||||
|
||||
if (sort_tests)
|
||||
if (context.sort)
|
||||
{
|
||||
quicksort(tests, &cmp_test_unit);
|
||||
}
|
||||
@@ -277,14 +323,17 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
{
|
||||
mute_output();
|
||||
mem.clear();
|
||||
if (check_leaks) allocator::thread_allocator = &mem;
|
||||
unit.func();
|
||||
if (context.check_leaks) allocator::thread_allocator = &mem;
|
||||
@pool()
|
||||
{
|
||||
unit.func();
|
||||
};
|
||||
// track cleanup that may take place in teardown_fn
|
||||
if (context.teardown_fn)
|
||||
{
|
||||
context.teardown_fn();
|
||||
}
|
||||
if (check_leaks) allocator::thread_allocator = context.stored.allocator;
|
||||
if (context.check_leaks) allocator::thread_allocator = context.stored.allocator;
|
||||
|
||||
unmute_output(false); // all good, discard output
|
||||
if (mem.has_leaks())
|
||||
@@ -305,7 +354,7 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
}
|
||||
mem.free();
|
||||
}
|
||||
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count > 1 ? "s" : "");
|
||||
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count != 1 ? "s" : "");
|
||||
|
||||
int n_failed = test_count - tests_passed - tests_skipped;
|
||||
io::printf("Test Result: %s%s%s: ",
|
||||
|
||||
@@ -29,11 +29,11 @@ alias ErrorCallback = fn void (ZString);
|
||||
@param addr : "Start of memory region."
|
||||
@param size : "Size of memory region."
|
||||
*>
|
||||
macro poison_memory_region(void* addr, usz size)
|
||||
macro void poison_memory_region(void* addr, usz size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__asan_poison_memory_region(addr, size);
|
||||
$endif
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -50,11 +50,11 @@ macro poison_memory_region(void* addr, usz size)
|
||||
@param addr : "Start of memory region."
|
||||
@param size : "Size of memory region."
|
||||
*>
|
||||
macro unpoison_memory_region(void* addr, usz size)
|
||||
macro void unpoison_memory_region(void* addr, usz size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__asan_unpoison_memory_region(addr, size);
|
||||
$endif
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
|
||||
@@ -2,16 +2,16 @@ module std::core::sanitizer::tsan;
|
||||
|
||||
typedef 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_LINKER_INIT = (MutexFlags)1 << 0;
|
||||
const MutexFlags MUTEX_WRITE_REENTRANT = (MutexFlags)1 << 1;
|
||||
const MutexFlags MUTEX_READ_REENTRANT = (MutexFlags)1 << 2;
|
||||
const MutexFlags MUTEX_NOT_STATIC = (MutexFlags)1 << 8;
|
||||
const MutexFlags MUTEX_READ_LOCK = (MutexFlags)1 << 3;
|
||||
const MutexFlags MUTEX_TRY_LOCK = (MutexFlags)1 << 4;
|
||||
const MutexFlags MUTEX_TRY_LOCK_FAILED = (MutexFlags)1 << 5;
|
||||
const MutexFlags MUTEX_RECURSIVE_LOCK = (MutexFlags)1 << 6;
|
||||
const MutexFlags MUTEX_RECURSIVE_UNLOCK = (MutexFlags)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 }
|
||||
|
||||
169
lib/std/core/slice2d.c3
Normal file
169
lib/std/core/slice2d.c3
Normal file
@@ -0,0 +1,169 @@
|
||||
module std::core::array;
|
||||
|
||||
<*
|
||||
A slice2d allows slicing an array like int[10][10] into an arbitrary "int[][]"-like counterpart
|
||||
Typically you'd use array::slice2d(...) to create one.
|
||||
*>
|
||||
struct Slice2d <Type>
|
||||
{
|
||||
Type* ptr;
|
||||
usz inner_len;
|
||||
usz ystart;
|
||||
usz ylen;
|
||||
usz xstart;
|
||||
usz xlen;
|
||||
}
|
||||
|
||||
<*
|
||||
@return `The length of the "outer" slice`
|
||||
*>
|
||||
fn usz Slice2d.len(&self) @operator(len)
|
||||
{
|
||||
return self.ylen;
|
||||
}
|
||||
|
||||
<*
|
||||
@return `The total number of elements.`
|
||||
*>
|
||||
fn usz Slice2d.count(&self)
|
||||
{
|
||||
return self.ylen * self.xlen;
|
||||
}
|
||||
|
||||
<*
|
||||
Step through each element of the slice.
|
||||
*>
|
||||
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
|
||||
{
|
||||
foreach (y, line : *self)
|
||||
{
|
||||
foreach (x, val : line)
|
||||
{
|
||||
@body({ x, y }, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Step through each element of the slice *by reference*
|
||||
*>
|
||||
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
|
||||
{
|
||||
foreach (y, line : *self)
|
||||
{
|
||||
foreach (x, &val : line)
|
||||
{
|
||||
@body({ x, y }, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Return a row as a slice.
|
||||
|
||||
@param idy : "The row to return"
|
||||
@return "The slice for the particular row"
|
||||
@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];
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value at a particular x/y position in the slice.
|
||||
|
||||
@param coord : "The xy coordinate"
|
||||
@return "The value at that coordinate"
|
||||
@require coord.y >= 0 && coord.y < self.ylen : "y value out of range"
|
||||
@require coord.x >= 0 && coord.x < self.xlen : "x value out of range"
|
||||
*>
|
||||
macro Type Slice2d.get_coord(self, usz[<2>] coord)
|
||||
{
|
||||
return *self.get_coord_ref(coord);
|
||||
}
|
||||
|
||||
<*
|
||||
Get a pointer to the value at a particular x/y position in the slice.
|
||||
|
||||
@param coord : "The xy coordinate"
|
||||
@return "A pointer to the value at that coordinate"
|
||||
@require coord.y >= 0 && coord.y < self.ylen : "y value out of range"
|
||||
@require coord.x >= 0 && coord.x < self.xlen : "x value out of range"
|
||||
*>
|
||||
macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord)
|
||||
{
|
||||
return self.get_xy_ref(coord.x, coord.y);
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value at a particular x/y position in the slice.
|
||||
|
||||
@param x : "The x coordinate"
|
||||
@param y : "The x coordinate"
|
||||
@return "The value at that coordinate"
|
||||
@require y >= 0 && y < self.ylen : "y value out of range"
|
||||
@require x >= 0 && x < self.xlen : "x value out of range"
|
||||
*>
|
||||
macro Type Slice2d.get_xy(self, x, y)
|
||||
{
|
||||
return *self.get_xy_ref(x, y);
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value at a particular x/y position in the slice by reference.
|
||||
|
||||
@param x : "The x coordinate"
|
||||
@param y : "The y coordinate"
|
||||
@return "A pointer to the value at that coordinate"
|
||||
@require y >= 0 && y < self.ylen : "y value out of range"
|
||||
@require x >= 0 && x < self.xlen : "x value out of range"
|
||||
*>
|
||||
macro Type* Slice2d.get_xy_ref(self, x, y)
|
||||
{
|
||||
return self.ptr + self.inner_len * (y + self.ystart) + self.xstart + x;
|
||||
}
|
||||
|
||||
<*
|
||||
Set the ´value at a particular x/y position in the slice.
|
||||
|
||||
@param coord : "The xy coordinate"
|
||||
@param value : "The new value"
|
||||
@require coord.y >= 0 && coord.y < self.ylen : "y value out of range"
|
||||
@require coord.x >= 0 && coord.x < self.xlen : "x value out of range"
|
||||
*>
|
||||
macro void Slice2d.set_coord(self, usz[<2>] coord, Type value)
|
||||
{
|
||||
*self.get_coord_ref(coord) = value;
|
||||
}
|
||||
|
||||
<*
|
||||
Set the value at a particular x/y position in the slice.
|
||||
|
||||
@param x : "The x coordinate"
|
||||
@param y : "The y coordinate"
|
||||
@param value : "The new value"
|
||||
@require y >= 0 && y < self.ylen : "y value out of range"
|
||||
@require x >= 0 && x < self.xlen : "x value out of range"
|
||||
*>
|
||||
macro void Slice2d.set_xy(self, x, y, Type value)
|
||||
{
|
||||
*self.get_xy_ref(x, y) = value;
|
||||
}
|
||||
|
||||
<*
|
||||
Reslice a slice2d returning a new slice.
|
||||
|
||||
@param x : "The starting x"
|
||||
@param xlen : "The length along x"
|
||||
@param y : "The starting y"
|
||||
@param ylen : "The length along y"
|
||||
@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 };
|
||||
}
|
||||
@@ -1,46 +1,90 @@
|
||||
module std::core::string;
|
||||
import std::ascii;
|
||||
import std::io;
|
||||
import std::io, std::ascii;
|
||||
import std::core::mem::allocator;
|
||||
|
||||
typedef String @if(!$defined(String)) = inline char[];
|
||||
typedef ZString = inline char*;
|
||||
|
||||
|
||||
typedef String @constinit @if(!$defined(String)) = inline char[];
|
||||
<*
|
||||
ZString is a pointer to a zero terminated array of chars.
|
||||
|
||||
Use ZString when you need to interop with C zero terminated strings.
|
||||
*>
|
||||
typedef ZString @constinit = inline char*;
|
||||
|
||||
<*
|
||||
WString is a pointer to a zero terminated array of Char16.
|
||||
|
||||
Depending on the platform, this may or may not correspond to wchar_t.
|
||||
For Windows, wchar_t is generally 16 bits, on MacOS it is 32 bits.
|
||||
However, for both MacOS and Linux, regular C strings (ZString)
|
||||
will be UTF-8 encoded, so there is no need to use the wchar_t versions
|
||||
of functions outside of encoding functions.
|
||||
*>
|
||||
typedef WString = inline Char16*;
|
||||
|
||||
<*
|
||||
Char32 is a UTF32 codepoint
|
||||
*>
|
||||
alias Char32 = uint;
|
||||
|
||||
<*
|
||||
Char16 is a UTF16 "character"
|
||||
*>
|
||||
alias Char16 = ushort;
|
||||
|
||||
<*
|
||||
Common faults used with strings
|
||||
*>
|
||||
faultdef INVALID_UTF8, INVALID_UTF16, CONVERSION_FAILED,
|
||||
EMPTY_STRING, NEGATIVE_VALUE, MALFORMED_INTEGER,
|
||||
INTEGER_OVERFLOW, MALFORMED_FLOAT, FLOAT_OUT_OF_RANGE;
|
||||
|
||||
const uint SURROGATE_OFFSET @private = 0x10000;
|
||||
const uint SURROGATE_GENERIC_MASK @private = 0xF800;
|
||||
const uint SURROGATE_MASK @private = 0xFC00;
|
||||
const uint SURROGATE_CODEPOINT_MASK @private = 0x03FF;
|
||||
const uint SURROGATE_BITS @private = 10;
|
||||
const uint SURROGATE_LOW_VALUE @private = 0xDC00;
|
||||
const uint SURROGATE_HIGH_VALUE @private = 0xD800;
|
||||
|
||||
<*
|
||||
Create a pointer to an UTF32 encoded string at compile time.
|
||||
|
||||
@param $string : "The string to encode"
|
||||
*>
|
||||
macro Char32* @wstring32(String $string) @builtin
|
||||
{
|
||||
return (Char32*)&&$$wstr32($string);
|
||||
}
|
||||
|
||||
<*
|
||||
Create a slice of an UTF32 encoded string at compile time.
|
||||
|
||||
@param $string : "The string to encode"
|
||||
*>
|
||||
macro Char32[] @char32(String $string) @builtin
|
||||
{
|
||||
return $$wstr32($string)[..^2];
|
||||
}
|
||||
|
||||
<*
|
||||
Create a WString (an UTF16 encoded string) at compile time.
|
||||
|
||||
@param $string : "The string to encode"
|
||||
*>
|
||||
macro WString @wstring(String $string) @builtin
|
||||
{
|
||||
return (WString)&&$$wstr16($string);
|
||||
}
|
||||
|
||||
<*
|
||||
Create a slice of an UTF16 encoded string at compile time.
|
||||
|
||||
@param $string : "The string to encode"
|
||||
*>
|
||||
macro Char16[] @char16(String $string) @builtin
|
||||
{
|
||||
return $$wstr16($string)[..^2];
|
||||
}
|
||||
|
||||
macro String @sprintf(String $format, ...) @builtin @const
|
||||
{
|
||||
return $$sprintf($format, $vasplat);
|
||||
}
|
||||
<*
|
||||
Return a temporary ZString created using the formatting function.
|
||||
|
||||
@@ -54,7 +98,8 @@ fn ZString tformat_zstr(String fmt, args...) @format(0)
|
||||
}
|
||||
|
||||
<*
|
||||
Return a new String created using the formatting function.
|
||||
Return a new String created using the formatting function, this function will implicitly
|
||||
use the temp allocator.
|
||||
|
||||
@param [inout] allocator : `The allocator to use`
|
||||
@param [in] fmt : `The formatting string`
|
||||
@@ -66,6 +111,28 @@ fn String format(Allocator allocator, String fmt, args...) @format(1) => @pool()
|
||||
return str.copy_str(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
Return a new String created using the formatting function, the resulting string must fit the buffer.
|
||||
|
||||
@param [inout] buffer : `The buffer to use`
|
||||
@param [in] fmt : `The formatting string`
|
||||
*>
|
||||
fn String bformat(char[] buffer, String fmt, args...) @format(1)
|
||||
{
|
||||
Formatter f;
|
||||
OutputFn format_fn = fn void?(void* buf, char c) {
|
||||
char[]* buffer_ref = buf;
|
||||
char[] buffer = *buffer_ref;
|
||||
if (buffer.len == 0) return io::BUFFER_EXCEEDED~;
|
||||
buffer[0] = c;
|
||||
*buffer_ref = buffer[1..];
|
||||
};
|
||||
char[] buffer_copy = buffer;
|
||||
f.init(format_fn, &buffer_copy);
|
||||
usz len = f.vprintf(fmt, args)!!;
|
||||
return (String)buffer[:len];
|
||||
}
|
||||
|
||||
<*
|
||||
Return a temporary String created using the formatting function.
|
||||
|
||||
@@ -82,7 +149,7 @@ fn String tformat(String fmt, args...) @format(0)
|
||||
Check if a character is in a set.
|
||||
|
||||
@param c : `the character to check`
|
||||
@param [in] set : `The formatting string`
|
||||
@param [in] set : `String containing the characters`
|
||||
@pure
|
||||
@return `True if a character is in the set`
|
||||
*>
|
||||
@@ -92,31 +159,90 @@ macro bool char_in_set(char c, String set)
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
Join together an array of strings via a "joiner" sequence, which is inserted between each element.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use."
|
||||
@param [in] s : "An array of strings to join in sequence."
|
||||
@param [in] joiner : "The string used to join each element of `s`."
|
||||
@return "A single string containing the result, allocated via `allocator`, safe to convert to a ZString."
|
||||
*>
|
||||
fn String join(Allocator allocator, String[] s, String joiner)
|
||||
{
|
||||
if (!s)
|
||||
{
|
||||
return (String)allocator::new_array(allocator, char, 2)[:0];
|
||||
}
|
||||
|
||||
usz total_size = joiner.len * s.len;
|
||||
usz joiner_len = joiner.len;
|
||||
usz total_size = joiner_len * (s.len - 1) + 1;
|
||||
foreach (String* &str : s)
|
||||
{
|
||||
total_size += str.len;
|
||||
}
|
||||
@pool()
|
||||
char[] data = allocator::alloc_array(allocator, char, total_size);
|
||||
usz offset = s[0].len;
|
||||
data[:offset] = s[0][:offset];
|
||||
foreach (String* &str : s[1..])
|
||||
{
|
||||
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(allocator);
|
||||
data[offset:joiner_len] = joiner[:joiner_len];
|
||||
offset += joiner_len;
|
||||
usz len = str.len;
|
||||
data[offset:len] = str.[:len];
|
||||
offset += len;
|
||||
};
|
||||
data[offset] = 0;
|
||||
return (String)data[:offset];
|
||||
}
|
||||
|
||||
<* Alias for `string::join` using the temp allocator. *>
|
||||
macro String tjoin(String[] s, String joiner) => join(tmem, s, joiner);
|
||||
|
||||
<*
|
||||
Replace all instances of one substring with a different string.
|
||||
|
||||
@param [in] self
|
||||
@param [in] needle : `The string to be replaced`
|
||||
@param [in] new_str : `The replacement string`
|
||||
@param [&inout] allocator : `The allocator to use for the String`
|
||||
@return "The new string with the elements replaced"
|
||||
*>
|
||||
fn String String.replace(self, Allocator allocator, String needle, String new_str) @nodiscard => @pool()
|
||||
{
|
||||
Splitter s = self.tokenize_all(needle);
|
||||
DString d;
|
||||
d.init(tmem, new_str.len * 2 + self.len + 16);
|
||||
(void)d.append(s.next());
|
||||
while (try element = s.next())
|
||||
{
|
||||
d.append(new_str);
|
||||
d.append(element);
|
||||
}
|
||||
return d.copy_str(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
Replace all instances of one substring with a different string, allocating the new string on the temp allocator.
|
||||
|
||||
@param [in] self
|
||||
@param [in] needle : `The string to be replaced`
|
||||
@param [in] new_str : `The replacement string`
|
||||
@return "The new string with the elements replaced"
|
||||
*>
|
||||
fn String String.treplace(self, String needle, String new_str)
|
||||
{
|
||||
Splitter s = self.tokenize_all(needle);
|
||||
DString d;
|
||||
d.init(tmem, new_str.len * 2 + self.len + 16);
|
||||
(void)d.append(s.next());
|
||||
while (try element = s.next())
|
||||
{
|
||||
d.append(new_str);
|
||||
d.append(element);
|
||||
}
|
||||
return d.str_view();
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Remove characters from the front and end of a string.
|
||||
|
||||
@@ -125,11 +251,28 @@ fn String join(Allocator allocator, String[] s, String joiner)
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim(self, String to_trim = "\t\n\r ")
|
||||
fn String String.trim(self, String to_trim = " \n\t\r\f\v")
|
||||
{
|
||||
return self.trim_left(to_trim).trim_right(to_trim);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove characters from the front and end of a string.
|
||||
|
||||
@param [in] self : `The string to trim`
|
||||
@param to_trim : `The set of characters to trim, defaults to whitespace`
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim_charset(self, AsciiCharset to_trim = ascii::WHITESPACE_SET)
|
||||
{
|
||||
usz start = 0;
|
||||
usz len = self.len;
|
||||
while (start < len && to_trim.contains(self[start])) start++;
|
||||
while (len > start && to_trim.contains(self[len - 1])) len--;
|
||||
return self[start..len - 1];
|
||||
}
|
||||
|
||||
<*
|
||||
Remove characters from the front of a string.
|
||||
|
||||
@@ -138,7 +281,7 @@ fn String String.trim(self, String to_trim = "\t\n\r ")
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim_left(self, String to_trim = "\t\n\r ")
|
||||
fn String String.trim_left(self, String to_trim = " \n\t\r\f\v")
|
||||
{
|
||||
usz start = 0;
|
||||
usz len = self.len;
|
||||
@@ -155,7 +298,7 @@ fn String String.trim_left(self, String to_trim = "\t\n\r ")
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim_right(self, String to_trim = "\t\n\r ")
|
||||
fn String String.trim_right(self, String to_trim = " \n\t\r\f\v")
|
||||
{
|
||||
usz len = self.len;
|
||||
while (len > 0 && char_in_set(self[len - 1], to_trim)) len--;
|
||||
@@ -231,7 +374,7 @@ fn String String.strip_end(self, String suffix)
|
||||
@param [&inout] allocator : "The allocator to use for the String[]"
|
||||
|
||||
@require delimiter.len > 0 : "The delimiter must be at least 1 character long"
|
||||
@ensure return.len > 0
|
||||
@ensure return.len > 0 || skip_empty
|
||||
*>
|
||||
fn String[] String.split(self, Allocator allocator, String delimiter, usz max = 0, bool skip_empty = false)
|
||||
{
|
||||
@@ -241,7 +384,7 @@ fn String[] String.split(self, Allocator allocator, String delimiter, usz max =
|
||||
bool no_more = false;
|
||||
while (!no_more)
|
||||
{
|
||||
usz? index = i == max - 1 ? NOT_FOUND? : self.index_of(delimiter);
|
||||
usz? index = i == max - 1 ? NOT_FOUND~ : self.index_of(delimiter);
|
||||
String res @noinit;
|
||||
if (try index)
|
||||
{
|
||||
@@ -290,7 +433,7 @@ faultdef BUFFER_EXCEEDED;
|
||||
@param [inout] buffer
|
||||
@param max : "Max number of elements, 0 means no limit, defaults to 0"
|
||||
@require delimiter.len > 0 : "The delimiter must be at least 1 character long"
|
||||
@ensure return.len > 0
|
||||
@ensure return.len > 0 || skip_empty
|
||||
@return? BUFFER_EXCEEDED : `If there are more elements than would fit the buffer`
|
||||
*>
|
||||
fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz max = 0, bool skip_empty = false)
|
||||
@@ -300,7 +443,7 @@ fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz ma
|
||||
bool no_more = false;
|
||||
while (!no_more)
|
||||
{
|
||||
usz? index = i == max - 1 ? NOT_FOUND? : s.index_of(delimiter);
|
||||
usz? index = i == max - 1 ? NOT_FOUND~ : s.index_of(delimiter);
|
||||
String res @noinit;
|
||||
if (try index)
|
||||
{
|
||||
@@ -318,7 +461,7 @@ fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz ma
|
||||
}
|
||||
if (i == max_capacity)
|
||||
{
|
||||
return BUFFER_EXCEEDED?;
|
||||
return BUFFER_EXCEEDED~;
|
||||
}
|
||||
buffer[i++] = res;
|
||||
}
|
||||
@@ -338,6 +481,51 @@ fn bool String.contains(s, String substr)
|
||||
return @ok(s.index_of(substr));
|
||||
}
|
||||
|
||||
<*
|
||||
Check if a character is found in the string.
|
||||
|
||||
@param [in] s
|
||||
@param character : "The character to look for."
|
||||
@pure
|
||||
@return "true if the string contains the character, false otherwise"
|
||||
*>
|
||||
fn bool String.contains_char(s, char character)
|
||||
{
|
||||
return @ok(s.index_of_char(character));
|
||||
}
|
||||
|
||||
<*
|
||||
Check how many non-overlapping instances of a substring there is.
|
||||
|
||||
If the substring has zero length, the number of matches is zero.
|
||||
|
||||
@param [in] self : "The string to search"
|
||||
@param [in] substr : "The string to look for."
|
||||
@pure
|
||||
@return "The number of times matched"
|
||||
*>
|
||||
fn usz String.count(self, String substr)
|
||||
{
|
||||
usz count = 0;
|
||||
usz needed = substr.len;
|
||||
if (needed == 0) return 0;
|
||||
char first = substr[0];
|
||||
while OUTER: (self.len >= needed)
|
||||
{
|
||||
foreach (i, c: self[..^needed])
|
||||
{
|
||||
if (c == first && self[i : needed] == substr)
|
||||
{
|
||||
count++;
|
||||
self = self[i + needed..];
|
||||
continue OUTER;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
<*
|
||||
Find the index of the first incidence of a string.
|
||||
|
||||
@@ -354,7 +542,7 @@ fn usz? String.index_of_char(self, char character)
|
||||
{
|
||||
if (c == character) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -377,7 +565,7 @@ fn usz? String.index_of_chars(String self, char[] characters)
|
||||
}
|
||||
}
|
||||
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -394,12 +582,12 @@ fn usz? String.index_of_chars(String self, char[] characters)
|
||||
fn usz? String.index_of_char_from(self, char character, usz start_index)
|
||||
{
|
||||
usz len = self.len;
|
||||
if (len <= start_index) return NOT_FOUND?;
|
||||
if (len <= start_index) return NOT_FOUND~;
|
||||
for (usz i = start_index; i < len; i++)
|
||||
{
|
||||
if (self[i] == character) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -418,7 +606,7 @@ fn usz? String.rindex_of_char(self, char character)
|
||||
{
|
||||
if (c == character) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -443,7 +631,7 @@ fn usz? String.index_of(self, String substr)
|
||||
if (c == first && self[i : needed] == substr) return i;
|
||||
}
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -468,7 +656,21 @@ fn usz? String.rindex_of(self, String substr)
|
||||
if (c == first && self[i : needed] == substr) return i;
|
||||
}
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
return NOT_FOUND~;
|
||||
}
|
||||
|
||||
fn bool ZString.eq(self, ZString other) @operator(==)
|
||||
{
|
||||
char* a = self;
|
||||
char* b = other;
|
||||
if (a == b) return true;
|
||||
if (!a || !b) return false;
|
||||
for (;; a++, b++)
|
||||
{
|
||||
char c = *a;
|
||||
if (c != *b) return false;
|
||||
if (!c) return true;
|
||||
}
|
||||
}
|
||||
|
||||
fn String ZString.str_view(self)
|
||||
@@ -489,12 +691,17 @@ fn usz ZString.char_len(str)
|
||||
|
||||
fn usz ZString.len(self)
|
||||
{
|
||||
usz len = 0;
|
||||
char* ptr = (char*)self;
|
||||
while (char c = ptr++[0]) len++;
|
||||
usz len;
|
||||
for (char* ptr = (char*)self; *ptr; ptr++) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
fn usz WString.len(self)
|
||||
{
|
||||
usz len;
|
||||
for (Char16* ptr = (Char16*)self; *ptr; ptr++) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
fn ZString String.zstr_copy(self, Allocator allocator)
|
||||
{
|
||||
@@ -637,6 +844,92 @@ fn String String.to_upper_copy(self, Allocator allocator)
|
||||
return copy;
|
||||
}
|
||||
|
||||
fn String String.capitalize_copy(self, Allocator allocator)
|
||||
{
|
||||
String s = self.copy(allocator);
|
||||
if (s.len > 0 && s[0].is_lower())
|
||||
{
|
||||
s[0] &= (char)~0x20;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
<*
|
||||
Convert a string from `snake_case` to PascalCase.
|
||||
|
||||
@param [in] self
|
||||
@return `"FooBar" from "foo_bar" the resulting pointer may safely be cast to ZString.`
|
||||
*>
|
||||
fn String String.snake_to_pascal_copy(self, Allocator allocator)
|
||||
{
|
||||
Splitter splitter = self.tokenize("_");
|
||||
char[] new_string = allocator::alloc_array(allocator, char, self.len + 1);
|
||||
usz index = 0;
|
||||
while (try s = splitter.next())
|
||||
{
|
||||
assert(s.len > 0);
|
||||
char c = s[0];
|
||||
if (c.is_lower()) c = c.to_upper();
|
||||
new_string[index++] = c;
|
||||
s = s[1..];
|
||||
new_string[index:s.len] = s[..];
|
||||
index += s.len;
|
||||
}
|
||||
new_string[index] = 0;
|
||||
return (String)new_string[:index];
|
||||
}
|
||||
|
||||
<*
|
||||
Movifies the current string from `snake_case` to PascalCase.
|
||||
|
||||
@param [inout] self
|
||||
*>
|
||||
fn void String.convert_snake_to_pascal(&self)
|
||||
{
|
||||
Splitter splitter = self.tokenize("_");
|
||||
String new_string = *self;
|
||||
usz index = 0;
|
||||
while (try s = splitter.next())
|
||||
{
|
||||
assert(s.len > 0);
|
||||
char c = s[0];
|
||||
if (c.is_lower()) c = c.to_upper();
|
||||
new_string[index++] = c;
|
||||
s = s[1..];
|
||||
new_string[index:s.len] = s[..];
|
||||
index += s.len;
|
||||
}
|
||||
*self = new_string[:index];
|
||||
}
|
||||
|
||||
<*
|
||||
Convert a string from `PascalCase` to `snake_case`.
|
||||
|
||||
@param [in] self
|
||||
@return `"foo_bar" from "FooBar" the resulting pointer may safely be cast to ZString.`
|
||||
*>
|
||||
fn String String.pascal_to_snake_copy(self, Allocator allocator) => @pool()
|
||||
{
|
||||
DString d;
|
||||
d.init(tmem, (usz)(self.len * 1.5));
|
||||
usz index = 0;
|
||||
foreach (i, c : self)
|
||||
{
|
||||
if (c.is_upper())
|
||||
{
|
||||
if (i > 0 && ((self[i - 1].is_lower() || self[i - 1].is_digit()) || (i < self.len - 1 && self[i + 1].is_lower())))
|
||||
{
|
||||
d.append_char('_');
|
||||
}
|
||||
d.append_char(c.to_lower());
|
||||
continue;
|
||||
}
|
||||
d.append_char(c);
|
||||
}
|
||||
return d.copy_str(allocator);
|
||||
}
|
||||
|
||||
|
||||
fn StringIterator String.iterator(self)
|
||||
{
|
||||
return { self, 0 };
|
||||
@@ -692,22 +985,69 @@ fn usz String.utf8_codepoints(s)
|
||||
return len;
|
||||
}
|
||||
|
||||
<*
|
||||
Determine whether the current string actually points to a ZString-like string.
|
||||
|
||||
This is done by looking at the byte one step after the end of the string. If this
|
||||
is zero, it is considered zero terminated.
|
||||
|
||||
This function can safely be used with data pointing to null. However, it will not
|
||||
work correctly if the pointer is invalid, for example it is already freed.
|
||||
*>
|
||||
fn bool String.is_zstr(self) @deprecated("Unsafe, use copy instead")
|
||||
{
|
||||
return self.ptr && *(self.ptr + self.len) == 0;
|
||||
}
|
||||
|
||||
<*
|
||||
@require (base <= 10 && base > 1) || base == 16 : "Unsupported base"
|
||||
Return a pointer to the string *iff* it is a pointer
|
||||
to a zero terminated string, otherwise return a temp allocated zstring copy.
|
||||
|
||||
This function is suitable if you are converting strings to ZString on the temp
|
||||
allocator, but suspect that the String might actually already point to zero
|
||||
terminated data.
|
||||
|
||||
The function looks one step beyond the end of the slice to determine this,
|
||||
which means that if that data is then modified after this call, this function
|
||||
might behave incorrectly.
|
||||
|
||||
For this reason, try to ensure that the resulting ZString is immediately used.
|
||||
|
||||
@ensure return[self.len] == 0
|
||||
*>
|
||||
fn ZString String.quick_zstr(self) @deprecated("Unsafe, use zstr_tcopy instead")
|
||||
{
|
||||
return self.zstr_tcopy();
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Convert a number to a given base. If the base is not given, then
|
||||
it will be inferred from the number if the string starts with 0x 0o or 0b and the
|
||||
base is given as 10.
|
||||
|
||||
Furthermore it will skip any spaces before and after the number.
|
||||
|
||||
@param $Type : "The type to convert to"
|
||||
@param base : "The base to convert to"
|
||||
@require base > 0 && base <= 16 : "Unsupported base"
|
||||
@return? MALFORMED_INTEGER : "When the value has some illegal character"
|
||||
@return? INTEGER_OVERFLOW : "If the value does not fit in the given type"
|
||||
@return? EMPTY_STRING : "If the string was empty"
|
||||
@return? NEGATIVE_VALUE : "If the type was unsigned, and the value had a - prefix"
|
||||
*>
|
||||
macro String.to_integer(self, $Type, int base = 10)
|
||||
{
|
||||
usz len = self.len;
|
||||
usz index = 0;
|
||||
char* ptr = self.ptr;
|
||||
while (index < len && ascii::is_blank_m(ptr[index])) index++;
|
||||
if (len == index) return EMPTY_STRING?;
|
||||
while (index < len && ptr[index].is_blank()) index++;
|
||||
if (len == index) return EMPTY_STRING~;
|
||||
bool is_negative;
|
||||
switch (self[index])
|
||||
{
|
||||
case '-':
|
||||
if ($Type.min == 0) return NEGATIVE_VALUE?;
|
||||
if ($Type.min == 0) return NEGATIVE_VALUE~;
|
||||
is_negative = true;
|
||||
index++;
|
||||
case '+':
|
||||
@@ -715,7 +1055,7 @@ macro String.to_integer(self, $Type, int base = 10)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (len == index) return MALFORMED_INTEGER?;
|
||||
if (len == index) return MALFORMED_INTEGER~;
|
||||
$Type base_used = ($Type)base;
|
||||
if (self[index] == '0' && base == 10)
|
||||
{
|
||||
@@ -738,7 +1078,7 @@ macro String.to_integer(self, $Type, int base = 10)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (len == index) return MALFORMED_INTEGER?;
|
||||
if (len == index) return MALFORMED_INTEGER~;
|
||||
}
|
||||
$Type value = 0;
|
||||
while (index != len)
|
||||
@@ -746,24 +1086,20 @@ macro String.to_integer(self, $Type, int base = 10)
|
||||
char c = self[index++];
|
||||
switch
|
||||
{
|
||||
case base_used != 16 || c < 'A': c -= '0';
|
||||
case base_used < 10 || c < 'A': c -= '0';
|
||||
case c <= 'F': c -= 'A' - 10;
|
||||
case c < 'a' || c > 'f': return MALFORMED_INTEGER?;
|
||||
case c < 'a' || c > 'f': return MALFORMED_INTEGER~;
|
||||
default: c -= 'a' - 10;
|
||||
}
|
||||
if (c >= base_used) return MALFORMED_INTEGER?;
|
||||
if (c >= base_used) return MALFORMED_INTEGER~;
|
||||
do
|
||||
{
|
||||
if (is_negative)
|
||||
{
|
||||
$Type new_value = value * base_used - c;
|
||||
if (new_value > value) return INTEGER_OVERFLOW?;
|
||||
value = new_value;
|
||||
value = value.overflow_mul(base_used).overflow_sub(c) ?? INTEGER_OVERFLOW~!;
|
||||
break;
|
||||
}
|
||||
$Type new_value = value * base_used + c;
|
||||
if (new_value < value) return INTEGER_OVERFLOW?;
|
||||
value = new_value;
|
||||
value = value.overflow_mul(base_used).overflow_add(c) ?? INTEGER_OVERFLOW~!;
|
||||
};
|
||||
}
|
||||
return value;
|
||||
@@ -784,50 +1120,49 @@ fn char? String.to_uchar(self, int base = 10) => self.to_integer(char, base);
|
||||
fn double? String.to_double(self) => self.to_real(double);
|
||||
fn float? String.to_float(self) => self.to_real(float);
|
||||
|
||||
fn Splitter String.splitter(self, String split)
|
||||
{
|
||||
return { .string = self, .split = split };
|
||||
}
|
||||
|
||||
<*
|
||||
Create a Splitter to track tokenizing of a string. Tokenize will turn "foo:bar::baz" into
|
||||
"foo", "bar" and "baz", if you want the empty string to be present, use `tokenize_all`
|
||||
instead.
|
||||
|
||||
@param [in] split : "The string to use for splitting"
|
||||
@return "A Splitter to track the state"
|
||||
*>
|
||||
fn Splitter String.tokenize(self, String split)
|
||||
{
|
||||
return { .string = self, .split = split, .tokenize = true };
|
||||
return { .string = self, .split = split, .type = TOKENIZE };
|
||||
}
|
||||
|
||||
struct Splitter
|
||||
<*
|
||||
Create a Splitter to track tokenizing of a string. Tokenize will turn "foo:bar::baz" into
|
||||
"foo", "bar" and "baz", if you want the empty string to be present, use `tokenize_all`
|
||||
instead.
|
||||
|
||||
@param [in] split : "The string to use for splitting"
|
||||
@param skip_last : "Set to true to not include the last empty token if present (default: false)"
|
||||
@return "A Splitter to track the state"
|
||||
*>
|
||||
fn Splitter String.tokenize_all(self, String split, bool skip_last = false)
|
||||
{
|
||||
String string;
|
||||
String split;
|
||||
usz current;
|
||||
bool tokenize;
|
||||
int last_index;
|
||||
return {
|
||||
.string = self,
|
||||
.split = split,
|
||||
.type = skip_last ? TOKENIZE_ALL_SKIP_LAST : TOKENIZE_ALL
|
||||
};
|
||||
}
|
||||
|
||||
fn void Splitter.reset(&self)
|
||||
fn Splitter String.splitter(self, String split) @deprecated("Use tokenize_all instead")
|
||||
{
|
||||
self.current = 0;
|
||||
return self.tokenize_all(split, skip_last: true);
|
||||
}
|
||||
|
||||
fn String? Splitter.next(&self)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
usz len = self.string.len;
|
||||
usz current = self.current;
|
||||
if (current >= len) return 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;
|
||||
}
|
||||
}
|
||||
<*
|
||||
This macro will create a string description of a struct.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param x : "The struct to create a description of"
|
||||
*>
|
||||
macro String from_struct(Allocator allocator, x)
|
||||
{
|
||||
DString s;
|
||||
@@ -839,4 +1174,72 @@ macro String from_struct(Allocator allocator, x)
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
This macro will create a temporary string description of a struct.
|
||||
|
||||
@param x : "The struct to create a description of"
|
||||
*>
|
||||
macro String tfrom_struct(x) => from_struct(tmem, x);
|
||||
|
||||
const uint SURROGATE_OFFSET @private = 0x10000;
|
||||
const uint SURROGATE_GENERIC_MASK @private = 0xF800;
|
||||
const uint SURROGATE_MASK @private = 0xFC00;
|
||||
const uint SURROGATE_CODEPOINT_MASK @private = 0x03FF;
|
||||
const uint SURROGATE_BITS @private = 10;
|
||||
const uint SURROGATE_LOW_VALUE @private = 0xDC00;
|
||||
const uint SURROGATE_HIGH_VALUE @private = 0xD800;
|
||||
|
||||
enum SplitterType
|
||||
{
|
||||
TOKENIZE,
|
||||
TOKENIZE_ALL,
|
||||
TOKENIZE_ALL_SKIP_LAST
|
||||
}
|
||||
|
||||
<*
|
||||
Splitter is handles tokenizing strings.
|
||||
*>
|
||||
struct Splitter
|
||||
{
|
||||
String string;
|
||||
String split;
|
||||
usz current;
|
||||
SplitterType type;
|
||||
int last_index;
|
||||
}
|
||||
|
||||
fn void Splitter.reset(&self)
|
||||
{
|
||||
self.current = 0;
|
||||
}
|
||||
|
||||
fn bool Splitter.at_end(&self)
|
||||
{
|
||||
return self.current > self.string.len;
|
||||
}
|
||||
|
||||
fn String? Splitter.next(&self)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
usz len = self.string.len;
|
||||
usz current = self.current;
|
||||
if (current > len) return NO_MORE_ELEMENT~;
|
||||
if (current == len)
|
||||
{
|
||||
if (self.type != TOKENIZE_ALL) return NO_MORE_ELEMENT~;
|
||||
self.current++;
|
||||
return self.string[current - 1:0];
|
||||
}
|
||||
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.type == TOKENIZE) continue;
|
||||
return remaining[:next];
|
||||
}
|
||||
self.current = len + 1;
|
||||
return remaining;
|
||||
}
|
||||
}
|
||||
|
||||
261
lib/std/core/string_escape.c3
Normal file
261
lib/std/core/string_escape.c3
Normal file
@@ -0,0 +1,261 @@
|
||||
// Copyright (c) 2024-2025 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.
|
||||
|
||||
<*
|
||||
This module provides functionality for escaping and unescaping strings
|
||||
with standard C-style escape sequences, similar to what's used in JSON
|
||||
and other string literals.
|
||||
*>
|
||||
module std::core::string;
|
||||
import std::io;
|
||||
|
||||
faultdef INVALID_ESCAPE_SEQUENCE, UNTERMINATED_STRING, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE;
|
||||
|
||||
<*
|
||||
Escape a string by adding quotes and converting special characters to escape sequences.
|
||||
|
||||
@param allocator : "The allocator to use for the result"
|
||||
@param s : "The string to escape"
|
||||
@param strip_quotes : "Do not include beginning and end quotes, defaults to false"
|
||||
@return "The escaped string with surrounding quotes, can safely be cast to ZString"
|
||||
*>
|
||||
fn String String.escape(String s, Allocator allocator, bool strip_quotes = true)
|
||||
{
|
||||
// Conservative allocation: most strings need minimal escaping
|
||||
usz initial_capacity = s.len + s.len / 5 + 2; // ~1.2x + quotes
|
||||
|
||||
if (allocator == tmem)
|
||||
{
|
||||
DString result = dstring::new_with_capacity(tmem, initial_capacity);
|
||||
escape_dstring(s, result, strip_quotes);
|
||||
return result.str_view();
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
DString result = dstring::temp_with_capacity(initial_capacity);
|
||||
escape_dstring(s, result, strip_quotes);
|
||||
return result.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
fn void escape_dstring(String s, DString result, bool strip_quotes) @private
|
||||
{
|
||||
if (!strip_quotes) result.append_char('"');
|
||||
|
||||
foreach (char c : s)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"': result.append(`\"`);
|
||||
case '\\': result.append(`\\`);
|
||||
case '\b': result.append(`\b`);
|
||||
case '\f': result.append(`\f`);
|
||||
case '\n': result.append(`\n`);
|
||||
case '\r': result.append(`\r`);
|
||||
case '\t': result.append(`\t`);
|
||||
case '\v': result.append(`\v`);
|
||||
case '\0': result.append(`\0`);
|
||||
default:
|
||||
if (c >= 32 && c <= 126)
|
||||
{
|
||||
// Printable ASCII
|
||||
result.append_char(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-printable, use hex escape
|
||||
result.appendf("\\x%02x", (uint)c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!strip_quotes) result.append_char('"');
|
||||
|
||||
}
|
||||
<*
|
||||
Escape a string using the temp allocator.
|
||||
|
||||
@param s : "The string to escape"
|
||||
@param strip_quotes : "Do not include beginning and end quotes, defaults to false"
|
||||
@return "The escaped string with surrounding quotes"
|
||||
*>
|
||||
fn String String.tescape(String s, bool strip_quotes = false) => s.escape(tmem, strip_quotes);
|
||||
|
||||
<*
|
||||
Calculate the length needed for an escaped string (including quotes).
|
||||
|
||||
@param s : "The string to check"
|
||||
@return "The length needed for the escaped version"
|
||||
*>
|
||||
fn usz escape_len(String s)
|
||||
{
|
||||
usz len = 2; // For quotes
|
||||
foreach (char c : s)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\v':
|
||||
case '\0':
|
||||
len += 2; // \X
|
||||
default:
|
||||
if (c >= 32 && c <= 126)
|
||||
{
|
||||
len += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
len += 4; // \xHH
|
||||
}
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
<*
|
||||
Unescape a quoted string by parsing escape sequences.
|
||||
|
||||
@param allocator : "The allocator to use for the result"
|
||||
@param s : "The quoted string to unescape"
|
||||
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
|
||||
@param lenient : "Be lenient with escapes, resolving unknown sequences to the escape character, defaults to false"
|
||||
@return "The unescaped string without quotes, safe to convert to ZString"
|
||||
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
|
||||
*>
|
||||
fn String? String.unescape(String s, Allocator allocator, bool allow_unquoted = false, bool lenient = false)
|
||||
{
|
||||
if (s.len >= 2 && s[0] == '"' && s[^1] == '"')
|
||||
{
|
||||
// Remove quotes.
|
||||
s = s[1:^2];
|
||||
}
|
||||
else if (!allow_unquoted) return UNTERMINATED_STRING~;
|
||||
|
||||
// Handle empty string case
|
||||
if (!s.len)
|
||||
{
|
||||
return "".copy(allocator);
|
||||
}
|
||||
if (allocator == tmem)
|
||||
{
|
||||
DString result = dstring::new_with_capacity(tmem, s.len);
|
||||
unescape_dstring(s, result, allow_unquoted, lenient)!;
|
||||
return result.str_view();
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
DString result = dstring::temp_with_capacity(s.len);
|
||||
unescape_dstring(s, result, allow_unquoted, lenient)!;
|
||||
return result.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
fn void? unescape_dstring(String s, DString result, bool allow_unquoted = false, bool lenient = false) @private
|
||||
{
|
||||
usz len = s.len;
|
||||
for (usz i = 0; i < len; i++)
|
||||
{
|
||||
char c = s[i];
|
||||
if (c != '\\')
|
||||
{
|
||||
result.append_char(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle escape sequence
|
||||
if (i + 1 >= len) return INVALID_ESCAPE_SEQUENCE~;
|
||||
|
||||
char escape_char = s[++i];
|
||||
switch (escape_char)
|
||||
{
|
||||
case '"': result.append_char('"');
|
||||
case '\\': result.append_char('\\');
|
||||
case '/': result.append_char('/');
|
||||
case 'b': result.append_char('\b');
|
||||
case 'f': result.append_char('\f');
|
||||
case 'n': result.append_char('\n');
|
||||
case 'r': result.append_char('\r');
|
||||
case 't': result.append_char('\t');
|
||||
case 'v': result.append_char('\v');
|
||||
case '0': result.append_char('\0');
|
||||
case 'x':
|
||||
// Hex escape \xHH
|
||||
if (i + 2 >= len) return INVALID_HEX_ESCAPE~;
|
||||
char h1 = s[++i];
|
||||
char h2 = s[++i];
|
||||
if (!h1.is_xdigit() || !h2.is_xdigit()) return INVALID_HEX_ESCAPE~;
|
||||
uint val = h1 > '9' ? (h1 | 32) - 'a' + 10 : h1 - '0';
|
||||
val = val << 4;
|
||||
val += h2 > '9' ? (h2 | 32) - 'a' + 10 : h2 - '0';
|
||||
result.append_char((char)val);
|
||||
case 'u':
|
||||
// Unicode escape \uHHHH
|
||||
if (i + 4 >= len) return INVALID_UNICODE_ESCAPE~;
|
||||
uint val;
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
char hex_char = s[++i];
|
||||
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~;
|
||||
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
|
||||
}
|
||||
result.append_char32(val);
|
||||
case 'U':
|
||||
// Unicode escape \UHHHHHHHH
|
||||
if (i + 8 >= len) return INVALID_UNICODE_ESCAPE~;
|
||||
uint val;
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
char hex_char = s[++i];
|
||||
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~;
|
||||
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
|
||||
}
|
||||
result.append_char32(val);
|
||||
default:
|
||||
if (!lenient) return INVALID_ESCAPE_SEQUENCE~;
|
||||
result.append_char(escape_char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Unescape a quoted string using the temp allocator.
|
||||
|
||||
@param s : "The quoted string to unescape"
|
||||
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
|
||||
@param lenient : "Be lenient with escapes, resolving unknown sequences to the escape character, defaults to false"
|
||||
@return "The unescaped string without quotes"
|
||||
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
|
||||
*>
|
||||
fn String? String.tunescape(String s, bool allow_unquoted = false, bool lenient = false) => s.unescape(tmem, allow_unquoted, lenient);
|
||||
|
||||
<*
|
||||
Check if a character needs to be escaped in a string literal.
|
||||
|
||||
@param c : "The character to check"
|
||||
@return "True if the character needs escaping"
|
||||
*>
|
||||
fn bool needs_escape(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\v':
|
||||
case '\0':
|
||||
return true;
|
||||
default:
|
||||
return c < 32 || c > 126;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ fn Char32? StringIterator.next(&self)
|
||||
{
|
||||
usz len = self.utf8.len;
|
||||
usz current = self.current;
|
||||
if (current >= len) return NO_MORE_ELEMENT?;
|
||||
if (current >= len) return NO_MORE_ELEMENT~;
|
||||
usz read = (len - current < 4 ? len - current : 4);
|
||||
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
|
||||
self.current += read;
|
||||
@@ -26,7 +26,7 @@ fn Char32? StringIterator.peek(&self)
|
||||
{
|
||||
usz len = self.utf8.len;
|
||||
usz current = self.current;
|
||||
if (current >= len) return NO_MORE_ELEMENT?;
|
||||
if (current >= len) return NO_MORE_ELEMENT~;
|
||||
usz read = (len - current < 4 ? len - current : 4);
|
||||
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
|
||||
return res;
|
||||
@@ -43,7 +43,7 @@ fn Char32? StringIterator.get(&self)
|
||||
usz current = self.current;
|
||||
usz read = (len - current < 4 ? len - current : 4);
|
||||
usz index = current > read ? current - read : 0;
|
||||
if (index >= len) return NO_MORE_ELEMENT?;
|
||||
if (index >= len) return NO_MORE_ELEMENT~;
|
||||
Char32 res = conv::utf8_to_char32(&self.utf8[index], &read)!;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
got_rad = true;
|
||||
if (index == last_char)
|
||||
{
|
||||
if (!got_digit) return MALFORMED_FLOAT?;
|
||||
if (!got_digit) return MALFORMED_FLOAT~;
|
||||
return sign * 0.0;
|
||||
}
|
||||
if (index != last_char && (c = chars[++index]) == '0')
|
||||
@@ -83,7 +83,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
switch
|
||||
{
|
||||
case c == '.':
|
||||
if (got_rad) return MALFORMED_FLOAT?;
|
||||
if (got_rad) return MALFORMED_FLOAT~;
|
||||
got_rad = true;
|
||||
lrp = dc;
|
||||
case k < KMAX - 3:
|
||||
@@ -113,24 +113,24 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
c = chars[++index];
|
||||
}
|
||||
if (!got_rad) lrp = dc;
|
||||
if (!got_digit) return MALFORMED_FLOAT?;
|
||||
if (!got_digit) return MALFORMED_FLOAT~;
|
||||
if ((c | 32) == 'e')
|
||||
{
|
||||
if (last_char == index) return MALFORMED_FLOAT?;
|
||||
long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT?!;
|
||||
if (last_char == index) return MALFORMED_FLOAT~;
|
||||
long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT~!;
|
||||
lrp += e10;
|
||||
}
|
||||
else if (index != last_char)
|
||||
{
|
||||
return MALFORMED_FLOAT?;
|
||||
return MALFORMED_FLOAT~;
|
||||
}
|
||||
// Handle zero specially to avoid nasty special cases later
|
||||
if (!x[0]) return sign * 0.0;
|
||||
|
||||
// Optimize small integers (w/no exponent) and over/under-flow
|
||||
if (lrp == dc && dc < 10 && ($bits > 30 || (ulong)x[0] >> $bits == 0)) return sign * (double)x[0];
|
||||
if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE?;
|
||||
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
|
||||
if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE~;
|
||||
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE~;
|
||||
|
||||
// Align incomplete final B1B digit
|
||||
if (j)
|
||||
@@ -158,7 +158,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
if (rp % 9)
|
||||
{
|
||||
long rpm9 = rp >= 0 ? rp % 9 : rp % 9 + 9;
|
||||
int p10 = P10S[8 - rpm9];
|
||||
uint p10 = P10S[8 - rpm9];
|
||||
uint carry = 0;
|
||||
for (k = a; k != z; k++)
|
||||
{
|
||||
@@ -320,7 +320,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
y *= 0.5;
|
||||
e2++;
|
||||
}
|
||||
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT?;
|
||||
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT~;
|
||||
}
|
||||
return math::scalbn(y, e2);
|
||||
}
|
||||
@@ -351,7 +351,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
got_rad = true;
|
||||
if (index == last_char)
|
||||
{
|
||||
if (!got_digit) return MALFORMED_FLOAT?;
|
||||
if (!got_digit) return MALFORMED_FLOAT~;
|
||||
return sign * 0.0;
|
||||
}
|
||||
if (index != last_char && (c = chars[++index]) == '0')
|
||||
@@ -369,7 +369,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
{
|
||||
if (c == '.')
|
||||
{
|
||||
if (got_rad) return MALFORMED_FLOAT?;
|
||||
if (got_rad) return MALFORMED_FLOAT~;
|
||||
got_rad = true;
|
||||
rp = dc;
|
||||
}
|
||||
@@ -393,20 +393,20 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
if (index == last_char) break;
|
||||
c = chars[++index];
|
||||
}
|
||||
if (!got_digit) return MALFORMED_FLOAT?;
|
||||
if (!got_digit) return MALFORMED_FLOAT~;
|
||||
if (!got_rad) rp = dc;
|
||||
for (; dc < 8; dc++) x *= 16;
|
||||
|
||||
long e2;
|
||||
if ((c | 32) == 'p')
|
||||
{
|
||||
long e2val = String.to_long((String)chars[index + 1..]) ?? (MALFORMED_FLOAT?)!;
|
||||
long e2val = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT~!;
|
||||
e2 = e2val;
|
||||
}
|
||||
e2 += 4 * rp - 32;
|
||||
if (!x) return sign * 0.0;
|
||||
if (e2 > -$emin) return FLOAT_OUT_OF_RANGE?;
|
||||
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
|
||||
if (e2 > -$emin) return FLOAT_OUT_OF_RANGE~;
|
||||
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE~;
|
||||
|
||||
while (x < 0x80000000)
|
||||
{
|
||||
@@ -441,7 +441,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
}
|
||||
y = bias + sign * (double)x + sign * y;
|
||||
y -= bias;
|
||||
if (!y) return FLOAT_OUT_OF_RANGE?;
|
||||
if (!y) return FLOAT_OUT_OF_RANGE~;
|
||||
|
||||
return math::scalbn(y, (int)e2);
|
||||
}
|
||||
@@ -462,8 +462,8 @@ macro String.to_real(chars, $Type) @private
|
||||
$error "Unexpected type";
|
||||
$endswitch
|
||||
|
||||
while (chars.len && chars[0] == ' ') chars = chars[1..];
|
||||
if (!chars.len) return MALFORMED_FLOAT?;
|
||||
chars = chars.trim();
|
||||
if (!chars.len) return MALFORMED_FLOAT~;
|
||||
|
||||
if (chars.len != 1)
|
||||
{
|
||||
@@ -476,6 +476,9 @@ macro String.to_real(chars, $Type) @private
|
||||
chars = chars[1..];
|
||||
}
|
||||
}
|
||||
chars = chars.trim();
|
||||
if (!chars.len) return MALFORMED_FLOAT~;
|
||||
|
||||
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
|
||||
if (chars == "NAN" || chars == "nan") return $Type.nan;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ faultdef DIVISION_BY_ZERO;
|
||||
|
||||
fn double? divide(int a, int b)
|
||||
{
|
||||
if (b == 0) return MathError.DIVISION_BY_ZERO?;
|
||||
if (b == 0) return MathError.DIVISION_BY_ZERO~;
|
||||
return (double)(a) / (double)(b);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ fn void? test_div() @test
|
||||
test::le(2, 3);
|
||||
test::eq_approx(m::divide(1, 3)!, 0.333, places: 3);
|
||||
test::@check(2 == 2, "divide: %d", divide(6, 3)!);
|
||||
test::@error(m::divide(3, 0));
|
||||
test::@error(m::divide(3, 0), MathError.DIVISION_BY_ZERO);
|
||||
}
|
||||
|
||||
@@ -78,14 +79,15 @@ macro @check(#condition, String format = "", args...)
|
||||
}
|
||||
|
||||
<*
|
||||
Check if function returns specific error
|
||||
Check if function returns (specific) error
|
||||
|
||||
@param #funcresult : `result of function execution`
|
||||
@param error_expected : `expected error of function execution`
|
||||
@require runtime::test_context != null : "Only allowed in @test functions"
|
||||
*>
|
||||
macro @error(#funcresult, fault error_expected)
|
||||
macro @error(#funcresult, fault error_expected = ...)
|
||||
{
|
||||
$if $defined(error_expected):
|
||||
if (catch err = #funcresult)
|
||||
{
|
||||
if (err != error_expected)
|
||||
@@ -96,6 +98,10 @@ macro @error(#funcresult, fault error_expected)
|
||||
return;
|
||||
}
|
||||
print_panicf("`%s` error [%s] was not returned.", $stringify(#funcresult), error_expected);
|
||||
$else
|
||||
if (catch err = #funcresult) return;
|
||||
print_panicf("`%s` unexpectedly did not return error.", $stringify(#funcresult));
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
|
||||
@@ -29,47 +29,47 @@ macro any_to_int(any v, $Type)
|
||||
{
|
||||
case ichar:
|
||||
ichar c = *(char*)v.ptr;
|
||||
if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
|
||||
return ($Type)c;
|
||||
case short:
|
||||
short s = *(short*)v.ptr;
|
||||
if (is_mixed_signed && s < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (is_mixed_signed && s < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
|
||||
if (s > max || s < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)s;
|
||||
case int:
|
||||
int i = *(int*)v.ptr;
|
||||
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)i;
|
||||
case long:
|
||||
long l = *(long*)v.ptr;
|
||||
if (is_mixed_signed && l < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (is_mixed_signed && l < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
|
||||
if (l > max || l < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)l;
|
||||
case int128:
|
||||
int128 i = *(int128*)v.ptr;
|
||||
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)i;
|
||||
case char:
|
||||
char c = *(char*)v.ptr;
|
||||
if (c > max) return VALUE_OUT_OF_RANGE?;
|
||||
if (c > max) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)c;
|
||||
case ushort:
|
||||
ushort s = *(ushort*)v.ptr;
|
||||
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (s > max || s < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)s;
|
||||
case uint:
|
||||
uint i = *(uint*)v.ptr;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)i;
|
||||
case ulong:
|
||||
ulong l = *(ulong*)v.ptr;
|
||||
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (l > max || l < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)l;
|
||||
case uint128:
|
||||
uint128 i = *(uint128*)v.ptr;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
|
||||
return ($Type)i;
|
||||
default:
|
||||
unreachable();
|
||||
@@ -96,21 +96,33 @@ macro bool is_subtype_of($Type, $OtherType)
|
||||
}
|
||||
macro bool is_numerical($Type)
|
||||
{
|
||||
var $kind = $Type.kindof;
|
||||
$if $kind == TypeKind.DISTINCT:
|
||||
return is_numerical($typefrom($Type.inner));
|
||||
$else
|
||||
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|
||||
|| $kind == TypeKind.VECTOR;
|
||||
$endif
|
||||
$switch $Type.kindof:
|
||||
$case TYPEDEF:
|
||||
$case CONSTDEF:
|
||||
return is_numerical($Type.inner);
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
$case FLOAT:
|
||||
$case VECTOR:
|
||||
return true;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn bool TypeKind.is_int(kind) @inline
|
||||
{
|
||||
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
|
||||
return kind == SIGNED_INT || kind == UNSIGNED_INT;
|
||||
}
|
||||
|
||||
macro bool is_slice_convertable($Type)
|
||||
macro bool TypeKind.@is_int($kind) @const
|
||||
{
|
||||
return $kind == SIGNED_INT ||| $kind == UNSIGNED_INT;
|
||||
}
|
||||
|
||||
macro bool is_slice_convertable($Type) @deprecated("Use is_slice_convertible") => is_slice_convertible($Type);
|
||||
|
||||
macro bool is_slice_convertible($Type)
|
||||
{
|
||||
$switch $Type.kindof:
|
||||
$case SLICE:
|
||||
@@ -135,7 +147,7 @@ macro bool is_signed($Type) @const
|
||||
$case FLOAT:
|
||||
return true;
|
||||
$case VECTOR:
|
||||
return is_signed($typefrom($Type.inner));
|
||||
return is_signed($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
@@ -150,7 +162,7 @@ macro bool is_unsigned($Type) @const
|
||||
$case UNSIGNED_INT:
|
||||
return true;
|
||||
$case VECTOR:
|
||||
return is_unsigned($typefrom($Type.inner));
|
||||
return is_unsigned($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
@@ -158,8 +170,8 @@ macro bool is_unsigned($Type) @const
|
||||
|
||||
macro typeid flat_type($Type) @const
|
||||
{
|
||||
$if $Type.kindof == DISTINCT:
|
||||
return flat_type($typefrom($Type.inner));
|
||||
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF:
|
||||
return flat_type($Type.inner);
|
||||
$else
|
||||
return $Type.typeid;
|
||||
$endif
|
||||
@@ -167,8 +179,8 @@ macro typeid flat_type($Type) @const
|
||||
|
||||
macro TypeKind flat_kind($Type) @const
|
||||
{
|
||||
$if $Type.kindof == DISTINCT:
|
||||
return flat_type($typefrom($Type.inner));
|
||||
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF:
|
||||
return flat_type($Type.inner);
|
||||
$else
|
||||
return $Type.kindof;
|
||||
$endif
|
||||
@@ -184,6 +196,21 @@ macro bool is_ref_indexable($Type) @const
|
||||
return $defined(&($Type){}[0]);
|
||||
}
|
||||
|
||||
macro bool is_flat_intlike($Type) @const
|
||||
{
|
||||
$switch $Type.kindof:
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
return true;
|
||||
$case VECTOR:
|
||||
$case TYPEDEF:
|
||||
$case CONSTDEF:
|
||||
return is_flat_intlike($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_intlike($Type) @const
|
||||
{
|
||||
$switch $Type.kindof:
|
||||
@@ -203,8 +230,8 @@ macro bool is_underlying_int($Type) @const
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
return true;
|
||||
$case DISTINCT:
|
||||
return is_underlying_int($typefrom($Type.inner));
|
||||
$case TYPEDEF:
|
||||
return is_underlying_int($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
@@ -231,8 +258,8 @@ macro bool is_vector($Type) @const
|
||||
|
||||
macro typeid inner_type($Type) @const
|
||||
{
|
||||
$if $Type.kindof == TypeKind.DISTINCT:
|
||||
return inner_type($typefrom($Type.inner));
|
||||
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF:
|
||||
return inner_type($Type.inner);
|
||||
$else
|
||||
return $Type.typeid;
|
||||
$endif
|
||||
@@ -271,7 +298,8 @@ macro bool may_load_atomic($Type) @const
|
||||
$case POINTER:
|
||||
$case FLOAT:
|
||||
return true;
|
||||
$case DISTINCT:
|
||||
$case TYPEDEF:
|
||||
$case ENUM:
|
||||
return may_load_atomic($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
@@ -285,7 +313,8 @@ macro lower_to_atomic_compatible_type($Type) @const
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
return $Type.typeid;
|
||||
$case DISTINCT:
|
||||
$case TYPEDEF:
|
||||
$case CONSTDEF:
|
||||
return lower_to_atomic_compatible_type($Type.inner);
|
||||
$case FLOAT:
|
||||
$switch $Type:
|
||||
@@ -305,8 +334,8 @@ macro lower_to_atomic_compatible_type($Type) @const
|
||||
$endswitch
|
||||
}
|
||||
|
||||
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_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
|
||||
{
|
||||
@@ -317,9 +346,11 @@ macro bool is_same_vector_type($Type1, $Type2) @const
|
||||
$endif
|
||||
}
|
||||
|
||||
macro bool has_equals($Type) @const => $defined(($Type){} == ($Type){});
|
||||
|
||||
macro bool is_equatable_type($Type) @const
|
||||
{
|
||||
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):
|
||||
$if $defined($Type.less) ||| $defined($Type.compare_to) ||| $defined($Type.equals):
|
||||
return true;
|
||||
$else
|
||||
return $Type.is_eq;
|
||||
@@ -331,7 +362,7 @@ macro bool is_equatable_type($Type) @const
|
||||
*>
|
||||
macro bool implements_copy($Type) @const
|
||||
{
|
||||
return $defined($Type.copy) && $defined($Type.free);
|
||||
return $defined($Type.copy) &&& $defined($Type.free);
|
||||
}
|
||||
|
||||
macro bool @equatable_value(#value) @const
|
||||
@@ -341,13 +372,16 @@ macro bool @equatable_value(#value) @const
|
||||
|
||||
macro bool @comparable_value(#value) @const
|
||||
{
|
||||
$if $defined(#value.less) || $defined(#value.compare_to):
|
||||
$if $defined(#value.less) ||| $defined(#value.compare_to):
|
||||
return true;
|
||||
$else
|
||||
return $typeof(#value).is_ordered;
|
||||
$endif
|
||||
}
|
||||
|
||||
const CONST_ENUM @builtin @deprecated("Use TypeKind.CONSTDEF instead") = TypeKind.CONSTDEF;
|
||||
const DISTINCT @builtin @deprecated("Use TypeKind.TYPEDEF instead") = TypeKind.TYPEDEF;
|
||||
|
||||
enum TypeKind : char
|
||||
{
|
||||
VOID,
|
||||
@@ -359,6 +393,7 @@ enum TypeKind : char
|
||||
FAULT,
|
||||
ANY,
|
||||
ENUM,
|
||||
CONSTDEF,
|
||||
STRUCT,
|
||||
UNION,
|
||||
BITSTRUCT,
|
||||
@@ -367,7 +402,7 @@ enum TypeKind : char
|
||||
ARRAY,
|
||||
SLICE,
|
||||
VECTOR,
|
||||
DISTINCT,
|
||||
TYPEDEF,
|
||||
POINTER,
|
||||
INTERFACE,
|
||||
}
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
module std::core::values;
|
||||
import std::core::types;
|
||||
|
||||
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;
|
||||
|
||||
macro bool @typematch(#value1, #value2) @builtin @const => $typeof(#value1) == $typeof(#value2);
|
||||
<*
|
||||
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_same_type(#value1, #value2) @const @deprecated("Use @typematch") => $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_flat_intlike(#value) @const => types::is_flat_intlike($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 bool @is_lvalue(#value) => $defined(#value = #value);
|
||||
macro bool @assign_to(#value1, #value2) @const @deprecated("use '$defined(#value1 = #value2)'") => @assignable_to(#value1, $typeof(#value2));
|
||||
macro bool @is_lvalue(#value) @deprecated("use '$defined(#value = #value)'")=> $defined(#value = #value);
|
||||
macro bool @is_const(#foo) @const @builtin @deprecated("use '$defined(var $v = expr)'")
|
||||
{
|
||||
return $defined(var $v = #foo);
|
||||
}
|
||||
|
||||
macro promote_int(x)
|
||||
{
|
||||
@@ -38,7 +43,7 @@ macro promote_int(x)
|
||||
@param #value_2
|
||||
@returns `The selected value.`
|
||||
*>
|
||||
macro @select(bool $bool, #value_1, #value_2) @builtin
|
||||
macro @select(bool $bool, #value_1, #value_2) @builtin @deprecated("Use '$bool ? #value_1 : #value_2' instead.")
|
||||
{
|
||||
$if $bool:
|
||||
return #value_1;
|
||||
@@ -46,6 +51,7 @@ macro @select(bool $bool, #value_1, #value_2) @builtin
|
||||
return #value_2;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro promote_int_same(x, y)
|
||||
{
|
||||
$if @is_int(x):
|
||||
|
||||
650
lib/std/crypto/aes.c3
Normal file
650
lib/std/crypto/aes.c3
Normal file
@@ -0,0 +1,650 @@
|
||||
<*
|
||||
This is an implementation of the AES algorithm with the ECB, CTR and CBC
|
||||
modes. The key size can be chosen among AES128, AES192, AES256.
|
||||
|
||||
Ported from github.com/kokke/tiny-aes-c by Koni Marti.
|
||||
|
||||
The implementation is verified against the test vectors from the National
|
||||
Institute of Standards and Technology Special Publication 800-38A 2001 ED.
|
||||
|
||||
Data length must be evenly divisible by 16 bytes (len % 16 == 0) unless CTR is
|
||||
used. You should pad the end of the string with zeros or use PKCS7 if this is not the case.
|
||||
For AES192/256 the key size is proportionally larger.
|
||||
|
||||
The following example demonstrates the AES encryption of a plaintext string
|
||||
with an AES 128-bit key:
|
||||
|
||||
```
|
||||
module app;
|
||||
import std::crypto::aes, std::io;
|
||||
fn void main()
|
||||
{
|
||||
char[] key = x"2b7e151628aed2a6abf7158809cf4f3c";
|
||||
char[] text = x"6bc1bee22e409f96e93d7e117393172a";
|
||||
char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
|
||||
Aes aes;
|
||||
aes.init(AES128, key, iv);
|
||||
defer aes.destroy();
|
||||
char[] cipher = aes.encrypt(mem, text);
|
||||
defer free(cipher);
|
||||
assert(cipher == x"874d6191b620e3261bef6864990db6ce");
|
||||
}
|
||||
```
|
||||
|
||||
*>
|
||||
module std::crypto::aes;
|
||||
|
||||
<* Block length in bytes. AES is 128-bit blocks only. *>
|
||||
const BLOCKLEN = 16;
|
||||
|
||||
<* Number of columns of a AES state. *>
|
||||
const COLNUM = 4;
|
||||
|
||||
<*
|
||||
Block modes:
|
||||
ECB - Electronic Code Book (Not recommended, indata be 16 byte multiple)
|
||||
CBC - Cipher Block Chaining (Indata be 16 byte multiple)
|
||||
CTR - Counter Mode (Recommended, data may be any size)
|
||||
*>
|
||||
enum BlockMode
|
||||
{
|
||||
ECB,
|
||||
CBC,
|
||||
CTR,
|
||||
}
|
||||
|
||||
<* AES type: 128, 192 or 256 bits *>
|
||||
enum AesType : (AesKey key)
|
||||
{
|
||||
AES128 {{ 128, 16, 176, 4, 10 }},
|
||||
AES192 {{ 192, 24, 208, 6, 12 }},
|
||||
AES256 {{ 256, 32, 240, 8, 14 }}
|
||||
}
|
||||
|
||||
struct AesKey
|
||||
{
|
||||
<* Size of key in bits *>
|
||||
usz key_size;
|
||||
<* Size of key in bytes *>
|
||||
int key_len;
|
||||
<* Size of the expanded round_key *>
|
||||
int key_exp_size; // expected size of round_key
|
||||
<* Number of 32 bit words in key *>
|
||||
usz nk;
|
||||
<* Number of rounds in the cipher *>
|
||||
usz nr;
|
||||
}
|
||||
|
||||
struct Aes
|
||||
{
|
||||
<* The type, AES128, AES192 or AES256 *>
|
||||
AesKey type;
|
||||
<* Block mode: ECB, CBC or CTR *>
|
||||
BlockMode mode;
|
||||
<* Initialization Vector *>
|
||||
char[BLOCKLEN] iv;
|
||||
<* Internal key state *>
|
||||
char[256] round_key;
|
||||
<* Internal state *>
|
||||
AesState state;
|
||||
}
|
||||
alias AesState = char[COLNUM][COLNUM];
|
||||
|
||||
<*
|
||||
Initializes the AES crypto. The initialization vector should be securely random for each encryption
|
||||
to mitigate things like replay attacks.
|
||||
|
||||
@param type : "The type or AES: 128, 192 or 256 bits"
|
||||
@param [in] key : "The key to use, should be the same bit size as the type, so 16, 24 or 32 bytes"
|
||||
@param iv : "The initialization vector"
|
||||
@param mode : "The block mode: EBC, CBC, CTR. Defaults to CTR"
|
||||
|
||||
@require key.len == type.key.key_len : "Key does not match expected length."
|
||||
*>
|
||||
fn Aes* Aes.init(&self, AesType type, char[] key, char[BLOCKLEN] iv, BlockMode mode = CTR)
|
||||
{
|
||||
*self = { .type = type.key, .mode = mode, .iv = iv };
|
||||
key_expansion(type, key, &self.round_key);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Completely erases data stored in the context.
|
||||
*>
|
||||
fn void Aes.destroy(&self)
|
||||
{
|
||||
*self = {};
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the length is valid using the given block mode. It has to be a multiple of 16 bytes unless CTR is used.
|
||||
*>
|
||||
macro bool is_valid_encryption_len(BlockMode mode, usz len)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case CTR:
|
||||
return true;
|
||||
case ECB:
|
||||
case CBC:
|
||||
return len % BLOCKLEN == 0;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The input must be a multiple of 16 unless CTR is used"
|
||||
@require out.len >= in.len : "Out buffer must be sufficiently large to hold the data"
|
||||
*>
|
||||
fn void Aes.encrypt_buffer(&self, char[] in, char[] out)
|
||||
{
|
||||
switch (self.mode)
|
||||
{
|
||||
case CTR: ctr_xcrypt_buffer(self, in, out);
|
||||
case ECB: ecb_encrypt_buffer(self, in, out);
|
||||
case CBC: cbc_encrypt_buffer(self, in, out);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The encrypted data must be a multiple of 16 unless CTR is used"
|
||||
@require out.len >= in.len : "Out buffer must be sufficiently large to hold the data"
|
||||
*>
|
||||
fn void Aes.decrypt_buffer(&self, char[] in, char[] out)
|
||||
{
|
||||
switch (self.mode)
|
||||
{
|
||||
case ECB: ecb_decrypt_buffer(self, in, out);
|
||||
case CBC: cbc_decrypt_buffer(self, in, out);
|
||||
case CTR: ctr_xcrypt_buffer(self, in, out);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Encrypt the data, allocating memory for the encrypted data.
|
||||
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [&inout] allocator : "The allocator to use for the output"
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
*>
|
||||
fn char[] Aes.encrypt(&self, Allocator allocator, char[] in)
|
||||
{
|
||||
char[] out = allocator::alloc_array(allocator, char, in.len);
|
||||
self.encrypt_buffer(in, out) @inline;
|
||||
return out;
|
||||
}
|
||||
|
||||
<*
|
||||
Encrypt the data, allocating temp memory for the encrypted data.
|
||||
|
||||
@param [in] in : "Plaintext input."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
*>
|
||||
fn char[] Aes.tencrypt(&self, char[] in)
|
||||
{
|
||||
return self.encrypt(tmem, in);
|
||||
}
|
||||
|
||||
<*
|
||||
Decrypt the data, allocating memory for the decrypted data.
|
||||
|
||||
@param [in] in : "Encrypted input."
|
||||
@param [&inout] allocator : "The allocator to use for the output"
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
*>
|
||||
fn char[] Aes.decrypt(&self, Allocator allocator, char[] in)
|
||||
{
|
||||
char[] out = allocator::alloc_array(allocator, char, in.len);
|
||||
self.decrypt_buffer(in, out) @inline;
|
||||
return out;
|
||||
}
|
||||
|
||||
<*
|
||||
Decrypt the data, allocating temp memory for the decrypted data.
|
||||
|
||||
@param [in] in : "Encrypted input."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
|
||||
*>
|
||||
fn char[] Aes.tdecrypt(&self, char[] in)
|
||||
{
|
||||
return self.decrypt(tmem, in);
|
||||
}
|
||||
|
||||
module std::crypto::aes @private;
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
*>
|
||||
fn void ecb_encrypt_block(Aes *aes, char[BLOCKLEN]* in, char[BLOCKLEN]* out)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[i][j] = (*in)[i * 4 + j];
|
||||
}
|
||||
}
|
||||
aes_cipher(aes, &aes.round_key);
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
(*out)[i * 4 + j] = aes.state[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
*>
|
||||
fn void ecb_decrypt_block(Aes *aes, char[BLOCKLEN]* in, char[BLOCKLEN]* out)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[i][j] = (*in)[i * 4 + j];
|
||||
}
|
||||
}
|
||||
inv_cipher(aes, &aes.round_key);
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
(*out)[i * 4 + j] = aes.state[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
@require out.len >= in.len : "out must be at least as large as buf"
|
||||
*>
|
||||
fn void ecb_decrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
usz len = in.len;
|
||||
for (usz i = 0; i < len; i += 4)
|
||||
{
|
||||
ecb_decrypt_block(aes, in[:BLOCKLEN], out[:BLOCKLEN]) @inline;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
*>
|
||||
fn void ecb_encrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
usz len = in.len;
|
||||
for (usz i = 0; i < len; i += BLOCKLEN)
|
||||
{
|
||||
ecb_encrypt_block(aes, in[i:BLOCKLEN], out[i:BLOCKLEN]) @inline;
|
||||
}
|
||||
}
|
||||
|
||||
fn void xor_with_iv(char[] buf, char[BLOCKLEN]* iv) @local
|
||||
{
|
||||
foreach (i, b : *iv)
|
||||
{
|
||||
buf[i] ^= b;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
*>
|
||||
fn void cbc_encrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
char[] iv = aes.iv[..];
|
||||
usz len = in.len;
|
||||
char[BLOCKLEN] tmp;
|
||||
char[BLOCKLEN] tmp2;
|
||||
for (usz i = 0; i < len; i += BLOCKLEN)
|
||||
{
|
||||
tmp[:BLOCKLEN] = in[i:BLOCKLEN];
|
||||
xor_with_iv(&tmp, iv);
|
||||
ecb_encrypt_block(aes, &tmp, &tmp2);
|
||||
out[i:BLOCKLEN] = tmp2[..];
|
||||
iv = tmp2[..];
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
*>
|
||||
fn void cbc_decrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
char[BLOCKLEN] tmp;
|
||||
usz len = in.len;
|
||||
for (usz i = 0; i < len; i += BLOCKLEN)
|
||||
{
|
||||
ecb_decrypt_block(aes, in[i:BLOCKLEN], &tmp);
|
||||
xor_with_iv(&tmp, aes.iv[..]);
|
||||
aes.iv[:BLOCKLEN] = in[i:BLOCKLEN];
|
||||
out[i:BLOCKLEN] = tmp[..];
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext/cipher input."
|
||||
@param [out] out : "Cipher/plaintext output."
|
||||
*>
|
||||
fn void ctr_xcrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
char[BLOCKLEN] buffer @noinit;
|
||||
usz len = in.len;
|
||||
for (int bi = BLOCKLEN, usz i = 0; i < len; i++)
|
||||
{
|
||||
if (bi == BLOCKLEN)
|
||||
{
|
||||
buffer = aes.iv;
|
||||
ecb_encrypt_block(aes, &buffer, &buffer);
|
||||
|
||||
for LOOP: (bi = (BLOCKLEN - 1); bi >= 0; bi--)
|
||||
{
|
||||
if (aes.iv[bi] == 255)
|
||||
{
|
||||
aes.iv[bi] = 0;
|
||||
continue;
|
||||
}
|
||||
aes.iv[bi]++;
|
||||
break LOOP;
|
||||
}
|
||||
bi = 0;
|
||||
}
|
||||
out[i] = in[i] ^ buffer[bi];
|
||||
bi++;
|
||||
}
|
||||
}
|
||||
|
||||
macro char get_sbox_value(num) => SBOX[num];
|
||||
macro char get_sbox_invert(num) => RSBOX[num];
|
||||
|
||||
const char[256] SBOX =
|
||||
x`637c777bf26b6fc53001672bfed7ab76
|
||||
ca82c97dfa5947f0add4a2af9ca472c0
|
||||
b7fd9326363ff7cc34a5e5f171d83115
|
||||
04c723c31896059a071280e2eb27b275
|
||||
09832c1a1b6e5aa0523bd6b329e32f84
|
||||
53d100ed20fcb15b6acbbe394a4c58cf
|
||||
d0efaafb434d338545f9027f503c9fa8
|
||||
51a3408f929d38f5bcb6da2110fff3d2
|
||||
cd0c13ec5f974417c4a77e3d645d1973
|
||||
60814fdc222a908846eeb814de5e0bdb
|
||||
e0323a0a4906245cc2d3ac629195e479
|
||||
e7c8376d8dd54ea96c56f4ea657aae08
|
||||
ba78252e1ca6b4c6e8dd741f4bbd8b8a
|
||||
703eb5664803f60e613557b986c11d9e
|
||||
e1f8981169d98e949b1e87e9ce5528df
|
||||
8ca1890dbfe6426841992d0fb054bb16`;
|
||||
|
||||
const char[256] RSBOX =
|
||||
x`52096ad53036a538bf40a39e81f3d7fb
|
||||
7ce339829b2fff87348e4344c4dee9cb
|
||||
547b9432a6c2233dee4c950b42fac34e
|
||||
082ea16628d924b2765ba2496d8bd125
|
||||
72f8f66486689816d4a45ccc5d65b692
|
||||
6c704850fdedb9da5e154657a78d9d84
|
||||
90d8ab008cbcd30af7e45805b8b34506
|
||||
d02c1e8fca3f0f02c1afbd0301138a6b
|
||||
3a9111414f67dcea97f2cfcef0b4e673
|
||||
96ac7422e7ad3585e2f937e81c75df6e
|
||||
47f11a711d29c5896fb7620eaa18be1b
|
||||
fc563e4bc6d279209adbc0fe78cd5af4
|
||||
1fdda8338807c731b11210592780ec5f
|
||||
60517fa919b54a0d2de57a9f93c99cef
|
||||
a0e03b4dae2af5b0c8ebbb3c83539961
|
||||
172b047eba77d626e169146355210c7d`;
|
||||
|
||||
const char[11] RCON = x`8d01020408102040801b36`;
|
||||
|
||||
fn void add_round_key(Aes* aes, usz round, char[] round_key)
|
||||
{
|
||||
usz i, j;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
for (j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[i][j] ^= round_key[(round * COLNUM * 4) + (i * COLNUM) + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void sub_bytes(Aes* aes)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[j][i] = get_sbox_value(aes.state[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void shift_rows(Aes* aes)
|
||||
{
|
||||
char temp;
|
||||
|
||||
temp = aes.state[0][1];
|
||||
aes.state[0][1] = aes.state[1][1];
|
||||
aes.state[1][1] = aes.state[2][1];
|
||||
aes.state[2][1] = aes.state[3][1];
|
||||
aes.state[3][1] = temp;
|
||||
|
||||
temp = aes.state[0][2];
|
||||
aes.state[0][2] = aes.state[2][2];
|
||||
aes.state[2][2] = temp;
|
||||
|
||||
temp = aes.state[1][2];
|
||||
aes.state[1][2] = aes.state[3][2];
|
||||
aes.state[3][2] = temp;
|
||||
|
||||
temp = aes.state[0][3];
|
||||
aes.state[0][3] = aes.state[3][3];
|
||||
aes.state[3][3] = aes.state[2][3];
|
||||
aes.state[2][3] = aes.state[1][3];
|
||||
aes.state[1][3] = temp;
|
||||
}
|
||||
|
||||
fn char xtime(char x) @local
|
||||
{
|
||||
return ((x << 1) ^ (((x >> 7) & 1) * 0x1b));
|
||||
}
|
||||
|
||||
fn void mix_columns(Aes* aes)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
char t = aes.state[i][0];
|
||||
char tmp = aes.state[i][0] ^ aes.state[i][1] ^ aes.state[i][2] ^ aes.state[i][3];
|
||||
|
||||
char tm = aes.state[i][0] ^ aes.state[i][1];
|
||||
tm = xtime(tm);
|
||||
aes.state[i][0] ^= tm ^ tmp;
|
||||
|
||||
tm = aes.state[i][1] ^ aes.state[i][2];
|
||||
tm = xtime(tm);
|
||||
aes.state[i][1] ^= tm ^ tmp;
|
||||
|
||||
tm = aes.state[i][2] ^ aes.state[i][3];
|
||||
tm = xtime(tm);
|
||||
aes.state[i][2] ^= tm ^ tmp;
|
||||
|
||||
tm = aes.state[i][3] ^ t;
|
||||
tm = xtime(tm);
|
||||
aes.state[i][3] ^= tm ^ tmp;
|
||||
}
|
||||
}
|
||||
|
||||
fn char multiply(char x, char y) @local
|
||||
{
|
||||
return (((y & 1) * x) ^
|
||||
(((y>>1) & 1) * xtime(x)) ^
|
||||
(((y>>2) & 1) * xtime(xtime(x))) ^
|
||||
(((y>>3) & 1) * xtime(xtime(xtime(x)))) ^
|
||||
(((y>>4) & 1) * xtime(xtime(xtime(xtime(x))))));
|
||||
}
|
||||
|
||||
fn void inv_mix_columns(Aes* aes)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
char a = aes.state[i][0];
|
||||
char b = aes.state[i][1];
|
||||
char c = aes.state[i][2];
|
||||
char d = aes.state[i][3];
|
||||
|
||||
aes.state[i][0] = multiply(a, 0x0e) ^ multiply(b, 0x0b) ^ multiply(c, 0x0d) ^ multiply(d, 0x09);
|
||||
aes.state[i][1] = multiply(a, 0x09) ^ multiply(b, 0x0e) ^ multiply(c, 0x0b) ^ multiply(d, 0x0d);
|
||||
aes.state[i][2] = multiply(a, 0x0d) ^ multiply(b, 0x09) ^ multiply(c, 0x0e) ^ multiply(d, 0x0b);
|
||||
aes.state[i][3] = multiply(a, 0x0b) ^ multiply(b, 0x0d) ^ multiply(c, 0x09) ^ multiply(d, 0x0e);
|
||||
}
|
||||
}
|
||||
|
||||
fn void inv_sub_bytes(Aes* aes)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[j][i] = get_sbox_invert(aes.state[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void inv_shift_rows(Aes* aes)
|
||||
{
|
||||
char temp;
|
||||
|
||||
temp = aes.state[3][1];
|
||||
aes.state[3][1] = aes.state[2][1];
|
||||
aes.state[2][1] = aes.state[1][1];
|
||||
aes.state[1][1] = aes.state[0][1];
|
||||
aes.state[0][1] = temp;
|
||||
|
||||
temp = aes.state[0][2];
|
||||
aes.state[0][2] = aes.state[2][2];
|
||||
aes.state[2][2] = temp;
|
||||
|
||||
temp = aes.state[1][2];
|
||||
aes.state[1][2] = aes.state[3][2];
|
||||
aes.state[3][2] = temp;
|
||||
|
||||
temp = aes.state[0][3];
|
||||
aes.state[0][3] = aes.state[1][3];
|
||||
aes.state[1][3] = aes.state[2][3];
|
||||
aes.state[2][3] = aes.state[3][3];
|
||||
aes.state[3][3] = temp;
|
||||
}
|
||||
|
||||
fn void aes_cipher(Aes* aes, char[] round_key)
|
||||
{
|
||||
usz round = 0;
|
||||
add_round_key(aes, 0, round_key);
|
||||
|
||||
for LOOP: (round = 1;; round++)
|
||||
{
|
||||
sub_bytes(aes);
|
||||
shift_rows(aes);
|
||||
if (round == aes.type.nr) break LOOP;
|
||||
mix_columns(aes);
|
||||
add_round_key(aes, round, round_key);
|
||||
}
|
||||
add_round_key(aes, aes.type.nr, round_key);
|
||||
}
|
||||
|
||||
fn void inv_cipher(Aes* aes, char[] round_key)
|
||||
{
|
||||
add_round_key(aes, aes.type.nr, round_key);
|
||||
for (usz round = aes.type.nr - 1; ; round--)
|
||||
{
|
||||
inv_shift_rows(aes);
|
||||
inv_sub_bytes(aes);
|
||||
add_round_key(aes, round, round_key);
|
||||
if (!round) return;
|
||||
inv_mix_columns(aes);
|
||||
}
|
||||
}
|
||||
|
||||
<*¨
|
||||
@param type : "The AES variant to expant the key for"
|
||||
@param [inout] round_key : "Key to expand into"
|
||||
@param [in] key : "The key to expand"
|
||||
@require key.len == type.key.key_len : "Key does not match expected length."
|
||||
*>
|
||||
fn void key_expansion(AesType type, char[] key, char[] round_key) @private
|
||||
{
|
||||
usz nk = type.key.nk;
|
||||
for (usz i = 0; i < nk; i++)
|
||||
{
|
||||
round_key[(i * 4) + 0] = key[(i * 4) + 0];
|
||||
round_key[(i * 4) + 1] = key[(i * 4) + 1];
|
||||
round_key[(i * 4) + 2] = key[(i * 4) + 2];
|
||||
round_key[(i * 4) + 3] = key[(i * 4) + 3];
|
||||
}
|
||||
|
||||
for (usz i = nk; i < COLNUM * (type.key.nr + 1); i++)
|
||||
{
|
||||
usz k = (i - 1) * 4;
|
||||
|
||||
char[4] tempa @noinit;
|
||||
|
||||
tempa[0] = round_key[k + 0];
|
||||
tempa[1] = round_key[k + 1];
|
||||
tempa[2] = round_key[k + 2];
|
||||
tempa[3] = round_key[k + 3];
|
||||
|
||||
if (i % nk == 0)
|
||||
{
|
||||
// rotword
|
||||
char tmp = tempa[0];
|
||||
tempa[0] = tempa[1];
|
||||
tempa[1] = tempa[2];
|
||||
tempa[2] = tempa[3];
|
||||
tempa[3] = tmp;
|
||||
|
||||
// subword
|
||||
tempa[0] = get_sbox_value(tempa[0]);
|
||||
tempa[1] = get_sbox_value(tempa[1]);
|
||||
tempa[2] = get_sbox_value(tempa[2]);
|
||||
tempa[3] = get_sbox_value(tempa[3]);
|
||||
|
||||
tempa[0] = tempa[0] ^ RCON[i / nk];
|
||||
}
|
||||
|
||||
if (type.key.key_size == 256)
|
||||
{
|
||||
if (i % nk == 4)
|
||||
{
|
||||
// subword
|
||||
tempa[0] = get_sbox_value(tempa[0]);
|
||||
tempa[1] = get_sbox_value(tempa[1]);
|
||||
tempa[2] = get_sbox_value(tempa[2]);
|
||||
tempa[3] = get_sbox_value(tempa[3]);
|
||||
}
|
||||
}
|
||||
usz j = i * 4;
|
||||
k = (i - nk) * 4;
|
||||
round_key[j + 0] = round_key[k + 0] ^ tempa[0];
|
||||
round_key[j + 1] = round_key[k + 1] ^ tempa[1];
|
||||
round_key[j + 2] = round_key[k + 2] ^ tempa[2];
|
||||
round_key[j + 3] = round_key[k + 3] ^ tempa[3];
|
||||
}
|
||||
}
|
||||
|
||||
87
lib/std/crypto/aes_128_192_256.c3
Normal file
87
lib/std/crypto/aes_128_192_256.c3
Normal file
@@ -0,0 +1,87 @@
|
||||
// Experimental implementation
|
||||
module std::crypto::aes128;
|
||||
import std::crypto::aes;
|
||||
|
||||
fn char[] encrypt(Allocator allocator, char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES128, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.encrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tencrypt(char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return encrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
fn char[] decrypt(Allocator allocator, char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES128, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.decrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tdecrypt(char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return decrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
module std::crypto::aes192;
|
||||
import std::crypto::aes;
|
||||
|
||||
fn char[] encrypt(Allocator allocator, char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES192, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.encrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tencrypt(char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return encrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
fn char[] decrypt(Allocator allocator, char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES192, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.decrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tdecrypt(char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return decrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
module std::crypto::aes256;
|
||||
import std::crypto::aes;
|
||||
|
||||
fn char[] encrypt(Allocator allocator, char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES256, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.encrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tencrypt(char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return encrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
fn char[] decrypt(Allocator allocator, char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES256, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.decrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tdecrypt(char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return decrypt(tmem, key, iv, data);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user