mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 20:11:17 +00:00
Compare commits
737 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
83e5a0c2ab | ||
|
|
c058c50aef | ||
|
|
46d3e3dc97 | ||
|
|
40ff6b1315 | ||
|
|
8453270921 | ||
|
|
6739de3a10 | ||
|
|
010a77816b | ||
|
|
61113a8471 | ||
|
|
7b0cc85b2c | ||
|
|
ea5fec80b0 | ||
|
|
9db316ddac | ||
|
|
cb164e2ca2 | ||
|
|
83ff1da80c | ||
|
|
d626dea52a | ||
|
|
5d026268a7 | ||
|
|
2ab318a178 | ||
|
|
638d5332ff | ||
|
|
5c46b0c2a0 | ||
|
|
4538a1f50d | ||
|
|
e0f1919849 | ||
|
|
df175dd48c | ||
|
|
6e340f22af | ||
|
|
ff2809a3ac | ||
|
|
a8554b4233 | ||
|
|
fa707db078 | ||
|
|
439349ceb8 | ||
|
|
d760378b02 | ||
|
|
50d7919fec | ||
|
|
f53f8bf423 | ||
|
|
82f1b543ed | ||
|
|
b48588ca8f | ||
|
|
a03d821602 | ||
|
|
fab00f21a6 | ||
|
|
49033320e2 | ||
|
|
207bcfea02 | ||
|
|
d2c44717f1 | ||
|
|
0beb30c979 | ||
|
|
de74e97ab1 | ||
|
|
7e100472e7 | ||
|
|
cfc87a9d66 | ||
|
|
84753bde6d | ||
|
|
72608ce01d | ||
|
|
82cc49b388 | ||
|
|
425676a98d | ||
|
|
5c77c9a754 | ||
|
|
fc5615a7a1 | ||
|
|
9707a9694f | ||
|
|
8b49e6c14d | ||
|
|
ae76839347 | ||
|
|
b8ae2b06d6 | ||
|
|
c9dbd86d82 | ||
|
|
d5b211a786 | ||
|
|
f5d02cd0d2 | ||
|
|
8c23c5028d | ||
|
|
461bd43a22 | ||
|
|
fbc8168bb9 | ||
|
|
ff75f2c21f | ||
|
|
25bccf4883 | ||
|
|
fefce25081 | ||
|
|
f21cc02320 | ||
|
|
1461414128 | ||
|
|
dc8b35e62f | ||
|
|
1dca90b89d | ||
|
|
a088a5057a | ||
|
|
facaa75083 | ||
|
|
cce097fdef | ||
|
|
5a6884b708 | ||
|
|
5898cad98d | ||
|
|
5482107ca8 | ||
|
|
c790ecbca5 | ||
|
|
1fba9a7993 | ||
|
|
ca87ff066b | ||
|
|
c0b80eccad | ||
|
|
cf0405930e | ||
|
|
28b1e4d182 | ||
|
|
9c65098a91 | ||
|
|
2439405e70 | ||
|
|
46b52ec9ce | ||
|
|
c6c78e7709 | ||
|
|
177df6321a | ||
|
|
d2a461d270 | ||
|
|
7a848416f7 | ||
|
|
83bc24f58c | ||
|
|
0ef99c23a8 | ||
|
|
0a905d8458 | ||
|
|
261bfb97c6 | ||
|
|
cc94199131 | ||
|
|
0925010c07 | ||
|
|
13f824e349 | ||
|
|
3d6f28919c | ||
|
|
910fc6e364 | ||
|
|
c40198b016 | ||
|
|
b35aafd3d5 | ||
|
|
222bfb158b | ||
|
|
61c67c8f23 | ||
|
|
2b90500c22 | ||
|
|
74a6e9f0c0 | ||
|
|
fbac2d6df3 | ||
|
|
8f0de40b3d | ||
|
|
8bb99c6f81 | ||
|
|
cc5f9c6aab | ||
|
|
fb6b048bd0 | ||
|
|
2a895ec7be | ||
|
|
cff6697818 | ||
|
|
453cd295e2 | ||
|
|
55c88408be | ||
|
|
f52c2a6f96 | ||
|
|
c430ff5d09 | ||
|
|
dd8e280835 | ||
|
|
34c2d8ce77 | ||
|
|
28b9ff8016 | ||
|
|
76f226f536 | ||
|
|
1b0ac13d76 | ||
|
|
f134b8b67a | ||
|
|
33b05bcfeb | ||
|
|
6d3c1f5d2f | ||
|
|
96943ca66f | ||
|
|
374d73af12 | ||
|
|
81397f0726 | ||
|
|
0c33b78a2f | ||
|
|
ee5b9e5826 | ||
|
|
5d3c3781e4 | ||
|
|
c13c0d04b1 | ||
|
|
88f44f1eac | ||
|
|
50680d6893 | ||
|
|
31096531e1 | ||
|
|
062a67fe75 | ||
|
|
1dfc24822e | ||
|
|
e35c7f0b90 | ||
|
|
7083b2b8e5 | ||
|
|
135213388d | ||
|
|
ed62268997 | ||
|
|
38110b0269 | ||
|
|
e34d56327a | ||
|
|
b50e6bd0e4 | ||
|
|
3ba68f85fe | ||
|
|
9f5c5a9acf | ||
|
|
b6f5938eda | ||
|
|
87725a3a9e | ||
|
|
70029cc4b8 | ||
|
|
4f72bc4be9 | ||
|
|
3a1aa8bdf0 | ||
|
|
a986d053c0 | ||
|
|
43943c1f33 | ||
|
|
3da9f73338 | ||
|
|
855be92881 | ||
|
|
e674deb486 | ||
|
|
3ef094a3d3 | ||
|
|
9c60c2cb33 | ||
|
|
b54d994475 | ||
|
|
80e360d8dd | ||
|
|
9f165342e2 | ||
|
|
a2bfeb156d | ||
|
|
bb8c03777d | ||
|
|
8338888976 | ||
|
|
79db06ecd1 | ||
|
|
341a70bd5d | ||
|
|
8bf9ca89a1 | ||
|
|
5046608d1f | ||
|
|
535151a2a5 | ||
|
|
b45cb22950 | ||
|
|
d6485ca08b | ||
|
|
d9e5926d57 | ||
|
|
cbacd64987 | ||
|
|
168c11e006 | ||
|
|
26362d5068 | ||
|
|
e77d1fb646 | ||
|
|
c41d551ead | ||
|
|
0d7697280c | ||
|
|
0a93581695 | ||
|
|
0509b40b21 | ||
|
|
6e11bdbd35 | ||
|
|
8a6e996442 | ||
|
|
be00fdb253 | ||
|
|
0dd1a93d0d | ||
|
|
7ca70b20be | ||
|
|
e0cfe56121 | ||
|
|
6ca77065d8 | ||
|
|
e96dce92cd | ||
|
|
cec9b21707 | ||
|
|
8c58b31bbd | ||
|
|
c785572467 | ||
|
|
a297470887 | ||
|
|
f0682422c0 | ||
|
|
1f856cacf5 | ||
|
|
c9ecb09cd7 | ||
|
|
4961d0433f | ||
|
|
ba48627ca0 | ||
|
|
45eb3acffe | ||
|
|
d3ad533dd6 | ||
|
|
9e54014848 | ||
|
|
f8e3ffd267 | ||
|
|
8b8a2beb0d | ||
|
|
79a4b6855b | ||
|
|
86680279fa | ||
|
|
b46d3947dd | ||
|
|
c4212c4649 | ||
|
|
63f619e5b6 | ||
|
|
ce06de4b18 | ||
|
|
e1d546225f | ||
|
|
c4f9efc8f5 | ||
|
|
69e30c19f8 | ||
|
|
940874e349 | ||
|
|
d3f2180330 | ||
|
|
68b5c1e1f1 | ||
|
|
c8e671d34b | ||
|
|
46c7e9aefa | ||
|
|
2126be2222 | ||
|
|
fa4fb44779 | ||
|
|
07e8779d4e | ||
|
|
77db50bce8 | ||
|
|
ea4c864d4b | ||
|
|
e6ec09f2c5 | ||
|
|
122179980c | ||
|
|
27e76fe59e | ||
|
|
d13f302ac8 | ||
|
|
3e1e3e3e29 | ||
|
|
0388910c17 | ||
|
|
4b984e12a5 | ||
|
|
4e717657bd | ||
|
|
78dcda0bb2 | ||
|
|
bc63c16c93 | ||
|
|
e3851f3723 | ||
|
|
3a502feb1d | ||
|
|
ef72e19bf0 | ||
|
|
8b794e8cea | ||
|
|
549e27a800 | ||
|
|
d05cc991f5 | ||
|
|
07be4b0e06 | ||
|
|
6fcda240b8 | ||
|
|
fff3cf33c7 | ||
|
|
a862437bac | ||
|
|
7a6df10b39 | ||
|
|
c54c400291 | ||
|
|
aaa5c0f743 | ||
|
|
9d2f4e72c2 | ||
|
|
70a849cbb5 | ||
|
|
ecb25a0010 | ||
|
|
300983f831 | ||
|
|
f2df4855ff | ||
|
|
50c590bb5f | ||
|
|
4a99ebef51 | ||
|
|
20d93ede0c | ||
|
|
f8b2f7f268 | ||
|
|
dc6d994480 | ||
|
|
2b3b7e32b8 | ||
|
|
03e2b30ede | ||
|
|
f3afec61bb | ||
|
|
bda33ca3f9 | ||
|
|
50c1aac9bb | ||
|
|
9092defd46 | ||
|
|
7dd9256e2d | ||
|
|
a056efce04 | ||
|
|
0bad8f92b0 | ||
|
|
b040736f7f | ||
|
|
778260213e | ||
|
|
50385be614 | ||
|
|
3c50376175 | ||
|
|
6848753a10 | ||
|
|
13771cc536 | ||
|
|
ac3b2f0fea | ||
|
|
4b61ac7dae | ||
|
|
70d0ad1fcc | ||
|
|
af2a0ffd3f | ||
|
|
02c3d5419b | ||
|
|
55fba09b3b | ||
|
|
d2a7dc4a9a | ||
|
|
d8ca0f69f6 | ||
|
|
7d0e143224 | ||
|
|
bd139f73ac | ||
|
|
f23dda8d50 | ||
|
|
a88364aaad | ||
|
|
9530fe8fcd | ||
|
|
26dc88e096 | ||
|
|
1f1c445a76 | ||
|
|
3e4f9e875f | ||
|
|
dab4844195 | ||
|
|
e40bab2d30 | ||
|
|
ca91ad4097 | ||
|
|
e2b11c17bc | ||
|
|
eda997545a | ||
|
|
92b3490210 | ||
|
|
ba545b44f0 | ||
|
|
4f130cfe56 | ||
|
|
145b76ec75 | ||
|
|
e30952b484 | ||
|
|
b145c073f0 | ||
|
|
948d56b321 | ||
|
|
69d0fa8c44 | ||
|
|
a845a932f5 | ||
|
|
3221180315 | ||
|
|
c326c525be | ||
|
|
16aadae9bd | ||
|
|
b7ffa3b17c | ||
|
|
772b20c26b | ||
|
|
c7eb0024c7 | ||
|
|
1a2dcd07ee | ||
|
|
ab32231cd1 | ||
|
|
a0192a0116 | ||
|
|
13e3ecbde2 | ||
|
|
fefe6d1342 | ||
|
|
bbef5656a5 | ||
|
|
ad3cd88350 | ||
|
|
5183370773 | ||
|
|
f74891d214 | ||
|
|
d2885faa79 | ||
|
|
c59d47f652 | ||
|
|
f863c4ae84 | ||
|
|
bb2a2526e4 | ||
|
|
f9b86226a8 | ||
|
|
a4f5c97150 | ||
|
|
5de03abe0d | ||
|
|
c3f5806aa3 | ||
|
|
5a36f0bc16 | ||
|
|
c5dbbf9ff7 | ||
|
|
304b604652 | ||
|
|
b787985bf7 | ||
|
|
d72ec09cee | ||
|
|
d4bd68c188 | ||
|
|
f51bfa5a44 | ||
|
|
3e4d1de70e | ||
|
|
721aaa28aa |
297
.github/workflows/main.yml
vendored
297
.github/workflows/main.yml
vendored
@@ -10,8 +10,9 @@ env:
|
||||
LLVM_RELEASE_VERSION_WINDOWS: 18
|
||||
LLVM_RELEASE_VERSION_MAC: 17
|
||||
LLVM_RELEASE_VERSION_LINUX: 17
|
||||
LLVM_RELEASE_VERSION_UBUNTU20: 17
|
||||
LLVM_DEV_VERSION: 20
|
||||
LLVM_RELEASE_VERSION_OPENBSD: 19
|
||||
LLVM_RELEASE_VERSION_UBUNTU22: 17
|
||||
LLVM_DEV_VERSION: 21
|
||||
jobs:
|
||||
|
||||
build-msvc:
|
||||
@@ -51,17 +52,17 @@ jobs:
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
cd resources/testproject
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32
|
||||
dir build\llvm_ir
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32 --trust=full
|
||||
dir out\llvm\windows-x64
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe clean
|
||||
dir build\llvm_ir
|
||||
dir out\llvm\windows-x64
|
||||
|
||||
|
||||
- name: Build testproject lib
|
||||
run: |
|
||||
cd resources/testproject
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv build hello_world_win32_lib
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv build hello_world_win32_lib --trust=full
|
||||
|
||||
- name: Compile and run dynlib-test
|
||||
run: |
|
||||
@@ -77,25 +78,25 @@ jobs:
|
||||
|
||||
- name: Vendor-fetch
|
||||
run: |
|
||||
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
|
||||
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55
|
||||
|
||||
- name: Try raylib5
|
||||
run: |
|
||||
cd resources
|
||||
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_arkanoid.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_snake.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_tetris.c3
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3.exe src/tester.py ..\build\${{ matrix.build_type }}\c3c.exe test_suite/
|
||||
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55 --print-linking examples\raylib\raylib_arkanoid.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55 --print-linking examples\raylib\raylib_snake.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55 --print-linking examples\raylib\raylib_tetris.c3
|
||||
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1 -D SLOW_TESTS
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ..\build\${{ matrix.build_type }}\c3c.exe test_suite/ --no-terminal
|
||||
|
||||
- name: Test python script
|
||||
run: |
|
||||
@@ -112,7 +113,7 @@ jobs:
|
||||
|
||||
build-msys2-mingw:
|
||||
runs-on: windows-latest
|
||||
# if: ${{ false }}
|
||||
if: ${{ false }}
|
||||
strategy:
|
||||
# Don't abort runners if a single one fails
|
||||
fail-fast: false
|
||||
@@ -132,17 +133,18 @@ jobs:
|
||||
install: git binutils mingw-w64-x86_64-clang mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
|
||||
- shell: msys2 {0}
|
||||
run: |
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-19.1.6-1-any.pkg.tar.zst
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-19.1.6-1-any.pkg.tar.zst
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-20.1.0-1-any.pkg.tar.zst
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-20.1.0-1-any.pkg.tar.zst
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_LINKER=lld
|
||||
cmake --build build
|
||||
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
cd resources
|
||||
../build/c3c compile-run --print-linking examples/hello_world_many.c3
|
||||
../build/c3c compile-run --print-linking examples/process.c3
|
||||
../build/c3c compile-run --print-linking examples/time.c3
|
||||
../build/c3c compile-run --print-linking examples/fannkuch-redux.c3
|
||||
../build/c3c compile-run --print-linking examples/contextfree/boolerr.c3
|
||||
@@ -153,22 +155,22 @@ jobs:
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv
|
||||
../../build/c3c run -vvv --trust=full
|
||||
|
||||
- name: Vendor-fetch
|
||||
run: |
|
||||
./build/c3c vendor-fetch raylib5
|
||||
./build/c3c vendor-fetch raylib55
|
||||
|
||||
- name: Build testproject lib
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c build hello_world_lib --cc cc -vvv
|
||||
../../build/c3c build hello_world_lib --cc cc -vvv --trust=full
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c.exe test_suite/
|
||||
|
||||
../build/c3c.exe compile --target windows-x64 -O1 src/test_suite_runner.c3
|
||||
./test_suite_runner.exe ../build/c3c.exe test_suite/ --no-terminal
|
||||
|
||||
build-msys2-clang:
|
||||
runs-on: windows-latest
|
||||
@@ -209,20 +211,20 @@ jobs:
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv
|
||||
../../build/c3c run -vvv --trust=full
|
||||
|
||||
- name: Build testproject lib
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c build hello_world_lib -vvv
|
||||
../../build/c3c build hello_world_lib -vvv --trust=full
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c.exe test_suite/
|
||||
../build/c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c.exe test_suite/ --no-terminal
|
||||
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
# Don't abort runners if a single one fails
|
||||
fail-fast: false
|
||||
@@ -272,6 +274,7 @@ jobs:
|
||||
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
|
||||
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
|
||||
-DLLVM_ENABLE_LIBXML2=OFF \
|
||||
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
|
||||
cmake --build build
|
||||
- name: CMake18
|
||||
@@ -286,6 +289,7 @@ jobs:
|
||||
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
|
||||
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
|
||||
-DLLVM_ENABLE_LIBXML2=OFF \
|
||||
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
|
||||
cmake --build build
|
||||
|
||||
@@ -344,12 +348,12 @@ jobs:
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit
|
||||
../build/c3c compile-test unit -D SLOW_TESTS
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv
|
||||
../../build/c3c run -vvv --trust=full
|
||||
|
||||
- name: Test WASM
|
||||
run: |
|
||||
@@ -368,7 +372,7 @@ jobs:
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv --linker=builtin
|
||||
../../build/c3c run -vvv --linker=builtin --trust=full
|
||||
|
||||
- name: Init a library & a project
|
||||
run: |
|
||||
@@ -380,7 +384,7 @@ jobs:
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
|
||||
@@ -389,6 +393,8 @@ jobs:
|
||||
cp -r lib c3
|
||||
cp msvc_build_libraries.py c3
|
||||
cp build/c3c c3
|
||||
cp README.md c3
|
||||
cp releasenotes.md c3
|
||||
tar czf c3-linux-${{matrix.build_type}}.tar.gz c3
|
||||
|
||||
- name: upload artifacts
|
||||
@@ -398,8 +404,8 @@ jobs:
|
||||
name: c3-linux-${{matrix.build_type}}
|
||||
path: c3-linux-${{matrix.build_type}}.tar.gz
|
||||
|
||||
build-linux-ubuntu20:
|
||||
runs-on: ubuntu-20.04
|
||||
build-linux-ubuntu22:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
# Don't abort runners if a single one fails
|
||||
fail-fast: false
|
||||
@@ -436,6 +442,7 @@ jobs:
|
||||
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
|
||||
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
|
||||
-DLLVM_ENABLE_LIBXML2=OFF \
|
||||
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
|
||||
cmake --build build
|
||||
- name: CMake
|
||||
@@ -450,6 +457,7 @@ jobs:
|
||||
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
|
||||
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
|
||||
-DLLVM_ENABLE_LIBXML2=OFF \
|
||||
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
|
||||
cmake --build build
|
||||
- name: Compile and run some examples
|
||||
@@ -486,47 +494,49 @@ jobs:
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit
|
||||
../build/c3c compile-test unit --sanitize=address -D SLOW_TESTS
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv
|
||||
../../build/c3c run -vvv --trust=full
|
||||
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv --linker=builtin
|
||||
../../build/c3c run -vvv --linker=builtin --trust=full
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU22
|
||||
run: |
|
||||
mkdir c3
|
||||
cp -r lib c3
|
||||
cp README.md c3
|
||||
cp releasenotes.md c3
|
||||
cp msvc_build_libraries.py c3
|
||||
cp build/c3c c3
|
||||
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz c3
|
||||
tar czf c3-ubuntu-22-${{matrix.build_type}}.tar.gz c3
|
||||
|
||||
- name: upload artifacts
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU22
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: c3-ubuntu-20-${{matrix.build_type}}
|
||||
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
|
||||
|
||||
name: c3-ubuntu-22-${{matrix.build_type}}
|
||||
path: c3-ubuntu-22-${{matrix.build_type}}.tar.gz
|
||||
|
||||
build-with-docker:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ubuntu_version: [20.04, 22.04]
|
||||
build_type: [Release, Debug]
|
||||
llvm_version: [17, 18, 19, 20]
|
||||
llvm_version: [17, 18, 19]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -578,12 +588,12 @@ jobs:
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit
|
||||
../build/c3c compile-test unit -D SLOW_TESTS
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv
|
||||
../../build/c3c run -vvv --trust=full
|
||||
|
||||
- name: Test WASM
|
||||
run: |
|
||||
@@ -593,7 +603,7 @@ jobs:
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv --linker=builtin
|
||||
../../build/c3c run -vvv --linker=builtin --trust=full
|
||||
|
||||
- name: Init a library & a project
|
||||
run: |
|
||||
@@ -605,7 +615,7 @@ jobs:
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
|
||||
|
||||
build-mac:
|
||||
runs-on: macos-latest
|
||||
@@ -637,7 +647,7 @@ jobs:
|
||||
|
||||
- name: Vendor-fetch
|
||||
run: |
|
||||
./build/c3c vendor-fetch raylib5
|
||||
./build/c3c vendor-fetch raylib55
|
||||
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
@@ -660,7 +670,7 @@ jobs:
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit
|
||||
../build/c3c compile-test unit -O1 -D SLOW_TESTS
|
||||
|
||||
- name: Test WASM
|
||||
run: |
|
||||
@@ -670,22 +680,28 @@ jobs:
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv
|
||||
../../build/c3c run -vvv --trust=full
|
||||
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv --linker=builtin
|
||||
../../build/c3c run -vvv --linker=builtin --trust=full
|
||||
|
||||
- name: Build testproject lib
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c build hello_world_lib -vvv
|
||||
../../build/c3c build hello_world_lib -vvv --trust=full
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
python3 src/tester.py ../build/c3c test_suite/
|
||||
../build/c3c compile -O1 src/test_suite_runner.c3
|
||||
./test_suite_runner ../build/c3c test_suite/
|
||||
|
||||
- name: run build test suite runner
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile -O1 src/test_suite_runner.c3
|
||||
|
||||
- name: bundle_output
|
||||
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
|
||||
@@ -693,6 +709,8 @@ jobs:
|
||||
mkdir macos
|
||||
cp -r lib macos
|
||||
cp msvc_build_libraries.py macos
|
||||
cp README.md macos
|
||||
cp releasenotes.md macos
|
||||
cp build/c3c macos
|
||||
zip -r c3-macos-${{matrix.build_type}}.zip macos
|
||||
|
||||
@@ -704,7 +722,7 @@ jobs:
|
||||
path: c3-macos-${{matrix.build_type}}.zip
|
||||
|
||||
build-nix:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
# Don't abort runners if a single one fails
|
||||
fail-fast: false
|
||||
@@ -734,56 +752,157 @@ jobs:
|
||||
nix build -L ".#c3c-checks"
|
||||
fi
|
||||
|
||||
release:
|
||||
build-openbsd:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu20]
|
||||
if: github.ref == 'refs/heads/master'
|
||||
strategy:
|
||||
# Don't abort runners if a single one fails
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: delete tag
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@v6
|
||||
- name: OpenBSD VM
|
||||
uses: vmactions/openbsd-vm@main
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.deleteRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'tags/latest',
|
||||
sha: context.sha
|
||||
})
|
||||
- name: create tag
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/tags/latest',
|
||||
sha: context.sha
|
||||
})
|
||||
prepare: |
|
||||
pkg_add cmake llvm-19.1.7p3 ninja
|
||||
|
||||
run: |
|
||||
echo "CMake"
|
||||
cmake -B build \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||
-DLLVM_ENABLE_LIBXML2=OFF \
|
||||
-DC3_LLVM_VERSION=${LLVM_RELEASE_VERSION_OPENBSD}
|
||||
cmake --build build
|
||||
echo "Compile and run some examples"
|
||||
cd resources
|
||||
../build/c3c compile examples/base64.c3
|
||||
../build/c3c compile examples/binarydigits.c3
|
||||
../build/c3c compile examples/brainfk.c3
|
||||
../build/c3c compile examples/factorial_macro.c3
|
||||
../build/c3c compile examples/fasta.c3
|
||||
../build/c3c compile examples/gameoflife.c3
|
||||
../build/c3c compile examples/hash.c3
|
||||
../build/c3c compile-only examples/levenshtein.c3
|
||||
../build/c3c compile examples/load_world.c3
|
||||
../build/c3c compile-only examples/map.c3
|
||||
../build/c3c compile examples/mandelbrot.c3
|
||||
../build/c3c compile examples/plus_minus.c3
|
||||
../build/c3c compile examples/nbodies.c3
|
||||
../build/c3c compile examples/spectralnorm.c3
|
||||
../build/c3c compile examples/swap.c3
|
||||
../build/c3c compile examples/contextfree/boolerr.c3
|
||||
../build/c3c compile examples/contextfree/dynscope.c3
|
||||
../build/c3c compile examples/contextfree/guess_number.c3
|
||||
../build/c3c compile examples/contextfree/multi.c3
|
||||
../build/c3c compile examples/contextfree/cleanup.c3
|
||||
../build/c3c compile-run examples/hello_world_many.c3
|
||||
../build/c3c compile-run examples/time.c3
|
||||
../build/c3c compile-run examples/fannkuch-redux.c3
|
||||
../build/c3c compile-run examples/contextfree/boolerr.c3
|
||||
../build/c3c compile-run examples/load_world.c3
|
||||
../build/c3c compile-run examples/process.c3
|
||||
../build/c3c compile-run examples/ls.c3
|
||||
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
|
||||
cd ..
|
||||
echo "Compile and run dynlib-test"
|
||||
cd resources/examples/dynlib-test
|
||||
../../../build/c3c -vv dynamic-lib add.c3
|
||||
mv add.so libadd.so
|
||||
cc test.c -L. -ladd -Wl,-rpath=.
|
||||
./a.out
|
||||
../../../build/c3c compile-run test.c3 -L . -l add -z -Wl,-rpath=.
|
||||
cd ../../../
|
||||
echo "Compile and run staticlib-test"
|
||||
cd resources/examples/staticlib-test
|
||||
../../../build/c3c -vv static-lib add.c3
|
||||
mv add.a libadd.a
|
||||
cc test.c -L. -ladd
|
||||
./a.out
|
||||
../../../build/c3c compile-run test.c3 -L . -l add
|
||||
cd ../../../
|
||||
echo "Compile run unit tests"
|
||||
cd test
|
||||
../build/c3c --max-mem 128 compile-test unit -D SLOW_TESTS
|
||||
cd ..
|
||||
echo "Build testproject"
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv --trust=full
|
||||
cd ../../
|
||||
echo "Test WASM"
|
||||
cd resources/testfragments
|
||||
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
|
||||
cd ../../
|
||||
echo "Build testproject direct linker"
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv --linker=builtin --trust=full
|
||||
cd ../../
|
||||
echo "Init a library & a project"
|
||||
./build/c3c init-lib mylib
|
||||
ls mylib.c3l
|
||||
./build/c3c init myproject
|
||||
ls myproject
|
||||
echo "run compiler tests"
|
||||
cd test
|
||||
../build/c3c --max-mem 128 compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
|
||||
cd ..
|
||||
|
||||
- name: bundle_output
|
||||
run: |
|
||||
mkdir c3
|
||||
cp -r lib c3
|
||||
cp msvc_build_libraries.py c3
|
||||
cp build/c3c c3
|
||||
cp README.md c3
|
||||
cp releasenotes.md c3
|
||||
tar czf c3-openbsd-${{matrix.build_type}}.tar.gz c3
|
||||
|
||||
- name: upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: c3-openbsd-${{matrix.build_type}}
|
||||
path: c3-openbsd-${{matrix.build_type}}.tar.gz
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu22]
|
||||
if: github.ref == 'refs/heads/master'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
- run: cp -r lib c3-windows-Release
|
||||
- run: cp -r lib c3-windows-Debug
|
||||
- run: cp msvc_build_libraries.py c3-windows-Release
|
||||
- run: cp msvc_build_libraries.py c3-windows-Debug
|
||||
- run: cp README.md c3-windows-Release
|
||||
- run: cp README.md c3-windows-Debug
|
||||
- run: cp releasenotes.md c3-windows-Release
|
||||
- run: cp releasenotes.md c3-windows-Debug
|
||||
- run: zip -r c3-windows.zip c3-windows-Release
|
||||
- run: zip -r c3-windows-debug.zip c3-windows-Debug
|
||||
- run: mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux-Release/c3-linux.tar.gz
|
||||
- run: mv c3-linux-Debug/c3-linux-Debug.tar.gz c3-linux-Debug/c3-linux-debug.tar.gz
|
||||
- run: mv c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
|
||||
- run: mv c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
|
||||
- run: mv c3-openbsd-Release/c3-openbsd-Release.tar.gz c3-openbsd-Release/c3-openbsd.tar.gz
|
||||
- run: mv c3-openbsd-Debug/c3-openbsd-Debug.tar.gz c3-openbsd-Debug/c3-openbsd-debug.tar.gz
|
||||
- run: mv c3-ubuntu-22-Release/c3-ubuntu-22-Release.tar.gz c3-ubuntu-22-Release/c3-ubuntu-22.tar.gz
|
||||
- run: mv c3-ubuntu-22-Debug/c3-ubuntu-22-Debug.tar.gz c3-ubuntu-22-Debug/c3-ubuntu-22-debug.tar.gz
|
||||
- run: mv c3-macos-Release/c3-macos-Release.zip c3-macos-Release/c3-macos.zip
|
||||
- run: mv c3-macos-Debug/c3-macos-Debug.zip c3-macos-Debug/c3-macos-debug.zip
|
||||
- run: gh release delete latest-prerelease --cleanup-tag -y || true
|
||||
- run: echo "RELEASE_NAME=latest-prerelease-$(date +'%Y%m%d-%H%M')" >> $GITHUB_ENV
|
||||
|
||||
- id: create_release
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: latest
|
||||
release_name: latest
|
||||
tag_name: latest-prerelease
|
||||
name: ${{ env.RELEASE_NAME }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
files: |
|
||||
@@ -791,7 +910,9 @@ jobs:
|
||||
c3-windows-debug.zip
|
||||
c3-linux-Release/c3-linux.tar.gz
|
||||
c3-linux-Debug/c3-linux-debug.tar.gz
|
||||
c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
|
||||
c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
|
||||
c3-openbsd-Release/c3-openbsd.tar.gz
|
||||
c3-openbsd-Debug/c3-openbsd-debug.tar.gz
|
||||
c3-ubuntu-22-Release/c3-ubuntu-22.tar.gz
|
||||
c3-ubuntu-22-Debug/c3-ubuntu-22-debug.tar.gz
|
||||
c3-macos-Release/c3-macos.zip
|
||||
c3-macos-Debug/c3-macos-debug.zip
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -80,4 +80,9 @@ TAGS
|
||||
result
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
# tests
|
||||
/test/tmp/*
|
||||
/test/testrun
|
||||
/test/test_suite_runner
|
||||
280
CMakeLists.txt
280
CMakeLists.txt
@@ -1,5 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
set(C3_LLVM_MIN_VERSION 17)
|
||||
set(C3_LLVM_MAX_VERSION 21)
|
||||
set(C3_LLVM_DEFAULT_VERSION 19)
|
||||
|
||||
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 ()
|
||||
@@ -36,35 +56,41 @@ set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
# Use /MT or /MTd
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
|
||||
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")
|
||||
add_compile_options(/utf-8)
|
||||
else()
|
||||
if (true)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined,address -fno-exceptions")
|
||||
endif()
|
||||
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_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation")
|
||||
set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
|
||||
|
||||
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
|
||||
option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF)
|
||||
option(C3_USE_TB "Use TB" OFF)
|
||||
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
|
||||
option(C3_WITH_LLVM "Build with LLVM" ON)
|
||||
option(C3_LLD_DIR "Use custom LLD directory" "")
|
||||
set(C3_OPTIONS
|
||||
C3_LINK_DYNAMIC
|
||||
C3_WITH_LLVM
|
||||
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)
|
||||
@@ -82,13 +108,6 @@ 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 20)
|
||||
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")
|
||||
@@ -106,7 +125,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(
|
||||
@@ -119,7 +137,7 @@ 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")
|
||||
set(C3_LLVM_VERSION ${C3_LLVM_DEFAULT_VERSION})
|
||||
endif()
|
||||
FetchContent_Declare(
|
||||
LLVM_Windows
|
||||
@@ -138,11 +156,26 @@ if(C3_WITH_LLVM)
|
||||
FetchContent_MakeAvailable(LLVM_Windows)
|
||||
set(llvm_dir ${llvm_windows_SOURCE_DIR})
|
||||
endif()
|
||||
message("Loaded Windows LLVM libraries into ${llvm_dir}")
|
||||
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH})
|
||||
|
||||
find_package(LLVM REQUIRED CONFIG)
|
||||
find_package(LLD REQUIRED CONFIG)
|
||||
else()
|
||||
# Add paths for LLVM CMake files of version 19 and higher as they follow a new installation
|
||||
# layout and are now in /usr/lib/llvm/*/lib/cmake/llvm/ rather than /usr/lib/cmake/llvm/
|
||||
#
|
||||
# Because of CMAKE_FIND_PACKAGE_SORT_ORDER CMAKE_FIND_PACKAGE_SORT_DIRECTION,
|
||||
# the newest version will always be found first.
|
||||
c3_print_variables(CMAKE_PREFIX_PATH)
|
||||
if (DEFINED LLVM_DIR)
|
||||
message(STATUS "Looking for LLVM CMake files in user-specified directory ${LLVM_DIR}")
|
||||
else()
|
||||
file (GLOB LLVM_CMAKE_PATHS "/usr/lib/llvm/*/lib/cmake/llvm/")
|
||||
list (APPEND CMAKE_PREFIX_PATH ${LLVM_CMAKE_PATHS} "/usr/lib/")
|
||||
message(STATUS "No LLVM_DIR specified, searching default directories ${CMAKE_PREFIX_PATH}")
|
||||
endif()
|
||||
|
||||
if (NOT C3_LLVM_VERSION STREQUAL "auto")
|
||||
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
|
||||
else()
|
||||
@@ -150,6 +183,10 @@ if(C3_WITH_LLVM)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (EXISTS /opt/homebrew/lib)
|
||||
list(APPEND LLVM_LIBRARY_DIRS /opt/homebrew/lib)
|
||||
endif()
|
||||
|
||||
if (EXISTS /usr/lib)
|
||||
# Some systems (such as Alpine Linux) seem to put some of the relevant
|
||||
# LLVM files in /usr/lib, but this doesn't seem to be included in the
|
||||
@@ -157,12 +194,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)
|
||||
@@ -212,44 +252,43 @@ 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 liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.dll.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.dll.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.dll.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
||||
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
else()
|
||||
set(LLD_MACHO "")
|
||||
endif()
|
||||
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.dll.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
|
||||
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.dll.a liblldWasm.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)
|
||||
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}
|
||||
@@ -261,10 +300,10 @@ 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")
|
||||
find_file(RT_TSAN_DYNAMIC NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
|
||||
find_file(RT_UBSAN_DYNAMIC NAMES libclang_rt.ubsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
|
||||
find_file(RT_LSAN_DYNAMIC NAMES libclang_rt.lsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
|
||||
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}
|
||||
@@ -274,8 +313,8 @@ if(C3_WITH_LLVM)
|
||||
)
|
||||
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)
|
||||
@@ -393,6 +432,11 @@ if(C3_WITH_LLVM)
|
||||
|
||||
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=1)
|
||||
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
|
||||
if (MSVC)
|
||||
target_compile_options(c3c PRIVATE
|
||||
"$<$<CONFIG:Debug>:/EHa>"
|
||||
"$<$<CONFIG:Release>:/EHsc>")
|
||||
endif()
|
||||
else()
|
||||
target_sources(c3c PRIVATE src/utils/hostinfo.c)
|
||||
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0)
|
||||
@@ -476,7 +520,7 @@ endif ()
|
||||
|
||||
if (CURL_FOUND)
|
||||
target_link_libraries(c3c ${CURL_LIBRARIES})
|
||||
target_include_directories(c3c PRIVATE ${CURL_INCLUDES})
|
||||
target_include_directories(c3c PRIVATE ${CURL_INCLUDE_DIRS})
|
||||
target_compile_definitions(c3c PUBLIC CURL_FOUND=1)
|
||||
else()
|
||||
target_compile_definitions(c3c PUBLIC CURL_FOUND=0)
|
||||
@@ -484,34 +528,27 @@ 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
|
||||
@@ -521,13 +558,20 @@ if(MSVC)
|
||||
${clang_lib_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "using gcc/clang warning switches")
|
||||
target_link_options(c3c PRIVATE -pthread)
|
||||
if (C3_WITH_LLVM AND NOT LLVM_ENABLE_RTTI)
|
||||
target_compile_options(c3c_wrappers PRIVATE -fno-rtti)
|
||||
endif()
|
||||
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
|
||||
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)
|
||||
target_compile_options(c3c PRIVATE
|
||||
-pthread
|
||||
-Wall
|
||||
-Werror
|
||||
-Wno-unknown-pragmas
|
||||
-Wno-unused-result
|
||||
-Wno-unused-function
|
||||
-Wno-unused-variable
|
||||
-Wno-unused-parameter
|
||||
)
|
||||
target_link_options(c3c PRIVATE -pthread)
|
||||
endif()
|
||||
|
||||
install(TARGETS c3c DESTINATION bin)
|
||||
@@ -538,6 +582,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
|
||||
@@ -557,3 +607,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"
|
||||
}
|
||||
]
|
||||
}
|
||||
114
CODESTYLE.md
114
CODESTYLE.md
@@ -74,7 +74,7 @@ No space inside parenthesis:
|
||||
|
||||
### Tab vs spaces
|
||||
|
||||
Recommendation: tabs, 4 spaces wide. No CRLF in the source.
|
||||
Use tabs for indentation, no CRLF in the source.
|
||||
|
||||
### If, braces and new lines
|
||||
|
||||
@@ -147,4 +147,114 @@ Iterating over the elements are done using `VECEACH`.
|
||||
### Scratch buffer for strings.
|
||||
|
||||
There is a scratch buffer for strings in the `global_context` prefer using that
|
||||
one with related functions when working on temporary strings.
|
||||
one with related functions when working on temporary strings.
|
||||
|
||||
# C3 Standard library style guide.
|
||||
|
||||
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
|
||||
|
||||
**NO:**
|
||||
```c
|
||||
fn void foo(String bar) {
|
||||
@pool() {
|
||||
...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**YES:**
|
||||
```c
|
||||
fn void foo(String bar)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Indentation with tabs
|
||||
|
||||
Use tab for indentation, not spaces, no CRLF in the sources
|
||||
|
||||
### Type names
|
||||
|
||||
Use `PascalCase` not `Ada_Case` for type names.
|
||||
|
||||
**YES:**
|
||||
```c
|
||||
enum MyEnum
|
||||
{
|
||||
ABC,
|
||||
DEF
|
||||
}
|
||||
```
|
||||
|
||||
**NO:**
|
||||
```c
|
||||
enum My_Enum
|
||||
{
|
||||
ABC,
|
||||
DEF
|
||||
}
|
||||
```
|
||||
|
||||
### Type names when binding to OS libraries
|
||||
|
||||
When doing bindings (for instance, adding declarations referring to Win32 APIs),
|
||||
try to retain the original name when possible. If it isn't possible use (consistently)
|
||||
one of two options:
|
||||
|
||||
1. Prefix: `HANDLE` -> `Win32_HANDLE`
|
||||
2. Change the first letter to upper case: `mode_t` -> `Mode_t`
|
||||
|
||||
### Variables, function, methods and globals
|
||||
|
||||
Use `snake_case`, not `camelCase`.
|
||||
|
||||
**YES:**
|
||||
```c
|
||||
int some_global = 1;
|
||||
|
||||
fn void open_file(String special_file)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**NO:**
|
||||
```c
|
||||
int someGlobal = 1;
|
||||
|
||||
fn void openFile(String specialFile)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Variables, function, methods and globals when binding to OS libraries
|
||||
|
||||
When doing bindings (for instance, adding declarations referring to Win32 APIs),
|
||||
try to retain the original name when possible. If it isn't possible use (consistently)
|
||||
one of two options:
|
||||
|
||||
1. Prefix: `win32_GetWindowLongPtrW`. However, this is usually only recommended if it is builtin.
|
||||
2. Change first character to lower case: `GetWindowLongPtrW` -> `getWindowLongPtrW`
|
||||
|
||||
### Use `self` as the first method argument
|
||||
|
||||
Unless there is a strong reason not to, use `self` for the first parameter in a method.
|
||||
|
||||
### The allocator argument
|
||||
|
||||
Prefer always calling the allocator parameter `allocator`, and make it the first regular
|
||||
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.
|
||||
|
||||
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.
|
||||
132
README.md
132
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-mac-with-precompiled-binaries).
|
||||
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
|
||||
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
|
||||
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/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/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
|
||||
- OpenBSD x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/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 [generic modules](https://c3-lang.org/generic-programming/generics/) (more examples can be found at https://c3-lang.org/language-overview/examples/).
|
||||
|
||||
```cpp
|
||||
module stack (<Type>);
|
||||
module stack {Type};
|
||||
// Above: the parameterized type is applied to the entire module.
|
||||
|
||||
struct Stack
|
||||
@@ -80,13 +83,13 @@ import stack;
|
||||
|
||||
// Define our new types, the first will implicitly create
|
||||
// a complete copy of the entire Stack module with "Type" set to "int"
|
||||
def IntStack = Stack(<int>);
|
||||
alias IntStack = Stack {int};
|
||||
// The second creates another copy with "Type" set to "double"
|
||||
def DoubleStack = Stack(<double>);
|
||||
alias DoubleStack = Stack {double};
|
||||
|
||||
// If we had added "define IntStack2 = Stack(<int>)"
|
||||
// If we had added "alias IntStack2 = Stack {int}"
|
||||
// no additional copy would have been made (since we already
|
||||
// have an parameterization of Stack(<int>)) so it would
|
||||
// have an parameterization of Stack {int} so it would
|
||||
// be same as declaring IntStack2 an alias of IntStack
|
||||
|
||||
// Importing an external C function is straightforward
|
||||
@@ -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,9 +142,10 @@ fn void main()
|
||||
|
||||
### Current status
|
||||
|
||||
The current stable version of the compiler is **version 0.6.5**.
|
||||
The current stable version of the compiler is **version 0.7.4**.
|
||||
|
||||
The upcoming 0.6.x releases will focus on expanding the standard library.
|
||||
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)
|
||||
@@ -172,12 +177,14 @@ The compiler is currently verified to compile on Linux, Windows and MacOS.
|
||||
| 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*
|
||||
|
||||
More platforms will be supported in the future.
|
||||
|
||||
@@ -193,34 +200,44 @@ More platforms will be supported in the future.
|
||||
|
||||
### 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/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows.zip)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/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 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/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux-debug.tar.gz))
|
||||
2. Unpack executable and standard lib.
|
||||
3. Run `./c3c`.
|
||||
|
||||
#### 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/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/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/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos.zip)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/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/c3-openbsd.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-openbsd.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/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:
|
||||
|
||||
@@ -249,20 +266,13 @@ makepkg -si
|
||||
|
||||
#### Building via Docker
|
||||
|
||||
You can build `c3c` using either an Ubuntu 18.04 or 20.04 container:
|
||||
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.
|
||||
|
||||
```
|
||||
./build-with-docker.sh 18
|
||||
UBUNTU_VERSION=20.04 ./build-with-docker.sh
|
||||
```
|
||||
|
||||
Replace `18` with `20` to build through Ubuntu 20.04.
|
||||
|
||||
For a release build specify:
|
||||
```
|
||||
./build-with-docker.sh 20 Release
|
||||
```
|
||||
|
||||
A `c3c` executable will be found under `bin/`.
|
||||
See the `build-with-docker.sh` script for more information on other configurable environment variables.
|
||||
|
||||
#### Installing on OS X using Homebrew
|
||||
|
||||
@@ -275,6 +285,14 @@ A `c3c` executable will be found under `bin/`.
|
||||
8. Set up CMake build for debug: `cmake ..`
|
||||
9. Build: `cmake --build .`
|
||||
|
||||
#### Installing on Windows using Scoop
|
||||
|
||||
c3c is included in 'Main' bucket.
|
||||
|
||||
```sh
|
||||
scoop install c3
|
||||
```
|
||||
|
||||
#### Getting started with a "hello world"
|
||||
|
||||
Create a `main.c3` file with:
|
||||
@@ -306,17 +324,25 @@ 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
|
||||
|
||||
@@ -362,6 +388,23 @@ For a sytem-wide installation, run the following as root: `cmake --install .`
|
||||
|
||||
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`
|
||||
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: `make -C 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 other Linux / Unix variants
|
||||
|
||||
@@ -401,4 +444,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)
|
||||
|
||||
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);
|
||||
12
flake.lock
generated
12
flake.lock
generated
@@ -5,11 +5,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1730958623,
|
||||
"narHash": "sha256-JwQZIGSYnRNOgDDoIgqKITrPVil+RMWHsZH1eE1VGN0=",
|
||||
"lastModified": 1738297584,
|
||||
"narHash": "sha256-AYvaFBzt8dU0fcSK2jKD0Vg23K2eIRxfsVXIPCW9a0E=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "85f7e662eda4fa3a995556527c87b2524b691933",
|
||||
"rev": "9189ac18287c599860e878e905da550aa6dec1cd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
// 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{Type};
|
||||
|
||||
struct Atomic
|
||||
{
|
||||
@@ -11,7 +11,7 @@ struct Atomic
|
||||
<*
|
||||
Loads data atomically, by default this uses SEQ_CONSISTENT ordering.
|
||||
|
||||
@param ordering "The ordering, cannot be release or acquire-release."
|
||||
@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)
|
||||
@@ -31,7 +31,7 @@ macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
<*
|
||||
Stores data atomically, by default this uses SEQ_CONSISTENT ordering.
|
||||
|
||||
@param ordering "The ordering, cannot be acquire or acquire-release."
|
||||
@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)
|
||||
@@ -76,7 +76,7 @@ macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTEN
|
||||
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_div, data, value, ordering);
|
||||
return @atomic_exec(atomic::fetch_max, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
@@ -85,36 +85,49 @@ macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTEN
|
||||
return @atomic_exec(atomic::fetch_min, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.or(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
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);
|
||||
}
|
||||
|
||||
fn Type Atomic.xor(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
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);
|
||||
}
|
||||
|
||||
macro Type Atomic.and(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
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);
|
||||
}
|
||||
|
||||
macro Type Atomic.shift_right(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
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);
|
||||
}
|
||||
|
||||
macro Type Atomic.shift_left(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -128,18 +141,60 @@ macro @atomic_exec(#func, data, value, ordering) @local
|
||||
}
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
module std::atomic;
|
||||
import std::math;
|
||||
|
||||
macro bool @is_native_atomic_value(#value) @private
|
||||
{
|
||||
return is_native_atomic_type($typeof(#value));
|
||||
}
|
||||
|
||||
macro bool is_native_atomic_type($Type)
|
||||
{
|
||||
$if $Type.sizeof > void*.sizeof:
|
||||
return false;
|
||||
$else
|
||||
$switch $Type.kindof:
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
$case POINTER:
|
||||
$case FUNC:
|
||||
$case FLOAT:
|
||||
$case BOOL:
|
||||
return true;
|
||||
$case DISTINCT:
|
||||
$case CONST_ENUM:
|
||||
return is_native_atomic_type($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@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"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
|
||||
@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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
@@ -150,14 +205,16 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@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"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
|
||||
@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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
@@ -168,22 +225,24 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@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"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@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 != 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;
|
||||
|
||||
@@ -193,34 +252,37 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
|
||||
|
||||
do {
|
||||
do
|
||||
{
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = old_value * y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
}
|
||||
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@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 types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@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 != 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;
|
||||
|
||||
@@ -230,163 +292,89 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
|
||||
do {
|
||||
do
|
||||
{
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = old_value / y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
}
|
||||
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@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"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
|
||||
@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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value | storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@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"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
|
||||
@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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value ^ storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@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"
|
||||
|
||||
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
|
||||
@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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value & storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@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"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@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 != 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;
|
||||
|
||||
@@ -397,34 +385,38 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
do
|
||||
{
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value >> storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
}
|
||||
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@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"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require types::is_int($typeof(y)) "The value for or must be an int"
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@require $defined(*ptr) : "Expected a pointer"
|
||||
@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 != 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;
|
||||
|
||||
@@ -435,64 +427,83 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
do
|
||||
{
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value << storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
}
|
||||
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@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 types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value = true;
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
do
|
||||
{
|
||||
old_value = $$atomic_load(ptr, false, $load_ordering.ordinal);
|
||||
}
|
||||
while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
|
||||
do {
|
||||
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
|
||||
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@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 types::is_int($typeof(*ptr)) "Only integer pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value = false;
|
||||
|
||||
do {
|
||||
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
|
||||
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = SEQ_CONSISTENT;
|
||||
$endif
|
||||
do
|
||||
{
|
||||
old_value = $$atomic_load(ptr, false, $load_ordering.ordinal);
|
||||
}
|
||||
while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
|
||||
return old_value;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [in] y : "the value to be compared to ptr."
|
||||
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
@@ -503,13 +514,15 @@ macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ptr "the variable or dereferenced pointer to the data."
|
||||
@param [in] y "the value to be added to ptr."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@param [&in] ptr : "the variable or dereferenced pointer to the data."
|
||||
@param [in] y : "the value to be compared to ptr."
|
||||
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@return "returns the old value of ptr"
|
||||
|
||||
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
|
||||
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@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 != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
|
||||
*>
|
||||
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::atomic;
|
||||
|
||||
macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment) {
|
||||
switch(failure)
|
||||
macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment)
|
||||
{
|
||||
switch (failure)
|
||||
{
|
||||
case AtomicOrdering.RELAXED.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.RELAXED.ordinal, $alignment);
|
||||
case AtomicOrdering.ACQUIRE.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.ACQUIRE.ordinal, $alignment);
|
||||
@@ -16,7 +17,7 @@ macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $succe
|
||||
|
||||
macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, success, failure, $alignment)
|
||||
{
|
||||
switch(success)
|
||||
switch (success)
|
||||
{
|
||||
case AtomicOrdering.RELAXED.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELAXED.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.ACQUIRE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE.ordinal, failure, $alignment);
|
||||
@@ -28,7 +29,7 @@ macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, succes
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, CInt success, CInt failure) @extern("__atomic_compare_exchange") @export
|
||||
fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, CInt success, CInt failure) @weak @export("__atomic_compare_exchange")
|
||||
{
|
||||
switch (size)
|
||||
{
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
module std::bits;
|
||||
|
||||
<*
|
||||
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
|
||||
@require types::is_intlike($typeof(i)) : `The input must be an integer or integer vector`
|
||||
*>
|
||||
macro reverse(i) => $$bitreverse(i);
|
||||
|
||||
<*
|
||||
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
|
||||
@require types::is_intlike($typeof(i)) : `The input must be an integer or integer vector`
|
||||
*>
|
||||
macro bswap(i) @builtin => $$bswap(i);
|
||||
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
// Copyright (c) 2024 Christoffer Lerno. All rights reserved.
|
||||
// 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.
|
||||
module std::collections::anylist;
|
||||
import std::io,std::math;
|
||||
|
||||
def AnyPredicate = fn bool(any value);
|
||||
def AnyTest = fn bool(any type, any context);
|
||||
alias AnyPredicate = fn bool(any value);
|
||||
alias AnyTest = fn bool(any type, any context);
|
||||
|
||||
<*
|
||||
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.
|
||||
*>
|
||||
struct AnyList (Printable)
|
||||
{
|
||||
usz size;
|
||||
@@ -17,18 +28,11 @@ struct AnyList (Printable)
|
||||
|
||||
|
||||
<*
|
||||
Use `init` for to use a custom allocator.
|
||||
Initialize the list. If not initialized then it will use the temp allocator
|
||||
when something is pushed to it.
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = null)
|
||||
{
|
||||
return self.init(allocator ?: allocator::heap(), initial_capacity) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param initial_capacity : "The initial capacity to reserve, defaults to 16"
|
||||
*>
|
||||
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
{
|
||||
@@ -50,14 +54,371 @@ fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
@param initial_capacity : "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16)
|
||||
fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(allocator::temp(), initial_capacity) @inline;
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
|
||||
fn bool AnyList.is_initialized(&self) @inline => self.allocator != null;
|
||||
|
||||
<*
|
||||
Push an element on the list by cloning it.
|
||||
*>
|
||||
macro void AnyList.push(&self, element)
|
||||
{
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
self._append(allocator::clone(self.allocator, element));
|
||||
}
|
||||
|
||||
<*
|
||||
Free a retained element removed using *_retained.
|
||||
*>
|
||||
fn void AnyList.free_element(&self, any element) @inline
|
||||
{
|
||||
allocator::free(self.allocator, element.ptr);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@param $Type : "The type we assume the value has"
|
||||
@return "The last 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);
|
||||
}
|
||||
|
||||
<*
|
||||
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 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]);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
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 any? AnyList.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 any? AnyList.pop_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Remove all elements in the list.
|
||||
*>
|
||||
fn void AnyList.clear(&self)
|
||||
{
|
||||
for (usz i = 0; i < self.size; i++)
|
||||
{
|
||||
self.free_element(self.entries[i]);
|
||||
}
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
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_first(&self, $Type)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return *anycast(self.entries[0], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
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 any? AnyList.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 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]);
|
||||
}
|
||||
|
||||
<*
|
||||
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 any? AnyList.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 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];
|
||||
}
|
||||
|
||||
<*
|
||||
Add all the elements in another AnyList.
|
||||
|
||||
@param [&in] other_list : "The list to add"
|
||||
*>
|
||||
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 order of the elements in the 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);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Return a view of the data as a slice.
|
||||
|
||||
@return "The slice view"
|
||||
*>
|
||||
fn any[] AnyList.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"
|
||||
*>
|
||||
macro void AnyList.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"
|
||||
*>
|
||||
macro void AnyList.insert_at(&self, usz index, type)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(type);
|
||||
return;
|
||||
}
|
||||
any value = allocator::copy(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 AnyList.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 AnyList.remove_first(&self)
|
||||
{
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
<*
|
||||
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)
|
||||
{
|
||||
return *anycast(self.first_any(), $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Return the first element
|
||||
|
||||
@return "The first element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.first_any(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the last element by value, assuming it is the given type.
|
||||
|
||||
@param $Type : "The type of the last element"
|
||||
@return "The last element"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.last(&self, $Type)
|
||||
{
|
||||
return *anycast(self.last_any(), $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Return the last element
|
||||
|
||||
@return "The last element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.last_any(&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 AnyList.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the length of the list.
|
||||
|
||||
@return "The number of elements in the list"
|
||||
*>
|
||||
fn usz AnyList.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Return an element in the list by value, assuming it is the given type.
|
||||
|
||||
@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)
|
||||
{
|
||||
return *anycast(self.entries[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 @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
<*
|
||||
Completely free and clear a list.
|
||||
*>
|
||||
fn void AnyList.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 AnyList.swap(&self, usz i, usz j)
|
||||
{
|
||||
any temp = self.entries[i];
|
||||
self.entries[i] = self.entries[j];
|
||||
self.entries[j] = temp;
|
||||
}
|
||||
|
||||
<*
|
||||
Print the list to a formatter.
|
||||
*>
|
||||
fn usz? AnyList.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
@@ -77,310 +438,10 @@ fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
|
||||
}
|
||||
}
|
||||
|
||||
fn String AnyList.to_new_string(&self, Allocator allocator = null) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
|
||||
}
|
||||
|
||||
|
||||
fn String AnyList.to_string(&self, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String AnyList.to_tstring(&self) => string::tformat("%s", *self);
|
||||
|
||||
<*
|
||||
Push an element on the list by cloning it.
|
||||
*>
|
||||
macro void AnyList.push(&self, element)
|
||||
{
|
||||
if (!self.allocator) self.allocator = allocator::heap();
|
||||
self.append_internal(allocator::clone(self.allocator, element));
|
||||
}
|
||||
Remove any elements matching the predicate.
|
||||
|
||||
fn void AnyList.append_internal(&self, any element) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
Free a retained element removed using *_retained.
|
||||
*>
|
||||
fn void AnyList.free_element(&self, any element) @inline
|
||||
{
|
||||
allocator::free(self.allocator, element.ptr);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@return! CastResult.TYPE_MISMATCH, IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.pop(&self, $Type)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return *anycast(self.entries[--self.size], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the last value and allocate the copy using the given allocator.
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any! AnyList.copy_pop(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return allocator::clone_any(allocator, self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the last value and allocate the copy using the given allocator.
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
@deprecated `use copy_pop`
|
||||
*>
|
||||
fn any! AnyList.new_pop(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return self.copy_pop(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the last value and allocate the copy using the temp allocator
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
@deprecated `use tcopy_pop`
|
||||
*>
|
||||
fn any! AnyList.temp_pop(&self) => self.copy_pop(allocator::temp());
|
||||
|
||||
<*
|
||||
Pop the last value and allocate the copy using the temp allocator
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any! AnyList.tcopy_pop(&self) => self.copy_pop(allocator::temp());
|
||||
|
||||
<*
|
||||
Pop the last value. It must later be released using list.free_element()
|
||||
@return! IteratorResult.NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any! AnyList.pop_retained(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
fn void AnyList.clear(&self)
|
||||
{
|
||||
for (usz i = 0; i < self.size; i++)
|
||||
{
|
||||
self.free_element(self.entries[i]);
|
||||
}
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Same as pop() but pops the first value instead.
|
||||
*>
|
||||
macro AnyList.pop_first(&self, $Type)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return *anycast(self.entries[0], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Same as pop_retained() but pops the first value instead.
|
||||
*>
|
||||
fn any! AnyList.pop_first_retained(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
<*
|
||||
Same as new_pop() but pops the first value instead.
|
||||
@deprecated `use copy_pop_first`
|
||||
*>
|
||||
fn any! AnyList.new_pop_first(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return self.copy_pop_first(allocator) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Same as new_pop() but pops the first value instead.
|
||||
*>
|
||||
fn any! AnyList.copy_pop_first(&self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
defer self.remove_at(0);
|
||||
return allocator::clone_any(allocator, self.entries[0]);
|
||||
}
|
||||
|
||||
<*
|
||||
Same as temp_pop() but pops the first value instead.
|
||||
*>
|
||||
fn any! AnyList.tcopy_pop_first(&self) => self.copy_pop_first(allocator::temp());
|
||||
|
||||
<*
|
||||
Same as temp_pop() but pops the first value instead.
|
||||
@deprecated `use tcopy_pop_first`
|
||||
*>
|
||||
fn any! AnyList.temp_pop_first(&self) => self.new_pop_first(allocator::temp());
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void AnyList.remove_at(&self, usz index)
|
||||
{
|
||||
if (!--self.size || index == self.size) return;
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
}
|
||||
|
||||
fn void AnyList.add_all(&self, AnyList* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
self.reserve(other_list.size);
|
||||
foreach (value : other_list)
|
||||
{
|
||||
self.entries[self.size++] = allocator::clone_any(self.allocator, value);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the elements in a list.
|
||||
*>
|
||||
fn void AnyList.reverse(&self)
|
||||
{
|
||||
if (self.size < 2) return;
|
||||
usz half = self.size / 2U;
|
||||
usz end = self.size - 1;
|
||||
for (usz i = 0; i < half; i++)
|
||||
{
|
||||
self.swap(i, end - i);
|
||||
}
|
||||
}
|
||||
|
||||
fn any[] AnyList.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Push an element to the front of the list.
|
||||
*>
|
||||
macro void AnyList.push_front(&self, type)
|
||||
{
|
||||
self.insert_at(0, type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
macro void AnyList.insert_at(&self, usz index, type) @local
|
||||
{
|
||||
any value = allocator::copy(self.allocator, type);
|
||||
self.insert_at_internal(self, index, value);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void AnyList.insert_at_internal(&self, usz index, any value) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
for (usz i = self.size; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.size++;
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn void AnyList.remove_last(&self)
|
||||
{
|
||||
self.free_element(self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn void AnyList.remove_first(&self)
|
||||
{
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
macro AnyList.first(&self, $Type)
|
||||
{
|
||||
return *anycast(self.first_any(), $Type);
|
||||
}
|
||||
|
||||
fn any! AnyList.first_any(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[0] : IteratorResult.NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
macro AnyList.last(&self, $Type)
|
||||
{
|
||||
return *anycast(self.last_any(), $Type);
|
||||
}
|
||||
|
||||
fn any! AnyList.last_any(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[self.size - 1] : IteratorResult.NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
fn bool AnyList.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
fn usz AnyList.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size "Index out of range"
|
||||
*>
|
||||
macro AnyList.get(&self, usz index, $Type)
|
||||
{
|
||||
return *anycast(self.entries[index], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size "Index out of range"
|
||||
*>
|
||||
fn any AnyList.get_any(&self, usz index) @inline
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn void AnyList.free(&self)
|
||||
{
|
||||
if (!self.allocator) return;
|
||||
self.clear();
|
||||
allocator::free(self.allocator, self.entries);
|
||||
self.capacity = 0;
|
||||
self.entries = null;
|
||||
}
|
||||
|
||||
fn void AnyList.swap(&self, usz i, usz j)
|
||||
{
|
||||
any temp = self.entries[i];
|
||||
self.entries[i] = self.entries[j];
|
||||
self.entries[j] = temp;
|
||||
}
|
||||
|
||||
<*
|
||||
@param filter "The function to determine if it should be removed or not"
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.remove_if(&self, AnyPredicate filter)
|
||||
@@ -389,7 +450,9 @@ fn usz AnyList.remove_if(&self, AnyPredicate filter)
|
||||
}
|
||||
|
||||
<*
|
||||
@param selection "The function to determine if it should be kept or not"
|
||||
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 AnyList.retain_if(&self, AnyPredicate selection)
|
||||
@@ -397,40 +460,95 @@ 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;
|
||||
}
|
||||
<*
|
||||
Remove any 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 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)
|
||||
<*
|
||||
Retain any 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 AnyList.retain_using_test(&self, AnyTest selection, any context)
|
||||
{
|
||||
return self._remove_using_test(filter, true, context);
|
||||
return self._remove_using_test(selection, true, context);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Reserve memory so that at least the `min_capacity` exists.
|
||||
|
||||
@param min_capacity : "The min capacity to hold"
|
||||
*>
|
||||
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;
|
||||
}
|
||||
|
||||
<*
|
||||
Set the element at any index.
|
||||
|
||||
@param index : "The index where to set the value."
|
||||
@param value : "The value to set"
|
||||
@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);
|
||||
}
|
||||
|
||||
// -- private
|
||||
|
||||
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 void AnyList._append(&self, any element) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void AnyList._insert_at(&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;
|
||||
}
|
||||
|
||||
macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @local
|
||||
@@ -459,45 +577,28 @@ macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Reserve at least min_capacity
|
||||
*>
|
||||
fn void AnyList.reserve(&self, usz min_capacity)
|
||||
macro usz AnyList._remove_if(&self, AnyPredicate filter, bool $invert) @local
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = allocator::heap();
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
self.entries = allocator::realloc(self.allocator, self.entries, any.sizeof * min_capacity);
|
||||
self.capacity = min_capacity;
|
||||
}
|
||||
|
||||
macro any AnyList.@item_at(&self, usz index) @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size "Index out of range"
|
||||
*>
|
||||
macro void AnyList.set(&self, usz index, value)
|
||||
{
|
||||
if (index == self.size)
|
||||
usz size = self.size;
|
||||
for (usz i = size, usz k = size; k > 0; k = i)
|
||||
{
|
||||
self.push(value);
|
||||
return;
|
||||
// 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
|
||||
}
|
||||
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);
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
def 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,11 @@ 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"
|
||||
*>
|
||||
fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
{
|
||||
@@ -59,7 +150,12 @@ fn usz BitSet.len(&self) @operator(len) @inline
|
||||
}
|
||||
|
||||
<*
|
||||
@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,12 +166,12 @@ 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;
|
||||
|
||||
def GrowableBitSetList = List(<Type>);
|
||||
alias GrowableBitSetList = List{Type};
|
||||
|
||||
struct GrowableBitSet
|
||||
{
|
||||
@@ -84,17 +180,17 @@ struct GrowableBitSet
|
||||
|
||||
<*
|
||||
@param initial_capacity
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
@param [&inout] allocator : "The allocator to use, defaults to the heap allocator"
|
||||
*>
|
||||
fn GrowableBitSet* GrowableBitSet.new_init(&self, usz initial_capacity = 1, Allocator allocator = allocator::heap())
|
||||
fn GrowableBitSet* GrowableBitSet.init(&self, Allocator allocator, usz initial_capacity = 1)
|
||||
{
|
||||
self.data.new_init(initial_capacity, allocator);
|
||||
self.data.init(allocator, initial_capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn GrowableBitSet* GrowableBitSet.temp_init(&self, usz initial_capacity = 1)
|
||||
fn GrowableBitSet* GrowableBitSet.tinit(&self, usz initial_capacity = 1)
|
||||
{
|
||||
return self.new_init(initial_capacity, allocator::temp()) @inline;
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.free(&self)
|
||||
@@ -117,15 +213,10 @@ fn void GrowableBitSet.set(&self, usz i)
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
usz current_len = self.data.len();
|
||||
if (q >= current_len)
|
||||
while (q >= current_len)
|
||||
{
|
||||
usz n = q + 1;
|
||||
self.data.reserve(n);
|
||||
if (n - 1 >= current_len)
|
||||
{
|
||||
self.data.entries[current_len .. (n - 1)] = 0;
|
||||
}
|
||||
self.data.size = n;
|
||||
self.data.push(0);
|
||||
current_len++;
|
||||
}
|
||||
self.data.set(q, self.data[q] | (1 << r));
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
<*
|
||||
@require MAX_SIZE >= 1 `The size must be at least 1 element big.`
|
||||
@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;
|
||||
|
||||
def ElementPredicate = fn bool(Type *type);
|
||||
def ElementTest = fn bool(Type *type, any context);
|
||||
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;
|
||||
@@ -19,7 +19,7 @@ struct ElasticArray (Printable)
|
||||
Type[MAX_SIZE] entries;
|
||||
}
|
||||
|
||||
fn usz! ElasticArray.to_format(&self, Formatter* formatter) @dynamic
|
||||
fn usz? ElasticArray.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
@@ -39,38 +39,28 @@ fn usz! ElasticArray.to_format(&self, Formatter* formatter) @dynamic
|
||||
}
|
||||
}
|
||||
|
||||
fn String ElasticArray.to_string(&self, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String ElasticArray.to_new_string(&self, Allocator allocator = nul) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
|
||||
}
|
||||
|
||||
fn String ElasticArray.to_tstring(&self)
|
||||
{
|
||||
return string::tformat("%s", *self);
|
||||
}
|
||||
|
||||
fn void! ElasticArray.push_try(&self, Type element) @inline
|
||||
fn void? ElasticArray.push_try(&self, Type element) @inline
|
||||
{
|
||||
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `Tried to exceed the max size`
|
||||
@require self.size < MAX_SIZE : `Tried to exceed the max size`
|
||||
*>
|
||||
fn void ElasticArray.push(&self, Type element) @inline
|
||||
{
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
fn Type! ElasticArray.pop(&self)
|
||||
fn Type? ElasticArray.pop(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
@@ -82,9 +72,9 @@ fn void ElasticArray.clear(&self)
|
||||
<*
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn Type! ElasticArray.pop_first(&self)
|
||||
fn Type? ElasticArray.pop_first(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
@@ -146,7 +136,7 @@ fn usz ElasticArray.add_array_to_limit(&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.`
|
||||
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void ElasticArray.add_array(&self, Type[] array)
|
||||
@@ -160,28 +150,12 @@ fn void ElasticArray.add_array(&self, Type[] array)
|
||||
|
||||
|
||||
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
fn Type[] ElasticArray.to_new_aligned_array(&self)
|
||||
{
|
||||
return list_common::list_to_new_aligned_array(Type, self, allocator::heap());
|
||||
}
|
||||
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
fn Type[] ElasticArray.to_aligned_array(&self, Allocator allocator)
|
||||
{
|
||||
return list_common::list_to_new_aligned_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
@require !type_is_overaligned() : "This function is not available on overaligned types"
|
||||
*>
|
||||
macro Type[] ElasticArray.to_new_array(&self)
|
||||
{
|
||||
return list_common::list_to_array(Type, self, allocator::heap());
|
||||
return list_common::list_to_aligned_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -189,15 +163,15 @@ macro Type[] ElasticArray.to_new_array(&self)
|
||||
*>
|
||||
macro Type[] ElasticArray.to_array(&self, Allocator allocator)
|
||||
{
|
||||
return list_common::list_to_new_array(Type, self, allocator);
|
||||
return list_common::list_to_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
fn Type[] ElasticArray.to_tarray(&self)
|
||||
{
|
||||
$if type_is_overaligned():
|
||||
return self.to_aligned_array(allocator::temp());
|
||||
return self.to_aligned_array(tmem);
|
||||
$else
|
||||
return self.to_array(allocator::temp());
|
||||
return self.to_array(tmem);
|
||||
$endif;
|
||||
}
|
||||
|
||||
@@ -215,7 +189,7 @@ fn Type[] ElasticArray.array_view(&self)
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `List would exceed max size`
|
||||
@require self.size < MAX_SIZE : `List would exceed max size`
|
||||
*>
|
||||
fn void ElasticArray.push_front(&self, Type type) @inline
|
||||
{
|
||||
@@ -223,9 +197,9 @@ fn void ElasticArray.push_front(&self, Type type) @inline
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `List would exceed max size`
|
||||
@require self.size < MAX_SIZE : `List would exceed max size`
|
||||
*>
|
||||
fn void! ElasticArray.push_front_try(&self, Type type) @inline
|
||||
fn void? ElasticArray.push_front_try(&self, Type type) @inline
|
||||
{
|
||||
return self.insert_at_try(0, type);
|
||||
}
|
||||
@@ -233,14 +207,14 @@ fn void! ElasticArray.push_front_try(&self, Type type) @inline
|
||||
<*
|
||||
@require index <= self.size
|
||||
*>
|
||||
fn void! ElasticArray.insert_at_try(&self, usz index, Type value)
|
||||
fn void? ElasticArray.insert_at_try(&self, usz index, Type value)
|
||||
{
|
||||
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
|
||||
self.insert_at(index, value);
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.size < MAX_SIZE `List would exceed max size`
|
||||
@require self.size < MAX_SIZE : `List would exceed max size`
|
||||
@require index <= self.size
|
||||
*>
|
||||
fn void ElasticArray.insert_at(&self, usz index, Type type)
|
||||
@@ -261,27 +235,27 @@ fn void ElasticArray.set_at(&self, usz index, Type type)
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
fn void! ElasticArray.remove_last(&self) @maydiscard
|
||||
fn void? ElasticArray.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
self.size--;
|
||||
}
|
||||
|
||||
fn void! ElasticArray.remove_first(&self) @maydiscard
|
||||
fn void? ElasticArray.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type! ElasticArray.first(&self)
|
||||
fn Type? ElasticArray.first(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
fn Type! ElasticArray.last(&self)
|
||||
fn Type? ElasticArray.last(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
@@ -311,7 +285,7 @@ fn void ElasticArray.swap(&self, usz i, usz j)
|
||||
}
|
||||
|
||||
<*
|
||||
@param filter "The function to determine if it should be removed or not"
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz ElasticArray.remove_if(&self, ElementPredicate filter)
|
||||
@@ -320,7 +294,7 @@ fn usz ElasticArray.remove_if(&self, ElementPredicate filter)
|
||||
}
|
||||
|
||||
<*
|
||||
@param selection "The function to determine if it should be kept or not"
|
||||
@param selection : "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz ElasticArray.retain_if(&self, ElementPredicate selection)
|
||||
@@ -356,22 +330,22 @@ fn void ElasticArray.set(&self, usz index, Type value) @operator([]=)
|
||||
|
||||
|
||||
// Functions for equatable types
|
||||
fn usz! ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
fn usz? ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
fn usz! ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach_r (i, v : self)
|
||||
{
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -387,8 +361,8 @@ fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUAT
|
||||
<*
|
||||
Check for presence of a value in a list.
|
||||
|
||||
@param [&in] self "the list to find elements in"
|
||||
@param value "The value to search for"
|
||||
@param [&in] self : "the list to find elements in"
|
||||
@param value : "The value to search for"
|
||||
@return "True if the value is found, false otherwise"
|
||||
*>
|
||||
fn bool ElasticArray.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -401,8 +375,8 @@ fn bool ElasticArray.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@param [&inout] self : "The list to remove elements from"
|
||||
@param value : "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool ElasticArray.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -411,8 +385,8 @@ fn bool ElasticArray.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABL
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@param [&inout] self : "The list to remove elements from"
|
||||
@param value : "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool ElasticArray.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -421,8 +395,8 @@ fn bool ElasticArray.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATAB
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@param [&inout] self : "The list to remove elements from"
|
||||
@param value : "The value to remove"
|
||||
@return "the number of deleted elements."
|
||||
*>
|
||||
fn usz ElasticArray.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<*
|
||||
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an 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)
|
||||
@@ -16,7 +17,7 @@ fn void EnumMap.init(&self, ValueType init_value)
|
||||
}
|
||||
}
|
||||
|
||||
fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
|
||||
fn usz? EnumMap.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
usz n = formatter.print("{ ")!;
|
||||
foreach (i, &value : self.values)
|
||||
@@ -28,21 +29,6 @@ fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
|
||||
return n;
|
||||
}
|
||||
|
||||
fn String EnumMap.to_string(&self, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String EnumMap.to_new_string(&self, Allocator allocator = null) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
|
||||
}
|
||||
|
||||
fn String EnumMap.to_tstring(&self) @dynamic
|
||||
{
|
||||
return string::tformat("%s", *self);
|
||||
}
|
||||
|
||||
<*
|
||||
@return "The total size of this map, which is the same as the number of enum values"
|
||||
@pure
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
<*
|
||||
@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;
|
||||
|
||||
def EnumSetType = $typefrom(private::type_for_enum_elements(Enum.elements)) @private;
|
||||
const ENUM_COUNT @private = Enum.values.len;
|
||||
alias EnumSetType @private = $typefrom(type_for_enum_elements(ENUM_COUNT));
|
||||
|
||||
const IS_CHAR_ARRAY = Enum.elements > 128;
|
||||
distinct EnumSet (Printable) = EnumSetType;
|
||||
const IS_CHAR_ARRAY = ENUM_COUNT > 128;
|
||||
typedef EnumSet (Printable) = EnumSetType;
|
||||
|
||||
fn void EnumSet.add(&self, Enum v)
|
||||
{
|
||||
@@ -126,7 +127,7 @@ fn EnumSet EnumSet.xor_of(&self, EnumSet s)
|
||||
$endif
|
||||
}
|
||||
|
||||
fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
|
||||
fn usz? EnumSet.to_format(&set, Formatter* formatter) @dynamic
|
||||
{
|
||||
usz n = formatter.print("[")!;
|
||||
bool found;
|
||||
@@ -141,26 +142,9 @@ fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
|
||||
return n;
|
||||
}
|
||||
|
||||
fn String EnumSet.to_new_string(&set, Allocator allocator = allocator::heap()) @dynamic
|
||||
macro typeid type_for_enum_elements(usz $elements) @local
|
||||
{
|
||||
return string::format("%s", *set, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String EnumSet.to_string(&set, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("%s", *set, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String EnumSet.to_tstring(&set) @dynamic
|
||||
{
|
||||
return string::tformat("%s", *set);
|
||||
}
|
||||
|
||||
module std::collections::enumset::private;
|
||||
|
||||
macro typeid type_for_enum_elements(usz $elements)
|
||||
{
|
||||
$switch
|
||||
$switch:
|
||||
$case ($elements > 128):
|
||||
return char[($elements + 7) / 8].typeid;
|
||||
$case ($elements > 64):
|
||||
|
||||
@@ -2,12 +2,31 @@
|
||||
// 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`
|
||||
@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;
|
||||
|
||||
struct HashMap
|
||||
const uint DEFAULT_INITIAL_CAPACITY = 16;
|
||||
const uint MAXIMUM_CAPACITY = 1u << 31;
|
||||
const float DEFAULT_LOAD_FACTOR = 0.75;
|
||||
const VALUE_IS_EQUATABLE = Value.is_eq;
|
||||
const bool COPY_KEYS = types::implements_copy(Key);
|
||||
|
||||
const Allocator MAP_HEAP_ALLOCATOR = (Allocator)&dummy;
|
||||
|
||||
const HashMap ONHEAP = { .allocator = MAP_HEAP_ALLOCATOR };
|
||||
|
||||
struct Entry
|
||||
{
|
||||
uint hash;
|
||||
Key key;
|
||||
Value value;
|
||||
Entry* next;
|
||||
}
|
||||
|
||||
struct HashMap (Printable)
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator allocator;
|
||||
@@ -16,24 +35,13 @@ struct HashMap
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.new_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = null)
|
||||
{
|
||||
return self.init(allocator ?: allocator::heap(), capacity, load_factor);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
@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 HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
@@ -46,47 +54,59 @@ fn HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INI
|
||||
}
|
||||
|
||||
<*
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
@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 HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
fn HashMap* HashMap.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
return self.init(allocator::temp(), capacity, load_factor) @inline;
|
||||
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.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
@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 HashMap* HashMap.new_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
macro HashMap* HashMap.init_with_key_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.new_init(capacity, load_factor, allocator);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
self.set($vaarg[$i], $vaarg[$i+1]);
|
||||
self.init(allocator, capacity, load_factor);
|
||||
$for var $i = 0; $i < $vacount; $i += 2:
|
||||
self.set($vaarg[$i], $vaarg[$i + 1]);
|
||||
$endfor
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys "The keys for the HashMap entries"
|
||||
@param [in] values "The values for the HashMap entries"
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require keys.len == values.len "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
@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"
|
||||
*>
|
||||
fn HashMap* HashMap.new_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
macro HashMap* HashMap.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 HashMap entries"
|
||||
@param [in] values : "The values for the HashMap entries"
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@require keys.len == values.len : "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 : "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 : "The load factor must be higher than 0"
|
||||
@require !self.is_initialized() : "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashMap* HashMap.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.new_init(capacity, load_factor, allocator);
|
||||
self.init(allocator, capacity, load_factor);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
self.set(keys[i], values[i]);
|
||||
@@ -94,79 +114,51 @@ fn HashMap* HashMap.new_init_from_keys_and_values(&self, Key[] keys, Value[] val
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro HashMap* HashMap.temp_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
self.temp_init(capacity, load_factor);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
self.set($vaarg[$i], $vaarg[$i+1]);
|
||||
$endfor
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys "The keys for the HashMap entries"
|
||||
@param [in] values "The values for the HashMap entries"
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require keys.len == values.len "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require !self.allocator "Map was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
@param [in] keys : "The keys for the HashMap entries"
|
||||
@param [in] values : "The values for the HashMap 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 HashMap* HashMap.temp_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
fn HashMap* HashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
assert(keys.len == values.len);
|
||||
self.temp_init(capacity, load_factor);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
self.set(keys[i], values[i]);
|
||||
}
|
||||
return self;
|
||||
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"
|
||||
@param [&in] map : "The hash map we are testing"
|
||||
@return "Returns true if it has been initialized, false otherwise"
|
||||
*>
|
||||
fn bool HashMap.is_initialized(&map)
|
||||
{
|
||||
return (bool)map.allocator;
|
||||
return map.allocator && map.allocator.ptr != &dummy;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
@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 HashMap* HashMap.new_init_from_map(&self, HashMap* other_map)
|
||||
fn HashMap* HashMap.init_from_map(&self, Allocator allocator, HashMap* other_map)
|
||||
{
|
||||
return self.init_from_map(other_map, allocator::heap()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn HashMap* HashMap.init_from_map(&self, HashMap* other_map, Allocator allocator)
|
||||
{
|
||||
self.new_init(other_map.table.len, other_map.load_factor, allocator);
|
||||
self.init(allocator, other_map.table.len, other_map.load_factor);
|
||||
self.put_all_for_create(other_map);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
@param [&in] other_map : "The map to copy from."
|
||||
@require !map.is_initialized() : "Map was already initialized"
|
||||
*>
|
||||
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map)
|
||||
fn HashMap* HashMap.tinit_from_map(&map, HashMap* other_map)
|
||||
{
|
||||
return map.init_from_map(other_map, allocator::temp()) @inline;
|
||||
return map.init_from_map(tmem, other_map) @inline;
|
||||
}
|
||||
|
||||
fn bool HashMap.is_empty(&map) @inline
|
||||
@@ -179,31 +171,31 @@ fn usz HashMap.len(&map) @inline
|
||||
return map.count;
|
||||
}
|
||||
|
||||
fn Value*! HashMap.get_ref(&map, Key key)
|
||||
fn Value*? HashMap.get_ref(&map, Key key)
|
||||
{
|
||||
if (!map.count) return SearchResult.MISSING?;
|
||||
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 SearchResult.MISSING?;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
fn Entry*! HashMap.get_entry(&map, Key key)
|
||||
fn Entry*? HashMap.get_entry(&map, Key key)
|
||||
{
|
||||
if (!map.count) return SearchResult.MISSING?;
|
||||
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 SearchResult.MISSING?;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value or update and
|
||||
@require $assignable(#expr, Value)
|
||||
@require @assignable_to(#expr, Value)
|
||||
*>
|
||||
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
{
|
||||
@@ -224,7 +216,7 @@ macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
return val;
|
||||
}
|
||||
|
||||
fn Value! HashMap.get(&map, Key key) @operator([])
|
||||
fn Value? HashMap.get(&map, Key key) @operator([])
|
||||
{
|
||||
return *map.get_ref(key) @inline;
|
||||
}
|
||||
@@ -237,10 +229,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.
|
||||
if (!map.allocator)
|
||||
{
|
||||
map.new_init();
|
||||
}
|
||||
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)
|
||||
@@ -255,9 +252,9 @@ fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void! HashMap.remove(&map, Key key) @maydiscard
|
||||
fn void? HashMap.remove(&map, Key key) @maydiscard
|
||||
{
|
||||
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
|
||||
if (!map.remove_entry_for_key(key)) return NOT_FOUND?;
|
||||
}
|
||||
|
||||
fn void HashMap.clear(&map)
|
||||
@@ -282,37 +279,24 @@ fn void HashMap.clear(&map)
|
||||
|
||||
fn void HashMap.free(&map)
|
||||
{
|
||||
if (!map.allocator) return;
|
||||
if (!map.is_initialized()) return;
|
||||
map.clear();
|
||||
map.free_internal(map.table.ptr);
|
||||
map.table = {};
|
||||
}
|
||||
|
||||
fn Key[] HashMap.tcopy_keys(&map)
|
||||
fn Key[] HashMap.tkeys(&self)
|
||||
{
|
||||
return map.copy_keys(allocator::temp()) @inline;
|
||||
return self.keys(tmem) @inline;
|
||||
}
|
||||
|
||||
fn Key[] HashMap.key_tlist(&map) @deprecated("Use 'tcopy_keys'")
|
||||
fn Key[] HashMap.keys(&self, Allocator allocator)
|
||||
{
|
||||
return map.copy_keys(allocator::temp()) @inline;
|
||||
}
|
||||
if (!self.count) return {};
|
||||
|
||||
<*
|
||||
@deprecated "use copy_keys"
|
||||
*>
|
||||
fn Key[] HashMap.key_new_list(&map, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return map.copy_keys(allocator) @inline;
|
||||
}
|
||||
|
||||
fn Key[] HashMap.copy_keys(&map, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!map.count) return {};
|
||||
|
||||
Key[] list = allocator::alloc_array(allocator, Key, map.count);
|
||||
Key[] list = allocator::alloc_array(allocator, Key, self.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.table)
|
||||
foreach (Entry* entry : self.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@@ -329,53 +313,36 @@ fn Key[] HashMap.copy_keys(&map, Allocator allocator = allocator::heap())
|
||||
|
||||
macro HashMap.@each(map; @body(key, value))
|
||||
{
|
||||
map.@each_entry(; Entry* entry) {
|
||||
map.@each_entry(; Entry* entry)
|
||||
{
|
||||
@body(entry.key, entry.value);
|
||||
};
|
||||
}
|
||||
|
||||
macro HashMap.@each_entry(map; @body(entry))
|
||||
{
|
||||
if (map.count)
|
||||
if (!map.count) return;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
foreach (Entry* entry : map.table)
|
||||
while (entry)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@body(entry);
|
||||
entry = entry.next;
|
||||
}
|
||||
@body(entry);
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@deprecated `use tcopy_values`
|
||||
*>
|
||||
fn Value[] HashMap.value_tlist(&map)
|
||||
fn Value[] HashMap.tvalues(&map)
|
||||
{
|
||||
return map.copy_values(allocator::temp()) @inline;
|
||||
return map.values(tmem) @inline;
|
||||
}
|
||||
|
||||
fn Value[] HashMap.tcopy_values(&map)
|
||||
fn Value[] HashMap.values(&self, Allocator allocator)
|
||||
{
|
||||
return map.copy_values(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@deprecated `use copy_values`
|
||||
*>
|
||||
fn Value[] HashMap.value_new_list(&map, Allocator allocator = allocator::heap())
|
||||
{
|
||||
return map.copy_values(allocator);
|
||||
}
|
||||
|
||||
fn Value[] HashMap.copy_values(&map, Allocator allocator = allocator::heap())
|
||||
{
|
||||
if (!map.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, map.count);
|
||||
if (!self.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, self.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.table)
|
||||
foreach (Entry* entry : self.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@@ -446,6 +413,18 @@ fn void HashMap.resize(&map, uint new_capacity) @private
|
||||
map.threshold = (uint)(new_capacity * map.load_factor);
|
||||
}
|
||||
|
||||
fn usz? HashMap.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len;
|
||||
len += f.print("{ ")!;
|
||||
self.@each_entry(; Entry* entry)
|
||||
{
|
||||
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
|
||||
{
|
||||
Entry*[] src = map.table;
|
||||
@@ -556,8 +535,8 @@ struct HashMapIterator
|
||||
Entry* current_entry;
|
||||
}
|
||||
|
||||
distinct HashMapValueIterator = HashMapIterator;
|
||||
distinct HashMapKeyIterator = HashMapIterator;
|
||||
typedef HashMapValueIterator = HashMapIterator;
|
||||
typedef HashMapKeyIterator = HashMapIterator;
|
||||
|
||||
|
||||
<*
|
||||
@@ -598,3 +577,16 @@ fn Key HashMapKeyIterator.get(&self, usz idx) @operator([])
|
||||
fn usz HashMapValueIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz HashMapKeyIterator.len(self) @operator(len) => self.map.count;
|
||||
fn usz HashMapIterator.len(self) @operator(len) => self.map.count;
|
||||
|
||||
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
|
||||
{
|
||||
return hash & (capacity - 1);
|
||||
}
|
||||
|
||||
int dummy @local;
|
||||
636
lib/std/collections/hashset.c3
Normal file
636
lib/std/collections/hashset.c3
Normal file
@@ -0,0 +1,636 @@
|
||||
<*
|
||||
@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;
|
||||
usz count; // Number of elements
|
||||
usz threshold; // Resize limit
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
fn int HashSet.len(&self) @operator(len) => (int) 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);
|
||||
self.put_all_for_create(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;
|
||||
}
|
||||
set.add_entry(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 (!set.remove_entry_for_value(value)) return NOT_FOUND?;
|
||||
}
|
||||
|
||||
fn usz HashSet.remove_all(&set, Value[] values)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : values)
|
||||
{
|
||||
if (set.remove_entry_for_value(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 (set.remove_entry_for_value(val)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
Free all memory allocated by the hash set.
|
||||
*>
|
||||
fn void HashSet.free(&set)
|
||||
{
|
||||
if (!set.is_initialized()) return;
|
||||
set.clear();
|
||||
set.free_internal(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;
|
||||
set.free_entry(to_delete);
|
||||
}
|
||||
|
||||
set.free_entry(entry);
|
||||
*entry_ref = null;
|
||||
}
|
||||
set.count = 0;
|
||||
}
|
||||
|
||||
fn void HashSet.reserve(&set, usz capacity)
|
||||
{
|
||||
if (capacity > set.threshold)
|
||||
{
|
||||
set.resize(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 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(&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)
|
||||
{
|
||||
set.resize(set.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashSet.resize(&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);
|
||||
self.transfer(new_table);
|
||||
self.table = new_table;
|
||||
self.free_internal(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(&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(&set, HashSet* other_set) @private
|
||||
{
|
||||
if (!other_set.count) return;
|
||||
foreach (Entry *e : other_set.table)
|
||||
{
|
||||
while (e)
|
||||
{
|
||||
set.put_for_create(e.value);
|
||||
e = e.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashSet.put_for_create(&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;
|
||||
}
|
||||
}
|
||||
set.create_entry(hash, value, i);
|
||||
}
|
||||
|
||||
fn void HashSet.free_internal(&self, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(self.allocator, ptr);
|
||||
}
|
||||
|
||||
fn void HashSet.create_entry(&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(&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;
|
||||
}
|
||||
set.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void HashSet.free_entry(&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;
|
||||
645
lib/std/collections/linked_hashmap.c3
Normal file
645
lib/std/collections/linked_hashmap.c3
Normal file
@@ -0,0 +1,645 @@
|
||||
// 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;
|
||||
LinkedEntry* next; // For bucket chain
|
||||
LinkedEntry* before; // Previous in insertion order
|
||||
LinkedEntry* after; // Next in insertion order
|
||||
}
|
||||
|
||||
struct LinkedHashMap (Printable)
|
||||
{
|
||||
LinkedEntry*[] table;
|
||||
Allocator allocator;
|
||||
usz count;
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
LinkedEntry* head; // First inserted LinkedEntry
|
||||
LinkedEntry* tail; // Last inserted LinkedEntry
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@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);
|
||||
self.put_all_for_create(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 update and
|
||||
@require @assignable_to(#expr, Value)
|
||||
*>
|
||||
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;
|
||||
}
|
||||
}
|
||||
map.add_entry(hash, key, value, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void? LinkedHashMap.remove(&map, Key key) @maydiscard
|
||||
{
|
||||
if (!map.remove_entry_for_key(key)) return NOT_FOUND?;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.clear(&map)
|
||||
{
|
||||
if (!map.count) return;
|
||||
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
LinkedEntry* next = entry.after;
|
||||
map.free_entry(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();
|
||||
map.free_internal(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(&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)
|
||||
{
|
||||
map.resize(map.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.resize(&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;
|
||||
}
|
||||
}
|
||||
|
||||
map.free_internal(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(&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(&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(&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;
|
||||
}
|
||||
}
|
||||
map.create_entry(hash, key, value, i);
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.free_internal(&map, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(map.allocator, ptr);
|
||||
}
|
||||
|
||||
fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
if (!map.count) return false;
|
||||
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
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--;
|
||||
map.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = e.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void LinkedHashMap.create_entry(&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(&self, LinkedEntry *entry) @local
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
allocator::free(self.allocator, entry.key);
|
||||
$endif
|
||||
self.free_internal(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;
|
||||
723
lib/std/collections/linked_hashset.c3
Normal file
723
lib/std/collections/linked_hashset.c3
Normal file
@@ -0,0 +1,723 @@
|
||||
<*
|
||||
@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;
|
||||
LinkedEntry* next; // For bucket chain
|
||||
LinkedEntry* before; // Previous in insertion order
|
||||
LinkedEntry* after; // Next in insertion order
|
||||
}
|
||||
|
||||
struct LinkedHashSet (Printable)
|
||||
{
|
||||
LinkedEntry*[] table;
|
||||
Allocator allocator;
|
||||
usz count; // Number of elements
|
||||
usz threshold; // Resize limit
|
||||
float load_factor;
|
||||
LinkedEntry* head; // First inserted LinkedEntry
|
||||
LinkedEntry* tail; // Last inserted LinkedEntry
|
||||
}
|
||||
|
||||
fn int LinkedHashSet.len(&self) @operator(len) => (int) 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
|
||||
{
|
||||
self.put_for_create(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;
|
||||
}
|
||||
set.add_entry(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 (!set.remove_entry_for_value(value)) return NOT_FOUND?;
|
||||
}
|
||||
|
||||
fn usz LinkedHashSet.remove_all(&set, Value[] values)
|
||||
{
|
||||
usz total;
|
||||
foreach (v : values)
|
||||
{
|
||||
if (set.remove_entry_for_value(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 (set.remove_entry_for_value(val)) total++;
|
||||
};
|
||||
return total;
|
||||
}
|
||||
|
||||
<*
|
||||
Free all memory allocated by the hash set.
|
||||
*>
|
||||
fn void LinkedHashSet.free(&set)
|
||||
{
|
||||
if (!set.is_initialized()) return;
|
||||
set.clear();
|
||||
set.free_internal(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;
|
||||
set.free_entry(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)
|
||||
{
|
||||
set.resize(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(&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)
|
||||
{
|
||||
set.resize(set.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.resize(&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;
|
||||
}
|
||||
}
|
||||
|
||||
set.free_internal(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 LinkedHashSet.transfer(&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(&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;
|
||||
}
|
||||
}
|
||||
set.create_entry(hash, value, i);
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.free_internal(&set, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(set.allocator, ptr);
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.create_entry(&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(&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--;
|
||||
set.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = e.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void LinkedHashSet.free_entry(&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,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::linkedlist(<Type>);
|
||||
module std::collections::linkedlist{Type};
|
||||
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
|
||||
@@ -21,7 +21,7 @@ struct LinkedList
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
@param [&inout] allocator : "The allocator to use, defaults to the heap allocator"
|
||||
@return "the initialized list"
|
||||
*>
|
||||
fn LinkedList* LinkedList.init(&self, Allocator allocator)
|
||||
@@ -30,22 +30,15 @@ fn LinkedList* LinkedList.init(&self, Allocator allocator)
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "the initialized list"
|
||||
*>
|
||||
fn LinkedList* LinkedList.new_init(&self)
|
||||
fn LinkedList* LinkedList.tinit(&self)
|
||||
{
|
||||
return self.init(allocator::heap()) @inline;
|
||||
return self.init(tmem) @inline;
|
||||
}
|
||||
|
||||
|
||||
fn LinkedList* LinkedList.temp_init(&self)
|
||||
{
|
||||
return self.init(allocator::temp()) @inline;
|
||||
}
|
||||
fn bool LinkedList.is_initialized(&self) @inline => self.allocator != null;
|
||||
|
||||
<*
|
||||
@require self.allocator
|
||||
@require self.is_initialized()
|
||||
*>
|
||||
macro void LinkedList.free_node(&self, Node* node) @private
|
||||
{
|
||||
@@ -54,7 +47,7 @@ macro void LinkedList.free_node(&self, Node* node) @private
|
||||
|
||||
macro Node* LinkedList.alloc_node(&self) @private
|
||||
{
|
||||
if (!self.allocator) self.allocator = allocator::heap();
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
return allocator::alloc(self.allocator, Node);
|
||||
}
|
||||
|
||||
@@ -92,18 +85,18 @@ fn void LinkedList.push(&self, Type value)
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.peek(&self) => self.first() @inline;
|
||||
fn Type! LinkedList.peek_last(&self) => self.last() @inline;
|
||||
fn Type? LinkedList.peek(&self) => self.first() @inline;
|
||||
fn Type? LinkedList.peek_last(&self) => self.last() @inline;
|
||||
|
||||
fn Type! LinkedList.first(&self)
|
||||
fn Type? LinkedList.first(&self)
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.last(&self)
|
||||
fn Type? LinkedList.last(&self)
|
||||
{
|
||||
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self._last) return NO_MORE_ELEMENT?;
|
||||
return self._last.value;
|
||||
}
|
||||
|
||||
@@ -200,7 +193,7 @@ fn void LinkedList.link_before(&self, Node *succ, Type value) @private
|
||||
}
|
||||
|
||||
<*
|
||||
@require self._first
|
||||
@require self._first != null
|
||||
*>
|
||||
fn void LinkedList.unlink_first(&self) @private
|
||||
{
|
||||
@@ -238,9 +231,9 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
return start - self.size;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.pop(&self)
|
||||
fn Type? LinkedList.pop(&self)
|
||||
{
|
||||
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self._last) return NO_MORE_ELEMENT?;
|
||||
defer self.unlink_last();
|
||||
return self._last.value;
|
||||
}
|
||||
@@ -250,22 +243,22 @@ fn bool LinkedList.is_empty(&self)
|
||||
return !self._first;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.pop_front(&self)
|
||||
fn Type? LinkedList.pop_front(&self)
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
defer self.unlink_first();
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn void! LinkedList.remove_last(&self) @maydiscard
|
||||
fn void? LinkedList.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
self.unlink_last();
|
||||
}
|
||||
|
||||
fn void! LinkedList.remove_first(&self) @maydiscard
|
||||
fn void? LinkedList.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self._first) return NO_MORE_ELEMENT?;
|
||||
self.unlink_first();
|
||||
}
|
||||
|
||||
@@ -296,7 +289,7 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
return false;
|
||||
}
|
||||
<*
|
||||
@require self._last
|
||||
@require self._last != null
|
||||
*>
|
||||
fn void LinkedList.unlink_last(&self) @inline @private
|
||||
{
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
// 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;
|
||||
|
||||
def ElementPredicate = fn bool(Type *type);
|
||||
def ElementTest = fn bool(Type *type, any context);
|
||||
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;
|
||||
|
||||
const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
|
||||
|
||||
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR };
|
||||
|
||||
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct List (Printable)
|
||||
@@ -20,10 +24,10 @@ struct List (Printable)
|
||||
}
|
||||
|
||||
<*
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
@param initial_capacity : "The initial capacity to reserve"
|
||||
@param [&inout] allocator : "The allocator to use, defaults to the heap allocator"
|
||||
*>
|
||||
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
|
||||
fn List* List.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
@@ -33,25 +37,26 @@ fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = a
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity "The initial capacity to reserve"
|
||||
@param initial_capacity : "The initial capacity to reserve"
|
||||
*>
|
||||
fn List* List.temp_init(&self, usz initial_capacity = 16)
|
||||
fn List* List.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.new_init(initial_capacity, allocator::temp()) @inline;
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize a new list with an array.
|
||||
|
||||
@param [in] values `The values to initialize the list with.`
|
||||
@require self.size == 0 "The List must be empty"
|
||||
@param [in] values : `The values to initialize the list with.`
|
||||
@require self.size == 0 : "The List must be empty"
|
||||
*>
|
||||
fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = allocator::heap())
|
||||
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
|
||||
{
|
||||
self.new_init(values.len, allocator) @inline;
|
||||
self.init(allocator, values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
return self;
|
||||
}
|
||||
@@ -59,20 +64,20 @@ fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = al
|
||||
<*
|
||||
Initialize a temporary list with an array.
|
||||
|
||||
@param [in] values `The values to initialize the list with.`
|
||||
@require self.size == 0 "The List must be empty"
|
||||
@param [in] values : `The values to initialize the list with.`
|
||||
@require self.size == 0 : "The List must be empty"
|
||||
*>
|
||||
fn List* List.temp_init_with_array(&self, Type[] values)
|
||||
fn List* List.tinit_with_array(&self, Type[] values)
|
||||
{
|
||||
self.temp_init(values.len) @inline;
|
||||
self.tinit(values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.capacity == 0 "The List must not be allocated"
|
||||
@require !self.is_initialized() : "The List must not be allocated"
|
||||
*>
|
||||
fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allocator::heap())
|
||||
fn void List.init_wrapping_array(&self, Allocator allocator, Type[] types)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.capacity = types.len;
|
||||
@@ -80,7 +85,9 @@ fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allo
|
||||
self.set_size(types.len);
|
||||
}
|
||||
|
||||
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
|
||||
fn bool List.is_initialized(&self) @inline => self.allocator != null && self.allocator != (Allocator)&dummy;
|
||||
|
||||
fn usz? List.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.size)
|
||||
{
|
||||
@@ -100,25 +107,15 @@ fn usz! List.to_format(&self, Formatter* formatter) @dynamic
|
||||
}
|
||||
}
|
||||
|
||||
fn String List.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
|
||||
{
|
||||
return string::format("%s", *self, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String List.to_tstring(&self)
|
||||
{
|
||||
return string::tformat("%s", *self);
|
||||
}
|
||||
|
||||
fn void List.push(&self, Type element) @inline
|
||||
{
|
||||
self.reserve(1);
|
||||
self.entries[self.set_size(self.size + 1)] = element;
|
||||
}
|
||||
|
||||
fn Type! List.pop(&self)
|
||||
fn Type? List.pop(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.set_size(self.size - 1);
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
@@ -128,15 +125,15 @@ fn void List.clear(&self)
|
||||
self.set_size(0);
|
||||
}
|
||||
|
||||
fn Type! List.pop_first(&self)
|
||||
fn Type? List.pop_first(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size `Removed element out of bounds`
|
||||
@require index < self.size : `Removed element out of bounds`
|
||||
*>
|
||||
fn void List.remove_at(&self, usz index)
|
||||
{
|
||||
@@ -160,25 +157,25 @@ fn void List.add_all(&self, List* other_list)
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
fn Type[] List.to_new_aligned_array(&self, Allocator allocator = allocator::heap())
|
||||
fn Type[] List.to_aligned_array(&self, Allocator allocator)
|
||||
{
|
||||
return list_common::list_to_new_aligned_array(Type, self, allocator);
|
||||
return list_common::list_to_aligned_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
@require !type_is_overaligned() : "This function is not available on overaligned types"
|
||||
*>
|
||||
macro Type[] List.to_new_array(&self, Allocator allocator = allocator::heap())
|
||||
macro Type[] List.to_array(&self, Allocator allocator)
|
||||
{
|
||||
return list_common::list_to_new_array(Type, self, allocator);
|
||||
return list_common::list_to_array(Type, self, allocator);
|
||||
}
|
||||
|
||||
fn Type[] List.to_tarray(&self)
|
||||
{
|
||||
$if type_is_overaligned():
|
||||
return self.to_new_aligned_array(allocator::temp());
|
||||
return self.to_aligned_array(tmem);
|
||||
$else
|
||||
return self.to_new_array(allocator::temp());
|
||||
return self.to_array(tmem);
|
||||
$endif;
|
||||
}
|
||||
|
||||
@@ -215,16 +212,16 @@ fn void List.push_front(&self, Type type) @inline
|
||||
}
|
||||
|
||||
<*
|
||||
@require index <= self.size `Insert was out of bounds`
|
||||
@require index <= self.size : `Insert was out of bounds`
|
||||
*>
|
||||
fn void List.insert_at(&self, usz index, Type type)
|
||||
{
|
||||
self.reserve(1);
|
||||
for (usz i = self.size; i > index; i--)
|
||||
self.set_size(self.size + 1);
|
||||
for (isz i = self.size - 1; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.set_size(self.size + 1);
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
@@ -236,27 +233,27 @@ fn void List.set_at(&self, usz index, Type type)
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
fn void! List.remove_last(&self) @maydiscard
|
||||
fn void? List.remove_last(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
self.set_size(self.size - 1);
|
||||
}
|
||||
|
||||
fn void! List.remove_first(&self) @maydiscard
|
||||
fn void? List.remove_first(&self) @maydiscard
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type! List.first(&self)
|
||||
fn Type? List.first(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
fn Type! List.last(&self)
|
||||
fn Type? List.last(&self)
|
||||
{
|
||||
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
return self.entries[self.size - 1];
|
||||
}
|
||||
|
||||
@@ -276,7 +273,7 @@ fn usz List.len(&self) @operator(len) @inline
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
@require index < self.size : `Access out of bounds`
|
||||
*>
|
||||
fn Type List.get(&self, usz index) @inline
|
||||
{
|
||||
@@ -285,7 +282,7 @@ fn Type List.get(&self, usz index) @inline
|
||||
|
||||
fn void List.free(&self)
|
||||
{
|
||||
if (!self.allocator || !self.capacity) return;
|
||||
if (!self.allocator || self.allocator.ptr == &dummy || !self.capacity) return;
|
||||
|
||||
self.pre_free(); // Remove sanitizer annotation
|
||||
|
||||
@@ -300,7 +297,7 @@ fn void List.free(&self)
|
||||
}
|
||||
|
||||
<*
|
||||
@require i < self.size && j < self.size `Access out of bounds`
|
||||
@require i < self.size && j < self.size : `Access out of bounds`
|
||||
*>
|
||||
fn void List.swap(&self, usz i, usz j)
|
||||
{
|
||||
@@ -308,7 +305,7 @@ fn void List.swap(&self, usz i, usz j)
|
||||
}
|
||||
|
||||
<*
|
||||
@param filter "The function to determine if it should be removed or not"
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz List.remove_if(&self, ElementPredicate filter)
|
||||
@@ -317,7 +314,7 @@ fn usz List.remove_if(&self, ElementPredicate filter)
|
||||
}
|
||||
|
||||
<*
|
||||
@param selection "The function to determine if it should be kept or not"
|
||||
@param selection : "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz List.retain_if(&self, ElementPredicate selection)
|
||||
@@ -328,7 +325,8 @@ fn usz List.retain_if(&self, ElementPredicate selection)
|
||||
fn usz List.remove_using_test(&self, ElementTest filter, any context)
|
||||
{
|
||||
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_using_test(self, filter, false, context);
|
||||
@@ -349,7 +347,17 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = allocator::heap();
|
||||
|
||||
// Get a proper allocator
|
||||
switch (self.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
self.allocator = mem;
|
||||
case null:
|
||||
self.allocator = tmem;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
self.pre_free(); // Remove sanitizer annotation
|
||||
|
||||
@@ -365,7 +373,7 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
@require index < self.size : `Access out of bounds`
|
||||
*>
|
||||
macro Type List.@item_at(&self, usz index) @operator([])
|
||||
{
|
||||
@@ -373,7 +381,7 @@ macro Type List.@item_at(&self, usz index) @operator([])
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
@require index < self.size : `Access out of bounds`
|
||||
*>
|
||||
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
|
||||
{
|
||||
@@ -381,7 +389,7 @@ fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size `Access out of bounds`
|
||||
@require index < self.size : `Access out of bounds`
|
||||
*>
|
||||
fn void List.set(&self, usz index, Type value) @operator([]=)
|
||||
{
|
||||
@@ -402,10 +410,13 @@ fn void List.reserve(&self, usz added)
|
||||
fn void List._update_size_change(&self,usz old_size, usz new_size)
|
||||
{
|
||||
if (old_size == new_size) return;
|
||||
sanitizer::annotate_contiguous_container(self.entries,
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
if (self.allocator.ptr != &allocator::LIBC_ALLOCATOR) return;
|
||||
sanitizer::annotate_contiguous_container(self.entries,
|
||||
&self.entries[self.capacity],
|
||||
&self.entries[old_size],
|
||||
&self.entries[new_size]);
|
||||
$endif
|
||||
}
|
||||
<*
|
||||
@require new_size == 0 || self.capacity != 0
|
||||
@@ -425,7 +436,7 @@ macro void List.pre_free(&self) @private
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.capacity
|
||||
@require self.capacity > 0
|
||||
*>
|
||||
macro void List.post_alloc(&self) @private
|
||||
{
|
||||
@@ -435,22 +446,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 SearchResult.MISSING?;
|
||||
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 SearchResult.MISSING?;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -466,8 +477,8 @@ fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
<*
|
||||
Check for presence of a value in a list.
|
||||
|
||||
@param [&in] self "the list to find elements in"
|
||||
@param value "The value to search for"
|
||||
@param [&in] self : "the list to find elements in"
|
||||
@param value : "The value to search for"
|
||||
@return "True if the value is found, false otherwise"
|
||||
*>
|
||||
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -480,8 +491,8 @@ fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@param [&inout] self : "The list to remove elements from"
|
||||
@param value : "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -490,8 +501,8 @@ fn bool List.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@param [&inout] self : "The list to remove elements from"
|
||||
@param value : "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -499,8 +510,8 @@ fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
return @ok(self.remove_at(self.index_of(value)));
|
||||
}
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@param [&inout] self : "The list to remove elements from"
|
||||
@param value : "The value to remove"
|
||||
@return "the number of deleted elements."
|
||||
*>
|
||||
fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
@@ -544,35 +555,4 @@ fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
|
||||
return list_common::list_compact(self);
|
||||
}
|
||||
|
||||
// --> Deprecated
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_last_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
|
||||
{
|
||||
return self.remove_last_item(value) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "true if the value was found"
|
||||
*>
|
||||
fn bool List.remove_first_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
|
||||
{
|
||||
return self.remove_first_item(value) @inline;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [&inout] self "The list to remove elements from"
|
||||
@param value "The value to remove"
|
||||
@return "the number of deleted elements."
|
||||
*>
|
||||
fn usz List.remove_all_matches(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
|
||||
{
|
||||
return self.remove_item(value) @inline;
|
||||
}
|
||||
int dummy @local;
|
||||
|
||||
@@ -3,17 +3,17 @@ module std::collections::list_common;
|
||||
<*
|
||||
IMPORTANT The returned array must be freed using free_aligned.
|
||||
*>
|
||||
macro list_to_new_aligned_array($Type, self, Allocator allocator)
|
||||
macro list_to_aligned_array($Type, self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return $Type[] {};
|
||||
if (!self.size) return ($Type[]){};
|
||||
$Type[] result = allocator::alloc_array_aligned(allocator, $Type, self.size);
|
||||
result[..] = self.entries[:self.size];
|
||||
return result;
|
||||
}
|
||||
|
||||
macro list_to_new_array($Type, self, Allocator allocator)
|
||||
macro list_to_array($Type, self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return $Type[] {};
|
||||
if (!self.size) return ($Type[]){};
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, self.size);
|
||||
result[..] = self.entries[:self.size];
|
||||
return result;
|
||||
|
||||
@@ -1,508 +0,0 @@
|
||||
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::map(<Key, Value>);
|
||||
import std::math;
|
||||
|
||||
const uint DEFAULT_INITIAL_CAPACITY = 16;
|
||||
const uint MAXIMUM_CAPACITY = 1u << 31;
|
||||
const float DEFAULT_LOAD_FACTOR = 0.75;
|
||||
const VALUE_IS_EQUATABLE = Value.is_eq;
|
||||
const bool COPY_KEYS = types::implements_copy(Key);
|
||||
|
||||
distinct Map = void*;
|
||||
|
||||
struct MapImpl
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator allocator;
|
||||
uint count; // Number of elements
|
||||
uint threshold; // Resize limit
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
<*
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn Map new(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
MapImpl* map = allocator::alloc(allocator, MapImpl);
|
||||
_init(map, capacity, load_factor, allocator);
|
||||
return (Map)map;
|
||||
}
|
||||
|
||||
<*
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn Map temp(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
MapImpl* map = mem::temp_alloc(MapImpl);
|
||||
_init(map, capacity, load_factor, allocator::temp());
|
||||
return (Map)map;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro Map new_init_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
Map map = new(capacity, load_factor, allocator);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
map.set($vaarg[$i], $vaarg[$i+1]);
|
||||
$endfor
|
||||
return map;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys "Array of keys for the Map entries"
|
||||
@param [in] values "Array of values for the Map entries"
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require keys.len == values.len "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn Map new_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
assert(keys.len == values.len);
|
||||
Map map = new(capacity, load_factor, allocator);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
map.set(keys[i], values[i]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
macro Map temp_new_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
Map map = temp(capacity, load_factor);
|
||||
$for (var $i = 0; $i < $vacount; $i += 2)
|
||||
map.set($vaarg[$i], $vaarg[$i+1]);
|
||||
$endfor
|
||||
return map;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] keys "The keys for the HashMap entries"
|
||||
@param [in] values "The values for the HashMap entries"
|
||||
@param [&inout] allocator "The allocator to use"
|
||||
@require keys.len == values.len "Both keys and values arrays must be the same length"
|
||||
@require capacity > 0 "The capacity must be 1 or higher"
|
||||
@require load_factor > 0.0 "The load factor must be higher than 0"
|
||||
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn Map temp_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
|
||||
{
|
||||
assert(keys.len == values.len);
|
||||
Map map = temp(capacity, load_factor);
|
||||
for (usz i = 0; i < keys.len; i++)
|
||||
{
|
||||
map.set(keys[i], values[i]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn Map new_from_map(Map other_map, Allocator allocator = null)
|
||||
{
|
||||
MapImpl* other_map_impl = (MapImpl*)other_map;
|
||||
if (!other_map_impl)
|
||||
{
|
||||
if (allocator) return new(allocator: allocator);
|
||||
return null;
|
||||
}
|
||||
MapImpl* map = (MapImpl*)new(other_map_impl.table.len, other_map_impl.load_factor, allocator ?: allocator::heap());
|
||||
if (!other_map_impl.count) return (Map)map;
|
||||
foreach (Entry *e : other_map_impl.table)
|
||||
{
|
||||
while (e)
|
||||
{
|
||||
map._put_for_create(e.key, e.value);
|
||||
e = e.next;
|
||||
}
|
||||
}
|
||||
return (Map)map;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] other_map "The map to copy from."
|
||||
*>
|
||||
fn Map temp_from_map(Map other_map)
|
||||
{
|
||||
return new_from_map(other_map, allocator::temp());
|
||||
}
|
||||
|
||||
fn bool Map.is_empty(map) @inline
|
||||
{
|
||||
return !map || !((MapImpl*)map).count;
|
||||
}
|
||||
|
||||
fn usz Map.len(map) @inline
|
||||
{
|
||||
return map ? ((MapImpl*)map).count : 0;
|
||||
}
|
||||
|
||||
fn Value*! Map.get_ref(self, Key key)
|
||||
{
|
||||
MapImpl *map = (MapImpl*)self;
|
||||
if (!map || !map.count) return SearchResult.MISSING?;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return &e.value;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn Entry*! Map.get_entry(map, Key key)
|
||||
{
|
||||
MapImpl *map_impl = (MapImpl*)map;
|
||||
if (!map_impl || !map_impl.count) return SearchResult.MISSING?;
|
||||
uint hash = rehash(key.hash());
|
||||
for (Entry *e = map_impl.table[index_for(hash, map_impl.table.len)]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value or update and
|
||||
@require $assignable(#expr, Value)
|
||||
*>
|
||||
macro Value Map.@get_or_set(&self, Key key, Value #expr)
|
||||
{
|
||||
MapImpl *map = (MapImpl*)*self;
|
||||
if (!map || !map.count)
|
||||
{
|
||||
Value val = #expr;
|
||||
map.set(key, val);
|
||||
return val;
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key)) return e.value;
|
||||
}
|
||||
Value val = #expr;
|
||||
map.add_entry(hash, key, val, index);
|
||||
return val;
|
||||
}
|
||||
|
||||
fn Value! Map.get(map, Key key) @operator([])
|
||||
{
|
||||
return *map.get_ref(key) @inline;
|
||||
}
|
||||
|
||||
fn bool Map.has_key(map, Key key)
|
||||
{
|
||||
return @ok(map.get_ref(key));
|
||||
}
|
||||
|
||||
macro Value Map.set_value_return(&map, Key key, Value value) @operator([]=)
|
||||
{
|
||||
map.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
fn bool Map.set(&self, Key key, Value value)
|
||||
{
|
||||
// If the map isn't initialized, use the defaults to initialize it.
|
||||
if (!*self) *self = new();
|
||||
MapImpl* map = (MapImpl*)*self;
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[index]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
e.value = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
map._add_entry(hash, key, value, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void! Map.remove(map, Key key) @maydiscard
|
||||
{
|
||||
if (!map || !((MapImpl*)map)._remove_entry_for_key(key)) return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn void Map.clear(self)
|
||||
{
|
||||
MapImpl* map = (MapImpl*)self;
|
||||
if (!map || !map.count) return;
|
||||
foreach (Entry** &entry_ref : map.table)
|
||||
{
|
||||
Entry* entry = *entry_ref;
|
||||
if (!entry) continue;
|
||||
Entry *next = entry.next;
|
||||
while (next)
|
||||
{
|
||||
Entry *to_delete = next;
|
||||
next = next.next;
|
||||
map._free_entry(to_delete);
|
||||
}
|
||||
map._free_entry(entry);
|
||||
*entry_ref = null;
|
||||
}
|
||||
map.count = 0;
|
||||
}
|
||||
|
||||
fn void Map.free(self)
|
||||
{
|
||||
if (!self) return;
|
||||
MapImpl* map = (MapImpl*)self;
|
||||
self.clear();
|
||||
map._free_internal(map.table.ptr);
|
||||
map.table = {};
|
||||
allocator::free(map.allocator, map);
|
||||
}
|
||||
|
||||
fn Key[] Map.temp_keys_list(map)
|
||||
{
|
||||
return map.new_keys_list(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
fn Key[] Map.new_keys_list(self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
MapImpl* map = (MapImpl*)self;
|
||||
if (!map || !map.count) return {};
|
||||
|
||||
Key[] list = allocator::alloc_array(allocator, Key, map.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
list[index++] = entry.key;
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
macro Map.@each(map; @body(key, value))
|
||||
{
|
||||
map.@each_entry(; Entry* entry) {
|
||||
@body(entry.key, entry.value);
|
||||
};
|
||||
}
|
||||
|
||||
macro Map.@each_entry(self; @body(entry))
|
||||
{
|
||||
MapImpl *map = (MapImpl*)self;
|
||||
if (!map || !map.count) return;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@body(entry);
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] Map.temp_values_list(map)
|
||||
{
|
||||
return map.new_values_list(allocator::temp()) @inline;
|
||||
}
|
||||
|
||||
fn Value[] Map.new_values_list(self, Allocator allocator = allocator::heap())
|
||||
{
|
||||
MapImpl* map = (MapImpl*)self;
|
||||
if (!map || !map.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, map.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
list[index++] = entry.value;
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
fn bool Map.has_value(self, Value v) @if(VALUE_IS_EQUATABLE)
|
||||
{
|
||||
MapImpl* map = (MapImpl*)self;
|
||||
if (!map || !map.count) return false;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
if (equals(v, entry.value)) return true;
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void MapImpl._add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
|
||||
map.table[bucket_index] = entry;
|
||||
if (map.count++ >= map.threshold)
|
||||
{
|
||||
map._resize(map.table.len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn void MapImpl._resize(&map, uint new_capacity) @private
|
||||
{
|
||||
Entry*[] old_table = map.table;
|
||||
uint old_capacity = old_table.len;
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
map.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
|
||||
map._transfer(new_table);
|
||||
map.table = new_table;
|
||||
map._free_internal(old_table.ptr);
|
||||
map.threshold = (uint)(new_capacity * map.load_factor);
|
||||
}
|
||||
|
||||
fn 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
|
||||
{
|
||||
return hash & (capacity - 1);
|
||||
}
|
||||
|
||||
fn void MapImpl._transfer(&map, Entry*[] new_table) @private
|
||||
{
|
||||
Entry*[] src = map.table;
|
||||
uint new_capacity = new_table.len;
|
||||
foreach (uint j, Entry *e : src)
|
||||
{
|
||||
if (!e) continue;
|
||||
do
|
||||
{
|
||||
Entry* next = e.next;
|
||||
uint i = index_for(e.hash, new_capacity);
|
||||
e.next = new_table[i];
|
||||
new_table[i] = e;
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
}
|
||||
}
|
||||
|
||||
fn void _init(MapImpl* impl, uint capacity, float load_factor, Allocator allocator) @private
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
*impl = {
|
||||
.allocator = allocator,
|
||||
.load_factor = load_factor,
|
||||
.threshold = (uint)(capacity * load_factor),
|
||||
.table = allocator::new_array(allocator, Entry*, capacity)
|
||||
};
|
||||
}
|
||||
|
||||
fn void MapImpl._put_for_create(&map, Key key, Value value) @private
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[i]; e != null; e = e.next)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
e.value = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
map._create_entry(hash, key, value, i);
|
||||
}
|
||||
|
||||
fn void MapImpl._free_internal(&map, void* ptr) @inline @private
|
||||
{
|
||||
allocator::free(map.allocator, ptr);
|
||||
}
|
||||
|
||||
fn bool MapImpl._remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
if (!map.count) return false;
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
Entry* prev = map.table[i];
|
||||
Entry* e = prev;
|
||||
while (e)
|
||||
{
|
||||
Entry *next = e.next;
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
{
|
||||
map.count--;
|
||||
if (prev == e)
|
||||
{
|
||||
map.table[i] = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
map._free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
e = next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void MapImpl._create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
|
||||
{
|
||||
Entry *e = map.table[bucket_index];
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
|
||||
map.table[bucket_index] = entry;
|
||||
map.count++;
|
||||
}
|
||||
|
||||
fn void MapImpl._free_entry(&self, Entry *entry) @local
|
||||
{
|
||||
$if COPY_KEYS:
|
||||
allocator::free(self.allocator, entry.key);
|
||||
$endif
|
||||
self._free_internal(entry);
|
||||
}
|
||||
|
||||
struct Entry
|
||||
{
|
||||
uint hash;
|
||||
Key key;
|
||||
Value value;
|
||||
Entry* next;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
module std::collections::maybe(<Type>);
|
||||
module std::collections::maybe{Type};
|
||||
import std::io;
|
||||
|
||||
struct Maybe (Printable)
|
||||
@@ -7,7 +7,7 @@ struct Maybe (Printable)
|
||||
bool has_value;
|
||||
}
|
||||
|
||||
fn usz! Maybe.to_format(&self, Formatter* f) @dynamic
|
||||
fn usz? Maybe.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
if (self.has_value) return f.printf("[%s]", self.value);
|
||||
return f.printf("[EMPTY]");
|
||||
@@ -28,19 +28,18 @@ fn Maybe value(Type val)
|
||||
return { .value = val, .has_value = true };
|
||||
}
|
||||
|
||||
fn Maybe Maybe.with_value(Type val) @operator(construct)
|
||||
{
|
||||
return { .value = val, .has_value = true };
|
||||
}
|
||||
|
||||
fn Maybe Maybe.empty() @operator(construct)
|
||||
{
|
||||
return { };
|
||||
}
|
||||
|
||||
const Maybe EMPTY = { };
|
||||
|
||||
macro Type! Maybe.get(self)
|
||||
macro Type? Maybe.get(self)
|
||||
{
|
||||
return self.has_value ? self.value : SearchResult.MISSING?;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ struct Object (Printable)
|
||||
}
|
||||
|
||||
|
||||
fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
|
||||
fn usz? Object.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (self.type)
|
||||
{
|
||||
@@ -50,7 +50,7 @@ fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
|
||||
usz n = formatter.printf("{")!;
|
||||
@stack_mem(1024; Allocator mem)
|
||||
{
|
||||
foreach (i, key : self.map.copy_keys(mem))
|
||||
foreach (i, key : self.map.keys(mem))
|
||||
{
|
||||
if (i > 0) n += formatter.printf(",")!;
|
||||
n += formatter.printf(`"%s":`, key)!;
|
||||
@@ -156,7 +156,7 @@ fn void Object.init_map_if_needed(&self) @private
|
||||
if (self.is_empty())
|
||||
{
|
||||
self.type = ObjectInternalMap.typeid;
|
||||
self.map.new_init(allocator: self.allocator);
|
||||
self.map.init(self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ fn void Object.init_array_if_needed(&self) @private
|
||||
if (self.is_empty())
|
||||
{
|
||||
self.type = ObjectInternalList.typeid;
|
||||
self.array.new_init(allocator: self.allocator);
|
||||
self.array.init(self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,18 +178,19 @@ fn void Object.init_array_if_needed(&self) @private
|
||||
fn void Object.set_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*? 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 !@typeis(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);
|
||||
$switch
|
||||
$switch:
|
||||
$case types::is_int($Type):
|
||||
return new_int(value, self.allocator);
|
||||
$case types::is_float($Type):
|
||||
@@ -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 CastResult.TYPE_MISMATCH?;
|
||||
return &NULL_OBJECT;
|
||||
$case $assignable(value, String):
|
||||
$case @assignable_to(value, String):
|
||||
return new_string(value, self.allocator);
|
||||
$default:
|
||||
$error "Unsupported object type.";
|
||||
@@ -242,7 +242,7 @@ macro Object* Object.push(&self, value)
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn Object*! Object.get(&self, String key) => self.is_empty() ? SearchResult.MISSING? : 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);
|
||||
@@ -292,7 +292,7 @@ fn void Object.set_object_at(&self, usz index, Object* to_set)
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof.is_int() "Expected an integer type."
|
||||
@require $Type.kindof.is_int() : "Expected an integer type."
|
||||
*>
|
||||
macro get_integer_value(Object* value, $Type)
|
||||
{
|
||||
@@ -308,7 +308,7 @@ macro get_integer_value(Object* value, $Type)
|
||||
return ($Type)value.s.to_uint128();
|
||||
$endif
|
||||
}
|
||||
if (!value.is_int()) return NumberConversion.MALFORMED_INTEGER?;
|
||||
if (!value.is_int()) return string::MALFORMED_INTEGER?;
|
||||
return ($Type)value.i;
|
||||
}
|
||||
|
||||
@@ -331,77 +331,77 @@ macro Object.get_integer(&self, $Type, String key) @private
|
||||
return get_integer_value(self.get(key), $Type);
|
||||
}
|
||||
|
||||
fn ichar! Object.get_ichar(&self, String key) => self.get_integer(ichar, key);
|
||||
fn short! Object.get_short(&self, String key) => self.get_integer(short, key);
|
||||
fn int! Object.get_int(&self, String key) => self.get_integer(int, key);
|
||||
fn long! Object.get_long(&self, String key) => self.get_integer(long, key);
|
||||
fn int128! Object.get_int128(&self, String key) => self.get_integer(int128, key);
|
||||
fn ichar? Object.get_ichar(&self, String key) => self.get_integer(ichar, key);
|
||||
fn short? Object.get_short(&self, String key) => self.get_integer(short, key);
|
||||
fn int? Object.get_int(&self, String key) => self.get_integer(int, key);
|
||||
fn long? Object.get_long(&self, String key) => self.get_integer(long, key);
|
||||
fn int128? Object.get_int128(&self, String key) => self.get_integer(int128, key);
|
||||
|
||||
fn ichar! Object.get_ichar_at(&self, usz index) => self.get_integer_at(ichar, index);
|
||||
fn short! Object.get_short_at(&self, usz index) => self.get_integer_at(short, index);
|
||||
fn int! Object.get_int_at(&self, usz index) => self.get_integer_at(int, index);
|
||||
fn long! Object.get_long_at(&self, usz index) => self.get_integer_at(long, index);
|
||||
fn int128! Object.get_int128_at(&self, usz index) => self.get_integer_at(int128, index);
|
||||
fn ichar? Object.get_ichar_at(&self, usz index) => self.get_integer_at(ichar, index);
|
||||
fn short? Object.get_short_at(&self, usz index) => self.get_integer_at(short, index);
|
||||
fn int? Object.get_int_at(&self, usz index) => self.get_integer_at(int, index);
|
||||
fn long? Object.get_long_at(&self, usz index) => self.get_integer_at(long, index);
|
||||
fn int128? Object.get_int128_at(&self, usz index) => self.get_integer_at(int128, index);
|
||||
|
||||
fn char! Object.get_char(&self, String key) => self.get_integer(ichar, key);
|
||||
fn short! Object.get_ushort(&self, String key) => self.get_integer(ushort, key);
|
||||
fn uint! Object.get_uint(&self, String key) => self.get_integer(uint, key);
|
||||
fn ulong! Object.get_ulong(&self, String key) => self.get_integer(ulong, key);
|
||||
fn uint128! Object.get_uint128(&self, String key) => self.get_integer(uint128, key);
|
||||
fn char? Object.get_char(&self, String key) => self.get_integer(ichar, key);
|
||||
fn short? Object.get_ushort(&self, String key) => self.get_integer(ushort, key);
|
||||
fn uint? Object.get_uint(&self, String key) => self.get_integer(uint, key);
|
||||
fn ulong? Object.get_ulong(&self, String key) => self.get_integer(ulong, key);
|
||||
fn uint128? Object.get_uint128(&self, String key) => self.get_integer(uint128, key);
|
||||
|
||||
fn char! Object.get_char_at(&self, usz index) => self.get_integer_at(char, index);
|
||||
fn ushort! Object.get_ushort_at(&self, usz index) => self.get_integer_at(ushort, index);
|
||||
fn uint! Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index);
|
||||
fn ulong! Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index);
|
||||
fn uint128! Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
|
||||
fn char? Object.get_char_at(&self, usz index) => self.get_integer_at(char, index);
|
||||
fn ushort? Object.get_ushort_at(&self, usz index) => self.get_integer_at(ushort, index);
|
||||
fn uint? Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index);
|
||||
fn ulong? Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index);
|
||||
fn uint128? Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn String! Object.get_string(&self, String key)
|
||||
fn String? Object.get_string(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
|
||||
if (!value.is_string()) return TYPE_MISMATCH?;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn String! Object.get_string_at(&self, usz index)
|
||||
fn String? Object.get_string_at(&self, usz index)
|
||||
{
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
|
||||
if (!value.is_string()) return TYPE_MISMATCH?;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
macro String! Object.get_enum(&self, $EnumType, String key)
|
||||
macro String? Object.get_enum(&self, $EnumType, String key)
|
||||
{
|
||||
Object value = self.get(key)!;
|
||||
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
|
||||
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
macro String! Object.get_enum_at(&self, $EnumType, usz index)
|
||||
macro String? Object.get_enum_at(&self, $EnumType, usz index)
|
||||
{
|
||||
Object value = self.get_at(index);
|
||||
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
|
||||
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn bool! Object.get_bool(&self, String key)
|
||||
fn bool? Object.get_bool(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
|
||||
if (!value.is_bool()) return TYPE_MISMATCH?;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
@@ -409,17 +409,17 @@ fn bool! Object.get_bool(&self, String key)
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn bool! Object.get_bool_at(&self, usz index)
|
||||
fn bool? Object.get_bool_at(&self, usz index)
|
||||
{
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
|
||||
if (!value.is_bool()) return TYPE_MISMATCH?;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_keyable()
|
||||
*>
|
||||
fn double! Object.get_float(&self, String key)
|
||||
fn double? Object.get_float(&self, String key)
|
||||
{
|
||||
Object* value = self.get(key)!;
|
||||
switch (value.type.kindof)
|
||||
@@ -431,14 +431,14 @@ fn double! Object.get_float(&self, String key)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
return CastResult.TYPE_MISMATCH?;
|
||||
return TYPE_MISMATCH?;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_indexable()
|
||||
*>
|
||||
fn double! Object.get_float_at(&self, usz index)
|
||||
fn double? Object.get_float_at(&self, usz index)
|
||||
{
|
||||
Object* value = self.get_at(index);
|
||||
switch (value.type.kindof)
|
||||
@@ -450,7 +450,7 @@ fn double! Object.get_float_at(&self, usz index)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
return CastResult.TYPE_MISMATCH?;
|
||||
return TYPE_MISMATCH?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,7 +462,7 @@ fn Object* Object.get_or_create_obj(&self, String key)
|
||||
return container;
|
||||
}
|
||||
|
||||
def ObjectInternalMap = HashMap(<String, Object*>) @private;
|
||||
def ObjectInternalList = List(<Object*>) @private;
|
||||
def ObjectInternalMapEntry = Entry(<String, Object*>) @private;
|
||||
alias ObjectInternalMap @private = HashMap {String, Object*};
|
||||
alias ObjectInternalList @private = List {Object*};
|
||||
alias ObjectInternalMapEntry @private = Entry {String, Object*};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// priorityqueue.c3
|
||||
// A priority queue using a classic binary heap for C3.
|
||||
//
|
||||
// Copyright (c) 2022 David Kopec
|
||||
// Copyright (c) 2022-2025 David Kopec
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -20,30 +20,30 @@
|
||||
// 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>);
|
||||
module std::collections::priorityqueue{Type};
|
||||
import std::collections::priorityqueue::private;
|
||||
|
||||
distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
|
||||
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);
|
||||
typedef PriorityQueue = inline PrivatePriorityQueue{Type, false};
|
||||
typedef PriorityQueueMax = inline PrivatePriorityQueue{Type, true};
|
||||
|
||||
module std::collections::priorityqueue::private(<Type, MAX>);
|
||||
module std::collections::priorityqueue::private{Type, MAX};
|
||||
import std::collections::list, std::io;
|
||||
|
||||
def Heap = List(<Type>);
|
||||
|
||||
struct PrivatePriorityQueue (Printable)
|
||||
{
|
||||
Heap heap;
|
||||
List{Type} heap;
|
||||
}
|
||||
|
||||
fn void PrivatePriorityQueue.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @inline
|
||||
fn PrivatePriorityQueue* PrivatePriorityQueue.init(&self, Allocator allocator, usz initial_capacity = 16, ) @inline
|
||||
{
|
||||
self.heap.new_init(initial_capacity, allocator);
|
||||
self.heap.init(allocator, initial_capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn void PrivatePriorityQueue.temp_init(&self, usz initial_capacity = 16) @inline
|
||||
fn PrivatePriorityQueue* PrivatePriorityQueue.tinit(&self, usz initial_capacity = 16) @inline
|
||||
{
|
||||
self.heap.new_init(initial_capacity, allocator::temp()) @inline;
|
||||
self.init(tmem, initial_capacity);
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,9 @@ fn void PrivatePriorityQueue.push(&self, Type element)
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len() : "Index out of range"
|
||||
*>
|
||||
fn void PrivatePriorityQueue.remove_at(&self, usz index)
|
||||
{
|
||||
if (index == 0)
|
||||
@@ -79,11 +82,11 @@ fn void PrivatePriorityQueue.remove_at(&self, usz index)
|
||||
<*
|
||||
@require self != null
|
||||
*>
|
||||
fn Type! PrivatePriorityQueue.pop(&self)
|
||||
fn Type? PrivatePriorityQueue.pop(&self)
|
||||
{
|
||||
usz i = 0;
|
||||
usz len = self.heap.len();
|
||||
if (!len) return IteratorResult.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)
|
||||
@@ -117,10 +120,9 @@ fn Type! PrivatePriorityQueue.pop(&self)
|
||||
return self.heap.pop();
|
||||
}
|
||||
|
||||
fn Type! PrivatePriorityQueue.first(&self)
|
||||
fn Type? PrivatePriorityQueue.first(&self)
|
||||
{
|
||||
if (!self.len()) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.heap.get(0);
|
||||
return self.heap.first();
|
||||
}
|
||||
|
||||
fn void PrivatePriorityQueue.free(&self)
|
||||
@@ -130,12 +132,12 @@ fn void PrivatePriorityQueue.free(&self)
|
||||
|
||||
fn usz PrivatePriorityQueue.len(&self) @operator(len)
|
||||
{
|
||||
return self.heap.len();
|
||||
return self.heap.len() @inline;
|
||||
}
|
||||
|
||||
fn bool PrivatePriorityQueue.is_empty(&self)
|
||||
{
|
||||
return self.heap.is_empty();
|
||||
return self.heap.is_empty() @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -146,13 +148,8 @@ fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
|
||||
return self.heap[index];
|
||||
}
|
||||
|
||||
fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
|
||||
fn usz? PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return self.heap.to_format(formatter);
|
||||
}
|
||||
|
||||
fn String PrivatePriorityQueue.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
|
||||
{
|
||||
return self.heap.to_new_string(allocator);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -29,22 +29,7 @@ fn Type Range.get(&self, usz index) @operator([])
|
||||
return (Type)(self.start + (usz)index);
|
||||
}
|
||||
|
||||
fn String Range.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic @deprecated
|
||||
{
|
||||
return string::format("[%s..%s]", self.start, self.end, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String Range.to_string(&self, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("[%s..%s]", self.start, self.end, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String Range.to_tstring(&self)
|
||||
{
|
||||
return self.to_string(allocator::temp());
|
||||
}
|
||||
|
||||
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
|
||||
fn usz? Range.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return formatter.printf("[%s..%s]", self.start, self.end)!;
|
||||
}
|
||||
@@ -66,26 +51,11 @@ fn bool ExclusiveRange.contains(&self, Type value) @inline
|
||||
return value >= self.start && value < self.end;
|
||||
}
|
||||
|
||||
fn usz! ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
|
||||
fn usz? ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return formatter.printf("[%s..<%s]", self.start, self.end)!;
|
||||
}
|
||||
|
||||
fn String ExclusiveRange.to_new_string(&self, Allocator allocator = null) @dynamic
|
||||
{
|
||||
return self.to_string(allocator ?: allocator::heap());
|
||||
}
|
||||
|
||||
fn String ExclusiveRange.to_string(&self, Allocator allocator) @dynamic
|
||||
{
|
||||
return string::format("[%s..<%s]", self.start, self.end, allocator: allocator);
|
||||
}
|
||||
|
||||
fn String ExclusiveRange.to_tstring(&self)
|
||||
{
|
||||
return self.to_new_string(allocator::temp());
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.len() : "Can't index into an empty range"
|
||||
*>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<*
|
||||
@require values::@is_int(SIZE) &&& SIZE > 0 "The size must be positive integer"
|
||||
@require Type.kindof == ARRAY : "Required an array type"
|
||||
*>
|
||||
module std::collections::ringbuffer(<Type, SIZE>);
|
||||
module std::collections::ringbuffer{Type};
|
||||
import std::io;
|
||||
|
||||
struct RingBuffer
|
||||
alias Element = $typeof((Type){}[0]);
|
||||
|
||||
struct RingBuffer (Printable)
|
||||
{
|
||||
Type[SIZE] buf;
|
||||
Type buf;
|
||||
usz written;
|
||||
usz head;
|
||||
}
|
||||
@@ -15,9 +18,9 @@ fn void RingBuffer.init(&self) @inline
|
||||
*self = {};
|
||||
}
|
||||
|
||||
fn void RingBuffer.push(&self, Type c)
|
||||
fn void RingBuffer.push(&self, Element c)
|
||||
{
|
||||
if (self.written < SIZE)
|
||||
if (self.written < self.buf.len)
|
||||
{
|
||||
self.buf[self.written] = c;
|
||||
self.written++;
|
||||
@@ -25,14 +28,14 @@ fn void RingBuffer.push(&self, Type c)
|
||||
else
|
||||
{
|
||||
self.buf[self.head] = c;
|
||||
self.head = (self.head + 1) % SIZE;
|
||||
self.head = (self.head + 1) % self.buf.len;
|
||||
}
|
||||
}
|
||||
|
||||
fn Type RingBuffer.get(&self, usz index) @operator([])
|
||||
fn Element RingBuffer.get(&self, usz index) @operator([])
|
||||
{
|
||||
index %= SIZE;
|
||||
usz avail = SIZE - self.head;
|
||||
index %= self.buf.len;
|
||||
usz avail = self.buf.len - self.head;
|
||||
if (index < avail)
|
||||
{
|
||||
return self.buf[self.head + index];
|
||||
@@ -40,25 +43,31 @@ fn Type RingBuffer.get(&self, usz index) @operator([])
|
||||
return self.buf[index - avail];
|
||||
}
|
||||
|
||||
fn Type! RingBuffer.pop(&self)
|
||||
fn Element? RingBuffer.pop(&self)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case self.written == 0:
|
||||
return SearchResult.MISSING?;
|
||||
case self.written < SIZE:
|
||||
return NO_MORE_ELEMENT?;
|
||||
case self.written < self.buf.len:
|
||||
self.written--;
|
||||
return self.buf[self.written];
|
||||
default:
|
||||
self.head = (self.head - 1) % SIZE;
|
||||
self.head = (self.head - 1) % self.buf.len;
|
||||
return self.buf[self.head];
|
||||
}
|
||||
}
|
||||
|
||||
fn usz RingBuffer.read(&self, usz index, Type[] buffer)
|
||||
fn usz? RingBuffer.to_format(&self, Formatter* format) @dynamic
|
||||
{
|
||||
index %= SIZE;
|
||||
if (self.written < SIZE)
|
||||
// Improve this?
|
||||
return format.printf("%s", self.buf);
|
||||
}
|
||||
|
||||
fn usz RingBuffer.read(&self, usz index, Element[] buffer)
|
||||
{
|
||||
index %= self.buf.len;
|
||||
if (self.written < self.buf.len)
|
||||
{
|
||||
if (index >= self.written) return 0;
|
||||
usz end = self.written - index;
|
||||
@@ -66,7 +75,7 @@ fn usz RingBuffer.read(&self, usz index, Type[] buffer)
|
||||
buffer[:n] = self.buf[index:n];
|
||||
return n;
|
||||
}
|
||||
usz end = SIZE - self.head;
|
||||
usz end = self.buf.len - self.head;
|
||||
if (index >= end)
|
||||
{
|
||||
index -= end;
|
||||
@@ -75,13 +84,13 @@ fn usz RingBuffer.read(&self, usz index, Type[] buffer)
|
||||
buffer[:n] = self.buf[index:n];
|
||||
return n;
|
||||
}
|
||||
if (buffer.len <= SIZE - index)
|
||||
if (buffer.len <= self.buf.len - index)
|
||||
{
|
||||
usz n = buffer.len;
|
||||
buffer[:n] = self.buf[self.head + index:n];
|
||||
return n;
|
||||
}
|
||||
usz n1 = SIZE - index;
|
||||
usz n1 = self.buf.len - index;
|
||||
buffer[:n1] = self.buf[self.head + index:n1];
|
||||
buffer = buffer[n1..];
|
||||
index -= n1;
|
||||
@@ -90,10 +99,10 @@ fn usz RingBuffer.read(&self, usz index, Type[] buffer)
|
||||
return n1 + n2;
|
||||
}
|
||||
|
||||
fn void RingBuffer.write(&self, Type[] buffer)
|
||||
fn void RingBuffer.write(&self, Element[] buffer)
|
||||
{
|
||||
usz i;
|
||||
while (self.written < SIZE && i < buffer.len)
|
||||
while (self.written < self.buf.len && i < buffer.len)
|
||||
{
|
||||
self.buf[self.written] = buffer[i++];
|
||||
self.written++;
|
||||
@@ -101,6 +110,6 @@ fn void RingBuffer.write(&self, Type[] buffer)
|
||||
foreach (c : buffer[i..])
|
||||
{
|
||||
self.buf[self.head] = c;
|
||||
self.head = (self.head + 1) % SIZE;
|
||||
self.head = (self.head + 1) % self.buf.len;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,51 @@
|
||||
module std::collections::tuple(<Type1, Type2>);
|
||||
module std::collections::pair{Type1, Type2};
|
||||
|
||||
struct Tuple
|
||||
struct Pair
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
}
|
||||
|
||||
module std::collections::triple(<Type1, Type2, Type3>);
|
||||
<*
|
||||
@param [&out] a
|
||||
@param [&out] b
|
||||
@require @assignable_to(self.first, $typeof(*a)) : "You cannot assign the first value to a"
|
||||
@require @assignable_to(self.second, $typeof(*b)) : "You cannot assign the second value to b"
|
||||
*>
|
||||
macro void Pair.unpack(&self, a, b)
|
||||
{
|
||||
*a = self.first;
|
||||
*b = self.second;
|
||||
}
|
||||
|
||||
module std::collections::triple{Type1, Type2, Type3};
|
||||
|
||||
struct Triple
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
Type3 third;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&out] a
|
||||
@param [&out] b
|
||||
@param [&out] c
|
||||
@require @assignable_to(self.first, $typeof(*a)) : "You cannot assign the first value to a"
|
||||
@require @assignable_to(self.second, $typeof(*b)) : "You cannot assign the second value to b"
|
||||
@require @assignable_to(self.third, $typeof(*c)) : "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;
|
||||
}
|
||||
|
||||
module std::collections::tuple{Type1, Type2};
|
||||
|
||||
struct Tuple @deprecated("Use 'Pair' instead")
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
}
|
||||
|
||||
@@ -37,19 +37,11 @@ struct QOIDesc
|
||||
QOIChannels channels;
|
||||
QOIColorspace colorspace;
|
||||
}
|
||||
|
||||
<*
|
||||
QOI Errors.
|
||||
These are all the possible bad outcomes.
|
||||
*>
|
||||
fault QOIError
|
||||
{
|
||||
INVALID_PARAMETERS,
|
||||
FILE_OPEN_FAILED,
|
||||
FILE_WRITE_FAILED,
|
||||
INVALID_DATA,
|
||||
TOO_MANY_PIXELS
|
||||
}
|
||||
faultdef INVALID_PARAMETERS, FILE_OPEN_FAILED, FILE_WRITE_FAILED, INVALID_DATA, TOO_MANY_PIXELS;
|
||||
|
||||
|
||||
// Let the user decide if they want to use std::io
|
||||
@@ -67,27 +59,17 @@ import std::io;
|
||||
The function returns an optional, which can either be a QOIError
|
||||
or the number of bytes written on success.
|
||||
|
||||
@param [in] filename `The file's name to write the image to`
|
||||
@param [in] input `The raw RGB or RGBA pixels to encode`
|
||||
@param [&in] desc `The descriptor of the image`
|
||||
@param [in] filename : `The file's name to write the image to`
|
||||
@param [in] input : `The raw RGB or RGBA pixels to encode`
|
||||
@param [&in] desc : `The descriptor of the image`
|
||||
*>
|
||||
fn usz! write(String filename, char[] input, QOIDesc* desc)
|
||||
fn usz? write(String filename, char[] input, QOIDesc* desc) => @pool()
|
||||
{
|
||||
@pool() {
|
||||
// encode data
|
||||
char[] output = encode(input, desc)!;
|
||||
// encode data
|
||||
char[] output = encode(tmem, input, desc)!;
|
||||
|
||||
// open file
|
||||
File! f = file::open(filename, "wb");
|
||||
if (catch f) { return QOIError.FILE_OPEN_FAILED?; }
|
||||
|
||||
// write data to file and close it
|
||||
usz! written = f.write(output);
|
||||
if (catch written) { return QOIError.FILE_WRITE_FAILED?; }
|
||||
if (catch f.close()) { return QOIError.FILE_WRITE_FAILED?; }
|
||||
|
||||
return written;
|
||||
};
|
||||
file::save(filename, output)!;
|
||||
return output.len;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,22 +90,21 @@ fn usz! write(String filename, char[] input, QOIDesc* desc)
|
||||
The returned pixel data should be free()d after use, or the decoding
|
||||
and use of the data should be wrapped in a @pool() { ... }; block.
|
||||
|
||||
@param [in] filename `The file's name to read the image from`
|
||||
@param [&out] desc `The descriptor to fill with the image's info`
|
||||
@param channels `The channels to be used`
|
||||
@param [in] filename : `The file's name to read the image from`
|
||||
@param [&out] desc : `The descriptor to fill with the image's info`
|
||||
@param channels : `The channels to be used`
|
||||
@return? FILE_OPEN_FAILED, INVALID_DATA, TOO_MANY_PIXELS
|
||||
*>
|
||||
fn char[]! read(String filename, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap())
|
||||
fn char[]? read(Allocator allocator, String filename, QOIDesc* desc, QOIChannels channels = AUTO) => @pool()
|
||||
{
|
||||
// read file
|
||||
char[]! data = file::load_new(filename);
|
||||
if (catch data) return QOIError.FILE_OPEN_FAILED?;
|
||||
defer mem::free(data);
|
||||
|
||||
char[] data = file::load_temp(filename) ?? FILE_OPEN_FAILED?!;
|
||||
// pass data to decode function
|
||||
return decode(data, desc, channels, allocator);
|
||||
return decode(allocator, data, desc, channels);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Back to basic non-stdio mode
|
||||
module std::compression::qoi;
|
||||
import std::bits;
|
||||
@@ -138,20 +119,21 @@ import std::bits;
|
||||
and use of the data should be wrapped in a @pool() { ... }; block.
|
||||
See the write() function for an example.
|
||||
|
||||
@param [in] input `The raw RGB or RGBA pixels to encode`
|
||||
@param [&in] desc `The descriptor of the image`
|
||||
@param [in] input : `The raw RGB or RGBA pixels to encode`
|
||||
@param [&in] desc : `The descriptor of the image`
|
||||
@return? INVALID_PARAMETERS, TOO_MANY_PIXELS, INVALID_DATA
|
||||
*>
|
||||
fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::heap())
|
||||
fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
{
|
||||
// check info in desc
|
||||
if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_PARAMETERS?;
|
||||
if (desc.channels == AUTO) return QOIError.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 QOIError.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 QOIError.INVALID_DATA?;
|
||||
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
|
||||
@@ -191,81 +173,89 @@ fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::
|
||||
if (desc.channels == RGBA) p.a = input[loc + 3];
|
||||
|
||||
// check if we can run the previous pixel
|
||||
if (prev == p) {
|
||||
if (prev == p)
|
||||
{
|
||||
run_length++;
|
||||
if (run_length == 62 || loc == loc_end) {
|
||||
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
|
||||
run_length = 0;
|
||||
}
|
||||
} else {
|
||||
// end last run if there was one
|
||||
if (run_length > 0) {
|
||||
if (run_length == 62 || loc == loc_end)
|
||||
{
|
||||
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
|
||||
run_length = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// end last run if there was one
|
||||
if (run_length > 0)
|
||||
{
|
||||
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
|
||||
run_length = 0;
|
||||
}
|
||||
|
||||
switch {
|
||||
// check if we can index the palette
|
||||
case (palette[p.hash()] == p):
|
||||
*@extract(OpIndex, output, &pos) = {
|
||||
OP_INDEX,
|
||||
p.hash()
|
||||
switch
|
||||
{
|
||||
// check if we can index the palette
|
||||
case (palette[p.hash()] == p):
|
||||
*@extract(OpIndex, output, &pos) = {
|
||||
OP_INDEX,
|
||||
p.hash()
|
||||
};
|
||||
|
||||
// check if we can use diff or luma
|
||||
case (prev != p && prev.a == p.a):
|
||||
// diff the pixels
|
||||
diff = p.rgb - prev.rgb;
|
||||
if (diff.r > -3 && diff.r < 2
|
||||
&& diff.g > -3 && diff.g < 2
|
||||
&& diff.b > -3 && diff.b < 2)
|
||||
{
|
||||
*@extract(OpDiff, output, &pos) = {
|
||||
OP_DIFF,
|
||||
(char)diff.r + 2,
|
||||
(char)diff.g + 2,
|
||||
(char)diff.b + 2
|
||||
};
|
||||
|
||||
// check if we can use diff or luma
|
||||
case (prev != p && prev.a == p.a):
|
||||
// diff the pixels
|
||||
diff = p.rgb - prev.rgb;
|
||||
if (
|
||||
diff.r > -3 && diff.r < 2 &&
|
||||
diff.g > -3 && diff.g < 2 &&
|
||||
diff.b > -3 && diff.b < 2
|
||||
) {
|
||||
*@extract(OpDiff, output, &pos) = {
|
||||
OP_DIFF,
|
||||
(char)diff.r + 2,
|
||||
(char)diff.g + 2,
|
||||
(char)diff.b + 2
|
||||
};
|
||||
palette[p.hash()] = p;
|
||||
} else {
|
||||
// check luma eligibility
|
||||
luma = { diff.r - diff.g, diff.g, diff.b - diff.g };
|
||||
if (
|
||||
luma.r >= -8 && luma.r <= 7 &&
|
||||
luma.g >= -32 && luma.g <= 31 &&
|
||||
luma.b >= -8 && luma.b <= 7
|
||||
) {
|
||||
*@extract(OpLuma, output, &pos) = {
|
||||
OP_LUMA,
|
||||
(char)luma.g + 32,
|
||||
(char)luma.r + 8,
|
||||
(char)luma.b + 8
|
||||
};
|
||||
palette[p.hash()] = p;
|
||||
} else { nextcase; }
|
||||
}
|
||||
|
||||
// worst case scenario: just encode the raw pixel
|
||||
default:
|
||||
if (prev.a != p.a) {
|
||||
*@extract(OpRGBA, output, &pos) = { OP_RGBA, p.r, p.g, p.b, p.a };
|
||||
} else {
|
||||
*@extract(OpRGB, output, &pos) = { OP_RGB, p.r, p.g, p.b };
|
||||
}
|
||||
palette[p.hash()] = p;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// check luma eligibility
|
||||
luma = { diff.r - diff.g, diff.g, diff.b - diff.g };
|
||||
if (luma.r >= -8 && luma.r <= 7
|
||||
&& luma.g >= -32 && luma.g <= 31
|
||||
&& luma.b >= -8 && luma.b <= 7)
|
||||
{
|
||||
*@extract(OpLuma, output, &pos) = {
|
||||
OP_LUMA,
|
||||
(char)luma.g + 32,
|
||||
(char)luma.r + 8,
|
||||
(char)luma.b + 8
|
||||
};
|
||||
palette[p.hash()] = p;
|
||||
break;
|
||||
}
|
||||
nextcase;
|
||||
|
||||
// worst case scenario: just encode the raw pixel
|
||||
default:
|
||||
if (prev.a != p.a)
|
||||
{
|
||||
*@extract(OpRGBA, output, &pos) = { OP_RGBA, p.r, p.g, p.b, p.a };
|
||||
}
|
||||
else
|
||||
{
|
||||
*@extract(OpRGB, output, &pos) = { OP_RGB, p.r, p.g, p.b };
|
||||
}
|
||||
palette[p.hash()] = p;
|
||||
}
|
||||
}
|
||||
|
||||
// write end of stream
|
||||
output[pos:END_OF_STREAM.len] = END_OF_STREAM;
|
||||
output[pos:END_OF_STREAM.len] = END_OF_STREAM[..];
|
||||
pos += END_OF_STREAM.len;
|
||||
|
||||
return output[:pos];
|
||||
}
|
||||
|
||||
|
||||
|
||||
<*
|
||||
Decode a QOI image from memory.
|
||||
|
||||
@@ -283,34 +273,35 @@ fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::
|
||||
The returned pixel data should be free()d after use, or the decoding
|
||||
and use of the data should be wrapped in a @pool() { ... }; block.
|
||||
|
||||
@param [in] data `The QOI image data to decode`
|
||||
@param [&out] desc `The descriptor to fill with the image's info`
|
||||
@param channels `The channels to be used`
|
||||
@param [in] data : `The QOI image data to decode`
|
||||
@param [&out] desc : `The descriptor to fill with the image's info`
|
||||
@param channels : `The channels to be used`
|
||||
@return? INVALID_DATA, TOO_MANY_PIXELS
|
||||
*>
|
||||
fn char[]! decode(char[] data, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap())
|
||||
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 QOIError.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 QOIError.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 QOIError.INVALID_DATA?; // Channels must be specified in the header
|
||||
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 QOIError.INVALID_DATA?;
|
||||
if (desc.width == 0 || desc.height == 0) return INVALID_DATA?;
|
||||
|
||||
// check pixel count
|
||||
ulong pixels = (ulong)desc.width * (ulong)desc.height;
|
||||
if (pixels > PIXELS_MAX) return QOIError.TOO_MANY_PIXELS?;
|
||||
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)
|
||||
@@ -373,7 +364,7 @@ fn char[]! decode(char[] data, QOIDesc* desc, QOIChannels channels = AUTO, Alloc
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -412,16 +403,22 @@ struct Header @packed
|
||||
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) {
|
||||
foreach (value : $Type.values)
|
||||
{
|
||||
if (value.id == raw) return value;
|
||||
}
|
||||
return QOIError.INVALID_DATA?;
|
||||
return INVALID_DATA?;
|
||||
}
|
||||
|
||||
distinct Pixel = inline char[<4>];
|
||||
macro char Pixel.hash(Pixel p) {
|
||||
typedef Pixel = inline char[<4>];
|
||||
macro char Pixel.hash(Pixel p)
|
||||
{
|
||||
return (p.r * 3 + p.g * 5 + p.b * 7 + p.a * 11) % 64;
|
||||
}
|
||||
|
||||
@@ -452,7 +449,7 @@ bitstruct OpDiff : char
|
||||
char diff_green : 2..3;
|
||||
char diff_blue : 0..1;
|
||||
}
|
||||
bitstruct OpLuma : ushort
|
||||
bitstruct OpLuma : ushort @align(1)
|
||||
{
|
||||
char tag : 6..7;
|
||||
char diff_green : 0..5;
|
||||
|
||||
@@ -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,18 +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
|
||||
<*
|
||||
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)
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
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
|
||||
@@ -45,24 +77,25 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
}
|
||||
}
|
||||
|
||||
fn usz ArenaAllocator.mark(&self) @dynamic => self.used;
|
||||
fn void ArenaAllocator.reset(&self, usz mark) @dynamic => 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 alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require size > 0
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
|
||||
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 AllocationFailure.OUT_OF_MEMORY?;
|
||||
if (end > total_len) return mem::OUT_OF_MEMORY?;
|
||||
self.used = end;
|
||||
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
|
||||
header.size = size;
|
||||
@@ -71,17 +104,20 @@ 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 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*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
|
||||
fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
|
||||
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?
|
||||
@@ -94,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 AllocationFailure.OUT_OF_MEMORY?;
|
||||
if (new_used > total_len) return mem::OUT_OF_MEMORY?;
|
||||
self.used = new_used;
|
||||
}
|
||||
header.size = size;
|
||||
@@ -102,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;
|
||||
}
|
||||
|
||||
219
lib/std/core/allocators/backed_arena_allocator.c3
Normal file
219
lib/std/core/allocators/backed_arena_allocator.c3
Normal file
@@ -0,0 +1,219 @@
|
||||
module std::core::mem::allocator;
|
||||
import std::io, std::math;
|
||||
|
||||
<*
|
||||
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;
|
||||
ExtraPage* last_page;
|
||||
usz used;
|
||||
usz capacity;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
struct AllocChunk @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
|
||||
|
||||
struct ExtraPage @local
|
||||
{
|
||||
ExtraPage* prev_page;
|
||||
void* start;
|
||||
usz mark;
|
||||
usz size;
|
||||
usz ident;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
macro usz ExtraPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
|
||||
macro bool ExtraPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
|
||||
|
||||
<*
|
||||
@require size >= 16
|
||||
*>
|
||||
fn BackedArenaAllocator*? new_backed_allocator(usz size, Allocator allocator)
|
||||
{
|
||||
BackedArenaAllocator* temp = allocator::alloc_with_padding(allocator, BackedArenaAllocator, size)!;
|
||||
temp.last_page = null;
|
||||
temp.backing_allocator = allocator;
|
||||
temp.used = 0;
|
||||
temp.capacity = size;
|
||||
return temp;
|
||||
}
|
||||
|
||||
fn void BackedArenaAllocator.destroy(&self)
|
||||
{
|
||||
self.reset(0);
|
||||
if (self.last_page) (void)self._free_page(self.last_page);
|
||||
allocator::free(self.backing_allocator, self);
|
||||
}
|
||||
|
||||
fn usz BackedArenaAllocator.mark(&self) => self.used;
|
||||
|
||||
fn void BackedArenaAllocator.release(&self, void* old_pointer, bool) @dynamic
|
||||
{
|
||||
usz old_size = *(usz*)(old_pointer - DEFAULT_SIZE_PREFIX);
|
||||
if (old_pointer + old_size == &self.data[self.used])
|
||||
{
|
||||
self.used -= old_size;
|
||||
asan::poison_memory_region(&self.data[self.used], old_size);
|
||||
}
|
||||
}
|
||||
fn void BackedArenaAllocator.reset(&self, usz mark)
|
||||
{
|
||||
ExtraPage *last_page = self.last_page;
|
||||
while (last_page && last_page.mark > mark)
|
||||
{
|
||||
self.used = last_page.mark;
|
||||
ExtraPage *to_free = last_page;
|
||||
last_page = last_page.prev_page;
|
||||
self._free_page(to_free)!!;
|
||||
}
|
||||
self.last_page = last_page;
|
||||
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
|
||||
if (!last_page)
|
||||
{
|
||||
usz cleaned = self.used - mark;
|
||||
if (cleaned > 0)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
|
||||
self.data[mark : cleaned] = 0xAA;
|
||||
$endif
|
||||
asan::poison_memory_region(&self.data[mark], cleaned);
|
||||
}
|
||||
}
|
||||
$endif
|
||||
self.used = mark;
|
||||
}
|
||||
|
||||
fn void? BackedArenaAllocator._free_page(&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
|
||||
{
|
||||
// Then the actual start pointer:
|
||||
void* real_pointer = page.start;
|
||||
|
||||
// Walk backwards to find the pointer to this page.
|
||||
ExtraPage **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)!;
|
||||
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*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
AllocChunk *chunk = pointer - AllocChunk.sizeof;
|
||||
if (chunk.size == (usz)-1)
|
||||
{
|
||||
assert(self.last_page, "Realloc of unrelated pointer");
|
||||
// First grab the page
|
||||
ExtraPage *page = pointer - ExtraPage.sizeof;
|
||||
return self._realloc_page(page, size, alignment);
|
||||
}
|
||||
|
||||
AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(data, pointer, math::min(size, chunk.size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
<*
|
||||
@require size > 0
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
*>
|
||||
fn void*? BackedArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
void* start_mem = &self.data;
|
||||
void* starting_ptr = start_mem + self.used;
|
||||
void* aligned_header_start = mem::aligned_pointer(starting_ptr, AllocChunk.alignof);
|
||||
void* mem = aligned_header_start + AllocChunk.sizeof;
|
||||
if (alignment > AllocChunk.alignof)
|
||||
{
|
||||
mem = mem::aligned_pointer(mem, alignment);
|
||||
}
|
||||
usz new_usage = (usz)(mem - start_mem) + size;
|
||||
|
||||
// Arena allocation, simple!
|
||||
if (new_usage <= self.capacity)
|
||||
{
|
||||
asan::unpoison_memory_region(starting_ptr, new_usage - self.used);
|
||||
AllocChunk* chunk_start = mem - AllocChunk.sizeof;
|
||||
chunk_start.size = size;
|
||||
self.used = new_usage;
|
||||
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
// Fallback to backing allocator
|
||||
ExtraPage* page;
|
||||
|
||||
// We have something we need to align.
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
// This is actually simpler, since it will create the offset for us.
|
||||
usz total_alloc_size = mem::aligned_offset(ExtraPage.sizeof + size, alignment);
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
|
||||
}
|
||||
void* start = mem;
|
||||
mem += mem::aligned_offset(ExtraPage.sizeof, alignment);
|
||||
page = (ExtraPage*)mem - 1;
|
||||
page.start = start;
|
||||
page.size = size | PAGE_IS_ALIGNED;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here we might need to pad
|
||||
usz padded_header_size = mem::aligned_offset(ExtraPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
usz total_alloc_size = padded_header_size + size;
|
||||
void* alloc = self.backing_allocator.acquire(total_alloc_size, init_type, 0)!;
|
||||
|
||||
// Find the page.
|
||||
page = alloc + padded_header_size - ExtraPage.sizeof;
|
||||
assert(mem::ptr_is_aligned(page, BackedArenaAllocator.alignof));
|
||||
assert(mem::ptr_is_aligned(&page.data[0], mem::DEFAULT_MEM_ALIGNMENT));
|
||||
page.start = alloc;
|
||||
page.size = size;
|
||||
}
|
||||
|
||||
// Mark it as a page
|
||||
page.ident = ~(usz)0;
|
||||
// Store when it was created
|
||||
page.mark = ++self.used;
|
||||
// Hook up the page.
|
||||
page.prev_page = self.last_page;
|
||||
self.last_page = page;
|
||||
return &page.data[0];
|
||||
}
|
||||
@@ -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), wheras the BackedArenaAllocator will have heap allocator characteristics.
|
||||
*>
|
||||
struct DynamicArenaAllocator (Allocator)
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
@@ -16,7 +27,7 @@ struct DynamicArenaAllocator (Allocator)
|
||||
@param [&inout] allocator
|
||||
@require page_size >= 128
|
||||
*>
|
||||
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator allocator)
|
||||
fn void DynamicArenaAllocator.init(&self, Allocator allocator, usz page_size)
|
||||
{
|
||||
self.page = null;
|
||||
self.unused_page = null;
|
||||
@@ -61,8 +72,8 @@ struct DynamicArenaChunk @local
|
||||
}
|
||||
|
||||
<*
|
||||
@require ptr
|
||||
@require self.page `tried to free pointer on invalid allocator`
|
||||
@require ptr != null
|
||||
@require self.page != null : `tried to free pointer on invalid allocator`
|
||||
*>
|
||||
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
{
|
||||
@@ -75,11 +86,12 @@ fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
}
|
||||
|
||||
<*
|
||||
@require size > 0 `Resize doesn't support zeroing`
|
||||
@require old_pointer != null `Resize doesn't handle null pointers`
|
||||
@require self.page `tried to realloc pointer on invalid allocator`
|
||||
@require size > 0 : `Resize doesn't support zeroing`
|
||||
@require old_pointer != null : `Resize doesn't handle null pointers`
|
||||
@require self.page != null : `tried to realloc pointer on invalid allocator`
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
DynamicArenaPage* current_page = self.page;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
@@ -105,13 +117,12 @@ 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;
|
||||
}
|
||||
|
||||
fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
|
||||
fn void DynamicArenaAllocator.reset(&self)
|
||||
{
|
||||
assert(mark == 0, "Unexpectedly reset dynamic arena allocator with mark %d", mark);
|
||||
DynamicArenaPage* page = self.page;
|
||||
DynamicArenaPage** unused_page_ptr = &self.unused_page;
|
||||
while (page)
|
||||
@@ -129,15 +140,16 @@ fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
|
||||
<*
|
||||
@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
|
||||
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);
|
||||
DynamicArenaPage*? page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
|
||||
if (catch err = page)
|
||||
{
|
||||
allocator::free(self.backing_allocator, mem);
|
||||
@@ -157,21 +169,29 @@ fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @loca
|
||||
}
|
||||
|
||||
<*
|
||||
@require size > 0 `acquire expects size > 0`
|
||||
@require size > 0 : `acquire expects size > 0`
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
DynamicArenaPage* page = self.page;
|
||||
void* ptr = {|
|
||||
|
||||
void* ptr @noinit;
|
||||
do SET_DONE:
|
||||
{
|
||||
if (!page && self.unused_page)
|
||||
{
|
||||
self.page = page = self.unused_page;
|
||||
self.unused_page = page.prev_arena;
|
||||
page.prev_arena = null;
|
||||
}
|
||||
if (!page) return self._alloc_new(size, alignment);
|
||||
if (!page)
|
||||
{
|
||||
ptr = self._alloc_new(size, alignment)!;
|
||||
break SET_DONE;
|
||||
}
|
||||
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
|
||||
usz new_used = start - page.memory + size;
|
||||
if ALLOCATE_NEW: (new_used > page.total)
|
||||
@@ -188,15 +208,15 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
|
||||
break ALLOCATE_NEW;
|
||||
}
|
||||
}
|
||||
return self._alloc_new(size, alignment);
|
||||
ptr = self._alloc_new(size, alignment)!;
|
||||
break SET_DONE;
|
||||
}
|
||||
page.used = new_used;
|
||||
assert(start + size == page.memory + page.used);
|
||||
void* mem = start;
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
|
||||
ptr = start;
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)ptr - 1;
|
||||
chunk.size = size;
|
||||
return mem;
|
||||
|}!;
|
||||
};
|
||||
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
// 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;
|
||||
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;
|
||||
@@ -12,8 +19,8 @@ struct SimpleHeapAllocator (Allocator)
|
||||
}
|
||||
|
||||
<*
|
||||
@require allocator "An underlying memory provider must be given"
|
||||
@require !self.free_list "The allocator may not be already initialized"
|
||||
@require allocator != null : "An underlying memory provider must be given"
|
||||
@require !self.free_list : "The allocator may not be already initialized"
|
||||
*>
|
||||
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
|
||||
{
|
||||
@@ -21,7 +28,7 @@ fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
|
||||
self.free_list = null;
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
fn void*? SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
@@ -30,7 +37,7 @@ fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type,
|
||||
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size);
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
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)
|
||||
@@ -52,7 +59,7 @@ fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dyn
|
||||
<*
|
||||
@require old_pointer && bytes > 0
|
||||
*>
|
||||
fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
|
||||
fn void*? SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
|
||||
{
|
||||
// Find the block header.
|
||||
Header* block = (Header*)old_pointer - 1;
|
||||
@@ -64,14 +71,14 @@ fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @loc
|
||||
return new;
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator._calloc(&self, usz bytes) @local
|
||||
fn void*? SimpleHeapAllocator._calloc(&self, usz bytes) @local
|
||||
{
|
||||
void* data = self._alloc(bytes)!;
|
||||
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
|
||||
fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
|
||||
{
|
||||
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
if (!self.free_list)
|
||||
@@ -120,7 +127,7 @@ fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
|
||||
return self._alloc(aligned_bytes);
|
||||
}
|
||||
|
||||
fn void! SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
|
||||
fn void? SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
|
||||
{
|
||||
assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes);
|
||||
char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!;
|
||||
|
||||
@@ -1,45 +1,44 @@
|
||||
// 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 = {};
|
||||
distinct LibcAllocator (Allocator, Printable) = uptr;
|
||||
|
||||
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
|
||||
fn usz! LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
|
||||
|
||||
module std::core::mem::allocator @if(env::POSIX);
|
||||
import std::os;
|
||||
import libc;
|
||||
|
||||
|
||||
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
void* data @noinit;
|
||||
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
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) ?: AllocationFailure.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 AllocationFailure.OUT_OF_MEMORY?;
|
||||
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(data = libc::malloc(bytes))) return AllocationFailure.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;
|
||||
@@ -48,13 +47,13 @@ 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
|
||||
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
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 AllocationFailure.OUT_OF_MEMORY?;
|
||||
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return mem::OUT_OF_MEMORY?;
|
||||
|
||||
$switch
|
||||
$switch:
|
||||
$case env::DARWIN:
|
||||
usz old_usable_size = darwin::malloc_size(old_ptr);
|
||||
$case env::LINUX:
|
||||
@@ -78,31 +77,31 @@ module std::core::mem::allocator @if(env::WIN32);
|
||||
import std::os::win32;
|
||||
import libc;
|
||||
|
||||
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
if (alignment > 0)
|
||||
{
|
||||
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY?;
|
||||
}
|
||||
return libc::calloc(1, bytes) ?: AllocationFailure.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 AllocationFailure.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
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
if (alignment)
|
||||
{
|
||||
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY?;
|
||||
}
|
||||
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.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
|
||||
@@ -118,17 +117,17 @@ fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
module std::core::mem::allocator @if(!env::WIN32 && !env::POSIX && env::LIBC);
|
||||
import libc;
|
||||
|
||||
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (init_type == ZERO)
|
||||
{
|
||||
void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1);
|
||||
return data ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
return data ?: mem::OUT_OF_MEMORY?;
|
||||
}
|
||||
else
|
||||
{
|
||||
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
|
||||
if (!data) return AllocationFailure.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
|
||||
@@ -137,14 +136,14 @@ 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
|
||||
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
if (alignment)
|
||||
{
|
||||
void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!;
|
||||
return data ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
return data ?: mem::OUT_OF_MEMORY?;
|
||||
}
|
||||
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.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;
|
||||
@@ -56,7 +64,7 @@ struct OnStackAllocatorHeader
|
||||
}
|
||||
|
||||
<*
|
||||
@require old_pointer
|
||||
@require old_pointer != null
|
||||
*>
|
||||
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
|
||||
{
|
||||
@@ -102,9 +110,9 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a
|
||||
<*
|
||||
@require size > 0
|
||||
@require old_pointer != null
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
*>
|
||||
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
if (!allocation_in_stack_mem(self, old_pointer))
|
||||
{
|
||||
@@ -116,15 +124,15 @@ 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;
|
||||
}
|
||||
|
||||
<*
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require size > 0
|
||||
*>
|
||||
fn void*! OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
fn void*? OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
bool aligned = alignment > 0;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
|
||||
@@ -1,29 +1,60 @@
|
||||
module std::core::mem::allocator;
|
||||
module std::core::mem::allocator @if(!(env::POSIX || env::WIN32) || !$feature(VMEM_TEMP));
|
||||
import std::io, std::math;
|
||||
|
||||
// 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)
|
||||
{
|
||||
Allocator backing_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;
|
||||
}
|
||||
|
||||
struct TempAllocatorChunk @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
struct TempAllocator (Allocator)
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
TempAllocatorPage* last_page;
|
||||
usz used;
|
||||
usz capacity;
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
|
||||
|
||||
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
|
||||
|
||||
struct TempAllocatorPage
|
||||
{
|
||||
TempAllocatorPage* prev_page;
|
||||
void* start;
|
||||
usz mark;
|
||||
usz size;
|
||||
usz ident;
|
||||
char[*] data;
|
||||
@@ -33,26 +64,109 @@ 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(usz size, Allocator allocator)
|
||||
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.capacity = size;
|
||||
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;
|
||||
return temp;
|
||||
}
|
||||
|
||||
fn void TempAllocator.destroy(&self)
|
||||
<*
|
||||
@require !self.derived
|
||||
*>
|
||||
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0)
|
||||
{
|
||||
self.reset(0);
|
||||
if (self.last_page) (void)self._free_page(self.last_page);
|
||||
allocator::free(self.backing_allocator, self);
|
||||
if (!reserve) reserve = self.reserve_size;
|
||||
usz remaining = self.capacity - self.used;
|
||||
void* mem @noinit;
|
||||
usz size @noinit;
|
||||
if (self.min_size + reserve > remaining)
|
||||
{
|
||||
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 + reserve, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
void* ptr = &self.data[start];
|
||||
TempAllocator* temp = (TempAllocator*)ptr;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(ptr, TempAllocator.sizeof);
|
||||
$endif
|
||||
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;
|
||||
self.capacity = start;
|
||||
self.derived = temp;
|
||||
return temp;
|
||||
}
|
||||
|
||||
fn usz TempAllocator.mark(&self) @dynamic => self.used;
|
||||
<*
|
||||
Reset the entire temp allocator, which will merge all the children into it.
|
||||
*>
|
||||
fn void TempAllocator.reset(&self)
|
||||
{
|
||||
TempAllocator* child = self.derived;
|
||||
if (!child) return;
|
||||
while (child)
|
||||
{
|
||||
TempAllocator* old = child;
|
||||
child = old.derived;
|
||||
old.destroy();
|
||||
}
|
||||
self.capacity = self.original_capacity;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(&self.data[self.used], self.capacity - self.used);
|
||||
$endif
|
||||
self.derived = null;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.allocated : "Only a top level allocator should be freed."
|
||||
*>
|
||||
fn void TempAllocator.free(&self)
|
||||
{
|
||||
self.reset();
|
||||
self.destroy();
|
||||
}
|
||||
|
||||
fn void TempAllocator.destroy(&self) @local
|
||||
{
|
||||
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)!!;
|
||||
}
|
||||
if (self.allocated)
|
||||
{
|
||||
allocator::free(self.backing_allocator, self);
|
||||
return;
|
||||
}
|
||||
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
|
||||
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
|
||||
self.data[0 : self.used] = 0xAA;
|
||||
$else
|
||||
asan::poison_memory_region(&self.data[0], self.used);
|
||||
$endif
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
|
||||
{
|
||||
@@ -63,40 +177,15 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
|
||||
asan::poison_memory_region(&self.data[self.used], old_size);
|
||||
}
|
||||
}
|
||||
fn void TempAllocator.reset(&self, usz mark) @dynamic
|
||||
{
|
||||
TempAllocatorPage *last_page = self.last_page;
|
||||
while (last_page && last_page.mark > mark)
|
||||
{
|
||||
self.used = last_page.mark;
|
||||
TempAllocatorPage *to_free = last_page;
|
||||
last_page = last_page.prev_page;
|
||||
self._free_page(to_free)!!;
|
||||
}
|
||||
self.last_page = last_page;
|
||||
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
|
||||
if (!last_page)
|
||||
{
|
||||
usz cleaned = self.used - mark;
|
||||
if (cleaned > 0)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
|
||||
self.data[mark : cleaned] = 0xAA;
|
||||
$endif
|
||||
asan::poison_memory_region(&self.data[mark], cleaned);
|
||||
}
|
||||
}
|
||||
$endif
|
||||
self.used = mark;
|
||||
}
|
||||
|
||||
fn void! TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
|
||||
|
||||
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
|
||||
fn void*? TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
|
||||
{
|
||||
// Then the actual start pointer:
|
||||
void* real_pointer = page.start;
|
||||
@@ -112,12 +201,13 @@ fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size,
|
||||
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
|
||||
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof;
|
||||
if (chunk.size == (usz)-1)
|
||||
@@ -127,19 +217,47 @@ fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d
|
||||
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
|
||||
return self._realloc_page(page, size, alignment);
|
||||
}
|
||||
|
||||
TempAllocatorChunk* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
|
||||
bool is_realloc_of_last = chunk.size + pointer == &self.data[self.used];
|
||||
if (is_realloc_of_last)
|
||||
{
|
||||
isz diff = size - chunk.size;
|
||||
if (diff == 0) return pointer;
|
||||
if (self.capacity - self.used > diff)
|
||||
{
|
||||
chunk.size += diff;
|
||||
self.used += diff;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
if (diff < 0)
|
||||
{
|
||||
asan::poison_memory_region(pointer + chunk.size, -diff);
|
||||
}
|
||||
else
|
||||
{
|
||||
asan::unpoison_memory_region(pointer, chunk.size);
|
||||
}
|
||||
$endif
|
||||
return pointer;
|
||||
}
|
||||
}
|
||||
void* data = self.acquire(size, NO_ZERO, alignment)!;
|
||||
usz len_to_copy = chunk.size > size ? size : chunk.size;
|
||||
mem::copy(data, pointer, len_to_copy, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
if (is_realloc_of_last)
|
||||
{
|
||||
self.used = (uptr)chunk - (uptr)&self.data;
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(chunk, TempAllocatorChunk.sizeof + chunk.size);
|
||||
$endif
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
<*
|
||||
@require size > 0
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
*>
|
||||
fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
void* start_mem = &self.data;
|
||||
@@ -202,29 +320,87 @@ fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
|
||||
|
||||
// Mark it as a page
|
||||
page.ident = ~(usz)0;
|
||||
// Store when it was created
|
||||
page.mark = ++self.used;
|
||||
// Hook up the page.
|
||||
page.prev_page = self.last_page;
|
||||
self.last_page = page;
|
||||
return &page.data[0];
|
||||
}
|
||||
|
||||
fn void! TempAllocator.print_pages(&self, File* f)
|
||||
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)
|
||||
{
|
||||
TempAllocatorPage *last_page = self.last_page;
|
||||
if (!last_page)
|
||||
{
|
||||
io::fprintf(f, "No pages.\n")!;
|
||||
return;
|
||||
}
|
||||
io::fprintf(f, "---Pages----\n")!;
|
||||
uint index = 0;
|
||||
while (last_page)
|
||||
{
|
||||
bool is_not_aligned = !(last_page.size & (1u64 << 63));
|
||||
io::fprintf(f, "%d. Alloc: %d %d at %p%s\n", ++index,
|
||||
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]")!;
|
||||
last_page = last_page.prev_page;
|
||||
}
|
||||
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)
|
||||
{
|
||||
self.destroy();
|
||||
}
|
||||
|
||||
fn void TempAllocator.destroy(&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.
|
||||
|
||||
@@ -13,11 +13,15 @@ struct Allocation
|
||||
void*[MAX_BACKTRACE] backtrace;
|
||||
}
|
||||
|
||||
def AllocMap = HashMap(<uptr, Allocation>);
|
||||
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 embarassingly single-threaded, so
|
||||
// do not use it to track allocations that cross threads.
|
||||
|
||||
struct TrackingAllocator (Allocator)
|
||||
{
|
||||
Allocator inner_allocator;
|
||||
@@ -29,12 +33,12 @@ struct TrackingAllocator (Allocator)
|
||||
<*
|
||||
Initialize a tracking allocator to wrap (and track) another allocator.
|
||||
|
||||
@param [&inout] allocator "The allocator to track"
|
||||
@param [&inout] allocator : "The allocator to track"
|
||||
*>
|
||||
fn void TrackingAllocator.init(&self, Allocator allocator)
|
||||
{
|
||||
*self = { .inner_allocator = allocator };
|
||||
self.map.new_init(allocator: allocator);
|
||||
self.map.init(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -49,13 +53,10 @@ fn void TrackingAllocator.free(&self)
|
||||
<*
|
||||
@return "the total allocated memory not yet freed."
|
||||
*>
|
||||
fn usz TrackingAllocator.allocated(&self)
|
||||
fn usz TrackingAllocator.allocated(&self) => @pool()
|
||||
{
|
||||
usz allocated = 0;
|
||||
@pool()
|
||||
{
|
||||
foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
|
||||
};
|
||||
foreach (&allocation : self.map.tvalues()) allocated += allocation.size;
|
||||
return allocated;
|
||||
}
|
||||
|
||||
@@ -71,7 +72,7 @@ fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
|
||||
|
||||
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
|
||||
{
|
||||
return self.map.value_tlist();
|
||||
return self.map.tvalues();
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -79,7 +80,7 @@ fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
|
||||
*>
|
||||
fn usz TrackingAllocator.allocation_count(&self) => self.map.count;
|
||||
|
||||
fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
fn void*? TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
void* data = self.inner_allocator.acquire(size, init_type, alignment)!;
|
||||
self.allocs_total++;
|
||||
@@ -90,7 +91,7 @@ fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, us
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
fn void*? TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
|
||||
{
|
||||
void* data = self.inner_allocator.resize(old_pointer, size, alignment)!;
|
||||
self.map.remove((uptr)old_pointer);
|
||||
@@ -116,101 +117,103 @@ fn void TrackingAllocator.clear(&self)
|
||||
self.map.clear();
|
||||
}
|
||||
|
||||
fn bool TrackingAllocator.has_leaks(&self)
|
||||
{
|
||||
return self.map.len() > 0;
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.print_report(&self) => self.fprint_report(io::stdout())!!;
|
||||
|
||||
fn void! TrackingAllocator.fprint_report(&self, OutStream out)
|
||||
{
|
||||
|
||||
fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
|
||||
{
|
||||
usz total = 0;
|
||||
usz entries = 0;
|
||||
bool leaks = false;
|
||||
@pool()
|
||||
{
|
||||
Allocation[] allocs = self.map.value_tlist();
|
||||
if (allocs.len)
|
||||
{
|
||||
if (!allocs[0].backtrace[0])
|
||||
{
|
||||
io::fprintn(out, "======== Memory Report ========")!;
|
||||
io::fprintn(out, "Size in bytes Address")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
entries++;
|
||||
total += allocation.size;
|
||||
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
|
||||
}
|
||||
io::fprintn(out, "===============================")!;
|
||||
|
||||
}
|
||||
else
|
||||
Allocation[] allocs = self.map.tvalues();
|
||||
if (allocs.len)
|
||||
{
|
||||
if (!allocs[0].backtrace[0])
|
||||
{
|
||||
io::fprintn(out, "======== Memory Report ========")!;
|
||||
io::fprintn(out, "Size in bytes Address")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
io::fprintn(out, "================================== Memory Report ==================================")!;
|
||||
io::fprintn(out, "Size in bytes Address Function ")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
entries++;
|
||||
total += allocation.size;
|
||||
BacktraceList backtraces = {};
|
||||
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
|
||||
if (allocation.backtrace[3])
|
||||
{
|
||||
trace = backtrace::symbolize_backtrace(allocation.backtrace[3:1], allocator::temp()).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
|
||||
}
|
||||
if (trace.function.len) leaks = true;
|
||||
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
|
||||
allocation.ptr, trace.function.len ? trace.function : "???",
|
||||
trace.line ? trace.line : 0)!;
|
||||
}
|
||||
io::fprintn(out, "===================================================================================")!;
|
||||
entries++;
|
||||
total += allocation.size;
|
||||
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
|
||||
}
|
||||
io::fprintn(out, "===============================")!;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
|
||||
}
|
||||
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
|
||||
io::fprintfn(out, "- Total current allocations: %d", entries)!;
|
||||
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
|
||||
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
|
||||
if (leaks)
|
||||
{
|
||||
io::fprintn(out)!;
|
||||
io::fprintn(out, "Full leak report:")!;
|
||||
io::fprintn(out, "================================== Memory Report ==================================")!;
|
||||
io::fprintn(out, "Size in bytes Address Function ")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
if (!allocation.backtrace[3])
|
||||
{
|
||||
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
|
||||
continue;
|
||||
}
|
||||
entries++;
|
||||
total += allocation.size;
|
||||
BacktraceList backtraces = {};
|
||||
usz end = MAX_BACKTRACE;
|
||||
foreach (j, val : allocation.backtrace)
|
||||
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
|
||||
if (allocation.backtrace[3])
|
||||
{
|
||||
if (!val)
|
||||
{
|
||||
end = j;
|
||||
break;
|
||||
}
|
||||
trace = backtrace::symbolize_backtrace(tmem, allocation.backtrace[3:1]).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
|
||||
}
|
||||
BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], allocator::temp())!;
|
||||
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
|
||||
foreach (trace : list)
|
||||
if (trace.function.len) leaks = true;
|
||||
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
|
||||
allocation.ptr, trace.function.len ? trace.function : "???",
|
||||
trace.line ? trace.line : 0)!;
|
||||
}
|
||||
io::fprintn(out, "===================================================================================")!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
|
||||
}
|
||||
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
|
||||
io::fprintfn(out, "- Total current allocations: %d", entries)!;
|
||||
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
|
||||
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
|
||||
if (leaks)
|
||||
{
|
||||
io::fprintn(out)!;
|
||||
io::fprintn(out, "Full leak report:")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
if (!allocation.backtrace[3])
|
||||
{
|
||||
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
|
||||
continue;
|
||||
}
|
||||
BacktraceList backtraces = {};
|
||||
usz end = MAX_BACKTRACE;
|
||||
foreach (j, val : allocation.backtrace)
|
||||
{
|
||||
if (!val)
|
||||
{
|
||||
if (trace.has_file())
|
||||
{
|
||||
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
|
||||
continue;
|
||||
}
|
||||
if (trace.is_unknown())
|
||||
{
|
||||
io::fprintfn(out, " ??? (in unknown)");
|
||||
continue;
|
||||
}
|
||||
io::fprintfn(out, " %s (source unavailable)", trace.function);
|
||||
end = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
BacktraceList list = backtrace::symbolize_backtrace(tmem, allocation.backtrace[3..(end - 1)])!;
|
||||
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
|
||||
foreach (trace : list)
|
||||
{
|
||||
if (trace.has_file())
|
||||
{
|
||||
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
|
||||
continue;
|
||||
}
|
||||
if (trace.is_unknown())
|
||||
{
|
||||
io::fprintfn(out, " ??? (in unknown)");
|
||||
continue;
|
||||
}
|
||||
io::fprintfn(out, " %s (source unavailable)", trace.function);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
252
lib/std/core/allocators/vmem.c3
Normal file
252
lib/std/core/allocators/vmem.c3
Normal file
@@ -0,0 +1,252 @@
|
||||
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
|
||||
{
|
||||
bool shrink_on_reset; // Release memory on reset
|
||||
bool protect_unused_pages; // Protect unused pages on reset
|
||||
bool scratch_released_data; // Overwrite released data with 0xAA
|
||||
}
|
||||
|
||||
<*
|
||||
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;
|
||||
}
|
||||
133
lib/std/core/ansi.c3
Normal file
133
lib/std/core/ansi.c3
Normal file
@@ -0,0 +1,133 @@
|
||||
module std::core::string::ansi;
|
||||
|
||||
enum Ansi : const 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",
|
||||
}
|
||||
|
||||
<*
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return string::format(tmem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
|
||||
}
|
||||
@@ -2,10 +2,29 @@ module std::core::array;
|
||||
import std::core::array::slice;
|
||||
|
||||
<*
|
||||
Returns true if the array contains at least one element, else false
|
||||
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@require @typekind(array) == SLICE || @typekind(array) == ARRAY
|
||||
@require @typeis(array[0], $typeof(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
|
||||
@return "the first index of the element"
|
||||
@return! SearchResult.MISSING
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro index_of(array, element)
|
||||
{
|
||||
@@ -13,10 +32,18 @@ macro index_of(array, element)
|
||||
{
|
||||
if (*e == element) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
<*
|
||||
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 @typekind(array_ptr) == POINTER
|
||||
@require @typekind(*array_ptr) == VECTOR || @typekind(*array_ptr) == ARRAY
|
||||
@require @typekind((*array_ptr)[0]) == VECTOR || @typekind((*array_ptr)[0]) == ARRAY
|
||||
@@ -26,15 +53,17 @@ 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! SearchResult.MISSING
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro rindex_of(array, element)
|
||||
{
|
||||
@@ -42,7 +71,7 @@ macro rindex_of(array, element)
|
||||
{
|
||||
if (*e == element) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -50,13 +79,13 @@ macro rindex_of(array, element)
|
||||
|
||||
@param [in] arr1
|
||||
@param [in] arr2
|
||||
@param [&inout] allocator "The allocator to use, default is the heap allocator"
|
||||
@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 @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
|
||||
@ensure result.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro concat(arr1, arr2, Allocator allocator) @nodiscard
|
||||
macro concat(Allocator allocator, arr1, arr2) @nodiscard
|
||||
{
|
||||
var $Type = $typeof(arr1[0]);
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len);
|
||||
@@ -70,22 +99,6 @@ macro concat(arr1, arr2, Allocator allocator) @nodiscard
|
||||
}
|
||||
return result;
|
||||
}
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
|
||||
|
||||
@param [in] arr1
|
||||
@param [in] arr2
|
||||
@param [&inout] allocator "The allocator to use, default is the heap allocator"
|
||||
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
|
||||
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
|
||||
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
|
||||
@ensure return.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro concat_new(arr1, arr2, Allocator allocator = allocator::heap()) @nodiscard
|
||||
{
|
||||
return concat(arr1, arr2, allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice containing the concatenation of them,
|
||||
allocated using the temp allocator.
|
||||
@@ -94,100 +107,7 @@ macro concat_new(arr1, arr2, Allocator allocator = allocator::heap()) @nodiscard
|
||||
@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 @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
|
||||
@ensure return.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro tconcat(arr1, arr2) @nodiscard => concat(arr1, arr2, allocator::temp());
|
||||
|
||||
module std::core::array::slice(<Type>);
|
||||
|
||||
struct Slice2d
|
||||
{
|
||||
Type* ptr;
|
||||
usz inner_len;
|
||||
usz ystart;
|
||||
usz ylen;
|
||||
usz xstart;
|
||||
usz xlen;
|
||||
}
|
||||
|
||||
fn usz Slice2d.len(&self) @operator(len)
|
||||
{
|
||||
return self.ylen;
|
||||
}
|
||||
|
||||
fn usz Slice2d.count(&self)
|
||||
{
|
||||
return self.ylen * self.xlen;
|
||||
}
|
||||
|
||||
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
|
||||
{
|
||||
foreach (y, line : *self)
|
||||
{
|
||||
foreach (x, val : line)
|
||||
{
|
||||
@body({ x, y }, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
|
||||
{
|
||||
foreach (y, line : *self)
|
||||
{
|
||||
foreach (x, &val : line)
|
||||
{
|
||||
@body({ x, y }, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@require idy >= 0 && idy < self.ylen
|
||||
*>
|
||||
macro Type[] Slice2d.get_row(self, usz idy) @operator([])
|
||||
{
|
||||
return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen];
|
||||
}
|
||||
|
||||
macro Type Slice2d.get_coord(self, usz[<2>] coord)
|
||||
{
|
||||
return *self.get_coord_ref(coord);
|
||||
}
|
||||
|
||||
macro Type Slice2d.get_xy(self, x, y)
|
||||
{
|
||||
return *self.get_xy_ref(x, y);
|
||||
}
|
||||
|
||||
macro Type* Slice2d.get_xy_ref(self, x, y)
|
||||
{
|
||||
return self.ptr + self.inner_len * (y + self.ystart) + self.xstart + x;
|
||||
}
|
||||
|
||||
macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord)
|
||||
{
|
||||
return self.get_xy_ref(coord.x, coord.y);
|
||||
}
|
||||
|
||||
macro void Slice2d.set_coord(self, usz[<2>] coord, Type value)
|
||||
{
|
||||
*self.get_coord_ref(coord) = value;
|
||||
}
|
||||
|
||||
macro void Slice2d.set_xy(self, x, y, Type value)
|
||||
{
|
||||
*self.get_xy_ref(x, y) = value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require y >= 0 && y < self.ylen
|
||||
@require x >= 0 && x < self.xlen
|
||||
*>
|
||||
fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0)
|
||||
{
|
||||
if (xlen < 1) xlen = self.xlen + xlen;
|
||||
if (ylen < 1) ylen = self.ylen + ylen;
|
||||
return { self.ptr, self.inner_len, y + self.ystart, ylen, x + self.xstart, xlen };
|
||||
}
|
||||
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
|
||||
114
lib/std/core/ascii.c3
Normal file
114
lib/std/core/ascii.c3
Normal file
@@ -0,0 +1,114 @@
|
||||
<*
|
||||
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' };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// 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;
|
||||
@@ -88,93 +88,97 @@ 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 @is_array_or_slice_of_char(bytes) : "argument must be an array, a pointer to an array or a slice of char"
|
||||
@require is_bitorder($Type) : "type must be a bitorder integer"
|
||||
*>
|
||||
macro read(bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$switch @typekind(bytes):
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$endswitch
|
||||
return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
|
||||
}
|
||||
|
||||
<*
|
||||
@require is_arrayptr_or_slice_of_char(bytes) "argument must be a pointer to an array or a slice of char"
|
||||
@require is_bitorder($Type) "type must be a bitorder integer"
|
||||
@require @is_arrayptr_or_slice_of_char(bytes) : "argument must be a pointer to an array or a slice of char"
|
||||
@require is_bitorder($Type) : "type must be a bitorder integer"
|
||||
*>
|
||||
macro write(x, bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$switch @typekind(bytes):
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$endswitch
|
||||
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
|
||||
}
|
||||
|
||||
macro is_bitorder($Type)
|
||||
{
|
||||
$switch ($Type)
|
||||
$case UShortLE:
|
||||
$case ShortLE:
|
||||
$case UIntLE:
|
||||
$case IntLE:
|
||||
$case ULongLE:
|
||||
$case LongLE:
|
||||
$case UInt128LE:
|
||||
$case Int128LE:
|
||||
$case UShortBE:
|
||||
$case ShortBE:
|
||||
$case UIntBE:
|
||||
$case IntBE:
|
||||
$case ULongBE:
|
||||
$case LongBE:
|
||||
$case UInt128BE:
|
||||
$case Int128BE:
|
||||
return true;
|
||||
$default:
|
||||
return false;
|
||||
$switch $Type:
|
||||
$case UShortLE:
|
||||
$case ShortLE:
|
||||
$case UIntLE:
|
||||
$case IntLE:
|
||||
$case ULongLE:
|
||||
$case LongLE:
|
||||
$case UInt128LE:
|
||||
$case Int128LE:
|
||||
$case UShortBE:
|
||||
$case ShortBE:
|
||||
$case UIntBE:
|
||||
$case IntBE:
|
||||
$case ULongBE:
|
||||
$case LongBE:
|
||||
$case UInt128BE:
|
||||
$case Int128BE:
|
||||
return true;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_array_or_slice_of_char(bytes)
|
||||
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
|
||||
}
|
||||
@@ -4,29 +4,63 @@
|
||||
module std::core::builtin;
|
||||
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.
|
||||
|
||||
You can use `@is_empty_macro_slot()` and `@is_valid_macro_slot()` to figure out whether
|
||||
the argument has been used or not.
|
||||
|
||||
An example:
|
||||
|
||||
```c3
|
||||
macro foo(a, #b = EMPTY_MACRO_SLOT)
|
||||
{
|
||||
$if @is_valid_macro_slot(#b):
|
||||
return invoke_foo2(a, #b);
|
||||
$else
|
||||
return invoke_foo1(a);
|
||||
$endif
|
||||
}
|
||||
*>
|
||||
const EmptySlot EMPTY_MACRO_SLOT @builtin = 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);
|
||||
|
||||
<*
|
||||
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.
|
||||
*/
|
||||
fault IteratorResult { NO_MORE_ELEMENT }
|
||||
faultdef NO_MORE_ELEMENT @builtin;
|
||||
|
||||
/*
|
||||
Use `SearchResult` when trying to return a value from some collection but the element is missing.
|
||||
*/
|
||||
fault SearchResult { MISSING }
|
||||
faultdef NOT_FOUND @builtin;
|
||||
|
||||
/*
|
||||
Use `CastResult` when an attempt at conversion fails.
|
||||
*/
|
||||
fault CastResult { TYPE_MISMATCH }
|
||||
faultdef TYPE_MISMATCH @builtin;
|
||||
|
||||
|
||||
def VoidFn = fn void();
|
||||
alias VoidFn = fn void();
|
||||
|
||||
<*
|
||||
Stores a variable on the stack, then restores it at the end of the
|
||||
macro scope.
|
||||
|
||||
@param #variable `the variable to store and restore`
|
||||
@param #variable : `the variable to store and restore`
|
||||
@require values::@is_lvalue(#variable)
|
||||
*>
|
||||
macro void @scope(#variable; @body) @builtin
|
||||
@@ -38,7 +72,7 @@ macro void @scope(#variable; @body) @builtin
|
||||
|
||||
<*
|
||||
Swap two variables
|
||||
@require $defined(#a = #b, #b = #a) `The values must be mutually assignable`
|
||||
@require $defined(#a = #b, #b = #a) : `The values must be mutually assignable`
|
||||
*>
|
||||
macro void @swap(#a, #b) @builtin
|
||||
{
|
||||
@@ -50,26 +84,53 @@ macro void @swap(#a, #b) @builtin
|
||||
<*
|
||||
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`
|
||||
@param v : `the any to convert to the given type.`
|
||||
@param $Type : `the type to convert to`
|
||||
@return `The any.ptr converted to its type.`
|
||||
@ensure @typeis(return, $Type*)
|
||||
@return! CastResult.TYPE_MISMATCH
|
||||
@return? TYPE_MISMATCH
|
||||
*>
|
||||
macro anycast(any v, $Type) @builtin
|
||||
{
|
||||
if (v.type != $Type.typeid) return CastResult.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)
|
||||
macro bool @assignable_to(#foo, $Type) @const @builtin => $defined(*&&($Type){} = #foo);
|
||||
|
||||
macro @addr(#val) @builtin
|
||||
{
|
||||
@pool()
|
||||
$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
|
||||
{
|
||||
return $typeof(#value).kindof;
|
||||
}
|
||||
|
||||
macro bool @typeis(#value, $Type) @const @builtin
|
||||
{
|
||||
return $typeof(#value).typeid == $Type.typeid;
|
||||
}
|
||||
|
||||
|
||||
fn bool print_backtrace(String message, int backtraces_to_ignore) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
|
||||
{
|
||||
void*[256] buffer;
|
||||
void*[] backtraces = backtrace::capture_current(&buffer);
|
||||
backtraces_to_ignore++;
|
||||
@stack_mem(2048; Allocator mem)
|
||||
{
|
||||
void*[256] buffer;
|
||||
void*[] backtraces = backtrace::capture_current(&buffer);
|
||||
backtraces_to_ignore++;
|
||||
BacktraceList! backtrace = backtrace::symbolize_backtrace(backtraces, allocator::temp());
|
||||
BacktraceList? backtrace = backtrace::symbolize_backtrace(mem, backtraces);
|
||||
if (catch backtrace) return false;
|
||||
if (backtrace.len() <= backtraces_to_ignore) return false;
|
||||
io::eprint("\nERROR: '");
|
||||
@@ -91,22 +152,24 @@ fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIV
|
||||
}
|
||||
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
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);
|
||||
}
|
||||
$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();
|
||||
@@ -114,62 +177,67 @@ macro void abort(String string = "Unrecoverable error reached", ...) @builtin @n
|
||||
|
||||
bool in_panic @local = 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();
|
||||
}
|
||||
|
||||
def PanicFn = fn void(String message, String file, String function, uint line);
|
||||
alias PanicFn = fn void(String message, String file, String function, uint line);
|
||||
|
||||
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.new_init(allocator: 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
|
||||
}
|
||||
|
||||
<*
|
||||
Marks the path as unreachable. This will panic in safe mode, and in fast will simply be assumed
|
||||
never happens.
|
||||
@param [in] string "The panic message or format string"
|
||||
@param [in] string : "The panic message or format string"
|
||||
*>
|
||||
macro void unreachable(String string = "Unreachable statement reached.", ...) @builtin @noreturn
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE:
|
||||
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
|
||||
$endif;
|
||||
$else
|
||||
$$unreachable();
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Marks the path as unsupported, this is similar to unreachable.
|
||||
@param [in] string "The error message"
|
||||
@param [in] string : "The error message"
|
||||
*>
|
||||
macro void unsupported(String string = "Unsupported function invoked") @builtin @noreturn
|
||||
{
|
||||
@@ -201,10 +269,10 @@ macro any.as_inner(&self)
|
||||
}
|
||||
|
||||
<*
|
||||
@param expr "the expression to cast"
|
||||
@param $Type "the type to cast to"
|
||||
@param expr : "the expression to cast"
|
||||
@param $Type : "the type to cast to"
|
||||
|
||||
@require $sizeof(expr) == $Type.sizeof "Cannot bitcast between types of different size."
|
||||
@require $sizeof(expr) == $Type.sizeof : "Cannot bitcast between types of different size."
|
||||
@ensure @typeis(return, $Type)
|
||||
*>
|
||||
macro bitcast(expr, $Type) @builtin
|
||||
@@ -219,11 +287,11 @@ 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`
|
||||
@param $Type : `The type of the enum`
|
||||
@param [in] enum_name : `The name of the enum to search for`
|
||||
@require $Type.kindof == ENUM : `Only enums may be used`
|
||||
@ensure @typeis(return, $Type)
|
||||
@return! SearchResult.MISSING
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro enum_by_name($Type, String enum_name) @builtin
|
||||
{
|
||||
@@ -232,37 +300,36 @@ macro enum_by_name($Type, String enum_name) @builtin
|
||||
{
|
||||
if (name == enum_name) return $Type.from_ordinal(i);
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
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`
|
||||
@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_to(value, $typeof(($Type){}.#value)) : `Expected the value to match the type of the associated value`
|
||||
@ensure @typeis(return, $Type)
|
||||
@return! SearchResult.MISSING
|
||||
@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 SearchResult.MISSING?;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
<*
|
||||
Mark an expression as likely to be true
|
||||
|
||||
@param #value "expression to be marked likely"
|
||||
@param $probability "in the range 0 - 1"
|
||||
@param #value : "expression to be marked likely"
|
||||
@param $probability : "in the range 0 - 1"
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro bool @likely(bool #value, $probability = 1.0) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch:
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value;
|
||||
$case $probability == 1.0:
|
||||
@@ -275,13 +342,13 @@ macro bool @likely(bool #value, $probability = 1.0) @builtin
|
||||
<*
|
||||
Mark an expression as unlikely to be true
|
||||
|
||||
@param #value "expression to be marked unlikely"
|
||||
@param $probability "in the range 0 - 1"
|
||||
@param #value : "expression to be marked unlikely"
|
||||
@param $probability : "in the range 0 - 1"
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro bool @unlikely(bool #value, $probability = 1.0) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch:
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value;
|
||||
$case $probability == 1.0:
|
||||
@@ -293,12 +360,12 @@ macro bool @unlikely(bool #value, $probability = 1.0) @builtin
|
||||
|
||||
<*
|
||||
@require values::@is_int(#value) || values::@is_bool(#value)
|
||||
@require $assignable(expected, $typeof(#value))
|
||||
@require @assignable_to(expected, $typeof(#value))
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro @expect(#value, expected, $probability = 1.0) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch:
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value == expected;
|
||||
$case $probability == 1.0:
|
||||
@@ -323,9 +390,9 @@ enum PrefetchLocality
|
||||
<*
|
||||
Prefetch a pointer.
|
||||
|
||||
@param [in] ptr `Pointer to prefetch`
|
||||
@param $locality `Locality ranging from none to extremely local`
|
||||
@param $write `Prefetch for write, otherwise prefetch for read.`
|
||||
@param [in] ptr : `Pointer to prefetch`
|
||||
@param $locality : `Locality ranging from none to extremely local`
|
||||
@param $write : `Prefetch for write, otherwise prefetch for read.`
|
||||
*>
|
||||
macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
|
||||
{
|
||||
@@ -343,16 +410,17 @@ macro swizzle2(v, v2, ...) @builtin
|
||||
{
|
||||
return $$swizzle2(v, v2, $vasplat);
|
||||
}
|
||||
|
||||
<*
|
||||
Return the excuse in the Optional if it is Empty, otherwise
|
||||
return a null fault.
|
||||
|
||||
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
|
||||
*>
|
||||
macro anyfault @catch(#expr) @builtin
|
||||
macro fault @catch(#expr) @builtin
|
||||
{
|
||||
if (catch f = #expr) return f;
|
||||
return anyfault {};
|
||||
return {};
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -368,7 +436,68 @@ macro bool @ok(#expr) @builtin
|
||||
}
|
||||
|
||||
<*
|
||||
@require $defined(&#value, (char*)&#value) "This must be a value that can be viewed as a char array"
|
||||
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 @assignable_to(#expr!!, $typeof(#v)) : `Type of #expr must be an optional of #v's type`
|
||||
*>
|
||||
macro void? @try(#v, #expr) @builtin
|
||||
{
|
||||
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 @assignable_to(#expr!!, $typeof(#v)) : `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"
|
||||
*>
|
||||
macro char[] @as_char_view(#value) @builtin
|
||||
{
|
||||
@@ -391,33 +520,70 @@ macro @generic_hash_core(h, value)
|
||||
macro @generic_hash(value)
|
||||
{
|
||||
uint h = @generic_hash_core((uint)0x3efd4391, value);
|
||||
$for (var $cnt = 4; $cnt < $sizeof(value); $cnt += 4)
|
||||
$for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4:
|
||||
value >>= 32; // reduce value
|
||||
h = @generic_hash_core(h, value);
|
||||
$endfor
|
||||
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::encode(c);
|
||||
macro uint char[].hash(char[] c) => (uint)fnv32a::encode(c);
|
||||
macro uint String.hash(String c) => (uint)fnv32a::hash(c);
|
||||
macro uint char[].hash(char[] c) => (uint)fnv32a::hash(c);
|
||||
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
|
||||
|
||||
distinct EmptySlot = void*;
|
||||
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
|
||||
macro @is_empty_macro_slot(#arg) @builtin => @typeis(#arg, EmptySlot);
|
||||
macro @is_valid_macro_slot(#arg) @builtin => !@typeis(#arg, EmptySlot);
|
||||
<*
|
||||
@require @typekind(array_ptr) == POINTER &&& @typekind(*array_ptr) == ARRAY
|
||||
*>
|
||||
macro uint hash_array(array_ptr) @local
|
||||
{
|
||||
return (uint)fnv32a::hash(((char*)array_ptr)[:$sizeof(*array_ptr)]);
|
||||
}
|
||||
|
||||
<*
|
||||
@require @typekind(vec) == VECTOR
|
||||
*>
|
||||
macro uint hash_vec(vec) @local
|
||||
{
|
||||
return (uint)fnv32a::hash(((char*)&&vec)[:$sizeof(vec.len * $typeof(vec).inner.sizeof)]);
|
||||
}
|
||||
|
||||
const MAX_FRAMEADDRESS = 128;
|
||||
<*
|
||||
@@ -702,7 +868,7 @@ macro void* get_returnaddress(int n)
|
||||
}
|
||||
}
|
||||
|
||||
module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
|
||||
module std::core::builtin @if((env::LINUX || env::ANDROID || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
|
||||
import libc, std::io;
|
||||
|
||||
fn void sig_panic(String message)
|
||||
|
||||
@@ -8,7 +8,7 @@ module std::core::builtin;
|
||||
*>
|
||||
macro less(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
return a.less(b);
|
||||
$case $defined(a.compare_to):
|
||||
@@ -23,7 +23,7 @@ macro less(a, b) @builtin
|
||||
*>
|
||||
macro less_eq(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
return !b.less(a);
|
||||
$case $defined(a.compare_to):
|
||||
@@ -38,7 +38,7 @@ macro less_eq(a, b) @builtin
|
||||
*>
|
||||
macro greater(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
return b.less(a);
|
||||
$case $defined(a.compare_to):
|
||||
@@ -53,7 +53,7 @@ macro greater(a, b) @builtin
|
||||
*>
|
||||
macro int compare_to(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch:
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b);
|
||||
$case $defined(a.less):
|
||||
@@ -67,7 +67,7 @@ macro int compare_to(a, b) @builtin
|
||||
*>
|
||||
macro greater_eq(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
return !a.less(b);
|
||||
$case $defined(a.compare_to):
|
||||
@@ -78,11 +78,11 @@ macro greater_eq(a, b) @builtin
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::@equatable_value(a) && types::@equatable_value(b) `values must be equatable`
|
||||
@require types::@equatable_value(a) && types::@equatable_value(b) : `values must be equatable`
|
||||
*>
|
||||
macro bool equals(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch:
|
||||
$case $defined(a.equals, a.equals(b)):
|
||||
return a.equals(b);
|
||||
$case $defined(a.compare_to, a.compare_to(b)):
|
||||
@@ -100,7 +100,7 @@ macro min(x, ...) @builtin
|
||||
return less(x, $vaarg[0]) ? x : $vaarg[0];
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
if (less($vaarg[$i], result))
|
||||
{
|
||||
result = $vaarg[$i];
|
||||
@@ -116,7 +116,7 @@ macro max(x, ...) @builtin
|
||||
return greater(x, $vaarg[0]) ? x : $vaarg[0];
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
if (greater($vaarg[$i], result))
|
||||
{
|
||||
result = $vaarg[$i];
|
||||
|
||||
@@ -16,18 +16,18 @@ $assert C_SHORT_SIZE <= C_INT_SIZE;
|
||||
$assert C_INT_SIZE <= C_LONG_SIZE;
|
||||
$assert C_LONG_SIZE <= C_LONG_LONG_SIZE;
|
||||
|
||||
def CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
|
||||
def CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
|
||||
def CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
|
||||
def CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
|
||||
def CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
|
||||
def CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
|
||||
def CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
def CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
def CSChar = ichar;
|
||||
def CUChar = char;
|
||||
alias CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
|
||||
alias CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
|
||||
alias CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
|
||||
alias CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
|
||||
alias CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
|
||||
alias CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
|
||||
alias CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
alias CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
alias CSChar = ichar;
|
||||
alias CUChar = char;
|
||||
|
||||
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
|
||||
alias CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
|
||||
|
||||
enum CBool : char
|
||||
{
|
||||
@@ -38,7 +38,7 @@ enum CBool : char
|
||||
// Helper macros
|
||||
macro typeid signed_int_from_bitsize(usz $bitsize) @private
|
||||
{
|
||||
$switch ($bitsize)
|
||||
$switch $bitsize:
|
||||
$case 128: return int128.typeid;
|
||||
$case 64: return long.typeid;
|
||||
$case 32: return int.typeid;
|
||||
@@ -50,7 +50,7 @@ macro typeid signed_int_from_bitsize(usz $bitsize) @private
|
||||
|
||||
macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
|
||||
{
|
||||
$switch ($bitsize)
|
||||
$switch $bitsize:
|
||||
$case 128: return uint128.typeid;
|
||||
$case 64: return ulong.typeid;
|
||||
$case 32: return uint.typeid;
|
||||
|
||||
@@ -10,30 +10,31 @@ const uint UTF16_SURROGATE_LOW_VALUE @private = 0xDC00;
|
||||
const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
|
||||
|
||||
<*
|
||||
@param c `The utf32 codepoint to convert`
|
||||
@param [out] output `the resulting buffer`
|
||||
@param c : `The utf32 codepoint to convert`
|
||||
@param [out] output : `the resulting buffer`
|
||||
@return? string::CONVERSION_FAILED
|
||||
*>
|
||||
fn usz! char32_to_utf8(Char32 c, char[] output)
|
||||
fn usz? char32_to_utf8(Char32 c, char[] output)
|
||||
{
|
||||
if (!output.len) return UnicodeResult.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 UnicodeResult.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 UnicodeResult.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 UnicodeResult.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));
|
||||
@@ -41,15 +42,15 @@ fn usz! char32_to_utf8(Char32 c, char[] output)
|
||||
return 4;
|
||||
default:
|
||||
// 0x10FFFF and above is not defined.
|
||||
return UnicodeResult.CONVERSION_FAILED?;
|
||||
return string::CONVERSION_FAILED?;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Convert a code pointer into 1-2 UTF16 characters.
|
||||
|
||||
@param c `The character to convert.`
|
||||
@param [inout] output `the resulting UTF16 buffer to write to.`
|
||||
@param c : `The character to convert.`
|
||||
@param [inout] output : `the resulting UTF16 buffer to write to.`
|
||||
*>
|
||||
fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
|
||||
{
|
||||
@@ -69,11 +70,11 @@ fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
|
||||
<*
|
||||
Convert 1-2 UTF16 data points into UTF8.
|
||||
|
||||
@param [in] ptr `The UTF16 data to convert.`
|
||||
@param [inout] available `amount of UTF16 data available.`
|
||||
@param [inout] output `the resulting utf8 buffer to write to.`
|
||||
@param [in] ptr : `The UTF16 data to convert.`
|
||||
@param [inout] available : `amount of UTF16 data available.`
|
||||
@param [inout] output : `the resulting utf8 buffer to write to.`
|
||||
*>
|
||||
fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
{
|
||||
Char16 high = *ptr;
|
||||
if (high & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
|
||||
@@ -83,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 UnicodeResult.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 UnicodeResult.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 UnicodeResult.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
|
||||
@@ -101,8 +102,8 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
*available = 2;
|
||||
}
|
||||
<*
|
||||
@param c `The utf32 codepoint to convert`
|
||||
@param [inout] output `the resulting buffer`
|
||||
@param c : `The utf32 codepoint to convert`
|
||||
@param [inout] output : `the resulting buffer`
|
||||
*>
|
||||
fn usz char32_to_utf8_unsafe(Char32 c, char** output)
|
||||
{
|
||||
@@ -130,14 +131,14 @@ fn usz char32_to_utf8_unsafe(Char32 c, char** output)
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] ptr `pointer to the first character to parse`
|
||||
@param [inout] size `Set to max characters to read, set to characters read`
|
||||
@param [in] ptr : `pointer to the first character to parse`
|
||||
@param [inout] size : `Set to max characters to read, set to characters read`
|
||||
@return `the parsed 32 bit codepoint`
|
||||
*>
|
||||
fn Char32! utf8_to_char32(char* ptr, usz* size)
|
||||
fn Char32? utf8_to_char32(char* ptr, usz* size)
|
||||
{
|
||||
usz max_size = *size;
|
||||
if (max_size < 1) return UnicodeResult.INVALID_UTF8?;
|
||||
if (max_size < 1) return string::INVALID_UTF8?;
|
||||
char c = (ptr++)[0];
|
||||
|
||||
if ((c & 0x80) == 0)
|
||||
@@ -147,45 +148,45 @@ fn Char32! utf8_to_char32(char* ptr, usz* size)
|
||||
}
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
if (max_size < 2) return UnicodeResult.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 UnicodeResult.INVALID_UTF8?;
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
if (max_size < 3) return UnicodeResult.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 UnicodeResult.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 UnicodeResult.INVALID_UTF8?;
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if (max_size < 4) return UnicodeResult.INVALID_UTF8?;
|
||||
if ((c & 0xF8) != 0xF0) return UnicodeResult.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 UnicodeResult.INVALID_UTF8?;
|
||||
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
uc += (c & 0x3F) << 12;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.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 UnicodeResult.INVALID_UTF8?;
|
||||
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
|
||||
<*
|
||||
@param utf8 `An UTF-8 encoded slice of bytes`
|
||||
@param utf8 : `An UTF-8 encoded slice of bytes`
|
||||
@return `the number of encoded code points`
|
||||
*>
|
||||
fn usz utf8_codepoints(String utf8)
|
||||
@@ -200,7 +201,7 @@ fn usz utf8_codepoints(String utf8)
|
||||
|
||||
<*
|
||||
Calculate the UTF8 length required to encode an UTF32 array.
|
||||
@param [in] utf32 `the utf32 data to calculate from`
|
||||
@param [in] utf32 : `the utf32 data to calculate from`
|
||||
@return `the length of the resulting UTF8 array`
|
||||
*>
|
||||
fn usz utf8len_for_utf32(Char32[] utf32)
|
||||
@@ -225,7 +226,7 @@ fn usz utf8len_for_utf32(Char32[] utf32)
|
||||
|
||||
<*
|
||||
Calculate the UTF8 length required to encode an UTF16 array.
|
||||
@param [in] utf16 `the utf16 data to calculate from`
|
||||
@param [in] utf16 : `the utf16 data to calculate from`
|
||||
@return `the length of the resulting UTF8 array`
|
||||
*>
|
||||
fn usz utf8len_for_utf16(Char16[] utf16)
|
||||
@@ -257,7 +258,7 @@ fn usz utf8len_for_utf16(Char16[] utf16)
|
||||
|
||||
<*
|
||||
Calculate the UTF16 length required to encode a UTF8 array.
|
||||
@param utf8 `the utf8 data to calculate from`
|
||||
@param utf8 : `the utf8 data to calculate from`
|
||||
@return `the length of the resulting UTF16 array`
|
||||
*>
|
||||
fn usz utf16len_for_utf8(String utf8)
|
||||
@@ -280,7 +281,7 @@ fn usz utf16len_for_utf8(String utf8)
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] utf32 `the UTF32 array to check the length for`
|
||||
@param [in] utf32 : `the UTF32 array to check the length for`
|
||||
@return `the required length of an UTF16 array to hold the UTF32 data.`
|
||||
*>
|
||||
fn usz utf16len_for_utf32(Char32[] utf32)
|
||||
@@ -300,7 +301,7 @@ fn usz utf16len_for_utf32(Char32[] utf32)
|
||||
@param [out] utf8_buffer
|
||||
@return `the number of bytes written.`
|
||||
*>
|
||||
fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
|
||||
fn usz? utf32to8(Char32[] utf32, char[] utf8_buffer)
|
||||
{
|
||||
char[] buffer = utf8_buffer;
|
||||
foreach (uc : utf32)
|
||||
@@ -320,7 +321,7 @@ fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
|
||||
@param [out] utf32_buffer
|
||||
@return `the number of Char32s written.`
|
||||
*>
|
||||
fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
Char32* ptr = utf32_buffer.ptr;
|
||||
@@ -328,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 UnicodeResult.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;
|
||||
@@ -344,10 +345,10 @@ fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf16 `The UTF16 array containing the data to convert.`
|
||||
@param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF16 data.`
|
||||
@param [in] utf16 : `The UTF16 array containing the data to convert.`
|
||||
@param [out] utf8_buffer : `the (sufficiently large) buffer to hold the UTF16 data.`
|
||||
*>
|
||||
fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
|
||||
fn void? utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
|
||||
{
|
||||
usz len16 = utf16.len;
|
||||
for (usz i = 0; i < len16;)
|
||||
@@ -363,10 +364,10 @@ fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf8 `The UTF8 buffer containing the data to convert.`
|
||||
@param [out] utf32_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
@param [in] utf8 : `The UTF8 buffer containing the data to convert.`
|
||||
@param [out] utf32_buffer : `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
*>
|
||||
fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
|
||||
fn void? utf8to32_unsafe(String utf8, Char32* utf32_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
for (usz i = 0; i < len;)
|
||||
@@ -383,10 +384,10 @@ fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf8 `The UTF8 buffer containing the data to convert.`
|
||||
@param [out] utf16_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
@param [in] utf8 : `The UTF8 buffer containing the data to convert.`
|
||||
@param [out] utf16_buffer : `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
*>
|
||||
fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
|
||||
fn void? utf8to16_unsafe(String utf8, Char16* utf16_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
for (usz i = 0; i < len;)
|
||||
@@ -403,8 +404,8 @@ fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
|
||||
checking. This will assume the buffer is sufficiently large to hold
|
||||
the converted data.
|
||||
|
||||
@param [in] utf32 `The UTF32 buffer containing the data to convert.`
|
||||
@param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
@param [in] utf32 : `The UTF32 buffer containing the data to convert.`
|
||||
@param [out] utf8_buffer : `the (sufficiently large) buffer to hold the UTF8 data.`
|
||||
*>
|
||||
fn void utf32to8_unsafe(Char32[] utf32, char* utf8_buffer)
|
||||
{
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
module std::core::dstring;
|
||||
import std::io;
|
||||
|
||||
distinct DString (OutStream) = DStringOpaque*;
|
||||
distinct DStringOpaque = void;
|
||||
<*
|
||||
The DString offers a dynamic string builder.
|
||||
*>
|
||||
typedef DString (OutStream) = DStringOpaque*;
|
||||
typedef DStringOpaque = void;
|
||||
|
||||
const usz MIN_CAPACITY @private = 16;
|
||||
|
||||
<*
|
||||
@require !self.data() "String already initialized"
|
||||
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.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap())
|
||||
fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY)
|
||||
{
|
||||
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
|
||||
StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
|
||||
@@ -20,25 +28,29 @@ fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator alloca
|
||||
}
|
||||
|
||||
<*
|
||||
@require !self.data() "String already initialized"
|
||||
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.temp_init(&self, usz capacity = MIN_CAPACITY)
|
||||
fn DString DString.tinit(&self, usz capacity = MIN_CAPACITY)
|
||||
{
|
||||
self.new_init(capacity, allocator::temp()) @inline;
|
||||
return *self;
|
||||
return self.init(tmem, capacity) @inline;
|
||||
}
|
||||
|
||||
fn DString new_with_capacity(usz capacity, Allocator allocator = allocator::heap())
|
||||
fn DString new_with_capacity(Allocator allocator, usz capacity)
|
||||
{
|
||||
return DString{}.new_init(capacity, allocator);
|
||||
return (DString){}.init(allocator, capacity);
|
||||
}
|
||||
|
||||
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, allocator::temp()) @inline;
|
||||
fn DString temp_with_capacity(usz capacity) => new_with_capacity(tmem, capacity) @inline;
|
||||
|
||||
fn DString new(String c = "", Allocator allocator = allocator::heap())
|
||||
fn DString new(Allocator allocator, String c = "")
|
||||
{
|
||||
usz len = c.len;
|
||||
StringData* data = (StringData*)new_with_capacity(len, allocator);
|
||||
StringData* data = (StringData*)new_with_capacity(allocator, len);
|
||||
if (len)
|
||||
{
|
||||
data.len = len;
|
||||
@@ -47,7 +59,7 @@ fn DString new(String c = "", Allocator allocator = allocator::heap())
|
||||
return (DString)data;
|
||||
}
|
||||
|
||||
fn DString temp_new(String s = "") => new(s, allocator::temp()) @inline;
|
||||
fn DString temp(String s = "") => new(tmem, s) @inline;
|
||||
|
||||
|
||||
fn void DString.replace_char(self, char ch, char replacement)
|
||||
@@ -70,7 +82,8 @@ fn void DString.replace(&self, String needle, String replacement)
|
||||
self.replace_char(needle[0], replacement[0]);
|
||||
return;
|
||||
}
|
||||
@pool(data.allocator) {
|
||||
@pool()
|
||||
{
|
||||
String str = self.tcopy_str();
|
||||
self.clear();
|
||||
usz len = str.len;
|
||||
@@ -99,16 +112,16 @@ fn void DString.replace(&self, String needle, String replacement)
|
||||
};
|
||||
}
|
||||
|
||||
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
|
||||
fn DString DString.concat(self, Allocator allocator, DString b) @nodiscard
|
||||
{
|
||||
DString string;
|
||||
string.new_init(self.len() + b.len(), allocator);
|
||||
string.init(allocator, self.len() + b.len());
|
||||
string.append(self);
|
||||
string.append(b);
|
||||
return string;
|
||||
}
|
||||
|
||||
fn DString DString.temp_concat(self, DString b) => self.new_concat(b, allocator::temp());
|
||||
fn DString DString.tconcat(self, DString b) => self.concat(tmem, b);
|
||||
|
||||
fn ZString DString.zstr_view(&self)
|
||||
{
|
||||
@@ -157,7 +170,7 @@ fn String DString.str_view(self)
|
||||
|
||||
<*
|
||||
@require index < self.len()
|
||||
@require self.data() "Empty string"
|
||||
@require self.data() != null : "Empty string"
|
||||
*>
|
||||
fn char DString.char_at(self, usz index) @operator([])
|
||||
{
|
||||
@@ -166,7 +179,7 @@ fn char DString.char_at(self, usz index) @operator([])
|
||||
|
||||
<*
|
||||
@require index < self.len()
|
||||
@require self.data() "Empty string"
|
||||
@require self.data() != null : "Empty string"
|
||||
*>
|
||||
fn char* DString.char_ref(&self, usz index) @operator(&[])
|
||||
{
|
||||
@@ -218,23 +231,18 @@ fn usz DString.append_char32(&self, Char32 c)
|
||||
return n;
|
||||
}
|
||||
|
||||
fn DString DString.tcopy(&self) => self.copy(allocator::temp());
|
||||
fn DString DString.tcopy(&self) => self.copy(tmem);
|
||||
|
||||
fn DString DString.copy(self, Allocator allocator = null)
|
||||
fn DString DString.copy(self, Allocator allocator) @nodiscard
|
||||
{
|
||||
if (!self)
|
||||
{
|
||||
if (allocator) return new_with_capacity(0, allocator);
|
||||
return (DString)null;
|
||||
}
|
||||
if (!self) return new(allocator);
|
||||
StringData* data = self.data();
|
||||
if (!allocator) allocator = allocator::heap();
|
||||
DString new_string = new_with_capacity(data.capacity, allocator);
|
||||
DString new_string = new_with_capacity(allocator, data.capacity);
|
||||
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
|
||||
return new_string;
|
||||
}
|
||||
|
||||
fn ZString DString.copy_zstr(self, Allocator allocator = allocator::heap())
|
||||
fn ZString DString.copy_zstr(self, Allocator allocator) @nodiscard
|
||||
{
|
||||
usz str_len = self.len();
|
||||
if (!str_len)
|
||||
@@ -248,12 +256,12 @@ fn ZString DString.copy_zstr(self, Allocator allocator = allocator::heap())
|
||||
return (ZString)zstr;
|
||||
}
|
||||
|
||||
fn String DString.copy_str(self, Allocator allocator = allocator::heap())
|
||||
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(allocator::temp()) @inline;
|
||||
fn String DString.tcopy_str(self) @nodiscard => self.copy_str(tmem) @inline;
|
||||
|
||||
fn bool DString.equals(self, DString other_string)
|
||||
{
|
||||
@@ -303,7 +311,7 @@ fn void DString.append_chars(&self, String str)
|
||||
if (!other_len) return;
|
||||
if (!*self)
|
||||
{
|
||||
*self = new(str);
|
||||
*self = temp(str);
|
||||
return;
|
||||
}
|
||||
self.reserve(other_len);
|
||||
@@ -312,7 +320,7 @@ fn void DString.append_chars(&self, String str)
|
||||
data.len += other_len;
|
||||
}
|
||||
|
||||
fn Char32[] DString.copy_utf32(&self, Allocator allocator = allocator::heap())
|
||||
fn Char32[] DString.copy_utf32(&self, Allocator allocator)
|
||||
{
|
||||
return self.str_view().to_utf32(allocator) @inline!!;
|
||||
}
|
||||
@@ -330,13 +338,13 @@ fn void DString.clear(self)
|
||||
self.data().len = 0;
|
||||
}
|
||||
|
||||
fn usz! DString.write(&self, char[] buffer) @dynamic
|
||||
fn usz? DString.write(&self, char[] buffer) @dynamic
|
||||
{
|
||||
self.append_chars((String)buffer);
|
||||
return buffer.len;
|
||||
}
|
||||
|
||||
fn void! DString.write_byte(&self, char c) @dynamic
|
||||
fn void? DString.write_byte(&self, char c) @dynamic
|
||||
{
|
||||
self.append_char(c);
|
||||
}
|
||||
@@ -345,7 +353,7 @@ fn void DString.append_char(&self, char c)
|
||||
{
|
||||
if (!*self)
|
||||
{
|
||||
*self = new_with_capacity(MIN_CAPACITY);
|
||||
*self = temp_with_capacity(MIN_CAPACITY);
|
||||
}
|
||||
self.reserve(1);
|
||||
StringData* data = self.data();
|
||||
@@ -355,7 +363,7 @@ fn void DString.append_char(&self, char c)
|
||||
<*
|
||||
@require start < self.len()
|
||||
@require end < self.len()
|
||||
@require end >= start "End must be same or equal to the start"
|
||||
@require end >= start : "End must be same or equal to the start"
|
||||
*>
|
||||
fn void DString.delete_range(&self, usz start, usz end)
|
||||
{
|
||||
@@ -387,7 +395,7 @@ fn void DString.delete(&self, usz start, usz len = 1)
|
||||
macro void DString.append(&self, value)
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
$switch ($Type)
|
||||
$switch $Type:
|
||||
$case char:
|
||||
$case ichar:
|
||||
self.append_char(value);
|
||||
@@ -398,7 +406,7 @@ macro void DString.append(&self, value)
|
||||
$case Char32:
|
||||
self.append_char32(value);
|
||||
$default:
|
||||
$switch
|
||||
$switch:
|
||||
$case $defined((Char32)value):
|
||||
self.append_char32((Char32)value);
|
||||
$case $defined((String)value):
|
||||
@@ -519,7 +527,7 @@ fn usz DString.insert_utf32_at(&self, usz index, Char32[] chars)
|
||||
macro void DString.insert_at(&self, usz index, value)
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
$switch ($Type)
|
||||
$switch $Type:
|
||||
$case char:
|
||||
$case ichar:
|
||||
self.insert_char_at(index, value);
|
||||
@@ -530,7 +538,7 @@ macro void DString.insert_at(&self, usz index, value)
|
||||
$case Char32:
|
||||
self.insert_char32_at(index, value);
|
||||
$default:
|
||||
$switch
|
||||
$switch:
|
||||
$case $defined((Char32)value):
|
||||
self.insert_char32_at(index, (Char32)value);
|
||||
$case $defined((String)value):
|
||||
@@ -541,21 +549,19 @@ macro void DString.insert_at(&self, usz index, value)
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn usz! DString.appendf(&self, String format, args...) @maydiscard
|
||||
import libc;
|
||||
fn usz? DString.appendf(&self, String format, args...) @maydiscard
|
||||
{
|
||||
if (!self.data()) self.new_init(format.len + 20);
|
||||
@pool(self.data().allocator)
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_string_append_fn, self);
|
||||
return formatter.vprintf(format, args);
|
||||
};
|
||||
if (!self.data()) self.tinit(format.len + 20);
|
||||
Formatter formatter;
|
||||
formatter.init(&out_string_append_fn, self);
|
||||
return formatter.vprintf(format, args);
|
||||
}
|
||||
|
||||
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
|
||||
fn usz? DString.appendfn(&self, String format, args...) @maydiscard
|
||||
{
|
||||
if (!self.data()) self.new_init(format.len + 20);
|
||||
@pool(self.data().allocator)
|
||||
if (!self.data()) self.tinit(format.len + 20);
|
||||
@pool()
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_string_append_fn, self);
|
||||
@@ -565,25 +571,25 @@ fn usz! DString.appendfn(&self, String format, args...) @maydiscard
|
||||
};
|
||||
}
|
||||
|
||||
fn DString new_join(String[] s, String joiner, Allocator allocator = allocator::heap())
|
||||
fn DString join(Allocator allocator, String[] s, String joiner) @nodiscard
|
||||
{
|
||||
if (!s.len) return (DString)null;
|
||||
if (!s.len) return new(allocator);
|
||||
usz total_size = joiner.len * s.len;
|
||||
foreach (String* &str : s)
|
||||
{
|
||||
total_size += str.len;
|
||||
}
|
||||
DString res = new_with_capacity(total_size, allocator);
|
||||
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;
|
||||
}
|
||||
|
||||
fn void! out_string_append_fn(void* data, char c) @private
|
||||
fn void? out_string_append_fn(void* data, char c) @private
|
||||
{
|
||||
DString* s = data;
|
||||
s.append_char(c);
|
||||
@@ -613,7 +619,7 @@ fn void DString.reserve(&self, usz addition)
|
||||
StringData* data = self.data();
|
||||
if (!data)
|
||||
{
|
||||
*self = dstring::new_with_capacity(addition);
|
||||
*self = dstring::temp_with_capacity(addition);
|
||||
return;
|
||||
}
|
||||
usz len = data.len + addition;
|
||||
@@ -625,7 +631,7 @@ fn void DString.reserve(&self, usz addition)
|
||||
*self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity);
|
||||
}
|
||||
|
||||
fn usz! DString.read_from_stream(&self, InStream reader)
|
||||
fn usz? DString.read_from_stream(&self, InStream reader)
|
||||
{
|
||||
if (&reader.available)
|
||||
{
|
||||
|
||||
@@ -57,6 +57,7 @@ enum OsType
|
||||
HURD,
|
||||
WASI,
|
||||
EMSCRIPTEN,
|
||||
ANDROID,
|
||||
}
|
||||
|
||||
enum ArchType
|
||||
@@ -119,6 +120,7 @@ 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;
|
||||
@@ -135,12 +137,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;
|
||||
@@ -150,15 +153,18 @@ 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 ANDROID = LIBC && OS_TYPE == ANDROID;
|
||||
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
|
||||
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
|
||||
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
|
||||
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
|
||||
const int LANGUAGE_DEV_VERSION = $$LANGUAGE_DEV_VERSION;
|
||||
const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::OPENBSD || env::DARWIN || env::WIN32;
|
||||
|
||||
macro bool os_is_darwin() @const
|
||||
{
|
||||
$switch (OS_TYPE)
|
||||
$switch OS_TYPE:
|
||||
$case IOS:
|
||||
$case MACOS:
|
||||
$case TVOS:
|
||||
@@ -171,7 +177,7 @@ macro bool os_is_darwin() @const
|
||||
|
||||
macro bool os_is_posix() @const
|
||||
{
|
||||
$switch (OS_TYPE)
|
||||
$switch OS_TYPE:
|
||||
$case IOS:
|
||||
$case MACOS:
|
||||
$case NETBSD:
|
||||
@@ -182,6 +188,7 @@ macro bool os_is_posix() @const
|
||||
$case SOLARIS:
|
||||
$case TVOS:
|
||||
$case WATCHOS:
|
||||
$case ANDROID:
|
||||
return true;
|
||||
$case WIN32:
|
||||
$case WASI:
|
||||
@@ -192,6 +199,7 @@ macro bool os_is_posix() @const
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
const String[] AUTHORS = $$AUTHORS;
|
||||
const String[] AUTHOR_EMAILS = $$AUTHOR_EMAILS;
|
||||
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
|
||||
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);
|
||||
|
||||
@@ -3,23 +3,48 @@
|
||||
// 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 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;
|
||||
|
||||
macro bool @constant_is_power_of_2($x) @const @private
|
||||
{
|
||||
return $x != 0 && ($x & ($x - 1)) == 0;
|
||||
}
|
||||
|
||||
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"
|
||||
@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_to(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
|
||||
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
|
||||
@@ -33,12 +58,12 @@ macro masked_load(ptr, bool[<*>] mask, passthru)
|
||||
<*
|
||||
Load a vector from memory according to a mask.
|
||||
|
||||
@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"
|
||||
@param $alignment "The alignment to assume for the pointer"
|
||||
@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"
|
||||
@param $alignment : "The alignment to assume for the pointer"
|
||||
|
||||
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
|
||||
@require @assignable_to(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
|
||||
@require @typekind(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"
|
||||
@@ -53,13 +78,13 @@ macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
|
||||
<*
|
||||
Load values from a pointer vector, assuming default alignment.
|
||||
|
||||
@param ptrvec "The vector of pointers to load from."
|
||||
@param mask "The mask for the load"
|
||||
@param passthru "The value to use for non masked values"
|
||||
@param ptrvec : "The vector of pointers to load from."
|
||||
@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 @assignable_to(&&passthru[0], $typeof(ptrvec[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"
|
||||
|
||||
@@ -74,14 +99,14 @@ macro gather(ptrvec, bool[<*>] mask, passthru)
|
||||
<*
|
||||
Load values from a pointer vector.
|
||||
|
||||
@param ptrvec "The vector of pointers to load from."
|
||||
@param mask "The mask for the load"
|
||||
@param passthru "The value to use for non masked values"
|
||||
@param $alignment "The alignment to assume for the pointers"
|
||||
@param ptrvec : "The vector of pointers to load from."
|
||||
@param mask : "The mask for the load"
|
||||
@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 @assignable_to(&&passthru[0], $typeof(ptrvec[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"
|
||||
@@ -97,11 +122,11 @@ macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
|
||||
<*
|
||||
Store parts of a vector according to the mask, assuming default alignment.
|
||||
|
||||
@param ptr "The pointer address to store to."
|
||||
@param value "The value to store masked"
|
||||
@param mask "The mask for the store"
|
||||
@param ptr : "The pointer address to store to."
|
||||
@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 @assignable_to(&&value, $typeof(ptr)) : "Pointer and value must match"
|
||||
@require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require value.len == mask.len : "Mask and value must have the same length"
|
||||
*>
|
||||
@@ -111,12 +136,12 @@ macro masked_store(ptr, value, bool[<*>] mask)
|
||||
}
|
||||
|
||||
<*
|
||||
@param ptr "The pointer address to store to."
|
||||
@param value "The value to store masked"
|
||||
@param mask "The mask for the store"
|
||||
@param $alignment "The alignment of the pointer"
|
||||
@param ptr : "The pointer address to store to."
|
||||
@param value : "The value to store masked"
|
||||
@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 @assignable_to(&&value, $typeof(ptr)) : "Pointer and value must match"
|
||||
@require @typekind(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"
|
||||
@@ -128,12 +153,12 @@ 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"
|
||||
@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 @assignable_to(&&value[0], $typeof(ptrvec[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"
|
||||
|
||||
@@ -144,14 +169,14 @@ macro scatter(ptrvec, value, bool[<*>] mask)
|
||||
}
|
||||
|
||||
<*
|
||||
@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"
|
||||
@param $alignment "The alignment of the load"
|
||||
@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"
|
||||
@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 @assignable_to(&&value[0], $typeof(ptrvec[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"
|
||||
@@ -162,8 +187,8 @@ macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "The variable or dereferenced pointer to load."
|
||||
@param $alignment "The alignment to assume for the load"
|
||||
@param #x : "The variable or dereferenced pointer to load."
|
||||
@param $alignment : "The alignment to assume for the load"
|
||||
@return "The value of the variable"
|
||||
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
@@ -175,9 +200,9 @@ macro @unaligned_load(#x, usz $alignment) @builtin
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "The variable or dereferenced pointer to store to."
|
||||
@param value "The value to store."
|
||||
@param $alignment "The alignment to assume for the store"
|
||||
@param #x : "The variable or dereferenced pointer to store to."
|
||||
@param value : "The value to store."
|
||||
@param $alignment : "The alignment to assume for the store"
|
||||
@return "The value stored"
|
||||
|
||||
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
|
||||
@@ -190,7 +215,7 @@ macro @unaligned_store(#x, value, usz $alignment) @builtin
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "The variable or dereferenced pointer to load."
|
||||
@param #x : "The variable or dereferenced pointer to load."
|
||||
@return "The value of the variable"
|
||||
|
||||
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
|
||||
@@ -201,8 +226,8 @@ macro @volatile_load(#x) @builtin
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "The variable or dereferenced pointer to store to."
|
||||
@param value "The value to store."
|
||||
@param #x : "The variable or dereferenced pointer to store to."
|
||||
@param value : "The value to store."
|
||||
@return "The value stored"
|
||||
|
||||
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
|
||||
@@ -225,15 +250,15 @@ enum AtomicOrdering : int
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "the variable or dereferenced pointer to load."
|
||||
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@param $volatile "whether the load should be volatile, defaults to 'false'"
|
||||
@param #x : "the variable or dereferenced pointer to load."
|
||||
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
|
||||
@param $volatile : "whether the load should be volatile, defaults to 'false'"
|
||||
@return "returns the value of x"
|
||||
|
||||
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
|
||||
@require $ordering != AtomicOrdering.RELEASE "Release ordering is not valid for load."
|
||||
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid for load."
|
||||
@require types::may_load_atomic($typeof(#x)) "Only integer, float and pointers may be used."
|
||||
@require $ordering != AtomicOrdering.RELEASE : "Release ordering is not valid for load."
|
||||
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid for load."
|
||||
@require types::may_load_atomic($typeof(#x)) : "Only integer, float and pointers may be used."
|
||||
*>
|
||||
macro @atomic_load(#x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
|
||||
{
|
||||
@@ -241,14 +266,14 @@ macro @atomic_load(#x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = fa
|
||||
}
|
||||
|
||||
<*
|
||||
@param #x "the variable or dereferenced pointer to store to."
|
||||
@param value "the value to store."
|
||||
@param $ordering "the atomic ordering of the store, defaults to SEQ_CONSISTENT"
|
||||
@param $volatile "whether the store should be volatile, defaults to 'false'"
|
||||
@param #x : "the variable or dereferenced pointer to store to."
|
||||
@param value : "the value to store."
|
||||
@param $ordering : "the atomic ordering of the store, defaults to SEQ_CONSISTENT"
|
||||
@param $volatile : "whether the store should be volatile, defaults to 'false'"
|
||||
|
||||
@require $ordering != AtomicOrdering.ACQUIRE "Acquire ordering is not valid for store."
|
||||
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid for store."
|
||||
@require types::may_load_atomic($typeof(#x)) "Only integer, float and pointers may be used."
|
||||
@require $ordering != AtomicOrdering.ACQUIRE : "Acquire ordering is not valid for store."
|
||||
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid for store."
|
||||
@require types::may_load_atomic($typeof(#x)) : "Only integer, float and pointers may be used."
|
||||
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
|
||||
@require $defined(#x = value) : "The value doesn't match the variable"
|
||||
*>
|
||||
@@ -258,8 +283,8 @@ macro void @atomic_store(#x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $
|
||||
}
|
||||
|
||||
<*
|
||||
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
|
||||
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid."
|
||||
*>
|
||||
macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT, bool $volatile = true, bool $weak = false, usz $alignment = 0)
|
||||
{
|
||||
@@ -267,8 +292,8 @@ macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSIS
|
||||
}
|
||||
|
||||
<*
|
||||
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
|
||||
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
|
||||
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid."
|
||||
*>
|
||||
macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT)
|
||||
{
|
||||
@@ -280,7 +305,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)
|
||||
@@ -296,12 +321,17 @@ 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);
|
||||
}
|
||||
|
||||
macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false, bool $inlined = false)
|
||||
macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false)
|
||||
{
|
||||
$$memset(dst, (char)0, len, $is_volatile, $dst_align);
|
||||
}
|
||||
@@ -314,16 +344,17 @@ macro void clear_inline(void* dst, usz $len, usz $dst_align = 0, bool $is_volati
|
||||
<*
|
||||
Copy memory from src to dst efficiently, assuming the memory ranges do not overlap.
|
||||
|
||||
@param [&out] dst "The destination to copy to"
|
||||
@param [&in] src "The source to copy from"
|
||||
@param len "The number of bytes to copy"
|
||||
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@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."
|
||||
@param [&out] dst : "The destination to copy to"
|
||||
@param [in] src : "The source to copy from"
|
||||
@param len : "The number of bytes to copy"
|
||||
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@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 len == 0 || dst + len <= src || src + len <= dst : "Ranges may not overlap"
|
||||
*>
|
||||
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false, bool $inlined = false)
|
||||
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
|
||||
{
|
||||
$$memcpy(dst, src, len, $is_volatile, $dst_align, $src_align);
|
||||
}
|
||||
@@ -332,13 +363,14 @@ macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig
|
||||
Copy memory from src to dst efficiently, assuming the memory ranges do not overlap, it
|
||||
will always be inlined and never call memcopy
|
||||
|
||||
@param [&out] dst "The destination to copy to"
|
||||
@param [&in] src "The source to copy from"
|
||||
@param $len "The number of bytes to copy"
|
||||
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@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."
|
||||
@param [&out] dst : "The destination to copy to"
|
||||
@param [in] src : "The source to copy from"
|
||||
@param $len : "The number of bytes to copy"
|
||||
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@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 $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)
|
||||
@@ -349,12 +381,14 @@ macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $
|
||||
<*
|
||||
Copy memory from src to dst but correctly handle the possibility of overlapping ranges.
|
||||
|
||||
@param [&out] dst "The destination to copy to"
|
||||
@param [&in] src "The source to copy from"
|
||||
@param len "The number of bytes to copy"
|
||||
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@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."
|
||||
@param [&out] dst : "The destination to copy to"
|
||||
@param [in] src : "The source to copy from"
|
||||
@param len : "The number of bytes to copy"
|
||||
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
@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 : "Moving a null with non-zero length is invalid"
|
||||
*>
|
||||
macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
|
||||
{
|
||||
@@ -364,11 +398,11 @@ macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig
|
||||
<*
|
||||
Sets all memory in a region to that of the provided byte.
|
||||
|
||||
@param [&out] dst "The destination to copy to"
|
||||
@param val "The value to copy into memory"
|
||||
@param len "The number of bytes to copy"
|
||||
@param $dst_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."
|
||||
@param [&out] dst : "The destination to copy to"
|
||||
@param val : "The value to copy into memory"
|
||||
@param len : "The number of bytes to copy"
|
||||
@param $dst_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."
|
||||
|
||||
@ensure !len || (dst[0] == val && dst[len - 1] == val)
|
||||
*>
|
||||
@@ -380,11 +414,11 @@ macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volati
|
||||
<*
|
||||
Sets all memory in a region to that of the provided byte. Never calls OS memset.
|
||||
|
||||
@param [&out] dst "The destination to copy to"
|
||||
@param val "The value to copy into memory"
|
||||
@param $len "The number of bytes to copy"
|
||||
@param $dst_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."
|
||||
@param [&out] dst : "The destination to copy to"
|
||||
@param val : "The value to copy into memory"
|
||||
@param $len : "The number of bytes to copy"
|
||||
@param $dst_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."
|
||||
|
||||
@ensure !$len || (dst[0] == val && dst[$len - 1] == val)
|
||||
*>
|
||||
@@ -421,7 +455,7 @@ macro bool equals(a, b, isz len = -1, usz $align = 0)
|
||||
|
||||
if (!len) return true;
|
||||
var $Type;
|
||||
$switch ($align)
|
||||
$switch $align:
|
||||
$case 1:
|
||||
$Type = char;
|
||||
$case 2:
|
||||
@@ -471,7 +505,7 @@ macro void @scoped(Allocator allocator; @body())
|
||||
Run the tracking allocator in the scope, then
|
||||
print out stats.
|
||||
|
||||
@param $enabled "Set to false to disable tracking"
|
||||
@param $enabled : "Set to false to disable tracking"
|
||||
*>
|
||||
macro void @report_heap_allocs_in_scope($enabled = true; @body())
|
||||
{
|
||||
@@ -493,14 +527,14 @@ macro void @report_heap_allocs_in_scope($enabled = true; @body())
|
||||
<*
|
||||
Assert on memory leak in the scope of the macro body.
|
||||
|
||||
@param $report "Set to false to disable memory report"
|
||||
@param $report : "Set to false to disable memory report"
|
||||
*>
|
||||
macro void @assert_leak($report = true; @body()) @builtin
|
||||
{
|
||||
$if env::DEBUG_SYMBOLS || $feature(MEMORY_ASSERTS):
|
||||
TrackingAllocator tracker;
|
||||
tracker.init(allocator::thread_allocator);
|
||||
Allocator old_allocator = allocator::thread_allocator;
|
||||
tracker.init(mem);
|
||||
Allocator old_allocator = mem;
|
||||
allocator::thread_allocator = &tracker;
|
||||
defer
|
||||
{
|
||||
@@ -509,7 +543,8 @@ macro void @assert_leak($report = true; @body()) @builtin
|
||||
usz allocated = tracker.allocated();
|
||||
if (allocated)
|
||||
{
|
||||
DString report = dstring::new();
|
||||
DString report;
|
||||
report.init(old_allocator);
|
||||
defer report.free();
|
||||
$if $report:
|
||||
report.append_char('\n');
|
||||
@@ -530,13 +565,13 @@ macro void @assert_leak($report = true; @body()) @builtin
|
||||
|
||||
Release everything on scope exit.
|
||||
|
||||
@param $size `the size of the buffer`
|
||||
@param $size : `the size of the buffer`
|
||||
*>
|
||||
macro void @stack_mem(usz $size; @body(Allocator mem)) @builtin
|
||||
{
|
||||
char[$size] buffer;
|
||||
OnStackAllocator allocator;
|
||||
allocator.init(&buffer, allocator::heap());
|
||||
allocator.init(&buffer, mem);
|
||||
defer allocator.free();
|
||||
@body(&allocator);
|
||||
}
|
||||
@@ -545,7 +580,7 @@ macro void @stack_pool(usz $size; @body) @builtin
|
||||
{
|
||||
char[$size] buffer;
|
||||
OnStackAllocator allocator;
|
||||
allocator.init(&buffer, allocator::heap());
|
||||
allocator.init(&buffer, mem);
|
||||
defer allocator.free();
|
||||
mem::@scoped(&allocator)
|
||||
{
|
||||
@@ -553,60 +588,63 @@ macro void @stack_pool(usz $size; @body) @builtin
|
||||
};
|
||||
}
|
||||
|
||||
struct TempState
|
||||
{
|
||||
TempAllocator* old;
|
||||
TempAllocator* current;
|
||||
usz mark;
|
||||
}
|
||||
<*
|
||||
Push the current temp allocator. A push must always be balanced with a pop using the current state.
|
||||
*>
|
||||
fn TempState temp_push(TempAllocator* other = null)
|
||||
fn PoolState temp_push()
|
||||
{
|
||||
TempAllocator* current = allocator::temp();
|
||||
TempAllocator* old = current;
|
||||
if (other == current)
|
||||
{
|
||||
current = allocator::temp_allocator_next();
|
||||
}
|
||||
return { old, current, current.used };
|
||||
return allocator::push_pool() @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the current temp allocator. A pop must always be balanced with a push.
|
||||
*>
|
||||
fn void temp_pop(TempState old_state)
|
||||
fn void temp_pop(PoolState old_state)
|
||||
{
|
||||
assert(allocator::thread_temp_allocator == old_state.current, "Tried to pop temp allocators out of order.");
|
||||
assert(old_state.current.used >= old_state.mark, "Tried to pop temp allocators out of order.");
|
||||
old_state.current.reset(old_state.mark);
|
||||
allocator::thread_temp_allocator = old_state.old;
|
||||
allocator::pop_pool(old_state) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_empty_macro_slot(#other_temp) ||| $assignable(#other_temp, Allocator) "Must be an allocator"
|
||||
*>
|
||||
macro void @pool(#other_temp = EMPTY_MACRO_SLOT; @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
|
||||
{
|
||||
TempAllocator* current = allocator::temp();
|
||||
$if @is_valid_macro_slot(#other_temp):
|
||||
TempAllocator* original = current;
|
||||
if (current == #other_temp.ptr) current = allocator::temp_allocator_next();
|
||||
$endif
|
||||
usz mark = current.used;
|
||||
Allocator current = allocator::current_temp;
|
||||
TempAllocator* top = allocator::top_temp;
|
||||
allocator::create_temp_allocator(allocator, pool_size, reserve_size, min_size, realloc_size);
|
||||
defer
|
||||
{
|
||||
current.reset(mark);
|
||||
$if @is_valid_macro_slot(#other_temp):
|
||||
allocator::thread_temp_allocator = original;
|
||||
$endif;
|
||||
allocator::destroy_temp_allocators();
|
||||
allocator::top_temp = top;
|
||||
allocator::current_temp = current;
|
||||
}
|
||||
@body();
|
||||
}
|
||||
|
||||
import libc;
|
||||
<*
|
||||
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(reserve) @inline;
|
||||
defer
|
||||
{
|
||||
allocator::pop_pool(state) @inline;
|
||||
}
|
||||
@body();
|
||||
}
|
||||
|
||||
module std::core::mem @if(WASM_NOLIBC);
|
||||
import std::core::mem::allocator @public;
|
||||
@@ -622,7 +660,6 @@ fn void initialize_wasm_mem() @init(1024) @private
|
||||
wasm_allocator.init(fn (x) => allocator::wasm_memory.allocate_block(x));
|
||||
allocator::thread_allocator = &wasm_allocator;
|
||||
allocator::temp_base_allocator = &wasm_allocator;
|
||||
allocator::init_default_temp_allocators();
|
||||
}
|
||||
|
||||
module std::core::mem;
|
||||
@@ -631,25 +668,47 @@ module std::core::mem;
|
||||
macro TrackingEnv* get_tracking_env()
|
||||
{
|
||||
$if env::TRACK_MEMORY:
|
||||
return &&TrackingEnv { $$FILE, $$FUNC, $$LINE };
|
||||
return &&(TrackingEnv){ $$FILE, $$FUNC, $$LINE };
|
||||
$else
|
||||
return null;
|
||||
$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(allocator::heap(), value);
|
||||
return allocator::clone(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 temp_new($typeof(value), value);
|
||||
$if $alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT:
|
||||
return tnew($typeof(value), value);
|
||||
$else
|
||||
return allocator::clone_aligned(tmem, value);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void* malloc(usz size) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::malloc(allocator::heap(), size);
|
||||
return allocator::malloc(mem, size);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -658,18 +717,18 @@ fn void* malloc(usz size) @builtin @inline @nodiscard
|
||||
*>
|
||||
fn void* malloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::malloc_aligned(allocator::heap(), size, alignment)!!;
|
||||
return allocator::malloc_aligned(mem, size, alignment)!!;
|
||||
}
|
||||
|
||||
fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator::temp().acquire(size, NO_ZERO, alignment)!!;
|
||||
return tmem.acquire(size, NO_ZERO, alignment)!!;
|
||||
}
|
||||
|
||||
<*
|
||||
@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 ||| @assignable_to($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"
|
||||
*>
|
||||
macro new($Type, ...) @nodiscard
|
||||
@@ -683,11 +742,27 @@ macro new($Type, ...) @nodiscard
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| @assignable_to($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"
|
||||
*>
|
||||
macro new_with_padding($Type, usz padding, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc($Type.sizeof + padding);
|
||||
$else
|
||||
$Type* val = malloc($Type.sizeof + padding);
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_aligned($Type, ...) @nodiscard
|
||||
{
|
||||
@@ -708,6 +783,14 @@ macro alloc($Type) @nodiscard
|
||||
return ($Type*)malloc($Type.sizeof);
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
*>
|
||||
macro alloc_with_padding($Type, usz padding) @nodiscard
|
||||
{
|
||||
return ($Type*)malloc($Type.sizeof + padding);
|
||||
}
|
||||
|
||||
<*
|
||||
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.
|
||||
@@ -719,31 +802,50 @@ 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"
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro temp_new($Type, ...) @nodiscard
|
||||
macro tnew($Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)tcalloc($Type.sizeof) @inline;
|
||||
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
|
||||
$else
|
||||
$Type* val = tmalloc($Type.sizeof) @inline;
|
||||
$Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro temp_alloc($Type) @nodiscard
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro temp_with_padding($Type, usz padding, ...) @nodiscard
|
||||
{
|
||||
return tmalloc($Type.sizeof);
|
||||
$if $vacount == 0:
|
||||
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
|
||||
$else
|
||||
$Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline;
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro talloc($Type) @nodiscard
|
||||
{
|
||||
return tmalloc($Type.sizeof, $Type.alignof);
|
||||
}
|
||||
|
||||
macro talloc_with_padding($Type, usz padding) @nodiscard
|
||||
{
|
||||
return tmalloc($Type.sizeof + padding, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
@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
|
||||
{
|
||||
return allocator::new_array(allocator::heap(), $Type, elements);
|
||||
return allocator::new_array(mem, $Type, elements);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -752,7 +854,7 @@ macro new_array($Type, usz elements) @nodiscard
|
||||
*>
|
||||
macro new_array_aligned($Type, usz elements) @nodiscard
|
||||
{
|
||||
return allocator::new_array_aligned(allocator::heap(), $Type, elements);
|
||||
return allocator::new_array_aligned(mem, $Type, elements);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -760,7 +862,7 @@ macro new_array_aligned($Type, usz elements) @nodiscard
|
||||
*>
|
||||
macro alloc_array($Type, usz elements) @nodiscard
|
||||
{
|
||||
return allocator::alloc_array(allocator::heap(), $Type, elements);
|
||||
return allocator::alloc_array(mem, $Type, elements);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -769,22 +871,22 @@ macro alloc_array($Type, usz elements) @nodiscard
|
||||
*>
|
||||
macro alloc_array_aligned($Type, usz elements) @nodiscard
|
||||
{
|
||||
return allocator::alloc_array_aligned(allocator::heap(), $Type, elements);
|
||||
return allocator::alloc_array_aligned(mem, $Type, elements);
|
||||
}
|
||||
|
||||
macro temp_alloc_array($Type, usz elements) @nodiscard
|
||||
macro talloc_array($Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)tmalloc($Type.sizeof * elements, $Type.alignof))[:elements];
|
||||
}
|
||||
|
||||
macro temp_new_array($Type, usz elements) @nodiscard
|
||||
macro temp_array($Type, usz elements) @nodiscard
|
||||
{
|
||||
return (($Type*)tcalloc($Type.sizeof * elements, $Type.alignof))[:elements];
|
||||
}
|
||||
|
||||
fn void* calloc(usz size) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::calloc(allocator::heap(), size);
|
||||
return allocator::calloc(mem, size);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -793,40 +895,52 @@ fn void* calloc(usz size) @builtin @inline @nodiscard
|
||||
*>
|
||||
fn void* calloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::calloc_aligned(allocator::heap(), size, alignment)!!;
|
||||
return allocator::calloc_aligned(mem, size, alignment)!!;
|
||||
}
|
||||
|
||||
fn void* tcalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator::temp().acquire(size, ZERO, alignment)!!;
|
||||
return tmem.acquire(size, ZERO, alignment)!!;
|
||||
}
|
||||
|
||||
fn void* realloc(void *ptr, usz new_size) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::realloc(allocator::heap(), ptr, new_size);
|
||||
return allocator::realloc(mem, ptr, new_size);
|
||||
}
|
||||
|
||||
fn void* realloc_aligned(void *ptr, usz new_size, usz alignment) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::realloc_aligned(allocator::heap(), ptr, new_size, alignment)!!;
|
||||
return allocator::realloc_aligned(mem, ptr, new_size, alignment)!!;
|
||||
}
|
||||
|
||||
fn void free(void* ptr) @builtin @inline
|
||||
{
|
||||
return allocator::free(allocator::heap(), ptr);
|
||||
return allocator::free(mem, ptr);
|
||||
}
|
||||
|
||||
fn void free_aligned(void* ptr) @builtin @inline
|
||||
{
|
||||
return allocator::free_aligned(allocator::heap(), ptr);
|
||||
return allocator::free_aligned(mem, ptr);
|
||||
}
|
||||
|
||||
fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
if (!ptr) return tmalloc(size, alignment);
|
||||
return allocator::temp().resize(ptr, size, alignment)!!;
|
||||
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);
|
||||
@@ -866,3 +980,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);
|
||||
}
|
||||
|
||||
macro Type UnalignedRef.set(&self, Type val)
|
||||
{
|
||||
return @unaligned_store(*(Type*)self, val, ALIGNMENT);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
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;
|
||||
@@ -18,34 +32,38 @@ enum AllocInitType
|
||||
|
||||
interface Allocator
|
||||
{
|
||||
fn void reset(usz mark) @optional;
|
||||
fn usz mark() @optional;
|
||||
<*
|
||||
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 alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@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);
|
||||
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 alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
|
||||
@require ptr != null
|
||||
@require new_size > 0
|
||||
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
|
||||
*>
|
||||
fn void*! resize(void* ptr, usz new_size, usz alignment = 0);
|
||||
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);
|
||||
}
|
||||
|
||||
def MemoryAllocFn = fn char[]!(usz);
|
||||
alias MemoryAllocFn = fn char[]?(usz);
|
||||
|
||||
|
||||
fault AllocationFailure
|
||||
{
|
||||
OUT_OF_MEMORY,
|
||||
CHUNK_TOO_LARGE,
|
||||
}
|
||||
|
||||
fn usz alignment_for_allocation(usz alignment) @inline @private
|
||||
{
|
||||
@@ -57,7 +75,7 @@ macro void* malloc(Allocator allocator, usz size) @nodiscard
|
||||
return malloc_try(allocator, size)!!;
|
||||
}
|
||||
|
||||
macro void*! malloc_try(Allocator allocator, usz size) @nodiscard
|
||||
macro void*? malloc_try(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
$if env::TESTING:
|
||||
@@ -74,7 +92,7 @@ macro void* calloc(Allocator allocator, usz size) @nodiscard
|
||||
return calloc_try(allocator, size)!!;
|
||||
}
|
||||
|
||||
macro void*! calloc_try(Allocator allocator, usz size) @nodiscard
|
||||
macro void*? calloc_try(Allocator allocator, usz size) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator.acquire(size, ZERO);
|
||||
@@ -85,7 +103,7 @@ macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard
|
||||
return realloc_try(allocator, ptr, new_size)!!;
|
||||
}
|
||||
|
||||
macro void*! realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
|
||||
macro void*? realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
|
||||
{
|
||||
if (!new_size)
|
||||
{
|
||||
@@ -105,7 +123,7 @@ macro void free(Allocator allocator, void* ptr)
|
||||
allocator.release(ptr, false);
|
||||
}
|
||||
|
||||
macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
|
||||
macro void*? malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
$if env::TESTING:
|
||||
@@ -117,13 +135,13 @@ macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodis
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void*! calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
|
||||
macro void*? calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!size) return null;
|
||||
return allocator.acquire(size, ZERO, alignment);
|
||||
}
|
||||
|
||||
macro void*! realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
|
||||
macro void*? realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
|
||||
{
|
||||
if (!new_size)
|
||||
{
|
||||
@@ -149,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 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
@@ -165,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 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_try(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
@@ -182,14 +200,14 @@ 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 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_aligned($Type, ...) @nodiscard
|
||||
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc_aligned(allocator, $Type.sizeof, $Type.alignof);
|
||||
$else
|
||||
$Type* val = malloc_aligned(allocator, $Type.sizeof, $Type.alignof);
|
||||
$Type* val = malloc_aligned(allocator, $Type.sizeof, $Type.alignof)!;
|
||||
*val = $vaexpr[0];
|
||||
return val;
|
||||
$endif
|
||||
@@ -286,11 +304,31 @@ macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
|
||||
return (($Type*)malloc_try(allocator, $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);
|
||||
}
|
||||
|
||||
<*
|
||||
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;
|
||||
@@ -305,7 +343,7 @@ fn any clone_any(Allocator allocator, any value) @nodiscard
|
||||
@require alignment > 0
|
||||
@require bytes <= isz.max
|
||||
*>
|
||||
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
|
||||
macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
|
||||
{
|
||||
if (alignment < void*.alignof) alignment = void*.alignof;
|
||||
usz header = AlignedBlock.sizeof + alignment;
|
||||
@@ -328,7 +366,7 @@ struct AlignedBlock
|
||||
void* start;
|
||||
}
|
||||
|
||||
macro void! @aligned_free(#free_fn, void* old_pointer)
|
||||
macro void? @aligned_free(#free_fn, void* old_pointer)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
|
||||
@@ -342,7 +380,7 @@ macro void! @aligned_free(#free_fn, void* old_pointer)
|
||||
@require bytes > 0
|
||||
@require alignment > 0
|
||||
*>
|
||||
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
|
||||
macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
void* data_start = desc.start;
|
||||
@@ -358,13 +396,34 @@ macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
|
||||
|
||||
|
||||
// All allocators
|
||||
|
||||
|
||||
alias mem @builtin = thread_allocator ;
|
||||
tlocal Allocator thread_allocator @private = base_allocator();
|
||||
Allocator temp_base_allocator @private = base_allocator();
|
||||
|
||||
tlocal TempAllocator* thread_temp_allocator @private = null;
|
||||
tlocal TempAllocator*[2] temp_allocator_pair @private;
|
||||
typedef PoolState = TempAllocator*;
|
||||
|
||||
const LazyTempAllocator LAZY_TEMP @private = {};
|
||||
tlocal Allocator current_temp = &LAZY_TEMP;
|
||||
tlocal TempAllocator* top_temp;
|
||||
tlocal bool auto_create_temp = false;
|
||||
|
||||
usz temp_allocator_min_size = temp_allocator_default_min_size();
|
||||
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(usz reserve = 0)
|
||||
{
|
||||
Allocator old = top_temp ? current_temp : create_temp_allocator_on_demand();
|
||||
current_temp = ((TempAllocator*)old).derive_allocator(reserve)!!;
|
||||
return (PoolState)old.ptr;
|
||||
}
|
||||
|
||||
fn void pop_pool(PoolState old)
|
||||
{
|
||||
TempAllocator* temp = (TempAllocator*)old;
|
||||
current_temp = temp;
|
||||
temp.reset();
|
||||
}
|
||||
|
||||
macro Allocator base_allocator() @private
|
||||
{
|
||||
@@ -375,36 +434,69 @@ macro Allocator base_allocator() @private
|
||||
$endif
|
||||
}
|
||||
|
||||
macro TempAllocator* create_default_sized_temp_allocator(Allocator allocator) @local
|
||||
macro usz temp_allocator_size() @local
|
||||
{
|
||||
$switch (env::MEMORY_ENV)
|
||||
$case NORMAL:
|
||||
return new_temp_allocator(1024 * 256, allocator)!!;
|
||||
$case SMALL:
|
||||
return new_temp_allocator(1024 * 16, allocator)!!;
|
||||
$case TINY:
|
||||
return new_temp_allocator(1024 * 2, allocator)!!;
|
||||
$case NONE:
|
||||
unreachable("Temp allocator must explicitly created when memory-env is set to 'none'.");
|
||||
$switch env::MEMORY_ENV:
|
||||
$case NORMAL: return 256 * 1024;
|
||||
$case SMALL: return 1024 * 32;
|
||||
$case TINY: return 1024 * 4;
|
||||
$case NONE: return 0;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro Allocator heap() => thread_allocator;
|
||||
|
||||
macro TempAllocator* temp()
|
||||
macro usz temp_allocator_default_min_size() @local
|
||||
{
|
||||
if (!thread_temp_allocator)
|
||||
{
|
||||
init_default_temp_allocators();
|
||||
}
|
||||
return thread_temp_allocator;
|
||||
$switch env::MEMORY_ENV:
|
||||
$case NORMAL: return 16 * 1024;
|
||||
$case SMALL: return 1024 * 2;
|
||||
$case TINY: return 256;
|
||||
$case NONE: return 256;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn void init_default_temp_allocators() @private
|
||||
macro usz temp_allocator_default_reserve_size() @local
|
||||
{
|
||||
temp_allocator_pair[0] = create_default_sized_temp_allocator(temp_base_allocator);
|
||||
temp_allocator_pair[1] = create_default_sized_temp_allocator(temp_base_allocator);
|
||||
thread_temp_allocator = temp_allocator_pair[0];
|
||||
$switch env::MEMORY_ENV:
|
||||
$case NORMAL: return 1024;
|
||||
$case SMALL: return 128;
|
||||
$case TINY: return 64;
|
||||
$case NONE: return 64;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro Allocator heap() @deprecated("Use 'mem' instead.") => thread_allocator;
|
||||
|
||||
<*
|
||||
@require !top_temp : "This should never be called when temp already exists"
|
||||
*>
|
||||
fn Allocator create_temp_allocator_on_demand() @private
|
||||
{
|
||||
if (!auto_create_temp)
|
||||
{
|
||||
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(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 reserve, usz min_size, usz realloc_size) @private
|
||||
{
|
||||
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size, reserve, min_size, realloc_size)!!;
|
||||
}
|
||||
|
||||
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 || env::WASM_NOLIBC)
|
||||
{
|
||||
auto_create_temp = true;
|
||||
}
|
||||
|
||||
fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::LIBC)
|
||||
@@ -417,35 +509,42 @@ fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::L
|
||||
*>
|
||||
fn void destroy_temp_allocators()
|
||||
{
|
||||
if (!thread_temp_allocator) return;
|
||||
temp_allocator_pair[0].destroy();
|
||||
temp_allocator_pair[1].destroy();
|
||||
temp_allocator_pair[..] = null;
|
||||
thread_temp_allocator = null;
|
||||
if (!top_temp) return;
|
||||
top_temp.free();
|
||||
top_temp = null;
|
||||
current_temp = &LAZY_TEMP;
|
||||
}
|
||||
|
||||
fn TempAllocator* temp_allocator_next() @private
|
||||
import libc;
|
||||
typedef LazyTempAllocator (Allocator) @private = uptr;
|
||||
|
||||
fn void*? LazyTempAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
if (!top_temp) create_temp_allocator_on_demand();
|
||||
return top_temp.acquire(bytes, init_type, alignment);
|
||||
}
|
||||
|
||||
fn void*? LazyTempAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
if (!top_temp) create_temp_allocator_on_demand();
|
||||
return top_temp.resize(old_ptr, new_bytes, alignment);
|
||||
}
|
||||
|
||||
fn void LazyTempAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
{
|
||||
if (!thread_temp_allocator)
|
||||
{
|
||||
init_default_temp_allocators();
|
||||
return thread_temp_allocator;
|
||||
}
|
||||
usz index = thread_temp_allocator == temp_allocator_pair[0] ? 1 : 0;
|
||||
return thread_temp_allocator = temp_allocator_pair[index];
|
||||
}
|
||||
|
||||
const NullAllocator NULL_ALLOCATOR = {};
|
||||
distinct NullAllocator (Allocator) = uptr;
|
||||
typedef NullAllocator (Allocator) = uptr;
|
||||
|
||||
fn void*! NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
fn void*? NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
|
||||
{
|
||||
return AllocationFailure.OUT_OF_MEMORY?;
|
||||
return mem::OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
fn void*! NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
fn void*? NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
|
||||
{
|
||||
return AllocationFailure.OUT_OF_MEMORY?;
|
||||
return mem::OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
|
||||
323
lib/std/core/os/mem_vm.c3
Normal file
323
lib/std/core/os/mem_vm.c3
Normal file
@@ -0,0 +1,323 @@
|
||||
<*
|
||||
The VM module holds code for working with virtual memory on supported platforms (currently Win32 and Posix)
|
||||
*>
|
||||
module std::core::mem::vm;
|
||||
import 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
|
||||
}
|
||||
|
||||
<*
|
||||
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) @private
|
||||
{
|
||||
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) @private
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ struct WasmMemory
|
||||
uptr use;
|
||||
}
|
||||
|
||||
fn char[]! WasmMemory.allocate_block(&self, usz bytes)
|
||||
fn char[]? WasmMemory.allocate_block(&self, usz bytes)
|
||||
{
|
||||
if (!self.allocation)
|
||||
{
|
||||
@@ -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 AllocationFailure.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,21 +143,21 @@ 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()
|
||||
{
|
||||
uint max_level = x86_cpuid(0).eax;
|
||||
CpuId feat = x86_cpuid(1);
|
||||
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : CpuId {};
|
||||
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : CpuId {};
|
||||
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : CpuId {};
|
||||
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : CpuId {};
|
||||
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : CpuId {};
|
||||
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : CpuId {};
|
||||
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : CpuId {};
|
||||
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : CpuId {};
|
||||
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : {};
|
||||
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : {};
|
||||
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : {};
|
||||
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : {};
|
||||
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : {};
|
||||
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : {};
|
||||
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : {};
|
||||
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : {};
|
||||
add_feature_if_bit(ADX, leaf7.ebx, 19);
|
||||
add_feature_if_bit(AES, feat.ecx, 25);
|
||||
add_feature_if_bit(AMX_BF16, leaf7.edx, 22);
|
||||
|
||||
@@ -56,10 +56,7 @@ struct MachHeader64
|
||||
|
||||
const LC_SEGMENT_64 = 0x19;
|
||||
|
||||
fault MachoSearch
|
||||
{
|
||||
NOT_FOUND
|
||||
}
|
||||
|
||||
fn bool name_cmp(char* a, char[16]* b)
|
||||
{
|
||||
for (usz i = 0; i < 16; i++)
|
||||
@@ -70,7 +67,7 @@ fn bool name_cmp(char* a, char[16]* b)
|
||||
return false;
|
||||
}
|
||||
|
||||
fn SegmentCommand64*! find_segment(MachHeader* header, char* segname)
|
||||
fn SegmentCommand64*? find_segment(MachHeader* header, char* segname)
|
||||
{
|
||||
LoadCommand* command = (void*)header + MachHeader64.sizeof;
|
||||
for (uint i = 0; i < header.ncmds; i++)
|
||||
@@ -82,9 +79,9 @@ fn SegmentCommand64*! find_segment(MachHeader* header, char* segname)
|
||||
}
|
||||
command = (void*)command + command.cmdsize;
|
||||
}
|
||||
return MachoSearch.NOT_FOUND?;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
fn Section64*! find_section(SegmentCommand64* command, char* sectname)
|
||||
fn Section64*? find_section(SegmentCommand64* command, char* sectname)
|
||||
{
|
||||
Section64* section = (void*)command + SegmentCommand64.sizeof;
|
||||
for (uint i = 0; i < command.nsects; i++)
|
||||
@@ -92,22 +89,22 @@ fn Section64*! find_section(SegmentCommand64* command, char* sectname)
|
||||
if (name_cmp(sectname, §ion.sectname)) return section;
|
||||
section++;
|
||||
}
|
||||
return MachoSearch.NOT_FOUND?;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type)
|
||||
{
|
||||
|
||||
Section64*! section = find_section(find_segment(header, segname), sectname);
|
||||
Section64*? section = find_section(find_segment(header, segname), sectname);
|
||||
if (catch section)
|
||||
{
|
||||
return $Type[] {};
|
||||
return ($Type[]){};
|
||||
}
|
||||
$Type* ptr = (void*)header + section.offset;
|
||||
return ptr[:section.size / $Type.sizeof];
|
||||
}
|
||||
|
||||
def DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide);
|
||||
alias DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide);
|
||||
|
||||
extern fn void _dyld_register_func_for_add_image(DyldCallback);
|
||||
|
||||
@@ -126,7 +123,7 @@ extern fn void* realloc(void* ptr, usz size);
|
||||
extern fn void* malloc(usz size);
|
||||
extern fn void free(void* ptr);
|
||||
|
||||
def CallbackFn = fn void();
|
||||
alias CallbackFn = fn void();
|
||||
struct Callback
|
||||
{
|
||||
uint priority;
|
||||
|
||||
@@ -80,7 +80,7 @@ macro String[] wargs_strings(int argc, Char16** argv) @private
|
||||
{
|
||||
Char16* arg = argv[i];
|
||||
Char16[] argstring = arg[:_strlen(arg)];
|
||||
list[i] = string::new_from_utf16(argstring) ?? "?".copy();
|
||||
list[i] = string::from_utf16(mem, argstring) ?? "?".copy(mem);
|
||||
}
|
||||
return list[:argc];
|
||||
}
|
||||
|
||||
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) ||| @typeis((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 ||| @assignable_to($vaexpr[0], Type) : "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 @assignable_to(refcounted, 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 @assignable_to(refcounted, RefCounted*) : "Expected a ref counted value"
|
||||
@require !$defined(refcounted.dealloc()) ||| @typeis(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,6 +22,22 @@ struct SliceRaw
|
||||
usz len;
|
||||
}
|
||||
|
||||
macro @enum_lookup($Type, #value, value)
|
||||
{
|
||||
$foreach $val : $Type.values:
|
||||
if ($val.#value == value) return $val;
|
||||
$endforeach
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
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(WASM_NOLIBC);
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
module std::core::runtime;
|
||||
import libc, std::time, std::io, std::sort;
|
||||
|
||||
def BenchmarkFn = fn void!() @if($$OLD_TEST);
|
||||
def BenchmarkFn = fn void() @if(!$$OLD_TEST);
|
||||
alias BenchmarkFn = fn void();
|
||||
|
||||
struct BenchmarkUnit
|
||||
{
|
||||
@@ -10,7 +9,7 @@ struct BenchmarkUnit
|
||||
BenchmarkFn func;
|
||||
}
|
||||
|
||||
fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator = allocator::heap())
|
||||
fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator)
|
||||
{
|
||||
BenchmarkFn[] fns = $$BENCHMARK_FNS;
|
||||
String[] names = $$BENCHMARK_NAMES;
|
||||
@@ -39,80 +38,7 @@ fn void set_benchmark_max_iterations(uint value) @builtin
|
||||
benchmark_max_iterations = value;
|
||||
}
|
||||
|
||||
fn bool run_benchmarks(BenchmarkUnit[] benchmarks) @if($$OLD_TEST)
|
||||
{
|
||||
int benchmarks_passed = 0;
|
||||
int benchmark_count = benchmarks.len;
|
||||
usz max_name;
|
||||
|
||||
foreach (&unit : benchmarks)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
}
|
||||
|
||||
usz len = max_name + 9;
|
||||
|
||||
DString name = dstring::temp_with_capacity(64);
|
||||
name.append_repeat('-', len / 2);
|
||||
name.append(" BENCHMARKS ");
|
||||
name.append_repeat('-', len - len / 2);
|
||||
|
||||
io::printn(name);
|
||||
|
||||
name.clear();
|
||||
|
||||
long sys_clock_started;
|
||||
long sys_clock_finished;
|
||||
long sys_clocks;
|
||||
Clock clock;
|
||||
anyfault err;
|
||||
|
||||
foreach(unit : benchmarks)
|
||||
{
|
||||
defer name.clear();
|
||||
name.appendf("Benchmarking %s ", unit.name);
|
||||
name.append_repeat('.', max_name - unit.name.len + 2);
|
||||
io::printf("%s ", name.str_view());
|
||||
|
||||
for (uint i = 0; i < benchmark_warmup_iterations; i++)
|
||||
{
|
||||
err = @catch(unit.func()) @inline;
|
||||
@volatile_load(err);
|
||||
}
|
||||
|
||||
clock = std::time::clock::now();
|
||||
sys_clock_started = $$sysclock();
|
||||
|
||||
for (uint i = 0; i < benchmark_max_iterations; i++)
|
||||
{
|
||||
err = @catch(unit.func()) @inline;
|
||||
@volatile_load(err);
|
||||
}
|
||||
|
||||
sys_clock_finished = $$sysclock();
|
||||
NanoDuration nano_seconds = clock.mark();
|
||||
sys_clocks = sys_clock_finished - sys_clock_started;
|
||||
|
||||
if (err)
|
||||
{
|
||||
io::printfn("[failed] Failed due to: %s", err);
|
||||
continue;
|
||||
}
|
||||
|
||||
io::printfn("[ok] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
|
||||
benchmarks_passed++;
|
||||
}
|
||||
|
||||
io::printfn("\n%d benchmark%s run.\n", benchmark_count, benchmark_count > 1 ? "s" : "");
|
||||
io::printfn("Benchmarks Result: %s. %d passed, %d failed.",
|
||||
benchmarks_passed < benchmark_count ? "FAILED" : "ok",
|
||||
benchmarks_passed,
|
||||
benchmark_count - benchmarks_passed);
|
||||
|
||||
return benchmark_count == benchmarks_passed;
|
||||
}
|
||||
|
||||
fn bool run_benchmarks(BenchmarkUnit[] benchmarks) @if(!$$OLD_TEST)
|
||||
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
|
||||
{
|
||||
usz max_name;
|
||||
|
||||
@@ -168,10 +94,7 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks) @if(!$$OLD_TEST)
|
||||
return true;
|
||||
}
|
||||
|
||||
fn bool default_benchmark_runner(String[] args)
|
||||
fn bool default_benchmark_runner(String[] args) => @pool()
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
return run_benchmarks(benchmark_collection_create(allocator::temp()));
|
||||
};
|
||||
return run_benchmarks(benchmark_collection_create(tmem));
|
||||
}
|
||||
|
||||
@@ -2,10 +2,43 @@
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::runtime;
|
||||
import std::core::test @public;
|
||||
import std::core::mem::allocator @public;
|
||||
import libc, std::time, std::io, std::sort;
|
||||
import std::os::env;
|
||||
|
||||
def TestFn = fn void!() @if($$OLD_TEST);
|
||||
def TestFn = fn void() @if(!$$OLD_TEST);
|
||||
alias TestFn = fn void();
|
||||
|
||||
TestContext* test_context @private;
|
||||
|
||||
struct TestContext
|
||||
{
|
||||
JmpBuf buf;
|
||||
// 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
|
||||
bool breakpoint_on_assert;
|
||||
|
||||
// internal state
|
||||
bool assert_print_backtrace;
|
||||
bool has_ansi_codes;
|
||||
bool is_in_panic;
|
||||
bool is_quiet_mode;
|
||||
bool is_no_capture;
|
||||
String current_test_name;
|
||||
TestFn setup_fn;
|
||||
TestFn teardown_fn;
|
||||
|
||||
char* error_buffer;
|
||||
usz error_buffer_capacity;
|
||||
File fake_stdout;
|
||||
struct stored
|
||||
{
|
||||
File stdout;
|
||||
File stderr;
|
||||
Allocator allocator;
|
||||
}
|
||||
}
|
||||
|
||||
struct TestUnit
|
||||
{
|
||||
@@ -13,7 +46,7 @@ struct TestUnit
|
||||
TestFn func;
|
||||
}
|
||||
|
||||
fn TestUnit[] test_collection_create(Allocator allocator = allocator::heap())
|
||||
fn TestUnit[] test_collection_create(Allocator allocator)
|
||||
{
|
||||
TestFn[] fns = $$TEST_FNS;
|
||||
String[] names = $$TEST_NAMES;
|
||||
@@ -25,11 +58,6 @@ fn TestUnit[] test_collection_create(Allocator allocator = allocator::heap())
|
||||
return tests;
|
||||
}
|
||||
|
||||
struct TestContext
|
||||
{
|
||||
JmpBuf buf;
|
||||
}
|
||||
|
||||
// Sort the tests by their name in ascending order.
|
||||
fn int cmp_test_unit(TestUnit a, TestUnit b)
|
||||
{
|
||||
@@ -44,114 +72,263 @@ fn int cmp_test_unit(TestUnit a, TestUnit b)
|
||||
return (int)(an - bn);
|
||||
}
|
||||
|
||||
TestContext* test_context @private;
|
||||
|
||||
fn void test_panic(String message, String file, String function, uint line)
|
||||
fn bool terminal_has_ansi_codes() @local => @pool()
|
||||
{
|
||||
io::printn("[error]");
|
||||
io::print("\n Error: ");
|
||||
io::print(message);
|
||||
io::printn();
|
||||
io::printfn(" - in %s %s:%s.\n", function, file, line);
|
||||
|
||||
if (try v = env::tget_var("TERM"))
|
||||
{
|
||||
if (v.contains("xterm") || v.contains("vt100") || v.contains("screen")) return true;
|
||||
}
|
||||
$if env::WIN32 || env::NO_LIBC:
|
||||
return false;
|
||||
$else
|
||||
return io::stdout().isatty();
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void test_panic(String message, String file, String function, uint line) @local
|
||||
{
|
||||
if (test_context.is_in_panic) return;
|
||||
test_context.is_in_panic = true;
|
||||
|
||||
unmute_output(true);
|
||||
(void)io::stdout().flush();
|
||||
if (test_context.assert_print_backtrace)
|
||||
{
|
||||
$if env::NATIVE_STACKTRACE:
|
||||
builtin::print_backtrace(message, 0);
|
||||
$endif
|
||||
}
|
||||
io::printf("\nTest failed ^^^ ( %s:%s ) %s\n", file, line, message);
|
||||
test_context.assert_print_backtrace = true;
|
||||
|
||||
if (test_context.breakpoint_on_assert)
|
||||
{
|
||||
breakpoint();
|
||||
}
|
||||
|
||||
if (test_context.teardown_fn)
|
||||
{
|
||||
test_context.teardown_fn();
|
||||
}
|
||||
|
||||
test_context.is_in_panic = false;
|
||||
allocator::thread_allocator = test_context.stored.allocator;
|
||||
libc::longjmp(&test_context.buf, 1);
|
||||
}
|
||||
|
||||
fn bool run_tests(TestUnit[] tests) @if($$OLD_TEST)
|
||||
fn void mute_output() @local
|
||||
{
|
||||
usz max_name;
|
||||
foreach (&unit : tests)
|
||||
if (test_context.is_no_capture || !test_context.fake_stdout.file) return;
|
||||
File* stdout = io::stdout();
|
||||
File* stderr = io::stderr();
|
||||
*stderr = test_context.fake_stdout;
|
||||
*stdout = test_context.fake_stdout;
|
||||
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
|
||||
}
|
||||
|
||||
fn void unmute_output(bool has_error) @local
|
||||
{
|
||||
if (test_context.is_no_capture || !test_context.fake_stdout.file) return;
|
||||
|
||||
File* stdout = io::stdout();
|
||||
File* stderr = io::stderr();
|
||||
|
||||
*stderr = test_context.stored.stderr;
|
||||
*stdout = test_context.stored.stdout;
|
||||
|
||||
usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
|
||||
if (has_error)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
io::printf("\nTesting %s ", test_context.current_test_name);
|
||||
io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
|
||||
}
|
||||
quicksort(tests, &cmp_test_unit);
|
||||
|
||||
TestContext context;
|
||||
test_context = &context;
|
||||
|
||||
PanicFn old_panic = builtin::panic;
|
||||
defer builtin::panic = old_panic;
|
||||
builtin::panic = &test_panic;
|
||||
int tests_passed = 0;
|
||||
int test_count = tests.len;
|
||||
DString name = dstring::temp_with_capacity(64);
|
||||
usz len = max_name + 9;
|
||||
name.append_repeat('-', len / 2);
|
||||
name.append(" TESTS ");
|
||||
name.append_repeat('-', len - len / 2);
|
||||
io::printn(name);
|
||||
name.clear();
|
||||
foreach(unit : tests)
|
||||
if (has_error && log_size > 0)
|
||||
{
|
||||
defer name.clear();
|
||||
name.appendf("Testing %s ", unit.name);
|
||||
name.append_repeat('.', max_name - unit.name.len + 2);
|
||||
io::printf("%s ", name.str_view());
|
||||
(void)io::stdout().flush();
|
||||
if (libc::setjmp(&context.buf) == 0)
|
||||
test_context.fake_stdout.write_byte('\n')!!;
|
||||
test_context.fake_stdout.write_byte('\0')!!;
|
||||
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
|
||||
|
||||
io::printfn("\n========== TEST LOG ============");
|
||||
io::printfn("%s\n", test_context.current_test_name);
|
||||
while (try c = test_context.fake_stdout.read_byte())
|
||||
{
|
||||
if (catch err = unit.func())
|
||||
if (@unlikely(c == '\0'))
|
||||
{
|
||||
io::printfn("[failed] Failed due to: %s", err);
|
||||
continue;
|
||||
// ignore junk from previous tests
|
||||
break;
|
||||
}
|
||||
io::printn("[ok]");
|
||||
tests_passed++;
|
||||
libc::putchar(c);
|
||||
}
|
||||
io::printf("========== TEST END ============");
|
||||
}
|
||||
io::printfn("\n%d test%s run.\n", test_count, test_count > 1 ? "s" : "");
|
||||
io::printfn("Test Result: %s. %d passed, %d failed.",
|
||||
tests_passed < test_count ? "FAILED" : "ok", tests_passed, test_count - tests_passed);
|
||||
return test_count == tests_passed;
|
||||
(void)stdout.flush();
|
||||
}
|
||||
|
||||
fn bool run_tests(TestUnit[] tests) @if(!$$OLD_TEST)
|
||||
fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
{
|
||||
usz max_name;
|
||||
bool sort_tests = true;
|
||||
bool check_leaks = true;
|
||||
foreach (&unit : tests)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
}
|
||||
quicksort(tests, &cmp_test_unit);
|
||||
|
||||
TestContext context;
|
||||
TestContext context =
|
||||
{
|
||||
.assert_print_backtrace = true,
|
||||
.breakpoint_on_assert = false,
|
||||
.test_filter = "",
|
||||
.has_ansi_codes = terminal_has_ansi_codes(),
|
||||
.stored.allocator = mem,
|
||||
.stored.stderr = *io::stderr(),
|
||||
.stored.stdout = *io::stdout(),
|
||||
};
|
||||
for (int i = 1; i < args.len; i++)
|
||||
{
|
||||
switch (args[i])
|
||||
{
|
||||
case "--test-breakpoint":
|
||||
context.breakpoint_on_assert = true;
|
||||
case "--test-nosort":
|
||||
sort_tests = false;
|
||||
case "--test-noleak":
|
||||
check_leaks = false;
|
||||
case "--test-nocapture":
|
||||
context.is_no_capture = true;
|
||||
case "--noansi":
|
||||
context.has_ansi_codes = false;
|
||||
case "--useansi":
|
||||
context.has_ansi_codes = true;
|
||||
case "--test-quiet":
|
||||
context.is_quiet_mode = true;
|
||||
case "--test-filter":
|
||||
if (i == args.len - 1)
|
||||
{
|
||||
io::printn("Invalid arguments to test runner.");
|
||||
return false;
|
||||
}
|
||||
context.test_filter = args[i + 1];
|
||||
i++;
|
||||
default:
|
||||
io::printfn("Unknown argument: %s", args[i]);
|
||||
}
|
||||
}
|
||||
test_context = &context;
|
||||
|
||||
if (sort_tests)
|
||||
{
|
||||
quicksort(tests, &cmp_test_unit);
|
||||
}
|
||||
|
||||
// Buffer for hijacking the output
|
||||
$if (!env::NO_LIBC):
|
||||
context.fake_stdout.file = libc::tmpfile();
|
||||
$endif
|
||||
if (context.fake_stdout.file == null)
|
||||
{
|
||||
io::print("Failed to hijack stdout, tests will print everything");
|
||||
}
|
||||
|
||||
PanicFn old_panic = builtin::panic;
|
||||
defer builtin::panic = old_panic;
|
||||
builtin::panic = &test_panic;
|
||||
int tests_passed = 0;
|
||||
int tests_skipped = 0;
|
||||
int test_count = tests.len;
|
||||
DString name = dstring::temp_with_capacity(64);
|
||||
usz len = max_name + 9;
|
||||
name.append_repeat('-', len / 2);
|
||||
name.append(" TESTS ");
|
||||
name.append_repeat('-', len - len / 2);
|
||||
io::printn(name);
|
||||
if (!context.is_quiet_mode) io::printn(name);
|
||||
name.clear();
|
||||
PoolState temp_state = mem::temp_push();
|
||||
defer mem::temp_pop(temp_state);
|
||||
foreach(unit : tests)
|
||||
{
|
||||
mem::temp_pop(temp_state);
|
||||
if (context.test_filter && !unit.name.contains(context.test_filter))
|
||||
{
|
||||
tests_skipped++;
|
||||
continue;
|
||||
}
|
||||
context.setup_fn = null;
|
||||
context.teardown_fn = null;
|
||||
context.current_test_name = unit.name;
|
||||
|
||||
defer name.clear();
|
||||
name.appendf("Testing %s ", unit.name);
|
||||
name.append_repeat('.', max_name - unit.name.len + 2);
|
||||
io::printf("%s ", name.str_view());
|
||||
if (context.is_quiet_mode)
|
||||
{
|
||||
io::print(".");
|
||||
}
|
||||
else
|
||||
{
|
||||
io::printf("%s ", name.str_view());
|
||||
}
|
||||
(void)io::stdout().flush();
|
||||
TrackingAllocator mem;
|
||||
|
||||
mem.init(context.stored.allocator);
|
||||
if (libc::setjmp(&context.buf) == 0)
|
||||
{
|
||||
mute_output();
|
||||
mem.clear();
|
||||
if (check_leaks) allocator::thread_allocator = &mem;
|
||||
unit.func();
|
||||
io::printn("[ok]");
|
||||
tests_passed++;
|
||||
// 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;
|
||||
|
||||
unmute_output(false); // all good, discard output
|
||||
if (mem.has_leaks())
|
||||
{
|
||||
if (context.is_quiet_mode) io::printf("\n%s ", context.current_test_name);
|
||||
io::print(context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
|
||||
io::printn(" LEAKS DETECTED!");
|
||||
mem.print_report();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!context.is_quiet_mode)
|
||||
{
|
||||
io::printfn(context.has_ansi_codes ? "[\e[0;32mPASS\e[0m]" : "[PASS]");
|
||||
}
|
||||
tests_passed++;
|
||||
}
|
||||
}
|
||||
mem.free();
|
||||
}
|
||||
io::printfn("\n%d test%s run.\n", test_count, test_count > 1 ? "s" : "");
|
||||
io::printfn("Test Result: %s. %d passed, %d failed.",
|
||||
tests_passed < test_count ? "FAILED" : "ok", tests_passed, test_count - tests_passed);
|
||||
return test_count == tests_passed;
|
||||
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: ",
|
||||
context.has_ansi_codes ? (n_failed ? "\e[0;31m" : "\e[0;32m") : "",
|
||||
n_failed ? "FAILED" : "PASSED",
|
||||
context.has_ansi_codes ? "\e[0m" : "",
|
||||
);
|
||||
|
||||
io::printfn("%d passed, %d failed, %d skipped.",
|
||||
tests_passed,
|
||||
n_failed,
|
||||
tests_skipped);
|
||||
|
||||
// cleanup fake_stdout file
|
||||
if (context.fake_stdout.file) libc::fclose(context.fake_stdout.file);
|
||||
context.fake_stdout.file = null;
|
||||
|
||||
return n_failed == 0;
|
||||
}
|
||||
|
||||
fn bool default_test_runner(String[] args)
|
||||
fn bool default_test_runner(String[] args) => @pool()
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
return run_tests(test_collection_create(allocator::temp()));
|
||||
};
|
||||
assert(test_context == null, "test suite is already running");
|
||||
return run_tests(args, test_collection_create(tmem));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
module std::core::sanitizer::asan;
|
||||
|
||||
def ErrorCallback = fn void (ZString);
|
||||
alias ErrorCallback = fn void (ZString);
|
||||
|
||||
<*
|
||||
Marks a memory region ([addr, addr+size)) as unaddressable.
|
||||
@@ -26,8 +26,8 @@ def ErrorCallback = fn void (ZString);
|
||||
NOTE This function is not thread-safe because no two threads can poison or
|
||||
unpoison memory in the same memory region simultaneously.
|
||||
|
||||
@param addr "Start of memory region."
|
||||
@param size "Size of memory region."
|
||||
@param addr : "Start of memory region."
|
||||
@param size : "Size of memory region."
|
||||
*>
|
||||
macro poison_memory_region(void* addr, usz size)
|
||||
{
|
||||
@@ -47,8 +47,8 @@ macro poison_memory_region(void* addr, usz size)
|
||||
NOTE This function is not thread-safe because no two threads can
|
||||
poison or unpoison memory in the same memory region simultaneously.
|
||||
|
||||
@param addr "Start of memory region."
|
||||
@param size "Size of memory region."
|
||||
@param addr : "Start of memory region."
|
||||
@param size : "Size of memory region."
|
||||
*>
|
||||
macro unpoison_memory_region(void* addr, usz size)
|
||||
{
|
||||
@@ -60,7 +60,7 @@ macro unpoison_memory_region(void* addr, usz size)
|
||||
<*
|
||||
Checks if an address is poisoned.
|
||||
@return "True if 'addr' is poisoned (that is, 1-byte read/write access to this address would result in an error report from ASan). Otherwise returns false."
|
||||
@param addr "Address to check."
|
||||
@param addr : "Address to check."
|
||||
*>
|
||||
macro bool address_is_poisoned(void* addr)
|
||||
{
|
||||
@@ -77,8 +77,8 @@ macro bool address_is_poisoned(void* addr)
|
||||
If at least one byte in [beg, beg+size) is poisoned, returns the
|
||||
address of the first such byte. Otherwise returns 0.
|
||||
|
||||
@param beg "Start of memory region."
|
||||
@param size "Start of memory region."
|
||||
@param beg : "Start of memory region."
|
||||
@param size : "Start of memory region."
|
||||
@return "Address of first poisoned byte."
|
||||
*>
|
||||
macro void* region_is_poisoned(void* beg, usz size)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module std::core::sanitizer::tsan;
|
||||
|
||||
distinct MutexFlags = inline CUInt;
|
||||
typedef MutexFlags = inline CUInt;
|
||||
|
||||
const MutexFlags MUTEX_LINKER_INIT = 1 << 0;
|
||||
const MutexFlags MUTEX_WRITE_REENTRANT = 1 << 1;
|
||||
|
||||
169
lib/std/core/slice2d.c3
Normal file
169
lib/std/core/slice2d.c3
Normal file
@@ -0,0 +1,169 @@
|
||||
module std::core::array::slice {Type};
|
||||
|
||||
<*
|
||||
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* 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 };
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
233
lib/std/core/string_escape.c3
Normal file
233
lib/std/core/string_escape.c3
Normal file
@@ -0,0 +1,233 @@
|
||||
// Copyright (c) 2024 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
<*
|
||||
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
|
||||
DString result = dstring::new_with_capacity(allocator, initial_capacity);
|
||||
|
||||
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('"');
|
||||
return result.copy_str(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
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"
|
||||
@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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
DString result = dstring::new_with_capacity(allocator, s.len);
|
||||
|
||||
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:
|
||||
return INVALID_ESCAPE_SEQUENCE?;
|
||||
}
|
||||
}
|
||||
|
||||
return result.copy_str(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
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"
|
||||
@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) => s.unescape(tmem, allow_unquoted);
|
||||
|
||||
<*
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -11,22 +11,22 @@ fn void StringIterator.reset(&self)
|
||||
self.current = 0;
|
||||
}
|
||||
|
||||
fn Char32! StringIterator.next(&self)
|
||||
fn Char32? StringIterator.next(&self)
|
||||
{
|
||||
usz len = self.utf8.len;
|
||||
usz current = self.current;
|
||||
if (current >= len) return IteratorResult.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;
|
||||
return res;
|
||||
}
|
||||
|
||||
fn Char32! StringIterator.peek(&self)
|
||||
fn Char32? StringIterator.peek(&self)
|
||||
{
|
||||
usz len = self.utf8.len;
|
||||
usz current = self.current;
|
||||
if (current >= len) return IteratorResult.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;
|
||||
@@ -37,13 +37,13 @@ fn bool StringIterator.has_next(&self)
|
||||
return self.current < self.utf8.len;
|
||||
}
|
||||
|
||||
fn Char32! StringIterator.get(&self)
|
||||
fn Char32? StringIterator.get(&self)
|
||||
{
|
||||
usz len = self.utf8.len;
|
||||
usz current = self.current;
|
||||
usz read = (len - current < 4 ? len - current : 4);
|
||||
usz index = current > read ? current - read : 0;
|
||||
if (index >= len) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
if (index >= len) return NO_MORE_ELEMENT?;
|
||||
Char32 res = conv::utf8_to_char32(&self.utf8[index], &read)!;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ const uint[2] B1B_MAX = { 9007199, 254740991 };
|
||||
<*
|
||||
@require chars.len > 0
|
||||
*>
|
||||
macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
{
|
||||
uint[KMAX] x;
|
||||
const uint[2] TH = B1B_MAX;
|
||||
@@ -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 NumberConversion.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 NumberConversion.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 NumberConversion.MALFORMED_FLOAT?;
|
||||
if (!got_digit) return MALFORMED_FLOAT?;
|
||||
if ((c | 32) == 'e')
|
||||
{
|
||||
if (last_char == index) return NumberConversion.MALFORMED_FLOAT?;
|
||||
long e10 = String.to_long((String)chars[index + 1..]) ?? NumberConversion.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 NumberConversion.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 NumberConversion.FLOAT_OUT_OF_RANGE?;
|
||||
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.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)
|
||||
@@ -320,12 +320,12 @@ 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 NumberConversion.MALFORMED_FLOAT?;
|
||||
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT?;
|
||||
}
|
||||
return math::scalbn(y, e2);
|
||||
}
|
||||
|
||||
macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
{
|
||||
double scale = 1;
|
||||
uint x;
|
||||
@@ -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 NumberConversion.MALFORMED_FLOAT?;
|
||||
if (!got_digit) return MALFORMED_FLOAT?;
|
||||
return sign * 0.0;
|
||||
}
|
||||
if (index != last_char && (c = chars[++index]) == '0')
|
||||
@@ -369,17 +369,14 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
{
|
||||
if (c == '.')
|
||||
{
|
||||
if (got_rad) return NumberConversion.MALFORMED_FLOAT?;
|
||||
if (got_rad) return MALFORMED_FLOAT?;
|
||||
got_rad = true;
|
||||
rp = dc;
|
||||
}
|
||||
else
|
||||
{
|
||||
got_digit = true;
|
||||
int d = {|
|
||||
if (c > '9') return (c | 32) + 10 - 'a';
|
||||
return c - '0';
|
||||
|};
|
||||
int d = c > '9' ? ((c | 32) + 10 - 'a') : (c - '0');
|
||||
switch
|
||||
{
|
||||
case dc < 8:
|
||||
@@ -396,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 NumberConversion.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..]) ?? (NumberConversion.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 NumberConversion.FLOAT_OUT_OF_RANGE?;
|
||||
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.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)
|
||||
{
|
||||
@@ -444,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 NumberConversion.FLOAT_OUT_OF_RANGE?;
|
||||
if (!y) return FLOAT_OUT_OF_RANGE?;
|
||||
|
||||
return math::scalbn(y, (int)e2);
|
||||
}
|
||||
@@ -452,7 +449,7 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
macro String.to_real(chars, $Type) @private
|
||||
{
|
||||
int sign = 1;
|
||||
$switch ($Type)
|
||||
$switch $Type:
|
||||
$case float:
|
||||
const int BITS = math::FLOAT_MANT_DIG;
|
||||
const int EMIN = math::FLOAT_MIN_EXP - BITS;
|
||||
@@ -465,16 +462,23 @@ macro String.to_real(chars, $Type) @private
|
||||
$error "Unexpected type";
|
||||
$endswitch
|
||||
|
||||
while (chars.len && chars[0] == ' ') chars = chars[1..];
|
||||
if (!chars.len) return NumberConversion.MALFORMED_FLOAT?;
|
||||
switch (chars[0])
|
||||
chars = chars.trim();
|
||||
if (!chars.len) return MALFORMED_FLOAT?;
|
||||
|
||||
if (chars.len != 1)
|
||||
{
|
||||
case '-':
|
||||
sign = -1;
|
||||
nextcase;
|
||||
case '+':
|
||||
chars = chars[1..];
|
||||
switch (chars[0])
|
||||
{
|
||||
case '-':
|
||||
sign = -1;
|
||||
nextcase;
|
||||
case '+':
|
||||
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;
|
||||
|
||||
|
||||
220
lib/std/core/test.c3
Normal file
220
lib/std/core/test.c3
Normal file
@@ -0,0 +1,220 @@
|
||||
<*
|
||||
Unit test module
|
||||
|
||||
This module provides a toolset of macros for running unit test checks
|
||||
|
||||
Example:
|
||||
```c3
|
||||
module sample::m;
|
||||
import std::io;
|
||||
|
||||
faultdef DIVISION_BY_ZERO;
|
||||
|
||||
fn double? divide(int a, int b)
|
||||
{
|
||||
if (b == 0) return MathError.DIVISION_BY_ZERO?;
|
||||
return (double)(a) / (double)(b);
|
||||
}
|
||||
|
||||
fn void? test_div() @test
|
||||
{
|
||||
test::eq(2, divide(6, 3)!);
|
||||
test::ne(1, 2);
|
||||
test::ge(3, 3);
|
||||
test::gt(2, divide(3, 3)!);
|
||||
test::lt(2, 3);
|
||||
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), MathError.DIVISION_BY_ZERO);
|
||||
}
|
||||
|
||||
```
|
||||
*>
|
||||
// Copyright (c) 2025 Alex Veden <i@alexveden.com>. 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::test;
|
||||
import std::core::runtime @public;
|
||||
import std::math, std::io, libc;
|
||||
|
||||
<*
|
||||
Initializes test case context.
|
||||
|
||||
@param setup_fn : `initializer function for test case`
|
||||
@param teardown_fn : `cleanup function for test context (may be null)`
|
||||
|
||||
@require runtime::test_context != null : "Only allowed in @test functions"
|
||||
@require setup_fn != null : "setup_fn must always be set"
|
||||
*>
|
||||
macro @setup(TestFn setup_fn, TestFn teardown_fn = null)
|
||||
{
|
||||
runtime::test_context.setup_fn = setup_fn;
|
||||
runtime::test_context.teardown_fn = teardown_fn;
|
||||
runtime::test_context.setup_fn();
|
||||
}
|
||||
|
||||
<*
|
||||
Checks condition and fails assertion if not true
|
||||
|
||||
@param #condition : `any boolean condition, will be expanded by text`
|
||||
@param format : `printf compatible format`
|
||||
@param args : `vargs for format`
|
||||
@require runtime::test_context != null : "Only allowed in @test functions"
|
||||
*>
|
||||
macro @check(#condition, String format = "", args...)
|
||||
{
|
||||
if (!#condition)
|
||||
{
|
||||
@stack_mem(512; Allocator allocator)
|
||||
{
|
||||
DString s;
|
||||
s.init(allocator);
|
||||
s.appendf("check `%s` failed. ", $stringify(#condition));
|
||||
s.appendf(format, ...args);
|
||||
print_panicf(s.str_view());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
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)
|
||||
{
|
||||
if (catch err = #funcresult)
|
||||
{
|
||||
if (err != error_expected)
|
||||
{
|
||||
print_panicf("`%s` expected to return error [%s], got [%s]",
|
||||
$stringify(#funcresult), error_expected, err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
print_panicf("`%s` error [%s] was not returned.", $stringify(#funcresult), error_expected);
|
||||
}
|
||||
|
||||
<*
|
||||
Check if left == right
|
||||
|
||||
@param left : `left argument of any comparable type`
|
||||
@param right : `right argument of any comparable type`
|
||||
@require runtime::test_context != null : "Only allowed in @test functions"
|
||||
*>
|
||||
macro eq(left, right)
|
||||
{
|
||||
if (!equals(left, right))
|
||||
{
|
||||
print_panicf("`%s` != `%s`", left, right);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check left floating point value is approximately equals to right value
|
||||
|
||||
@param places : `number of decimal places to compare (default: 7)`
|
||||
@param delta : `minimal allowed difference (overrides places parameter)`
|
||||
@param equal_nan : `allows comparing nan values, if left and right both nans result is ok`
|
||||
|
||||
@require places > 0, places <= 20 : "too many decimal places"
|
||||
@require delta >= 0, delta <= 1 : "delta must be a small number"
|
||||
@require runtime::test_context != null : "Only allowed in @test functions"
|
||||
*>
|
||||
macro void eq_approx(double left, double right, uint places = 7, double delta = 0, bool equal_nan = true)
|
||||
{
|
||||
double diff = left - right;
|
||||
double eps = delta;
|
||||
if (eps == 0) eps = 1.0 / math::pow(10.0, places);
|
||||
|
||||
if (!math::is_approx(left, right, eps))
|
||||
{
|
||||
if (equal_nan && math::is_nan(left) && math::is_nan(right)) return;
|
||||
print_panicf("Not almost equal: `%s` !~~ `%s` delta=%e diff: %e", left, right, eps, diff);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check if left != right
|
||||
|
||||
@param left : `left argument of any comparable type`
|
||||
@param right : `right argument of any comparable type`
|
||||
@require runtime::test_context != null : "Only allowed in @test functions"
|
||||
*>
|
||||
macro void ne(left, right)
|
||||
{
|
||||
if (equals(left, right))
|
||||
{
|
||||
print_panicf("`%s` == `%s`", left, right);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check if left > right
|
||||
|
||||
@param left : `left argument of any comparable type`
|
||||
@param right : `right argument of any comparable type`
|
||||
@require runtime::test_context != null : "Only allowed in @test functions"
|
||||
*>
|
||||
macro gt(left, right)
|
||||
{
|
||||
if (!builtin::greater(left, right))
|
||||
{
|
||||
print_panicf("`%s` <= `%s`", left, right);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check if left >= right
|
||||
|
||||
@param left : `left argument of any comparable type`
|
||||
@param right : `right argument of any comparable type`
|
||||
@require runtime::test_context != null : "Only allowed in @test functions"
|
||||
*>
|
||||
macro ge(left, right)
|
||||
{
|
||||
if (!builtin::greater_eq(left, right))
|
||||
{
|
||||
print_panicf("`%s` < `%s`", left, right);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check if left < right
|
||||
|
||||
@param left : `left argument of any comparable type`
|
||||
@param right : `right argument of any comparable type`
|
||||
@require runtime::test_context != null : "Only allowed in @test functions"
|
||||
*>
|
||||
macro lt(left, right)
|
||||
{
|
||||
if (!builtin::less(left, right))
|
||||
{
|
||||
print_panicf("`%s` >= `%s`", left, right);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Check if left <= right
|
||||
|
||||
@param left : `left argument of any comparable type`
|
||||
@param right : `right argument of any comparable type`
|
||||
@require runtime::test_context != null : "Only allowed in @test functions"
|
||||
*>
|
||||
macro le(left, right)
|
||||
{
|
||||
if (!builtin::less_eq(left, right))
|
||||
{
|
||||
print_panicf("`%s` > `%s`", left, right);
|
||||
}
|
||||
}
|
||||
|
||||
macro void print_panicf(format, ...) @local
|
||||
{
|
||||
runtime::test_context.assert_print_backtrace = false;
|
||||
builtin::panicf(format, $$FILE, $$FUNC, $$LINE, $vasplat);
|
||||
}
|
||||
|
||||
@@ -3,15 +3,11 @@ module std::core::types;
|
||||
import libc;
|
||||
|
||||
|
||||
fault ConversionResult
|
||||
{
|
||||
VALUE_OUT_OF_RANGE,
|
||||
VALUE_OUT_OF_UNSIGNED_RANGE,
|
||||
}
|
||||
faultdef VALUE_OUT_OF_RANGE, VALUE_OUT_OF_UNSIGNED_RANGE;
|
||||
|
||||
<*
|
||||
@require $Type.kindof.is_int() "Type was not an integer"
|
||||
@require v.type.kindof == ENUM "Value was not an enum"
|
||||
@require $Type.kindof.is_int() : "Type was not an integer"
|
||||
@require v.type.kindof == ENUM : "Value was not an enum"
|
||||
*>
|
||||
macro any_to_enum_ordinal(any v, $Type)
|
||||
{
|
||||
@@ -19,8 +15,8 @@ macro any_to_enum_ordinal(any v, $Type)
|
||||
}
|
||||
|
||||
<*
|
||||
@require $Type.kindof.is_int() "Type was not an integer"
|
||||
@require v.type.kindof.is_int() "Value was not an integer"
|
||||
@require $Type.kindof.is_int() : "Type was not an integer"
|
||||
@require v.type.kindof.is_int() : "Value was not an integer"
|
||||
*>
|
||||
macro any_to_int(any v, $Type)
|
||||
{
|
||||
@@ -33,47 +29,47 @@ macro any_to_int(any v, $Type)
|
||||
{
|
||||
case ichar:
|
||||
ichar c = *(char*)v.ptr;
|
||||
if (is_mixed_signed && c < 0) return ConversionResult.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 ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (s > max || s < min) return ConversionResult.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 ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (i > max || i < min) return ConversionResult.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 ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (l > max || l < min) return ConversionResult.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 ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
|
||||
if (i > max || i < min) return ConversionResult.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 ConversionResult.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 ConversionResult.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 ConversionResult.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 ConversionResult.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 ConversionResult.VALUE_OUT_OF_RANGE?;
|
||||
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
|
||||
return ($Type)i;
|
||||
default:
|
||||
unreachable();
|
||||
@@ -93,20 +89,25 @@ fn bool typeid.is_subtype_of(self, typeid other)
|
||||
macro bool is_subtype_of($Type, $OtherType)
|
||||
{
|
||||
var $typeid = $Type.typeid;
|
||||
$switch ($Type)
|
||||
$switch $Type:
|
||||
$case $OtherType: return true;
|
||||
$default: return false;
|
||||
$endswitch
|
||||
}
|
||||
macro bool is_numerical($Type)
|
||||
{
|
||||
var $kind = $Type.kindof;
|
||||
$if $kind == TypeKind.DISTINCT:
|
||||
return is_numerical($typefrom($Type.inner));
|
||||
$else
|
||||
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|
||||
|| $kind == TypeKind.VECTOR;
|
||||
$endif
|
||||
$switch $Type.kindof:
|
||||
$case DISTINCT:
|
||||
$case CONST_ENUM:
|
||||
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
|
||||
@@ -116,7 +117,7 @@ fn bool TypeKind.is_int(kind) @inline
|
||||
|
||||
macro bool is_slice_convertable($Type)
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$switch $Type.kindof:
|
||||
$case SLICE:
|
||||
return true;
|
||||
$case POINTER:
|
||||
@@ -130,49 +131,82 @@ macro bool is_bool($Type) @const => $Type.kindof == TypeKind.BOOL;
|
||||
macro bool is_int($Type) @const => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT;
|
||||
|
||||
<*
|
||||
@require is_numerical($Type) "Expected a numerical type"
|
||||
@require is_numerical($Type) : "Expected a numerical type"
|
||||
*>
|
||||
macro bool is_signed($Type) @const
|
||||
{
|
||||
$switch (inner_kind($Type))
|
||||
$switch inner_kind($Type):
|
||||
$case SIGNED_INT:
|
||||
$case FLOAT:
|
||||
return true;
|
||||
$case VECTOR:
|
||||
return is_signed($typefrom($Type.inner));
|
||||
return is_signed($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
@require is_numerical($Type) "Expected a numerical type"
|
||||
@require is_numerical($Type) : "Expected a numerical type"
|
||||
*>
|
||||
macro bool is_unsigned($Type) @const
|
||||
{
|
||||
$switch (inner_kind($Type))
|
||||
$switch inner_kind($Type):
|
||||
$case UNSIGNED_INT:
|
||||
return true;
|
||||
$case VECTOR:
|
||||
return is_unsigned($typefrom($Type.inner));
|
||||
return is_unsigned($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro typeid flat_type($Type) @const
|
||||
{
|
||||
$if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
|
||||
return flat_type($Type.inner);
|
||||
$else
|
||||
return $Type.typeid;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro TypeKind flat_kind($Type) @const
|
||||
{
|
||||
$if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
|
||||
return flat_type($Type.inner);
|
||||
$else
|
||||
return $Type.kindof;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro bool is_indexable($Type) @const
|
||||
{
|
||||
return $defined($Type{}[0]);
|
||||
return $defined(($Type){}[0]);
|
||||
}
|
||||
|
||||
macro bool is_ref_indexable($Type) @const
|
||||
{
|
||||
return $defined(&$Type{}[0]);
|
||||
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 DISTINCT:
|
||||
$case CONST_ENUM:
|
||||
return is_flat_intlike($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_intlike($Type) @const
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$switch $Type.kindof:
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
return true;
|
||||
@@ -185,12 +219,12 @@ macro bool is_intlike($Type) @const
|
||||
|
||||
macro bool is_underlying_int($Type) @const
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$switch $Type.kindof:
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
return true;
|
||||
$case DISTINCT:
|
||||
return is_underlying_int($typefrom($Type.inner));
|
||||
return is_underlying_int($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
@@ -200,7 +234,7 @@ macro bool is_float($Type) @const => $Type.kindof == TypeKind.FLOAT;
|
||||
|
||||
macro bool is_floatlike($Type) @const
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$switch $Type.kindof:
|
||||
$case FLOAT:
|
||||
return true;
|
||||
$case VECTOR:
|
||||
@@ -217,8 +251,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 == DISTINCT || $Type.kindof == CONST_ENUM:
|
||||
return inner_type($Type.inner);
|
||||
$else
|
||||
return $Type.typeid;
|
||||
$endif
|
||||
@@ -240,7 +274,7 @@ macro bool @has_same(#a, #b, ...) @const
|
||||
$if $type_a != @typeid(#b):
|
||||
return false;
|
||||
$endif
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
$if @typeid($vaexpr[$i]) != $type_a:
|
||||
return false;
|
||||
$endif
|
||||
@@ -250,13 +284,15 @@ macro bool @has_same(#a, #b, ...) @const
|
||||
|
||||
macro bool may_load_atomic($Type) @const
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$switch $Type.kindof:
|
||||
$case BOOL:
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
$case POINTER:
|
||||
$case FLOAT:
|
||||
return true;
|
||||
$case DISTINCT:
|
||||
$case ENUM:
|
||||
return may_load_atomic($Type.inner);
|
||||
$default:
|
||||
return false;
|
||||
@@ -265,15 +301,17 @@ macro bool may_load_atomic($Type) @const
|
||||
|
||||
macro lower_to_atomic_compatible_type($Type) @const
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
return $Type.typeid;
|
||||
$switch $Type.kindof:
|
||||
$case BOOL:
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
return $Type.typeid;
|
||||
$case DISTINCT:
|
||||
$case CONST_ENUM:
|
||||
return lower_to_atomic_compatible_type($Type.inner);
|
||||
$case FLOAT:
|
||||
$switch ($Type)
|
||||
$case float16:
|
||||
$switch $Type:
|
||||
$case float16:
|
||||
return ushort.typeid;
|
||||
$case float:
|
||||
return uint.typeid;
|
||||
@@ -318,11 +356,6 @@ macro bool implements_copy($Type) @const
|
||||
return $defined($Type.copy) && $defined($Type.free);
|
||||
}
|
||||
|
||||
macro bool is_equatable_value(value) @deprecated
|
||||
{
|
||||
return is_equatable_type($typeof(value));
|
||||
}
|
||||
|
||||
macro bool @equatable_value(#value) @const
|
||||
{
|
||||
return is_equatable_type($typeof(#value));
|
||||
@@ -337,15 +370,6 @@ macro bool @comparable_value(#value) @const
|
||||
$endif
|
||||
}
|
||||
|
||||
macro bool is_comparable_value(value) @deprecated
|
||||
{
|
||||
$if $defined(value.less) || $defined(value.compare_to):
|
||||
return true;
|
||||
$else
|
||||
return $typeof(value).is_ordered;
|
||||
$endif
|
||||
}
|
||||
|
||||
enum TypeKind : char
|
||||
{
|
||||
VOID,
|
||||
@@ -354,10 +378,10 @@ enum TypeKind : char
|
||||
UNSIGNED_INT,
|
||||
FLOAT,
|
||||
TYPEID,
|
||||
ANYFAULT,
|
||||
FAULT,
|
||||
ANY,
|
||||
ENUM,
|
||||
FAULT,
|
||||
CONST_ENUM,
|
||||
STRUCT,
|
||||
UNION,
|
||||
BITSTRUCT,
|
||||
|
||||
@@ -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;
|
||||
<*
|
||||
Return true if two values have the same type before any conversions.
|
||||
*>
|
||||
macro bool @is_same_type(#value1, #value2) @const => $typeof(#value1).typeid == $typeof(#value2).typeid;
|
||||
macro bool @is_bool(#value) @const => types::is_bool($typeof(#value));
|
||||
macro bool @is_int(#value) @const => types::is_int($typeof(#value));
|
||||
macro bool @is_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 @assign_to(#value1, #value2) @const => @assignable_to(#value1, $typeof(#value2));
|
||||
macro bool @is_lvalue(#value) => $defined(#value = #value);
|
||||
macro bool @is_const(#foo) @const @builtin
|
||||
{
|
||||
var $v;
|
||||
return $defined($v = #foo);
|
||||
}
|
||||
|
||||
macro promote_int(x)
|
||||
{
|
||||
@@ -27,10 +32,29 @@ macro promote_int(x)
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Select between two values at compile time,
|
||||
the values do not have to be of the same type.
|
||||
|
||||
This acts like `$bool ? #value_1 : #value_2` but at compile time.
|
||||
|
||||
@param $bool : `true for picking the first value, false for the other`
|
||||
@param #value_1
|
||||
@param #value_2
|
||||
@returns `The selected value.`
|
||||
*>
|
||||
macro @select(bool $bool, #value_1, #value_2) @builtin
|
||||
{
|
||||
$if $bool:
|
||||
return #value_1;
|
||||
$else
|
||||
return #value_2;
|
||||
$endif
|
||||
}
|
||||
macro promote_int_same(x, y)
|
||||
{
|
||||
$if @is_int(x):
|
||||
$switch
|
||||
$switch:
|
||||
$case @is_vector(y) &&& $typeof(y).inner == float.typeid:
|
||||
return (float)x;
|
||||
$case $typeof(y).typeid == float.typeid:
|
||||
|
||||
736
lib/std/crypto/ed25519.c3
Normal file
736
lib/std/crypto/ed25519.c3
Normal file
@@ -0,0 +1,736 @@
|
||||
/*
|
||||
Ed25519 Digital Signature Algorithm
|
||||
*/
|
||||
|
||||
module std::crypto::ed25519;
|
||||
import std::hash::sha512;
|
||||
|
||||
alias Ed25519PrivateKey = char[32];
|
||||
alias Ed25519PublicKey = char[Ed25519PrivateKey.len];
|
||||
alias Ed25519Signature = char[2 * Ed25519PublicKey.len];
|
||||
|
||||
<*
|
||||
Generate a public key from a private key.
|
||||
|
||||
@param [in] private_key : "32 bytes of cryptographically secure random data"
|
||||
@require private_key.len == Ed25519PrivateKey.len
|
||||
*>
|
||||
fn Ed25519PublicKey public_keygen(char[] private_key)
|
||||
{
|
||||
return pack(&&unproject(&&(BASE * expand_private_key(private_key)[:FBaseInt.len])));
|
||||
}
|
||||
|
||||
<*
|
||||
Sign a message.
|
||||
|
||||
@param [in] message
|
||||
@param [in] private_key
|
||||
@param [in] public_key
|
||||
@require private_key.len == Ed25519PrivateKey.len
|
||||
@require public_key.len == Ed25519PublicKey.len
|
||||
*>
|
||||
fn Ed25519Signature sign(char[] message, char[] private_key, char[] public_key)
|
||||
{
|
||||
Ed25519Signature r @noinit;
|
||||
|
||||
char[*] exp = expand_private_key(private_key);
|
||||
|
||||
Sha512 sha @noinit;
|
||||
sha.init();
|
||||
|
||||
sha.update(exp[FBaseInt.len..]);
|
||||
sha.update(message);
|
||||
|
||||
FBaseInt k = from_bytes(&&sha.final());
|
||||
|
||||
r[:F25519Int.len] = pack(&&unproject(&&(BASE * k[..])))[..];
|
||||
|
||||
sha.init();
|
||||
|
||||
sha.update(r[:F25519Int.len]);
|
||||
sha.update(public_key);
|
||||
sha.update(message);
|
||||
|
||||
FBaseInt z = from_bytes(&&sha.final());
|
||||
FBaseInt e = from_bytes(exp[:FBaseInt.len]);
|
||||
|
||||
r[F25519Int.len..] = (z * e + k)[..];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
<*
|
||||
Verify the signature of a message.
|
||||
|
||||
@param [in] message
|
||||
@param [in] signature
|
||||
@param [in] public_key
|
||||
@require signature.len == Ed25519Signature.len
|
||||
@require public_key.len == Ed25519PublicKey.len
|
||||
*>
|
||||
fn bool verify(char[] message, char[] signature, char[] public_key)
|
||||
{
|
||||
char ok = 1;
|
||||
|
||||
F25519Int lhs = pack(&&unproject(&&(BASE * signature[F25519Int.len..])));
|
||||
|
||||
Unpacking unp_p = unpack_on_curve((F25519Int*)public_key);
|
||||
Projection p = project(&unp_p.point);
|
||||
ok &= unp_p.on_curve;
|
||||
|
||||
Sha512 sha @noinit;
|
||||
sha.init();
|
||||
|
||||
sha.update(signature[:F25519Int.len]);
|
||||
sha.update(public_key);
|
||||
sha.update(message);
|
||||
|
||||
FBaseInt z = from_bytes(&&sha.final());
|
||||
|
||||
p = p * z[..];
|
||||
|
||||
Unpacking unp_q = unpack_on_curve((F25519Int*)signature[:F25519Int.len]);
|
||||
Projection q = project(&unp_q.point);
|
||||
ok &= unp_q.on_curve;
|
||||
|
||||
p = p + q;
|
||||
|
||||
F25519Int rhs = pack(&&unproject(&p));
|
||||
|
||||
return (bool)(ok & eq(&lhs, &rhs));
|
||||
}
|
||||
|
||||
// Base point for Ed25519. Generate a subgroup of order 2^252+0x14def9dea2f79cd65812631a5cf5d3ed
|
||||
const Projection BASE @private =
|
||||
{
|
||||
x"1ad5258f602d56c9 b2a7259560c72c69 5cdcd6fd31e2a4c0 fe536ecdd3366921",
|
||||
x"5866666666666666 6666666666666666 6666666666666666 6666666666666666",
|
||||
x"a3ddb7a5b38ade6d f5525177809ff020 7de3ab648e4eea66 65768bd70f5f8767",
|
||||
ONE
|
||||
};
|
||||
|
||||
<*
|
||||
Compute the pruned SHA-512 hash of a private key.
|
||||
|
||||
@param [in] private_key
|
||||
@require private_key.len == Ed25519PrivateKey.len
|
||||
*>
|
||||
fn char[sha512::HASH_SIZE] expand_private_key(char[] private_key) @local
|
||||
{
|
||||
char[*] r = sha512::hash(private_key);
|
||||
|
||||
r[0] &= 0b11111000;
|
||||
r[FBaseInt.len - 1] &= 0b01111111;
|
||||
r[FBaseInt.len - 1] |= 0b01000000;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Operations on the twisted Edwards curve -x^2+y^2=1-121665/121666*x^2*y^2 over the prime field F_(2^255-19) (edwards25519)
|
||||
The set of F_(2^255-19)-rational curve points is a group of order 2^3*(2^252+0x14def9dea2f79cd65812631a5cf5d3ed)
|
||||
*/
|
||||
|
||||
module std::crypto::ed25519 @private;
|
||||
|
||||
// Affine coordinates.
|
||||
struct Point
|
||||
{
|
||||
F25519Int x;
|
||||
F25519Int y;
|
||||
}
|
||||
|
||||
// Projective coordinates.
|
||||
struct Projection
|
||||
{
|
||||
F25519Int x;
|
||||
F25519Int y;
|
||||
F25519Int t;
|
||||
F25519Int z;
|
||||
}
|
||||
|
||||
// Neutral.
|
||||
const Projection NEUTRAL =
|
||||
{
|
||||
ZERO,
|
||||
ONE,
|
||||
ZERO,
|
||||
ONE
|
||||
};
|
||||
|
||||
<*
|
||||
Convert affine to projective coordinates.
|
||||
|
||||
@param [&in] p
|
||||
*>
|
||||
fn Projection project(Point* p) => { p.x, p.y, p.x * p.y, ONE };
|
||||
|
||||
<*
|
||||
Convert projective to affine coordinates.
|
||||
|
||||
@param [&in] p
|
||||
*>
|
||||
fn Point unproject(Projection* p)
|
||||
{
|
||||
Point r @noinit;
|
||||
|
||||
F25519Int inv = p.z.inv();
|
||||
r.x = p.x * inv;
|
||||
r.y = p.y * inv;
|
||||
|
||||
r.x.normalize();
|
||||
r.y.normalize();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
// d parameter for edwards25519 : -121665/121666
|
||||
const F25519Int D = x"a3785913ca4deb75 abd841414d0a7000 98e879777940c78c 73fe6f2bee6c0352";
|
||||
|
||||
// 2*d
|
||||
const F25519Int DD = x"59f1b226949bd6eb 56b183829a14e000 30d1f3eef2808e19 e7fcdf56dcd90624";
|
||||
|
||||
<*
|
||||
Compress a point.
|
||||
|
||||
@param [&in] p
|
||||
*>
|
||||
fn F25519Int pack(Point* p)
|
||||
{
|
||||
Point r = *p;
|
||||
|
||||
r.x.normalize();
|
||||
r.y.normalize();
|
||||
|
||||
r.y[^1] |= (r.x[0] & 1) << 7;
|
||||
|
||||
return r.y;
|
||||
}
|
||||
|
||||
struct Unpacking
|
||||
{
|
||||
Point point;
|
||||
char on_curve; // Non-zero if true.
|
||||
}
|
||||
|
||||
<*
|
||||
Uncompress a point. Check if it is on the curve.
|
||||
|
||||
@param [&in] encoding
|
||||
*>
|
||||
fn Unpacking unpack_on_curve(F25519Int* encoding)
|
||||
{
|
||||
Point p @noinit;
|
||||
|
||||
char parity = (*encoding)[^1] >> 7;
|
||||
|
||||
p.y = *encoding;
|
||||
p.y[^1] &= 0b01111111;
|
||||
|
||||
F25519Int y2 = p.y * p.y;
|
||||
F25519Int x2 = (D * y2 + ONE).inv() * (y2 - ONE);
|
||||
|
||||
F25519Int x = x2.sqrt();
|
||||
|
||||
p.x = f25519_select(&x, &&-x, (x[0] ^ parity) & 1);
|
||||
|
||||
F25519Int _x2 = p.x * p.x;
|
||||
|
||||
x2.normalize();
|
||||
_x2.normalize();
|
||||
|
||||
return {p, eq(&x2, &_x2)};
|
||||
}
|
||||
|
||||
macro Projection Projection.@add(&s, Projection #p) @operator(+) => s.add(@addr(#p));
|
||||
<*
|
||||
Addition.
|
||||
|
||||
@param [&in] s
|
||||
*>
|
||||
fn Projection Projection.add(&s, Projection* p) @operator(+)
|
||||
{
|
||||
Projection r @noinit;
|
||||
|
||||
F25519Int a = (s.y - s.x) * (p.y - p.x);
|
||||
F25519Int b = (s.y + s.x) * (p.y + p.x);
|
||||
F25519Int c = s.t * DD * p.t;
|
||||
F25519Int d = (s.z * p.z).mul_s(2);
|
||||
F25519Int e = b - a;
|
||||
F25519Int f = d - c;
|
||||
F25519Int g = d + c;
|
||||
F25519Int h = b + a;
|
||||
|
||||
r.x = e * f;
|
||||
r.y = g * h;
|
||||
r.t = e * h;
|
||||
r.z = f * g;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
<*
|
||||
Double a point.
|
||||
|
||||
@param [&in] s
|
||||
*>
|
||||
fn Projection Projection.twice(&s)
|
||||
{
|
||||
Projection r @noinit;
|
||||
|
||||
F25519Int a = s.x * s.x;
|
||||
F25519Int b = s.y * s.y;
|
||||
F25519Int c = (s.z * s.z).mul_s(2);
|
||||
F25519Int d = s.x + s.y;
|
||||
F25519Int e = d * d - a - b;
|
||||
F25519Int g = b - a;
|
||||
F25519Int f = g - c;
|
||||
F25519Int h = -b - a;
|
||||
|
||||
r.x = e * f;
|
||||
r.y = g * h;
|
||||
r.t = e * h;
|
||||
r.z = f * g;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
<*
|
||||
Variable base scalar multiplication.
|
||||
|
||||
@param [&in] s
|
||||
@param [in] n
|
||||
*>
|
||||
fn Projection Projection.mul(&s, char[] n) @operator(*)
|
||||
{
|
||||
Projection r = NEUTRAL;
|
||||
|
||||
for (isz i = n.len << 3 - 1; i >= 0; i--)
|
||||
{
|
||||
r = r.twice();
|
||||
|
||||
Projection t = r + s;
|
||||
|
||||
char bit = n[i >> 3] >> (i & 7) & 1;
|
||||
r.x = f25519_select(&r.x, &t.x, bit);
|
||||
r.y = f25519_select(&r.y, &t.y, bit);
|
||||
r.z = f25519_select(&r.z, &t.z, bit);
|
||||
r.t = f25519_select(&r.t, &t.t, bit);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Modular arithmetic over the prime field F_(2^255-19)
|
||||
*/
|
||||
|
||||
module std::crypto::ed25519 @private;
|
||||
|
||||
typedef F25519Int = inline char[32];
|
||||
|
||||
const F25519Int ZERO = {};
|
||||
const F25519Int ONE = {[0] = 1};
|
||||
|
||||
<*
|
||||
Reduce an element with carry to at most 2^255+18 (32 bytes)
|
||||
|
||||
@param [&inout] s
|
||||
*>
|
||||
fn void F25519Int.reduce_carry(&s, uint carry)
|
||||
{
|
||||
// Reduce using 2^255 = 19 mod p
|
||||
(*s)[^1] &= 0b01111111;
|
||||
|
||||
carry *= 19;
|
||||
|
||||
foreach (i, &v : s)
|
||||
{
|
||||
carry += *v;
|
||||
*v = (char)carry;
|
||||
carry >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Reduce an element to at most 2^255-19
|
||||
|
||||
@param [&inout] s
|
||||
*>
|
||||
fn void F25519Int.normalize(&s)
|
||||
{
|
||||
s.reduce_carry((*s)[^1] >> 7);
|
||||
|
||||
// Substract p
|
||||
F25519Int sub @noinit;
|
||||
ushort c = 19;
|
||||
foreach (i, v : (*s)[:^1])
|
||||
{
|
||||
c += v;
|
||||
sub[i] = (char)c;
|
||||
c >>= 8;
|
||||
}
|
||||
c += (*s)[^1] - 0b10000000;
|
||||
sub[^1] = (char)c;
|
||||
|
||||
*s = f25519_select(&sub, s, (char)(c >> 15));
|
||||
}
|
||||
|
||||
<*
|
||||
Constant-time equality comparison. Return is non-zero if true.
|
||||
|
||||
@param [&in] a
|
||||
@param [&in] b
|
||||
*>
|
||||
fn char eq(F25519Int* a, F25519Int* b)
|
||||
{
|
||||
char e;
|
||||
foreach (i, v : a) e |= v ^ (*b)[i];
|
||||
|
||||
e |= (e >> 4);
|
||||
e |= (e >> 2);
|
||||
e |= (e >> 1);
|
||||
|
||||
return e ^ 1;
|
||||
}
|
||||
|
||||
<*
|
||||
Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
|
||||
|
||||
@param [&in] zero : "selected if condition is 0"
|
||||
@param [&in] one : "selected if condition is 1"
|
||||
*>
|
||||
fn F25519Int f25519_select(F25519Int* zero, F25519Int* one, char condition)
|
||||
{
|
||||
F25519Int r @noinit;
|
||||
|
||||
foreach (i, z : zero) r[i] = z ^ (-condition & ((*one)[i] ^ z));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
macro F25519Int F25519Int.@add(&s, F25519Int #n) @operator(+) => s.add(@addr(#n));
|
||||
|
||||
<*
|
||||
Addition.
|
||||
|
||||
@param [&in] s
|
||||
@param [&in] n
|
||||
*>
|
||||
fn F25519Int F25519Int.add(&s, F25519Int* n) @operator(+)
|
||||
{
|
||||
F25519Int r @noinit;
|
||||
|
||||
ushort c;
|
||||
foreach (i, v : s)
|
||||
{
|
||||
c >>= 8;
|
||||
c += v + (*n)[i];
|
||||
r[i] = (char)c;
|
||||
}
|
||||
|
||||
r.reduce_carry(c >> 7);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
macro F25519Int F25519Int.@sub(&s, F25519Int #n) @operator(-) => s.sub(@addr(#n));
|
||||
|
||||
<*
|
||||
Substraction.
|
||||
|
||||
@param [&in] s
|
||||
@param [&in] n
|
||||
*>
|
||||
fn F25519Int F25519Int.sub(&s, F25519Int* n) @operator(-)
|
||||
{
|
||||
// Compute s+2*p-n instead of s-n to avoid underflow.
|
||||
F25519Int r @noinit;
|
||||
|
||||
uint c = (char)~(2 * 19 - 1);
|
||||
foreach (i, v : (*s)[:^1])
|
||||
{
|
||||
c += 0b11111111_00000000 + v - (*n)[i];
|
||||
r[i] = (char)c;
|
||||
c >>= 8;
|
||||
}
|
||||
c += (*s)[^1] - (*n)[^1];
|
||||
r[^1] = (char)c;
|
||||
|
||||
r.reduce_carry(c >> 7);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
<*
|
||||
Negation.
|
||||
|
||||
@param [&in] s
|
||||
*>
|
||||
fn F25519Int F25519Int.neg(&s) @operator(-)
|
||||
{
|
||||
// Compute 2*p-s instead of -s to avoid underflow.
|
||||
F25519Int r @noinit;
|
||||
|
||||
uint c = (char)~(2 * 19 - 1);
|
||||
foreach (i, v : (*s)[:^1])
|
||||
{
|
||||
c += 0b11111111_00000000 - v;
|
||||
r[i] = (char)c;
|
||||
c >>= 8;
|
||||
}
|
||||
c -= (*s)[^1];
|
||||
r[^1] = (char)c;
|
||||
|
||||
r.reduce_carry(c >> 7);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
macro F25519Int F25519Int.@mul(&s, F25519Int #n) @operator(*) => s.mul(@addr(#n));
|
||||
|
||||
<*
|
||||
Multiplication.
|
||||
|
||||
@param [&in] s
|
||||
@param [&in] n
|
||||
*>
|
||||
fn F25519Int F25519Int.mul(&s, F25519Int* n) @operator(*)
|
||||
{
|
||||
F25519Int r @noinit;
|
||||
|
||||
uint c;
|
||||
for (usz i = 0; i < F25519Int.len; i++)
|
||||
{
|
||||
c >>= 8;
|
||||
for (usz j; j <= i; j++) c += (*s)[j] * (*n)[i - j];
|
||||
// Reduce using 2^256 = 2*19 mod p
|
||||
for (usz j = i + 1; j < F25519Int.len; j++) c += (*s)[j] * (*n)[^j - i] * 2 * 19;
|
||||
r[i] = (char)c;
|
||||
}
|
||||
|
||||
r.reduce_carry(c >> 7);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
<*
|
||||
Multiplication by a small element.
|
||||
|
||||
@param [&in] s
|
||||
*>
|
||||
fn F25519Int F25519Int.mul_s(&s, uint n)
|
||||
{
|
||||
F25519Int r @noinit;
|
||||
|
||||
uint c;
|
||||
foreach (i, v : s)
|
||||
{
|
||||
c >>= 8;
|
||||
c += v * n;
|
||||
r[i] = (char)c;
|
||||
}
|
||||
|
||||
r.reduce_carry(c >> 7);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
<*
|
||||
Inverse an element.
|
||||
|
||||
@param [&in] s
|
||||
*>
|
||||
fn F25519Int F25519Int.inv(&s)
|
||||
{
|
||||
//Compute s^(p-2)
|
||||
F25519Int r = *s;
|
||||
|
||||
for (usz i; i < 255 - 1 - 5; i++) r = r * r * s;
|
||||
|
||||
r *= r;
|
||||
r = r * r * s;
|
||||
r *= r;
|
||||
r = r * r * s;
|
||||
r = r * r * s;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
<*
|
||||
Raise an element to the power of 2^252-3
|
||||
|
||||
@param [&in] s
|
||||
*>
|
||||
fn F25519Int F25519Int.pow_2523(&s) @local
|
||||
{
|
||||
F25519Int r = *s;
|
||||
|
||||
for (usz i; i < 252 - 1 - 2; i++) r = r * r * s;
|
||||
|
||||
r *= r;
|
||||
r = r * r * s;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
<*
|
||||
Compute the square root of an element.
|
||||
|
||||
@param [&in] s
|
||||
*>
|
||||
fn F25519Int F25519Int.sqrt(&s)
|
||||
{
|
||||
F25519Int twice = s.mul_s(2);
|
||||
F25519Int pow = twice.pow_2523();
|
||||
|
||||
return (twice * pow * pow - ONE) * s * pow;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Modular arithmetic over the prime field F_(2^252+0x14def9dea2f79cd65812631a5cf5d3ed)
|
||||
*/
|
||||
|
||||
module std::crypto::ed25519 @private;
|
||||
import std::math;
|
||||
|
||||
typedef FBaseInt = inline char[32];
|
||||
|
||||
// Order of the field : 2^252+0x14def9dea2f79cd65812631a5cf5d3ed
|
||||
const FBaseInt ORDER = x"edd3f55c1a631258 d69cf7a2def9de14 0000000000000000 0000000000000010";
|
||||
|
||||
<*
|
||||
Interpret bytes as a normalized element.
|
||||
|
||||
@param [in] bytes
|
||||
*>
|
||||
fn FBaseInt from_bytes(char[] bytes)
|
||||
{
|
||||
FBaseInt r;
|
||||
|
||||
usz bitc = min(252 - 1, bytes.len << 3);
|
||||
usz bytec = bitc >> 3;
|
||||
usz mod = bitc & 7;
|
||||
usz rem = bytes.len << 3 - bitc;
|
||||
|
||||
r[:bytec] = bytes[^bytec..];
|
||||
|
||||
if (mod)
|
||||
{
|
||||
r <<= mod;
|
||||
r[0] |= bytes[^bytec + 1] >> (8 - mod);
|
||||
}
|
||||
|
||||
for (isz i = rem - 1; i >= 0; i--)
|
||||
{
|
||||
r <<= 1;
|
||||
r[0] |= bytes[i >> 3] >> (i & 7) & 1;
|
||||
r = r.sub_l(&ORDER);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
<*
|
||||
Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
|
||||
|
||||
@param [&in] zero : "selected if condition is 0"
|
||||
@param [&in] one : "selected if condition is 1"
|
||||
*>
|
||||
fn FBaseInt fbase_select(FBaseInt* zero, FBaseInt* one, char condition)
|
||||
{
|
||||
FBaseInt r @noinit;
|
||||
|
||||
foreach (i, z : zero) r[i] = z ^ (-condition & ((*one)[i] ^ z));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
macro FBaseInt FBaseInt.@add(&s, FBaseInt #n) @operator(+) => s.add(@addr(#n));
|
||||
|
||||
<*
|
||||
Addition.
|
||||
|
||||
@param [&in] s
|
||||
@param [&in] n
|
||||
*>
|
||||
fn FBaseInt FBaseInt.add(&s, FBaseInt* n) @operator(+)
|
||||
{
|
||||
FBaseInt r @noinit;
|
||||
|
||||
ushort c;
|
||||
foreach (i, v : s)
|
||||
{
|
||||
c += v + (*n)[i];
|
||||
r[i] = (char)c;
|
||||
c >>= 8;
|
||||
}
|
||||
|
||||
return r.sub_l(&ORDER);
|
||||
}
|
||||
|
||||
<*
|
||||
Substraction if RHS is less than LHS else identity.
|
||||
|
||||
@param [&in] s
|
||||
@param [&in] n
|
||||
*>
|
||||
fn FBaseInt FBaseInt.sub_l(&s, FBaseInt* n)
|
||||
{
|
||||
FBaseInt sub @noinit;
|
||||
ushort c;
|
||||
foreach (i, v : s)
|
||||
{
|
||||
c = v - (*n)[i] - c;
|
||||
sub[i] = (char)c;
|
||||
c = (c >> 8) & 1;
|
||||
}
|
||||
|
||||
return fbase_select(&sub, s, (char)c);
|
||||
}
|
||||
|
||||
<*
|
||||
Left shift.
|
||||
|
||||
@param [&in] s
|
||||
*>
|
||||
fn FBaseInt FBaseInt.shl(&s, usz n) @operator(<<)
|
||||
{
|
||||
FBaseInt r @noinit;
|
||||
|
||||
ushort c;
|
||||
foreach (i, v : s)
|
||||
{
|
||||
c |= v << n;
|
||||
r[i] = (char)c;
|
||||
c >>= 8;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
macro FBaseInt FBaseInt.@mul(&s, FBaseInt #n) @operator(*) => s.mul(@addr(#n));
|
||||
<*
|
||||
Multiplication.
|
||||
|
||||
@param [&in] s
|
||||
@param [&in] n
|
||||
*>
|
||||
fn FBaseInt FBaseInt.mul(&s, FBaseInt* n) @operator(*)
|
||||
{
|
||||
FBaseInt r;
|
||||
|
||||
for (isz i = 252; i >= 0; i--)
|
||||
{
|
||||
r = (r << 1).sub_l(&ORDER);
|
||||
r = fbase_select(&r, &&(r + s), (*n)[i >> 3] >> (i & 7) & 1);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -12,8 +12,8 @@ struct Rc4
|
||||
<*
|
||||
Initialize the RC4 state.
|
||||
|
||||
@param [in] key "The key to use"
|
||||
@require key.len > 0 "The key must be at least 1 byte long"
|
||||
@param [in] key : "The key to use"
|
||||
@require key.len > 0 : "The key must be at least 1 byte long"
|
||||
*>
|
||||
fn void Rc4.init(&self, char[] key)
|
||||
{
|
||||
@@ -43,9 +43,9 @@ fn void crypt(char[] key, char[] data)
|
||||
<*
|
||||
Encrypt or decrypt a sequence of bytes.
|
||||
|
||||
@param [in] in "The input"
|
||||
@param [out] out "The output"
|
||||
@require in.len <= out.len "Output would overflow"
|
||||
@param [in] in : "The input"
|
||||
@param [out] out : "The output"
|
||||
@require in.len <= out.len : "Output would overflow"
|
||||
*>
|
||||
fn void Rc4.crypt(&self, char[] in, char[] out)
|
||||
{
|
||||
@@ -67,7 +67,7 @@ fn void Rc4.crypt(&self, char[] in, char[] out)
|
||||
<*
|
||||
Clear the rc4 state.
|
||||
|
||||
@param [&out] self "The RC4 State"
|
||||
@param [&out] self : "The RC4 State"
|
||||
*>
|
||||
fn void Rc4.destroy(&self)
|
||||
{
|
||||
|
||||
@@ -14,13 +14,13 @@ const char DEFAULT_PAD = '=';
|
||||
|
||||
<*
|
||||
Encode the content of src into a newly allocated string
|
||||
@param [in] src "The input to be encoded."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@param [in] src : "The input to be encoded."
|
||||
@param padding : "The padding character or 0 if none"
|
||||
@param alphabet : "The alphabet to use"
|
||||
@require padding < 0xFF : "Invalid padding character"
|
||||
@return "The encoded string."
|
||||
*>
|
||||
fn String! encode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
|
||||
fn String? encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding));
|
||||
return encode_buffer(src, dst, padding, alphabet);
|
||||
@@ -28,28 +28,26 @@ fn String! encode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, B
|
||||
|
||||
<*
|
||||
Decode the content of src into a newly allocated char array.
|
||||
@param [in] src "The input to be encoded."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@param [in] src : "The input to be encoded."
|
||||
@param padding : "The padding character or 0 if none"
|
||||
@param alphabet : "The alphabet to use"
|
||||
@require padding < 0xFF : "Invalid padding character"
|
||||
@return "The decoded data."
|
||||
*>
|
||||
fn char[]! decode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
|
||||
fn char[]? decode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding));
|
||||
return decode_buffer(src, dst, padding, alphabet);
|
||||
}
|
||||
|
||||
fn String! encode_new(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::heap(), padding, alphabet);
|
||||
fn String! encode_temp(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::temp(), padding, alphabet);
|
||||
fn char[]! decode_new(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::heap(), padding, alphabet);
|
||||
fn char[]! decode_temp(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::temp(), padding, alphabet);
|
||||
fn String? tencode(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(tmem, code, padding, alphabet);
|
||||
fn char[]? tdecode(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(tmem, code, padding, alphabet);
|
||||
|
||||
<*
|
||||
Calculate the length in bytes of the decoded data.
|
||||
@param n "Length in bytes of input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@param n : "Length in bytes of input."
|
||||
@param padding : "The padding character or 0 if none"
|
||||
@require padding < 0xFF : "Invalid padding character"
|
||||
@return "Length in bytes of the decoded data."
|
||||
*>
|
||||
fn usz decode_len(usz n, char padding)
|
||||
@@ -62,9 +60,9 @@ fn usz decode_len(usz n, char padding)
|
||||
|
||||
<*
|
||||
Calculate the length in bytes of the encoded data.
|
||||
@param n "Length in bytes on input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@param n : "Length in bytes on input."
|
||||
@param padding : "The padding character or 0 if none"
|
||||
@require padding < 0xFF : "Invalid padding character"
|
||||
@return "Length in bytes of the encoded data."
|
||||
*>
|
||||
fn usz encode_len(usz n, char padding)
|
||||
@@ -79,16 +77,16 @@ fn usz encode_len(usz n, char padding)
|
||||
|
||||
<*
|
||||
Decode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be decoded."
|
||||
@param dst "The decoded input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@require dst.len >= decode_len(src.len, padding) "Destination buffer too small"
|
||||
@param src : "The input to be decoded."
|
||||
@param dst : "The decoded input."
|
||||
@param padding : "The padding character or 0 if none"
|
||||
@param alphabet : "The alphabet to use"
|
||||
@require padding < 0xFF : "Invalid padding character"
|
||||
@require dst.len >= decode_len(src.len, padding) : "Destination buffer too small"
|
||||
@return "The resulting dst buffer"
|
||||
@return! DecodingFailure
|
||||
@return? encoding::INVALID_PADDING, encoding::INVALID_CHARACTER
|
||||
*>
|
||||
fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
|
||||
fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
if (src.len == 0) return dst[:0];
|
||||
char* dst_ptr = dst;
|
||||
@@ -103,12 +101,12 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
{
|
||||
if (src.len == 0)
|
||||
{
|
||||
if (padding > 0) return DecodingFailure.INVALID_PADDING?;
|
||||
if (padding > 0) return encoding::INVALID_PADDING?;
|
||||
break;
|
||||
}
|
||||
if (src[0] == padding) break;
|
||||
buf[i] = alphabet.reverse[src[0]];
|
||||
if (buf[i] == INVALID) return DecodingFailure.INVALID_CHARACTER?;
|
||||
if (buf[i] == INVALID) return encoding::INVALID_CHARACTER?;
|
||||
src = src[1..];
|
||||
}
|
||||
|
||||
@@ -152,7 +150,7 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
dst[0] = buf[1] >> 2 | buf[0] << 3;
|
||||
n++;
|
||||
default:
|
||||
return DecodingFailure.INVALID_CHARACTER?;
|
||||
return encoding::INVALID_CHARACTER?;
|
||||
}
|
||||
if (dst.len < 5) break;
|
||||
dst = dst[5..];
|
||||
@@ -162,12 +160,12 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
|
||||
<*
|
||||
Encode the content of src into dst, which must be properly sized.
|
||||
@param [in] src "The input to be encoded."
|
||||
@param [inout] dst "The encoded input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@require dst.len >= encode_len(src.len, padding) "Destination buffer too small"
|
||||
@param [in] src : "The input to be encoded."
|
||||
@param [inout] dst : "The encoded input."
|
||||
@param padding : "The padding character or 0 if none"
|
||||
@param alphabet : "The alphabet to use"
|
||||
@require padding < 0xFF : "Invalid padding character"
|
||||
@require dst.len >= encode_len(src.len, padding) : "Destination buffer too small"
|
||||
@return "The encoded size."
|
||||
*>
|
||||
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
|
||||
@@ -244,138 +242,7 @@ const char INVALID @private = 0xff;
|
||||
const int STD_PADDING = '=';
|
||||
const int NO_PADDING = -1;
|
||||
|
||||
fault Base32Error
|
||||
{
|
||||
DUPLICATE_IN_ALPHABET,
|
||||
PADDING_IN_ALPHABET,
|
||||
INVALID_CHARACTER_IN_ALPHABET,
|
||||
DESTINATION_TOO_SMALL,
|
||||
INVALID_PADDING,
|
||||
CORRUPT_INPUT
|
||||
}
|
||||
|
||||
struct Base32Encoder @deprecated
|
||||
{
|
||||
Base32Alphabet alphabet;
|
||||
char padding;
|
||||
}
|
||||
|
||||
<*
|
||||
@param encoder "The 32-character alphabet for encoding."
|
||||
@param padding "Set to a negative value to disable padding."
|
||||
@require padding < 256
|
||||
*>
|
||||
fn void! Base32Encoder.init(&self, Alphabet encoder = STD_ALPHABET, int padding = STD_PADDING)
|
||||
{
|
||||
encoder.validate(padding)!;
|
||||
*self = { .alphabet = { .encoding = (char[32])encoder }, .padding = padding < 0 ? (char)0 : (char)padding};
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the length in bytes of the encoded data.
|
||||
@param n "Length in bytes on input."
|
||||
@return "Length in bytes of the encoded data."
|
||||
*>
|
||||
fn usz Base32Encoder.encode_len(&self, usz n)
|
||||
{
|
||||
return encode_len(n, self.padding);
|
||||
}
|
||||
|
||||
<*
|
||||
Encode the content of src into dst, which must be properly sized.
|
||||
@param [in] src "The input to be encoded."
|
||||
@param [inout] dst "The encoded input."
|
||||
@return "The encoded size."
|
||||
@return! Base32Error.DESTINATION_TOO_SMALL
|
||||
*>
|
||||
fn usz! Base32Encoder.encode(&self, char[] src, char[] dst)
|
||||
{
|
||||
usz dn = self.encode_len(src.len);
|
||||
if (dst.len < dn) return Base32Error.DESTINATION_TOO_SMALL?;
|
||||
return encode_buffer(src, dst, self.padding, &self.alphabet).len;
|
||||
}
|
||||
|
||||
struct Base32Decoder @deprecated
|
||||
{
|
||||
Base32Alphabet alphabet;
|
||||
char padding;
|
||||
}
|
||||
|
||||
<*
|
||||
@param decoder "The alphabet used for decoding."
|
||||
@param padding "Set to a negative value to disable padding."
|
||||
@require padding < 256
|
||||
*>
|
||||
fn void! Base32Decoder.init(&self, Alphabet decoder = STD_ALPHABET, int padding = STD_PADDING)
|
||||
{
|
||||
decoder.validate(padding)!;
|
||||
*self = { .alphabet = { .encoding = (char[32])decoder }, .padding = padding < 0 ? (char)0 : (char)padding };
|
||||
|
||||
self.alphabet.reverse[..] = INVALID;
|
||||
foreach (char i, c : decoder)
|
||||
{
|
||||
self.alphabet.reverse[c] = i;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the length in bytes of the decoded data.
|
||||
@param n "Length in bytes of input."
|
||||
@return "Length in bytes of the decoded data."
|
||||
*>
|
||||
fn usz Base32Decoder.decode_len(&self, usz n)
|
||||
{
|
||||
return decode_len(n, self.padding);
|
||||
}
|
||||
|
||||
<*
|
||||
Decode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be decoded."
|
||||
@param dst "The decoded input."
|
||||
@return "The decoded size."
|
||||
@return! Base32Error.DESTINATION_TOO_SMALL, Base32Error.CORRUPT_INPUT
|
||||
*>
|
||||
fn usz! Base32Decoder.decode(&self, char[] src, char[] dst)
|
||||
{
|
||||
if (src.len == 0) return 0;
|
||||
usz dn = self.decode_len(src.len);
|
||||
if (dst.len < dn) return Base32Error.DESTINATION_TOO_SMALL?;
|
||||
return decode_buffer(src, dst, self.padding, &self.alphabet).len;
|
||||
}
|
||||
|
||||
|
||||
// Validate the 32-character alphabet to make sure that no character occurs
|
||||
// twice and that the padding is not present in the alphabet.
|
||||
fn void! Alphabet.validate(&self, int padding)
|
||||
{
|
||||
bool[256] checked;
|
||||
foreach (c : self)
|
||||
{
|
||||
if (checked[c])
|
||||
{
|
||||
return Base32Error.DUPLICATE_IN_ALPHABET?;
|
||||
}
|
||||
checked[c] = true;
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
return Base32Error.INVALID_CHARACTER_IN_ALPHABET?;
|
||||
}
|
||||
}
|
||||
if (padding >= 0)
|
||||
{
|
||||
char pad = (char)padding;
|
||||
if (pad == '\r' || pad == '\n')
|
||||
{
|
||||
return Base32Error.INVALID_PADDING?;
|
||||
}
|
||||
if (checked[pad])
|
||||
{
|
||||
return Base32Error.PADDING_IN_ALPHABET?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
distinct Alphabet = char[32];
|
||||
typedef Alphabet = char[32];
|
||||
// Standard base32 Alphabet
|
||||
const Alphabet STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
// Extended Hex Alphabet
|
||||
|
||||
@@ -43,29 +43,27 @@ const Base64Alphabet URL = {
|
||||
const STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
||||
fn String encode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
|
||||
fn String encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding));
|
||||
return encode_buffer(src, dst, padding, alphabet);
|
||||
}
|
||||
|
||||
fn char[]! decode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
|
||||
fn char[]? decode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding))!;
|
||||
return decode_buffer(src, dst, padding, alphabet);
|
||||
}
|
||||
|
||||
fn String encode_new(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::heap(), padding, alphabet);
|
||||
fn String encode_temp(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::temp(), padding, alphabet);
|
||||
fn char[]! decode_new(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::heap(), padding, alphabet);
|
||||
fn char[]! decode_temp(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::temp(), padding, alphabet);
|
||||
fn String tencode(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(tmem, code, padding, alphabet);
|
||||
fn char[]? tdecode(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(tmem, code, padding, alphabet);
|
||||
|
||||
|
||||
<*
|
||||
Calculate the size of the encoded data.
|
||||
@param n "Size of the input to be encoded."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@param n : "Size of the input to be encoded."
|
||||
@param padding : "The padding character or 0 if none"
|
||||
@require padding < 0xFF : "Invalid padding character"
|
||||
@return "The size of the input once encoded."
|
||||
*>
|
||||
fn usz encode_len(usz n, char padding)
|
||||
@@ -77,35 +75,34 @@ fn usz encode_len(usz n, char padding)
|
||||
|
||||
<*
|
||||
Calculate the size of the decoded data.
|
||||
@param n "Size of the input to be decoded."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@param n : "Size of the input to be decoded."
|
||||
@param padding : "The padding character or 0 if none"
|
||||
@require padding < 0xFF : "Invalid padding character"
|
||||
@return "The size of the input once decoded."
|
||||
@return! DecodingFailure.INVALID_PADDING
|
||||
@return? encoding::INVALID_PADDING
|
||||
*>
|
||||
fn usz! decode_len(usz n, char padding)
|
||||
fn usz? decode_len(usz n, char padding)
|
||||
{
|
||||
usz dn = n / 4 * 3;
|
||||
usz trailing = n % 4;
|
||||
if (padding)
|
||||
{
|
||||
if (trailing != 0) return DecodingFailure.INVALID_PADDING?;
|
||||
if (trailing != 0) return encoding::INVALID_PADDING?;
|
||||
// source size is multiple of 4
|
||||
return dn;
|
||||
}
|
||||
if (trailing == 1) return DecodingFailure.INVALID_PADDING?;
|
||||
if (trailing == 1) return encoding::INVALID_PADDING?;
|
||||
return dn + trailing * 3 / 4;
|
||||
}
|
||||
|
||||
<*
|
||||
Encode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be encoded."
|
||||
@param dst "The encoded input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@param src : "The input to be encoded."
|
||||
@param dst : "The encoded input."
|
||||
@param padding : "The padding character or 0 if none"
|
||||
@param alphabet : "The alphabet to use"
|
||||
@require padding < 0xFF : "Invalid padding character"
|
||||
@return "The encoded size."
|
||||
@return! Base64Error.DESTINATION_TOO_SMALL
|
||||
*>
|
||||
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
@@ -159,16 +156,16 @@ fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base
|
||||
|
||||
<*
|
||||
Decode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be decoded."
|
||||
@param dst "The decoded input."
|
||||
@param padding "The padding character or 0 if none"
|
||||
@param alphabet "The alphabet to use"
|
||||
@require (decode_len(src.len, padding) ?? 0) <= dst.len "Destination buffer too small"
|
||||
@require padding < 0xFF "Invalid padding character"
|
||||
@param src : "The input to be decoded."
|
||||
@param dst : "The decoded input."
|
||||
@param padding : "The padding character or 0 if none"
|
||||
@param alphabet : "The alphabet to use"
|
||||
@require (decode_len(src.len, padding) ?? 0) <= dst.len : "Destination buffer too small"
|
||||
@require padding < 0xFF : "Invalid padding character"
|
||||
@return "The decoded data."
|
||||
@return! DecodingFailure
|
||||
@return? encoding::INVALID_CHARACTER, encoding::INVALID_PADDING
|
||||
*>
|
||||
fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
|
||||
fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
|
||||
{
|
||||
if (src.len == 0) return dst[:0];
|
||||
usz dn = decode_len(src.len, padding)!;
|
||||
@@ -199,7 +196,7 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
case c1:
|
||||
case c2:
|
||||
case c3:
|
||||
return DecodingFailure.INVALID_CHARACTER?;
|
||||
return encoding::INVALID_CHARACTER?;
|
||||
}
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6 | (uint)c3;
|
||||
dst[0] = (char)(group >> 16);
|
||||
@@ -214,7 +211,7 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
src = src[^trailing..];
|
||||
char c0 = alphabet.reverse[src[0]];
|
||||
char c1 = alphabet.reverse[src[1]];
|
||||
if (c0 == 0xFF || c1 == 0xFF) return DecodingFailure.INVALID_PADDING?;
|
||||
if (c0 == 0xFF || c1 == 0xFF) return encoding::INVALID_PADDING?;
|
||||
if (!padding)
|
||||
{
|
||||
switch (src.len)
|
||||
@@ -224,7 +221,7 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
dst[0] = (char)(group >> 16);
|
||||
case 3:
|
||||
char c2 = alphabet.reverse[src[2]];
|
||||
if (c2 == 0xFF) return DecodingFailure.INVALID_CHARACTER?;
|
||||
if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
|
||||
dst[0] = (char)(group >> 16);
|
||||
dst[1] = (char)(group >> 8);
|
||||
@@ -238,13 +235,13 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
switch (padding)
|
||||
{
|
||||
case src[2]:
|
||||
if (src[3] != padding) return DecodingFailure.INVALID_PADDING?;
|
||||
if (src[3] != padding) return encoding::INVALID_PADDING?;
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12;
|
||||
dst[0] = (char)(group >> 16);
|
||||
dn -= 2;
|
||||
case src[3]:
|
||||
char c2 = alphabet.reverse[src[2]];
|
||||
if (c2 == 0xFF) return DecodingFailure.INVALID_CHARACTER?;
|
||||
if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
|
||||
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
|
||||
dst[0] = (char)(group >> 16);
|
||||
dst[1] = (char)(group >> 8);
|
||||
@@ -256,146 +253,3 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
|
||||
|
||||
const MASK @private = 0b111111;
|
||||
|
||||
struct Base64Encoder @deprecated
|
||||
{
|
||||
char padding;
|
||||
String alphabet;
|
||||
}
|
||||
|
||||
fault Base64Error
|
||||
{
|
||||
DUPLICATE_IN_ALPHABET,
|
||||
PADDING_IN_ALPHABET,
|
||||
DESTINATION_TOO_SMALL,
|
||||
INVALID_PADDING,
|
||||
INVALID_CHARACTER,
|
||||
}
|
||||
|
||||
<*
|
||||
@param alphabet "The alphabet used for encoding."
|
||||
@param padding "Set to a negative value to disable padding."
|
||||
@require alphabet.len == 64
|
||||
@require padding < 256
|
||||
@return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
|
||||
*>
|
||||
fn Base64Encoder*! Base64Encoder.init(&self, String alphabet, int padding = '=')
|
||||
{
|
||||
check_alphabet(alphabet, padding)!;
|
||||
*self = { .padding = padding < 0 ? 0 : (char)padding, .alphabet = alphabet };
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the size of the encoded data.
|
||||
@param n "Size of the input to be encoded."
|
||||
@return "The size of the input once encoded."
|
||||
*>
|
||||
fn usz Base64Encoder.encode_len(&self, usz n)
|
||||
{
|
||||
return encode_len(n, self.padding);
|
||||
}
|
||||
|
||||
<*
|
||||
Encode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be encoded."
|
||||
@param dst "The encoded input."
|
||||
@return "The encoded size."
|
||||
@return! Base64Error.DESTINATION_TOO_SMALL
|
||||
*>
|
||||
fn usz! Base64Encoder.encode(&self, char[] src, char[] dst)
|
||||
{
|
||||
if (src.len == 0) return 0;
|
||||
usz dn = self.encode_len(src.len);
|
||||
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
|
||||
Base64Alphabet a = { .encoding = self.alphabet[:64] };
|
||||
return encode_buffer(src, dst, self.padding, &a).len;
|
||||
}
|
||||
|
||||
struct Base64Decoder @deprecated
|
||||
{
|
||||
char padding;
|
||||
Base64Alphabet encoding;
|
||||
bool init_done;
|
||||
}
|
||||
|
||||
import std;
|
||||
<*
|
||||
@param alphabet "The alphabet used for encoding."
|
||||
@param padding "Set to a negative value to disable padding."
|
||||
@require alphabet.len == 64
|
||||
@require padding < 256
|
||||
@return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
|
||||
*>
|
||||
fn void! Base64Decoder.init(&self, String alphabet, int padding = '=')
|
||||
{
|
||||
self.init_done = true;
|
||||
check_alphabet(alphabet, padding)!;
|
||||
*self = { .padding = padding < 0 ? 0 : (char)padding, .encoding.encoding = alphabet[:64] };
|
||||
|
||||
self.encoding.reverse[..] = 0xFF;
|
||||
|
||||
foreach (i, c : alphabet)
|
||||
{
|
||||
self.encoding.reverse[c] = (char)i;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Calculate the size of the decoded data.
|
||||
@param n "Size of the input to be decoded."
|
||||
@return "The size of the input once decoded."
|
||||
@return! Base64Error.INVALID_PADDING
|
||||
*>
|
||||
fn usz! Base64Decoder.decode_len(&self, usz n)
|
||||
{
|
||||
return decode_len(n, self.padding) ?? Base64Error.INVALID_PADDING?;
|
||||
}
|
||||
|
||||
<*
|
||||
Decode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be decoded."
|
||||
@param dst "The decoded input."
|
||||
@return "The decoded size."
|
||||
@return! Base64Error.DESTINATION_TOO_SMALL, Base64Error.INVALID_PADDING, Base64Error.INVALID_CHARACTER
|
||||
*>
|
||||
fn usz! Base64Decoder.decode(&self, char[] src, char[] dst)
|
||||
{
|
||||
if (src.len == 0) return 0;
|
||||
usz dn = self.decode_len(src.len)!;
|
||||
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
|
||||
char[]! decoded = decode_buffer(src, dst, self.padding, &self.encoding);
|
||||
if (catch err = decoded)
|
||||
{
|
||||
case DecodingFailure.INVALID_PADDING:
|
||||
return Base64Error.INVALID_PADDING?;
|
||||
case DecodingFailure.INVALID_CHARACTER:
|
||||
return Base64Error.INVALID_CHARACTER?;
|
||||
default:
|
||||
return err?;
|
||||
}
|
||||
return decoded.len;
|
||||
}
|
||||
|
||||
// Make sure that all bytes in the alphabet are unique and
|
||||
// the padding is not present in the alphabet.
|
||||
fn void! check_alphabet(String alphabet, int padding) @local
|
||||
{
|
||||
bool[256] checked;
|
||||
if (padding < 0)
|
||||
{
|
||||
foreach (c : alphabet)
|
||||
{
|
||||
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
|
||||
checked[c] = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
char pad = (char)padding;
|
||||
foreach (c : alphabet)
|
||||
{
|
||||
if (c == pad) return Base64Error.PADDING_IN_ALPHABET?;
|
||||
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
|
||||
checked[c] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ struct CsvRow (Printable)
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
fn usz! CsvRow.to_format(&self, Formatter* f) @dynamic
|
||||
fn usz? CsvRow.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
return f.printf("%s", self.list);
|
||||
}
|
||||
@@ -38,30 +38,24 @@ fn void CsvReader.init(&self, InStream stream, String separator = ",")
|
||||
self.stream = stream;
|
||||
self.separator = separator;
|
||||
}
|
||||
|
||||
fn CsvRow! CsvReader.read_new_row(self)
|
||||
{
|
||||
return self.read_row(allocator::heap()) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator
|
||||
*>
|
||||
fn CsvRow! CsvReader.read_row(self, Allocator allocator)
|
||||
fn CsvRow? CsvReader.read_row(self, Allocator allocator)
|
||||
{
|
||||
String row = io::readline(self.stream, allocator: allocator)!;
|
||||
String row = io::readline(allocator, self.stream)!;
|
||||
defer catch allocator::free(allocator, row);
|
||||
String[] list = row.split(self.separator, allocator: allocator);
|
||||
String[] list = row.split(allocator, self.separator);
|
||||
return { list, row, allocator };
|
||||
}
|
||||
|
||||
fn CsvRow! CsvReader.read_temp_row(self)
|
||||
fn CsvRow? CsvReader.tread_row(self)
|
||||
{
|
||||
return self.read_row(allocator::temp()) @inline;
|
||||
return self.read_row(tmem) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.allocator `Row already freed`
|
||||
@require self.allocator != null : `Row already freed`
|
||||
*>
|
||||
fn void CsvRow.free(&self)
|
||||
{
|
||||
@@ -70,29 +64,32 @@ fn void CsvRow.free(&self)
|
||||
self.allocator = null;
|
||||
}
|
||||
|
||||
fn void! CsvReader.skip_row(self) @maydiscard
|
||||
fn void? CsvReader.skip_row(self) @maydiscard => @pool()
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
(void)io::treadline(self.stream);
|
||||
};
|
||||
(void)io::treadline(self.stream);
|
||||
}
|
||||
|
||||
macro void! CsvReader.@each_row(self, int rows = int.max; @body(String[] row)) @maydiscard
|
||||
macro void? @each_row(InStream stream, String separator = ",", int max_rows = int.max; @body(String[] row)) @maydiscard
|
||||
{
|
||||
InStream stream = self.stream;
|
||||
String sep = self.separator;
|
||||
while (rows--)
|
||||
while (max_rows--)
|
||||
{
|
||||
@stack_mem(512; Allocator mem)
|
||||
@stack_mem(512; mem)
|
||||
{
|
||||
String! s = io::readline(stream, mem);
|
||||
String? s = io::readline(mem, stream);
|
||||
if (catch err = s)
|
||||
{
|
||||
if (err == IoError.EOF) return;
|
||||
if (err == io::EOF) return;
|
||||
return err?;
|
||||
}
|
||||
@body(s.split(sep, allocator: mem));
|
||||
@body(s.split(mem, separator));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
macro void? CsvReader.@each_row(self, int rows = int.max; @body(String[] row)) @maydiscard
|
||||
{
|
||||
return @each_row(self.stream, self.separator, rows; row)
|
||||
{
|
||||
@body(row);
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
module std::encoding;
|
||||
|
||||
fault DecodingFailure
|
||||
{
|
||||
INVALID_CHARACTER,
|
||||
INVALID_PADDING,
|
||||
}
|
||||
faultdef INVALID_CHARACTER, INVALID_PADDING;
|
||||
@@ -8,41 +8,40 @@ fn String encode_buffer(char[] code, char[] buffer)
|
||||
return (String)buffer[:encode_bytes(code, buffer)];
|
||||
}
|
||||
|
||||
fn char[]! decode_buffer(char[] code, char[] buffer)
|
||||
fn char[]? decode_buffer(char[] code, char[] buffer)
|
||||
{
|
||||
return buffer[:decode_bytes(code, buffer)!];
|
||||
}
|
||||
|
||||
fn String encode(char[] code, Allocator allocator)
|
||||
fn String encode(Allocator allocator, char[] code)
|
||||
{
|
||||
char[] data = allocator::alloc_array(allocator, char, encode_len(code.len));
|
||||
return (String)data[:encode_bytes(code, data)];
|
||||
}
|
||||
|
||||
fn char[]! decode(char[] code, Allocator allocator)
|
||||
fn char[]? decode(Allocator allocator, char[] code)
|
||||
{
|
||||
char[] data = allocator::alloc_array(allocator, char, decode_len(code.len));
|
||||
return data[:decode_bytes(code, data)!];
|
||||
}
|
||||
|
||||
fn String encode_new(char[] code) @inline => encode(code, allocator::heap());
|
||||
fn String encode_temp(char[] code) @inline => encode(code, allocator::temp());
|
||||
fn char[]! decode_new(char[] code) @inline => decode(code, allocator::heap());
|
||||
fn char[]! decode_temp(char[] code) @inline => decode(code, allocator::temp());
|
||||
fn String tencode(char[] code) @inline => encode(tmem, code);
|
||||
fn char[]? tdecode(char[] code) @inline => decode(tmem, code);
|
||||
|
||||
|
||||
<*
|
||||
Calculate the size of the encoded data.
|
||||
@param n "Size of the input to be encoded."
|
||||
@param n : "Size of the input to be encoded."
|
||||
@return "The size of the input once encoded."
|
||||
*>
|
||||
fn usz encode_len(usz n) => n * 2;
|
||||
|
||||
<*
|
||||
Encode the content of src into dst, which must be properly sized.
|
||||
@param src "The input to be encoded."
|
||||
@param dst "The encoded input."
|
||||
@param src : "The input to be encoded."
|
||||
@param dst : "The encoded input."
|
||||
@return "The encoded size."
|
||||
@require dst.len >= encode_len(src.len) "Destination array is not large enough"
|
||||
@require dst.len >= encode_len(src.len) : "Destination array is not large enough"
|
||||
*>
|
||||
fn usz encode_bytes(char[] src, char[] dst)
|
||||
{
|
||||
@@ -58,7 +57,7 @@ fn usz encode_bytes(char[] src, char[] dst)
|
||||
|
||||
<*
|
||||
Calculate the size of the decoded data.
|
||||
@param n "Size of the input to be decoded."
|
||||
@param n : "Size of the input to be decoded."
|
||||
@return "The size of the input once decoded."
|
||||
*>
|
||||
macro usz decode_len(usz n) => n / 2;
|
||||
@@ -69,20 +68,20 @@ macro usz decode_len(usz n) => n / 2;
|
||||
Expects that src only contains hexadecimal characters and that src has even
|
||||
length.
|
||||
|
||||
@param src "The input to be decoded."
|
||||
@param dst "The decoded input."
|
||||
@require src.len % 2 == 0 "src is not of even length"
|
||||
@require dst.len >= decode_len(src.len) "Destination array is not large enough"
|
||||
@return! DecodingFailure.INVALID_CHARACTER
|
||||
@param src : "The input to be decoded."
|
||||
@param dst : "The decoded input."
|
||||
@require src.len % 2 == 0 : "src is not of even length"
|
||||
@require dst.len >= decode_len(src.len) : "Destination array is not large enough"
|
||||
@return? encoding::INVALID_CHARACTER
|
||||
*>
|
||||
fn usz! decode_bytes(char[] src, char[] dst)
|
||||
fn usz? decode_bytes(char[] src, char[] dst)
|
||||
{
|
||||
usz i;
|
||||
for (usz j = 1; j < src.len; j += 2)
|
||||
{
|
||||
char a = HEXREVERSE[src[j - 1]];
|
||||
char b = HEXREVERSE[src[j]];
|
||||
if (a > 0x0f || b > 0x0f) return DecodingFailure.INVALID_CHARACTER?;
|
||||
if (a > 0x0f || b > 0x0f) return encoding::INVALID_CHARACTER?;
|
||||
dst[i] = (a << 4) | b;
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -3,43 +3,45 @@
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::encoding::json;
|
||||
import std::io;
|
||||
import std::ascii;
|
||||
import std::collections::object;
|
||||
|
||||
fault JsonParsingError
|
||||
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, INVALID_NUMBER, MAX_DEPTH_REACHED;
|
||||
|
||||
int max_depth = 128;
|
||||
|
||||
fn Object*? parse_string(Allocator allocator, String s)
|
||||
{
|
||||
EOF,
|
||||
UNEXPECTED_CHARACTER,
|
||||
INVALID_ESCAPE_SEQUENCE,
|
||||
DUPLICATE_MEMBERS,
|
||||
INVALID_NUMBER,
|
||||
return parse(allocator, (ByteReader){}.init(s));
|
||||
}
|
||||
|
||||
fn Object*! parse_string(String s, Allocator allocator = allocator::heap())
|
||||
fn Object*? tparse_string(String s)
|
||||
{
|
||||
return parse(ByteReader{}.init(s), allocator);
|
||||
return parse(tmem, (ByteReader){}.init(s));
|
||||
}
|
||||
|
||||
fn Object*! temp_parse_string(String s)
|
||||
fn Object*? parse(Allocator allocator, InStream s)
|
||||
{
|
||||
return parse(ByteReader{}.init(s), allocator::temp());
|
||||
}
|
||||
|
||||
fn Object*! parse(InStream s, Allocator allocator = allocator::heap())
|
||||
{
|
||||
@stack_mem(512; Allocator mem)
|
||||
@stack_mem(512; Allocator smem)
|
||||
{
|
||||
JsonContext context = { .last_string = dstring::new_with_capacity(64, mem), .stream = s, .allocator = allocator };
|
||||
@pool(allocator)
|
||||
JsonContext context = { .last_string = dstring::new_with_capacity(smem, 64), .stream = s, .allocator = allocator };
|
||||
@pool()
|
||||
{
|
||||
return parse_any(&context);
|
||||
Object* o = parse_any(&context)!;
|
||||
defer catch o.free();
|
||||
while (char c = read_next(&context)!, c != 0)
|
||||
{
|
||||
if (c.is_space()) continue;
|
||||
return UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
if (!@catch(context.stream.read_byte())) return UNEXPECTED_CHARACTER?;
|
||||
return o;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
fn Object*! temp_parse(InStream s)
|
||||
fn Object*? tparse(InStream s)
|
||||
{
|
||||
return parse(s, allocator::temp());
|
||||
return parse(tmem, s);
|
||||
}
|
||||
|
||||
// -- Implementation follows --
|
||||
@@ -70,15 +72,18 @@ struct JsonContext @local
|
||||
DString last_string;
|
||||
double last_number;
|
||||
char current;
|
||||
bitstruct : char {
|
||||
int depth;
|
||||
bitstruct : char
|
||||
{
|
||||
bool skip_comments;
|
||||
bool reached_end;
|
||||
bool pushed_back;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fn Object*! parse_from_token(JsonContext* context, JsonTokenType token) @local
|
||||
fn Object*? parse_from_token(JsonContext* context, JsonTokenType token) @local
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
@@ -88,35 +93,41 @@ fn Object*! parse_from_token(JsonContext* context, JsonTokenType token) @local
|
||||
case COMMA:
|
||||
case RBRACE:
|
||||
case RBRACKET:
|
||||
case COLON: return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
case COLON: return UNEXPECTED_CHARACTER?;
|
||||
case STRING: return object::new_string(context.last_string.str_view(), context.allocator);
|
||||
case NUMBER: return object::new_float(context.last_number, context.allocator);
|
||||
case TRUE: return object::new_bool(true);
|
||||
case FALSE: return object::new_bool(false);
|
||||
case NULL: return object::new_null();
|
||||
case EOF: return JsonParsingError.EOF?;
|
||||
case EOF: return io::EOF?;
|
||||
}
|
||||
}
|
||||
fn Object*! parse_any(JsonContext* context) @local
|
||||
fn Object*? parse_any(JsonContext* context) @local
|
||||
{
|
||||
return parse_from_token(context, advance(context));
|
||||
}
|
||||
|
||||
fn JsonTokenType! lex_number(JsonContext *context, char c) @local
|
||||
fn JsonTokenType? lex_number(JsonContext *context, char c) @local
|
||||
{
|
||||
@stack_mem(256; Allocator mem)
|
||||
{
|
||||
DString t = dstring::new_with_capacity(32, allocator: mem);
|
||||
DString t = dstring::new_with_capacity(mem, 32);
|
||||
bool negate = c == '-';
|
||||
if (negate)
|
||||
{
|
||||
t.append(c);
|
||||
c = read_next(context)!;
|
||||
}
|
||||
bool leading_zero = c == '0';
|
||||
while (c.is_digit())
|
||||
{
|
||||
t.append(c);
|
||||
c = read_next(context)!;
|
||||
if (leading_zero)
|
||||
{
|
||||
if (c.is_digit()) return INVALID_NUMBER?;
|
||||
leading_zero = false;
|
||||
}
|
||||
}
|
||||
if (c == '.')
|
||||
{
|
||||
@@ -137,7 +148,7 @@ fn JsonTokenType! lex_number(JsonContext *context, char c) @local
|
||||
t.append(c);
|
||||
c = read_next(context)!;
|
||||
}
|
||||
if (!c.is_digit()) return JsonParsingError.INVALID_NUMBER?;
|
||||
if (!c.is_digit()) return INVALID_NUMBER?;
|
||||
while (c.is_digit())
|
||||
{
|
||||
t.append(c);
|
||||
@@ -145,26 +156,27 @@ fn JsonTokenType! lex_number(JsonContext *context, char c) @local
|
||||
}
|
||||
}
|
||||
pushback(context, c);
|
||||
double! d = t.str_view().to_double() ?? JsonParsingError.INVALID_NUMBER?;
|
||||
double? d = t.str_view().to_double() ?? INVALID_NUMBER?;
|
||||
context.last_number = d!;
|
||||
return NUMBER;
|
||||
};
|
||||
}
|
||||
|
||||
fn Object*! parse_map(JsonContext* context) @local
|
||||
fn Object*? parse_map(JsonContext* context) @local
|
||||
{
|
||||
Object* map = object::new_obj(context.allocator);
|
||||
defer catch map.free();
|
||||
JsonTokenType token = advance(context)!;
|
||||
defer context.depth--;
|
||||
if (++context.depth >= max_depth) return json::MAX_DEPTH_REACHED?;
|
||||
|
||||
@stack_mem(256; Allocator mem)
|
||||
{
|
||||
DString temp_key = dstring::new_with_capacity(32, mem);
|
||||
DString temp_key = dstring::new_with_capacity(mem, 32);
|
||||
while (token != JsonTokenType.RBRACE)
|
||||
{
|
||||
if (token != JsonTokenType.STRING) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
if (token != JsonTokenType.STRING) return UNEXPECTED_CHARACTER?;
|
||||
DString string = context.last_string;
|
||||
if (map.has_key(string.str_view())) return JsonParsingError.DUPLICATE_MEMBERS?;
|
||||
// Copy the key to our temp holder, since our
|
||||
// last_string may be used in parse_any
|
||||
temp_key.clear();
|
||||
@@ -178,16 +190,18 @@ fn Object*! parse_map(JsonContext* context) @local
|
||||
token = advance(context)!;
|
||||
continue;
|
||||
}
|
||||
if (token != JsonTokenType.RBRACE) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
if (token != JsonTokenType.RBRACE) return UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
return map;
|
||||
};
|
||||
}
|
||||
|
||||
fn Object*! parse_array(JsonContext* context) @local
|
||||
fn Object*? parse_array(JsonContext* context) @local
|
||||
{
|
||||
Object* list = object::new_obj(context.allocator);
|
||||
defer catch list.free();
|
||||
defer context.depth--;
|
||||
if (++context.depth >= max_depth) return json::MAX_DEPTH_REACHED?;
|
||||
JsonTokenType token = advance(context)!;
|
||||
while (token != JsonTokenType.RBRACKET)
|
||||
{
|
||||
@@ -199,7 +213,7 @@ fn Object*! parse_array(JsonContext* context) @local
|
||||
token = advance(context)!;
|
||||
continue;
|
||||
}
|
||||
if (token != JsonTokenType.RBRACKET) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
if (token != JsonTokenType.RBRACKET) return UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@@ -214,7 +228,7 @@ fn void pushback(JsonContext* context, char c) @local
|
||||
}
|
||||
}
|
||||
|
||||
fn char! read_next(JsonContext* context) @local
|
||||
fn char? read_next(JsonContext* context) @local
|
||||
{
|
||||
if (context.reached_end) return '\0';
|
||||
if (context.pushed_back)
|
||||
@@ -222,14 +236,15 @@ fn char! read_next(JsonContext* context) @local
|
||||
context.pushed_back = false;
|
||||
return context.current;
|
||||
}
|
||||
char! c = context.stream.read_byte();
|
||||
char? c = context.stream.read_byte();
|
||||
if (catch err = c)
|
||||
{
|
||||
case IoError.EOF:
|
||||
if (err == io::EOF)
|
||||
{
|
||||
context.reached_end = true;
|
||||
return '\0';
|
||||
default:
|
||||
return err?;
|
||||
}
|
||||
return err?;
|
||||
}
|
||||
if (c == 0)
|
||||
{
|
||||
@@ -238,7 +253,7 @@ fn char! read_next(JsonContext* context) @local
|
||||
return c;
|
||||
}
|
||||
|
||||
fn JsonTokenType! advance(JsonContext* context) @local
|
||||
fn JsonTokenType? advance(JsonContext* context) @local
|
||||
{
|
||||
char c;
|
||||
// Skip whitespace
|
||||
@@ -255,7 +270,7 @@ fn JsonTokenType! advance(JsonContext* context) @local
|
||||
case '\v':
|
||||
continue;
|
||||
case '/':
|
||||
if (!context.skip_comments) break;
|
||||
if (!context.skip_comments) break WS;
|
||||
c = read_next(context)!;
|
||||
if (c != '*')
|
||||
{
|
||||
@@ -286,7 +301,7 @@ fn JsonTokenType! advance(JsonContext* context) @local
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
return IoError.EOF?;
|
||||
return io::EOF?;
|
||||
case '{':
|
||||
return LBRACE;
|
||||
case '}':
|
||||
@@ -314,25 +329,25 @@ fn JsonTokenType! advance(JsonContext* context) @local
|
||||
match(context, "ull")!;
|
||||
return NULL;
|
||||
default:
|
||||
return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
return UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
}
|
||||
|
||||
fn void! match(JsonContext* context, String str) @local
|
||||
fn void? match(JsonContext* context, String str) @local
|
||||
{
|
||||
foreach (c : str)
|
||||
{
|
||||
char l = read_next(context)!;
|
||||
if (l != c) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
if (l != c) return UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
}
|
||||
|
||||
fn void! parse_expected(JsonContext* context, JsonTokenType token) @local
|
||||
fn void? parse_expected(JsonContext* context, JsonTokenType token) @local
|
||||
{
|
||||
if (advance(context)! != token) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
if (advance(context)! != token) return UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
|
||||
fn JsonTokenType! lex_string(JsonContext* context)
|
||||
fn JsonTokenType? lex_string(JsonContext* context)
|
||||
{
|
||||
context.last_string.clear();
|
||||
while LOOP: (true)
|
||||
@@ -341,9 +356,9 @@ fn JsonTokenType! lex_string(JsonContext* context)
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
return JsonParsingError.EOF?;
|
||||
return io::EOF?;
|
||||
case 1..31:
|
||||
return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
return UNEXPECTED_CHARACTER?;
|
||||
case '"':
|
||||
break LOOP;
|
||||
case '\\':
|
||||
@@ -356,9 +371,9 @@ fn JsonTokenType! lex_string(JsonContext* context)
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
return JsonParsingError.EOF?;
|
||||
return io::EOF?;
|
||||
case 1..31:
|
||||
return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
return UNEXPECTED_CHARACTER?;
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
@@ -378,13 +393,13 @@ fn JsonTokenType! lex_string(JsonContext* context)
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
c = read_next(context)!;
|
||||
if (!c.is_xdigit()) return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
|
||||
if (!c.is_xdigit()) return INVALID_ESCAPE_SEQUENCE?;
|
||||
val = val << 4 + (c > '9' ? (c | 32) - 'a' + 10 : c - '0');
|
||||
}
|
||||
context.last_string.append_char32(val);
|
||||
continue;
|
||||
default:
|
||||
return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
|
||||
return INVALID_ESCAPE_SEQUENCE?;
|
||||
}
|
||||
context.last_string.append(c);
|
||||
}
|
||||
|
||||
94
lib/std/experimental/FrameScheduler.c3
Normal file
94
lib/std/experimental/FrameScheduler.c3
Normal file
@@ -0,0 +1,94 @@
|
||||
module std::experimental::scheduler{Event};
|
||||
import std::collections, std::thread, std::time;
|
||||
|
||||
struct DelayedSchedulerEvent @local
|
||||
{
|
||||
inline Event event;
|
||||
Clock execution_time;
|
||||
}
|
||||
|
||||
fn int DelayedSchedulerEvent.compare_to(self, DelayedSchedulerEvent other) @local
|
||||
{
|
||||
switch
|
||||
{
|
||||
case self.execution_time < other.execution_time: return -1;
|
||||
case self.execution_time > other.execution_time: return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct FrameScheduler
|
||||
{
|
||||
PriorityQueue{DelayedSchedulerEvent} delayed_events;
|
||||
List{Event} events;
|
||||
List{Event} pending_events;
|
||||
bool pending;
|
||||
Mutex mtx;
|
||||
}
|
||||
|
||||
fn void FrameScheduler.init(&self)
|
||||
{
|
||||
self.events.init(mem);
|
||||
self.pending_events.init(mem);
|
||||
self.delayed_events.init(mem);
|
||||
(void)self.mtx.init();
|
||||
bool pending;
|
||||
}
|
||||
|
||||
macro void FrameScheduler.@destroy(&self; @destruct(Event e))
|
||||
{
|
||||
foreach (e : self.events) @destruct(e);
|
||||
foreach (e : self.pending_events) @destruct(e);
|
||||
foreach (e : self.delayed_events.heap) @destruct(e.event);
|
||||
self.events.free();
|
||||
self.pending_events.free();
|
||||
self.delayed_events.free();
|
||||
(void)self.mtx.destroy();
|
||||
}
|
||||
|
||||
fn void FrameScheduler.queue_delayed_event(&self, Event event, Duration delay)
|
||||
{
|
||||
self.mtx.@in_lock()
|
||||
{
|
||||
self.delayed_events.push({ event, clock::now().add_duration(delay)});
|
||||
@atomic_store(self.pending, true);
|
||||
};
|
||||
}
|
||||
|
||||
fn bool FrameScheduler.has_delayed(&self)
|
||||
{
|
||||
self.mtx.@in_lock()
|
||||
{
|
||||
return @ok(self.delayed_events.first());
|
||||
};
|
||||
}
|
||||
|
||||
fn void FrameScheduler.queue_event(&self, Event event)
|
||||
{
|
||||
self.mtx.@in_lock()
|
||||
{
|
||||
self.pending_events.push(event);
|
||||
@atomic_store(self.pending, true);
|
||||
};
|
||||
}
|
||||
fn Event? FrameScheduler.pop_event(&self)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (try event = self.events.pop()) return event;
|
||||
if (!@atomic_load(self.pending)) return NO_MORE_ELEMENT?;
|
||||
self.mtx.@in_lock()
|
||||
{
|
||||
self.events.add_all(&self.pending_events);
|
||||
self.pending_events.clear();
|
||||
Clock c = clock::now();
|
||||
while (try top = self.delayed_events.first())
|
||||
{
|
||||
if (top.execution_time > c) break;
|
||||
self.events.push(self.delayed_events.pop()!!);
|
||||
}
|
||||
@atomic_store(self.pending, self.delayed_events.len() > 0);
|
||||
if (!self.events.len()) return NO_MORE_ELEMENT?;
|
||||
};
|
||||
}
|
||||
}
|
||||
96
lib/std/hash/a5hash.c3
Normal file
96
lib/std/hash/a5hash.c3
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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.
|
||||
//
|
||||
// An implementation of Aleksey Vaneev's a5hash, version 5.16, in C3:
|
||||
// https://github.com/avaneev/komihash
|
||||
//
|
||||
// The license for komihash from the above repository at the time of writing is as follows:
|
||||
//
|
||||
// >> MIT License
|
||||
// >>
|
||||
// >> Copyright (c) 2025 Aleksey Vaneev
|
||||
// >>
|
||||
// >> 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.
|
||||
//
|
||||
//
|
||||
module std::hash::a5hash;
|
||||
|
||||
|
||||
macro @a5mul(#u, #v, #lo, #hi) @local
|
||||
{
|
||||
uint128 imd = (uint128)#u * (uint128)#v;
|
||||
#lo = (ulong)imd;
|
||||
#hi = (ulong)(imd >> 64);
|
||||
}
|
||||
|
||||
|
||||
fn ulong hash(char[] data, ulong seed = 0)
|
||||
{
|
||||
ulong seed1 = 0x243F_6A88_85A3_08D3 ^ data.len;
|
||||
ulong seed2 = 0x4528_21E6_38D0_1377 ^ data.len;
|
||||
ulong val10 = 0xAAAA_AAAA_AAAA_AAAA;
|
||||
ulong val01 = 0x5555_5555_5555_5555;
|
||||
ulong a, b;
|
||||
|
||||
@a5mul(seed2 ^ (seed & val10), seed1 ^ (seed & val01), seed1, seed2);
|
||||
|
||||
val10 ^= seed2;
|
||||
|
||||
if (@likely(data.len > 3))
|
||||
{
|
||||
if (data.len > 16)
|
||||
{
|
||||
val01 ^= seed1;
|
||||
|
||||
for (; data.len > 16; data = data[16..])
|
||||
{
|
||||
@a5mul(
|
||||
@unaligned_load(((ulong*)data.ptr)[0], 1) ^ seed1,
|
||||
@unaligned_load(((ulong*)data.ptr)[1], 1) ^ seed2,
|
||||
seed1, seed2
|
||||
);
|
||||
|
||||
seed1 += val01;
|
||||
seed2 += val10;
|
||||
}
|
||||
|
||||
a = @unaligned_load(*(ulong*)(data.ptr + (uptr)data.len - 16), 1);
|
||||
b = @unaligned_load(*(ulong*)(data.ptr + (uptr)data.len - 8), 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
a = ((ulong)@unaligned_load(*(uint*)&data[0], 1) << 32)
|
||||
| @unaligned_load(*(uint*)&data[^4], 1);
|
||||
|
||||
b = ((ulong)@unaligned_load(*(uint*)&data[(data.len >> 3) * 4], 1) << 32)
|
||||
| @unaligned_load(*(uint*)(data.ptr + data.len - 4 - (data.len >> 3) * 4), 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
a = data.len ? (data[0] | (data.len > 1 ? ((ulong)data[1] << 8) : 0) | (data.len > 2 ? ((ulong)data[2] << 16) : 0)) : 0;
|
||||
b = 0;
|
||||
}
|
||||
|
||||
@a5mul(a ^ seed1, b ^ seed2, seed1, seed2);
|
||||
@a5mul(val01 ^ seed1, seed2, a, b);
|
||||
|
||||
return a ^ b;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ fn uint Adler32.final(&self)
|
||||
return (self.b << 16) | self.a;
|
||||
}
|
||||
|
||||
fn uint encode(char[] data)
|
||||
fn uint hash(char[] data)
|
||||
{
|
||||
uint a = 1;
|
||||
uint b = 0;
|
||||
|
||||
@@ -33,7 +33,7 @@ fn uint Crc32.final(&self)
|
||||
return ~self.result;
|
||||
}
|
||||
|
||||
fn uint encode(char[] data)
|
||||
fn uint hash(char[] data)
|
||||
{
|
||||
uint result = ~(uint)(0);
|
||||
foreach (char x : data)
|
||||
|
||||
@@ -33,7 +33,7 @@ fn ulong Crc64.final(&self)
|
||||
return self.result;
|
||||
}
|
||||
|
||||
fn ulong encode(char[] data)
|
||||
fn ulong hash(char[] data)
|
||||
{
|
||||
ulong result = (ulong)(0);
|
||||
foreach (char x : data)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::hash::fnv32a;
|
||||
|
||||
distinct Fnv32a = uint;
|
||||
typedef Fnv32a = uint;
|
||||
|
||||
const FNV32A_START @private = 0x811c9dc5;
|
||||
const FNV32A_MUL @private = 0x01000193;
|
||||
@@ -30,7 +30,7 @@ macro void Fnv32a.update_char(&self, char c)
|
||||
update(self, c);
|
||||
}
|
||||
|
||||
fn uint encode(char[] data)
|
||||
fn uint hash(char[] data)
|
||||
{
|
||||
uint h = FNV32A_START;
|
||||
foreach (char x : data)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::hash::fnv64a;
|
||||
|
||||
distinct Fnv64a = ulong;
|
||||
typedef Fnv64a = ulong;
|
||||
|
||||
const FNV64A_START @private = 0xcbf29ce484222325;
|
||||
const FNV64A_MUL @private = 0x00000100000001b3;
|
||||
@@ -30,7 +30,7 @@ macro void Fnv64a.update_char(&self, char c)
|
||||
update(self, c);
|
||||
}
|
||||
|
||||
fn ulong encode(char[] data)
|
||||
fn ulong hash(char[] data)
|
||||
{
|
||||
ulong h = FNV64A_START;
|
||||
foreach (char x : data)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module std::hash::hmac(<HashAlg, HASH_BYTES, BLOCK_BYTES>);
|
||||
module std::hash::hmac{HashAlg, HASH_BYTES, BLOCK_BYTES};
|
||||
import std::crypto;
|
||||
|
||||
struct Hmac
|
||||
@@ -15,8 +15,8 @@ fn char[HASH_BYTES] hash(char[] key, char[] message)
|
||||
}
|
||||
|
||||
<*
|
||||
@require output.len > 0 "Output must be greater than zero"
|
||||
@require output.len < int.max / HASH_BYTES "Output is too large"
|
||||
@require output.len > 0 : "Output must be greater than zero"
|
||||
@require output.len < int.max / HASH_BYTES : "Output is too large"
|
||||
*>
|
||||
fn void pbkdf2(char[] pw, char[] salt, uint iterations, char[] output)
|
||||
{
|
||||
@@ -93,7 +93,7 @@ macro @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[]
|
||||
UIntBE be = { (uint)index };
|
||||
hmac.update(&&bitcast(be, char[4]));
|
||||
tmp = hmac.final();
|
||||
out[..] = tmp;
|
||||
out[..] = tmp[..];
|
||||
for (int it = 1; it < iterations; it++)
|
||||
{
|
||||
hmac = *hmac_start;
|
||||
|
||||
156
lib/std/hash/komi.c3
Normal file
156
lib/std/hash/komi.c3
Normal file
@@ -0,0 +1,156 @@
|
||||
// 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.
|
||||
//
|
||||
// An implementation of Aleksey Vaneev's komihash, version 5.27, in C3:
|
||||
// https://github.com/avaneev/komihash
|
||||
//
|
||||
// The license for komihash from the above repository at the time of writing is as follows:
|
||||
//
|
||||
// >> MIT License
|
||||
// >>
|
||||
// >> Copyright (c) 2021-2025 Aleksey Vaneev
|
||||
// >>
|
||||
// >> 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.
|
||||
//
|
||||
//
|
||||
module std::hash::komi;
|
||||
|
||||
|
||||
macro @komimul(#u, #v, #lo, #hi) @local
|
||||
{
|
||||
uint128 imd = (uint128)#u * (uint128)#v;
|
||||
#lo = (ulong)imd;
|
||||
#hi += (ulong)(imd >> 64);
|
||||
}
|
||||
|
||||
|
||||
fn ulong hash(char[] data, ulong seed = 0)
|
||||
{
|
||||
ulong seed1 = 0x243F_6A88_85A3_08D3 ^ (seed & 0x5555_5555_5555_5555);
|
||||
ulong seed5 = 0x4528_21E6_38D0_1377 ^ (seed & 0xAAAA_AAAA_AAAA_AAAA);
|
||||
ulong r1h, r2h;
|
||||
|
||||
// HASHROUND
|
||||
@komimul(seed1, seed5, seed1, seed5);
|
||||
seed1 ^= seed5;
|
||||
|
||||
if (@likely(data.len < 16))
|
||||
{
|
||||
r1h = seed1;
|
||||
r2h = seed5;
|
||||
|
||||
if (@likely(data.len >= 8))
|
||||
{
|
||||
r1h ^= @unaligned_load(*(ulong*)data.ptr, 1);
|
||||
|
||||
r2h ^= (data.len < 12)
|
||||
? ((data[data.len - 3] | ((ulong)data[data.len - 2] << 8) | ((ulong)data[data.len - 1] << 16) | ((ulong)1 << 24)) >> ((data.len * 8) ^ 88))
|
||||
: (((@unaligned_load(*(uint*)&data[^4], 1) | ((ulong)1 << 32)) >> (128 - data.len * 8)) << 32 | @unaligned_load(*(uint*)&data[8], 1));
|
||||
}
|
||||
else if (data.len != 0)
|
||||
{
|
||||
r1h ^= (data.len < 4)
|
||||
? (((ulong)1 << (data.len * 8)) ^ data[0] ^ (data.len > 1 ? (ulong)data[1] << 8 : 0) ^ (data.len > 2 ? (ulong)data[2] << 16 : 0))
|
||||
: (((@unaligned_load(*(uint*)&data[^4], 1) | ((ulong)1 << 32)) >> (64 - data.len * 8)) << 32 | @unaligned_load(*(uint*)&data[0], 1));
|
||||
}
|
||||
}
|
||||
else if (data.len < 32)
|
||||
{
|
||||
// HASH16
|
||||
@komimul(
|
||||
@unaligned_load(*(ulong*)&data[0], 1) ^ seed1,
|
||||
@unaligned_load(*(ulong*)&data[8], 1) ^ seed5,
|
||||
seed1, seed5
|
||||
);
|
||||
seed1 ^= seed5;
|
||||
|
||||
if (data.len < 24)
|
||||
{
|
||||
r1h = (((@unaligned_load(*(ulong*)&data[^8], 1) >> 8) | ((ulong)1 << 56)) >> (((int)(data.len * 8) ^ 184))) ^ seed1;
|
||||
r2h = seed5;
|
||||
}
|
||||
else
|
||||
{
|
||||
r1h = @unaligned_load(*(ulong*)&data[16], 1) ^ seed1;
|
||||
r2h = (((@unaligned_load(*(ulong*)&data[^8], 1) >> 8) | ((ulong)1 << 56)) >> (((int)(data.len * 8) ^ 248))) ^ seed5;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (data.len >= 64)
|
||||
{
|
||||
ulong[8] seeds = {
|
||||
seed1, 0x1319_8A2E_0370_7344 ^ seed1, 0xA409_3822_299F_31D0 ^ seed1, 0x082E_FA98_EC4E_6C89 ^ seed1,
|
||||
seed5, 0xBE54_66CF_34E9_0C6C ^ seed5, 0xC0AC_29B7_C97C_50DD ^ seed5, 0x3F84_D5B5_B547_0917 ^ seed5,
|
||||
};
|
||||
|
||||
// HASHLOOP64
|
||||
for (; data.len >= 64; data = data[64:^64])
|
||||
{
|
||||
$for var $x = 0; $x < 4; ++$x :
|
||||
@komimul(
|
||||
@unaligned_load(*(ulong*)&data[0 + ($x * 8)], 1) ^ seeds[$x],
|
||||
@unaligned_load(*(ulong*)&data[32 + ($x * 8)], 1) ^ seeds[4 + $x],
|
||||
seeds[$x], seeds[4 + $x]
|
||||
);
|
||||
$endfor
|
||||
|
||||
seeds[3] ^= seeds[6];
|
||||
seeds[0] ^= seeds[7];
|
||||
seeds[2] ^= seeds[5];
|
||||
seeds[1] ^= seeds[4];
|
||||
}
|
||||
|
||||
seed1 = seeds[0] ^ seeds[1] ^ seeds[2] ^ seeds[3];
|
||||
seed5 = seeds[4] ^ seeds[5] ^ seeds[6] ^ seeds[7];
|
||||
}
|
||||
|
||||
for (; data.len >= 16; data = data[16:^16])
|
||||
{
|
||||
@komimul(
|
||||
@unaligned_load(*(ulong*)&data[0], 1) ^ seed1,
|
||||
@unaligned_load(*(ulong*)&data[8], 1) ^ seed5,
|
||||
seed1, seed5
|
||||
);
|
||||
seed1 ^= seed5;
|
||||
}
|
||||
|
||||
if (data.len < 8)
|
||||
{
|
||||
// NOTE: This is translated from the original code. It grabs the last ulong off the buffer even though the
|
||||
// data slice is less than 8 bytes. This is possible because this branch only occurs in a loop where
|
||||
// the original data slice length is >= 32.
|
||||
r1h = (((@unaligned_load(*(ulong*)(data.ptr + data.len - 8), 1) >> 8) | ((ulong)1 << 56)) >> ((data.len * 8) ^ 0x38)) ^ seed1;
|
||||
r2h = seed5;
|
||||
}
|
||||
else
|
||||
{
|
||||
r1h = @unaligned_load(*(ulong*)data.ptr, 1) ^ seed1;
|
||||
r2h = (((@unaligned_load(*(ulong*)&data[^8], 1) >> 8) | ((ulong)1 << 56)) >> ((data.len * 8) ^ 0x78)) ^ seed5;
|
||||
}
|
||||
}
|
||||
|
||||
// HASHFIN
|
||||
@komimul(r1h, r2h, seed1, seed5);
|
||||
seed1 ^= seed5;
|
||||
@komimul(seed1, seed5, seed1, seed5);
|
||||
seed1 ^= seed5;
|
||||
return seed1;
|
||||
}
|
||||
@@ -13,9 +13,9 @@ struct Md5
|
||||
uint[16] block;
|
||||
}
|
||||
|
||||
def HmacMd5 = Hmac(<Md5, HASH_BYTES, BLOCK_BYTES>);
|
||||
def hmac = hmac::hash(<Md5, HASH_BYTES, BLOCK_BYTES>);
|
||||
def pbkdf2 = hmac::pbkdf2(<Md5, HASH_BYTES, BLOCK_BYTES>);
|
||||
alias HmacMd5 = Hmac{Md5, HASH_BYTES, BLOCK_BYTES};
|
||||
alias hmac = hmac::hash{Md5, HASH_BYTES, BLOCK_BYTES};
|
||||
alias pbkdf2 = hmac::pbkdf2{Md5, HASH_BYTES, BLOCK_BYTES};
|
||||
|
||||
fn char[HASH_BYTES] hash(char[] data)
|
||||
{
|
||||
@@ -89,10 +89,10 @@ fn char[HASH_BYTES] Md5.final(&ctx)
|
||||
body(ctx, &ctx.buffer, 64);
|
||||
|
||||
char[16] res @noinit;
|
||||
res[0:4] = bitcast(ctx.a, char[4]);
|
||||
res[4:4] = bitcast(ctx.b, char[4]);
|
||||
res[8:4] = bitcast(ctx.c, char[4]);
|
||||
res[12:4] = bitcast(ctx.d, char[4]);
|
||||
res[0:4] = bitcast(ctx.a, char[4])[..];
|
||||
res[4:4] = bitcast(ctx.b, char[4])[..];
|
||||
res[8:4] = bitcast(ctx.c, char[4])[..];
|
||||
res[12:4] = bitcast(ctx.d, char[4])[..];
|
||||
*ctx = {};
|
||||
return res;
|
||||
}
|
||||
@@ -108,7 +108,7 @@ macro @i(x, y, z) => y ^ (x | ~z);
|
||||
|
||||
macro @step(#f, a, b, c, d, ptr, n, t, s)
|
||||
{
|
||||
*a += #f(b, c, d) + *(uint *)&ptr[n * 4] + t;
|
||||
*a += #f(b, c, d) + @unaligned_load(*(uint *)&ptr[n * 4], 2) + t;
|
||||
*a = (*a << s) | ((*a & 0xffffffff) >> (32 - s));
|
||||
*a += b;
|
||||
}
|
||||
|
||||
149
lib/std/hash/metro128.c3
Normal file
149
lib/std/hash/metro128.c3
Normal file
@@ -0,0 +1,149 @@
|
||||
// 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.
|
||||
//
|
||||
// MetroHash64 and MetroHash128 are different enough to warrant their own
|
||||
// modules, and there would be no reason to create a generic module just
|
||||
// for the two. If you inspect the differences, the only shared portion
|
||||
// of the entire process is the `update` method.
|
||||
//
|
||||
module std::hash::metro128;
|
||||
|
||||
|
||||
const ulong[4] K @local = {
|
||||
0xc83a91e1,
|
||||
0x8648dbdb,
|
||||
0x7bdec03b,
|
||||
0x2f5870a5,
|
||||
};
|
||||
|
||||
|
||||
struct MetroHash128
|
||||
{
|
||||
union
|
||||
{
|
||||
ulong[4] state;
|
||||
uint128 result;
|
||||
}
|
||||
union
|
||||
{
|
||||
ulong[4] stomach_64;
|
||||
char[32] stomach;
|
||||
}
|
||||
ulong bytes;
|
||||
}
|
||||
|
||||
|
||||
fn uint128 hash(char[] data, ulong seed = 0)
|
||||
{
|
||||
MetroHash128 m;
|
||||
m.init(seed);
|
||||
m.update(data);
|
||||
return m.final();
|
||||
}
|
||||
|
||||
|
||||
fn void MetroHash128.init(&self, ulong seed = 0)
|
||||
{
|
||||
self.state = {
|
||||
(seed - K[0]) * K[3],
|
||||
(seed + K[1]) * K[2],
|
||||
(seed + K[0]) * K[2],
|
||||
(seed - K[1]) * K[3],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
fn void MetroHash128.update(&self, char[] data)
|
||||
{
|
||||
if (self.bytes % 32) // partial buffer
|
||||
{
|
||||
ulong to_fill = min(data.len, (32 - (self.bytes % 32)));
|
||||
|
||||
self.stomach[(self.bytes % 32):to_fill] = data[:to_fill];
|
||||
|
||||
data = data[to_fill..];
|
||||
self.bytes += to_fill;
|
||||
|
||||
if (self.bytes % 32) return; // still awaiting more input, or final
|
||||
|
||||
self.state[0] += self.stomach_64[0] * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
|
||||
self.state[1] += self.stomach_64[1] * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
|
||||
self.state[2] += self.stomach_64[2] * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
|
||||
self.state[3] += self.stomach_64[3] * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
|
||||
}
|
||||
|
||||
self.bytes += data.len;
|
||||
|
||||
for (; data.len >= 32; data = data[32:^32])
|
||||
{
|
||||
self.state[0] += @unaligned_load(((ulong*)data.ptr)[0], 1) * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
|
||||
self.state[1] += @unaligned_load(((ulong*)data.ptr)[1], 1) * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
|
||||
self.state[2] += @unaligned_load(((ulong*)data.ptr)[2], 1) * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
|
||||
self.state[3] += @unaligned_load(((ulong*)data.ptr)[3], 1) * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
|
||||
}
|
||||
|
||||
// Gobble up the leftover bytes. Nom nom.
|
||||
if (data.len > 0) self.stomach[:data.len] = data[..];
|
||||
}
|
||||
|
||||
|
||||
fn uint128 MetroHash128.final(&self)
|
||||
{
|
||||
if (self.bytes >= 32)
|
||||
{
|
||||
self.state[2] ^= (((self.state[0] + self.state[3]) * K[0]) + self.state[1]).rotr(21) * K[1];
|
||||
self.state[3] ^= (((self.state[1] + self.state[2]) * K[1]) + self.state[0]).rotr(21) * K[0];
|
||||
self.state[0] ^= (((self.state[0] + self.state[2]) * K[0]) + self.state[3]).rotr(21) * K[1];
|
||||
self.state[1] ^= (((self.state[1] + self.state[3]) * K[1]) + self.state[2]).rotr(21) * K[0];
|
||||
}
|
||||
|
||||
char[] final_data = self.stomach[:(self.bytes % 32)];
|
||||
|
||||
if (final_data.len >= 16)
|
||||
{
|
||||
self.state[0] += ((ulong*)final_data.ptr)[0] * K[2]; self.state[0] = self.state[0].rotr(33) * K[3];
|
||||
self.state[1] += ((ulong*)final_data.ptr)[1] * K[2]; self.state[1] = self.state[1].rotr(33) * K[3];
|
||||
self.state[0] ^= ((self.state[0] * K[2]) + self.state[1]).rotr(45) * K[1];
|
||||
self.state[1] ^= ((self.state[1] * K[3]) + self.state[0]).rotr(45) * K[0];
|
||||
|
||||
final_data = final_data[16:^16];
|
||||
}
|
||||
|
||||
if (final_data.len >= 8)
|
||||
{
|
||||
self.state[0] += @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[2]; self.state[0] = self.state[0].rotr(33) * K[3];
|
||||
self.state[0] ^= ((self.state[0] * K[2]) + self.state[1]).rotr(27) * K[1];
|
||||
|
||||
final_data = final_data[8:^8];
|
||||
}
|
||||
|
||||
if (final_data.len >= 4)
|
||||
{
|
||||
self.state[1] += @unaligned_load(((uint*)final_data.ptr)[0], 1) * K[2]; self.state[1] = self.state[1].rotr(33) * K[3];
|
||||
self.state[1] ^= ((self.state[1] * K[3]) + self.state[0]).rotr(46) * K[0];
|
||||
|
||||
final_data = final_data[4:^4];
|
||||
}
|
||||
|
||||
if (final_data.len >= 2)
|
||||
{
|
||||
self.state[0] += @unaligned_load(((ushort*)final_data.ptr)[0], 1) * K[2]; self.state[0] = self.state[0].rotr(33) * K[3];
|
||||
self.state[0] ^= ((self.state[0] * K[2]) + self.state[1]).rotr(22) * K[1];
|
||||
|
||||
final_data = final_data[2:^2];
|
||||
}
|
||||
|
||||
if (final_data.len >= 1)
|
||||
{
|
||||
self.state[1] += ((char*)final_data.ptr)[0] * K[2]; self.state[1] = self.state[1].rotr(33) * K[3];
|
||||
self.state[1] ^= ((self.state[1] * K[3]) + self.state[0]).rotr(58) * K[0];
|
||||
}
|
||||
|
||||
self.state[0] += ((self.state[0] * K[0]) + self.state[1]).rotr(13);
|
||||
self.state[1] += ((self.state[1] * K[1]) + self.state[0]).rotr(37);
|
||||
self.state[0] += ((self.state[0] * K[2]) + self.state[1]).rotr(13);
|
||||
self.state[1] += ((self.state[1] * K[3]) + self.state[0]).rotr(37);
|
||||
|
||||
return self.result;
|
||||
}
|
||||
152
lib/std/hash/metro64.c3
Normal file
152
lib/std/hash/metro64.c3
Normal file
@@ -0,0 +1,152 @@
|
||||
// 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.
|
||||
//
|
||||
// MetroHash64 and MetroHash128 are different enough to warrant their own
|
||||
// modules, and there would be no reason to create a generic module just
|
||||
// for the two. If you inspect the differences, the only shared portion
|
||||
// of the entire process is the `update` method.
|
||||
//
|
||||
module std::hash::metro64;
|
||||
|
||||
|
||||
const ulong[4] K @local = {
|
||||
0xd6d018f5,
|
||||
0xa2aa033b,
|
||||
0x62992fc1,
|
||||
0x30bc5b29,
|
||||
};
|
||||
|
||||
|
||||
struct MetroHash64
|
||||
{
|
||||
union
|
||||
{
|
||||
ulong[4] state;
|
||||
ulong result;
|
||||
}
|
||||
union
|
||||
{
|
||||
ulong[4] stomach_64;
|
||||
char[32] stomach;
|
||||
}
|
||||
ulong bytes;
|
||||
ulong vseed;
|
||||
}
|
||||
|
||||
|
||||
fn ulong hash(char[] data, ulong seed = 0)
|
||||
{
|
||||
MetroHash64 m;
|
||||
m.init(seed);
|
||||
m.update(data);
|
||||
return m.final();
|
||||
}
|
||||
|
||||
|
||||
fn void MetroHash64.init(&self, ulong seed = 0)
|
||||
{
|
||||
self.vseed = (seed + K[2]) * K[0];
|
||||
|
||||
self.state[0] = self.vseed;
|
||||
self.state[1] = self.vseed;
|
||||
self.state[2] = self.vseed;
|
||||
self.state[3] = self.vseed;
|
||||
}
|
||||
|
||||
|
||||
fn void MetroHash64.update(&self, char[] data)
|
||||
{
|
||||
if (self.bytes % 32) // partial buffer
|
||||
{
|
||||
ulong to_fill = min(data.len, (32 - (self.bytes % 32)));
|
||||
|
||||
self.stomach[(self.bytes % 32):to_fill] = data[:to_fill];
|
||||
|
||||
data = data[to_fill..];
|
||||
self.bytes += to_fill;
|
||||
|
||||
if (self.bytes % 32) return; // still awaiting more input, or final
|
||||
|
||||
self.state[0] += self.stomach_64[0] * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
|
||||
self.state[1] += self.stomach_64[1] * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
|
||||
self.state[2] += self.stomach_64[2] * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
|
||||
self.state[3] += self.stomach_64[3] * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
|
||||
}
|
||||
|
||||
self.bytes += data.len;
|
||||
|
||||
for (; data.len >= 32; data = data[32:^32])
|
||||
{
|
||||
self.state[0] += @unaligned_load(((ulong*)data.ptr)[0], 1) * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
|
||||
self.state[1] += @unaligned_load(((ulong*)data.ptr)[1], 1) * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
|
||||
self.state[2] += @unaligned_load(((ulong*)data.ptr)[2], 1) * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
|
||||
self.state[3] += @unaligned_load(((ulong*)data.ptr)[3], 1) * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
|
||||
}
|
||||
|
||||
// Gobble up the leftover bytes. Nom nom.
|
||||
if (data.len > 0) self.stomach[:data.len] = data[..];
|
||||
}
|
||||
|
||||
|
||||
fn ulong MetroHash64.final(&self)
|
||||
{
|
||||
if (self.bytes >= 32)
|
||||
{
|
||||
self.state[2] ^= (((self.state[0] + self.state[3]) * K[0]) + self.state[1]).rotr(37) * K[1];
|
||||
self.state[3] ^= (((self.state[1] + self.state[2]) * K[1]) + self.state[0]).rotr(37) * K[0];
|
||||
self.state[0] ^= (((self.state[0] + self.state[2]) * K[0]) + self.state[3]).rotr(37) * K[1];
|
||||
self.state[1] ^= (((self.state[1] + self.state[3]) * K[1]) + self.state[2]).rotr(37) * K[0];
|
||||
|
||||
self.state[0] = self.vseed + (self.state[0] ^ self.state[1]);
|
||||
}
|
||||
|
||||
char[] final_data = self.stomach[:(self.bytes % 32)];
|
||||
|
||||
if (final_data.len >= 16)
|
||||
{
|
||||
self.state[1] = self.state[0] + @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[2]; self.state[1] = self.state[1].rotr(29) * K[3];
|
||||
self.state[2] = self.state[0] + @unaligned_load(((ulong*)final_data.ptr)[1], 1) * K[2]; self.state[2] = self.state[2].rotr(29) * K[3];
|
||||
self.state[1] ^= (self.state[1] * K[0]).rotr(21) + self.state[2];
|
||||
self.state[2] ^= (self.state[2] * K[3]).rotr(21) + self.state[1];
|
||||
self.state[0] += self.state[2];
|
||||
|
||||
final_data = final_data[16:^16];
|
||||
}
|
||||
|
||||
if (final_data.len >= 8)
|
||||
{
|
||||
self.state[0] += @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[3];
|
||||
self.state[0] ^= self.state[0].rotr(55) * K[1];
|
||||
|
||||
final_data = final_data[8:^8];
|
||||
}
|
||||
|
||||
if (final_data.len >= 4)
|
||||
{
|
||||
self.state[0] += @unaligned_load(((uint*)final_data.ptr)[0], 1) * K[3];
|
||||
self.state[0] ^= self.state[0].rotr(26) * K[1];
|
||||
|
||||
final_data = final_data[4:^4];
|
||||
}
|
||||
|
||||
if (final_data.len >= 2)
|
||||
{
|
||||
self.state[0] += @unaligned_load(((ushort*)final_data.ptr)[0], 1) * K[3];
|
||||
self.state[0] ^= self.state[0].rotr(48) * K[1];
|
||||
|
||||
final_data = final_data[2:^2];
|
||||
}
|
||||
|
||||
if (final_data.len >= 1)
|
||||
{
|
||||
self.state[0] += ((char*)final_data.ptr)[0] * K[3];
|
||||
self.state[0] ^= self.state[0].rotr(37) * K[1];
|
||||
}
|
||||
|
||||
self.state[0] ^= self.state[0].rotr(28);
|
||||
self.state[0] *= K[0];
|
||||
self.state[0] ^= self.state[0].rotr(29);
|
||||
|
||||
return self.result;
|
||||
}
|
||||
@@ -18,9 +18,9 @@ struct Sha1
|
||||
char[BLOCK_BYTES] buffer;
|
||||
}
|
||||
|
||||
def HmacSha1 = Hmac(<Sha1, HASH_BYTES, BLOCK_BYTES>);
|
||||
def hmac = hmac::hash(<Sha1, HASH_BYTES, BLOCK_BYTES>);
|
||||
def pbkdf2 = hmac::pbkdf2(<Sha1, HASH_BYTES, BLOCK_BYTES>);
|
||||
alias HmacSha1 = Hmac{Sha1, HASH_BYTES, BLOCK_BYTES};
|
||||
alias hmac = hmac::hash{Sha1, HASH_BYTES, BLOCK_BYTES};
|
||||
alias pbkdf2 = hmac::pbkdf2{Sha1, HASH_BYTES, BLOCK_BYTES};
|
||||
|
||||
fn char[HASH_BYTES] hash(char[] data)
|
||||
{
|
||||
@@ -78,10 +78,10 @@ fn char[HASH_BYTES] Sha1.final(&self)
|
||||
{
|
||||
finalcount[i] = (char)((self.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
|
||||
}
|
||||
self.update(char[] { 0o200 });
|
||||
self.update((char[]){ 0o200 });
|
||||
while ((self.count[0] & 504) != 448)
|
||||
{
|
||||
self.update(char[] { 0 });
|
||||
self.update((char[]){ 0 });
|
||||
}
|
||||
|
||||
self.update(&finalcount);
|
||||
@@ -103,13 +103,13 @@ union Long16 @local
|
||||
uint[16] l;
|
||||
}
|
||||
|
||||
macro blk(block, i) @local
|
||||
macro blk(Long16* block, i) @local
|
||||
{
|
||||
return (block.l[i & 15] = (block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15]
|
||||
^ block.l[(i + 2) & 15] ^ block.l[i & 15]).rotl(1));
|
||||
}
|
||||
|
||||
macro blk0(block, i) @local
|
||||
macro blk0(Long16* block, i) @local
|
||||
{
|
||||
$if env::BIG_ENDIAN:
|
||||
return block.l[i];
|
||||
@@ -119,35 +119,35 @@ macro blk0(block, i) @local
|
||||
$endif
|
||||
}
|
||||
|
||||
macro r0(block, v, wref, x, y, z, i) @local
|
||||
macro r0(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
{
|
||||
var w = *wref;
|
||||
*z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro r1(block, v, wref, x, y, z, i) @local
|
||||
macro r1(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
{
|
||||
var w = *wref;
|
||||
*z += ((w & (x ^ y)) ^ y) + blk(block, i) + 0x5A827999 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro r2(block, v, wref, x, y, z, i) @local
|
||||
macro r2(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
{
|
||||
var w = *wref;
|
||||
*z += (w ^ x ^ y) + blk(block, i) + 0x6ED9EBA1 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro r3(block, v, wref, x, y, z, i) @local
|
||||
macro r3(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
{
|
||||
var w = *wref;
|
||||
*z += (((w | x) & y) | (w & x)) + blk(block, i) + 0x8F1BBCDC + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro r4(block, v, wref, x, y, z, i) @local
|
||||
macro r4(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
{
|
||||
var w = *wref;
|
||||
*z += (w ^ x ^ y) + blk(block, i) + 0xCA62C1D6 + v.rotl(5);
|
||||
@@ -158,15 +158,15 @@ macro r4(block, v, wref, x, y, z, i) @local
|
||||
@param [&inout] state
|
||||
@param [&in] buffer
|
||||
*>
|
||||
fn void sha1_transform(uint* state, char* buffer) @local
|
||||
fn void sha1_transform(uint[5]* state, char* buffer) @local
|
||||
{
|
||||
Long16 block;
|
||||
block.c[..] = buffer[:64];
|
||||
uint a = state[0];
|
||||
uint b = state[1];
|
||||
uint c = state[2];
|
||||
uint d = state[3];
|
||||
uint e = state[4];
|
||||
uint a = (*state)[0];
|
||||
uint b = (*state)[1];
|
||||
uint c = (*state)[2];
|
||||
uint d = (*state)[3];
|
||||
uint e = (*state)[4];
|
||||
r0(&block, a, &b, c, d, &e, 0);
|
||||
r0(&block, e, &a, b, c, &d, 1);
|
||||
r0(&block, d, &e, a, b, &c, 2);
|
||||
@@ -247,11 +247,11 @@ fn void sha1_transform(uint* state, char* buffer) @local
|
||||
r4(&block, d, &e, a, b, &c, 77);
|
||||
r4(&block, c, &d, e, a, &b, 78);
|
||||
r4(&block, b, &c, d, e, &a, 79);
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
state[4] += e;
|
||||
(*state)[0] += a;
|
||||
(*state)[1] += b;
|
||||
(*state)[2] += c;
|
||||
(*state)[3] += d;
|
||||
(*state)[4] += e;
|
||||
a = b = c = d = e = 0;
|
||||
buffer[:64] = 0;
|
||||
block = {};
|
||||
}
|
||||
@@ -34,9 +34,9 @@ struct Sha256
|
||||
char[BLOCK_SIZE] buffer;
|
||||
}
|
||||
|
||||
def HmacSha256 = Hmac(<Sha256, HASH_SIZE, BLOCK_SIZE>);
|
||||
def hmac = hmac::hash(<Sha256, HASH_SIZE, BLOCK_SIZE>);
|
||||
def pbkdf2 = hmac::pbkdf2(<Sha256, HASH_SIZE, BLOCK_SIZE>);
|
||||
alias HmacSha256 = Hmac{Sha256, HASH_SIZE, BLOCK_SIZE};
|
||||
alias hmac = hmac::hash{Sha256, HASH_SIZE, BLOCK_SIZE};
|
||||
alias pbkdf2 = hmac::pbkdf2{Sha256, HASH_SIZE, BLOCK_SIZE};
|
||||
|
||||
fn char[HASH_SIZE] hash(char[] data)
|
||||
{
|
||||
@@ -71,7 +71,7 @@ fn void Sha256.update(&self, char[] data) {
|
||||
uint i = 0;
|
||||
uint len = data.len;
|
||||
uint buffer_pos = (uint)(self.bitcount / 8) % BLOCK_SIZE;
|
||||
self.bitcount += (ulong)(len * 8);
|
||||
self.bitcount += ((ulong)len * 8);
|
||||
|
||||
while (len--) {
|
||||
self.buffer[buffer_pos++] = data[i++];
|
||||
@@ -173,4 +173,5 @@ fn void sha256_transform(uint* state, char* buffer) @local {
|
||||
state[7] += h;
|
||||
a = b = c = d = e = f = g = h = t1 = t2 = i = 0;
|
||||
m[:64] = buffer[:64] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
281
lib/std/hash/sha512.c3
Normal file
281
lib/std/hash/sha512.c3
Normal file
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* SHA-512 implementation for the C3 stdlib.
|
||||
*
|
||||
* The core components are based almost exclusively on the musl libc sha512 implementation:
|
||||
* https://git.musl-libc.org/cgit/musl/commit/src/misc/crypt_sha512.c?id=88bf5a8a8d7d796f63cca8589f4de67aa8345f1a
|
||||
*
|
||||
* SHA-384, 512/224, and 512/256 are very simple addenda to this module.
|
||||
*
|
||||
*/
|
||||
module std::hash::sha512;
|
||||
|
||||
import std::hash::hmac;
|
||||
|
||||
|
||||
const BLOCK_SIZE = 128;
|
||||
const HASH_SIZE = 64;
|
||||
|
||||
struct Sha512
|
||||
{
|
||||
ulong length;
|
||||
ulong[8] hash_state;
|
||||
char[BLOCK_SIZE] buffer;
|
||||
}
|
||||
|
||||
alias HmacSha512 = Hmac{Sha512, HASH_SIZE, BLOCK_SIZE};
|
||||
alias hmac = hmac::hash{Sha512, HASH_SIZE, BLOCK_SIZE};
|
||||
alias pbkdf2 = hmac::pbkdf2{Sha512, HASH_SIZE, BLOCK_SIZE};
|
||||
|
||||
macro ulong ror(ulong n, int k) @local => ((n >> k) | (n << (64 - k)));
|
||||
|
||||
macro ulong ch(ulong x, ulong y, ulong z) @local => (z ^ (x & (y ^ z)));
|
||||
macro ulong maj(ulong x, ulong y, ulong z) @local => ((x & y) | (z & (x | y)));
|
||||
macro ulong s0(ulong x) @local => (ror(x, 28) ^ ror(x, 34) ^ ror(x, 39));
|
||||
macro ulong s1(ulong x) @local => (ror(x, 14) ^ ror(x, 18) ^ ror(x, 41));
|
||||
macro ulong r0(ulong x) @local => (ror(x, 1) ^ ror(x, 8) ^ (x >> 7));
|
||||
macro ulong r1(ulong x) @local => (ror(x, 19) ^ ror(x, 61) ^ (x >> 6));
|
||||
|
||||
const ulong[80] K @local = {
|
||||
0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
|
||||
0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
|
||||
0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
|
||||
0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
|
||||
0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
|
||||
0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
|
||||
0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
|
||||
0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
|
||||
0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
|
||||
0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
|
||||
0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
|
||||
0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
|
||||
0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
|
||||
0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
|
||||
0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
|
||||
0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
|
||||
0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
|
||||
0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
|
||||
0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
|
||||
0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
|
||||
};
|
||||
|
||||
|
||||
// See: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
|
||||
// All truncation types are simple to add onto the base SHA512 implementation at a (near) future time.
|
||||
enum HashTruncationType : uint (uint truncation_width, ulong[8] initial_state)
|
||||
{
|
||||
SHA512 = {
|
||||
512,
|
||||
{
|
||||
0x6a09e667f3bcc908,
|
||||
0xbb67ae8584caa73b,
|
||||
0x3c6ef372fe94f82b,
|
||||
0xa54ff53a5f1d36f1,
|
||||
0x510e527fade682d1,
|
||||
0x9b05688c2b3e6c1f,
|
||||
0x1f83d9abfb41bd6b,
|
||||
0x5be0cd19137e2179
|
||||
}
|
||||
},
|
||||
SHA384 = {
|
||||
384,
|
||||
{
|
||||
0xcbbb9d5dc1059ed8,
|
||||
0x629a292a367cd507,
|
||||
0x9159015a3070dd17,
|
||||
0x152fecd8f70e5939,
|
||||
0x67332667ffc00b31,
|
||||
0x8eb44a8768581511,
|
||||
0xdb0c2e0d64f98fa7,
|
||||
0x47b5481dbefa4fa4
|
||||
}
|
||||
},
|
||||
SHA512_224 = {
|
||||
224,
|
||||
{
|
||||
0x8C3D37C819544DA2,
|
||||
0x73E1996689DCD4D6,
|
||||
0x1DFAB7AE32FF9C82,
|
||||
0x679DD514582F9FCF,
|
||||
0x0F6D2B697BD44DA8,
|
||||
0x77E36F7304C48942,
|
||||
0x3F9D85A86A1D36C8,
|
||||
0x1112E6AD91D692A1
|
||||
}
|
||||
},
|
||||
SHA512_256 = {
|
||||
256,
|
||||
{
|
||||
0x22312194FC2BF72C,
|
||||
0x9F555FA3C84C64C2,
|
||||
0x2393B86B6F53B151,
|
||||
0x963877195940EABD,
|
||||
0x96283EE2A88EFFE3,
|
||||
0xBE5E1E2553863992,
|
||||
0x2B0199FC2C85B8AA,
|
||||
0x0EB72DDC81C52CA2
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [in] data
|
||||
*>
|
||||
fn char[HASH_SIZE] hash(char[] data)
|
||||
{
|
||||
Sha512 s @noinit;
|
||||
s.init();
|
||||
s.update(data);
|
||||
return s.final();
|
||||
}
|
||||
|
||||
|
||||
fn void Sha512.init(&self)
|
||||
{
|
||||
*self = {
|
||||
.hash_state = HashTruncationType.SHA512.initial_state
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [in] data
|
||||
@require data.len <= ulong.max
|
||||
*>
|
||||
fn void Sha512.update(&self, char[] data)
|
||||
{
|
||||
char* p = data.ptr;
|
||||
ulong len = data.len;
|
||||
ulong l;
|
||||
ulong r = self.length % 128;
|
||||
|
||||
self.length += len;
|
||||
|
||||
if (r)
|
||||
{
|
||||
if (len < (128 - r))
|
||||
{
|
||||
for (l = 0; l < len; ++l) self.buffer[r+l] = p[l];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (l = 0; l < 128 - r; ++l) self.buffer[r+l] = p[l];
|
||||
|
||||
len -= (128 - r);
|
||||
p = &p[128 - r];
|
||||
|
||||
sha512_transform(&self.hash_state, &self.buffer);
|
||||
}
|
||||
|
||||
for (; len >= 128; len -= 128, p = &p[128]) sha512_transform(&self.hash_state, p);
|
||||
|
||||
for (l = 0; l < len; ++l) self.buffer[l] = p[l];
|
||||
}
|
||||
|
||||
|
||||
fn char[HASH_SIZE] Sha512.final(&self)
|
||||
{
|
||||
char[HASH_SIZE] hash;
|
||||
|
||||
int i;
|
||||
ulong r = self.length % 128;
|
||||
|
||||
self.buffer[r++] = 0x80;
|
||||
|
||||
if (r > 112)
|
||||
{
|
||||
for (i = 0; i < 128 - r; ++i) self.buffer[r+i] = 0;
|
||||
|
||||
r = 0;
|
||||
|
||||
sha512_transform(&self.hash_state, &self.buffer);
|
||||
}
|
||||
|
||||
for (i = 0; i < 120 - r; ++i) self.buffer[r+i] = 0;
|
||||
|
||||
self.length *= 8;
|
||||
|
||||
self.buffer[120] = (char)(self.length >> 56);
|
||||
self.buffer[121] = (char)(self.length >> 48);
|
||||
self.buffer[122] = (char)(self.length >> 40);
|
||||
self.buffer[123] = (char)(self.length >> 32);
|
||||
self.buffer[124] = (char)(self.length >> 24);
|
||||
self.buffer[125] = (char)(self.length >> 16);
|
||||
self.buffer[126] = (char)(self.length >> 8);
|
||||
self.buffer[127] = (char)(self.length);
|
||||
|
||||
sha512_transform(&self.hash_state, &self.buffer);
|
||||
|
||||
for (i = 0; i < 8; ++i)
|
||||
{
|
||||
hash[(8 * i)] = (char)(self.hash_state[i] >> 56);
|
||||
hash[(8 * i) + 1] = (char)(self.hash_state[i] >> 48);
|
||||
hash[(8 * i) + 2] = (char)(self.hash_state[i] >> 40);
|
||||
hash[(8 * i) + 3] = (char)(self.hash_state[i] >> 32);
|
||||
hash[(8 * i) + 4] = (char)(self.hash_state[i] >> 24);
|
||||
hash[(8 * i) + 5] = (char)(self.hash_state[i] >> 16);
|
||||
hash[(8 * i) + 6] = (char)(self.hash_state[i] >> 8);
|
||||
hash[(8 * i) + 7] = (char)(self.hash_state[i]);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@param [&inout] state
|
||||
@param [&in] buf
|
||||
*>
|
||||
fn void sha512_transform(ulong *state, char *buf) @local
|
||||
{
|
||||
ulong t1, t2, a, b, c, d, e, f, g, h;
|
||||
ulong[80] w;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; ++i)
|
||||
{
|
||||
w[i] = (ulong)buf[(8 * i)] << 56;
|
||||
w[i] |= (ulong)buf[(8 * i) + 1] << 48;
|
||||
w[i] |= (ulong)buf[(8 * i) + 2] << 40;
|
||||
w[i] |= (ulong)buf[(8 * i) + 3] << 32;
|
||||
w[i] |= (ulong)buf[(8 * i) + 4] << 24;
|
||||
w[i] |= (ulong)buf[(8 * i) + 5] << 16;
|
||||
w[i] |= (ulong)buf[(8 * i) + 6] << 8;
|
||||
w[i] |= buf[(8 * i) + 7];
|
||||
}
|
||||
|
||||
for (; i < 80; ++i) w[i] = r1(w[i - 2]) + w[i - 7] + r0(w[i - 15]) + w[i - 16];
|
||||
|
||||
a = state[0];
|
||||
b = state[1];
|
||||
c = state[2];
|
||||
d = state[3];
|
||||
e = state[4];
|
||||
f = state[5];
|
||||
g = state[6];
|
||||
h = state[7];
|
||||
|
||||
for (i = 0; i < 80; ++i)
|
||||
{
|
||||
t1 = h + s1(e) + ch(e, f, g) + K[i] + w[i];
|
||||
t2 = s0(a) + maj(a, b, c);
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + t1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = t1 + t2;
|
||||
}
|
||||
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
state[4] += e;
|
||||
state[5] += f;
|
||||
state[6] += g;
|
||||
state[7] += h;
|
||||
}
|
||||
164
lib/std/hash/siphash.c3
Normal file
164
lib/std/hash/siphash.c3
Normal file
@@ -0,0 +1,164 @@
|
||||
// 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.
|
||||
//
|
||||
// SipHash is a secure pseudorandom function (PRF) which digests a 128-bit key
|
||||
// and a variable-length message to produce a 64- or 128-bit hash value.
|
||||
//
|
||||
// SipHash can be employed in numerous useful ways and structures, e.g.:
|
||||
// - Hash Tables
|
||||
// - Message Authentication Codes
|
||||
// - Denial of Service (hash flooding) resistance
|
||||
// - Bloom filters
|
||||
// - Keyed runtime identifier derivation
|
||||
//
|
||||
// Read more: https://en.wikipedia.org/wiki/SipHash
|
||||
//
|
||||
//
|
||||
|
||||
// COMMON HASH VARIANTS.
|
||||
// These two forms of SipHash (24 and 48) are the most widely
|
||||
// used by many implementations.
|
||||
|
||||
// These provide typical 64-bit hash results.
|
||||
// -- Best for performance-critical applications.
|
||||
module std::hash::siphash24;
|
||||
import std::hash::siphash;
|
||||
alias SipHash24 = SipHash { ulong, 2, 4 };
|
||||
alias hash = siphash::hash { ulong, 2, 4 };
|
||||
|
||||
// -- Best for conservative security applications.
|
||||
module std::hash::siphash48;
|
||||
import std::hash::siphash;
|
||||
alias SipHash48 = SipHash { ulong, 4, 8 };
|
||||
alias hash = siphash::hash { ulong, 4, 8 };
|
||||
|
||||
|
||||
// Exact same as above, but for 128-bit outputs. Algorithm internally changes slightly.
|
||||
module std::hash::siphash24_128;
|
||||
import std::hash::siphash;
|
||||
alias SipHash24_128 = SipHash { uint128, 2, 4 };
|
||||
alias hash = siphash::hash { uint128, 2, 4 };
|
||||
|
||||
module std::hash::siphash48_128;
|
||||
import std::hash::siphash;
|
||||
alias SipHash48_128 = SipHash { uint128, 4, 8 };
|
||||
alias hash = siphash::hash { uint128, 4, 8 };
|
||||
|
||||
<*
|
||||
@require OutType.typeid == uint128.typeid || OutType.typeid == ulong.typeid : "Module OutType must be either uint128 or ulong."
|
||||
*>
|
||||
module std::hash::siphash { OutType, BLOCK_ROUNDS, FINALIZE_ROUNDS };
|
||||
|
||||
|
||||
struct SipHash
|
||||
{
|
||||
ulong[4] v;
|
||||
long m;
|
||||
int m_idx;
|
||||
usz len;
|
||||
}
|
||||
|
||||
fn OutType hash(char[] data, uint128 key)
|
||||
{
|
||||
SipHash s;
|
||||
s.init(key);
|
||||
s.update(data);
|
||||
return s.final();
|
||||
}
|
||||
|
||||
fn void SipHash.init(&self, uint128 key)
|
||||
{
|
||||
ulong[2] key_64 = bitcast(key, ulong[2]);
|
||||
|
||||
self.v = {
|
||||
0x736f_6d65_7073_6575 ^ key_64[0],
|
||||
0x646f_7261_6e64_6f6d ^ key_64[1],
|
||||
0x6c79_6765_6e65_7261 ^ key_64[0],
|
||||
0x7465_6462_7974_6573 ^ key_64[1],
|
||||
};
|
||||
|
||||
$if OutType.typeid == uint128.typeid :
|
||||
self.v[1] ^= 0xEE;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] data : "Bytes to hash"
|
||||
*>
|
||||
fn void SipHash.update(&self, char[] data)
|
||||
{
|
||||
self.len += data.len;
|
||||
|
||||
foreach (byte : data)
|
||||
{
|
||||
self.m |= (long)byte << (self.m_idx++ << 3);
|
||||
|
||||
if (self.m_idx < 8) continue;
|
||||
|
||||
// This runs every time the m_idx is 8, then it resets to 0.
|
||||
self.v[3] ^= self.m;
|
||||
|
||||
$for var $i = 0; $i < BLOCK_ROUNDS; ++$i : // unrolled loop
|
||||
self.round();
|
||||
$endfor
|
||||
|
||||
self.v[0] ^= self.m;
|
||||
|
||||
self.m_idx = 0;
|
||||
self.m = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn OutType SipHash.final(&self)
|
||||
{
|
||||
char[8] last = { [7] = (char)self.len };
|
||||
|
||||
self.update(last[(self.m_idx < 7 ? self.m_idx : 7)..]);
|
||||
|
||||
$if OutType.typeid == uint128.typeid :
|
||||
self.v[2] ^= 0xEE;
|
||||
$else
|
||||
self.v[2] ^= 0xFF;
|
||||
$endif
|
||||
|
||||
$for var $i = 0; $i < FINALIZE_ROUNDS; ++$i : // unrolled loop
|
||||
self.round();
|
||||
$endfor
|
||||
|
||||
$if OutType.typeid == ulong.typeid :
|
||||
return self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3];
|
||||
$else
|
||||
ulong lo = self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3];
|
||||
|
||||
self.v[1] ^= 0xDD;
|
||||
|
||||
$for var $i = 0; $i < FINALIZE_ROUNDS; ++$i : // unrolled loop
|
||||
self.round();
|
||||
$endfor
|
||||
|
||||
return lo | ((uint128)(self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3]) << 64);
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
fn void SipHash.round(&self) @local
|
||||
{
|
||||
self.v[0] += self.v[1];
|
||||
self.v[1] = self.v[1].rotl(13);
|
||||
self.v[1] ^= self.v[0];
|
||||
self.v[0] = self.v[0].rotl(32);
|
||||
self.v[2] += self.v[3];
|
||||
self.v[3] = self.v[3].rotl(16);
|
||||
self.v[3] ^= self.v[2];
|
||||
self.v[0] += self.v[3];
|
||||
self.v[3] = self.v[3].rotl(21);
|
||||
self.v[3] ^= self.v[0];
|
||||
self.v[2] += self.v[1];
|
||||
self.v[1] = self.v[1].rotl(17);
|
||||
self.v[1] ^= self.v[2];
|
||||
self.v[2] = self.v[2].rotl(32);
|
||||
}
|
||||
|
||||
|
||||
|
||||
185
lib/std/hash/whirlpool/whirlpool.c3
Normal file
185
lib/std/hash/whirlpool/whirlpool.c3
Normal file
@@ -0,0 +1,185 @@
|
||||
// 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.
|
||||
//
|
||||
// Dedicated from repo: https://github.com/NotsoanoNimus/whirlpool.c3l
|
||||
module std::hash::whirlpool;
|
||||
|
||||
import std::hash::hmac;
|
||||
|
||||
|
||||
const BLOCK_SIZE = 64;
|
||||
const HASH_SIZE = 64;
|
||||
const BLOCK_128 = 64 / int128.sizeof;
|
||||
|
||||
struct Whirlpool
|
||||
{
|
||||
ulong[8] hash;
|
||||
union
|
||||
{
|
||||
char[BLOCK_SIZE] block;
|
||||
int128[BLOCK_128] block_128;
|
||||
}
|
||||
|
||||
// Using these two integers with a funnel shift permits the original WHIRLPOOL 2^256 length limit.
|
||||
uint128 counter_high;
|
||||
uint128 counter_low;
|
||||
}
|
||||
|
||||
alias HmacWhirlpool = Hmac { Whirlpool, HASH_SIZE, BLOCK_SIZE };
|
||||
alias hmac = hmac::hash { Whirlpool, HASH_SIZE, BLOCK_SIZE };
|
||||
alias pbkdf2 = hmac::pbkdf2 { Whirlpool, HASH_SIZE, BLOCK_SIZE };
|
||||
|
||||
fn char[HASH_SIZE] hash(char[] data)
|
||||
{
|
||||
Whirlpool w;
|
||||
w.update(data);
|
||||
return w.final();
|
||||
}
|
||||
|
||||
// Added to satisfy HMAC
|
||||
macro void Whirlpool.init(&self) => *self = { };
|
||||
|
||||
<*
|
||||
@require data.len <= isz.max : "Update with smaller slices"
|
||||
*>
|
||||
fn void Whirlpool.update(&self, char[] data)
|
||||
{
|
||||
char remainder = (char)self.counter_low & 0x3F; // if not at an even BLOCK_SIZE, how many bytes are over it
|
||||
uint to_pad = BLOCK_SIZE - remainder; // how many bytes must be filled to get the BLOCK_SIZE
|
||||
|
||||
uint128 prev_ctr = self.counter_low;
|
||||
self.counter_low += data.len;
|
||||
|
||||
// Handle counter rollover by just adding 1 onto 'high'. The overflow amount in 'low' remains valid.
|
||||
// Since 'data' is limited to INT64_MAX, there are no edge cases where 'high' must be incremented by more than 1.
|
||||
// This is so much data (> 2^128 bytes) that this will likely NEVER happen in practice...
|
||||
if (@unlikely(self.counter_low < prev_ctr)) ++self.counter_high;
|
||||
|
||||
// Pad partial blocks of input data.
|
||||
if (remainder > 0)
|
||||
{
|
||||
usz span = to_pad < data.len ? to_pad : data.len;
|
||||
self.block[remainder:span] = data[:span];
|
||||
|
||||
// When there wasn't enough data ingested to fill a block, just return to allow more incoming data to fill in.
|
||||
// If the algorithm is finalized during a 'partial' block, it has its own padding to fill the remaining gap.
|
||||
if (data.len < to_pad) return;
|
||||
|
||||
self.process_block(&self.block);
|
||||
data = data[to_pad..];
|
||||
}
|
||||
|
||||
// Digest blocks wholesale.
|
||||
while (data.len >= BLOCK_SIZE)
|
||||
{
|
||||
self.process_block(data);
|
||||
|
||||
data = (data.len > BLOCK_SIZE) ? data[BLOCK_SIZE..] : {};
|
||||
}
|
||||
|
||||
// Any leftovers should be swept into the 'block' buffer in the context for the next update or for 'final'.
|
||||
if (data.len)
|
||||
{
|
||||
usz leftover_span = (BLOCK_SIZE < data.len ? BLOCK_SIZE : data.len);
|
||||
|
||||
self.block[:leftover_span] = data[:leftover_span];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn char[HASH_SIZE] Whirlpool.final(&self)
|
||||
{
|
||||
char remainder = (char)self.counter_low & 0x3F; // if not at an even BLOCK_SIZE, how many bytes are over it
|
||||
|
||||
// Terminating byte gets added to the block.
|
||||
self.block[remainder++] = 0x80;
|
||||
|
||||
// When fewer than 256 bits are available to store the counter into, pad the block with zeroes and process it.
|
||||
if (remainder > 32)
|
||||
{
|
||||
if (remainder < 64) self.block[remainder..63] = 0x00;
|
||||
|
||||
self.process_block(&self.block);
|
||||
remainder = 0;
|
||||
}
|
||||
|
||||
// Zero out the rest of the chunk (or a new chunk if the above 'if' was true), up to byte 32.
|
||||
if (remainder < 32) self.block[remainder..31] = 0x00;
|
||||
|
||||
// Insert the big-endian values of the counter as a suffix of the block.
|
||||
// This 'counter' value is actually the number of BITS processed, hence the << 3 (or x8).
|
||||
self.block_128[2] = $$bswap(self.counter_high << 3 | (self.counter_low >> 125 & 0x07));
|
||||
self.block_128[3] = $$bswap(self.counter_low << 3);
|
||||
|
||||
// Process the final block.
|
||||
self.process_block(&self.block);
|
||||
|
||||
// Each ulong in the resultant hash should be bit-swapped before the final return.
|
||||
char[HASH_SIZE] hash @align(ulong.alignof);
|
||||
ulong* hash_ref = (ulong*)&hash;
|
||||
|
||||
$for var $i = 0; $i < 8; ++$i :
|
||||
hash_ref[$i] = $$bswap(self.hash[$i]);
|
||||
$endfor
|
||||
|
||||
// All done!
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
macro ulong @w_op(#src, $shift) @private
|
||||
=> S_BOX[(0 * 256) + (int)(#src[($shift + 0) & 7] >> 56) ]
|
||||
^ S_BOX[(1 * 256) + (int)(#src[($shift + 7) & 7] >> 48) & 0xFF]
|
||||
^ S_BOX[(2 * 256) + (int)(#src[($shift + 6) & 7] >> 40) & 0xFF]
|
||||
^ S_BOX[(3 * 256) + (int)(#src[($shift + 5) & 7] >> 32) & 0xFF]
|
||||
^ S_BOX[(4 * 256) + (int)(#src[($shift + 4) & 7] >> 24) & 0xFF]
|
||||
^ S_BOX[(5 * 256) + (int)(#src[($shift + 3) & 7] >> 16) & 0xFF]
|
||||
^ S_BOX[(6 * 256) + (int)(#src[($shift + 2) & 7] >> 8) & 0xFF]
|
||||
^ S_BOX[(7 * 256) + (int)(#src[($shift + 1) & 7] >> 0) & 0xFF];
|
||||
|
||||
|
||||
const ulong[10] RC @private = {
|
||||
0x1823c6e887b8014f,
|
||||
0x36a6d2f5796f9152,
|
||||
0x60bc9b8ea30c7b35,
|
||||
0x1de0d7c22e4bfe57,
|
||||
0x157737e59ff04ada,
|
||||
0x58c9290ab1a06b85,
|
||||
0xbd5d10f4cb3e0567,
|
||||
0xe427418ba77d95d8,
|
||||
0xfbee7c66dd17479e,
|
||||
0xca2dbf07ad5a8333
|
||||
};
|
||||
const ROUNDS = 10;
|
||||
|
||||
fn void Whirlpool.process_block(&self, char* block) @local
|
||||
{
|
||||
ulong[2 * 8] k; // key
|
||||
ulong[2 * 8] state; // state
|
||||
|
||||
// NOTE: These loops are unrolled with C3's Chad-tier compile-time evaluation.
|
||||
$for var $round = 0; $round < 8; $round++:
|
||||
k[$round] = self.hash[$round];
|
||||
state[$round] = $$bswap(@unaligned_load(((ulong*)block)[$round], 1)) ^ self.hash[$round];
|
||||
self.hash[$round] = state[$round];
|
||||
$endfor
|
||||
|
||||
$for var $round = 0; $round < ROUNDS; ++$round :
|
||||
var $m = $round % 2;
|
||||
|
||||
k[(($m ^ 1) * 8) + 0] = @w_op((&k[$m * 8]), 0) ^ RC[$round];
|
||||
|
||||
$for var $i = 1; $i < 8; $i++ :
|
||||
k[(($m ^ 1) * 8) + $i] = @w_op((&k[$m * 8]), $i);
|
||||
$endfor
|
||||
|
||||
$for var $i = 0; $i < 8; $i++ :
|
||||
state[(($m ^ 1) * 8) + $i] = @w_op(&(state[$m * 8]), $i) ^ k[(($m ^ 1) * 8) + $i];
|
||||
$endfor
|
||||
$endfor
|
||||
|
||||
$for var $x = 0; $x < 8; $x++:
|
||||
self.hash[$x] ^= state[$x];
|
||||
$endfor
|
||||
}
|
||||
539
lib/std/hash/whirlpool/whirlpool_sbox.c3
Normal file
539
lib/std/hash/whirlpool/whirlpool_sbox.c3
Normal file
@@ -0,0 +1,539 @@
|
||||
// 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.
|
||||
//
|
||||
// Dedicated from repo: https://github.com/NotsoanoNimus/whirlpool.c3l
|
||||
module std::hash::whirlpool @private;
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// WHIRLPOOL s-box data.
|
||||
const ulong[8 * 256] S_BOX = {
|
||||
// C0
|
||||
0x18186018c07830d8, 0x23238c2305af4626, 0xc6c63fc67ef991b8, 0xe8e887e8136fcdfb,
|
||||
0x878726874ca113cb, 0xb8b8dab8a9626d11, 0x0101040108050209, 0x4f4f214f426e9e0d,
|
||||
0x3636d836adee6c9b, 0xa6a6a2a6590451ff, 0xd2d26fd2debdb90c, 0xf5f5f3f5fb06f70e,
|
||||
0x7979f979ef80f296, 0x6f6fa16f5fcede30, 0x91917e91fcef3f6d, 0x52525552aa07a4f8,
|
||||
0x60609d6027fdc047, 0xbcbccabc89766535, 0x9b9b569baccd2b37, 0x8e8e028e048c018a,
|
||||
0xa3a3b6a371155bd2, 0x0c0c300c603c186c, 0x7b7bf17bff8af684, 0x3535d435b5e16a80,
|
||||
0x1d1d741de8693af5, 0xe0e0a7e05347ddb3, 0xd7d77bd7f6acb321, 0xc2c22fc25eed999c,
|
||||
0x2e2eb82e6d965c43, 0x4b4b314b627a9629, 0xfefedffea321e15d, 0x575741578216aed5,
|
||||
0x15155415a8412abd, 0x7777c1779fb6eee8, 0x3737dc37a5eb6e92, 0xe5e5b3e57b56d79e,
|
||||
0x9f9f469f8cd92313, 0xf0f0e7f0d317fd23, 0x4a4a354a6a7f9420, 0xdada4fda9e95a944,
|
||||
0x58587d58fa25b0a2, 0xc9c903c906ca8fcf, 0x2929a429558d527c, 0x0a0a280a5022145a,
|
||||
0xb1b1feb1e14f7f50, 0xa0a0baa0691a5dc9, 0x6b6bb16b7fdad614, 0x85852e855cab17d9,
|
||||
0xbdbdcebd8173673c, 0x5d5d695dd234ba8f, 0x1010401080502090, 0xf4f4f7f4f303f507,
|
||||
0xcbcb0bcb16c08bdd, 0x3e3ef83eedc67cd3, 0x0505140528110a2d, 0x676781671fe6ce78,
|
||||
0xe4e4b7e47353d597, 0x27279c2725bb4e02, 0x4141194132588273, 0x8b8b168b2c9d0ba7,
|
||||
0xa7a7a6a7510153f6, 0x7d7de97dcf94fab2, 0x95956e95dcfb3749, 0xd8d847d88e9fad56,
|
||||
0xfbfbcbfb8b30eb70, 0xeeee9fee2371c1cd, 0x7c7ced7cc791f8bb, 0x6666856617e3cc71,
|
||||
0xdddd53dda68ea77b, 0x17175c17b84b2eaf, 0x4747014702468e45, 0x9e9e429e84dc211a,
|
||||
0xcaca0fca1ec589d4, 0x2d2db42d75995a58, 0xbfbfc6bf9179632e, 0x07071c07381b0e3f,
|
||||
0xadad8ead012347ac, 0x5a5a755aea2fb4b0, 0x838336836cb51bef, 0x3333cc3385ff66b6,
|
||||
0x636391633ff2c65c, 0x02020802100a0412, 0xaaaa92aa39384993, 0x7171d971afa8e2de,
|
||||
0xc8c807c80ecf8dc6, 0x19196419c87d32d1, 0x494939497270923b, 0xd9d943d9869aaf5f,
|
||||
0xf2f2eff2c31df931, 0xe3e3abe34b48dba8, 0x5b5b715be22ab6b9, 0x88881a8834920dbc,
|
||||
0x9a9a529aa4c8293e, 0x262698262dbe4c0b, 0x3232c8328dfa64bf, 0xb0b0fab0e94a7d59,
|
||||
0xe9e983e91b6acff2, 0x0f0f3c0f78331e77, 0xd5d573d5e6a6b733, 0x80803a8074ba1df4,
|
||||
0xbebec2be997c6127, 0xcdcd13cd26de87eb, 0x3434d034bde46889, 0x48483d487a759032,
|
||||
0xffffdbffab24e354, 0x7a7af57af78ff48d, 0x90907a90f4ea3d64, 0x5f5f615fc23ebe9d,
|
||||
0x202080201da0403d, 0x6868bd6867d5d00f, 0x1a1a681ad07234ca, 0xaeae82ae192c41b7,
|
||||
0xb4b4eab4c95e757d, 0x54544d549a19a8ce, 0x93937693ece53b7f, 0x222288220daa442f,
|
||||
0x64648d6407e9c863, 0xf1f1e3f1db12ff2a, 0x7373d173bfa2e6cc, 0x12124812905a2482,
|
||||
0x40401d403a5d807a, 0x0808200840281048, 0xc3c32bc356e89b95, 0xecec97ec337bc5df,
|
||||
0xdbdb4bdb9690ab4d, 0xa1a1bea1611f5fc0, 0x8d8d0e8d1c830791, 0x3d3df43df5c97ac8,
|
||||
0x97976697ccf1335b, 0x0000000000000000, 0xcfcf1bcf36d483f9, 0x2b2bac2b4587566e,
|
||||
0x7676c57697b3ece1, 0x8282328264b019e6, 0xd6d67fd6fea9b128, 0x1b1b6c1bd87736c3,
|
||||
0xb5b5eeb5c15b7774, 0xafaf86af112943be, 0x6a6ab56a77dfd41d, 0x50505d50ba0da0ea,
|
||||
0x45450945124c8a57, 0xf3f3ebf3cb18fb38, 0x3030c0309df060ad, 0xefef9bef2b74c3c4,
|
||||
0x3f3ffc3fe5c37eda, 0x55554955921caac7, 0xa2a2b2a2791059db, 0xeaea8fea0365c9e9,
|
||||
0x656589650fecca6a, 0xbabad2bab9686903, 0x2f2fbc2f65935e4a, 0xc0c027c04ee79d8e,
|
||||
0xdede5fdebe81a160, 0x1c1c701ce06c38fc, 0xfdfdd3fdbb2ee746, 0x4d4d294d52649a1f,
|
||||
0x92927292e4e03976, 0x7575c9758fbceafa, 0x06061806301e0c36, 0x8a8a128a249809ae,
|
||||
0xb2b2f2b2f940794b, 0xe6e6bfe66359d185, 0x0e0e380e70361c7e, 0x1f1f7c1ff8633ee7,
|
||||
0x6262956237f7c455, 0xd4d477d4eea3b53a, 0xa8a89aa829324d81, 0x96966296c4f43152,
|
||||
0xf9f9c3f99b3aef62, 0xc5c533c566f697a3, 0x2525942535b14a10, 0x59597959f220b2ab,
|
||||
0x84842a8454ae15d0, 0x7272d572b7a7e4c5, 0x3939e439d5dd72ec, 0x4c4c2d4c5a619816,
|
||||
0x5e5e655eca3bbc94, 0x7878fd78e785f09f, 0x3838e038ddd870e5, 0x8c8c0a8c14860598,
|
||||
0xd1d163d1c6b2bf17, 0xa5a5aea5410b57e4, 0xe2e2afe2434dd9a1, 0x616199612ff8c24e,
|
||||
0xb3b3f6b3f1457b42, 0x2121842115a54234, 0x9c9c4a9c94d62508, 0x1e1e781ef0663cee,
|
||||
0x4343114322528661, 0xc7c73bc776fc93b1, 0xfcfcd7fcb32be54f, 0x0404100420140824,
|
||||
0x51515951b208a2e3, 0x99995e99bcc72f25, 0x6d6da96d4fc4da22, 0x0d0d340d68391a65,
|
||||
0xfafacffa8335e979, 0xdfdf5bdfb684a369, 0x7e7ee57ed79bfca9, 0x242490243db44819,
|
||||
0x3b3bec3bc5d776fe, 0xabab96ab313d4b9a, 0xcece1fce3ed181f0, 0x1111441188552299,
|
||||
0x8f8f068f0c890383, 0x4e4e254e4a6b9c04, 0xb7b7e6b7d1517366, 0xebeb8beb0b60cbe0,
|
||||
0x3c3cf03cfdcc78c1, 0x81813e817cbf1ffd, 0x94946a94d4fe3540, 0xf7f7fbf7eb0cf31c,
|
||||
0xb9b9deb9a1676f18, 0x13134c13985f268b, 0x2c2cb02c7d9c5851, 0xd3d36bd3d6b8bb05,
|
||||
0xe7e7bbe76b5cd38c, 0x6e6ea56e57cbdc39, 0xc4c437c46ef395aa, 0x03030c03180f061b,
|
||||
0x565645568a13acdc, 0x44440d441a49885e, 0x7f7fe17fdf9efea0, 0xa9a99ea921374f88,
|
||||
0x2a2aa82a4d825467, 0xbbbbd6bbb16d6b0a, 0xc1c123c146e29f87, 0x53535153a202a6f1,
|
||||
0xdcdc57dcae8ba572, 0x0b0b2c0b58271653, 0x9d9d4e9d9cd32701, 0x6c6cad6c47c1d82b,
|
||||
0x3131c43195f562a4, 0x7474cd7487b9e8f3, 0xf6f6fff6e309f115, 0x464605460a438c4c,
|
||||
0xacac8aac092645a5, 0x89891e893c970fb5, 0x14145014a04428b4, 0xe1e1a3e15b42dfba,
|
||||
0x16165816b04e2ca6, 0x3a3ae83acdd274f7, 0x6969b9696fd0d206, 0x09092409482d1241,
|
||||
0x7070dd70a7ade0d7, 0xb6b6e2b6d954716f, 0xd0d067d0ceb7bd1e, 0xeded93ed3b7ec7d6,
|
||||
0xcccc17cc2edb85e2, 0x424215422a578468, 0x98985a98b4c22d2c, 0xa4a4aaa4490e55ed,
|
||||
0x2828a0285d885075, 0x5c5c6d5cda31b886, 0xf8f8c7f8933fed6b, 0x8686228644a411c2,
|
||||
|
||||
// C1
|
||||
0xd818186018c07830, 0x2623238c2305af46, 0xb8c6c63fc67ef991, 0xfbe8e887e8136fcd,
|
||||
0xcb878726874ca113, 0x11b8b8dab8a9626d, 0x0901010401080502, 0x0d4f4f214f426e9e,
|
||||
0x9b3636d836adee6c, 0xffa6a6a2a6590451, 0x0cd2d26fd2debdb9, 0x0ef5f5f3f5fb06f7,
|
||||
0x967979f979ef80f2, 0x306f6fa16f5fcede, 0x6d91917e91fcef3f, 0xf852525552aa07a4,
|
||||
0x4760609d6027fdc0, 0x35bcbccabc897665, 0x379b9b569baccd2b, 0x8a8e8e028e048c01,
|
||||
0xd2a3a3b6a371155b, 0x6c0c0c300c603c18, 0x847b7bf17bff8af6, 0x803535d435b5e16a,
|
||||
0xf51d1d741de8693a, 0xb3e0e0a7e05347dd, 0x21d7d77bd7f6acb3, 0x9cc2c22fc25eed99,
|
||||
0x432e2eb82e6d965c, 0x294b4b314b627a96, 0x5dfefedffea321e1, 0xd5575741578216ae,
|
||||
0xbd15155415a8412a, 0xe87777c1779fb6ee, 0x923737dc37a5eb6e, 0x9ee5e5b3e57b56d7,
|
||||
0x139f9f469f8cd923, 0x23f0f0e7f0d317fd, 0x204a4a354a6a7f94, 0x44dada4fda9e95a9,
|
||||
0xa258587d58fa25b0, 0xcfc9c903c906ca8f, 0x7c2929a429558d52, 0x5a0a0a280a502214,
|
||||
0x50b1b1feb1e14f7f, 0xc9a0a0baa0691a5d, 0x146b6bb16b7fdad6, 0xd985852e855cab17,
|
||||
0x3cbdbdcebd817367, 0x8f5d5d695dd234ba, 0x9010104010805020, 0x07f4f4f7f4f303f5,
|
||||
0xddcbcb0bcb16c08b, 0xd33e3ef83eedc67c, 0x2d0505140528110a, 0x78676781671fe6ce,
|
||||
0x97e4e4b7e47353d5, 0x0227279c2725bb4e, 0x7341411941325882, 0xa78b8b168b2c9d0b,
|
||||
0xf6a7a7a6a7510153, 0xb27d7de97dcf94fa, 0x4995956e95dcfb37, 0x56d8d847d88e9fad,
|
||||
0x70fbfbcbfb8b30eb, 0xcdeeee9fee2371c1, 0xbb7c7ced7cc791f8, 0x716666856617e3cc,
|
||||
0x7bdddd53dda68ea7, 0xaf17175c17b84b2e, 0x454747014702468e, 0x1a9e9e429e84dc21,
|
||||
0xd4caca0fca1ec589, 0x582d2db42d75995a, 0x2ebfbfc6bf917963, 0x3f07071c07381b0e,
|
||||
0xacadad8ead012347, 0xb05a5a755aea2fb4, 0xef838336836cb51b, 0xb63333cc3385ff66,
|
||||
0x5c636391633ff2c6, 0x1202020802100a04, 0x93aaaa92aa393849, 0xde7171d971afa8e2,
|
||||
0xc6c8c807c80ecf8d, 0xd119196419c87d32, 0x3b49493949727092, 0x5fd9d943d9869aaf,
|
||||
0x31f2f2eff2c31df9, 0xa8e3e3abe34b48db, 0xb95b5b715be22ab6, 0xbc88881a8834920d,
|
||||
0x3e9a9a529aa4c829, 0x0b262698262dbe4c, 0xbf3232c8328dfa64, 0x59b0b0fab0e94a7d,
|
||||
0xf2e9e983e91b6acf, 0x770f0f3c0f78331e, 0x33d5d573d5e6a6b7, 0xf480803a8074ba1d,
|
||||
0x27bebec2be997c61, 0xebcdcd13cd26de87, 0x893434d034bde468, 0x3248483d487a7590,
|
||||
0x54ffffdbffab24e3, 0x8d7a7af57af78ff4, 0x6490907a90f4ea3d, 0x9d5f5f615fc23ebe,
|
||||
0x3d202080201da040, 0x0f6868bd6867d5d0, 0xca1a1a681ad07234, 0xb7aeae82ae192c41,
|
||||
0x7db4b4eab4c95e75, 0xce54544d549a19a8, 0x7f93937693ece53b, 0x2f222288220daa44,
|
||||
0x6364648d6407e9c8, 0x2af1f1e3f1db12ff, 0xcc7373d173bfa2e6, 0x8212124812905a24,
|
||||
0x7a40401d403a5d80, 0x4808082008402810, 0x95c3c32bc356e89b, 0xdfecec97ec337bc5,
|
||||
0x4ddbdb4bdb9690ab, 0xc0a1a1bea1611f5f, 0x918d8d0e8d1c8307, 0xc83d3df43df5c97a,
|
||||
0x5b97976697ccf133, 0x0000000000000000, 0xf9cfcf1bcf36d483, 0x6e2b2bac2b458756,
|
||||
0xe17676c57697b3ec, 0xe68282328264b019, 0x28d6d67fd6fea9b1, 0xc31b1b6c1bd87736,
|
||||
0x74b5b5eeb5c15b77, 0xbeafaf86af112943, 0x1d6a6ab56a77dfd4, 0xea50505d50ba0da0,
|
||||
0x5745450945124c8a, 0x38f3f3ebf3cb18fb, 0xad3030c0309df060, 0xc4efef9bef2b74c3,
|
||||
0xda3f3ffc3fe5c37e, 0xc755554955921caa, 0xdba2a2b2a2791059, 0xe9eaea8fea0365c9,
|
||||
0x6a656589650fecca, 0x03babad2bab96869, 0x4a2f2fbc2f65935e, 0x8ec0c027c04ee79d,
|
||||
0x60dede5fdebe81a1, 0xfc1c1c701ce06c38, 0x46fdfdd3fdbb2ee7, 0x1f4d4d294d52649a,
|
||||
0x7692927292e4e039, 0xfa7575c9758fbcea, 0x3606061806301e0c, 0xae8a8a128a249809,
|
||||
0x4bb2b2f2b2f94079, 0x85e6e6bfe66359d1, 0x7e0e0e380e70361c, 0xe71f1f7c1ff8633e,
|
||||
0x556262956237f7c4, 0x3ad4d477d4eea3b5, 0x81a8a89aa829324d, 0x5296966296c4f431,
|
||||
0x62f9f9c3f99b3aef, 0xa3c5c533c566f697, 0x102525942535b14a, 0xab59597959f220b2,
|
||||
0xd084842a8454ae15, 0xc57272d572b7a7e4, 0xec3939e439d5dd72, 0x164c4c2d4c5a6198,
|
||||
0x945e5e655eca3bbc, 0x9f7878fd78e785f0, 0xe53838e038ddd870, 0x988c8c0a8c148605,
|
||||
0x17d1d163d1c6b2bf, 0xe4a5a5aea5410b57, 0xa1e2e2afe2434dd9, 0x4e616199612ff8c2,
|
||||
0x42b3b3f6b3f1457b, 0x342121842115a542, 0x089c9c4a9c94d625, 0xee1e1e781ef0663c,
|
||||
0x6143431143225286, 0xb1c7c73bc776fc93, 0x4ffcfcd7fcb32be5, 0x2404041004201408,
|
||||
0xe351515951b208a2, 0x2599995e99bcc72f, 0x226d6da96d4fc4da, 0x650d0d340d68391a,
|
||||
0x79fafacffa8335e9, 0x69dfdf5bdfb684a3, 0xa97e7ee57ed79bfc, 0x19242490243db448,
|
||||
0xfe3b3bec3bc5d776, 0x9aabab96ab313d4b, 0xf0cece1fce3ed181, 0x9911114411885522,
|
||||
0x838f8f068f0c8903, 0x044e4e254e4a6b9c, 0x66b7b7e6b7d15173, 0xe0ebeb8beb0b60cb,
|
||||
0xc13c3cf03cfdcc78, 0xfd81813e817cbf1f, 0x4094946a94d4fe35, 0x1cf7f7fbf7eb0cf3,
|
||||
0x18b9b9deb9a1676f, 0x8b13134c13985f26, 0x512c2cb02c7d9c58, 0x05d3d36bd3d6b8bb,
|
||||
0x8ce7e7bbe76b5cd3, 0x396e6ea56e57cbdc, 0xaac4c437c46ef395, 0x1b03030c03180f06,
|
||||
0xdc565645568a13ac, 0x5e44440d441a4988, 0xa07f7fe17fdf9efe, 0x88a9a99ea921374f,
|
||||
0x672a2aa82a4d8254, 0x0abbbbd6bbb16d6b, 0x87c1c123c146e29f, 0xf153535153a202a6,
|
||||
0x72dcdc57dcae8ba5, 0x530b0b2c0b582716, 0x019d9d4e9d9cd327, 0x2b6c6cad6c47c1d8,
|
||||
0xa43131c43195f562, 0xf37474cd7487b9e8, 0x15f6f6fff6e309f1, 0x4c464605460a438c,
|
||||
0xa5acac8aac092645, 0xb589891e893c970f, 0xb414145014a04428, 0xbae1e1a3e15b42df,
|
||||
0xa616165816b04e2c, 0xf73a3ae83acdd274, 0x066969b9696fd0d2, 0x4109092409482d12,
|
||||
0xd77070dd70a7ade0, 0x6fb6b6e2b6d95471, 0x1ed0d067d0ceb7bd, 0xd6eded93ed3b7ec7,
|
||||
0xe2cccc17cc2edb85, 0x68424215422a5784, 0x2c98985a98b4c22d, 0xeda4a4aaa4490e55,
|
||||
0x752828a0285d8850, 0x865c5c6d5cda31b8, 0x6bf8f8c7f8933fed, 0xc28686228644a411,
|
||||
|
||||
// C2
|
||||
0x30d818186018c078, 0x462623238c2305af, 0x91b8c6c63fc67ef9, 0xcdfbe8e887e8136f,
|
||||
0x13cb878726874ca1, 0x6d11b8b8dab8a962, 0x0209010104010805, 0x9e0d4f4f214f426e,
|
||||
0x6c9b3636d836adee, 0x51ffa6a6a2a65904, 0xb90cd2d26fd2debd, 0xf70ef5f5f3f5fb06,
|
||||
0xf2967979f979ef80, 0xde306f6fa16f5fce, 0x3f6d91917e91fcef, 0xa4f852525552aa07,
|
||||
0xc04760609d6027fd, 0x6535bcbccabc8976, 0x2b379b9b569baccd, 0x018a8e8e028e048c,
|
||||
0x5bd2a3a3b6a37115, 0x186c0c0c300c603c, 0xf6847b7bf17bff8a, 0x6a803535d435b5e1,
|
||||
0x3af51d1d741de869, 0xddb3e0e0a7e05347, 0xb321d7d77bd7f6ac, 0x999cc2c22fc25eed,
|
||||
0x5c432e2eb82e6d96, 0x96294b4b314b627a, 0xe15dfefedffea321, 0xaed5575741578216,
|
||||
0x2abd15155415a841, 0xeee87777c1779fb6, 0x6e923737dc37a5eb, 0xd79ee5e5b3e57b56,
|
||||
0x23139f9f469f8cd9, 0xfd23f0f0e7f0d317, 0x94204a4a354a6a7f, 0xa944dada4fda9e95,
|
||||
0xb0a258587d58fa25, 0x8fcfc9c903c906ca, 0x527c2929a429558d, 0x145a0a0a280a5022,
|
||||
0x7f50b1b1feb1e14f, 0x5dc9a0a0baa0691a, 0xd6146b6bb16b7fda, 0x17d985852e855cab,
|
||||
0x673cbdbdcebd8173, 0xba8f5d5d695dd234, 0x2090101040108050, 0xf507f4f4f7f4f303,
|
||||
0x8bddcbcb0bcb16c0, 0x7cd33e3ef83eedc6, 0x0a2d050514052811, 0xce78676781671fe6,
|
||||
0xd597e4e4b7e47353, 0x4e0227279c2725bb, 0x8273414119413258, 0x0ba78b8b168b2c9d,
|
||||
0x53f6a7a7a6a75101, 0xfab27d7de97dcf94, 0x374995956e95dcfb, 0xad56d8d847d88e9f,
|
||||
0xeb70fbfbcbfb8b30, 0xc1cdeeee9fee2371, 0xf8bb7c7ced7cc791, 0xcc716666856617e3,
|
||||
0xa77bdddd53dda68e, 0x2eaf17175c17b84b, 0x8e45474701470246, 0x211a9e9e429e84dc,
|
||||
0x89d4caca0fca1ec5, 0x5a582d2db42d7599, 0x632ebfbfc6bf9179, 0x0e3f07071c07381b,
|
||||
0x47acadad8ead0123, 0xb4b05a5a755aea2f, 0x1bef838336836cb5, 0x66b63333cc3385ff,
|
||||
0xc65c636391633ff2, 0x041202020802100a, 0x4993aaaa92aa3938, 0xe2de7171d971afa8,
|
||||
0x8dc6c8c807c80ecf, 0x32d119196419c87d, 0x923b494939497270, 0xaf5fd9d943d9869a,
|
||||
0xf931f2f2eff2c31d, 0xdba8e3e3abe34b48, 0xb6b95b5b715be22a, 0x0dbc88881a883492,
|
||||
0x293e9a9a529aa4c8, 0x4c0b262698262dbe, 0x64bf3232c8328dfa, 0x7d59b0b0fab0e94a,
|
||||
0xcff2e9e983e91b6a, 0x1e770f0f3c0f7833, 0xb733d5d573d5e6a6, 0x1df480803a8074ba,
|
||||
0x6127bebec2be997c, 0x87ebcdcd13cd26de, 0x68893434d034bde4, 0x903248483d487a75,
|
||||
0xe354ffffdbffab24, 0xf48d7a7af57af78f, 0x3d6490907a90f4ea, 0xbe9d5f5f615fc23e,
|
||||
0x403d202080201da0, 0xd00f6868bd6867d5, 0x34ca1a1a681ad072, 0x41b7aeae82ae192c,
|
||||
0x757db4b4eab4c95e, 0xa8ce54544d549a19, 0x3b7f93937693ece5, 0x442f222288220daa,
|
||||
0xc86364648d6407e9, 0xff2af1f1e3f1db12, 0xe6cc7373d173bfa2, 0x248212124812905a,
|
||||
0x807a40401d403a5d, 0x1048080820084028, 0x9b95c3c32bc356e8, 0xc5dfecec97ec337b,
|
||||
0xab4ddbdb4bdb9690, 0x5fc0a1a1bea1611f, 0x07918d8d0e8d1c83, 0x7ac83d3df43df5c9,
|
||||
0x335b97976697ccf1, 0x0000000000000000, 0x83f9cfcf1bcf36d4, 0x566e2b2bac2b4587,
|
||||
0xece17676c57697b3, 0x19e68282328264b0, 0xb128d6d67fd6fea9, 0x36c31b1b6c1bd877,
|
||||
0x7774b5b5eeb5c15b, 0x43beafaf86af1129, 0xd41d6a6ab56a77df, 0xa0ea50505d50ba0d,
|
||||
0x8a5745450945124c, 0xfb38f3f3ebf3cb18, 0x60ad3030c0309df0, 0xc3c4efef9bef2b74,
|
||||
0x7eda3f3ffc3fe5c3, 0xaac755554955921c, 0x59dba2a2b2a27910, 0xc9e9eaea8fea0365,
|
||||
0xca6a656589650fec, 0x6903babad2bab968, 0x5e4a2f2fbc2f6593, 0x9d8ec0c027c04ee7,
|
||||
0xa160dede5fdebe81, 0x38fc1c1c701ce06c, 0xe746fdfdd3fdbb2e, 0x9a1f4d4d294d5264,
|
||||
0x397692927292e4e0, 0xeafa7575c9758fbc, 0x0c3606061806301e, 0x09ae8a8a128a2498,
|
||||
0x794bb2b2f2b2f940, 0xd185e6e6bfe66359, 0x1c7e0e0e380e7036, 0x3ee71f1f7c1ff863,
|
||||
0xc4556262956237f7, 0xb53ad4d477d4eea3, 0x4d81a8a89aa82932, 0x315296966296c4f4,
|
||||
0xef62f9f9c3f99b3a, 0x97a3c5c533c566f6, 0x4a102525942535b1, 0xb2ab59597959f220,
|
||||
0x15d084842a8454ae, 0xe4c57272d572b7a7, 0x72ec3939e439d5dd, 0x98164c4c2d4c5a61,
|
||||
0xbc945e5e655eca3b, 0xf09f7878fd78e785, 0x70e53838e038ddd8, 0x05988c8c0a8c1486,
|
||||
0xbf17d1d163d1c6b2, 0x57e4a5a5aea5410b, 0xd9a1e2e2afe2434d, 0xc24e616199612ff8,
|
||||
0x7b42b3b3f6b3f145, 0x42342121842115a5, 0x25089c9c4a9c94d6, 0x3cee1e1e781ef066,
|
||||
0x8661434311432252, 0x93b1c7c73bc776fc, 0xe54ffcfcd7fcb32b, 0x0824040410042014,
|
||||
0xa2e351515951b208, 0x2f2599995e99bcc7, 0xda226d6da96d4fc4, 0x1a650d0d340d6839,
|
||||
0xe979fafacffa8335, 0xa369dfdf5bdfb684, 0xfca97e7ee57ed79b, 0x4819242490243db4,
|
||||
0x76fe3b3bec3bc5d7, 0x4b9aabab96ab313d, 0x81f0cece1fce3ed1, 0x2299111144118855,
|
||||
0x03838f8f068f0c89, 0x9c044e4e254e4a6b, 0x7366b7b7e6b7d151, 0xcbe0ebeb8beb0b60,
|
||||
0x78c13c3cf03cfdcc, 0x1ffd81813e817cbf, 0x354094946a94d4fe, 0xf31cf7f7fbf7eb0c,
|
||||
0x6f18b9b9deb9a167, 0x268b13134c13985f, 0x58512c2cb02c7d9c, 0xbb05d3d36bd3d6b8,
|
||||
0xd38ce7e7bbe76b5c, 0xdc396e6ea56e57cb, 0x95aac4c437c46ef3, 0x061b03030c03180f,
|
||||
0xacdc565645568a13, 0x885e44440d441a49, 0xfea07f7fe17fdf9e, 0x4f88a9a99ea92137,
|
||||
0x54672a2aa82a4d82, 0x6b0abbbbd6bbb16d, 0x9f87c1c123c146e2, 0xa6f153535153a202,
|
||||
0xa572dcdc57dcae8b, 0x16530b0b2c0b5827, 0x27019d9d4e9d9cd3, 0xd82b6c6cad6c47c1,
|
||||
0x62a43131c43195f5, 0xe8f37474cd7487b9, 0xf115f6f6fff6e309, 0x8c4c464605460a43,
|
||||
0x45a5acac8aac0926, 0x0fb589891e893c97, 0x28b414145014a044, 0xdfbae1e1a3e15b42,
|
||||
0x2ca616165816b04e, 0x74f73a3ae83acdd2, 0xd2066969b9696fd0, 0x124109092409482d,
|
||||
0xe0d77070dd70a7ad, 0x716fb6b6e2b6d954, 0xbd1ed0d067d0ceb7, 0xc7d6eded93ed3b7e,
|
||||
0x85e2cccc17cc2edb, 0x8468424215422a57, 0x2d2c98985a98b4c2, 0x55eda4a4aaa4490e,
|
||||
0x50752828a0285d88, 0xb8865c5c6d5cda31, 0xed6bf8f8c7f8933f, 0x11c28686228644a4,
|
||||
|
||||
// C3
|
||||
0x7830d818186018c0, 0xaf462623238c2305, 0xf991b8c6c63fc67e, 0x6fcdfbe8e887e813,
|
||||
0xa113cb878726874c, 0x626d11b8b8dab8a9, 0x0502090101040108, 0x6e9e0d4f4f214f42,
|
||||
0xee6c9b3636d836ad, 0x0451ffa6a6a2a659, 0xbdb90cd2d26fd2de, 0x06f70ef5f5f3f5fb,
|
||||
0x80f2967979f979ef, 0xcede306f6fa16f5f, 0xef3f6d91917e91fc, 0x07a4f852525552aa,
|
||||
0xfdc04760609d6027, 0x766535bcbccabc89, 0xcd2b379b9b569bac, 0x8c018a8e8e028e04,
|
||||
0x155bd2a3a3b6a371, 0x3c186c0c0c300c60, 0x8af6847b7bf17bff, 0xe16a803535d435b5,
|
||||
0x693af51d1d741de8, 0x47ddb3e0e0a7e053, 0xacb321d7d77bd7f6, 0xed999cc2c22fc25e,
|
||||
0x965c432e2eb82e6d, 0x7a96294b4b314b62, 0x21e15dfefedffea3, 0x16aed55757415782,
|
||||
0x412abd15155415a8, 0xb6eee87777c1779f, 0xeb6e923737dc37a5, 0x56d79ee5e5b3e57b,
|
||||
0xd923139f9f469f8c, 0x17fd23f0f0e7f0d3, 0x7f94204a4a354a6a, 0x95a944dada4fda9e,
|
||||
0x25b0a258587d58fa, 0xca8fcfc9c903c906, 0x8d527c2929a42955, 0x22145a0a0a280a50,
|
||||
0x4f7f50b1b1feb1e1, 0x1a5dc9a0a0baa069, 0xdad6146b6bb16b7f, 0xab17d985852e855c,
|
||||
0x73673cbdbdcebd81, 0x34ba8f5d5d695dd2, 0x5020901010401080, 0x03f507f4f4f7f4f3,
|
||||
0xc08bddcbcb0bcb16, 0xc67cd33e3ef83eed, 0x110a2d0505140528, 0xe6ce78676781671f,
|
||||
0x53d597e4e4b7e473, 0xbb4e0227279c2725, 0x5882734141194132, 0x9d0ba78b8b168b2c,
|
||||
0x0153f6a7a7a6a751, 0x94fab27d7de97dcf, 0xfb374995956e95dc, 0x9fad56d8d847d88e,
|
||||
0x30eb70fbfbcbfb8b, 0x71c1cdeeee9fee23, 0x91f8bb7c7ced7cc7, 0xe3cc716666856617,
|
||||
0x8ea77bdddd53dda6, 0x4b2eaf17175c17b8, 0x468e454747014702, 0xdc211a9e9e429e84,
|
||||
0xc589d4caca0fca1e, 0x995a582d2db42d75, 0x79632ebfbfc6bf91, 0x1b0e3f07071c0738,
|
||||
0x2347acadad8ead01, 0x2fb4b05a5a755aea, 0xb51bef838336836c, 0xff66b63333cc3385,
|
||||
0xf2c65c636391633f, 0x0a04120202080210, 0x384993aaaa92aa39, 0xa8e2de7171d971af,
|
||||
0xcf8dc6c8c807c80e, 0x7d32d119196419c8, 0x70923b4949394972, 0x9aaf5fd9d943d986,
|
||||
0x1df931f2f2eff2c3, 0x48dba8e3e3abe34b, 0x2ab6b95b5b715be2, 0x920dbc88881a8834,
|
||||
0xc8293e9a9a529aa4, 0xbe4c0b262698262d, 0xfa64bf3232c8328d, 0x4a7d59b0b0fab0e9,
|
||||
0x6acff2e9e983e91b, 0x331e770f0f3c0f78, 0xa6b733d5d573d5e6, 0xba1df480803a8074,
|
||||
0x7c6127bebec2be99, 0xde87ebcdcd13cd26, 0xe468893434d034bd, 0x75903248483d487a,
|
||||
0x24e354ffffdbffab, 0x8ff48d7a7af57af7, 0xea3d6490907a90f4, 0x3ebe9d5f5f615fc2,
|
||||
0xa0403d202080201d, 0xd5d00f6868bd6867, 0x7234ca1a1a681ad0, 0x2c41b7aeae82ae19,
|
||||
0x5e757db4b4eab4c9, 0x19a8ce54544d549a, 0xe53b7f93937693ec, 0xaa442f222288220d,
|
||||
0xe9c86364648d6407, 0x12ff2af1f1e3f1db, 0xa2e6cc7373d173bf, 0x5a24821212481290,
|
||||
0x5d807a40401d403a, 0x2810480808200840, 0xe89b95c3c32bc356, 0x7bc5dfecec97ec33,
|
||||
0x90ab4ddbdb4bdb96, 0x1f5fc0a1a1bea161, 0x8307918d8d0e8d1c, 0xc97ac83d3df43df5,
|
||||
0xf1335b97976697cc, 0x0000000000000000, 0xd483f9cfcf1bcf36, 0x87566e2b2bac2b45,
|
||||
0xb3ece17676c57697, 0xb019e68282328264, 0xa9b128d6d67fd6fe, 0x7736c31b1b6c1bd8,
|
||||
0x5b7774b5b5eeb5c1, 0x2943beafaf86af11, 0xdfd41d6a6ab56a77, 0x0da0ea50505d50ba,
|
||||
0x4c8a574545094512, 0x18fb38f3f3ebf3cb, 0xf060ad3030c0309d, 0x74c3c4efef9bef2b,
|
||||
0xc37eda3f3ffc3fe5, 0x1caac75555495592, 0x1059dba2a2b2a279, 0x65c9e9eaea8fea03,
|
||||
0xecca6a656589650f, 0x686903babad2bab9, 0x935e4a2f2fbc2f65, 0xe79d8ec0c027c04e,
|
||||
0x81a160dede5fdebe, 0x6c38fc1c1c701ce0, 0x2ee746fdfdd3fdbb, 0x649a1f4d4d294d52,
|
||||
0xe0397692927292e4, 0xbceafa7575c9758f, 0x1e0c360606180630, 0x9809ae8a8a128a24,
|
||||
0x40794bb2b2f2b2f9, 0x59d185e6e6bfe663, 0x361c7e0e0e380e70, 0x633ee71f1f7c1ff8,
|
||||
0xf7c4556262956237, 0xa3b53ad4d477d4ee, 0x324d81a8a89aa829, 0xf4315296966296c4,
|
||||
0x3aef62f9f9c3f99b, 0xf697a3c5c533c566, 0xb14a102525942535, 0x20b2ab59597959f2,
|
||||
0xae15d084842a8454, 0xa7e4c57272d572b7, 0xdd72ec3939e439d5, 0x6198164c4c2d4c5a,
|
||||
0x3bbc945e5e655eca, 0x85f09f7878fd78e7, 0xd870e53838e038dd, 0x8605988c8c0a8c14,
|
||||
0xb2bf17d1d163d1c6, 0x0b57e4a5a5aea541, 0x4dd9a1e2e2afe243, 0xf8c24e616199612f,
|
||||
0x457b42b3b3f6b3f1, 0xa542342121842115, 0xd625089c9c4a9c94, 0x663cee1e1e781ef0,
|
||||
0x5286614343114322, 0xfc93b1c7c73bc776, 0x2be54ffcfcd7fcb3, 0x1408240404100420,
|
||||
0x08a2e351515951b2, 0xc72f2599995e99bc, 0xc4da226d6da96d4f, 0x391a650d0d340d68,
|
||||
0x35e979fafacffa83, 0x84a369dfdf5bdfb6, 0x9bfca97e7ee57ed7, 0xb44819242490243d,
|
||||
0xd776fe3b3bec3bc5, 0x3d4b9aabab96ab31, 0xd181f0cece1fce3e, 0x5522991111441188,
|
||||
0x8903838f8f068f0c, 0x6b9c044e4e254e4a, 0x517366b7b7e6b7d1, 0x60cbe0ebeb8beb0b,
|
||||
0xcc78c13c3cf03cfd, 0xbf1ffd81813e817c, 0xfe354094946a94d4, 0x0cf31cf7f7fbf7eb,
|
||||
0x676f18b9b9deb9a1, 0x5f268b13134c1398, 0x9c58512c2cb02c7d, 0xb8bb05d3d36bd3d6,
|
||||
0x5cd38ce7e7bbe76b, 0xcbdc396e6ea56e57, 0xf395aac4c437c46e, 0x0f061b03030c0318,
|
||||
0x13acdc565645568a, 0x49885e44440d441a, 0x9efea07f7fe17fdf, 0x374f88a9a99ea921,
|
||||
0x8254672a2aa82a4d, 0x6d6b0abbbbd6bbb1, 0xe29f87c1c123c146, 0x02a6f153535153a2,
|
||||
0x8ba572dcdc57dcae, 0x2716530b0b2c0b58, 0xd327019d9d4e9d9c, 0xc1d82b6c6cad6c47,
|
||||
0xf562a43131c43195, 0xb9e8f37474cd7487, 0x09f115f6f6fff6e3, 0x438c4c464605460a,
|
||||
0x2645a5acac8aac09, 0x970fb589891e893c, 0x4428b414145014a0, 0x42dfbae1e1a3e15b,
|
||||
0x4e2ca616165816b0, 0xd274f73a3ae83acd, 0xd0d2066969b9696f, 0x2d12410909240948,
|
||||
0xade0d77070dd70a7, 0x54716fb6b6e2b6d9, 0xb7bd1ed0d067d0ce, 0x7ec7d6eded93ed3b,
|
||||
0xdb85e2cccc17cc2e, 0x578468424215422a, 0xc22d2c98985a98b4, 0x0e55eda4a4aaa449,
|
||||
0x8850752828a0285d, 0x31b8865c5c6d5cda, 0x3fed6bf8f8c7f893, 0xa411c28686228644,
|
||||
|
||||
// C4
|
||||
0xc07830d818186018, 0x05af462623238c23, 0x7ef991b8c6c63fc6, 0x136fcdfbe8e887e8,
|
||||
0x4ca113cb87872687, 0xa9626d11b8b8dab8, 0x0805020901010401, 0x426e9e0d4f4f214f,
|
||||
0xadee6c9b3636d836, 0x590451ffa6a6a2a6, 0xdebdb90cd2d26fd2, 0xfb06f70ef5f5f3f5,
|
||||
0xef80f2967979f979, 0x5fcede306f6fa16f, 0xfcef3f6d91917e91, 0xaa07a4f852525552,
|
||||
0x27fdc04760609d60, 0x89766535bcbccabc, 0xaccd2b379b9b569b, 0x048c018a8e8e028e,
|
||||
0x71155bd2a3a3b6a3, 0x603c186c0c0c300c, 0xff8af6847b7bf17b, 0xb5e16a803535d435,
|
||||
0xe8693af51d1d741d, 0x5347ddb3e0e0a7e0, 0xf6acb321d7d77bd7, 0x5eed999cc2c22fc2,
|
||||
0x6d965c432e2eb82e, 0x627a96294b4b314b, 0xa321e15dfefedffe, 0x8216aed557574157,
|
||||
0xa8412abd15155415, 0x9fb6eee87777c177, 0xa5eb6e923737dc37, 0x7b56d79ee5e5b3e5,
|
||||
0x8cd923139f9f469f, 0xd317fd23f0f0e7f0, 0x6a7f94204a4a354a, 0x9e95a944dada4fda,
|
||||
0xfa25b0a258587d58, 0x06ca8fcfc9c903c9, 0x558d527c2929a429, 0x5022145a0a0a280a,
|
||||
0xe14f7f50b1b1feb1, 0x691a5dc9a0a0baa0, 0x7fdad6146b6bb16b, 0x5cab17d985852e85,
|
||||
0x8173673cbdbdcebd, 0xd234ba8f5d5d695d, 0x8050209010104010, 0xf303f507f4f4f7f4,
|
||||
0x16c08bddcbcb0bcb, 0xedc67cd33e3ef83e, 0x28110a2d05051405, 0x1fe6ce7867678167,
|
||||
0x7353d597e4e4b7e4, 0x25bb4e0227279c27, 0x3258827341411941, 0x2c9d0ba78b8b168b,
|
||||
0x510153f6a7a7a6a7, 0xcf94fab27d7de97d, 0xdcfb374995956e95, 0x8e9fad56d8d847d8,
|
||||
0x8b30eb70fbfbcbfb, 0x2371c1cdeeee9fee, 0xc791f8bb7c7ced7c, 0x17e3cc7166668566,
|
||||
0xa68ea77bdddd53dd, 0xb84b2eaf17175c17, 0x02468e4547470147, 0x84dc211a9e9e429e,
|
||||
0x1ec589d4caca0fca, 0x75995a582d2db42d, 0x9179632ebfbfc6bf, 0x381b0e3f07071c07,
|
||||
0x012347acadad8ead, 0xea2fb4b05a5a755a, 0x6cb51bef83833683, 0x85ff66b63333cc33,
|
||||
0x3ff2c65c63639163, 0x100a041202020802, 0x39384993aaaa92aa, 0xafa8e2de7171d971,
|
||||
0x0ecf8dc6c8c807c8, 0xc87d32d119196419, 0x7270923b49493949, 0x869aaf5fd9d943d9,
|
||||
0xc31df931f2f2eff2, 0x4b48dba8e3e3abe3, 0xe22ab6b95b5b715b, 0x34920dbc88881a88,
|
||||
0xa4c8293e9a9a529a, 0x2dbe4c0b26269826, 0x8dfa64bf3232c832, 0xe94a7d59b0b0fab0,
|
||||
0x1b6acff2e9e983e9, 0x78331e770f0f3c0f, 0xe6a6b733d5d573d5, 0x74ba1df480803a80,
|
||||
0x997c6127bebec2be, 0x26de87ebcdcd13cd, 0xbde468893434d034, 0x7a75903248483d48,
|
||||
0xab24e354ffffdbff, 0xf78ff48d7a7af57a, 0xf4ea3d6490907a90, 0xc23ebe9d5f5f615f,
|
||||
0x1da0403d20208020, 0x67d5d00f6868bd68, 0xd07234ca1a1a681a, 0x192c41b7aeae82ae,
|
||||
0xc95e757db4b4eab4, 0x9a19a8ce54544d54, 0xece53b7f93937693, 0x0daa442f22228822,
|
||||
0x07e9c86364648d64, 0xdb12ff2af1f1e3f1, 0xbfa2e6cc7373d173, 0x905a248212124812,
|
||||
0x3a5d807a40401d40, 0x4028104808082008, 0x56e89b95c3c32bc3, 0x337bc5dfecec97ec,
|
||||
0x9690ab4ddbdb4bdb, 0x611f5fc0a1a1bea1, 0x1c8307918d8d0e8d, 0xf5c97ac83d3df43d,
|
||||
0xccf1335b97976697, 0x0000000000000000, 0x36d483f9cfcf1bcf, 0x4587566e2b2bac2b,
|
||||
0x97b3ece17676c576, 0x64b019e682823282, 0xfea9b128d6d67fd6, 0xd87736c31b1b6c1b,
|
||||
0xc15b7774b5b5eeb5, 0x112943beafaf86af, 0x77dfd41d6a6ab56a, 0xba0da0ea50505d50,
|
||||
0x124c8a5745450945, 0xcb18fb38f3f3ebf3, 0x9df060ad3030c030, 0x2b74c3c4efef9bef,
|
||||
0xe5c37eda3f3ffc3f, 0x921caac755554955, 0x791059dba2a2b2a2, 0x0365c9e9eaea8fea,
|
||||
0x0fecca6a65658965, 0xb9686903babad2ba, 0x65935e4a2f2fbc2f, 0x4ee79d8ec0c027c0,
|
||||
0xbe81a160dede5fde, 0xe06c38fc1c1c701c, 0xbb2ee746fdfdd3fd, 0x52649a1f4d4d294d,
|
||||
0xe4e0397692927292, 0x8fbceafa7575c975, 0x301e0c3606061806, 0x249809ae8a8a128a,
|
||||
0xf940794bb2b2f2b2, 0x6359d185e6e6bfe6, 0x70361c7e0e0e380e, 0xf8633ee71f1f7c1f,
|
||||
0x37f7c45562629562, 0xeea3b53ad4d477d4, 0x29324d81a8a89aa8, 0xc4f4315296966296,
|
||||
0x9b3aef62f9f9c3f9, 0x66f697a3c5c533c5, 0x35b14a1025259425, 0xf220b2ab59597959,
|
||||
0x54ae15d084842a84, 0xb7a7e4c57272d572, 0xd5dd72ec3939e439, 0x5a6198164c4c2d4c,
|
||||
0xca3bbc945e5e655e, 0xe785f09f7878fd78, 0xddd870e53838e038, 0x148605988c8c0a8c,
|
||||
0xc6b2bf17d1d163d1, 0x410b57e4a5a5aea5, 0x434dd9a1e2e2afe2, 0x2ff8c24e61619961,
|
||||
0xf1457b42b3b3f6b3, 0x15a5423421218421, 0x94d625089c9c4a9c, 0xf0663cee1e1e781e,
|
||||
0x2252866143431143, 0x76fc93b1c7c73bc7, 0xb32be54ffcfcd7fc, 0x2014082404041004,
|
||||
0xb208a2e351515951, 0xbcc72f2599995e99, 0x4fc4da226d6da96d, 0x68391a650d0d340d,
|
||||
0x8335e979fafacffa, 0xb684a369dfdf5bdf, 0xd79bfca97e7ee57e, 0x3db4481924249024,
|
||||
0xc5d776fe3b3bec3b, 0x313d4b9aabab96ab, 0x3ed181f0cece1fce, 0x8855229911114411,
|
||||
0x0c8903838f8f068f, 0x4a6b9c044e4e254e, 0xd1517366b7b7e6b7, 0x0b60cbe0ebeb8beb,
|
||||
0xfdcc78c13c3cf03c, 0x7cbf1ffd81813e81, 0xd4fe354094946a94, 0xeb0cf31cf7f7fbf7,
|
||||
0xa1676f18b9b9deb9, 0x985f268b13134c13, 0x7d9c58512c2cb02c, 0xd6b8bb05d3d36bd3,
|
||||
0x6b5cd38ce7e7bbe7, 0x57cbdc396e6ea56e, 0x6ef395aac4c437c4, 0x180f061b03030c03,
|
||||
0x8a13acdc56564556, 0x1a49885e44440d44, 0xdf9efea07f7fe17f, 0x21374f88a9a99ea9,
|
||||
0x4d8254672a2aa82a, 0xb16d6b0abbbbd6bb, 0x46e29f87c1c123c1, 0xa202a6f153535153,
|
||||
0xae8ba572dcdc57dc, 0x582716530b0b2c0b, 0x9cd327019d9d4e9d, 0x47c1d82b6c6cad6c,
|
||||
0x95f562a43131c431, 0x87b9e8f37474cd74, 0xe309f115f6f6fff6, 0x0a438c4c46460546,
|
||||
0x092645a5acac8aac, 0x3c970fb589891e89, 0xa04428b414145014, 0x5b42dfbae1e1a3e1,
|
||||
0xb04e2ca616165816, 0xcdd274f73a3ae83a, 0x6fd0d2066969b969, 0x482d124109092409,
|
||||
0xa7ade0d77070dd70, 0xd954716fb6b6e2b6, 0xceb7bd1ed0d067d0, 0x3b7ec7d6eded93ed,
|
||||
0x2edb85e2cccc17cc, 0x2a57846842421542, 0xb4c22d2c98985a98, 0x490e55eda4a4aaa4,
|
||||
0x5d8850752828a028, 0xda31b8865c5c6d5c, 0x933fed6bf8f8c7f8, 0x44a411c286862286,
|
||||
|
||||
// C5
|
||||
0x18c07830d8181860, 0x2305af462623238c, 0xc67ef991b8c6c63f, 0xe8136fcdfbe8e887,
|
||||
0x874ca113cb878726, 0xb8a9626d11b8b8da, 0x0108050209010104, 0x4f426e9e0d4f4f21,
|
||||
0x36adee6c9b3636d8, 0xa6590451ffa6a6a2, 0xd2debdb90cd2d26f, 0xf5fb06f70ef5f5f3,
|
||||
0x79ef80f2967979f9, 0x6f5fcede306f6fa1, 0x91fcef3f6d91917e, 0x52aa07a4f8525255,
|
||||
0x6027fdc04760609d, 0xbc89766535bcbcca, 0x9baccd2b379b9b56, 0x8e048c018a8e8e02,
|
||||
0xa371155bd2a3a3b6, 0x0c603c186c0c0c30, 0x7bff8af6847b7bf1, 0x35b5e16a803535d4,
|
||||
0x1de8693af51d1d74, 0xe05347ddb3e0e0a7, 0xd7f6acb321d7d77b, 0xc25eed999cc2c22f,
|
||||
0x2e6d965c432e2eb8, 0x4b627a96294b4b31, 0xfea321e15dfefedf, 0x578216aed5575741,
|
||||
0x15a8412abd151554, 0x779fb6eee87777c1, 0x37a5eb6e923737dc, 0xe57b56d79ee5e5b3,
|
||||
0x9f8cd923139f9f46, 0xf0d317fd23f0f0e7, 0x4a6a7f94204a4a35, 0xda9e95a944dada4f,
|
||||
0x58fa25b0a258587d, 0xc906ca8fcfc9c903, 0x29558d527c2929a4, 0x0a5022145a0a0a28,
|
||||
0xb1e14f7f50b1b1fe, 0xa0691a5dc9a0a0ba, 0x6b7fdad6146b6bb1, 0x855cab17d985852e,
|
||||
0xbd8173673cbdbdce, 0x5dd234ba8f5d5d69, 0x1080502090101040, 0xf4f303f507f4f4f7,
|
||||
0xcb16c08bddcbcb0b, 0x3eedc67cd33e3ef8, 0x0528110a2d050514, 0x671fe6ce78676781,
|
||||
0xe47353d597e4e4b7, 0x2725bb4e0227279c, 0x4132588273414119, 0x8b2c9d0ba78b8b16,
|
||||
0xa7510153f6a7a7a6, 0x7dcf94fab27d7de9, 0x95dcfb374995956e, 0xd88e9fad56d8d847,
|
||||
0xfb8b30eb70fbfbcb, 0xee2371c1cdeeee9f, 0x7cc791f8bb7c7ced, 0x6617e3cc71666685,
|
||||
0xdda68ea77bdddd53, 0x17b84b2eaf17175c, 0x4702468e45474701, 0x9e84dc211a9e9e42,
|
||||
0xca1ec589d4caca0f, 0x2d75995a582d2db4, 0xbf9179632ebfbfc6, 0x07381b0e3f07071c,
|
||||
0xad012347acadad8e, 0x5aea2fb4b05a5a75, 0x836cb51bef838336, 0x3385ff66b63333cc,
|
||||
0x633ff2c65c636391, 0x02100a0412020208, 0xaa39384993aaaa92, 0x71afa8e2de7171d9,
|
||||
0xc80ecf8dc6c8c807, 0x19c87d32d1191964, 0x497270923b494939, 0xd9869aaf5fd9d943,
|
||||
0xf2c31df931f2f2ef, 0xe34b48dba8e3e3ab, 0x5be22ab6b95b5b71, 0x8834920dbc88881a,
|
||||
0x9aa4c8293e9a9a52, 0x262dbe4c0b262698, 0x328dfa64bf3232c8, 0xb0e94a7d59b0b0fa,
|
||||
0xe91b6acff2e9e983, 0x0f78331e770f0f3c, 0xd5e6a6b733d5d573, 0x8074ba1df480803a,
|
||||
0xbe997c6127bebec2, 0xcd26de87ebcdcd13, 0x34bde468893434d0, 0x487a75903248483d,
|
||||
0xffab24e354ffffdb, 0x7af78ff48d7a7af5, 0x90f4ea3d6490907a, 0x5fc23ebe9d5f5f61,
|
||||
0x201da0403d202080, 0x6867d5d00f6868bd, 0x1ad07234ca1a1a68, 0xae192c41b7aeae82,
|
||||
0xb4c95e757db4b4ea, 0x549a19a8ce54544d, 0x93ece53b7f939376, 0x220daa442f222288,
|
||||
0x6407e9c86364648d, 0xf1db12ff2af1f1e3, 0x73bfa2e6cc7373d1, 0x12905a2482121248,
|
||||
0x403a5d807a40401d, 0x0840281048080820, 0xc356e89b95c3c32b, 0xec337bc5dfecec97,
|
||||
0xdb9690ab4ddbdb4b, 0xa1611f5fc0a1a1be, 0x8d1c8307918d8d0e, 0x3df5c97ac83d3df4,
|
||||
0x97ccf1335b979766, 0x0000000000000000, 0xcf36d483f9cfcf1b, 0x2b4587566e2b2bac,
|
||||
0x7697b3ece17676c5, 0x8264b019e6828232, 0xd6fea9b128d6d67f, 0x1bd87736c31b1b6c,
|
||||
0xb5c15b7774b5b5ee, 0xaf112943beafaf86, 0x6a77dfd41d6a6ab5, 0x50ba0da0ea50505d,
|
||||
0x45124c8a57454509, 0xf3cb18fb38f3f3eb, 0x309df060ad3030c0, 0xef2b74c3c4efef9b,
|
||||
0x3fe5c37eda3f3ffc, 0x55921caac7555549, 0xa2791059dba2a2b2, 0xea0365c9e9eaea8f,
|
||||
0x650fecca6a656589, 0xbab9686903babad2, 0x2f65935e4a2f2fbc, 0xc04ee79d8ec0c027,
|
||||
0xdebe81a160dede5f, 0x1ce06c38fc1c1c70, 0xfdbb2ee746fdfdd3, 0x4d52649a1f4d4d29,
|
||||
0x92e4e03976929272, 0x758fbceafa7575c9, 0x06301e0c36060618, 0x8a249809ae8a8a12,
|
||||
0xb2f940794bb2b2f2, 0xe66359d185e6e6bf, 0x0e70361c7e0e0e38, 0x1ff8633ee71f1f7c,
|
||||
0x6237f7c455626295, 0xd4eea3b53ad4d477, 0xa829324d81a8a89a, 0x96c4f43152969662,
|
||||
0xf99b3aef62f9f9c3, 0xc566f697a3c5c533, 0x2535b14a10252594, 0x59f220b2ab595979,
|
||||
0x8454ae15d084842a, 0x72b7a7e4c57272d5, 0x39d5dd72ec3939e4, 0x4c5a6198164c4c2d,
|
||||
0x5eca3bbc945e5e65, 0x78e785f09f7878fd, 0x38ddd870e53838e0, 0x8c148605988c8c0a,
|
||||
0xd1c6b2bf17d1d163, 0xa5410b57e4a5a5ae, 0xe2434dd9a1e2e2af, 0x612ff8c24e616199,
|
||||
0xb3f1457b42b3b3f6, 0x2115a54234212184, 0x9c94d625089c9c4a, 0x1ef0663cee1e1e78,
|
||||
0x4322528661434311, 0xc776fc93b1c7c73b, 0xfcb32be54ffcfcd7, 0x0420140824040410,
|
||||
0x51b208a2e3515159, 0x99bcc72f2599995e, 0x6d4fc4da226d6da9, 0x0d68391a650d0d34,
|
||||
0xfa8335e979fafacf, 0xdfb684a369dfdf5b, 0x7ed79bfca97e7ee5, 0x243db44819242490,
|
||||
0x3bc5d776fe3b3bec, 0xab313d4b9aabab96, 0xce3ed181f0cece1f, 0x1188552299111144,
|
||||
0x8f0c8903838f8f06, 0x4e4a6b9c044e4e25, 0xb7d1517366b7b7e6, 0xeb0b60cbe0ebeb8b,
|
||||
0x3cfdcc78c13c3cf0, 0x817cbf1ffd81813e, 0x94d4fe354094946a, 0xf7eb0cf31cf7f7fb,
|
||||
0xb9a1676f18b9b9de, 0x13985f268b13134c, 0x2c7d9c58512c2cb0, 0xd3d6b8bb05d3d36b,
|
||||
0xe76b5cd38ce7e7bb, 0x6e57cbdc396e6ea5, 0xc46ef395aac4c437, 0x03180f061b03030c,
|
||||
0x568a13acdc565645, 0x441a49885e44440d, 0x7fdf9efea07f7fe1, 0xa921374f88a9a99e,
|
||||
0x2a4d8254672a2aa8, 0xbbb16d6b0abbbbd6, 0xc146e29f87c1c123, 0x53a202a6f1535351,
|
||||
0xdcae8ba572dcdc57, 0x0b582716530b0b2c, 0x9d9cd327019d9d4e, 0x6c47c1d82b6c6cad,
|
||||
0x3195f562a43131c4, 0x7487b9e8f37474cd, 0xf6e309f115f6f6ff, 0x460a438c4c464605,
|
||||
0xac092645a5acac8a, 0x893c970fb589891e, 0x14a04428b4141450, 0xe15b42dfbae1e1a3,
|
||||
0x16b04e2ca6161658, 0x3acdd274f73a3ae8, 0x696fd0d2066969b9, 0x09482d1241090924,
|
||||
0x70a7ade0d77070dd, 0xb6d954716fb6b6e2, 0xd0ceb7bd1ed0d067, 0xed3b7ec7d6eded93,
|
||||
0xcc2edb85e2cccc17, 0x422a578468424215, 0x98b4c22d2c98985a, 0xa4490e55eda4a4aa,
|
||||
0x285d8850752828a0, 0x5cda31b8865c5c6d, 0xf8933fed6bf8f8c7, 0x8644a411c2868622,
|
||||
|
||||
// C6
|
||||
0x6018c07830d81818, 0x8c2305af46262323, 0x3fc67ef991b8c6c6, 0x87e8136fcdfbe8e8,
|
||||
0x26874ca113cb8787, 0xdab8a9626d11b8b8, 0x0401080502090101, 0x214f426e9e0d4f4f,
|
||||
0xd836adee6c9b3636, 0xa2a6590451ffa6a6, 0x6fd2debdb90cd2d2, 0xf3f5fb06f70ef5f5,
|
||||
0xf979ef80f2967979, 0xa16f5fcede306f6f, 0x7e91fcef3f6d9191, 0x5552aa07a4f85252,
|
||||
0x9d6027fdc0476060, 0xcabc89766535bcbc, 0x569baccd2b379b9b, 0x028e048c018a8e8e,
|
||||
0xb6a371155bd2a3a3, 0x300c603c186c0c0c, 0xf17bff8af6847b7b, 0xd435b5e16a803535,
|
||||
0x741de8693af51d1d, 0xa7e05347ddb3e0e0, 0x7bd7f6acb321d7d7, 0x2fc25eed999cc2c2,
|
||||
0xb82e6d965c432e2e, 0x314b627a96294b4b, 0xdffea321e15dfefe, 0x41578216aed55757,
|
||||
0x5415a8412abd1515, 0xc1779fb6eee87777, 0xdc37a5eb6e923737, 0xb3e57b56d79ee5e5,
|
||||
0x469f8cd923139f9f, 0xe7f0d317fd23f0f0, 0x354a6a7f94204a4a, 0x4fda9e95a944dada,
|
||||
0x7d58fa25b0a25858, 0x03c906ca8fcfc9c9, 0xa429558d527c2929, 0x280a5022145a0a0a,
|
||||
0xfeb1e14f7f50b1b1, 0xbaa0691a5dc9a0a0, 0xb16b7fdad6146b6b, 0x2e855cab17d98585,
|
||||
0xcebd8173673cbdbd, 0x695dd234ba8f5d5d, 0x4010805020901010, 0xf7f4f303f507f4f4,
|
||||
0x0bcb16c08bddcbcb, 0xf83eedc67cd33e3e, 0x140528110a2d0505, 0x81671fe6ce786767,
|
||||
0xb7e47353d597e4e4, 0x9c2725bb4e022727, 0x1941325882734141, 0x168b2c9d0ba78b8b,
|
||||
0xa6a7510153f6a7a7, 0xe97dcf94fab27d7d, 0x6e95dcfb37499595, 0x47d88e9fad56d8d8,
|
||||
0xcbfb8b30eb70fbfb, 0x9fee2371c1cdeeee, 0xed7cc791f8bb7c7c, 0x856617e3cc716666,
|
||||
0x53dda68ea77bdddd, 0x5c17b84b2eaf1717, 0x014702468e454747, 0x429e84dc211a9e9e,
|
||||
0x0fca1ec589d4caca, 0xb42d75995a582d2d, 0xc6bf9179632ebfbf, 0x1c07381b0e3f0707,
|
||||
0x8ead012347acadad, 0x755aea2fb4b05a5a, 0x36836cb51bef8383, 0xcc3385ff66b63333,
|
||||
0x91633ff2c65c6363, 0x0802100a04120202, 0x92aa39384993aaaa, 0xd971afa8e2de7171,
|
||||
0x07c80ecf8dc6c8c8, 0x6419c87d32d11919, 0x39497270923b4949, 0x43d9869aaf5fd9d9,
|
||||
0xeff2c31df931f2f2, 0xabe34b48dba8e3e3, 0x715be22ab6b95b5b, 0x1a8834920dbc8888,
|
||||
0x529aa4c8293e9a9a, 0x98262dbe4c0b2626, 0xc8328dfa64bf3232, 0xfab0e94a7d59b0b0,
|
||||
0x83e91b6acff2e9e9, 0x3c0f78331e770f0f, 0x73d5e6a6b733d5d5, 0x3a8074ba1df48080,
|
||||
0xc2be997c6127bebe, 0x13cd26de87ebcdcd, 0xd034bde468893434, 0x3d487a7590324848,
|
||||
0xdbffab24e354ffff, 0xf57af78ff48d7a7a, 0x7a90f4ea3d649090, 0x615fc23ebe9d5f5f,
|
||||
0x80201da0403d2020, 0xbd6867d5d00f6868, 0x681ad07234ca1a1a, 0x82ae192c41b7aeae,
|
||||
0xeab4c95e757db4b4, 0x4d549a19a8ce5454, 0x7693ece53b7f9393, 0x88220daa442f2222,
|
||||
0x8d6407e9c8636464, 0xe3f1db12ff2af1f1, 0xd173bfa2e6cc7373, 0x4812905a24821212,
|
||||
0x1d403a5d807a4040, 0x2008402810480808, 0x2bc356e89b95c3c3, 0x97ec337bc5dfecec,
|
||||
0x4bdb9690ab4ddbdb, 0xbea1611f5fc0a1a1, 0x0e8d1c8307918d8d, 0xf43df5c97ac83d3d,
|
||||
0x6697ccf1335b9797, 0x0000000000000000, 0x1bcf36d483f9cfcf, 0xac2b4587566e2b2b,
|
||||
0xc57697b3ece17676, 0x328264b019e68282, 0x7fd6fea9b128d6d6, 0x6c1bd87736c31b1b,
|
||||
0xeeb5c15b7774b5b5, 0x86af112943beafaf, 0xb56a77dfd41d6a6a, 0x5d50ba0da0ea5050,
|
||||
0x0945124c8a574545, 0xebf3cb18fb38f3f3, 0xc0309df060ad3030, 0x9bef2b74c3c4efef,
|
||||
0xfc3fe5c37eda3f3f, 0x4955921caac75555, 0xb2a2791059dba2a2, 0x8fea0365c9e9eaea,
|
||||
0x89650fecca6a6565, 0xd2bab9686903baba, 0xbc2f65935e4a2f2f, 0x27c04ee79d8ec0c0,
|
||||
0x5fdebe81a160dede, 0x701ce06c38fc1c1c, 0xd3fdbb2ee746fdfd, 0x294d52649a1f4d4d,
|
||||
0x7292e4e039769292, 0xc9758fbceafa7575, 0x1806301e0c360606, 0x128a249809ae8a8a,
|
||||
0xf2b2f940794bb2b2, 0xbfe66359d185e6e6, 0x380e70361c7e0e0e, 0x7c1ff8633ee71f1f,
|
||||
0x956237f7c4556262, 0x77d4eea3b53ad4d4, 0x9aa829324d81a8a8, 0x6296c4f431529696,
|
||||
0xc3f99b3aef62f9f9, 0x33c566f697a3c5c5, 0x942535b14a102525, 0x7959f220b2ab5959,
|
||||
0x2a8454ae15d08484, 0xd572b7a7e4c57272, 0xe439d5dd72ec3939, 0x2d4c5a6198164c4c,
|
||||
0x655eca3bbc945e5e, 0xfd78e785f09f7878, 0xe038ddd870e53838, 0x0a8c148605988c8c,
|
||||
0x63d1c6b2bf17d1d1, 0xaea5410b57e4a5a5, 0xafe2434dd9a1e2e2, 0x99612ff8c24e6161,
|
||||
0xf6b3f1457b42b3b3, 0x842115a542342121, 0x4a9c94d625089c9c, 0x781ef0663cee1e1e,
|
||||
0x1143225286614343, 0x3bc776fc93b1c7c7, 0xd7fcb32be54ffcfc, 0x1004201408240404,
|
||||
0x5951b208a2e35151, 0x5e99bcc72f259999, 0xa96d4fc4da226d6d, 0x340d68391a650d0d,
|
||||
0xcffa8335e979fafa, 0x5bdfb684a369dfdf, 0xe57ed79bfca97e7e, 0x90243db448192424,
|
||||
0xec3bc5d776fe3b3b, 0x96ab313d4b9aabab, 0x1fce3ed181f0cece, 0x4411885522991111,
|
||||
0x068f0c8903838f8f, 0x254e4a6b9c044e4e, 0xe6b7d1517366b7b7, 0x8beb0b60cbe0ebeb,
|
||||
0xf03cfdcc78c13c3c, 0x3e817cbf1ffd8181, 0x6a94d4fe35409494, 0xfbf7eb0cf31cf7f7,
|
||||
0xdeb9a1676f18b9b9, 0x4c13985f268b1313, 0xb02c7d9c58512c2c, 0x6bd3d6b8bb05d3d3,
|
||||
0xbbe76b5cd38ce7e7, 0xa56e57cbdc396e6e, 0x37c46ef395aac4c4, 0x0c03180f061b0303,
|
||||
0x45568a13acdc5656, 0x0d441a49885e4444, 0xe17fdf9efea07f7f, 0x9ea921374f88a9a9,
|
||||
0xa82a4d8254672a2a, 0xd6bbb16d6b0abbbb, 0x23c146e29f87c1c1, 0x5153a202a6f15353,
|
||||
0x57dcae8ba572dcdc, 0x2c0b582716530b0b, 0x4e9d9cd327019d9d, 0xad6c47c1d82b6c6c,
|
||||
0xc43195f562a43131, 0xcd7487b9e8f37474, 0xfff6e309f115f6f6, 0x05460a438c4c4646,
|
||||
0x8aac092645a5acac, 0x1e893c970fb58989, 0x5014a04428b41414, 0xa3e15b42dfbae1e1,
|
||||
0x5816b04e2ca61616, 0xe83acdd274f73a3a, 0xb9696fd0d2066969, 0x2409482d12410909,
|
||||
0xdd70a7ade0d77070, 0xe2b6d954716fb6b6, 0x67d0ceb7bd1ed0d0, 0x93ed3b7ec7d6eded,
|
||||
0x17cc2edb85e2cccc, 0x15422a5784684242, 0x5a98b4c22d2c9898, 0xaaa4490e55eda4a4,
|
||||
0xa0285d8850752828, 0x6d5cda31b8865c5c, 0xc7f8933fed6bf8f8, 0x228644a411c28686,
|
||||
|
||||
// C7
|
||||
0x186018c07830d818, 0x238c2305af462623, 0xc63fc67ef991b8c6, 0xe887e8136fcdfbe8,
|
||||
0x8726874ca113cb87, 0xb8dab8a9626d11b8, 0x0104010805020901, 0x4f214f426e9e0d4f,
|
||||
0x36d836adee6c9b36, 0xa6a2a6590451ffa6, 0xd26fd2debdb90cd2, 0xf5f3f5fb06f70ef5,
|
||||
0x79f979ef80f29679, 0x6fa16f5fcede306f, 0x917e91fcef3f6d91, 0x525552aa07a4f852,
|
||||
0x609d6027fdc04760, 0xbccabc89766535bc, 0x9b569baccd2b379b, 0x8e028e048c018a8e,
|
||||
0xa3b6a371155bd2a3, 0x0c300c603c186c0c, 0x7bf17bff8af6847b, 0x35d435b5e16a8035,
|
||||
0x1d741de8693af51d, 0xe0a7e05347ddb3e0, 0xd77bd7f6acb321d7, 0xc22fc25eed999cc2,
|
||||
0x2eb82e6d965c432e, 0x4b314b627a96294b, 0xfedffea321e15dfe, 0x5741578216aed557,
|
||||
0x155415a8412abd15, 0x77c1779fb6eee877, 0x37dc37a5eb6e9237, 0xe5b3e57b56d79ee5,
|
||||
0x9f469f8cd923139f, 0xf0e7f0d317fd23f0, 0x4a354a6a7f94204a, 0xda4fda9e95a944da,
|
||||
0x587d58fa25b0a258, 0xc903c906ca8fcfc9, 0x29a429558d527c29, 0x0a280a5022145a0a,
|
||||
0xb1feb1e14f7f50b1, 0xa0baa0691a5dc9a0, 0x6bb16b7fdad6146b, 0x852e855cab17d985,
|
||||
0xbdcebd8173673cbd, 0x5d695dd234ba8f5d, 0x1040108050209010, 0xf4f7f4f303f507f4,
|
||||
0xcb0bcb16c08bddcb, 0x3ef83eedc67cd33e, 0x05140528110a2d05, 0x6781671fe6ce7867,
|
||||
0xe4b7e47353d597e4, 0x279c2725bb4e0227, 0x4119413258827341, 0x8b168b2c9d0ba78b,
|
||||
0xa7a6a7510153f6a7, 0x7de97dcf94fab27d, 0x956e95dcfb374995, 0xd847d88e9fad56d8,
|
||||
0xfbcbfb8b30eb70fb, 0xee9fee2371c1cdee, 0x7ced7cc791f8bb7c, 0x66856617e3cc7166,
|
||||
0xdd53dda68ea77bdd, 0x175c17b84b2eaf17, 0x47014702468e4547, 0x9e429e84dc211a9e,
|
||||
0xca0fca1ec589d4ca, 0x2db42d75995a582d, 0xbfc6bf9179632ebf, 0x071c07381b0e3f07,
|
||||
0xad8ead012347acad, 0x5a755aea2fb4b05a, 0x8336836cb51bef83, 0x33cc3385ff66b633,
|
||||
0x6391633ff2c65c63, 0x020802100a041202, 0xaa92aa39384993aa, 0x71d971afa8e2de71,
|
||||
0xc807c80ecf8dc6c8, 0x196419c87d32d119, 0x4939497270923b49, 0xd943d9869aaf5fd9,
|
||||
0xf2eff2c31df931f2, 0xe3abe34b48dba8e3, 0x5b715be22ab6b95b, 0x881a8834920dbc88,
|
||||
0x9a529aa4c8293e9a, 0x2698262dbe4c0b26, 0x32c8328dfa64bf32, 0xb0fab0e94a7d59b0,
|
||||
0xe983e91b6acff2e9, 0x0f3c0f78331e770f, 0xd573d5e6a6b733d5, 0x803a8074ba1df480,
|
||||
0xbec2be997c6127be, 0xcd13cd26de87ebcd, 0x34d034bde4688934, 0x483d487a75903248,
|
||||
0xffdbffab24e354ff, 0x7af57af78ff48d7a, 0x907a90f4ea3d6490, 0x5f615fc23ebe9d5f,
|
||||
0x2080201da0403d20, 0x68bd6867d5d00f68, 0x1a681ad07234ca1a, 0xae82ae192c41b7ae,
|
||||
0xb4eab4c95e757db4, 0x544d549a19a8ce54, 0x937693ece53b7f93, 0x2288220daa442f22,
|
||||
0x648d6407e9c86364, 0xf1e3f1db12ff2af1, 0x73d173bfa2e6cc73, 0x124812905a248212,
|
||||
0x401d403a5d807a40, 0x0820084028104808, 0xc32bc356e89b95c3, 0xec97ec337bc5dfec,
|
||||
0xdb4bdb9690ab4ddb, 0xa1bea1611f5fc0a1, 0x8d0e8d1c8307918d, 0x3df43df5c97ac83d,
|
||||
0x976697ccf1335b97, 0x0000000000000000, 0xcf1bcf36d483f9cf, 0x2bac2b4587566e2b,
|
||||
0x76c57697b3ece176, 0x82328264b019e682, 0xd67fd6fea9b128d6, 0x1b6c1bd87736c31b,
|
||||
0xb5eeb5c15b7774b5, 0xaf86af112943beaf, 0x6ab56a77dfd41d6a, 0x505d50ba0da0ea50,
|
||||
0x450945124c8a5745, 0xf3ebf3cb18fb38f3, 0x30c0309df060ad30, 0xef9bef2b74c3c4ef,
|
||||
0x3ffc3fe5c37eda3f, 0x554955921caac755, 0xa2b2a2791059dba2, 0xea8fea0365c9e9ea,
|
||||
0x6589650fecca6a65, 0xbad2bab9686903ba, 0x2fbc2f65935e4a2f, 0xc027c04ee79d8ec0,
|
||||
0xde5fdebe81a160de, 0x1c701ce06c38fc1c, 0xfdd3fdbb2ee746fd, 0x4d294d52649a1f4d,
|
||||
0x927292e4e0397692, 0x75c9758fbceafa75, 0x061806301e0c3606, 0x8a128a249809ae8a,
|
||||
0xb2f2b2f940794bb2, 0xe6bfe66359d185e6, 0x0e380e70361c7e0e, 0x1f7c1ff8633ee71f,
|
||||
0x62956237f7c45562, 0xd477d4eea3b53ad4, 0xa89aa829324d81a8, 0x966296c4f4315296,
|
||||
0xf9c3f99b3aef62f9, 0xc533c566f697a3c5, 0x25942535b14a1025, 0x597959f220b2ab59,
|
||||
0x842a8454ae15d084, 0x72d572b7a7e4c572, 0x39e439d5dd72ec39, 0x4c2d4c5a6198164c,
|
||||
0x5e655eca3bbc945e, 0x78fd78e785f09f78, 0x38e038ddd870e538, 0x8c0a8c148605988c,
|
||||
0xd163d1c6b2bf17d1, 0xa5aea5410b57e4a5, 0xe2afe2434dd9a1e2, 0x6199612ff8c24e61,
|
||||
0xb3f6b3f1457b42b3, 0x21842115a5423421, 0x9c4a9c94d625089c, 0x1e781ef0663cee1e,
|
||||
0x4311432252866143, 0xc73bc776fc93b1c7, 0xfcd7fcb32be54ffc, 0x0410042014082404,
|
||||
0x515951b208a2e351, 0x995e99bcc72f2599, 0x6da96d4fc4da226d, 0x0d340d68391a650d,
|
||||
0xfacffa8335e979fa, 0xdf5bdfb684a369df, 0x7ee57ed79bfca97e, 0x2490243db4481924,
|
||||
0x3bec3bc5d776fe3b, 0xab96ab313d4b9aab, 0xce1fce3ed181f0ce, 0x1144118855229911,
|
||||
0x8f068f0c8903838f, 0x4e254e4a6b9c044e, 0xb7e6b7d1517366b7, 0xeb8beb0b60cbe0eb,
|
||||
0x3cf03cfdcc78c13c, 0x813e817cbf1ffd81, 0x946a94d4fe354094, 0xf7fbf7eb0cf31cf7,
|
||||
0xb9deb9a1676f18b9, 0x134c13985f268b13, 0x2cb02c7d9c58512c, 0xd36bd3d6b8bb05d3,
|
||||
0xe7bbe76b5cd38ce7, 0x6ea56e57cbdc396e, 0xc437c46ef395aac4, 0x030c03180f061b03,
|
||||
0x5645568a13acdc56, 0x440d441a49885e44, 0x7fe17fdf9efea07f, 0xa99ea921374f88a9,
|
||||
0x2aa82a4d8254672a, 0xbbd6bbb16d6b0abb, 0xc123c146e29f87c1, 0x535153a202a6f153,
|
||||
0xdc57dcae8ba572dc, 0x0b2c0b582716530b, 0x9d4e9d9cd327019d, 0x6cad6c47c1d82b6c,
|
||||
0x31c43195f562a431, 0x74cd7487b9e8f374, 0xf6fff6e309f115f6, 0x4605460a438c4c46,
|
||||
0xac8aac092645a5ac, 0x891e893c970fb589, 0x145014a04428b414, 0xe1a3e15b42dfbae1,
|
||||
0x165816b04e2ca616, 0x3ae83acdd274f73a, 0x69b9696fd0d20669, 0x092409482d124109,
|
||||
0x70dd70a7ade0d770, 0xb6e2b6d954716fb6, 0xd067d0ceb7bd1ed0, 0xed93ed3b7ec7d6ed,
|
||||
0xcc17cc2edb85e2cc, 0x4215422a57846842, 0x985a98b4c22d2c98, 0xa4aaa4490e55eda4,
|
||||
0x28a0285d88507528, 0x5c6d5cda31b8865c, 0xf8c7f8933fed6bf8, 0x86228644a411c286,
|
||||
};
|
||||
56
lib/std/hash/wyhash2.c3
Normal file
56
lib/std/hash/wyhash2.c3
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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.
|
||||
//
|
||||
// An implementation of Wang Yi's wyhash(2) algorithm in C3:
|
||||
// https://github.com/wangyi-fudan/wyhash
|
||||
//
|
||||
module std::hash::wyhash2;
|
||||
|
||||
|
||||
fn ulong wyr3(char* in, usz len) @inline
|
||||
=> ((ulong)in[0] << 16) | ((ulong)in[len >> 1] << 8) | (ulong)in[len - 1];
|
||||
|
||||
|
||||
// See: https://docs.google.com/spreadsheets/d/1HmqDj-suH4wBFNg7etwE8WVBlfCufvD5-gAnIENs94k/edit?gid=1915335726#gid=1915335726
|
||||
// Credit to article:
|
||||
// https://medium.com/@tprodanov/benchmarking-non-cryptographic-hash-functions-in-rust-2e6091077d11
|
||||
//
|
||||
// wyhash2 has a >90% chance of collisions when its input data is above 16 bytes in length.
|
||||
// However, it is the fastest performing and most evenly randomized hash for very low-length inputs,
|
||||
// making it an ideal candidate for hashing primitive data types quickly and making things like hash
|
||||
// tables even faster. Therefore, a 16-byte input limit is imposed on all calls to the hash function.
|
||||
//
|
||||
<*
|
||||
@require input.len <= 16 : `wyhash2 is not useable for inputs over 16 bytes in length.`
|
||||
*>
|
||||
fn ulong hash(char[] input, ulong seed = 0)
|
||||
{
|
||||
seed ^= 0xa076_1d64_78bd_642f;
|
||||
|
||||
ulong a, b;
|
||||
|
||||
if (@likely(input.len <= 8)) // more likely to encounter 8-byte or lower type here
|
||||
{
|
||||
if (@likely(input.len >= 4))
|
||||
{
|
||||
a = (ulong)@unaligned_load(*(uint*)input.ptr, 1); // first 4 bytes widened to a u64
|
||||
b = (ulong)@unaligned_load(*(uint*)&input[^4], 1); // a walking 4-byte window based on input.len
|
||||
}
|
||||
else if (input.len > 0)
|
||||
{
|
||||
a = wyr3(input, input.len);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
a = @unaligned_load(*(ulong*)input.ptr, 1); // first 8 bytes
|
||||
b = @unaligned_load(*(ulong*)&input[^8], 1); // a walking 8-byte window based on input.len
|
||||
}
|
||||
|
||||
uint128 r = ((uint128)a ^ 0xe703_7ed1_a0b4_28db) * ((uint128)b ^ seed);
|
||||
ulong pre_res = (ulong)r ^ (ulong)(r >> 64);
|
||||
|
||||
r = ((uint128)0xe703_7ed1_a0b4_28db ^ input.len) * (uint128)pre_res;
|
||||
return (ulong)r ^ (ulong)(r >> 64);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user