mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-06 11:41:37 -07:00
Compare commits
615 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b4ca044ae0 | |||
| a2b23de0a7 | |||
| 42ff711114 | |||
| 544959326b | |||
| 942f3f5220 | |||
| 8ddfcf174d | |||
| 4086a62167 | |||
| 3a43928184 | |||
| 7f6fe91896 | |||
| 11617af8f5 | |||
| 4a2b87672c | |||
| fb0b95bcad | |||
| 1a4edad63e | |||
| ef6a73c7ef | |||
| e3dde8caf8 | |||
| 8e64265aed | |||
| 1e7dfd0e34 | |||
| 13ace1dac9 | |||
| efd933e221 | |||
| e0face1ac8 | |||
| 4fbd22b668 | |||
| 2b615b09f0 | |||
| d1603ebac4 | |||
| 561b96b20d | |||
| afe24a0d4d | |||
| 2737ccab3d | |||
| 8ed5cb283b | |||
| 8b305a4c67 | |||
| 80592f0f51 | |||
| af3401631a | |||
| 5637ed9ecd | |||
| 604551eb2d | |||
| 476d0087c8 | |||
| 34fce83d66 | |||
| b1a1da6618 | |||
| e296d6fb90 | |||
| 90244a0849 | |||
| f4abdf716e | |||
| f64d1df90b | |||
| 888bf28076 | |||
| 103eccf104 | |||
| 663661db53 | |||
| 835e8bf87a | |||
| 2187f3e7ff | |||
| 5413a8b744 | |||
| 3f9a58808c | |||
| e8517e2694 | |||
| 96330996a6 | |||
| b0cbda4ee0 | |||
| 930c929294 | |||
| 7f05b4caf2 | |||
| 6db748b4a4 | |||
| a935ade0d2 | |||
| ff2d042313 | |||
| 913c08a33e | |||
| c88a1bef91 | |||
| 706adb1232 | |||
| b9861a0cf6 | |||
| e46d772b6d | |||
| c18c0a3364 | |||
| d6b8544f50 | |||
| 4b71c47fd5 | |||
| 704530497b | |||
| 4be0fc05bb | |||
| 52aa7085e4 | |||
| 9113f389d8 | |||
| 55e9b8d994 | |||
| ee2a0c4010 | |||
| f974002839 | |||
| 83b575aec2 | |||
| 906afa4154 | |||
| 679f9b4e41 | |||
| b3caae6db4 | |||
| 6496432b80 | |||
| 0ea0fac2f9 | |||
| 13539d3be1 | |||
| 929cc48703 | |||
| 06652bebce | |||
| 574342af6f | |||
| 007832488d | |||
| 2af121752a | |||
| 0c8924ea85 | |||
| 6a894195cb | |||
| dc954307d7 | |||
| a15cbc474d | |||
| 37afd469c6 | |||
| 853173a11b | |||
| a1ae6f161b | |||
| 883f6c129a | |||
| 861d51b760 | |||
| 17efb87eef | |||
| 1747fdc3f0 | |||
| bef3ca98f0 | |||
| e15c5c4692 | |||
| 9a4ffa79db | |||
| ec38215842 | |||
| 67e9a6fd9b | |||
| 4824050c99 | |||
| 35651cfc17 | |||
| dde7cb6e7f | |||
| 862a04376f | |||
| 5a9698e8cb | |||
| 0c8a81d298 | |||
| 06ff08b9cf | |||
| 52ea63f89c | |||
| f883cd5053 | |||
| 4bf9de7237 | |||
| 0a504c2647 | |||
| d31f88bfaa | |||
| b79d7e6917 | |||
| ed7d34beb0 | |||
| aba274cf75 | |||
| 140ee036ce | |||
| 58cd75350b | |||
| f24f72c280 | |||
| f22754fc90 | |||
| a0a48bfe34 | |||
| 9f8b84c212 | |||
| 1c199f52d6 | |||
| 521182a100 | |||
| aa27cd4b0b | |||
| b1d06ea03f | |||
| e37afa3ada | |||
| b834bd32f5 | |||
| 3726f0b73c | |||
| c33bf7673f | |||
| 67e69f7196 | |||
| 18dadd94e3 | |||
| ba354e0524 | |||
| f13d30ad23 | |||
| 8d1827838f | |||
| f227a40652 | |||
| 4e2d12c540 | |||
| 5d1d98cef3 | |||
| 845613c404 | |||
| 93441a043a | |||
| c098739484 | |||
| e42029c5ed | |||
| d97fe41834 | |||
| d3d73590d3 | |||
| 185e39e53d | |||
| 8b05ec1765 | |||
| fbd609fa37 | |||
| f325a08e57 | |||
| 7a43404ea1 | |||
| 7da96c484d | |||
| e397bdf11d | |||
| 67b4cb0038 | |||
| 8eafd9eb82 | |||
| ca58d7771b | |||
| 8a4a3ed66e | |||
| 4481f9c695 | |||
| 5a75cac5b9 | |||
| e9b882be05 | |||
| 859cbf7d72 | |||
| d559feb701 | |||
| f6344577d3 | |||
| 339b2b23f6 | |||
| 8b8f8c7f7d | |||
| 95a9c9b016 | |||
| 9b3a104640 | |||
| 9b265b2309 | |||
| ea771d0cb7 | |||
| fcdba334ea | |||
| 3aa232a894 | |||
| 9866b54d59 | |||
| 525bfca4ef | |||
| 4cfbd83b10 | |||
| fee81985b4 | |||
| 6da99b888a | |||
| 31873ed466 | |||
| 4dacddd85e | |||
| 3af9d31bd5 | |||
| dae9a8dfff | |||
| f58eded5d2 | |||
| 13c58948f4 | |||
| 9f696a646f | |||
| bad4a6237e | |||
| bf44a94065 | |||
| 82d92dc46c | |||
| b81458073e | |||
| 585747bbbf | |||
| 99c955d124 | |||
| fd28199178 | |||
| dd099d9dd6 | |||
| 2241ca8e72 | |||
| b66b960e7e | |||
| 4a3684c5e8 | |||
| 91cc006e8f | |||
| ac4577ca66 | |||
| 1dfc89567e | |||
| 28e33d86de | |||
| dab3c832e0 | |||
| 94a8c38d86 | |||
| 8c760e140f | |||
| 3db7780a2c | |||
| 8b446fc88d | |||
| c9884906d9 | |||
| 5dc98336a8 | |||
| 23351ca8be | |||
| 56383e45a7 | |||
| 7184792f7a | |||
| d4aa6c3288 | |||
| 7703bc595c | |||
| e620645a03 | |||
| 1a6bb59125 | |||
| 271782d2f4 | |||
| 1b7c4c2efe | |||
| 4380934283 | |||
| 21806e5fa5 | |||
| 93e67f6bec | |||
| 6b1e76985c | |||
| 0e9bf86123 | |||
| 8f2c4a7ecf | |||
| e6d84d18d3 | |||
| a9b6d28291 | |||
| 2797dc6452 | |||
| 322b7f1f2f | |||
| e0de52efa1 | |||
| 53755824fb | |||
| 7064166da2 | |||
| d4803583ff | |||
| dff8a9153e | |||
| e3f4772d01 | |||
| c987b964c8 | |||
| 6b25d17ef9 | |||
| 3d9db56410 | |||
| 02cd53d42c | |||
| de23965ecb | |||
| e8c17ac356 | |||
| dc03e8380d | |||
| aad7ddf2d1 | |||
| de44dd5412 | |||
| 11e586494b | |||
| dd364c0c0f | |||
| 0196cc46e3 | |||
| eb5a66c944 | |||
| 9e4899d35c | |||
| 63973f431e | |||
| 1620a69398 | |||
| 1a93dfd28f | |||
| f280ba8511 | |||
| a66ce4f871 | |||
| fe3baae7a6 | |||
| eebc0dd026 | |||
| 78a5a27212 | |||
| 339bafe6ff | |||
| 0268be1925 | |||
| e41878a64f | |||
| 8b31cddaba | |||
| aab5338134 | |||
| 1a52cf1f1c | |||
| 5dfd303fd1 | |||
| a619ea3bcd | |||
| 53140dca15 | |||
| ed99e77433 | |||
| bacb915ff8 | |||
| e41ad2bf16 | |||
| 4c7469a264 | |||
| 753516c392 | |||
| f353adc7fb | |||
| bb823d5ba0 | |||
| 1c940e3968 | |||
| c97ffbecbc | |||
| 2380720fa2 | |||
| 784408358d | |||
| 149ecafdef | |||
| 7ec17ecf98 | |||
| 318d5e4a7e | |||
| eaec8a2bbf | |||
| c2a01096c4 | |||
| 827cb24023 | |||
| dc4ec8638c | |||
| 94ec647923 | |||
| 51a013fcf1 | |||
| f28c6c3bba | |||
| 53e2cdf7af | |||
| 76575e834b | |||
| 42a5a2cf17 | |||
| c3acdeb310 | |||
| d4e2fa0377 | |||
| 582154f20d | |||
| 908a6ff2d4 | |||
| 09b3f5a7a7 | |||
| fa4fbbe1ce | |||
| 759139089f | |||
| 8ba644dd79 | |||
| 8626d38db1 | |||
| b19bf5bbda | |||
| ca481dc52d | |||
| c3302615a3 | |||
| 3c3f0f90c2 | |||
| cd5fa8523f | |||
| ec7b77fc0f | |||
| 4baf101f15 | |||
| ff4787070d | |||
| 54a3cfb540 | |||
| c560553c21 | |||
| 45044de0b7 | |||
| c9c7d3270d | |||
| 9f7ac1469f | |||
| ca9d1f940d | |||
| 9f190f3937 | |||
| 657c516360 | |||
| 20c17ba6f9 | |||
| 3d4e23d741 | |||
| 50564a301e | |||
| f8f6f2dada | |||
| 104ca2ce22 | |||
| abe5c2ca83 | |||
| 0c9f487783 | |||
| a67df07392 | |||
| 05972eb26b | |||
| 4ccc473e97 | |||
| 90815452ca | |||
| a804463a57 | |||
| 33270f14a4 | |||
| c7ea4ec71c | |||
| d37b5a7b67 | |||
| 1128bd1d7f | |||
| 20c32c807d | |||
| 5b5402fb23 | |||
| 108b8feb35 | |||
| 603581aa53 | |||
| c5f7788652 | |||
| f57c03c170 | |||
| 2fe961cbcd | |||
| ebadff555d | |||
| cb45aa58fd | |||
| e87c5bca58 | |||
| 0398388a59 | |||
| 1a22f82f94 | |||
| 35a845b93f | |||
| 3ff8952813 | |||
| 56c5e93fd1 | |||
| 39b166edd3 | |||
| eaf1d88287 | |||
| 1dc90103bd | |||
| 0b02c67cdf | |||
| 9b0e87544a | |||
| 61c630bbf8 | |||
| 0e6bcd0dbb | |||
| fc88de12c2 | |||
| a3da796d54 | |||
| 40e99ebb10 | |||
| c68560c573 | |||
| 76292c8ed5 | |||
| c6c00c706a | |||
| 6454c6f087 | |||
| c9e732d141 | |||
| d2cd96c3c8 | |||
| 34af2bb8ad | |||
| bd198aeada | |||
| 50464bdce3 | |||
| f1779c85de | |||
| 903e254e36 | |||
| eef2aef021 | |||
| c1e81dc14d | |||
| fa3cae2bb0 | |||
| 71929f737b | |||
| 1945218f6d | |||
| bedeaa34d9 | |||
| 71a812e7fe | |||
| 741ee00b64 | |||
| d3ae70264e | |||
| c8b376f07f | |||
| ff7fcb6d38 | |||
| 45d1328a85 | |||
| 8702bf00d5 | |||
| 9d28f2e18c | |||
| 5985c6e3df | |||
| 82e2d1916f | |||
| 70820c2c40 | |||
| fe5c278fca | |||
| f0d65112b8 | |||
| 5fbd876db1 | |||
| 4ea593bde0 | |||
| 047b505836 | |||
| 1f64d8d5bd | |||
| 7c529e990d | |||
| 828870004b | |||
| 7e60e21934 | |||
| 5be7d8e32d | |||
| fc2ba81be0 | |||
| edcbca51c3 | |||
| 3c7e2659ac | |||
| 6b88d0a820 | |||
| d2a2c1e74e | |||
| ef7c6b9895 | |||
| c17981ac38 | |||
| 4b52f7fe2b | |||
| 8fcfd8c506 | |||
| e0d0dc704c | |||
| 14f08ff02b | |||
| 5d5ef78de9 | |||
| e595bdc805 | |||
| 6d862cc4e5 | |||
| 3628154849 | |||
| 9ad9236c3b | |||
| 0d698c7b53 | |||
| 49f147cc86 | |||
| e5bf6fd3aa | |||
| 5c8f78a25a | |||
| 1f4cfd52fd | |||
| be0774acc8 | |||
| de7d3e2487 | |||
| 7e994b6d21 | |||
| 35e57fdef8 | |||
| 371749d474 | |||
| 575e5a255b | |||
| 323a6e0728 | |||
| affd48c791 | |||
| b65589d036 | |||
| 61c481bd81 | |||
| 96c06185dd | |||
| 00dfff7ee0 | |||
| b35e72c82b | |||
| edb685f04b | |||
| 7c977bb859 | |||
| 072825ac5a | |||
| 4c45f7d3ae | |||
| 11137cfec5 | |||
| e627fcb0e6 | |||
| 29250f2657 | |||
| 9122c20d4b | |||
| 0a528777e8 | |||
| f2be35f1f0 | |||
| ee93d7c05e | |||
| 315695b4f8 | |||
| 68781f8dd3 | |||
| 1d99bc0f87 | |||
| 7044a7d776 | |||
| 4b6fe2baa7 | |||
| ed060819f3 | |||
| 566119ff83 | |||
| 08612423b9 | |||
| ff37a7435c | |||
| fa08690686 | |||
| b818a77131 | |||
| 9a95049393 | |||
| b196b84ef2 | |||
| e2eb3cdd8a | |||
| d0dbe9a1bd | |||
| 039bb8794a | |||
| f010544eff | |||
| 4a290f47ad | |||
| 678fdae966 | |||
| 3a9b86628a | |||
| bea47db495 | |||
| 9ef43fc782 | |||
| 28ea9425fd | |||
| 72f6b5479d | |||
| 0b6d73c86e | |||
| 971229fe66 | |||
| c4ef8e7f6c | |||
| 155516b897 | |||
| 9b66b0c8e6 | |||
| 483015fe57 | |||
| a73741d3b7 | |||
| fb2849e02e | |||
| 70592630a4 | |||
| fcfc1cb97f | |||
| cdd90a9a0b | |||
| 460ffe1aee | |||
| 08382cb05d | |||
| 8455e159f5 | |||
| 76d3be7912 | |||
| 97f0f72080 | |||
| a667878d23 | |||
| cbabcb0907 | |||
| fac9ce5d83 | |||
| d3342c2381 | |||
| cd4375061c | |||
| f0e98372fb | |||
| a747e47582 | |||
| adcda88501 | |||
| e7190aab41 | |||
| 929437c7bc | |||
| ed6667ebf2 | |||
| 58f07698e8 | |||
| c406bbb6e3 | |||
| b2f9f0af68 | |||
| 25feff3eb4 | |||
| 1fc6ff91b2 | |||
| 2c580aa6fb | |||
| f0a6fb4057 | |||
| 448c0d8dd3 | |||
| b4cfae222c | |||
| 265e6aa781 | |||
| 9d234998c0 | |||
| c2ddf4266f | |||
| a0b1b8d1c3 | |||
| dbaf1a1ce0 | |||
| c656a9e4cd | |||
| cd99625dd3 | |||
| 7d670f6562 | |||
| 2a526058b3 | |||
| 3e159736cd | |||
| d33668fa91 | |||
| b47a15733d | |||
| 4f5b2bd127 | |||
| 3b7100f8e5 | |||
| 6b386631dd | |||
| 11b1a48bf0 | |||
| 72ce111a95 | |||
| 606608c02b | |||
| eacf2918ad | |||
| afcccfdec5 | |||
| 60a064984d | |||
| 0504c12f04 | |||
| 7d29389834 | |||
| 52ba3357ee | |||
| 78a67ef31e | |||
| f9d59ef6d4 | |||
| c3b94b9e1d | |||
| eb93779f63 | |||
| d2a9122176 | |||
| 0ff130d82b | |||
| fa29974dab | |||
| 7764ab2ab0 | |||
| 6a5633df2d | |||
| c8539fe411 | |||
| ac9484206b | |||
| f77ce359ce | |||
| 3f1249c27e | |||
| 8d8c42e962 | |||
| 60d0c03134 | |||
| 9d8d864400 | |||
| 21a1ddfbae | |||
| 890fe07c6e | |||
| d581dbbec5 | |||
| 5e3e958574 | |||
| 6a9203328b | |||
| 5db65aa796 | |||
| cb00b8022b | |||
| 4875f745c8 | |||
| ccdbd4b6ce | |||
| 9dcf345795 | |||
| 21064fbb60 | |||
| 45fa9d8148 | |||
| cb8faf5b74 | |||
| 6a5d51f0d6 | |||
| 433ca538bf | |||
| d1723664a7 | |||
| fb37572c4c | |||
| 6050bc3bf6 | |||
| 306169699c | |||
| 3354212f8e | |||
| 54dae06ad1 | |||
| 8d93379e29 | |||
| 62b7d8de97 | |||
| dacb0f7786 | |||
| 5b1ffba915 | |||
| a406ff7063 | |||
| 9ba02e888d | |||
| ed0384c102 | |||
| 80b115748f | |||
| 9829a02571 | |||
| 8383a45b62 | |||
| b0faab29e0 | |||
| d7bfbe0552 | |||
| 39fd73fe17 | |||
| 6f7c5a7577 | |||
| d334b8c72a | |||
| 9d0f4833bf | |||
| 6641a6f6c9 | |||
| 601df0e8f7 | |||
| 1b32e27aa4 | |||
| 3404dea8ac | |||
| 40b20fb473 | |||
| b74b956fda | |||
| 1f6a6f2cd3 | |||
| a27b167218 | |||
| 6a1649d8aa | |||
| 84ad71fdb3 | |||
| 49fa66370f | |||
| e1a3c0e21d | |||
| bf42e39b1c | |||
| c531427ee5 | |||
| b7e1ae7073 | |||
| e11f3d2520 | |||
| dcfda195d2 | |||
| a1c5bebac7 | |||
| 89d8df28be | |||
| dffc3af86c | |||
| eadfbb1318 | |||
| 1afc235359 | |||
| 09ef08f035 | |||
| 0f675fa436 | |||
| 568b746c98 | |||
| a0b2ea6d6e | |||
| a463e282db | |||
| e3181c13c6 | |||
| 22c092f846 | |||
| eb3d6d7d75 | |||
| 852f694bee | |||
| 87ea4a2652 | |||
| d03024088a | |||
| b6c4dfb68d | |||
| 95c2e020ff | |||
| 50dffaf131 | |||
| fc4f6b87bb | |||
| 8137b9dd75 | |||
| 558c330028 | |||
| 1875e7c36a | |||
| 7d4da6eaa7 | |||
| 8ae375dbff | |||
| a7a6ff8c69 | |||
| 0cba33075f | |||
| 8664b88c8f | |||
| 7934e92d14 | |||
| 2918baa3e8 | |||
| 805bb69c6c | |||
| 3b632b4d90 | |||
| 77a0e50298 |
+112
-133
@@ -10,7 +10,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Build, Check, and Test
|
- name: Build, Check, and Test
|
||||||
timeout-minutes: 25
|
timeout-minutes: 15
|
||||||
uses: vmactions/netbsd-vm@v1
|
uses: vmactions/netbsd-vm@v1
|
||||||
with:
|
with:
|
||||||
release: "10.0"
|
release: "10.0"
|
||||||
@@ -24,157 +24,141 @@ jobs:
|
|||||||
/usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz
|
/usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz
|
||||||
/usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz
|
/usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz
|
||||||
ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
|
ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
|
||||||
ln -s /usr/pkg/bin/bash /bin/bash
|
|
||||||
run: |
|
run: |
|
||||||
git config --global --add safe.directory $(pwd)
|
git config --global --add safe.directory $(pwd)
|
||||||
gmake release
|
gmake release
|
||||||
./odin version
|
./odin version
|
||||||
./odin report
|
./odin report
|
||||||
./odin check examples/all -vet -strict-style -target:netbsd_amd64
|
gmake -C vendor/stb/src
|
||||||
(cd tests/core; gmake all_bsd)
|
gmake -C vendor/cgltf/src
|
||||||
(cd tests/internal; gmake all_bsd)
|
gmake -C vendor/miniaudio/src
|
||||||
|
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64
|
||||||
|
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64
|
||||||
|
./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
|
./odin test tests/core/speed.odin -file -all-packages -o:speed -define:ODIN_TEST_FANCY=false
|
||||||
|
./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
|
./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
(cd tests/issues; ./run.sh)
|
(cd tests/issues; ./run.sh)
|
||||||
build_linux:
|
build_freebsd:
|
||||||
name: Ubuntu Build, Check, and Test
|
name: FreeBSD Build, Check, and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- name: Download LLVM
|
- name: Build, Check, and Test
|
||||||
|
timeout-minutes: 15
|
||||||
|
uses: vmactions/freebsd-vm@v1
|
||||||
|
with:
|
||||||
|
usesh: true
|
||||||
|
copyback: false
|
||||||
|
prepare: |
|
||||||
|
pkg install -y gmake git bash python3 libxml2 llvm17
|
||||||
|
run: |
|
||||||
|
# `set -e` is needed for test failures to register. https://github.com/vmactions/freebsd-vm/issues/72
|
||||||
|
set -e -x
|
||||||
|
git config --global --add safe.directory $(pwd)
|
||||||
|
gmake release
|
||||||
|
./odin version
|
||||||
|
./odin report
|
||||||
|
gmake -C vendor/stb/src
|
||||||
|
gmake -C vendor/cgltf/src
|
||||||
|
gmake -C vendor/miniaudio/src
|
||||||
|
./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
|
||||||
|
./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
|
./odin test tests/core/speed.odin -file -all-packages -o:speed -define:ODIN_TEST_FANCY=false
|
||||||
|
./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
|
./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
|
(cd tests/issues; ./run.sh)
|
||||||
|
ci:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# MacOS 13 runs on Intel, 14 runs on ARM
|
||||||
|
os: [ubuntu-latest, macos-13, macos-14]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download LLVM (Linux)
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
wget https://apt.llvm.org/llvm.sh
|
wget https://apt.llvm.org/llvm.sh
|
||||||
chmod +x llvm.sh
|
chmod +x llvm.sh
|
||||||
sudo ./llvm.sh 17
|
sudo ./llvm.sh 17
|
||||||
echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH
|
echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH
|
||||||
- name: build odin
|
|
||||||
run: ./build_odin.sh release
|
- name: Download LLVM (MacOS Intel)
|
||||||
- name: Odin version
|
if: matrix.os == 'macos-13'
|
||||||
run: ./odin version
|
|
||||||
timeout-minutes: 1
|
|
||||||
- name: Odin report
|
|
||||||
run: ./odin report
|
|
||||||
timeout-minutes: 1
|
|
||||||
- name: Odin check
|
|
||||||
run: ./odin check examples/demo -vet
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin run
|
|
||||||
run: ./odin run examples/demo
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin run -debug
|
|
||||||
run: ./odin run examples/demo -debug
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin check examples/all
|
|
||||||
run: ./odin check examples/all -strict-style
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Core library tests
|
|
||||||
run: |
|
|
||||||
cd tests/core
|
|
||||||
make
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Vendor library tests
|
|
||||||
run: |
|
|
||||||
cd tests/vendor
|
|
||||||
make
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin internals tests
|
|
||||||
run: |
|
|
||||||
cd tests/internal
|
|
||||||
make
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin check examples/all for Linux i386
|
|
||||||
run: ./odin check examples/all -vet -strict-style -target:linux_i386
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin check examples/all for Linux arm64
|
|
||||||
run: ./odin check examples/all -vet -strict-style -target:linux_arm64
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin check examples/all for FreeBSD amd64
|
|
||||||
run: ./odin check examples/all -vet -strict-style -target:freebsd_amd64
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin check examples/all for OpenBSD amd64
|
|
||||||
run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64
|
|
||||||
timeout-minutes: 10
|
|
||||||
build_macOS:
|
|
||||||
name: MacOS Build, Check, and Test
|
|
||||||
runs-on: macos-13
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: Download LLVM, and setup PATH
|
|
||||||
run: |
|
run: |
|
||||||
brew install llvm@17
|
brew install llvm@17
|
||||||
echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
|
echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
|
||||||
- name: build odin
|
|
||||||
run: ./build_odin.sh release
|
- name: Download LLVM (MacOS ARM)
|
||||||
- name: Odin version
|
if: matrix.os == 'macos-14'
|
||||||
run: ./odin version
|
|
||||||
timeout-minutes: 1
|
|
||||||
- name: Odin report
|
|
||||||
run: ./odin report
|
|
||||||
timeout-minutes: 1
|
|
||||||
- name: Odin check
|
|
||||||
run: ./odin check examples/demo -vet
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin run
|
|
||||||
run: ./odin run examples/demo
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin run -debug
|
|
||||||
run: ./odin run examples/demo -debug
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin check examples/all
|
|
||||||
run: ./odin check examples/all -strict-style
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Core library tests
|
|
||||||
run: |
|
run: |
|
||||||
cd tests/core
|
brew install llvm@17 wasmtime
|
||||||
make
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin internals tests
|
|
||||||
run: |
|
|
||||||
cd tests/internal
|
|
||||||
make
|
|
||||||
timeout-minutes: 10
|
|
||||||
build_macOS_arm:
|
|
||||||
name: MacOS ARM Build, Check, and Test
|
|
||||||
runs-on: macos-14 # This is an arm/m1 runner.
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: Download LLVM and setup PATH
|
|
||||||
run: |
|
|
||||||
brew install llvm@17
|
|
||||||
echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
|
echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
|
||||||
- name: build odin
|
|
||||||
|
- name: Build Odin
|
||||||
run: ./build_odin.sh release
|
run: ./build_odin.sh release
|
||||||
- name: Odin version
|
- name: Odin version
|
||||||
run: ./odin version
|
run: ./odin version
|
||||||
timeout-minutes: 1
|
|
||||||
- name: Odin report
|
- name: Odin report
|
||||||
run: ./odin report
|
run: ./odin report
|
||||||
timeout-minutes: 1
|
- name: Compile needed Vendor
|
||||||
|
run: |
|
||||||
|
make -C vendor/stb/src
|
||||||
|
make -C vendor/cgltf/src
|
||||||
|
make -C vendor/miniaudio/src
|
||||||
- name: Odin check
|
- name: Odin check
|
||||||
run: ./odin check examples/demo -vet
|
run: ./odin check examples/demo -vet
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin run
|
- name: Odin run
|
||||||
run: ./odin run examples/demo
|
run: ./odin run examples/demo
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin run -debug
|
- name: Odin run -debug
|
||||||
run: ./odin run examples/demo -debug
|
run: ./odin run examples/demo -debug
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin check examples/all
|
- name: Odin check examples/all
|
||||||
run: ./odin check examples/all -strict-style
|
run: ./odin check examples/all -strict-style
|
||||||
timeout-minutes: 10
|
- name: Normal Core library tests
|
||||||
- name: Core library tests
|
run: ./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
|
- name: Optimized Core library tests
|
||||||
|
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
|
- name: Vendor library tests
|
||||||
|
run: ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
|
- name: Internals tests
|
||||||
|
run: ./odin test tests/internal -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
|
- name: Core library benchmarks
|
||||||
|
run: ./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
|
- name: GitHub Issue tests
|
||||||
run: |
|
run: |
|
||||||
cd tests/core
|
cd tests/issues
|
||||||
make
|
./run.sh
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin internals tests
|
- name: Odin check examples/all for Linux i386
|
||||||
|
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
- name: Odin check examples/all for Linux arm64
|
||||||
|
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
- name: Odin check examples/all for FreeBSD amd64
|
||||||
|
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
- name: Odin check examples/all for OpenBSD amd64
|
||||||
|
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
|
||||||
|
- name: Run demo on WASI WASM32
|
||||||
run: |
|
run: |
|
||||||
cd tests/internal
|
./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo.wasm
|
||||||
make
|
wasmtime ./demo.wasm
|
||||||
timeout-minutes: 10
|
if: matrix.os == 'macos-14'
|
||||||
|
|
||||||
build_windows:
|
build_windows:
|
||||||
name: Windows Build, Check, and Test
|
name: Windows Build, Check, and Test
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- name: build Odin
|
- name: build Odin
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
@@ -182,72 +166,67 @@ jobs:
|
|||||||
./build.bat 1
|
./build.bat 1
|
||||||
- name: Odin version
|
- name: Odin version
|
||||||
run: ./odin version
|
run: ./odin version
|
||||||
timeout-minutes: 1
|
|
||||||
- name: Odin report
|
- name: Odin report
|
||||||
run: ./odin report
|
run: ./odin report
|
||||||
timeout-minutes: 1
|
|
||||||
- name: Odin check
|
- name: Odin check
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
odin check examples/demo -vet
|
odin check examples/demo -vet
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin run
|
- name: Odin run
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
odin run examples/demo
|
odin run examples/demo
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin run -debug
|
- name: Odin run -debug
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
odin run examples/demo -debug
|
odin run examples/demo -debug
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin check examples/all
|
- name: Odin check examples/all
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
odin check examples/all -strict-style
|
odin check examples/all -strict-style
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Core library tests
|
- name: Core library tests
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
cd tests\core
|
odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
call build.bat
|
- name: Optimized core library tests
|
||||||
timeout-minutes: 10
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
|
odin test tests/core/speed.odin -o:speed -file -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
|
- name: Core library benchmarks
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
|
odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
- name: Vendor library tests
|
- name: Vendor library tests
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
cd tests\vendor
|
odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
call build.bat
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin internals tests
|
- name: Odin internals tests
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
cd tests\internal
|
odin test tests/internal -all-packages -define:ODIN_TEST_FANCY=false
|
||||||
call build.bat
|
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin documentation tests
|
- name: Odin documentation tests
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
cd tests\documentation
|
cd tests\documentation
|
||||||
call build.bat
|
call build.bat
|
||||||
timeout-minutes: 10
|
|
||||||
- name: core:math/big tests
|
- name: core:math/big tests
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
cd tests\core\math\big
|
cd tests\core\math\big
|
||||||
call build.bat
|
call build.bat
|
||||||
timeout-minutes: 10
|
|
||||||
- name: Odin check examples/all for Windows 32bits
|
- name: Odin check examples/all for Windows 32bits
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
odin check examples/all -strict-style -target:windows_i386
|
odin check examples/all -strict-style -target:windows_i386
|
||||||
timeout-minutes: 10
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
if: github.repository == 'odin-lang/Odin'
|
if: github.repository == 'odin-lang/Odin'
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- name: build Odin
|
- name: build Odin
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
if: github.repository == 'odin-lang/Odin'
|
if: github.repository == 'odin-lang/Odin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- name: (Linux) Download LLVM
|
- name: (Linux) Download LLVM
|
||||||
run: |
|
run: |
|
||||||
wget https://apt.llvm.org/llvm.sh
|
wget https://apt.llvm.org/llvm.sh
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
if: github.repository == 'odin-lang/Odin'
|
if: github.repository == 'odin-lang/Odin'
|
||||||
runs-on: macos-13
|
runs-on: macos-13
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- name: Download LLVM and setup PATH
|
- name: Download LLVM and setup PATH
|
||||||
run: |
|
run: |
|
||||||
brew install llvm@17 dylibbundler
|
brew install llvm@17 dylibbundler
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
if: github.repository == 'odin-lang/Odin'
|
if: github.repository == 'odin-lang/Odin'
|
||||||
runs-on: macos-14 # ARM machine
|
runs-on: macos-14 # ARM machine
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- name: Download LLVM and setup PATH
|
- name: Download LLVM and setup PATH
|
||||||
run: |
|
run: |
|
||||||
brew install llvm@17 dylibbundler
|
brew install llvm@17 dylibbundler
|
||||||
@@ -146,16 +146,16 @@ jobs:
|
|||||||
runs-on: [ubuntu-latest]
|
runs-on: [ubuntu-latest]
|
||||||
needs: [build_windows, build_macos, build_macos_arm, build_ubuntu]
|
needs: [build_windows, build_macos, build_macos_arm, build_ubuntu]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.8.x'
|
python-version: '3.8.x'
|
||||||
|
|
||||||
- name: Install B2 CLI
|
- name: Install B2 SDK
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install --upgrade b2
|
pip install --upgrade b2sdk
|
||||||
|
|
||||||
- name: Display Python version
|
- name: Display Python version
|
||||||
run: python -c "import sys; print(sys.version)"
|
run: python -c "import sys; print(sys.version)"
|
||||||
@@ -188,24 +188,9 @@ jobs:
|
|||||||
BUCKET: ${{ secrets.B2_BUCKET }}
|
BUCKET: ${{ secrets.B2_BUCKET }}
|
||||||
DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }}
|
DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }}
|
||||||
run: |
|
run: |
|
||||||
echo Authorizing B2 account
|
python3 ci/nightly.py artifact windows-amd64 windows_artifacts/
|
||||||
b2 account authorize "$APPID" "$APPKEY"
|
python3 ci/nightly.py artifact ubuntu-amd64 ubuntu_artifacts/dist.zip
|
||||||
|
python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.zip
|
||||||
echo Uploading artifcates to B2
|
python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.zip
|
||||||
chmod +x ./ci/upload_create_nightly.sh
|
python3 ci/nightly.py prune
|
||||||
./ci/upload_create_nightly.sh "$BUCKET" windows-amd64 windows_artifacts/
|
python3 ci/nightly.py json
|
||||||
./ci/upload_create_nightly.sh "$BUCKET" ubuntu-amd64 ubuntu_artifacts/dist.zip
|
|
||||||
./ci/upload_create_nightly.sh "$BUCKET" macos-amd64 macos_artifacts/dist.zip
|
|
||||||
./ci/upload_create_nightly.sh "$BUCKET" macos-arm64 macos_arm_artifacts/dist.zip
|
|
||||||
|
|
||||||
echo Deleting old artifacts in B2
|
|
||||||
python3 ci/delete_old_binaries.py "$BUCKET" "$DAYS_TO_KEEP"
|
|
||||||
|
|
||||||
echo Creating nightly.json
|
|
||||||
python3 ci/create_nightly_json.py "$BUCKET" > nightly.json
|
|
||||||
|
|
||||||
echo Uploading nightly.json
|
|
||||||
b2 upload-file "$BUCKET" nightly.json nightly.json
|
|
||||||
|
|
||||||
echo Clear B2 account info
|
|
||||||
b2 clear-account
|
|
||||||
|
|||||||
+1
-1
@@ -303,7 +303,7 @@ bin/
|
|||||||
# - Linux/MacOS
|
# - Linux/MacOS
|
||||||
odin
|
odin
|
||||||
!odin/
|
!odin/
|
||||||
odin.dSYM
|
**/*.dSYM
|
||||||
*.bin
|
*.bin
|
||||||
demo.bin
|
demo.bin
|
||||||
libLLVM*.so*
|
libLLVM*.so*
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ expect :: proc(val, expected_val: T) -> T ---
|
|||||||
|
|
||||||
// Linux and Darwin Only
|
// Linux and Darwin Only
|
||||||
syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr ---
|
syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr ---
|
||||||
|
// FreeBSD, NetBSD, et cetera
|
||||||
|
syscall_bsd :: proc(id: uintptr, args: ..uintptr) -> (uintptr, bool) ---
|
||||||
|
|
||||||
|
|
||||||
// Atomics
|
// Atomics
|
||||||
@@ -192,7 +194,8 @@ type_proc_return_count :: proc($T: typeid) -> int where type_is_proc(T) ---
|
|||||||
type_proc_parameter_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
|
type_proc_parameter_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
|
||||||
type_proc_return_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
|
type_proc_return_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
|
||||||
|
|
||||||
type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) ---
|
type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) ---
|
||||||
|
type_struct_has_implicit_padding :: proc($T: typeid) -> bool where type_is_struct(T) ---
|
||||||
|
|
||||||
type_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid ---
|
type_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid ---
|
||||||
type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V ---
|
type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V ---
|
||||||
@@ -295,6 +298,10 @@ simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
|
|||||||
// if all listed features are supported.
|
// if all listed features are supported.
|
||||||
has_target_feature :: proc($test: $T) -> bool where type_is_string(T) || type_is_proc(T) ---
|
has_target_feature :: proc($test: $T) -> bool where type_is_string(T) || type_is_proc(T) ---
|
||||||
|
|
||||||
|
|
||||||
|
// Returns the value of the procedure where `x` must be a call expression
|
||||||
|
procedure_of :: proc(x: $T) -> T where type_is_proc(T) ---
|
||||||
|
|
||||||
// WASM targets only
|
// WASM targets only
|
||||||
wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
|
wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
|
||||||
wasm_memory_size :: proc(index: uintptr) -> int ---
|
wasm_memory_size :: proc(index: uintptr) -> int ---
|
||||||
|
|||||||
+52
-2
@@ -397,11 +397,34 @@ Logger :: struct {
|
|||||||
options: Logger_Options,
|
options: Logger_Options,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Random_Generator_Mode :: enum {
|
||||||
|
Read,
|
||||||
|
Reset,
|
||||||
|
Query_Info,
|
||||||
|
}
|
||||||
|
|
||||||
|
Random_Generator_Query_Info_Flag :: enum u32 {
|
||||||
|
Cryptographic,
|
||||||
|
Uniform,
|
||||||
|
External_Entropy,
|
||||||
|
Resettable,
|
||||||
|
}
|
||||||
|
Random_Generator_Query_Info :: distinct bit_set[Random_Generator_Query_Info_Flag; u32]
|
||||||
|
|
||||||
|
Random_Generator_Proc :: #type proc(data: rawptr, mode: Random_Generator_Mode, p: []byte)
|
||||||
|
|
||||||
|
Random_Generator :: struct {
|
||||||
|
procedure: Random_Generator_Proc,
|
||||||
|
data: rawptr,
|
||||||
|
}
|
||||||
|
|
||||||
Context :: struct {
|
Context :: struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
temp_allocator: Allocator,
|
temp_allocator: Allocator,
|
||||||
assertion_failure_proc: Assertion_Failure_Proc,
|
assertion_failure_proc: Assertion_Failure_Proc,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
|
random_generator: Random_Generator,
|
||||||
|
|
||||||
user_ptr: rawptr,
|
user_ptr: rawptr,
|
||||||
user_index: int,
|
user_index: int,
|
||||||
@@ -560,6 +583,19 @@ Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET)
|
|||||||
*/
|
*/
|
||||||
Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS)
|
Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Defined internally by the compiler
|
||||||
|
Odin_Optimization_Mode :: enum int {
|
||||||
|
None = -1,
|
||||||
|
Minimal = 0,
|
||||||
|
Size = 1,
|
||||||
|
Speed = 2,
|
||||||
|
Aggressive = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
ODIN_OPTIMIZATION_MODE // is a constant
|
||||||
|
*/
|
||||||
|
Odin_Optimization_Mode :: type_of(ODIN_OPTIMIZATION_MODE)
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// Init Startup Procedures //
|
// Init Startup Procedures //
|
||||||
@@ -695,13 +731,16 @@ __init_context :: proc "contextless" (c: ^Context) {
|
|||||||
|
|
||||||
c.logger.procedure = default_logger_proc
|
c.logger.procedure = default_logger_proc
|
||||||
c.logger.data = nil
|
c.logger.data = nil
|
||||||
|
|
||||||
|
c.random_generator.procedure = default_random_generator_proc
|
||||||
|
c.random_generator.data = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! {
|
default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! {
|
||||||
when ODIN_OS == .Freestanding {
|
when ODIN_OS == .Freestanding {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
} else {
|
} else {
|
||||||
when !ODIN_DISABLE_ASSERT {
|
when ODIN_OS != .Orca && !ODIN_DISABLE_ASSERT {
|
||||||
print_caller_location(loc)
|
print_caller_location(loc)
|
||||||
print_string(" ")
|
print_string(" ")
|
||||||
}
|
}
|
||||||
@@ -710,7 +749,18 @@ default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code
|
|||||||
print_string(": ")
|
print_string(": ")
|
||||||
print_string(message)
|
print_string(message)
|
||||||
}
|
}
|
||||||
print_byte('\n')
|
|
||||||
|
when ODIN_OS == .Orca {
|
||||||
|
assert_fail(
|
||||||
|
cstring(raw_data(loc.file_path)),
|
||||||
|
cstring(raw_data(loc.procedure)),
|
||||||
|
loc.line,
|
||||||
|
"",
|
||||||
|
cstring(raw_data(orca_stderr_buffer[:orca_stderr_buffer_idx])),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
print_byte('\n')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
trap()
|
trap()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ copy :: proc{copy_slice, copy_from_string}
|
|||||||
// with the old value, and reducing the length of the dynamic array by 1.
|
// with the old value, and reducing the length of the dynamic array by 1.
|
||||||
//
|
//
|
||||||
// Note: This is an O(1) operation.
|
// Note: This is an O(1) operation.
|
||||||
// Note: If you the elements to remain in their order, use `ordered_remove`.
|
// Note: If you want the elements to remain in their order, use `ordered_remove`.
|
||||||
// Note: If the index is out of bounds, this procedure will panic.
|
// Note: If the index is out of bounds, this procedure will panic.
|
||||||
@builtin
|
@builtin
|
||||||
unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check {
|
unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check {
|
||||||
@@ -79,7 +79,7 @@ unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_loca
|
|||||||
// `ordered_remove` removed the element at the specified `index` whilst keeping the order of the other elements.
|
// `ordered_remove` removed the element at the specified `index` whilst keeping the order of the other elements.
|
||||||
//
|
//
|
||||||
// Note: This is an O(N) operation.
|
// Note: This is an O(N) operation.
|
||||||
// Note: If you the elements do not have to remain in their order, prefer `unordered_remove`.
|
// Note: If the elements do not have to remain in their order, prefer `unordered_remove`.
|
||||||
// Note: If the index is out of bounds, this procedure will panic.
|
// Note: If the index is out of bounds, this procedure will panic.
|
||||||
@builtin
|
@builtin
|
||||||
ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check {
|
ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check {
|
||||||
@@ -163,21 +163,43 @@ pop_front_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bo
|
|||||||
|
|
||||||
// `clear` will set the length of a passed dynamic array or map to `0`
|
// `clear` will set the length of a passed dynamic array or map to `0`
|
||||||
@builtin
|
@builtin
|
||||||
clear :: proc{clear_dynamic_array, clear_map}
|
clear :: proc{
|
||||||
|
clear_dynamic_array,
|
||||||
|
clear_map,
|
||||||
|
|
||||||
|
clear_soa_dynamic_array,
|
||||||
|
}
|
||||||
|
|
||||||
// `reserve` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
|
// `reserve` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
|
||||||
@builtin
|
@builtin
|
||||||
reserve :: proc{reserve_dynamic_array, reserve_map}
|
reserve :: proc{
|
||||||
|
reserve_dynamic_array,
|
||||||
|
reserve_map,
|
||||||
|
|
||||||
|
reserve_soa,
|
||||||
|
}
|
||||||
|
|
||||||
@builtin
|
@builtin
|
||||||
non_zero_reserve :: proc{non_zero_reserve_dynamic_array}
|
non_zero_reserve :: proc{
|
||||||
|
non_zero_reserve_dynamic_array,
|
||||||
|
|
||||||
|
non_zero_reserve_soa,
|
||||||
|
}
|
||||||
|
|
||||||
// `resize` will try to resize memory of a passed dynamic array to the requested element count (setting the `len`, and possibly `cap`).
|
// `resize` will try to resize memory of a passed dynamic array to the requested element count (setting the `len`, and possibly `cap`).
|
||||||
@builtin
|
@builtin
|
||||||
resize :: proc{resize_dynamic_array}
|
resize :: proc{
|
||||||
|
resize_dynamic_array,
|
||||||
|
|
||||||
|
resize_soa,
|
||||||
|
}
|
||||||
|
|
||||||
@builtin
|
@builtin
|
||||||
non_zero_resize :: proc{non_zero_resize_dynamic_array}
|
non_zero_resize :: proc{
|
||||||
|
non_zero_resize_dynamic_array,
|
||||||
|
|
||||||
|
non_zero_resize_soa,
|
||||||
|
}
|
||||||
|
|
||||||
// Shrinks the capacity of a dynamic array or map down to the current length, or the given capacity.
|
// Shrinks the capacity of a dynamic array or map down to the current length, or the given capacity.
|
||||||
@builtin
|
@builtin
|
||||||
@@ -268,7 +290,7 @@ new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_locat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_RESERVE_CAPACITY :: 16
|
DEFAULT_DYNAMIC_ARRAY_CAPACITY :: 8
|
||||||
|
|
||||||
@(require_results)
|
@(require_results)
|
||||||
make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error {
|
make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error {
|
||||||
@@ -295,7 +317,7 @@ make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allo
|
|||||||
// Note: Prefer using the procedure group `make`.
|
// Note: Prefer using the procedure group `make`.
|
||||||
@(builtin, require_results)
|
@(builtin, require_results)
|
||||||
make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error {
|
make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error {
|
||||||
return make_dynamic_array_len_cap(T, 0, DEFAULT_RESERVE_CAPACITY, allocator, loc)
|
return make_dynamic_array_len_cap(T, 0, 0, allocator, loc)
|
||||||
}
|
}
|
||||||
// `make_dynamic_array_len` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value.
|
// `make_dynamic_array_len` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value.
|
||||||
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
|
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
|
||||||
@@ -364,6 +386,11 @@ make :: proc{
|
|||||||
make_dynamic_array_len_cap,
|
make_dynamic_array_len_cap,
|
||||||
make_map,
|
make_map,
|
||||||
make_multi_pointer,
|
make_multi_pointer,
|
||||||
|
|
||||||
|
make_soa_slice,
|
||||||
|
make_soa_dynamic_array,
|
||||||
|
make_soa_dynamic_array_len,
|
||||||
|
make_soa_dynamic_array_len_cap,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -383,7 +410,7 @@ clear_map :: proc "contextless" (m: ^$T/map[$K]$V) {
|
|||||||
//
|
//
|
||||||
// Note: Prefer the procedure group `reserve`
|
// Note: Prefer the procedure group `reserve`
|
||||||
@builtin
|
@builtin
|
||||||
reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) -> Allocator_Error {
|
reserve_map :: proc(m: ^$T/map[$K]$V, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
|
||||||
return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil
|
return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,7 +450,8 @@ _append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero:
|
|||||||
return 1, nil
|
return 1, nil
|
||||||
} else {
|
} else {
|
||||||
if cap(array) < len(array)+1 {
|
if cap(array) < len(array)+1 {
|
||||||
cap := 2 * cap(array) + max(8, 1)
|
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
|
||||||
|
cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY
|
||||||
|
|
||||||
// do not 'or_return' here as it could be a partial success
|
// do not 'or_return' here as it could be a partial success
|
||||||
if should_zero {
|
if should_zero {
|
||||||
@@ -472,7 +500,7 @@ _append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, l
|
|||||||
return arg_len, nil
|
return arg_len, nil
|
||||||
} else {
|
} else {
|
||||||
if cap(array) < len(array)+arg_len {
|
if cap(array) < len(array)+arg_len {
|
||||||
cap := 2 * cap(array) + max(8, arg_len)
|
cap := 2 * cap(array) + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
|
||||||
|
|
||||||
// do not 'or_return' here as it could be a partial success
|
// do not 'or_return' here as it could be a partial success
|
||||||
if should_zero {
|
if should_zero {
|
||||||
@@ -540,8 +568,23 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The append built-in procedure appends elements to the end of a dynamic array
|
// The append built-in procedure appends elements to the end of a dynamic array
|
||||||
@builtin append :: proc{append_elem, append_elems, append_elem_string}
|
@builtin append :: proc{
|
||||||
@builtin non_zero_append :: proc{non_zero_append_elem, non_zero_append_elems, non_zero_append_elem_string}
|
append_elem,
|
||||||
|
append_elems,
|
||||||
|
append_elem_string,
|
||||||
|
|
||||||
|
append_soa_elem,
|
||||||
|
append_soa_elems,
|
||||||
|
}
|
||||||
|
|
||||||
|
@builtin non_zero_append :: proc{
|
||||||
|
non_zero_append_elem,
|
||||||
|
non_zero_append_elems,
|
||||||
|
non_zero_append_elem_string,
|
||||||
|
|
||||||
|
non_zero_append_soa_elem,
|
||||||
|
non_zero_append_soa_elems,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@builtin
|
@builtin
|
||||||
@@ -721,12 +764,12 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i
|
|||||||
}
|
}
|
||||||
|
|
||||||
@builtin
|
@builtin
|
||||||
reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
|
reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
|
||||||
return _reserve_dynamic_array(array, capacity, true, loc)
|
return _reserve_dynamic_array(array, capacity, true, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@builtin
|
@builtin
|
||||||
non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
|
non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
|
||||||
return _reserve_dynamic_array(array, capacity, false, loc)
|
return _reserve_dynamic_array(array, capacity, false, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,12 +816,12 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@builtin
|
@builtin
|
||||||
resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error {
|
resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
|
||||||
return _resize_dynamic_array(array, length, true, loc=loc)
|
return _resize_dynamic_array(array, length, true, loc=loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@builtin
|
@builtin
|
||||||
non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error {
|
non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
|
||||||
return _resize_dynamic_array(array, length, false, loc=loc)
|
return _resize_dynamic_array(array, length, false, loc=loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Sl
|
|||||||
if array == nil {
|
if array == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
field_count := uintptr(intrinsics.type_struct_field_count(E))
|
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||||
footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr))
|
footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -64,12 +64,7 @@ raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Ra
|
|||||||
if array == nil {
|
if array == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
field_count: uintptr
|
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||||
when intrinsics.type_is_array(E) {
|
|
||||||
field_count = len(E)
|
|
||||||
} else {
|
|
||||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
||||||
}
|
|
||||||
footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr))
|
footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -98,7 +93,7 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc
|
|||||||
ti = type_info_base(ti)
|
ti = type_info_base(ti)
|
||||||
si := &ti.variant.(Type_Info_Struct)
|
si := &ti.variant.(Type_Info_Struct)
|
||||||
|
|
||||||
field_count := uintptr(intrinsics.type_struct_field_count(E))
|
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||||
|
|
||||||
total_size := 0
|
total_size := 0
|
||||||
for i in 0..<field_count {
|
for i in 0..<field_count {
|
||||||
@@ -147,7 +142,7 @@ make_soa_slice :: proc($T: typeid/#soa[]$E, length: int, allocator := context.al
|
|||||||
@(builtin, require_results)
|
@(builtin, require_results)
|
||||||
make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
|
make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
reserve_soa(&array, DEFAULT_RESERVE_CAPACITY, loc) or_return
|
reserve_soa(&array, 0, loc) or_return
|
||||||
return array, nil
|
return array, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +182,28 @@ resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_locat
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@builtin
|
||||||
|
non_zero_resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error {
|
||||||
|
if array == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
non_zero_reserve_soa(array, length, loc) or_return
|
||||||
|
footer := raw_soa_footer(array)
|
||||||
|
footer.len = length
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
@builtin
|
@builtin
|
||||||
reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
|
reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
|
||||||
|
return _reserve_soa(array, capacity, true, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
@builtin
|
||||||
|
non_zero_reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
|
||||||
|
return _reserve_soa(array, capacity, false, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
_reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: bool, loc := #caller_location) -> Allocator_Error {
|
||||||
if array == nil {
|
if array == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -213,12 +228,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
|
|||||||
ti = type_info_base(ti)
|
ti = type_info_base(ti)
|
||||||
si := &ti.variant.(Type_Info_Struct)
|
si := &ti.variant.(Type_Info_Struct)
|
||||||
|
|
||||||
field_count: uintptr
|
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||||
when intrinsics.type_is_array(E) {
|
|
||||||
field_count = len(E)
|
|
||||||
} else {
|
|
||||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
||||||
}
|
|
||||||
assert(footer.cap == old_cap)
|
assert(footer.cap == old_cap)
|
||||||
|
|
||||||
old_size := 0
|
old_size := 0
|
||||||
@@ -238,7 +248,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
|
|||||||
old_data := (^rawptr)(array)^
|
old_data := (^rawptr)(array)^
|
||||||
|
|
||||||
new_bytes := array.allocator.procedure(
|
new_bytes := array.allocator.procedure(
|
||||||
array.allocator.data, .Alloc, new_size, max_align,
|
array.allocator.data, .Alloc if zero_memory else .Alloc_Non_Zeroed, new_size, max_align,
|
||||||
nil, old_size, loc,
|
nil, old_size, loc,
|
||||||
) or_return
|
) or_return
|
||||||
new_data := raw_data(new_bytes)
|
new_data := raw_data(new_bytes)
|
||||||
@@ -273,15 +283,26 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@builtin
|
@builtin
|
||||||
append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||||
|
return _append_soa_elem(array, true, arg, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
@builtin
|
||||||
|
non_zero_append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||||
|
return _append_soa_elem(array, false, arg, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
_append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||||
if array == nil {
|
if array == nil {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if cap(array) <= len(array) + 1 {
|
if cap(array) <= len(array) + 1 {
|
||||||
cap := 2 * cap(array) + 8
|
// Same behavior as append_soa_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
|
||||||
err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success
|
cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY
|
||||||
|
err = _reserve_soa(array, cap, zero_memory, loc) // do not 'or_return' here as it could be a partial success
|
||||||
}
|
}
|
||||||
|
|
||||||
footer := raw_soa_footer(array)
|
footer := raw_soa_footer(array)
|
||||||
@@ -290,12 +311,7 @@ append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_locat
|
|||||||
ti := type_info_of(T)
|
ti := type_info_of(T)
|
||||||
ti = type_info_base(ti)
|
ti = type_info_base(ti)
|
||||||
si := &ti.variant.(Type_Info_Struct)
|
si := &ti.variant.(Type_Info_Struct)
|
||||||
field_count: uintptr
|
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||||
when intrinsics.type_is_array(E) {
|
|
||||||
field_count = len(E)
|
|
||||||
} else {
|
|
||||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
||||||
}
|
|
||||||
|
|
||||||
data := (^rawptr)(array)^
|
data := (^rawptr)(array)^
|
||||||
|
|
||||||
@@ -326,7 +342,17 @@ append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_locat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@builtin
|
@builtin
|
||||||
append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||||
|
return _append_soa_elems(array, true, args=args, loc=loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
@builtin
|
||||||
|
non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||||
|
return _append_soa_elems(array, false, args=args, loc=loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||||
if array == nil {
|
if array == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -337,8 +363,8 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cap(array) <= len(array)+arg_len {
|
if cap(array) <= len(array)+arg_len {
|
||||||
cap := 2 * cap(array) + max(8, arg_len)
|
cap := 2 * cap(array) + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
|
||||||
err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success
|
err = _reserve_soa(array, cap, zero_memory, loc) // do not 'or_return' here as it could be a partial success
|
||||||
}
|
}
|
||||||
arg_len = min(cap(array)-len(array), arg_len)
|
arg_len = min(cap(array)-len(array), arg_len)
|
||||||
|
|
||||||
@@ -347,7 +373,7 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l
|
|||||||
ti := type_info_of(typeid_of(T))
|
ti := type_info_of(typeid_of(T))
|
||||||
ti = type_info_base(ti)
|
ti = type_info_base(ti)
|
||||||
si := &ti.variant.(Type_Info_Struct)
|
si := &ti.variant.(Type_Info_Struct)
|
||||||
field_count := uintptr(intrinsics.type_struct_field_count(E))
|
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||||
|
|
||||||
data := (^rawptr)(array)^
|
data := (^rawptr)(array)^
|
||||||
|
|
||||||
@@ -389,7 +415,8 @@ append_soa :: proc{
|
|||||||
|
|
||||||
|
|
||||||
delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||||
when intrinsics.type_struct_field_count(E) != 0 {
|
field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
|
||||||
|
when field_count != 0 {
|
||||||
array := array
|
array := array
|
||||||
ptr := (^rawptr)(&array)^
|
ptr := (^rawptr)(&array)^
|
||||||
free(ptr, allocator, loc) or_return
|
free(ptr, allocator, loc) or_return
|
||||||
@@ -398,7 +425,8 @@ delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error {
|
delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error {
|
||||||
when intrinsics.type_struct_field_count(E) != 0 {
|
field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
|
||||||
|
when field_count != 0 {
|
||||||
array := array
|
array := array
|
||||||
ptr := (^rawptr)(&array)^
|
ptr := (^rawptr)(&array)^
|
||||||
footer := raw_soa_footer(&array)
|
footer := raw_soa_footer(&array)
|
||||||
@@ -416,7 +444,8 @@ delete_soa :: proc{
|
|||||||
|
|
||||||
|
|
||||||
clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) {
|
clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) {
|
||||||
when intrinsics.type_struct_field_count(E) != 0 {
|
field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
|
||||||
|
when field_count != 0 {
|
||||||
footer := raw_soa_footer(array)
|
footer := raw_soa_footer(array)
|
||||||
footer.len = 0
|
footer.len = 0
|
||||||
}
|
}
|
||||||
@@ -438,12 +467,7 @@ into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E {
|
|||||||
allocator = nil_allocator(),
|
allocator = nil_allocator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
field_count: uintptr
|
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||||
when intrinsics.type_is_array(E) {
|
|
||||||
field_count = len(E)
|
|
||||||
} else {
|
|
||||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
||||||
}
|
|
||||||
|
|
||||||
array := array
|
array := array
|
||||||
dynamic_data := ([^]rawptr)(&d)[:field_count]
|
dynamic_data := ([^]rawptr)(&d)[:field_count]
|
||||||
@@ -467,12 +491,7 @@ unordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #cal
|
|||||||
ti = type_info_base(ti)
|
ti = type_info_base(ti)
|
||||||
si := &ti.variant.(Type_Info_Struct)
|
si := &ti.variant.(Type_Info_Struct)
|
||||||
|
|
||||||
field_count: uintptr
|
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||||
when intrinsics.type_is_array(E) {
|
|
||||||
field_count = len(E)
|
|
||||||
} else {
|
|
||||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
||||||
}
|
|
||||||
|
|
||||||
data := uintptr(array)
|
data := uintptr(array)
|
||||||
for i in 0..<field_count {
|
for i in 0..<field_count {
|
||||||
@@ -500,12 +519,7 @@ ordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #calle
|
|||||||
ti = type_info_base(ti)
|
ti = type_info_base(ti)
|
||||||
si := &ti.variant.(Type_Info_Struct)
|
si := &ti.variant.(Type_Info_Struct)
|
||||||
|
|
||||||
field_count: uintptr
|
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||||
when intrinsics.type_is_array(E) {
|
|
||||||
field_count = len(E)
|
|
||||||
} else {
|
|
||||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
||||||
}
|
|
||||||
|
|
||||||
data := uintptr(array)
|
data := uintptr(array)
|
||||||
for i in 0..<field_count {
|
for i in 0..<field_count {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ when ODIN_DEFAULT_TO_NIL_ALLOCATOR {
|
|||||||
} else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR {
|
} else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR {
|
||||||
default_allocator_proc :: panic_allocator_proc
|
default_allocator_proc :: panic_allocator_proc
|
||||||
default_allocator :: panic_allocator
|
default_allocator :: panic_allocator
|
||||||
} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
|
} else when ODIN_OS != .Orca && (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
|
||||||
default_allocator :: default_wasm_allocator
|
default_allocator :: default_wasm_allocator
|
||||||
default_allocator_proc :: wasm_allocator_proc
|
default_allocator_proc :: wasm_allocator_proc
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+2
-1
@@ -12,7 +12,8 @@ Memory_Block :: struct {
|
|||||||
capacity: uint,
|
capacity: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: This is for internal use, prefer `Arena` from `core:mem/virtual` if necessary
|
// NOTE: This is a growing arena that is only used for the default temp allocator.
|
||||||
|
// For your own growing arena needs, prefer `Arena` from `core:mem/virtual`.
|
||||||
Arena :: struct {
|
Arena :: struct {
|
||||||
backing_allocator: Allocator,
|
backing_allocator: Allocator,
|
||||||
curr_block: ^Memory_Block,
|
curr_block: ^Memory_Block,
|
||||||
@@ -157,7 +157,7 @@ __dynamic_map_get // dynamic map calls
|
|||||||
__dynamic_map_set // dynamic map calls
|
__dynamic_map_set // dynamic map calls
|
||||||
|
|
||||||
|
|
||||||
## Dynamic literals ([dymamic]T and map[K]V) (can be disabled with -no-dynamic-literals)
|
## Dynamic literals ([dynamic]T and map[K]V) (can be disabled with -no-dynamic-literals)
|
||||||
|
|
||||||
__dynamic_array_reserve
|
__dynamic_array_reserve
|
||||||
__dynamic_array_append
|
__dynamic_array_append
|
||||||
|
|||||||
@@ -6,15 +6,34 @@ package runtime
|
|||||||
import "base:intrinsics"
|
import "base:intrinsics"
|
||||||
|
|
||||||
when !ODIN_TEST && !ODIN_NO_ENTRY_POINT {
|
when !ODIN_TEST && !ODIN_NO_ENTRY_POINT {
|
||||||
@(link_name="_start", linkage="strong", require, export)
|
when ODIN_OS == .Orca {
|
||||||
_start :: proc "c" () {
|
@(linkage="strong", require, export)
|
||||||
context = default_context()
|
oc_on_init :: proc "c" () {
|
||||||
#force_no_inline _startup_runtime()
|
context = default_context()
|
||||||
intrinsics.__entry_point()
|
#force_no_inline _startup_runtime()
|
||||||
|
intrinsics.__entry_point()
|
||||||
|
}
|
||||||
|
@(linkage="strong", require, export)
|
||||||
|
oc_on_terminate :: proc "c" () {
|
||||||
|
context = default_context()
|
||||||
|
#force_no_inline _cleanup_runtime()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@(link_name="_start", linkage="strong", require, export)
|
||||||
|
_start :: proc "c" () {
|
||||||
|
context = default_context()
|
||||||
|
|
||||||
|
when ODIN_OS == .WASI {
|
||||||
|
_wasi_setup_args()
|
||||||
|
}
|
||||||
|
|
||||||
|
#force_no_inline _startup_runtime()
|
||||||
|
intrinsics.__entry_point()
|
||||||
|
}
|
||||||
|
@(link_name="_end", linkage="strong", require, export)
|
||||||
|
_end :: proc "c" () {
|
||||||
|
context = default_context()
|
||||||
|
#force_no_inline _cleanup_runtime()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@(link_name="_end", linkage="strong", require, export)
|
}
|
||||||
_end :: proc "c" () {
|
|
||||||
context = default_context()
|
|
||||||
#force_no_inline _cleanup_runtime()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ package runtime
|
|||||||
bounds_trap :: proc "contextless" () -> ! {
|
bounds_trap :: proc "contextless" () -> ! {
|
||||||
when ODIN_OS == .Windows {
|
when ODIN_OS == .Windows {
|
||||||
windows_trap_array_bounds()
|
windows_trap_array_bounds()
|
||||||
|
} else when ODIN_OS == .Orca {
|
||||||
|
abort_ext("", "", 0, "bounds trap")
|
||||||
} else {
|
} else {
|
||||||
trap()
|
trap()
|
||||||
}
|
}
|
||||||
@@ -13,6 +15,8 @@ bounds_trap :: proc "contextless" () -> ! {
|
|||||||
type_assertion_trap :: proc "contextless" () -> ! {
|
type_assertion_trap :: proc "contextless" () -> ! {
|
||||||
when ODIN_OS == .Windows {
|
when ODIN_OS == .Windows {
|
||||||
windows_trap_type_assertion()
|
windows_trap_type_assertion()
|
||||||
|
} else when ODIN_OS == .Orca {
|
||||||
|
abort_ext("", "", 0, "type assertion trap")
|
||||||
} else {
|
} else {
|
||||||
trap()
|
trap()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,14 +97,14 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||||
return _heap_alloc(size, zero_memory)
|
return _heap_alloc(size, zero_memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||||
return _heap_resize(ptr, new_size)
|
return _heap_resize(ptr, new_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
heap_free :: proc(ptr: rawptr) {
|
heap_free :: proc "contextless" (ptr: rawptr) {
|
||||||
_heap_free(ptr)
|
_heap_free(ptr)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//+build orca
|
||||||
|
//+private
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
foreign {
|
||||||
|
@(link_name="malloc") _orca_malloc :: proc "c" (size: int) -> rawptr ---
|
||||||
|
@(link_name="calloc") _orca_calloc :: proc "c" (num, size: int) -> rawptr ---
|
||||||
|
@(link_name="free") _orca_free :: proc "c" (ptr: rawptr) ---
|
||||||
|
@(link_name="realloc") _orca_realloc :: proc "c" (ptr: rawptr, size: int) -> rawptr ---
|
||||||
|
}
|
||||||
|
|
||||||
|
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||||
|
if size <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if zero_memory {
|
||||||
|
return _orca_calloc(1, size)
|
||||||
|
} else {
|
||||||
|
return _orca_malloc(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||||
|
return _orca_realloc(ptr, new_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
_heap_free :: proc "contextless" (ptr: rawptr) {
|
||||||
|
_orca_free(ptr)
|
||||||
|
}
|
||||||
@@ -2,14 +2,17 @@
|
|||||||
//+private
|
//+private
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||||
|
context = default_context()
|
||||||
unimplemented("base:runtime 'heap_alloc' procedure is not supported on this platform")
|
unimplemented("base:runtime 'heap_alloc' procedure is not supported on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||||
|
context = default_context()
|
||||||
unimplemented("base:runtime 'heap_resize' procedure is not supported on this platform")
|
unimplemented("base:runtime 'heap_resize' procedure is not supported on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
_heap_free :: proc(ptr: rawptr) {
|
_heap_free :: proc "contextless" (ptr: rawptr) {
|
||||||
|
context = default_context()
|
||||||
unimplemented("base:runtime 'heap_free' procedure is not supported on this platform")
|
unimplemented("base:runtime 'heap_free' procedure is not supported on this platform")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ foreign libc {
|
|||||||
@(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: int) -> rawptr ---
|
@(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: int) -> rawptr ---
|
||||||
}
|
}
|
||||||
|
|
||||||
_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||||
if size <= 0 {
|
if size <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -27,12 +27,12 @@ _heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||||
// NOTE: _unix_realloc doesn't guarantee new memory will be zeroed on
|
// NOTE: _unix_realloc doesn't guarantee new memory will be zeroed on
|
||||||
// POSIX platforms. Ensure your caller takes this into account.
|
// POSIX platforms. Ensure your caller takes this into account.
|
||||||
return _unix_realloc(ptr, new_size)
|
return _unix_realloc(ptr, new_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
_heap_free :: proc(ptr: rawptr) {
|
_heap_free :: proc "contextless" (ptr: rawptr) {
|
||||||
_unix_free(ptr)
|
_unix_free(ptr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ foreign kernel32 {
|
|||||||
HeapFree :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 ---
|
HeapFree :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 ---
|
||||||
}
|
}
|
||||||
|
|
||||||
_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||||
HEAP_ZERO_MEMORY :: 0x00000008
|
HEAP_ZERO_MEMORY :: 0x00000008
|
||||||
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
|
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
|
||||||
}
|
}
|
||||||
_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||||
if new_size == 0 {
|
if new_size == 0 {
|
||||||
_heap_free(ptr)
|
_heap_free(ptr)
|
||||||
return nil
|
return nil
|
||||||
@@ -30,7 +30,7 @@ _heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
|||||||
HEAP_ZERO_MEMORY :: 0x00000008
|
HEAP_ZERO_MEMORY :: 0x00000008
|
||||||
return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
|
return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
|
||||||
}
|
}
|
||||||
_heap_free :: proc(ptr: rawptr) {
|
_heap_free :: proc "contextless" (ptr: rawptr) {
|
||||||
if ptr == nil {
|
if ptr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-29
@@ -1,3 +1,4 @@
|
|||||||
|
//+vet !cast
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import "base:intrinsics"
|
import "base:intrinsics"
|
||||||
@@ -29,7 +30,7 @@ is_power_of_two_int :: #force_inline proc "contextless" (x: int) -> bool {
|
|||||||
return (x & (x-1)) == 0
|
return (x & (x-1)) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
align_forward_int :: #force_inline proc(ptr, align: int) -> int {
|
align_forward_int :: #force_inline proc "odin" (ptr, align: int) -> int {
|
||||||
assert(is_power_of_two_int(align))
|
assert(is_power_of_two_int(align))
|
||||||
|
|
||||||
p := ptr
|
p := ptr
|
||||||
@@ -47,7 +48,7 @@ is_power_of_two_uint :: #force_inline proc "contextless" (x: uint) -> bool {
|
|||||||
return (x & (x-1)) == 0
|
return (x & (x-1)) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
align_forward_uint :: #force_inline proc(ptr, align: uint) -> uint {
|
align_forward_uint :: #force_inline proc "odin" (ptr, align: uint) -> uint {
|
||||||
assert(is_power_of_two_uint(align))
|
assert(is_power_of_two_uint(align))
|
||||||
|
|
||||||
p := ptr
|
p := ptr
|
||||||
@@ -65,7 +66,7 @@ is_power_of_two_uintptr :: #force_inline proc "contextless" (x: uintptr) -> bool
|
|||||||
return (x & (x-1)) == 0
|
return (x & (x-1)) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr {
|
align_forward_uintptr :: #force_inline proc "odin" (ptr, align: uintptr) -> uintptr {
|
||||||
assert(is_power_of_two_uintptr(align))
|
assert(is_power_of_two_uintptr(align))
|
||||||
|
|
||||||
p := ptr
|
p := ptr
|
||||||
@@ -483,7 +484,7 @@ quaternion256_ne :: #force_inline proc "contextless" (a, b: quaternion256) -> bo
|
|||||||
string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) {
|
string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) {
|
||||||
// NOTE(bill): Duplicated here to remove dependency on package unicode/utf8
|
// NOTE(bill): Duplicated here to remove dependency on package unicode/utf8
|
||||||
|
|
||||||
@static accept_sizes := [256]u8{
|
@(static, rodata) accept_sizes := [256]u8{
|
||||||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f
|
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f
|
||||||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f
|
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f
|
||||||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f
|
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f
|
||||||
@@ -504,7 +505,7 @@ string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int
|
|||||||
}
|
}
|
||||||
Accept_Range :: struct {lo, hi: u8}
|
Accept_Range :: struct {lo, hi: u8}
|
||||||
|
|
||||||
@static accept_ranges := [5]Accept_Range{
|
@(static, rodata) accept_ranges := [5]Accept_Range{
|
||||||
{0x80, 0xbf},
|
{0x80, 0xbf},
|
||||||
{0xa0, 0xbf},
|
{0xa0, 0xbf},
|
||||||
{0x80, 0x9f},
|
{0x80, 0x9f},
|
||||||
@@ -642,21 +643,24 @@ abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64
|
|||||||
|
|
||||||
|
|
||||||
quo_complex32 :: proc "contextless" (n, m: complex32) -> complex32 {
|
quo_complex32 :: proc "contextless" (n, m: complex32) -> complex32 {
|
||||||
e, f: f16
|
nr, ni := f32(real(n)), f32(imag(n))
|
||||||
|
mr, mi := f32(real(m)), f32(imag(m))
|
||||||
|
|
||||||
if abs(real(m)) >= abs(imag(m)) {
|
e, f: f32
|
||||||
ratio := imag(m) / real(m)
|
|
||||||
denom := real(m) + ratio*imag(m)
|
if abs(mr) >= abs(mi) {
|
||||||
e = (real(n) + imag(n)*ratio) / denom
|
ratio := mi / mr
|
||||||
f = (imag(n) - real(n)*ratio) / denom
|
denom := mr + ratio*mi
|
||||||
|
e = (nr + ni*ratio) / denom
|
||||||
|
f = (ni - nr*ratio) / denom
|
||||||
} else {
|
} else {
|
||||||
ratio := real(m) / imag(m)
|
ratio := mr / mi
|
||||||
denom := imag(m) + ratio*real(m)
|
denom := mi + ratio*mr
|
||||||
e = (real(n)*ratio + imag(n)) / denom
|
e = (nr*ratio + ni) / denom
|
||||||
f = (imag(n)*ratio - real(n)) / denom
|
f = (ni*ratio - nr) / denom
|
||||||
}
|
}
|
||||||
|
|
||||||
return complex(e, f)
|
return complex(f16(e), f16(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -697,15 +701,15 @@ quo_complex128 :: proc "contextless" (n, m: complex128) -> complex128 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mul_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
|
mul_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
|
||||||
q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q)
|
q0, q1, q2, q3 := f32(real(q)), f32(imag(q)), f32(jmag(q)), f32(kmag(q))
|
||||||
r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r)
|
r0, r1, r2, r3 := f32(real(r)), f32(imag(r)), f32(jmag(r)), f32(kmag(r))
|
||||||
|
|
||||||
t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3
|
t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3
|
||||||
t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2
|
t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2
|
||||||
t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
|
t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
|
||||||
t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0
|
t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0
|
||||||
|
|
||||||
return quaternion(w=t0, x=t1, y=t2, z=t3)
|
return quaternion(w=f16(t0), x=f16(t1), y=f16(t2), z=f16(t3))
|
||||||
}
|
}
|
||||||
|
|
||||||
mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
|
mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
|
||||||
@@ -733,8 +737,8 @@ mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
|
quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
|
||||||
q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q)
|
q0, q1, q2, q3 := f32(real(q)), f32(imag(q)), f32(jmag(q)), f32(kmag(q))
|
||||||
r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r)
|
r0, r1, r2, r3 := f32(real(r)), f32(imag(r)), f32(jmag(r)), f32(kmag(r))
|
||||||
|
|
||||||
invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3)
|
invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3)
|
||||||
|
|
||||||
@@ -743,7 +747,7 @@ quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
|
|||||||
t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2
|
t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2
|
||||||
t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2
|
t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2
|
||||||
|
|
||||||
return quaternion(w=t0, x=t1, y=t2, z=t3)
|
return quaternion(w=f16(t0), x=f16(t1), y=f16(t2), z=f16(t3))
|
||||||
}
|
}
|
||||||
|
|
||||||
quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
|
quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
|
||||||
@@ -1012,26 +1016,26 @@ modti3 :: proc "c" (a, b: i128) -> i128 {
|
|||||||
bn := (b ~ s_b) - s_b
|
bn := (b ~ s_b) - s_b
|
||||||
|
|
||||||
r: u128 = ---
|
r: u128 = ---
|
||||||
_ = udivmod128(transmute(u128)an, transmute(u128)bn, &r)
|
_ = udivmod128(u128(an), u128(bn), &r)
|
||||||
return (transmute(i128)r ~ s_a) - s_a
|
return (i128(r) ~ s_a) - s_a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
@(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||||
divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 {
|
divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 {
|
||||||
u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem)
|
u := udivmod128(u128(a), u128(b), (^u128)(rem))
|
||||||
return transmute(i128)u
|
return i128(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
@(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||||
divti3 :: proc "c" (a, b: i128) -> i128 {
|
divti3 :: proc "c" (a, b: i128) -> i128 {
|
||||||
u := udivmodti4(transmute(u128)a, transmute(u128)b, nil)
|
u := udivmodti4(u128(a), u128(b), nil)
|
||||||
return transmute(i128)u
|
return i128(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||||
fixdfti :: proc(a: u64) -> i128 {
|
fixdfti :: proc "c" (a: u64) -> i128 {
|
||||||
significandBits :: 52
|
significandBits :: 52
|
||||||
typeWidth :: (size_of(u64)*8)
|
typeWidth :: (size_of(u64)*8)
|
||||||
exponentBits :: (typeWidth - significandBits - 1)
|
exponentBits :: (typeWidth - significandBits - 1)
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
//+build orca
|
||||||
|
//+private
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import "base:intrinsics"
|
||||||
|
|
||||||
|
// Constants allowing to specify the level of logging verbosity.
|
||||||
|
log_level :: enum u32 {
|
||||||
|
// Only errors are logged.
|
||||||
|
ERROR = 0,
|
||||||
|
// Only warnings and errors are logged.
|
||||||
|
WARNING = 1,
|
||||||
|
// All messages are logged.
|
||||||
|
INFO = 2,
|
||||||
|
COUNT = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
@(default_calling_convention="c", link_prefix="oc_")
|
||||||
|
foreign {
|
||||||
|
abort_ext :: proc(file: cstring, function: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) -> ! ---
|
||||||
|
assert_fail :: proc(file: cstring, function: cstring, line: i32, src: cstring, fmt: cstring, #c_vararg args: ..any) -> ! ---
|
||||||
|
log_ext :: proc(level: log_level, function: cstring, file: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) ---
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This is all pretty gross, don't look.
|
||||||
|
|
||||||
|
// WASM is single threaded so this should be fine.
|
||||||
|
orca_stderr_buffer: [4096]byte
|
||||||
|
orca_stderr_buffer_idx: int
|
||||||
|
|
||||||
|
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||||
|
for b in data {
|
||||||
|
orca_stderr_buffer[orca_stderr_buffer_idx] = b
|
||||||
|
orca_stderr_buffer_idx += 1
|
||||||
|
|
||||||
|
if b == '\n' || orca_stderr_buffer_idx == len(orca_stderr_buffer)-1 {
|
||||||
|
log_ext(.ERROR, "", "", 0, cstring(raw_data(orca_stderr_buffer[:orca_stderr_buffer_idx])))
|
||||||
|
orca_stderr_buffer_idx = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(data), 0
|
||||||
|
}
|
||||||
@@ -2,10 +2,54 @@
|
|||||||
//+private
|
//+private
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import "core:sys/wasm/wasi"
|
foreign import wasi "wasi_snapshot_preview1"
|
||||||
|
|
||||||
|
@(default_calling_convention="contextless")
|
||||||
|
foreign wasi {
|
||||||
|
fd_write :: proc(
|
||||||
|
fd: i32,
|
||||||
|
iovs: [][]byte,
|
||||||
|
n: ^uint,
|
||||||
|
) -> u16 ---
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
args_sizes_get :: proc(
|
||||||
|
num_of_args: ^uint,
|
||||||
|
size_of_args: ^uint,
|
||||||
|
) -> u16 ---
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
args_get :: proc(
|
||||||
|
argv: [^]cstring,
|
||||||
|
argv_buf: [^]byte,
|
||||||
|
) -> u16 ---
|
||||||
|
}
|
||||||
|
|
||||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||||
data_iovec := (wasi.ciovec_t)(data)
|
n: uint
|
||||||
n, err := wasi.fd_write(1, {data_iovec})
|
err := fd_write(1, {data}, &n)
|
||||||
return int(n), _OS_Errno(err)
|
return int(n), _OS_Errno(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_wasi_setup_args :: proc() {
|
||||||
|
num_of_args, size_of_args: uint
|
||||||
|
if errno := args_sizes_get(&num_of_args, &size_of_args); errno != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err: Allocator_Error
|
||||||
|
if args__, err = make([]cstring, num_of_args); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
args_buf: []byte
|
||||||
|
if args_buf, err = make([]byte, size_of_args); err != nil {
|
||||||
|
delete(args__)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if errno := args_get(raw_data(args__), raw_data(args_buf)); errno != 0 {
|
||||||
|
delete(args__)
|
||||||
|
delete(args_buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
|
|||||||
RtlMoveMemory(dst, src, len)
|
RtlMoveMemory(dst, src, len)
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
} else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
|
} else when ODIN_NO_CRT || (ODIN_OS != .Orca && (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32)) {
|
||||||
// NOTE: on wasm, calls to these procs are generated (by LLVM) with type `i32` instead of `int`.
|
// NOTE: on wasm, calls to these procs are generated (by LLVM) with type `i32` instead of `int`.
|
||||||
//
|
//
|
||||||
// NOTE: `#any_int` is also needed, because calls that we generate (and package code)
|
// NOTE: `#any_int` is also needed, because calls that we generate (and package code)
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import "base:intrinsics"
|
||||||
|
|
||||||
|
@(require_results)
|
||||||
|
random_generator_read_bytes :: proc(rg: Random_Generator, p: []byte) -> bool {
|
||||||
|
if rg.procedure != nil {
|
||||||
|
rg.procedure(rg.data, .Read, p)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@(require_results)
|
||||||
|
random_generator_read_ptr :: proc(rg: Random_Generator, p: rawptr, len: uint) -> bool {
|
||||||
|
if rg.procedure != nil {
|
||||||
|
rg.procedure(rg.data, .Read, ([^]byte)(p)[:len])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@(require_results)
|
||||||
|
random_generator_query_info :: proc(rg: Random_Generator) -> (info: Random_Generator_Query_Info) {
|
||||||
|
if rg.procedure != nil {
|
||||||
|
rg.procedure(rg.data, .Query_Info, ([^]byte)(&info)[:size_of(info)])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
random_generator_reset_bytes :: proc(rg: Random_Generator, p: []byte) {
|
||||||
|
if rg.procedure != nil {
|
||||||
|
rg.procedure(rg.data, .Reset, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
random_generator_reset_u64 :: proc(rg: Random_Generator, p: u64) {
|
||||||
|
if rg.procedure != nil {
|
||||||
|
p := p
|
||||||
|
rg.procedure(rg.data, .Reset, ([^]byte)(&p)[:size_of(p)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Default_Random_State :: struct {
|
||||||
|
state: u64,
|
||||||
|
inc: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
default_random_generator_proc :: proc(data: rawptr, mode: Random_Generator_Mode, p: []byte) {
|
||||||
|
@(require_results)
|
||||||
|
read_u64 :: proc "contextless" (r: ^Default_Random_State) -> u64 {
|
||||||
|
old_state := r.state
|
||||||
|
r.state = old_state * 6364136223846793005 + (r.inc|1)
|
||||||
|
xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081
|
||||||
|
rot := (old_state >> 59)
|
||||||
|
return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(thread_local)
|
||||||
|
global_rand_seed: Default_Random_State
|
||||||
|
|
||||||
|
init :: proc "contextless" (r: ^Default_Random_State, seed: u64) {
|
||||||
|
seed := seed
|
||||||
|
if seed == 0 {
|
||||||
|
seed = u64(intrinsics.read_cycle_counter())
|
||||||
|
}
|
||||||
|
r.state = 0
|
||||||
|
r.inc = (seed << 1) | 1
|
||||||
|
_ = read_u64(r)
|
||||||
|
r.state += seed
|
||||||
|
_ = read_u64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
r: ^Default_Random_State = ---
|
||||||
|
if data == nil {
|
||||||
|
r = &global_rand_seed
|
||||||
|
} else {
|
||||||
|
r = cast(^Default_Random_State)data
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case .Read:
|
||||||
|
if r.state == 0 && r.inc == 0 {
|
||||||
|
init(r, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(p) {
|
||||||
|
case size_of(u64):
|
||||||
|
// Fast path for a 64-bit destination.
|
||||||
|
intrinsics.unaligned_store((^u64)(raw_data(p)), read_u64(r))
|
||||||
|
case:
|
||||||
|
// All other cases.
|
||||||
|
pos := i8(0)
|
||||||
|
val := u64(0)
|
||||||
|
for &v in p {
|
||||||
|
if pos == 0 {
|
||||||
|
val = read_u64(r)
|
||||||
|
pos = 7
|
||||||
|
}
|
||||||
|
v = byte(val)
|
||||||
|
val >>= 8
|
||||||
|
pos -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .Reset:
|
||||||
|
seed: u64
|
||||||
|
mem_copy_non_overlapping(&seed, raw_data(p), min(size_of(seed), len(p)))
|
||||||
|
init(r, seed)
|
||||||
|
|
||||||
|
case .Query_Info:
|
||||||
|
if len(p) != size_of(Random_Generator_Query_Info) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info := (^Random_Generator_Query_Info)(raw_data(p))
|
||||||
|
info^ += {.Uniform, .Resettable}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default_random_generator :: proc "contextless" (state: ^Default_Random_State = nil) -> Random_Generator {
|
||||||
|
return {
|
||||||
|
procedure = default_random_generator_proc,
|
||||||
|
data = state,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,7 +58,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
|
|||||||
return u128(n[high] >> _ctz(d[high]))
|
return u128(n[high] >> _ctz(d[high]))
|
||||||
}
|
}
|
||||||
|
|
||||||
sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high])))
|
sr = u32(i32(_clz(d[high])) - i32(_clz(n[high])))
|
||||||
if sr > U64_BITS - 2 {
|
if sr > U64_BITS - 2 {
|
||||||
if rem != nil {
|
if rem != nil {
|
||||||
rem^ = a
|
rem^ = a
|
||||||
@@ -107,7 +107,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
|
|||||||
r[low] = n[high] >> (sr - U64_BITS)
|
r[low] = n[high] >> (sr - U64_BITS)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high])))
|
sr = u32(i32(_clz(d[high])) - i32(_clz(n[high])))
|
||||||
|
|
||||||
if sr > U64_BITS - 1 {
|
if sr > U64_BITS - 1 {
|
||||||
if rem != nil {
|
if rem != nil {
|
||||||
@@ -143,7 +143,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
|
|||||||
r_all = transmute(u128)r
|
r_all = transmute(u128)r
|
||||||
s := i128(b - r_all - 1) >> (U128_BITS - 1)
|
s := i128(b - r_all - 1) >> (U128_BITS - 1)
|
||||||
carry = u32(s & 1)
|
carry = u32(s & 1)
|
||||||
r_all -= b & transmute(u128)s
|
r_all -= b & u128(s)
|
||||||
r = transmute([2]u64)r_all
|
r = transmute([2]u64)r_all
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,20 +7,20 @@ import "base:intrinsics"
|
|||||||
Port of emmalloc, modified for use in Odin.
|
Port of emmalloc, modified for use in Odin.
|
||||||
|
|
||||||
Invariants:
|
Invariants:
|
||||||
- Per-allocation header overhead is 8 bytes, smallest allocated payload
|
- Per-allocation header overhead is 8 bytes, smallest allocated payload
|
||||||
amount is 8 bytes, and a multiple of 4 bytes.
|
amount is 8 bytes, and a multiple of 4 bytes.
|
||||||
- Acquired memory blocks are subdivided into disjoint regions that lie
|
- Acquired memory blocks are subdivided into disjoint regions that lie
|
||||||
next to each other.
|
next to each other.
|
||||||
- A region is either in used or free.
|
- A region is either in used or free.
|
||||||
Used regions may be adjacent, and a used and unused region
|
Used regions may be adjacent, and a used and unused region
|
||||||
may be adjacent, but not two unused ones - they would be
|
may be adjacent, but not two unused ones - they would be
|
||||||
merged.
|
merged.
|
||||||
- Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow()
|
- Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow()
|
||||||
or memory is very close to being exhausted.
|
or memory is very close to being exhausted.
|
||||||
- Free and used regions are managed inside "root regions", which are slabs
|
- Free and used regions are managed inside "root regions", which are slabs
|
||||||
of memory acquired via wasm_memory_grow().
|
of memory acquired via wasm_memory_grow().
|
||||||
- Memory retrieved using wasm_memory_grow() can not be given back to the OS.
|
- Memory retrieved using wasm_memory_grow() can not be given back to the OS.
|
||||||
Therefore, frees are internal to the allocator.
|
Therefore, frees are internal to the allocator.
|
||||||
|
|
||||||
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
|
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
|
||||||
|
|
||||||
@@ -495,7 +495,7 @@ claim_more_memory :: proc(a: ^WASM_Allocator, num_bytes: uint) -> bool {
|
|||||||
// we can just extend the spill.
|
// we can just extend the spill.
|
||||||
spill_end := uintptr(raw_data(a.spill)) + uintptr(len(a.spill))
|
spill_end := uintptr(raw_data(a.spill)) + uintptr(len(a.spill))
|
||||||
if spill_end == uintptr(raw_data(allocated)) {
|
if spill_end == uintptr(raw_data(allocated)) {
|
||||||
raw_spill := transmute(^Raw_Slice)(&a.spill)
|
raw_spill := (^Raw_Slice)(&a.spill)
|
||||||
raw_spill.len += len(allocated)
|
raw_spill.len += len(allocated)
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we have to "waste" the previous spill.
|
// Otherwise, we have to "waste" the previous spill.
|
||||||
@@ -679,7 +679,7 @@ allocate_memory :: proc(a: ^WASM_Allocator, alignment: uint, size: uint, loc :=
|
|||||||
// but we just had a stale bit set to mark a populated bucket.
|
// but we just had a stale bit set to mark a populated bucket.
|
||||||
// Reset the bit to update latest status so that we do not
|
// Reset the bit to update latest status so that we do not
|
||||||
// redundantly look at this bucket again.
|
// redundantly look at this bucket again.
|
||||||
a.free_region_buckets_used &= ~(BUCKET_BITMASK_T(1) << bucket_index)
|
a.free_region_buckets_used &~= BUCKET_BITMASK_T(1) << bucket_index
|
||||||
bucket_mask ~= 1
|
bucket_mask ~= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -760,7 +760,7 @@ free :: proc(a: ^WASM_Allocator, ptr: rawptr, loc := #caller_location) {
|
|||||||
defer unlock(a)
|
defer unlock(a)
|
||||||
|
|
||||||
size := region.size
|
size := region.size
|
||||||
assert(region_is_in_use(region), "double free", loc=loc)
|
assert(region_is_in_use(region), "double free or corrupt region", loc=loc)
|
||||||
|
|
||||||
prev_region_size_field := ([^]uint)(region)[-1]
|
prev_region_size_field := ([^]uint)(region)[-1]
|
||||||
prev_region_size := prev_region_size_field & ~uint(FREE_REGION_FLAG)
|
prev_region_size := prev_region_size_field & ~uint(FREE_REGION_FLAG)
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import datetime
|
|
||||||
import urllib.parse
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def main():
|
|
||||||
files_by_date = {}
|
|
||||||
bucket = sys.argv[1]
|
|
||||||
|
|
||||||
files_lines = execute_cli(f"b2 ls --long b2://{bucket}/nightly/").split("\n")
|
|
||||||
for x in files_lines:
|
|
||||||
parts = x.split(" ", 1)
|
|
||||||
if parts[0]:
|
|
||||||
json_str = execute_cli(f"b2 file info {parts[0]}")
|
|
||||||
data = json.loads(json_str)
|
|
||||||
name = remove_prefix(data['fileName'], "nightly/")
|
|
||||||
url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}"
|
|
||||||
sha1 = data['contentSha1']
|
|
||||||
size = int(data['size'])
|
|
||||||
ts = int(data['fileInfo']['src_last_modified_millis'])
|
|
||||||
date = datetime.datetime.fromtimestamp(ts/1000).strftime('%Y-%m-%d')
|
|
||||||
|
|
||||||
if date not in files_by_date.keys():
|
|
||||||
files_by_date[date] = []
|
|
||||||
|
|
||||||
files_by_date[date].append({
|
|
||||||
'name': name,
|
|
||||||
'url': url,
|
|
||||||
'sha1': sha1,
|
|
||||||
'sizeInBytes': size,
|
|
||||||
})
|
|
||||||
|
|
||||||
now = datetime.datetime.utcnow().isoformat()
|
|
||||||
|
|
||||||
print(json.dumps({
|
|
||||||
'last_updated' : now,
|
|
||||||
'files': files_by_date
|
|
||||||
}, sort_keys=True, indent=4))
|
|
||||||
|
|
||||||
def remove_prefix(text, prefix):
|
|
||||||
return text[text.startswith(prefix) and len(prefix):]
|
|
||||||
|
|
||||||
def execute_cli(command):
|
|
||||||
sb = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
|
|
||||||
return sb.stdout.read().decode("utf-8");
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import datetime
|
|
||||||
import urllib.parse
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def main():
|
|
||||||
files_by_date = {}
|
|
||||||
bucket = sys.argv[1]
|
|
||||||
days_to_keep = int(sys.argv[2])
|
|
||||||
print(f"Looking for binaries to delete older than {days_to_keep} days")
|
|
||||||
|
|
||||||
files_lines = execute_cli(f"b2 ls --long --versions b2://{bucket}/nightly/").split("\n")
|
|
||||||
for x in files_lines:
|
|
||||||
parts = [y for y in x.split(' ') if y]
|
|
||||||
|
|
||||||
if parts and parts[0]:
|
|
||||||
date = datetime.datetime.strptime(parts[2], '%Y-%m-%d').replace(hour=0, minute=0, second=0, microsecond=0)
|
|
||||||
now = datetime.datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
||||||
delta = now - date
|
|
||||||
|
|
||||||
if delta.days > days_to_keep:
|
|
||||||
print(f'Deleting {parts[5]}')
|
|
||||||
execute_cli(f'b2 rm {parts[0]}')
|
|
||||||
|
|
||||||
|
|
||||||
def execute_cli(command):
|
|
||||||
sb = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
|
|
||||||
return sb.stdout.read().decode("utf-8");
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from zipfile import ZipFile, ZIP_DEFLATED
|
||||||
|
from b2sdk.v2 import InMemoryAccountInfo, B2Api
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
|
||||||
|
UPLOAD_FOLDER = "nightly/"
|
||||||
|
|
||||||
|
info = InMemoryAccountInfo()
|
||||||
|
b2_api = B2Api(info)
|
||||||
|
application_key_id = os.environ['APPID']
|
||||||
|
application_key = os.environ['APPKEY']
|
||||||
|
bucket_name = os.environ['BUCKET']
|
||||||
|
days_to_keep = os.environ['DAYS_TO_KEEP']
|
||||||
|
|
||||||
|
def auth() -> bool:
|
||||||
|
try:
|
||||||
|
realm = b2_api.account_info.get_realm()
|
||||||
|
return True # Already authenticated
|
||||||
|
except:
|
||||||
|
pass # Not yet authenticated
|
||||||
|
|
||||||
|
err = b2_api.authorize_account("production", application_key_id, application_key)
|
||||||
|
return err == None
|
||||||
|
|
||||||
|
def get_bucket():
|
||||||
|
if not auth(): sys.exit(1)
|
||||||
|
return b2_api.get_bucket_by_name(bucket_name)
|
||||||
|
|
||||||
|
def remove_prefix(text: str, prefix: str) -> str:
|
||||||
|
return text[text.startswith(prefix) and len(prefix):]
|
||||||
|
|
||||||
|
def create_and_upload_artifact_zip(platform: str, artifact: str) -> int:
|
||||||
|
now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
destination_zip_name = "odin-{}-nightly+{}.zip".format(platform, now.strftime("%Y-%m-%d"))
|
||||||
|
|
||||||
|
source_zip_name = artifact
|
||||||
|
if not artifact.endswith(".zip"):
|
||||||
|
print(f"Creating archive {destination_zip_name} from {artifact} and uploading to {bucket_name}")
|
||||||
|
|
||||||
|
source_zip_name = destination_zip_name
|
||||||
|
with ZipFile(source_zip_name, mode='w', compression=ZIP_DEFLATED, compresslevel=9) as z:
|
||||||
|
for root, directory, filenames in os.walk(artifact):
|
||||||
|
for file in filenames:
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
zip_path = os.path.join("dist", os.path.relpath(file_path, artifact))
|
||||||
|
z.write(file_path, zip_path)
|
||||||
|
|
||||||
|
if not os.path.exists(source_zip_name):
|
||||||
|
print(f"Error: Newly created ZIP archive {source_zip_name} not found.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print("Uploading {} to {}".format(source_zip_name, UPLOAD_FOLDER + destination_zip_name))
|
||||||
|
bucket = get_bucket()
|
||||||
|
res = bucket.upload_local_file(
|
||||||
|
source_zip_name, # Local file to upload
|
||||||
|
"nightly/" + destination_zip_name, # B2 destination path
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def prune_artifacts():
|
||||||
|
print(f"Looking for binaries to delete older than {days_to_keep} days")
|
||||||
|
|
||||||
|
bucket = get_bucket()
|
||||||
|
for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=False):
|
||||||
|
# Timestamp is in milliseconds
|
||||||
|
date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
delta = now - date
|
||||||
|
|
||||||
|
if delta.days > int(days_to_keep):
|
||||||
|
print("Deleting {}".format(file.file_name))
|
||||||
|
file.delete()
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def update_nightly_json():
|
||||||
|
print(f"Updating nightly.json with files {days_to_keep} days or newer")
|
||||||
|
|
||||||
|
files_by_date = {}
|
||||||
|
|
||||||
|
bucket = get_bucket()
|
||||||
|
|
||||||
|
for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=True):
|
||||||
|
# Timestamp is in milliseconds
|
||||||
|
date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0).strftime('%Y-%m-%d')
|
||||||
|
name = remove_prefix(file.file_name, UPLOAD_FOLDER)
|
||||||
|
sha1 = file.content_sha1
|
||||||
|
size = file.size
|
||||||
|
url = bucket.get_download_url(file.file_name)
|
||||||
|
|
||||||
|
if date not in files_by_date.keys():
|
||||||
|
files_by_date[date] = []
|
||||||
|
|
||||||
|
files_by_date[date].append({
|
||||||
|
'name': name,
|
||||||
|
'url': url,
|
||||||
|
'sha1': sha1,
|
||||||
|
'sizeInBytes': size,
|
||||||
|
})
|
||||||
|
|
||||||
|
now = datetime.utcnow().isoformat()
|
||||||
|
|
||||||
|
nightly = json.dumps({
|
||||||
|
'last_updated' : now,
|
||||||
|
'files': files_by_date
|
||||||
|
}, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8')
|
||||||
|
|
||||||
|
res = bucket.upload_bytes(
|
||||||
|
nightly, # JSON bytes
|
||||||
|
"nightly.json", # B2 destination path
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
print("Usage: {} <verb> [arguments]".format(sys.argv[0]))
|
||||||
|
print("\tartifact <platform prefix> <artifact path>\n\t\tCreates and uploads a platform artifact zip.")
|
||||||
|
print("\tprune\n\t\tDeletes old artifacts from bucket")
|
||||||
|
print("\tjson\n\t\tUpdate and upload nightly.json")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
command = sys.argv[1].lower()
|
||||||
|
if command == "artifact":
|
||||||
|
if len(sys.argv) != 4:
|
||||||
|
print("Usage: {} artifact <platform prefix> <artifact path>".format(sys.argv[0]))
|
||||||
|
print("Error: Expected artifact command to be given platform prefix and artifact path.\n")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
res = create_and_upload_artifact_zip(sys.argv[2], sys.argv[3])
|
||||||
|
sys.exit(res)
|
||||||
|
|
||||||
|
elif command == "prune":
|
||||||
|
res = prune_artifacts()
|
||||||
|
sys.exit(res)
|
||||||
|
|
||||||
|
elif command == "json":
|
||||||
|
res = update_nightly_json()
|
||||||
|
sys.exit(res)
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
bucket=$1
|
|
||||||
platform=$2
|
|
||||||
artifact=$3
|
|
||||||
|
|
||||||
now=$(date +'%Y-%m-%d')
|
|
||||||
filename="odin-$platform-nightly+$now.zip"
|
|
||||||
|
|
||||||
echo "Creating archive $filename from $artifact and uploading to $bucket"
|
|
||||||
|
|
||||||
# If this is already zipped up (done before artifact upload to keep permissions in tact), just move it.
|
|
||||||
if [ "${artifact: -4}" == ".zip" ]
|
|
||||||
then
|
|
||||||
echo "Artifact already a zip"
|
|
||||||
mkdir -p "output"
|
|
||||||
mv "$artifact" "output/$filename"
|
|
||||||
else
|
|
||||||
echo "Artifact needs to be zipped"
|
|
||||||
7z a -bd "output/$filename" -r "$artifact"
|
|
||||||
fi
|
|
||||||
|
|
||||||
b2 file upload "$bucket" "output/$filename" "nightly/$filename"
|
|
||||||
@@ -29,12 +29,12 @@ MIN_READ_BUFFER_SIZE :: 16
|
|||||||
@(private)
|
@(private)
|
||||||
DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128
|
DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128
|
||||||
|
|
||||||
reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) {
|
reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator, loc := #caller_location) {
|
||||||
size := size
|
size := size
|
||||||
size = max(size, MIN_READ_BUFFER_SIZE)
|
size = max(size, MIN_READ_BUFFER_SIZE)
|
||||||
reader_reset(b, rd)
|
reader_reset(b, rd)
|
||||||
b.buf_allocator = allocator
|
b.buf_allocator = allocator
|
||||||
b.buf = make([]byte, size, allocator)
|
b.buf = make([]byte, size, allocator, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) {
|
reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) {
|
||||||
|
|||||||
+35
-35
@@ -27,19 +27,19 @@ Read_Op :: enum i8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
buffer_init :: proc(b: ^Buffer, buf: []byte) {
|
buffer_init :: proc(b: ^Buffer, buf: []byte, loc := #caller_location) {
|
||||||
resize(&b.buf, len(buf))
|
resize(&b.buf, len(buf), loc=loc)
|
||||||
copy(b.buf[:], buf)
|
copy(b.buf[:], buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_init_string :: proc(b: ^Buffer, s: string) {
|
buffer_init_string :: proc(b: ^Buffer, s: string, loc := #caller_location) {
|
||||||
resize(&b.buf, len(s))
|
resize(&b.buf, len(s), loc=loc)
|
||||||
copy(b.buf[:], s)
|
copy(b.buf[:], s)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator) {
|
buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator, loc := #caller_location) {
|
||||||
if b.buf == nil {
|
if b.buf == nil {
|
||||||
b.buf = make([dynamic]byte, len, cap, allocator)
|
b.buf = make([dynamic]byte, len, cap, allocator, loc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,28 +96,28 @@ buffer_truncate :: proc(b: ^Buffer, n: int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
_buffer_try_grow :: proc(b: ^Buffer, n: int) -> (int, bool) {
|
_buffer_try_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> (int, bool) {
|
||||||
if l := len(b.buf); n <= cap(b.buf)-l {
|
if l := len(b.buf); n <= cap(b.buf)-l {
|
||||||
resize(&b.buf, l+n)
|
resize(&b.buf, l+n, loc=loc)
|
||||||
return l, true
|
return l, true
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
_buffer_grow :: proc(b: ^Buffer, n: int) -> int {
|
_buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> int {
|
||||||
m := buffer_length(b)
|
m := buffer_length(b)
|
||||||
if m == 0 && b.off != 0 {
|
if m == 0 && b.off != 0 {
|
||||||
buffer_reset(b)
|
buffer_reset(b)
|
||||||
}
|
}
|
||||||
if i, ok := _buffer_try_grow(b, n); ok {
|
if i, ok := _buffer_try_grow(b, n, loc=loc); ok {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.buf == nil && n <= SMALL_BUFFER_SIZE {
|
if b.buf == nil && n <= SMALL_BUFFER_SIZE {
|
||||||
// Fixes #2756 by preserving allocator if already set on Buffer via init_buffer_allocator
|
// Fixes #2756 by preserving allocator if already set on Buffer via init_buffer_allocator
|
||||||
reserve(&b.buf, SMALL_BUFFER_SIZE)
|
reserve(&b.buf, SMALL_BUFFER_SIZE, loc=loc)
|
||||||
resize(&b.buf, n)
|
resize(&b.buf, n, loc=loc)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,31 +127,31 @@ _buffer_grow :: proc(b: ^Buffer, n: int) -> int {
|
|||||||
} else if c > max(int) - c - n {
|
} else if c > max(int) - c - n {
|
||||||
panic("bytes.Buffer: too large")
|
panic("bytes.Buffer: too large")
|
||||||
} else {
|
} else {
|
||||||
resize(&b.buf, 2*c + n)
|
resize(&b.buf, 2*c + n, loc=loc)
|
||||||
copy(b.buf[:], b.buf[b.off:])
|
copy(b.buf[:], b.buf[b.off:])
|
||||||
}
|
}
|
||||||
b.off = 0
|
b.off = 0
|
||||||
resize(&b.buf, m+n)
|
resize(&b.buf, m+n, loc=loc)
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_grow :: proc(b: ^Buffer, n: int) {
|
buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) {
|
||||||
if n < 0 {
|
if n < 0 {
|
||||||
panic("bytes.buffer_grow: negative count")
|
panic("bytes.buffer_grow: negative count")
|
||||||
}
|
}
|
||||||
m := _buffer_grow(b, n)
|
m := _buffer_grow(b, n, loc=loc)
|
||||||
resize(&b.buf, m)
|
resize(&b.buf, m, loc=loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
|
buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) {
|
||||||
b.last_read = .Invalid
|
b.last_read = .Invalid
|
||||||
if offset < 0 {
|
if offset < 0 {
|
||||||
err = .Invalid_Offset
|
err = .Invalid_Offset
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, ok := _buffer_try_grow(b, offset+len(p))
|
_, ok := _buffer_try_grow(b, offset+len(p), loc=loc)
|
||||||
if !ok {
|
if !ok {
|
||||||
_ = _buffer_grow(b, offset+len(p))
|
_ = _buffer_grow(b, offset+len(p), loc=loc)
|
||||||
}
|
}
|
||||||
if len(b.buf) <= offset {
|
if len(b.buf) <= offset {
|
||||||
return 0, .Short_Write
|
return 0, .Short_Write
|
||||||
@@ -160,47 +160,47 @@ buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) {
|
buffer_write :: proc(b: ^Buffer, p: []byte, loc := #caller_location) -> (n: int, err: io.Error) {
|
||||||
b.last_read = .Invalid
|
b.last_read = .Invalid
|
||||||
m, ok := _buffer_try_grow(b, len(p))
|
m, ok := _buffer_try_grow(b, len(p), loc=loc)
|
||||||
if !ok {
|
if !ok {
|
||||||
m = _buffer_grow(b, len(p))
|
m = _buffer_grow(b, len(p), loc=loc)
|
||||||
}
|
}
|
||||||
return copy(b.buf[m:], p), nil
|
return copy(b.buf[m:], p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.Error) {
|
buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int, loc := #caller_location) -> (n: int, err: io.Error) {
|
||||||
return buffer_write(b, ([^]byte)(ptr)[:size])
|
return buffer_write(b, ([^]byte)(ptr)[:size], loc=loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_write_string :: proc(b: ^Buffer, s: string) -> (n: int, err: io.Error) {
|
buffer_write_string :: proc(b: ^Buffer, s: string, loc := #caller_location) -> (n: int, err: io.Error) {
|
||||||
b.last_read = .Invalid
|
b.last_read = .Invalid
|
||||||
m, ok := _buffer_try_grow(b, len(s))
|
m, ok := _buffer_try_grow(b, len(s), loc=loc)
|
||||||
if !ok {
|
if !ok {
|
||||||
m = _buffer_grow(b, len(s))
|
m = _buffer_grow(b, len(s), loc=loc)
|
||||||
}
|
}
|
||||||
return copy(b.buf[m:], s), nil
|
return copy(b.buf[m:], s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_write_byte :: proc(b: ^Buffer, c: byte) -> io.Error {
|
buffer_write_byte :: proc(b: ^Buffer, c: byte, loc := #caller_location) -> io.Error {
|
||||||
b.last_read = .Invalid
|
b.last_read = .Invalid
|
||||||
m, ok := _buffer_try_grow(b, 1)
|
m, ok := _buffer_try_grow(b, 1, loc=loc)
|
||||||
if !ok {
|
if !ok {
|
||||||
m = _buffer_grow(b, 1)
|
m = _buffer_grow(b, 1, loc=loc)
|
||||||
}
|
}
|
||||||
b.buf[m] = c
|
b.buf[m] = c
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_write_rune :: proc(b: ^Buffer, r: rune) -> (n: int, err: io.Error) {
|
buffer_write_rune :: proc(b: ^Buffer, r: rune, loc := #caller_location) -> (n: int, err: io.Error) {
|
||||||
if r < utf8.RUNE_SELF {
|
if r < utf8.RUNE_SELF {
|
||||||
buffer_write_byte(b, byte(r))
|
buffer_write_byte(b, byte(r), loc=loc)
|
||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
b.last_read = .Invalid
|
b.last_read = .Invalid
|
||||||
m, ok := _buffer_try_grow(b, utf8.UTF_MAX)
|
m, ok := _buffer_try_grow(b, utf8.UTF_MAX, loc=loc)
|
||||||
if !ok {
|
if !ok {
|
||||||
m = _buffer_grow(b, utf8.UTF_MAX)
|
m = _buffer_grow(b, utf8.UTF_MAX, loc=loc)
|
||||||
}
|
}
|
||||||
res: [4]byte
|
res: [4]byte
|
||||||
res, n = utf8.encode_rune(r)
|
res, n = utf8.encode_rune(r)
|
||||||
|
|||||||
+1
-14
@@ -34,20 +34,7 @@ when ODIN_OS == .Windows {
|
|||||||
SIGTERM :: 15
|
SIGTERM :: 15
|
||||||
}
|
}
|
||||||
|
|
||||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD {
|
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin {
|
||||||
SIG_ERR :: rawptr(~uintptr(0))
|
|
||||||
SIG_DFL :: rawptr(uintptr(0))
|
|
||||||
SIG_IGN :: rawptr(uintptr(1))
|
|
||||||
|
|
||||||
SIGABRT :: 6
|
|
||||||
SIGFPE :: 8
|
|
||||||
SIGILL :: 4
|
|
||||||
SIGINT :: 2
|
|
||||||
SIGSEGV :: 11
|
|
||||||
SIGTERM :: 15
|
|
||||||
}
|
|
||||||
|
|
||||||
when ODIN_OS == .Darwin {
|
|
||||||
SIG_ERR :: rawptr(~uintptr(0))
|
SIG_ERR :: rawptr(~uintptr(0))
|
||||||
SIG_DFL :: rawptr(uintptr(0))
|
SIG_DFL :: rawptr(uintptr(0))
|
||||||
SIG_IGN :: rawptr(uintptr(1))
|
SIG_IGN :: rawptr(uintptr(1))
|
||||||
|
|||||||
@@ -102,10 +102,12 @@ when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
|
|||||||
SEEK_END :: 2
|
SEEK_END :: 2
|
||||||
|
|
||||||
foreign libc {
|
foreign libc {
|
||||||
stderr: ^FILE
|
__sF: [3]FILE
|
||||||
stdin: ^FILE
|
|
||||||
stdout: ^FILE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stdin: ^FILE = &__sF[0]
|
||||||
|
stdout: ^FILE = &__sF[1]
|
||||||
|
stderr: ^FILE = &__sF[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
when ODIN_OS == .FreeBSD {
|
when ODIN_OS == .FreeBSD {
|
||||||
@@ -127,9 +129,9 @@ when ODIN_OS == .FreeBSD {
|
|||||||
SEEK_END :: 2
|
SEEK_END :: 2
|
||||||
|
|
||||||
foreign libc {
|
foreign libc {
|
||||||
stderr: ^FILE
|
@(link_name="__stderrp") stderr: ^FILE
|
||||||
stdin: ^FILE
|
@(link_name="__stdinp") stdin: ^FILE
|
||||||
stdout: ^FILE
|
@(link_name="__stdoutp") stdout: ^FILE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ COMPRESS_OUTPUT_ALLOCATE_MIN :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MIN, 1 << 2
|
|||||||
*/
|
*/
|
||||||
when size_of(uintptr) == 8 {
|
when size_of(uintptr) == 8 {
|
||||||
|
|
||||||
// For 64-bit platforms, we set the default max buffer size to 4 GiB,
|
// For 64-bit platforms, we set the default max buffer size to 4 GiB,
|
||||||
// which is GZIP and PKZIP's max payload size.
|
// which is GZIP and PKZIP's max payload size.
|
||||||
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 32))
|
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 32))
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// For 32-bit platforms, we set the default max buffer size to 512 MiB.
|
// For 32-bit platforms, we set the default max buffer size to 512 MiB.
|
||||||
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 29))
|
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 29))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ decompress_slice_to_output_buffer :: proc(input: []u8, output: []u8, model := DE
|
|||||||
validate_model(model) or_return
|
validate_model(model) or_return
|
||||||
|
|
||||||
for inp < inp_end {
|
for inp < inp_end {
|
||||||
val := transmute(i8)input[inp]
|
val := i8(input[inp])
|
||||||
mark := int(-1)
|
mark := int(-1)
|
||||||
|
|
||||||
for val < 0 {
|
for val < 0 {
|
||||||
@@ -274,12 +274,9 @@ compress_string_to_buffer :: proc(input: string, output: []u8, model := DEFAULT_
|
|||||||
out_ptr := raw_data(output[out:])
|
out_ptr := raw_data(output[out:])
|
||||||
|
|
||||||
switch pack.bytes_packed {
|
switch pack.bytes_packed {
|
||||||
case 4:
|
case 4: intrinsics.unaligned_store((^u32)(out_ptr), code)
|
||||||
intrinsics.unaligned_store(transmute(^u32)out_ptr, code)
|
case 2: intrinsics.unaligned_store((^u16)(out_ptr), u16(code))
|
||||||
case 2:
|
case 1: intrinsics.unaligned_store( (^u8)(out_ptr), u8(code))
|
||||||
intrinsics.unaligned_store(transmute(^u16)out_ptr, u16(code))
|
|
||||||
case 1:
|
|
||||||
intrinsics.unaligned_store(transmute(^u8)out_ptr, u8(code))
|
|
||||||
case:
|
case:
|
||||||
return out, .Unknown_Compression_Method
|
return out, .Unknown_Compression_Method
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ init_cmp :: proc(
|
|||||||
init_ordered :: proc(
|
init_ordered :: proc(
|
||||||
t: ^$T/Tree($Value),
|
t: ^$T/Tree($Value),
|
||||||
node_allocator := context.allocator,
|
node_allocator := context.allocator,
|
||||||
) where intrinsics.type_is_ordered_numeric(Value) {
|
) where intrinsics.type_is_ordered(Value) {
|
||||||
init_cmp(t, slice.cmp_proc(Value), node_allocator)
|
init_cmp(t, slice.cmp_proc(Value), node_allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -210,8 +210,11 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator
|
|||||||
|
|
||||||
ba.max_index = max(idx, ba.max_index)
|
ba.max_index = max(idx, ba.max_index)
|
||||||
|
|
||||||
if set_to{ ba.bits[leg_index] |= 1 << uint(bit_index) }
|
if set_to {
|
||||||
else { ba.bits[leg_index] &= ~(1 << uint(bit_index)) }
|
ba.bits[leg_index] |= 1 << uint(bit_index)
|
||||||
|
} else {
|
||||||
|
ba.bits[leg_index] &~= 1 << uint(bit_index)
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -253,7 +256,7 @@ Inputs:
|
|||||||
- index: Which bit in the array
|
- index: Which bit in the array
|
||||||
*/
|
*/
|
||||||
unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check {
|
unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check {
|
||||||
b.bits[bit >> INDEX_SHIFT] &= ~(1 << uint(bit & INDEX_MASK))
|
b.bits[bit >> INDEX_SHIFT] &~= 1 << uint(bit & INDEX_MASK)
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative).
|
A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative).
|
||||||
|
|||||||
@@ -70,8 +70,7 @@ set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Alloc
|
|||||||
if c.count == c.capacity {
|
if c.count == c.capacity {
|
||||||
e = c.tail
|
e = c.tail
|
||||||
_remove_node(c, e)
|
_remove_node(c, e)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
c.count += 1
|
c.count += 1
|
||||||
e = new(Node(Key, Value), c.node_allocator) or_return
|
e = new(Node(Key, Value), c.node_allocator) or_return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push multiple elements to the front of the queue
|
// Push multiple elements to the back of the queue
|
||||||
push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) {
|
push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) {
|
||||||
n := uint(builtin.len(elems))
|
n := uint(builtin.len(elems))
|
||||||
if space(q^) < int(n) {
|
if space(q^) < int(n) {
|
||||||
@@ -241,7 +241,7 @@ clear :: proc(q: ^$Q/Queue($T)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Internal growinh procedure
|
// Internal growing procedure
|
||||||
_grow :: proc(q: ^$Q/Queue($T), min_capacity: uint = 0) -> runtime.Allocator_Error {
|
_grow :: proc(q: ^$Q/Queue($T), min_capacity: uint = 0) -> runtime.Allocator_Error {
|
||||||
new_capacity := max(min_capacity, uint(8), uint(builtin.len(q.data))*2)
|
new_capacity := max(min_capacity, uint(8), uint(builtin.len(q.data))*2)
|
||||||
n := uint(builtin.len(q.data))
|
n := uint(builtin.len(q.data))
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Tree :: struct($Key: typeid, $Value: typeid) {
|
|||||||
|
|
||||||
_root: ^Node(Key, Value),
|
_root: ^Node(Key, Value),
|
||||||
_node_allocator: runtime.Allocator,
|
_node_allocator: runtime.Allocator,
|
||||||
_cmp_fn: proc(Key, Key) -> Ordering,
|
_cmp_fn: proc(Key, Key) -> Ordering,
|
||||||
_size: int,
|
_size: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ init_cmp :: proc(t: ^$T/Tree($Key, $Value), cmp_fn: proc(a, b: Key) -> Ordering,
|
|||||||
|
|
||||||
// init_ordered initializes a tree containing ordered keys, with
|
// init_ordered initializes a tree containing ordered keys, with
|
||||||
// a comparison function that results in an ascending order sort.
|
// a comparison function that results in an ascending order sort.
|
||||||
init_ordered :: proc(t: ^$T/Tree($Key, $Value), node_allocator := context.allocator) where intrinsics.type_is_ordered_numeric(Key) {
|
init_ordered :: proc(t: ^$T/Tree($Key, $Value), node_allocator := context.allocator) where intrinsics.type_is_ordered(Key) {
|
||||||
init_cmp(t, slice.cmp_proc(Key), node_allocator)
|
init_cmp(t, slice.cmp_proc(Key), node_allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,20 +119,20 @@ consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_l
|
|||||||
}
|
}
|
||||||
|
|
||||||
ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
|
ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
|
||||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||||
if index+1 < a.len {
|
if index+1 < a.len {
|
||||||
copy(a.data[index:], a.data[index+1:])
|
copy(a.data[index:], a.data[index+1:])
|
||||||
}
|
}
|
||||||
a.len -= 1
|
a.len -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
|
unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
|
||||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||||
n := a.len-1
|
n := a.len-1
|
||||||
if index != n {
|
if index != n {
|
||||||
a.data[index] = a.data[n]
|
a.data[index] = a.data[n]
|
||||||
}
|
}
|
||||||
a.len -= 1
|
a.len -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) {
|
clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ add_dependency :: proc(sorter: ^$S/Sorter($K), key, dependency: K) -> bool {
|
|||||||
}
|
}
|
||||||
find.dependents[key] = true
|
find.dependents[key] = true
|
||||||
|
|
||||||
find = &sorter.relations[key]
|
find = &sorter.relations[key]
|
||||||
if find == nil {
|
if find == nil {
|
||||||
find = map_insert(&sorter.relations, key, make_relations(sorter))
|
find = map_insert(&sorter.relations, key, make_relations(sorter))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool {
|
|||||||
if len(b) != 32 {
|
if len(b) != 32 {
|
||||||
intrinsics.trap()
|
intrinsics.trap()
|
||||||
}
|
}
|
||||||
b_ := transmute(^[32]byte)(raw_data(b))
|
b_ := (^[32]byte)(raw_data(b))
|
||||||
|
|
||||||
// Do the work in a scratch element, so that ge is unchanged on
|
// Do the work in a scratch element, so that ge is unchanged on
|
||||||
// failure.
|
// failure.
|
||||||
@@ -169,7 +169,7 @@ ge_bytes :: proc "contextless" (ge: ^Group_Element, dst: []byte) {
|
|||||||
if len(dst) != 32 {
|
if len(dst) != 32 {
|
||||||
intrinsics.trap()
|
intrinsics.trap()
|
||||||
}
|
}
|
||||||
dst_ := transmute(^[32]byte)(raw_data(dst))
|
dst_ := (^[32]byte)(raw_data(dst))
|
||||||
|
|
||||||
// Convert the element to affine (x, y) representation.
|
// Convert the element to affine (x, y) representation.
|
||||||
x, y, z_inv: field.Tight_Field_Element = ---, ---, ---
|
x, y, z_inv: field.Tight_Field_Element = ---, ---, ---
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ sc_set_bytes :: proc "contextless" (sc: ^Scalar, b: []byte) -> bool {
|
|||||||
if len(b) != 32 {
|
if len(b) != 32 {
|
||||||
intrinsics.trap()
|
intrinsics.trap()
|
||||||
}
|
}
|
||||||
b_ := transmute(^[32]byte)(raw_data(b))
|
b_ := (^[32]byte)(raw_data(b))
|
||||||
return field.fe_from_bytes(sc, b_)
|
return field.fe_from_bytes(sc, b_)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ sc_set_bytes_rfc8032 :: proc "contextless" (sc: ^Scalar, b: []byte) {
|
|||||||
if len(b) != 32 {
|
if len(b) != 32 {
|
||||||
intrinsics.trap()
|
intrinsics.trap()
|
||||||
}
|
}
|
||||||
b_ := transmute(^[32]byte)(raw_data(b))
|
b_ := (^[32]byte)(raw_data(b))
|
||||||
field.fe_from_bytes_rfc8032(sc, b_)
|
field.fe_from_bytes_rfc8032(sc, b_)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import "core:mem"
|
|||||||
fe_relax_cast :: #force_inline proc "contextless" (
|
fe_relax_cast :: #force_inline proc "contextless" (
|
||||||
arg1: ^Tight_Field_Element,
|
arg1: ^Tight_Field_Element,
|
||||||
) -> ^Loose_Field_Element {
|
) -> ^Loose_Field_Element {
|
||||||
return transmute(^Loose_Field_Element)(arg1)
|
return (^Loose_Field_Element)(arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fe_tighten_cast :: #force_inline proc "contextless" (
|
fe_tighten_cast :: #force_inline proc "contextless" (
|
||||||
arg1: ^Loose_Field_Element,
|
arg1: ^Loose_Field_Element,
|
||||||
) -> ^Tight_Field_Element {
|
) -> ^Tight_Field_Element {
|
||||||
return transmute(^Tight_Field_Element)(arg1)
|
return (^Tight_Field_Element)(arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fe_clear :: proc "contextless" (
|
fe_clear :: proc "contextless" (
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import "core:mem"
|
|||||||
fe_relax_cast :: #force_inline proc "contextless" (
|
fe_relax_cast :: #force_inline proc "contextless" (
|
||||||
arg1: ^Tight_Field_Element,
|
arg1: ^Tight_Field_Element,
|
||||||
) -> ^Loose_Field_Element {
|
) -> ^Loose_Field_Element {
|
||||||
return transmute(^Loose_Field_Element)(arg1)
|
return (^Loose_Field_Element)(arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fe_tighten_cast :: #force_inline proc "contextless" (
|
fe_tighten_cast :: #force_inline proc "contextless" (
|
||||||
arg1: ^Loose_Field_Element,
|
arg1: ^Loose_Field_Element,
|
||||||
) -> ^Tight_Field_Element {
|
) -> ^Tight_Field_Element {
|
||||||
return transmute(^Tight_Field_Element)(arg1)
|
return (^Tight_Field_Element)(arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fe_from_bytes :: #force_inline proc "contextless" (
|
fe_from_bytes :: #force_inline proc "contextless" (
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ helper routines.
|
|||||||
*/
|
*/
|
||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
|
||||||
// compare_constant_time returns 1 iff a and b are equal, 0 otherwise.
|
// compare_constant_time returns 1 iff a and b are equal, 0 otherwise.
|
||||||
@@ -58,3 +59,24 @@ rand_bytes :: proc (dst: []byte) {
|
|||||||
|
|
||||||
_rand_bytes(dst)
|
_rand_bytes(dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
random_generator :: proc() -> runtime.Random_Generator {
|
||||||
|
return {
|
||||||
|
procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) {
|
||||||
|
switch mode {
|
||||||
|
case .Read:
|
||||||
|
rand_bytes(p)
|
||||||
|
case .Reset:
|
||||||
|
// do nothing
|
||||||
|
case .Query_Info:
|
||||||
|
if len(p) != size_of(runtime.Random_Generator_Query_Info) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info := (^runtime.Random_Generator_Query_Info)(raw_data(p))
|
||||||
|
info^ += {.Uniform, .Cryptographic, .External_Entropy}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data = nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ init_256 :: proc(ctx: ^Context, key, domain_sep: []byte) {
|
|||||||
update :: proc(ctx: ^Context, data: []byte) {
|
update :: proc(ctx: ^Context, data: []byte) {
|
||||||
assert(ctx.is_initialized)
|
assert(ctx.is_initialized)
|
||||||
|
|
||||||
shake.write(transmute(^shake.Context)(ctx), data)
|
shake.write((^shake.Context)(ctx), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// final finalizes the Context, writes the tag to dst, and calls reset
|
// final finalizes the Context, writes the tag to dst, and calls reset
|
||||||
@@ -75,7 +75,7 @@ final :: proc(ctx: ^Context, dst: []byte) {
|
|||||||
panic("crypto/kmac: invalid KMAC tag_size, too short")
|
panic("crypto/kmac: invalid KMAC tag_size, too short")
|
||||||
}
|
}
|
||||||
|
|
||||||
_sha3.final_cshake(transmute(^_sha3.Context)(ctx), dst)
|
_sha3.final_cshake((^_sha3.Context)(ctx), dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
// clone clones the Context other into ctx.
|
// clone clones the Context other into ctx.
|
||||||
@@ -84,7 +84,7 @@ clone :: proc(ctx, other: ^Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
shake.clone(transmute(^shake.Context)(ctx), transmute(^shake.Context)(other))
|
shake.clone((^shake.Context)(ctx), (^shake.Context)(other))
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset sanitizes the Context. The Context must be re-initialized to
|
// reset sanitizes the Context. The Context must be re-initialized to
|
||||||
@@ -94,7 +94,7 @@ reset :: proc(ctx: ^Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
shake.reset(transmute(^shake.Context)(ctx))
|
shake.reset((^shake.Context)(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
@@ -107,7 +107,7 @@ _init_kmac :: proc(ctx: ^Context, key, s: []byte, sec_strength: int) {
|
|||||||
panic("crypto/kmac: invalid KMAC key, too short")
|
panic("crypto/kmac: invalid KMAC key, too short")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx_ := transmute(^_sha3.Context)(ctx)
|
ctx_ := (^_sha3.Context)(ctx)
|
||||||
_sha3.init_cshake(ctx_, N_KMAC, s, sec_strength)
|
_sha3.init_cshake(ctx_, N_KMAC, s, sec_strength)
|
||||||
_sha3.bytepad(ctx_, [][]byte{key}, _sha3.rate_cshake(sec_strength))
|
_sha3.bytepad(ctx_, [][]byte{key}, _sha3.rate_cshake(sec_strength))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,12 +66,12 @@ init_512 :: proc(ctx: ^Context) {
|
|||||||
@(private)
|
@(private)
|
||||||
_init :: proc(ctx: ^Context) {
|
_init :: proc(ctx: ^Context) {
|
||||||
ctx.dsbyte = _sha3.DS_KECCAK
|
ctx.dsbyte = _sha3.DS_KECCAK
|
||||||
_sha3.init(transmute(^_sha3.Context)(ctx))
|
_sha3.init((^_sha3.Context)(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
// update adds more data to the Context.
|
// update adds more data to the Context.
|
||||||
update :: proc(ctx: ^Context, data: []byte) {
|
update :: proc(ctx: ^Context, data: []byte) {
|
||||||
_sha3.update(transmute(^_sha3.Context)(ctx), data)
|
_sha3.update((^_sha3.Context)(ctx), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// final finalizes the Context, writes the digest to hash, and calls
|
// final finalizes the Context, writes the digest to hash, and calls
|
||||||
@@ -80,16 +80,16 @@ update :: proc(ctx: ^Context, data: []byte) {
|
|||||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||||
// which is useful for for calculating rolling digests.
|
// which is useful for for calculating rolling digests.
|
||||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||||
_sha3.final(transmute(^_sha3.Context)(ctx), hash, finalize_clone)
|
_sha3.final((^_sha3.Context)(ctx), hash, finalize_clone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// clone clones the Context other into ctx.
|
// clone clones the Context other into ctx.
|
||||||
clone :: proc(ctx, other: ^Context) {
|
clone :: proc(ctx, other: ^Context) {
|
||||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset sanitizes the Context. The Context must be re-initialized to
|
// reset sanitizes the Context. The Context must be re-initialized to
|
||||||
// be used again.
|
// be used again.
|
||||||
reset :: proc(ctx: ^Context) {
|
reset :: proc(ctx: ^Context) {
|
||||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
_sha3.reset((^_sha3.Context)(ctx))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ HAS_RAND_BYTES :: true
|
|||||||
_rand_bytes :: proc(dst: []byte) {
|
_rand_bytes :: proc(dst: []byte) {
|
||||||
err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst))
|
err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst))
|
||||||
if err != .Success {
|
if err != .Success {
|
||||||
msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err))
|
msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err))
|
||||||
panic(fmt.tprintf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg))
|
fmt.panicf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ _rand_bytes :: proc (dst: []byte) {
|
|||||||
// All other failures are things that should NEVER happen
|
// All other failures are things that should NEVER happen
|
||||||
// unless the kernel interface changes (ie: the Linux
|
// unless the kernel interface changes (ie: the Linux
|
||||||
// developers break userland).
|
// developers break userland).
|
||||||
panic(fmt.tprintf("crypto: getrandom failed: %v", errno))
|
fmt.panicf("crypto: getrandom failed: %v", errno)
|
||||||
}
|
}
|
||||||
l -= n_read
|
l -= n_read
|
||||||
dst = dst[n_read:]
|
dst = dst[n_read:]
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ _rand_bytes :: proc(dst: []byte) {
|
|||||||
ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG))
|
ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG))
|
||||||
if ret != os.ERROR_NONE {
|
if ret != os.ERROR_NONE {
|
||||||
switch ret {
|
switch ret {
|
||||||
case os.ERROR_INVALID_HANDLE:
|
case os.ERROR_INVALID_HANDLE:
|
||||||
// The handle to the first parameter is invalid.
|
// The handle to the first parameter is invalid.
|
||||||
// This should not happen here, since we explicitly pass nil to it
|
// This should not happen here, since we explicitly pass nil to it
|
||||||
panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm")
|
panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm")
|
||||||
case os.ERROR_INVALID_PARAMETER:
|
case os.ERROR_INVALID_PARAMETER:
|
||||||
// One of the parameters was invalid
|
// One of the parameters was invalid
|
||||||
panic("crypto: BCryptGenRandom Invalid parameter")
|
panic("crypto: BCryptGenRandom Invalid parameter")
|
||||||
case:
|
case:
|
||||||
// Unknown error
|
// Unknown error
|
||||||
panic(fmt.tprintf("crypto: BCryptGenRandom failed: %d\n", ret))
|
fmt.panicf("crypto: BCryptGenRandom failed: %d\n", ret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
b_ := transmute(^[32]byte)(raw_data(b))
|
b_ := (^[32]byte)(raw_data(b))
|
||||||
|
|
||||||
s: field.Tight_Field_Element = ---
|
s: field.Tight_Field_Element = ---
|
||||||
defer field.fe_clear(&s)
|
defer field.fe_clear(&s)
|
||||||
@@ -297,7 +297,7 @@ ge_bytes :: proc(ge: ^Group_Element, dst: []byte) {
|
|||||||
// 2. Return the 32-byte little-endian encoding of s. More
|
// 2. Return the 32-byte little-endian encoding of s. More
|
||||||
// specifically, this is the encoding of the canonical
|
// specifically, this is the encoding of the canonical
|
||||||
// representation of s as an integer between 0 and p-1, inclusive.
|
// representation of s as an integer between 0 and p-1, inclusive.
|
||||||
dst_ := transmute(^[32]byte)(raw_data(dst))
|
dst_ := (^[32]byte)(raw_data(dst))
|
||||||
field.fe_to_bytes(dst_, &tmp)
|
field.fe_to_bytes(dst_, &tmp)
|
||||||
|
|
||||||
field.fe_clear_vec([]^field.Tight_Field_Element{&u1, &u2, &tmp, &z_inv, &ix0, &iy0, &x, &y})
|
field.fe_clear_vec([]^field.Tight_Field_Element{&u1, &u2, &tmp, &z_inv, &ix0, &iy0, &x, &y})
|
||||||
@@ -417,7 +417,7 @@ ge_is_identity :: proc(ge: ^Group_Element) -> int {
|
|||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
ge_map :: proc "contextless" (ge: ^Group_Element, b: []byte) {
|
ge_map :: proc "contextless" (ge: ^Group_Element, b: []byte) {
|
||||||
b_ := transmute(^[32]byte)(raw_data(b))
|
b_ := (^[32]byte)(raw_data(b))
|
||||||
|
|
||||||
// The MAP function is defined on 32-byte strings as:
|
// The MAP function is defined on 32-byte strings as:
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ sc_set_bytes_wide :: proc(sc: ^Scalar, b: []byte) {
|
|||||||
panic("crypto/ristretto255: invalid wide input size")
|
panic("crypto/ristretto255: invalid wide input size")
|
||||||
}
|
}
|
||||||
|
|
||||||
b_ := transmute(^[WIDE_SCALAR_SIZE]byte)(raw_data(b))
|
b_ := (^[WIDE_SCALAR_SIZE]byte)(raw_data(b))
|
||||||
grp.sc_set_bytes_wide(sc, b_)
|
grp.sc_set_bytes_wide(sc, b_)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,12 +68,12 @@ init_512 :: proc(ctx: ^Context) {
|
|||||||
@(private)
|
@(private)
|
||||||
_init :: proc(ctx: ^Context) {
|
_init :: proc(ctx: ^Context) {
|
||||||
ctx.dsbyte = _sha3.DS_SHA3
|
ctx.dsbyte = _sha3.DS_SHA3
|
||||||
_sha3.init(transmute(^_sha3.Context)(ctx))
|
_sha3.init((^_sha3.Context)(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
// update adds more data to the Context.
|
// update adds more data to the Context.
|
||||||
update :: proc(ctx: ^Context, data: []byte) {
|
update :: proc(ctx: ^Context, data: []byte) {
|
||||||
_sha3.update(transmute(^_sha3.Context)(ctx), data)
|
_sha3.update((^_sha3.Context)(ctx), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// final finalizes the Context, writes the digest to hash, and calls
|
// final finalizes the Context, writes the digest to hash, and calls
|
||||||
@@ -82,16 +82,16 @@ update :: proc(ctx: ^Context, data: []byte) {
|
|||||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||||
// which is useful for for calculating rolling digests.
|
// which is useful for for calculating rolling digests.
|
||||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||||
_sha3.final(transmute(^_sha3.Context)(ctx), hash, finalize_clone)
|
_sha3.final((^_sha3.Context)(ctx), hash, finalize_clone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// clone clones the Context other into ctx.
|
// clone clones the Context other into ctx.
|
||||||
clone :: proc(ctx, other: ^Context) {
|
clone :: proc(ctx, other: ^Context) {
|
||||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset sanitizes the Context. The Context must be re-initialized to
|
// reset sanitizes the Context. The Context must be re-initialized to
|
||||||
// be used again.
|
// be used again.
|
||||||
reset :: proc(ctx: ^Context) {
|
reset :: proc(ctx: ^Context) {
|
||||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
_sha3.reset((^_sha3.Context)(ctx))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,35 +24,35 @@ Context :: distinct _sha3.Context
|
|||||||
|
|
||||||
// init_128 initializes a Context for SHAKE128.
|
// init_128 initializes a Context for SHAKE128.
|
||||||
init_128 :: proc(ctx: ^Context) {
|
init_128 :: proc(ctx: ^Context) {
|
||||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, nil, 128)
|
_sha3.init_cshake((^_sha3.Context)(ctx), nil, nil, 128)
|
||||||
}
|
}
|
||||||
|
|
||||||
// init_256 initializes a Context for SHAKE256.
|
// init_256 initializes a Context for SHAKE256.
|
||||||
init_256 :: proc(ctx: ^Context) {
|
init_256 :: proc(ctx: ^Context) {
|
||||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, nil, 256)
|
_sha3.init_cshake((^_sha3.Context)(ctx), nil, nil, 256)
|
||||||
}
|
}
|
||||||
|
|
||||||
// init_cshake_128 initializes a Context for cSHAKE128.
|
// init_cshake_128 initializes a Context for cSHAKE128.
|
||||||
init_cshake_128 :: proc(ctx: ^Context, domain_sep: []byte) {
|
init_cshake_128 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, domain_sep, 128)
|
_sha3.init_cshake((^_sha3.Context)(ctx), nil, domain_sep, 128)
|
||||||
}
|
}
|
||||||
|
|
||||||
// init_cshake_256 initializes a Context for cSHAKE256.
|
// init_cshake_256 initializes a Context for cSHAKE256.
|
||||||
init_cshake_256 :: proc(ctx: ^Context, domain_sep: []byte) {
|
init_cshake_256 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, domain_sep, 256)
|
_sha3.init_cshake((^_sha3.Context)(ctx), nil, domain_sep, 256)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write writes more data into the SHAKE instance. This MUST not be called
|
// write writes more data into the SHAKE instance. This MUST not be called
|
||||||
// after any reads have been done, and attempts to do so will panic.
|
// after any reads have been done, and attempts to do so will panic.
|
||||||
write :: proc(ctx: ^Context, data: []byte) {
|
write :: proc(ctx: ^Context, data: []byte) {
|
||||||
_sha3.update(transmute(^_sha3.Context)(ctx), data)
|
_sha3.update((^_sha3.Context)(ctx), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// read reads output from the SHAKE instance. There is no practical upper
|
// read reads output from the SHAKE instance. There is no practical upper
|
||||||
// limit to the amount of data that can be read from SHAKE. After read has
|
// limit to the amount of data that can be read from SHAKE. After read has
|
||||||
// been called one or more times, further calls to write will panic.
|
// been called one or more times, further calls to write will panic.
|
||||||
read :: proc(ctx: ^Context, dst: []byte) {
|
read :: proc(ctx: ^Context, dst: []byte) {
|
||||||
ctx_ := transmute(^_sha3.Context)(ctx)
|
ctx_ := (^_sha3.Context)(ctx)
|
||||||
if !ctx.is_finalized {
|
if !ctx.is_finalized {
|
||||||
_sha3.shake_xof(ctx_)
|
_sha3.shake_xof(ctx_)
|
||||||
}
|
}
|
||||||
@@ -62,11 +62,11 @@ read :: proc(ctx: ^Context, dst: []byte) {
|
|||||||
|
|
||||||
// clone clones the Context other into ctx.
|
// clone clones the Context other into ctx.
|
||||||
clone :: proc(ctx, other: ^Context) {
|
clone :: proc(ctx, other: ^Context) {
|
||||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset sanitizes the Context. The Context must be re-initialized to
|
// reset sanitizes the Context. The Context must be re-initialized to
|
||||||
// be used again.
|
// be used again.
|
||||||
reset :: proc(ctx: ^Context) {
|
reset :: proc(ctx: ^Context) {
|
||||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
_sha3.reset((^_sha3.Context)(ctx))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,19 +13,19 @@ Context :: distinct _sha3.Context
|
|||||||
|
|
||||||
// init_128 initializes a Context for TupleHash128 or TupleHashXOF128.
|
// init_128 initializes a Context for TupleHash128 or TupleHashXOF128.
|
||||||
init_128 :: proc(ctx: ^Context, domain_sep: []byte) {
|
init_128 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 128)
|
_sha3.init_cshake((^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 128)
|
||||||
}
|
}
|
||||||
|
|
||||||
// init_256 initializes a Context for TupleHash256 or TupleHashXOF256.
|
// init_256 initializes a Context for TupleHash256 or TupleHashXOF256.
|
||||||
init_256 :: proc(ctx: ^Context, domain_sep: []byte) {
|
init_256 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 256)
|
_sha3.init_cshake((^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 256)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write_element writes a tuple element into the TupleHash or TupleHashXOF
|
// write_element writes a tuple element into the TupleHash or TupleHashXOF
|
||||||
// instance. This MUST not be called after any reads have been done, and
|
// instance. This MUST not be called after any reads have been done, and
|
||||||
// any attempts to do so will panic.
|
// any attempts to do so will panic.
|
||||||
write_element :: proc(ctx: ^Context, data: []byte) {
|
write_element :: proc(ctx: ^Context, data: []byte) {
|
||||||
_, _ = _sha3.encode_string(transmute(^_sha3.Context)(ctx), data)
|
_, _ = _sha3.encode_string((^_sha3.Context)(ctx), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// final finalizes the Context, writes the digest to hash, and calls
|
// final finalizes the Context, writes the digest to hash, and calls
|
||||||
@@ -34,7 +34,7 @@ write_element :: proc(ctx: ^Context, data: []byte) {
|
|||||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||||
// which is useful for for calculating rolling digests.
|
// which is useful for for calculating rolling digests.
|
||||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||||
_sha3.final_cshake(transmute(^_sha3.Context)(ctx), hash, finalize_clone)
|
_sha3.final_cshake((^_sha3.Context)(ctx), hash, finalize_clone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// read reads output from the TupleHashXOF instance. There is no practical
|
// read reads output from the TupleHashXOF instance. There is no practical
|
||||||
@@ -42,7 +42,7 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
|||||||
// After read has been called one or more times, further calls to
|
// After read has been called one or more times, further calls to
|
||||||
// write_element will panic.
|
// write_element will panic.
|
||||||
read :: proc(ctx: ^Context, dst: []byte) {
|
read :: proc(ctx: ^Context, dst: []byte) {
|
||||||
ctx_ := transmute(^_sha3.Context)(ctx)
|
ctx_ := (^_sha3.Context)(ctx)
|
||||||
if !ctx.is_finalized {
|
if !ctx.is_finalized {
|
||||||
_sha3.encode_byte_len(ctx_, 0, false) // right_encode
|
_sha3.encode_byte_len(ctx_, 0, false) // right_encode
|
||||||
_sha3.shake_xof(ctx_)
|
_sha3.shake_xof(ctx_)
|
||||||
@@ -53,13 +53,13 @@ read :: proc(ctx: ^Context, dst: []byte) {
|
|||||||
|
|
||||||
// clone clones the Context other into ctx.
|
// clone clones the Context other into ctx.
|
||||||
clone :: proc(ctx, other: ^Context) {
|
clone :: proc(ctx, other: ^Context) {
|
||||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset sanitizes the Context. The Context must be re-initialized to
|
// reset sanitizes the Context. The Context must be re-initialized to
|
||||||
// be used again.
|
// be used again.
|
||||||
reset :: proc(ctx: ^Context) {
|
reset :: proc(ctx: ^Context) {
|
||||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
_sha3.reset((^_sha3.Context)(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ _Context :: struct {
|
|||||||
|
|
||||||
@(private="package")
|
@(private="package")
|
||||||
_init :: proc(ctx: ^Context) -> (ok: bool) {
|
_init :: proc(ctx: ^Context) -> (ok: bool) {
|
||||||
defer if !ok do destroy(ctx)
|
defer if !ok { destroy(ctx) }
|
||||||
|
|
||||||
ctx.impl.state = backtrace_create_state("odin-debug-trace", 1, nil, ctx)
|
ctx.impl.state = backtrace_create_state("odin-debug-trace", 1, nil, ctx)
|
||||||
return ctx.impl.state != nil
|
return ctx.impl.state != nil
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
//+build !windows !linux !darwin
|
//+build !windows !linux !darwin
|
||||||
package debug_trace
|
package debug_trace
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
|
||||||
_Context :: struct {
|
_Context :: struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10,9 +12,9 @@ _init :: proc(ctx: ^Context) -> (ok: bool) {
|
|||||||
_destroy :: proc(ctx: ^Context) -> bool {
|
_destroy :: proc(ctx: ^Context) -> bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
_frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> []Frame {
|
_frames :: proc(ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: runtime.Source_Code_Location) {
|
_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: Frame_Location) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package ansi
|
||||||
|
|
||||||
|
BEL :: "\a" // Bell
|
||||||
|
BS :: "\b" // Backspace
|
||||||
|
ESC :: "\e" // Escape
|
||||||
|
|
||||||
|
// Fe Escape sequences
|
||||||
|
|
||||||
|
CSI :: ESC + "[" // Control Sequence Introducer
|
||||||
|
OSC :: ESC + "]" // Operating System Command
|
||||||
|
ST :: ESC + "\\" // String Terminator
|
||||||
|
|
||||||
|
// CSI sequences
|
||||||
|
|
||||||
|
CUU :: "A" // Cursor Up
|
||||||
|
CUD :: "B" // Cursor Down
|
||||||
|
CUF :: "C" // Cursor Forward
|
||||||
|
CUB :: "D" // Cursor Back
|
||||||
|
CNL :: "E" // Cursor Next Line
|
||||||
|
CPL :: "F" // Cursor Previous Line
|
||||||
|
CHA :: "G" // Cursor Horizontal Absolute
|
||||||
|
CUP :: "H" // Cursor Position
|
||||||
|
ED :: "J" // Erase in Display
|
||||||
|
EL :: "K" // Erase in Line
|
||||||
|
SU :: "S" // Scroll Up
|
||||||
|
SD :: "T" // Scroll Down
|
||||||
|
HVP :: "f" // Horizontal Vertical Position
|
||||||
|
SGR :: "m" // Select Graphic Rendition
|
||||||
|
AUX_ON :: "5i" // AUX Port On
|
||||||
|
AUX_OFF :: "4i" // AUX Port Off
|
||||||
|
DSR :: "6n" // Device Status Report
|
||||||
|
|
||||||
|
// CSI: private sequences
|
||||||
|
|
||||||
|
SCP :: "s" // Save Current Cursor Position
|
||||||
|
RCP :: "u" // Restore Saved Cursor Position
|
||||||
|
DECAWM_ON :: "?7h" // Auto Wrap Mode (Enabled)
|
||||||
|
DECAWM_OFF :: "?7l" // Auto Wrap Mode (Disabled)
|
||||||
|
DECTCEM_SHOW :: "?25h" // Text Cursor Enable Mode (Visible)
|
||||||
|
DECTCEM_HIDE :: "?25l" // Text Cursor Enable Mode (Invisible)
|
||||||
|
|
||||||
|
// SGR sequences
|
||||||
|
|
||||||
|
RESET :: "0"
|
||||||
|
BOLD :: "1"
|
||||||
|
FAINT :: "2"
|
||||||
|
ITALIC :: "3" // Not widely supported.
|
||||||
|
UNDERLINE :: "4"
|
||||||
|
BLINK_SLOW :: "5"
|
||||||
|
BLINK_RAPID :: "6" // Not widely supported.
|
||||||
|
INVERT :: "7" // Also known as reverse video.
|
||||||
|
HIDE :: "8" // Not widely supported.
|
||||||
|
STRIKE :: "9"
|
||||||
|
FONT_PRIMARY :: "10"
|
||||||
|
FONT_ALT1 :: "11"
|
||||||
|
FONT_ALT2 :: "12"
|
||||||
|
FONT_ALT3 :: "13"
|
||||||
|
FONT_ALT4 :: "14"
|
||||||
|
FONT_ALT5 :: "15"
|
||||||
|
FONT_ALT6 :: "16"
|
||||||
|
FONT_ALT7 :: "17"
|
||||||
|
FONT_ALT8 :: "18"
|
||||||
|
FONT_ALT9 :: "19"
|
||||||
|
FONT_FRAKTUR :: "20" // Rarely supported.
|
||||||
|
UNDERLINE_DOUBLE :: "21" // May be interpreted as "disable bold."
|
||||||
|
NO_BOLD_FAINT :: "22"
|
||||||
|
NO_ITALIC_BLACKLETTER :: "23"
|
||||||
|
NO_UNDERLINE :: "24"
|
||||||
|
NO_BLINK :: "25"
|
||||||
|
PROPORTIONAL_SPACING :: "26"
|
||||||
|
NO_REVERSE :: "27"
|
||||||
|
NO_HIDE :: "28"
|
||||||
|
NO_STRIKE :: "29"
|
||||||
|
|
||||||
|
FG_BLACK :: "30"
|
||||||
|
FG_RED :: "31"
|
||||||
|
FG_GREEN :: "32"
|
||||||
|
FG_YELLOW :: "33"
|
||||||
|
FG_BLUE :: "34"
|
||||||
|
FG_MAGENTA :: "35"
|
||||||
|
FG_CYAN :: "36"
|
||||||
|
FG_WHITE :: "37"
|
||||||
|
FG_COLOR :: "38"
|
||||||
|
FG_COLOR_8_BIT :: "38;5" // Followed by ";n" where n is in 0..=255
|
||||||
|
FG_COLOR_24_BIT :: "38;2" // Followed by ";r;g;b" where r,g,b are in 0..=255
|
||||||
|
FG_DEFAULT :: "39"
|
||||||
|
|
||||||
|
BG_BLACK :: "40"
|
||||||
|
BG_RED :: "41"
|
||||||
|
BG_GREEN :: "42"
|
||||||
|
BG_YELLOW :: "43"
|
||||||
|
BG_BLUE :: "44"
|
||||||
|
BG_MAGENTA :: "45"
|
||||||
|
BG_CYAN :: "46"
|
||||||
|
BG_WHITE :: "47"
|
||||||
|
BG_COLOR :: "48"
|
||||||
|
BG_COLOR_8_BIT :: "48;5" // Followed by ";n" where n is in 0..=255
|
||||||
|
BG_COLOR_24_BIT :: "48;2" // Followed by ";r;g;b" where r,g,b are in 0..=255
|
||||||
|
BG_DEFAULT :: "49"
|
||||||
|
|
||||||
|
NO_PROPORTIONAL_SPACING :: "50"
|
||||||
|
FRAMED :: "51"
|
||||||
|
ENCIRCLED :: "52"
|
||||||
|
OVERLINED :: "53"
|
||||||
|
NO_FRAME_ENCIRCLE :: "54"
|
||||||
|
NO_OVERLINE :: "55"
|
||||||
|
|
||||||
|
// SGR: non-standard bright colors
|
||||||
|
|
||||||
|
FG_BRIGHT_BLACK :: "90" // Also known as grey.
|
||||||
|
FG_BRIGHT_RED :: "91"
|
||||||
|
FG_BRIGHT_GREEN :: "92"
|
||||||
|
FG_BRIGHT_YELLOW :: "93"
|
||||||
|
FG_BRIGHT_BLUE :: "94"
|
||||||
|
FG_BRIGHT_MAGENTA :: "95"
|
||||||
|
FG_BRIGHT_CYAN :: "96"
|
||||||
|
FG_BRIGHT_WHITE :: "97"
|
||||||
|
|
||||||
|
BG_BRIGHT_BLACK :: "100" // Also known as grey.
|
||||||
|
BG_BRIGHT_RED :: "101"
|
||||||
|
BG_BRIGHT_GREEN :: "102"
|
||||||
|
BG_BRIGHT_YELLOW :: "103"
|
||||||
|
BG_BRIGHT_BLUE :: "104"
|
||||||
|
BG_BRIGHT_MAGENTA :: "105"
|
||||||
|
BG_BRIGHT_CYAN :: "106"
|
||||||
|
BG_BRIGHT_WHITE :: "107"
|
||||||
|
|
||||||
|
// Fp Escape sequences
|
||||||
|
|
||||||
|
DECSC :: ESC + "7" // DEC Save Cursor
|
||||||
|
DECRC :: ESC + "8" // DEC Restore Cursor
|
||||||
|
|
||||||
|
// OSC sequences
|
||||||
|
|
||||||
|
WINDOW_TITLE :: "2" // Followed by ";<text>" ST.
|
||||||
|
HYPERLINK :: "8" // Followed by ";[params];<URI>" ST. Closed by OSC HYPERLINK ";;" ST.
|
||||||
|
CLIPBOARD :: "52" // Followed by ";c;<Base64-encoded string>" ST.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
package ansi implements constant references to many widely-supported ANSI
|
||||||
|
escape codes, primarily used in terminal emulators for enhanced graphics, such
|
||||||
|
as colors, text styling, and animated displays.
|
||||||
|
|
||||||
|
For example, you can print out a line of cyan text like this:
|
||||||
|
fmt.println(ansi.CSI + ansi.FG_CYAN + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
|
||||||
|
|
||||||
|
Multiple SGR (Select Graphic Rendition) codes can be joined by semicolons:
|
||||||
|
fmt.println(ansi.CSI + ansi.BOLD + ";" + ansi.FG_BLUE + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
|
||||||
|
|
||||||
|
If your terminal supports 24-bit true color mode, you can also do this:
|
||||||
|
fmt.println(ansi.CSI + ansi.FG_COLOR_24_BIT + ";0;255;255" + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
|
||||||
|
|
||||||
|
For more information, see:
|
||||||
|
1. https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
2. https://www.vt100.net/docs/vt102-ug/chapter5.html
|
||||||
|
3. https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
|
*/
|
||||||
|
package ansi
|
||||||
+116
-116
@@ -8,141 +8,141 @@ package encoding_base32
|
|||||||
// truncate it from the encoded output.
|
// truncate it from the encoded output.
|
||||||
|
|
||||||
ENC_TABLE := [32]byte {
|
ENC_TABLE := [32]byte {
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||||
'Y', 'Z', '2', '3', '4', '5', '6', '7',
|
'Y', 'Z', '2', '3', '4', '5', '6', '7',
|
||||||
}
|
}
|
||||||
|
|
||||||
PADDING :: '='
|
PADDING :: '='
|
||||||
|
|
||||||
DEC_TABLE := [?]u8 {
|
DEC_TABLE := [?]u8 {
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 26, 27, 28, 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 26, 27, 28, 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
||||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> string {
|
encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> string {
|
||||||
out_length := (len(data) + 4) / 5 * 8
|
out_length := (len(data) + 4) / 5 * 8
|
||||||
out := make([]byte, out_length)
|
out := make([]byte, out_length)
|
||||||
_encode(out, data)
|
_encode(out, data)
|
||||||
return string(out)
|
return string(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@private
|
@private
|
||||||
_encode :: proc(out, data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) {
|
_encode :: proc(out, data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) {
|
||||||
out := out
|
out := out
|
||||||
data := data
|
data := data
|
||||||
|
|
||||||
for len(data) > 0 {
|
for len(data) > 0 {
|
||||||
carry: byte
|
carry: byte
|
||||||
switch len(data) {
|
switch len(data) {
|
||||||
case:
|
case:
|
||||||
out[7] = ENC_TABLE[data[4] & 0x1f]
|
out[7] = ENC_TABLE[data[4] & 0x1f]
|
||||||
carry = data[4] >> 5
|
carry = data[4] >> 5
|
||||||
fallthrough
|
fallthrough
|
||||||
case 4:
|
case 4:
|
||||||
out[6] = ENC_TABLE[carry | (data[3] << 3) & 0x1f]
|
out[6] = ENC_TABLE[carry | (data[3] << 3) & 0x1f]
|
||||||
out[5] = ENC_TABLE[(data[3] >> 2) & 0x1f]
|
out[5] = ENC_TABLE[(data[3] >> 2) & 0x1f]
|
||||||
carry = data[3] >> 7
|
carry = data[3] >> 7
|
||||||
fallthrough
|
fallthrough
|
||||||
case 3:
|
case 3:
|
||||||
out[4] = ENC_TABLE[carry | (data[2] << 1) & 0x1f]
|
out[4] = ENC_TABLE[carry | (data[2] << 1) & 0x1f]
|
||||||
carry = (data[2] >> 4) & 0x1f
|
carry = (data[2] >> 4) & 0x1f
|
||||||
fallthrough
|
fallthrough
|
||||||
case 2:
|
case 2:
|
||||||
out[3] = ENC_TABLE[carry | (data[1] << 4) & 0x1f]
|
out[3] = ENC_TABLE[carry | (data[1] << 4) & 0x1f]
|
||||||
out[2] = ENC_TABLE[(data[1] >> 1) & 0x1f]
|
out[2] = ENC_TABLE[(data[1] >> 1) & 0x1f]
|
||||||
carry = (data[1] >> 6) & 0x1f
|
carry = (data[1] >> 6) & 0x1f
|
||||||
fallthrough
|
fallthrough
|
||||||
case 1:
|
case 1:
|
||||||
out[1] = ENC_TABLE[carry | (data[0] << 2) & 0x1f]
|
out[1] = ENC_TABLE[carry | (data[0] << 2) & 0x1f]
|
||||||
out[0] = ENC_TABLE[data[0] >> 3]
|
out[0] = ENC_TABLE[data[0] >> 3]
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data) < 5 {
|
if len(data) < 5 {
|
||||||
out[7] = byte(PADDING)
|
out[7] = byte(PADDING)
|
||||||
if len(data) < 4 {
|
if len(data) < 4 {
|
||||||
out[6] = byte(PADDING)
|
out[6] = byte(PADDING)
|
||||||
out[5] = byte(PADDING)
|
out[5] = byte(PADDING)
|
||||||
if len(data) < 3 {
|
if len(data) < 3 {
|
||||||
out[4] = byte(PADDING)
|
out[4] = byte(PADDING)
|
||||||
if len(data) < 2 {
|
if len(data) < 2 {
|
||||||
out[3] = byte(PADDING)
|
out[3] = byte(PADDING)
|
||||||
out[2] = byte(PADDING)
|
out[2] = byte(PADDING)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
data = data[5:]
|
data = data[5:]
|
||||||
out = out[8:]
|
out = out[8:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> []byte #no_bounds_check{
|
decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> []byte #no_bounds_check{
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
outi := 0
|
outi := 0
|
||||||
data := data
|
data := data
|
||||||
|
|
||||||
out := make([]byte, len(data) / 8 * 5, allocator)
|
out := make([]byte, len(data) / 8 * 5, allocator)
|
||||||
end := false
|
end := false
|
||||||
for len(data) > 0 && !end {
|
for len(data) > 0 && !end {
|
||||||
dbuf : [8]byte
|
dbuf : [8]byte
|
||||||
dlen := 8
|
dlen := 8
|
||||||
|
|
||||||
for j := 0; j < 8; {
|
for j := 0; j < 8; {
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
dlen, end = j, true
|
dlen, end = j, true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
input := data[0]
|
input := data[0]
|
||||||
data = data[1:]
|
data = data[1:]
|
||||||
if input == byte(PADDING) && j >= 2 && len(data) < 8 {
|
if input == byte(PADDING) && j >= 2 && len(data) < 8 {
|
||||||
assert(!(len(data) + j < 8 - 1), "Corrupted input")
|
assert(!(len(data) + j < 8 - 1), "Corrupted input")
|
||||||
for k := 0; k < 8-1-j; k +=1 {
|
for k := 0; k < 8-1-j; k +=1 {
|
||||||
assert(len(data) < k || data[k] == byte(PADDING), "Corrupted input")
|
assert(len(data) < k || data[k] == byte(PADDING), "Corrupted input")
|
||||||
}
|
}
|
||||||
dlen, end = j, true
|
dlen, end = j, true
|
||||||
assert(dlen != 1 && dlen != 3 && dlen != 6, "Corrupted input")
|
assert(dlen != 1 && dlen != 3 && dlen != 6, "Corrupted input")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
dbuf[j] = DEC_TABLE[input]
|
dbuf[j] = DEC_TABLE[input]
|
||||||
assert(dbuf[j] != 0xff, "Corrupted input")
|
assert(dbuf[j] != 0xff, "Corrupted input")
|
||||||
j += 1
|
j += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
switch dlen {
|
switch dlen {
|
||||||
case 8:
|
case 8:
|
||||||
out[outi + 4] = dbuf[6] << 5 | dbuf[7]
|
out[outi + 4] = dbuf[6] << 5 | dbuf[7]
|
||||||
fallthrough
|
fallthrough
|
||||||
case 7:
|
case 7:
|
||||||
out[outi + 3] = dbuf[4] << 7 | dbuf[5] << 2 | dbuf[6] >> 3
|
out[outi + 3] = dbuf[4] << 7 | dbuf[5] << 2 | dbuf[6] >> 3
|
||||||
fallthrough
|
fallthrough
|
||||||
case 5:
|
case 5:
|
||||||
out[outi + 2] = dbuf[3] << 4 | dbuf[4] >> 1
|
out[outi + 2] = dbuf[3] << 4 | dbuf[4] >> 1
|
||||||
fallthrough
|
fallthrough
|
||||||
case 4:
|
case 4:
|
||||||
out[outi + 1] = dbuf[1] << 6 | dbuf[2] << 1 | dbuf[3] >> 4
|
out[outi + 1] = dbuf[1] << 6 | dbuf[2] << 1 | dbuf[3] >> 4
|
||||||
fallthrough
|
fallthrough
|
||||||
case 2:
|
case 2:
|
||||||
out[outi + 0] = dbuf[0] << 3 | dbuf[1] >> 2
|
out[outi + 0] = dbuf[0] << 3 | dbuf[1] >> 2
|
||||||
}
|
}
|
||||||
outi += 5
|
outi += 5
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,8 +320,8 @@ to_diagnostic_format :: proc {
|
|||||||
|
|
||||||
// Turns the given CBOR value into a human-readable string.
|
// Turns the given CBOR value into a human-readable string.
|
||||||
// See docs on the proc group `diagnose` for more info.
|
// See docs on the proc group `diagnose` for more info.
|
||||||
to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error {
|
to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator, loc := #caller_location) -> (string, mem.Allocator_Error) #optional_allocator_error {
|
||||||
b := strings.builder_make(allocator)
|
b := strings.builder_make(allocator, loc)
|
||||||
w := strings.to_stream(&b)
|
w := strings.to_stream(&b)
|
||||||
err := to_diagnostic_format_writer(w, val, padding)
|
err := to_diagnostic_format_writer(w, val, padding)
|
||||||
if err == .EOF {
|
if err == .EOF {
|
||||||
|
|||||||
@@ -95,24 +95,25 @@ decode :: decode_from
|
|||||||
|
|
||||||
// Decodes the given string as CBOR.
|
// Decodes the given string as CBOR.
|
||||||
// See docs on the proc group `decode` for more information.
|
// See docs on the proc group `decode` for more information.
|
||||||
decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
|
decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
|
||||||
r: strings.Reader
|
r: strings.Reader
|
||||||
strings.reader_init(&r, s)
|
strings.reader_init(&r, s)
|
||||||
return decode_from_reader(strings.reader_to_stream(&r), flags, allocator)
|
return decode_from_reader(strings.reader_to_stream(&r), flags, allocator, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads a CBOR value from the given reader.
|
// Reads a CBOR value from the given reader.
|
||||||
// See docs on the proc group `decode` for more information.
|
// See docs on the proc group `decode` for more information.
|
||||||
decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
|
decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
|
||||||
return decode_from_decoder(
|
return decode_from_decoder(
|
||||||
Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r },
|
Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r },
|
||||||
allocator=allocator,
|
allocator=allocator,
|
||||||
|
loc = loc,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads a CBOR value from the given decoder.
|
// Reads a CBOR value from the given decoder.
|
||||||
// See docs on the proc group `decode` for more information.
|
// See docs on the proc group `decode` for more information.
|
||||||
decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
|
decode_from_decoder :: proc(d: Decoder, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
d := d
|
d := d
|
||||||
@@ -121,13 +122,13 @@ decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: V
|
|||||||
d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC
|
d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err = _decode_from_decoder(d)
|
v, err = _decode_from_decoder(d, {}, allocator, loc)
|
||||||
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
||||||
if err == .EOF { err = .Unexpected_EOF }
|
if err == .EOF { err = .Unexpected_EOF }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, err: Decode_Error) {
|
_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0), allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
|
||||||
hdr := hdr
|
hdr := hdr
|
||||||
r := d.reader
|
r := d.reader
|
||||||
if hdr == Header(0) { hdr = _decode_header(r) or_return }
|
if hdr == Header(0) { hdr = _decode_header(r) or_return }
|
||||||
@@ -161,11 +162,11 @@ _decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value,
|
|||||||
switch maj {
|
switch maj {
|
||||||
case .Unsigned: return _decode_tiny_u8(add)
|
case .Unsigned: return _decode_tiny_u8(add)
|
||||||
case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil
|
case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil
|
||||||
case .Bytes: return _decode_bytes_ptr(d, add)
|
case .Bytes: return _decode_bytes_ptr(d, add, .Bytes, allocator, loc)
|
||||||
case .Text: return _decode_text_ptr(d, add)
|
case .Text: return _decode_text_ptr(d, add, allocator, loc)
|
||||||
case .Array: return _decode_array_ptr(d, add)
|
case .Array: return _decode_array_ptr(d, add, allocator, loc)
|
||||||
case .Map: return _decode_map_ptr(d, add)
|
case .Map: return _decode_map_ptr(d, add, allocator, loc)
|
||||||
case .Tag: return _decode_tag_ptr(d, add)
|
case .Tag: return _decode_tag_ptr(d, add, allocator, loc)
|
||||||
case .Other: return _decode_tiny_simple(add)
|
case .Other: return _decode_tiny_simple(add)
|
||||||
case: return nil, .Bad_Major
|
case: return nil, .Bad_Major
|
||||||
}
|
}
|
||||||
@@ -203,27 +204,27 @@ encode :: encode_into
|
|||||||
|
|
||||||
// Encodes the CBOR value into binary CBOR allocated on the given allocator.
|
// Encodes the CBOR value into binary CBOR allocated on the given allocator.
|
||||||
// See the docs on the proc group `encode_into` for more info.
|
// See the docs on the proc group `encode_into` for more info.
|
||||||
encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (data: []byte, err: Encode_Error) {
|
encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (data: []byte, err: Encode_Error) {
|
||||||
b := strings.builder_make(allocator) or_return
|
b := strings.builder_make(allocator, loc) or_return
|
||||||
encode_into_builder(&b, v, flags, temp_allocator) or_return
|
encode_into_builder(&b, v, flags, temp_allocator) or_return
|
||||||
return b.buf[:], nil
|
return b.buf[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encodes the CBOR value into binary CBOR written to the given builder.
|
// Encodes the CBOR value into binary CBOR written to the given builder.
|
||||||
// See the docs on the proc group `encode_into` for more info.
|
// See the docs on the proc group `encode_into` for more info.
|
||||||
encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
|
encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error {
|
||||||
return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator)
|
return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator, loc=loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encodes the CBOR value into binary CBOR written to the given writer.
|
// Encodes the CBOR value into binary CBOR written to the given writer.
|
||||||
// See the docs on the proc group `encode_into` for more info.
|
// See the docs on the proc group `encode_into` for more info.
|
||||||
encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
|
encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error {
|
||||||
return encode_into_encoder(Encoder{flags, w, temp_allocator}, v)
|
return encode_into_encoder(Encoder{flags, w, temp_allocator}, v, loc=loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encodes the CBOR value into binary CBOR written to the given encoder.
|
// Encodes the CBOR value into binary CBOR written to the given encoder.
|
||||||
// See the docs on the proc group `encode_into` for more info.
|
// See the docs on the proc group `encode_into` for more info.
|
||||||
encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error {
|
encode_into_encoder :: proc(e: Encoder, v: Value, loc := #caller_location) -> Encode_Error {
|
||||||
e := e
|
e := e
|
||||||
|
|
||||||
if e.temp_allocator.procedure == nil {
|
if e.temp_allocator.procedure == nil {
|
||||||
@@ -232,7 +233,7 @@ encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error {
|
|||||||
|
|
||||||
if .Self_Described_CBOR in e.flags {
|
if .Self_Described_CBOR in e.flags {
|
||||||
_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return
|
_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return
|
||||||
e.flags &~= { .Self_Described_CBOR }
|
e.flags -= { .Self_Described_CBOR }
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v_spec in v {
|
switch v_spec in v {
|
||||||
@@ -366,21 +367,21 @@ _encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) {
|
_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: ^Bytes, err: Decode_Error) {
|
||||||
v = new(Bytes) or_return
|
v = new(Bytes, allocator, loc) or_return
|
||||||
defer if err != nil { free(v) }
|
defer if err != nil { free(v, allocator, loc) }
|
||||||
|
|
||||||
v^ = _decode_bytes(d, add, type) or_return
|
v^ = _decode_bytes(d, add, type, allocator, loc) or_return
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator) -> (v: Bytes, err: Decode_Error) {
|
_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: Bytes, err: Decode_Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
add := add
|
add := add
|
||||||
n, scap := _decode_len_str(d, add) or_return
|
n, scap := _decode_len_str(d, add) or_return
|
||||||
|
|
||||||
buf := strings.builder_make(0, scap) or_return
|
buf := strings.builder_make(0, scap, allocator, loc) or_return
|
||||||
defer if err != nil { strings.builder_destroy(&buf) }
|
defer if err != nil { strings.builder_destroy(&buf) }
|
||||||
buf_stream := strings.to_stream(&buf)
|
buf_stream := strings.to_stream(&buf)
|
||||||
|
|
||||||
@@ -422,44 +423,44 @@ _decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := c
|
|||||||
_encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: Encode_Error) {
|
_encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: Encode_Error) {
|
||||||
assert(len(val) >= 0)
|
assert(len(val) >= 0)
|
||||||
_encode_u64(e, u64(len(val)), major) or_return
|
_encode_u64(e, u64(len(val)), major) or_return
|
||||||
_, err = io.write_full(e.writer, val[:])
|
_, err = io.write_full(e.writer, val[:])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) {
|
_decode_text_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Text, err: Decode_Error) {
|
||||||
v = new(Text) or_return
|
v = new(Text, allocator, loc) or_return
|
||||||
defer if err != nil { free(v) }
|
defer if err != nil { free(v) }
|
||||||
|
|
||||||
v^ = _decode_text(d, add) or_return
|
v^ = _decode_text(d, add, allocator, loc) or_return
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator) -> (v: Text, err: Decode_Error) {
|
_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Text, err: Decode_Error) {
|
||||||
return (Text)(_decode_bytes(d, add, .Text, allocator) or_return), nil
|
return (Text)(_decode_bytes(d, add, .Text, allocator, loc) or_return), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_encode_text :: proc(e: Encoder, val: Text) -> Encode_Error {
|
_encode_text :: proc(e: Encoder, val: Text) -> Encode_Error {
|
||||||
return _encode_bytes(e, transmute([]byte)val, .Text)
|
return _encode_bytes(e, transmute([]byte)val, .Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_array_ptr :: proc(d: Decoder, add: Add) -> (v: ^Array, err: Decode_Error) {
|
_decode_array_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Array, err: Decode_Error) {
|
||||||
v = new(Array) or_return
|
v = new(Array, allocator, loc) or_return
|
||||||
defer if err != nil { free(v) }
|
defer if err != nil { free(v) }
|
||||||
|
|
||||||
v^ = _decode_array(d, add) or_return
|
v^ = _decode_array(d, add, allocator, loc) or_return
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) {
|
_decode_array :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Array, err: Decode_Error) {
|
||||||
n, scap := _decode_len_container(d, add) or_return
|
n, scap := _decode_len_container(d, add) or_return
|
||||||
array := make([dynamic]Value, 0, scap) or_return
|
array := make([dynamic]Value, 0, scap, allocator, loc) or_return
|
||||||
defer if err != nil {
|
defer if err != nil {
|
||||||
for entry in array { destroy(entry) }
|
for entry in array { destroy(entry, allocator) }
|
||||||
delete(array)
|
delete(array, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; n == -1 || i < n; i += 1 {
|
for i := 0; n == -1 || i < n; i += 1 {
|
||||||
val, verr := _decode_from_decoder(d)
|
val, verr := _decode_from_decoder(d, {}, allocator, loc)
|
||||||
if n == -1 && verr == .Break {
|
if n == -1 && verr == .Break {
|
||||||
break
|
break
|
||||||
} else if verr != nil {
|
} else if verr != nil {
|
||||||
@@ -479,45 +480,45 @@ _decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) {
|
|||||||
_encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error {
|
_encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error {
|
||||||
assert(len(arr) >= 0)
|
assert(len(arr) >= 0)
|
||||||
_encode_u64(e, u64(len(arr)), .Array)
|
_encode_u64(e, u64(len(arr)), .Array)
|
||||||
for val in arr {
|
for val in arr {
|
||||||
encode(e, val) or_return
|
encode(e, val) or_return
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_map_ptr :: proc(d: Decoder, add: Add) -> (v: ^Map, err: Decode_Error) {
|
_decode_map_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Map, err: Decode_Error) {
|
||||||
v = new(Map) or_return
|
v = new(Map, allocator, loc) or_return
|
||||||
defer if err != nil { free(v) }
|
defer if err != nil { free(v) }
|
||||||
|
|
||||||
v^ = _decode_map(d, add) or_return
|
v^ = _decode_map(d, add, allocator, loc) or_return
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_map :: proc(d: Decoder, add: Add) -> (v: Map, err: Decode_Error) {
|
_decode_map :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Map, err: Decode_Error) {
|
||||||
n, scap := _decode_len_container(d, add) or_return
|
n, scap := _decode_len_container(d, add) or_return
|
||||||
items := make([dynamic]Map_Entry, 0, scap) or_return
|
items := make([dynamic]Map_Entry, 0, scap, allocator, loc) or_return
|
||||||
defer if err != nil {
|
defer if err != nil {
|
||||||
for entry in items {
|
for entry in items {
|
||||||
destroy(entry.key)
|
destroy(entry.key)
|
||||||
destroy(entry.value)
|
destroy(entry.value)
|
||||||
}
|
}
|
||||||
delete(items)
|
delete(items, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; n == -1 || i < n; i += 1 {
|
for i := 0; n == -1 || i < n; i += 1 {
|
||||||
key, kerr := _decode_from_decoder(d)
|
key, kerr := _decode_from_decoder(d, {}, allocator, loc)
|
||||||
if n == -1 && kerr == .Break {
|
if n == -1 && kerr == .Break {
|
||||||
break
|
break
|
||||||
} else if kerr != nil {
|
} else if kerr != nil {
|
||||||
return nil, kerr
|
return nil, kerr
|
||||||
}
|
}
|
||||||
|
|
||||||
value := _decode_from_decoder(d) or_return
|
value := _decode_from_decoder(d, {}, allocator, loc) or_return
|
||||||
|
|
||||||
append(&items, Map_Entry{
|
append(&items, Map_Entry{
|
||||||
key = key,
|
key = key,
|
||||||
value = value,
|
value = value,
|
||||||
}) or_return
|
}, loc) or_return
|
||||||
}
|
}
|
||||||
|
|
||||||
if .Shrink_Excess in d.flags { shrink(&items) }
|
if .Shrink_Excess in d.flags { shrink(&items) }
|
||||||
@@ -575,23 +576,23 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) {
|
|||||||
encode(e, entry.entry.value) or_return
|
encode(e, entry.entry.value) or_return
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_tag_ptr :: proc(d: Decoder, add: Add) -> (v: Value, err: Decode_Error) {
|
_decode_tag_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
|
||||||
tag := _decode_tag(d, add) or_return
|
tag := _decode_tag(d, add, allocator, loc) or_return
|
||||||
if t, ok := tag.?; ok {
|
if t, ok := tag.?; ok {
|
||||||
defer if err != nil { destroy(t.value) }
|
defer if err != nil { destroy(t.value) }
|
||||||
tp := new(Tag) or_return
|
tp := new(Tag, allocator, loc) or_return
|
||||||
tp^ = t
|
tp^ = t
|
||||||
return tp, nil
|
return tp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// no error, no tag, this was the self described CBOR tag, skip it.
|
// no error, no tag, this was the self described CBOR tag, skip it.
|
||||||
return _decode_from_decoder(d)
|
return _decode_from_decoder(d, {}, allocator, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) {
|
_decode_tag :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Maybe(Tag), err: Decode_Error) {
|
||||||
num := _decode_uint_as_u64(d.reader, add) or_return
|
num := _decode_uint_as_u64(d.reader, add) or_return
|
||||||
|
|
||||||
// CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR.
|
// CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR.
|
||||||
@@ -602,7 +603,7 @@ _decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error)
|
|||||||
|
|
||||||
t := Tag{
|
t := Tag{
|
||||||
number = num,
|
number = num,
|
||||||
value = _decode_from_decoder(d) or_return,
|
value = _decode_from_decoder(d, {}, allocator, loc) or_return,
|
||||||
}
|
}
|
||||||
|
|
||||||
if nested, ok := t.value.(^Tag); ok {
|
if nested, ok := t.value.(^Tag); ok {
|
||||||
@@ -625,7 +626,7 @@ _decode_uint_as_u64 :: proc(r: io.Reader, add: Add) -> (nr: u64, err: Decode_Err
|
|||||||
|
|
||||||
_encode_tag :: proc(e: Encoder, val: Tag) -> Encode_Error {
|
_encode_tag :: proc(e: Encoder, val: Tag) -> Encode_Error {
|
||||||
_encode_u64(e, val.number, .Tag) or_return
|
_encode_u64(e, val.number, .Tag) or_return
|
||||||
return encode(e, val.value)
|
return encode(e, val.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_simple :: proc(r: io.Reader) -> (v: Simple, err: io.Error) {
|
_decode_simple :: proc(r: io.Reader) -> (v: Simple, err: io.Error) {
|
||||||
@@ -738,16 +739,16 @@ _encode_nil :: proc(w: io.Writer) -> io.Error {
|
|||||||
// Streaming
|
// Streaming
|
||||||
|
|
||||||
encode_stream_begin :: proc(w: io.Writer, major: Major) -> (err: io.Error) {
|
encode_stream_begin :: proc(w: io.Writer, major: Major) -> (err: io.Error) {
|
||||||
assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type")
|
assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type")
|
||||||
|
|
||||||
header := (u8(major) << 5) | u8(Add.Length_Unknown)
|
header := (u8(major) << 5) | u8(Add.Length_Unknown)
|
||||||
_, err = io.write_full(w, {header})
|
_, err = io.write_full(w, {header})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
encode_stream_end :: proc(w: io.Writer) -> io.Error {
|
encode_stream_end :: proc(w: io.Writer) -> io.Error {
|
||||||
header := (u8(Major.Other) << 5) | u8(Add.Break)
|
header := (u8(Major.Other) << 5) | u8(Add.Break)
|
||||||
_, err := io.write_full(w, {header})
|
_, err := io.write_full(w, {header})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,8 +757,8 @@ encode_stream_text :: _encode_text
|
|||||||
encode_stream_array_item :: encode
|
encode_stream_array_item :: encode
|
||||||
|
|
||||||
encode_stream_map_entry :: proc(e: Encoder, key: Value, val: Value) -> Encode_Error {
|
encode_stream_map_entry :: proc(e: Encoder, key: Value, val: Value) -> Encode_Error {
|
||||||
encode(e, key) or_return
|
encode(e, key) or_return
|
||||||
return encode(e, val)
|
return encode(e, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For `Bytes` and `Text` strings: Decodes the number of items the header says follows.
|
// For `Bytes` and `Text` strings: Decodes the number of items the header says follows.
|
||||||
@@ -883,4 +884,4 @@ _encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return _encode_f64_exact(w, v)
|
return _encode_f64_exact(w, v)
|
||||||
}
|
}
|
||||||
@@ -45,8 +45,8 @@ marshal :: marshal_into
|
|||||||
|
|
||||||
// Marshals the given value into a CBOR byte stream (allocated using the given allocator).
|
// Marshals the given value into a CBOR byte stream (allocated using the given allocator).
|
||||||
// See docs on the `marshal_into` proc group for more info.
|
// See docs on the `marshal_into` proc group for more info.
|
||||||
marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (bytes: []byte, err: Marshal_Error) {
|
marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (bytes: []byte, err: Marshal_Error) {
|
||||||
b, alloc_err := strings.builder_make(allocator)
|
b, alloc_err := strings.builder_make(allocator, loc=loc)
|
||||||
// The builder as a stream also returns .EOF if it ran out of memory so this is consistent.
|
// The builder as a stream also returns .EOF if it ran out of memory so this is consistent.
|
||||||
if alloc_err != nil {
|
if alloc_err != nil {
|
||||||
return nil, .EOF
|
return nil, .EOF
|
||||||
@@ -54,7 +54,7 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a
|
|||||||
|
|
||||||
defer if err != nil { strings.builder_destroy(&b) }
|
defer if err != nil { strings.builder_destroy(&b) }
|
||||||
|
|
||||||
if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil {
|
if err = marshal_into_builder(&b, v, flags, temp_allocator, loc=loc); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,20 +63,20 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a
|
|||||||
|
|
||||||
// Marshals the given value into a CBOR byte stream written to the given builder.
|
// Marshals the given value into a CBOR byte stream written to the given builder.
|
||||||
// See docs on the `marshal_into` proc group for more info.
|
// See docs on the `marshal_into` proc group for more info.
|
||||||
marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
|
marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error {
|
||||||
return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator)
|
return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator, loc=loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshals the given value into a CBOR byte stream written to the given writer.
|
// Marshals the given value into a CBOR byte stream written to the given writer.
|
||||||
// See docs on the `marshal_into` proc group for more info.
|
// See docs on the `marshal_into` proc group for more info.
|
||||||
marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
|
marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error {
|
||||||
encoder := Encoder{flags, w, temp_allocator}
|
encoder := Encoder{flags, w, temp_allocator}
|
||||||
return marshal_into_encoder(encoder, v)
|
return marshal_into_encoder(encoder, v, loc=loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshals the given value into a CBOR byte stream written to the given encoder.
|
// Marshals the given value into a CBOR byte stream written to the given encoder.
|
||||||
// See docs on the `marshal_into` proc group for more info.
|
// See docs on the `marshal_into` proc group for more info.
|
||||||
marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (err: Marshal_Error) {
|
||||||
e := e
|
e := e
|
||||||
|
|
||||||
if e.temp_allocator.procedure == nil {
|
if e.temp_allocator.procedure == nil {
|
||||||
@@ -85,7 +85,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
|||||||
|
|
||||||
if .Self_Described_CBOR in e.flags {
|
if .Self_Described_CBOR in e.flags {
|
||||||
err_conv(_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag)) or_return
|
err_conv(_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag)) or_return
|
||||||
e.flags &~= { .Self_Described_CBOR }
|
e.flags -= { .Self_Described_CBOR }
|
||||||
}
|
}
|
||||||
|
|
||||||
if v == nil {
|
if v == nil {
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Controls initialization of default tag implementations.
|
// Controls initialization of default tag implementations.
|
||||||
// JS and WASI default to a panic allocator so we don't want to do it on those.
|
|
||||||
INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_PANIC_ALLOCATOR && !ODIN_DEFAULT_TO_NIL_ALLOCATOR)
|
INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_PANIC_ALLOCATOR && !ODIN_DEFAULT_TO_NIL_ALLOCATOR)
|
||||||
|
|
||||||
@(private, init, disabled=!INITIALIZE_DEFAULT_TAGS)
|
@(private, init, disabled=!INITIALIZE_DEFAULT_TAGS)
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ unmarshal :: proc {
|
|||||||
unmarshal_from_string,
|
unmarshal_from_string,
|
||||||
}
|
}
|
||||||
|
|
||||||
unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
|
unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||||
err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator)
|
err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator, loc)
|
||||||
|
|
||||||
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
||||||
if err == .EOF { err = .Unexpected_EOF }
|
if err == .EOF { err = .Unexpected_EOF }
|
||||||
@@ -40,21 +40,21 @@ unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{},
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshals from a string, see docs on the proc group `Unmarshal` for more info.
|
// Unmarshals from a string, see docs on the proc group `Unmarshal` for more info.
|
||||||
unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
|
unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||||
sr: strings.Reader
|
sr: strings.Reader
|
||||||
r := strings.to_reader(&sr, s)
|
r := strings.to_reader(&sr, s)
|
||||||
|
|
||||||
err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator)
|
err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator, loc)
|
||||||
|
|
||||||
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
||||||
if err == .EOF { err = .Unexpected_EOF }
|
if err == .EOF { err = .Unexpected_EOF }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
|
unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||||
d := d
|
d := d
|
||||||
|
|
||||||
err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator)
|
err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator, loc)
|
||||||
|
|
||||||
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
||||||
if err == .EOF { err = .Unexpected_EOF }
|
if err == .EOF { err = .Unexpected_EOF }
|
||||||
@@ -62,7 +62,7 @@ unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.alloca
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator) -> Unmarshal_Error {
|
_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> Unmarshal_Error {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
context.temp_allocator = temp_allocator
|
context.temp_allocator = temp_allocator
|
||||||
v := v
|
v := v
|
||||||
@@ -78,10 +78,10 @@ _unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocat
|
|||||||
}
|
}
|
||||||
|
|
||||||
data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id}
|
data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id}
|
||||||
return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return))
|
return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return), allocator, temp_allocator, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Error) {
|
_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||||
v := v
|
v := v
|
||||||
ti := reflect.type_info_base(type_info_of(v.id))
|
ti := reflect.type_info_base(type_info_of(v.id))
|
||||||
r := d.reader
|
r := d.reader
|
||||||
@@ -104,7 +104,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
|
|||||||
// Allow generic unmarshal by doing it into a `Value`.
|
// Allow generic unmarshal by doing it into a `Value`.
|
||||||
switch &dst in v {
|
switch &dst in v {
|
||||||
case Value:
|
case Value:
|
||||||
dst = err_conv(_decode_from_decoder(d, hdr)) or_return
|
dst = err_conv(_decode_from_decoder(d, hdr, allocator, loc)) or_return
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,13 +273,13 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
|
|||||||
|
|
||||||
// NOTE: Because this is a special type and not to be treated as a general integer,
|
// NOTE: Because this is a special type and not to be treated as a general integer,
|
||||||
// We only put the value of it in fields that are explicitly of type `Simple`.
|
// We only put the value of it in fields that are explicitly of type `Simple`.
|
||||||
switch &dst in v {
|
switch &dst in v {
|
||||||
case Simple:
|
case Simple:
|
||||||
dst = decoded
|
dst = decoded
|
||||||
return
|
return
|
||||||
case:
|
case:
|
||||||
return _unsupported(v, hdr, add)
|
return _unsupported(v, hdr, add)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .Tag:
|
case .Tag:
|
||||||
switch &dst in v {
|
switch &dst in v {
|
||||||
@@ -308,7 +308,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
|
|||||||
if impl, ok := _tag_implementations_nr[nr]; ok {
|
if impl, ok := _tag_implementations_nr[nr]; ok {
|
||||||
return impl->unmarshal(d, nr, v)
|
return impl->unmarshal(d, nr, v)
|
||||||
} else if nr == TAG_OBJECT_TYPE {
|
} else if nr == TAG_OBJECT_TYPE {
|
||||||
return _unmarshal_union(d, v, ti, hdr)
|
return _unmarshal_union(d, v, ti, hdr, loc=loc)
|
||||||
} else {
|
} else {
|
||||||
// Discard the tag info and unmarshal as its value.
|
// Discard the tag info and unmarshal as its value.
|
||||||
return _unmarshal_value(d, v, _decode_header(r) or_return)
|
return _unmarshal_value(d, v, _decode_header(r) or_return)
|
||||||
@@ -316,19 +316,19 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
|
|||||||
|
|
||||||
return _unsupported(v, hdr, add)
|
return _unsupported(v, hdr, add)
|
||||||
|
|
||||||
case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add)
|
case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add, allocator=allocator, loc=loc)
|
||||||
case .Text: return _unmarshal_string(d, v, ti, hdr, add)
|
case .Text: return _unmarshal_string(d, v, ti, hdr, add, allocator=allocator, loc=loc)
|
||||||
case .Array: return _unmarshal_array(d, v, ti, hdr, add)
|
case .Array: return _unmarshal_array(d, v, ti, hdr, add, allocator=allocator, loc=loc)
|
||||||
case .Map: return _unmarshal_map(d, v, ti, hdr, add)
|
case .Map: return _unmarshal_map(d, v, ti, hdr, add, allocator=allocator, loc=loc)
|
||||||
|
|
||||||
case: return .Bad_Major
|
case: return .Bad_Major
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
|
_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||||
#partial switch t in ti.variant {
|
#partial switch t in ti.variant {
|
||||||
case reflect.Type_Info_String:
|
case reflect.Type_Info_String:
|
||||||
bytes := err_conv(_decode_bytes(d, add)) or_return
|
bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
|
||||||
|
|
||||||
if t.is_cstring {
|
if t.is_cstring {
|
||||||
raw := (^cstring)(v.data)
|
raw := (^cstring)(v.data)
|
||||||
@@ -347,7 +347,7 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
|
|
||||||
if elem_base.id != byte { return _unsupported(v, hdr) }
|
if elem_base.id != byte { return _unsupported(v, hdr) }
|
||||||
|
|
||||||
bytes := err_conv(_decode_bytes(d, add)) or_return
|
bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
|
||||||
raw := (^mem.Raw_Slice)(v.data)
|
raw := (^mem.Raw_Slice)(v.data)
|
||||||
raw^ = transmute(mem.Raw_Slice)bytes
|
raw^ = transmute(mem.Raw_Slice)bytes
|
||||||
return
|
return
|
||||||
@@ -357,12 +357,12 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
|
|
||||||
if elem_base.id != byte { return _unsupported(v, hdr) }
|
if elem_base.id != byte { return _unsupported(v, hdr) }
|
||||||
|
|
||||||
bytes := err_conv(_decode_bytes(d, add)) or_return
|
bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
|
||||||
raw := (^mem.Raw_Dynamic_Array)(v.data)
|
raw := (^mem.Raw_Dynamic_Array)(v.data)
|
||||||
raw.data = raw_data(bytes)
|
raw.data = raw_data(bytes)
|
||||||
raw.len = len(bytes)
|
raw.len = len(bytes)
|
||||||
raw.cap = len(bytes)
|
raw.cap = len(bytes)
|
||||||
raw.allocator = context.allocator
|
raw.allocator = allocator
|
||||||
return
|
return
|
||||||
|
|
||||||
case reflect.Type_Info_Array:
|
case reflect.Type_Info_Array:
|
||||||
@@ -385,10 +385,10 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
return _unsupported(v, hdr)
|
return _unsupported(v, hdr)
|
||||||
}
|
}
|
||||||
|
|
||||||
_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
|
_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||||
#partial switch t in ti.variant {
|
#partial switch t in ti.variant {
|
||||||
case reflect.Type_Info_String:
|
case reflect.Type_Info_String:
|
||||||
text := err_conv(_decode_text(d, add)) or_return
|
text := err_conv(_decode_text(d, add, allocator, loc)) or_return
|
||||||
|
|
||||||
if t.is_cstring {
|
if t.is_cstring {
|
||||||
raw := (^cstring)(v.data)
|
raw := (^cstring)(v.data)
|
||||||
@@ -403,8 +403,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
|
|||||||
|
|
||||||
// Enum by its variant name.
|
// Enum by its variant name.
|
||||||
case reflect.Type_Info_Enum:
|
case reflect.Type_Info_Enum:
|
||||||
text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
|
text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return
|
||||||
defer delete(text, context.temp_allocator)
|
defer delete(text, temp_allocator, loc)
|
||||||
|
|
||||||
for name, i in t.names {
|
for name, i in t.names {
|
||||||
if name == text {
|
if name == text {
|
||||||
@@ -414,8 +414,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
|
|||||||
}
|
}
|
||||||
|
|
||||||
case reflect.Type_Info_Rune:
|
case reflect.Type_Info_Rune:
|
||||||
text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
|
text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return
|
||||||
defer delete(text, context.temp_allocator)
|
defer delete(text, temp_allocator, loc)
|
||||||
|
|
||||||
r := (^rune)(v.data)
|
r := (^rune)(v.data)
|
||||||
dr, n := utf8.decode_rune(text)
|
dr, n := utf8.decode_rune(text)
|
||||||
@@ -430,13 +430,15 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
|
|||||||
return _unsupported(v, hdr)
|
return _unsupported(v, hdr)
|
||||||
}
|
}
|
||||||
|
|
||||||
_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
|
_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||||
assign_array :: proc(
|
assign_array :: proc(
|
||||||
d: Decoder,
|
d: Decoder,
|
||||||
da: ^mem.Raw_Dynamic_Array,
|
da: ^mem.Raw_Dynamic_Array,
|
||||||
elemt: ^reflect.Type_Info,
|
elemt: ^reflect.Type_Info,
|
||||||
length: int,
|
length: int,
|
||||||
growable := true,
|
growable := true,
|
||||||
|
allocator := context.allocator,
|
||||||
|
loc := #caller_location,
|
||||||
) -> (out_of_space: bool, err: Unmarshal_Error) {
|
) -> (out_of_space: bool, err: Unmarshal_Error) {
|
||||||
for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 {
|
for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 {
|
||||||
elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size))
|
elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size))
|
||||||
@@ -450,13 +452,13 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
if !growable { return true, .Out_Of_Memory }
|
if !growable { return true, .Out_Of_Memory }
|
||||||
|
|
||||||
cap := 2 * da.cap
|
cap := 2 * da.cap
|
||||||
ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap)
|
ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap, loc)
|
||||||
|
|
||||||
// NOTE: Might be lying here, but it is at least an allocator error.
|
// NOTE: Might be lying here, but it is at least an allocator error.
|
||||||
if !ok { return false, .Out_Of_Memory }
|
if !ok { return false, .Out_Of_Memory }
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _unmarshal_value(d, elem, hdr)
|
err = _unmarshal_value(d, elem, hdr, allocator=allocator, loc=loc)
|
||||||
if length == -1 && err == .Break { break }
|
if length == -1 && err == .Break { break }
|
||||||
if err != nil { return }
|
if err != nil { return }
|
||||||
|
|
||||||
@@ -469,10 +471,10 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
// Allow generically storing the values array.
|
// Allow generically storing the values array.
|
||||||
switch &dst in v {
|
switch &dst in v {
|
||||||
case ^Array:
|
case ^Array:
|
||||||
dst = err_conv(_decode_array_ptr(d, add)) or_return
|
dst = err_conv(_decode_array_ptr(d, add, allocator=allocator, loc=loc)) or_return
|
||||||
return
|
return
|
||||||
case Array:
|
case Array:
|
||||||
dst = err_conv(_decode_array(d, add)) or_return
|
dst = err_conv(_decode_array(d, add, allocator=allocator, loc=loc)) or_return
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,8 +482,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
case reflect.Type_Info_Slice:
|
case reflect.Type_Info_Slice:
|
||||||
length, scap := err_conv(_decode_len_container(d, add)) or_return
|
length, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||||
|
|
||||||
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
|
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return
|
||||||
defer if err != nil { mem.free_bytes(data) }
|
defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
|
||||||
|
|
||||||
da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator }
|
da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator }
|
||||||
|
|
||||||
@@ -489,7 +491,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
|
|
||||||
if .Shrink_Excess in d.flags {
|
if .Shrink_Excess in d.flags {
|
||||||
// Ignoring an error here, but this is not critical to succeed.
|
// Ignoring an error here, but this is not critical to succeed.
|
||||||
_ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len)
|
_ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len, loc=loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
raw := (^mem.Raw_Slice)(v.data)
|
raw := (^mem.Raw_Slice)(v.data)
|
||||||
@@ -500,8 +502,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
case reflect.Type_Info_Dynamic_Array:
|
case reflect.Type_Info_Dynamic_Array:
|
||||||
length, scap := err_conv(_decode_len_container(d, add)) or_return
|
length, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||||
|
|
||||||
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
|
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, loc=loc) or_return
|
||||||
defer if err != nil { mem.free_bytes(data) }
|
defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
|
||||||
|
|
||||||
raw := (^mem.Raw_Dynamic_Array)(v.data)
|
raw := (^mem.Raw_Dynamic_Array)(v.data)
|
||||||
raw.data = raw_data(data)
|
raw.data = raw_data(data)
|
||||||
@@ -513,7 +515,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
|
|
||||||
if .Shrink_Excess in d.flags {
|
if .Shrink_Excess in d.flags {
|
||||||
// Ignoring an error here, but this is not critical to succeed.
|
// Ignoring an error here, but this is not critical to succeed.
|
||||||
_ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len)
|
_ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len, loc=loc)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -525,7 +527,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
return _unsupported(v, hdr)
|
return _unsupported(v, hdr)
|
||||||
}
|
}
|
||||||
|
|
||||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
|
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
|
||||||
|
|
||||||
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
|
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
|
||||||
if out_of_space { return _unsupported(v, hdr) }
|
if out_of_space { return _unsupported(v, hdr) }
|
||||||
@@ -539,7 +541,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
return _unsupported(v, hdr)
|
return _unsupported(v, hdr)
|
||||||
}
|
}
|
||||||
|
|
||||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
|
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
|
||||||
|
|
||||||
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
|
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
|
||||||
if out_of_space { return _unsupported(v, hdr) }
|
if out_of_space { return _unsupported(v, hdr) }
|
||||||
@@ -553,7 +555,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
return _unsupported(v, hdr)
|
return _unsupported(v, hdr)
|
||||||
}
|
}
|
||||||
|
|
||||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, context.allocator }
|
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, allocator }
|
||||||
|
|
||||||
info: ^runtime.Type_Info
|
info: ^runtime.Type_Info
|
||||||
switch ti.id {
|
switch ti.id {
|
||||||
@@ -575,7 +577,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
return _unsupported(v, hdr)
|
return _unsupported(v, hdr)
|
||||||
}
|
}
|
||||||
|
|
||||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, context.allocator }
|
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, allocator }
|
||||||
|
|
||||||
info: ^runtime.Type_Info
|
info: ^runtime.Type_Info
|
||||||
switch ti.id {
|
switch ti.id {
|
||||||
@@ -593,17 +595,17 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
|
_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||||
r := d.reader
|
r := d.reader
|
||||||
decode_key :: proc(d: Decoder, v: any, allocator := context.allocator) -> (k: string, err: Unmarshal_Error) {
|
decode_key :: proc(d: Decoder, v: any, allocator := context.allocator, loc := #caller_location) -> (k: string, err: Unmarshal_Error) {
|
||||||
entry_hdr := _decode_header(d.reader) or_return
|
entry_hdr := _decode_header(d.reader) or_return
|
||||||
entry_maj, entry_add := _header_split(entry_hdr)
|
entry_maj, entry_add := _header_split(entry_hdr)
|
||||||
#partial switch entry_maj {
|
#partial switch entry_maj {
|
||||||
case .Text:
|
case .Text:
|
||||||
k = err_conv(_decode_text(d, entry_add, allocator)) or_return
|
k = err_conv(_decode_text(d, entry_add, allocator=allocator, loc=loc)) or_return
|
||||||
return
|
return
|
||||||
case .Bytes:
|
case .Bytes:
|
||||||
bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator)) or_return
|
bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator, loc=loc)) or_return
|
||||||
k = string(bytes)
|
k = string(bytes)
|
||||||
return
|
return
|
||||||
case:
|
case:
|
||||||
@@ -615,10 +617,10 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
|
|||||||
// Allow generically storing the map array.
|
// Allow generically storing the map array.
|
||||||
switch &dst in v {
|
switch &dst in v {
|
||||||
case ^Map:
|
case ^Map:
|
||||||
dst = err_conv(_decode_map_ptr(d, add)) or_return
|
dst = err_conv(_decode_map_ptr(d, add, allocator=allocator, loc=loc)) or_return
|
||||||
return
|
return
|
||||||
case Map:
|
case Map:
|
||||||
dst = err_conv(_decode_map(d, add)) or_return
|
dst = err_conv(_decode_map(d, add, allocator=allocator, loc=loc)) or_return
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -754,7 +756,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
|
|||||||
// Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which
|
// Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which
|
||||||
// contains an array of exactly two elements, the first is a textual representation of the following
|
// contains an array of exactly two elements, the first is a textual representation of the following
|
||||||
// CBOR value's type.
|
// CBOR value's type.
|
||||||
_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) {
|
_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||||
r := d.reader
|
r := d.reader
|
||||||
#partial switch t in ti.variant {
|
#partial switch t in ti.variant {
|
||||||
case reflect.Type_Info_Union:
|
case reflect.Type_Info_Union:
|
||||||
@@ -792,7 +794,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
case reflect.Type_Info_Named:
|
case reflect.Type_Info_Named:
|
||||||
if vti.name == target_name {
|
if vti.name == target_name {
|
||||||
reflect.set_union_variant_raw_tag(v, tag)
|
reflect.set_union_variant_raw_tag(v, tag)
|
||||||
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
|
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
case:
|
case:
|
||||||
@@ -804,7 +806,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
|||||||
|
|
||||||
if variant_name == target_name {
|
if variant_name == target_name {
|
||||||
reflect.set_union_variant_raw_tag(v, tag)
|
reflect.set_union_variant_raw_tag(v, tag)
|
||||||
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
|
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,9 @@ iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, mo
|
|||||||
return record, r.line_count - 1, r.last_iterator_error, r.last_iterator_error == nil
|
return record, r.line_count - 1, r.last_iterator_error, r.last_iterator_error == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get last error if we the iterator
|
// Get last CSV parse error if we ignored it in the iterator loop
|
||||||
|
//
|
||||||
|
// for record, row_idx in csv.iterator_next(&r) { ... }
|
||||||
iterator_last_error :: proc(r: Reader) -> (err: Error) {
|
iterator_last_error :: proc(r: Reader) -> (err: Error) {
|
||||||
return r.last_iterator_error
|
return r.last_iterator_error
|
||||||
}
|
}
|
||||||
@@ -169,7 +171,7 @@ is_io_error :: proc(err: Error, io_err: io.Error) -> bool {
|
|||||||
|
|
||||||
// read_all reads all the remaining records from r.
|
// read_all reads all the remaining records from r.
|
||||||
// Each record is a slice of fields.
|
// Each record is a slice of fields.
|
||||||
// read_all is defined to read until an EOF, and does not treat, and does not treat EOF as an error
|
// read_all is defined to read until an EOF, and does not treat EOF as an error
|
||||||
@(require_results)
|
@(require_results)
|
||||||
read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Error) {
|
read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|||||||
@@ -56,38 +56,27 @@ CDATA_END :: "]]>"
|
|||||||
COMMENT_START :: "<!--"
|
COMMENT_START :: "<!--"
|
||||||
COMMENT_END :: "-->"
|
COMMENT_END :: "-->"
|
||||||
|
|
||||||
/*
|
// Default: CDATA and comments are passed through unchanged.
|
||||||
Default: CDATA and comments are passed through unchanged.
|
|
||||||
*/
|
|
||||||
XML_Decode_Option :: enum u8 {
|
XML_Decode_Option :: enum u8 {
|
||||||
/*
|
// Do not decode & entities. It decodes by default. If given, overrides `Decode_CDATA`.
|
||||||
Do not decode & entities. It decodes by default.
|
|
||||||
If given, overrides `Decode_CDATA`.
|
|
||||||
*/
|
|
||||||
No_Entity_Decode,
|
No_Entity_Decode,
|
||||||
|
|
||||||
/*
|
// CDATA is unboxed.
|
||||||
CDATA is unboxed.
|
|
||||||
*/
|
|
||||||
Unbox_CDATA,
|
Unbox_CDATA,
|
||||||
|
|
||||||
/*
|
// Unboxed CDATA is decoded as well. Ignored if `.Unbox_CDATA` is not given.
|
||||||
Unboxed CDATA is decoded as well.
|
|
||||||
Ignored if `.Unbox_CDATA` is not given.
|
|
||||||
*/
|
|
||||||
Decode_CDATA,
|
Decode_CDATA,
|
||||||
|
|
||||||
/*
|
// Comments are stripped.
|
||||||
Comments are stripped.
|
|
||||||
*/
|
|
||||||
Comment_Strip,
|
Comment_Strip,
|
||||||
|
|
||||||
|
// Normalize whitespace
|
||||||
|
Normalize_Whitespace,
|
||||||
}
|
}
|
||||||
XML_Decode_Options :: bit_set[XML_Decode_Option; u8]
|
XML_Decode_Options :: bit_set[XML_Decode_Option; u8]
|
||||||
|
|
||||||
/*
|
// Decode a string that may include SGML/XML/HTML entities.
|
||||||
Decode a string that may include SGML/XML/HTML entities.
|
// The caller has to free the result.
|
||||||
The caller has to free the result.
|
|
||||||
*/
|
|
||||||
decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := context.allocator) -> (decoded: string, err: Error) {
|
decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := context.allocator) -> (decoded: string, err: Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
@@ -100,14 +89,14 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
|||||||
t := Tokenizer{src=input}
|
t := Tokenizer{src=input}
|
||||||
in_data := false
|
in_data := false
|
||||||
|
|
||||||
|
prev: rune = ' '
|
||||||
|
|
||||||
loop: for {
|
loop: for {
|
||||||
advance(&t) or_return
|
advance(&t) or_return
|
||||||
if t.r < 0 { break loop }
|
if t.r < 0 { break loop }
|
||||||
|
|
||||||
/*
|
// Below here we're never inside a CDATA tag. At most we'll see the start of one,
|
||||||
Below here we're never inside a CDATA tag.
|
// but that doesn't affect the logic.
|
||||||
At most we'll see the start of one, but that doesn't affect the logic.
|
|
||||||
*/
|
|
||||||
switch t.r {
|
switch t.r {
|
||||||
case '<':
|
case '<':
|
||||||
/*
|
/*
|
||||||
@@ -126,9 +115,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
|||||||
in_data = _handle_xml_special(&t, &builder, options) or_return
|
in_data = _handle_xml_special(&t, &builder, options) or_return
|
||||||
|
|
||||||
case ']':
|
case ']':
|
||||||
/*
|
// If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
|
||||||
If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
|
|
||||||
*/
|
|
||||||
if in_data {
|
if in_data {
|
||||||
if t.read_offset + len(CDATA_END) < len(t.src) {
|
if t.read_offset + len(CDATA_END) < len(t.src) {
|
||||||
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
||||||
@@ -143,22 +130,16 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
|||||||
|
|
||||||
case:
|
case:
|
||||||
if in_data && .Decode_CDATA not_in options {
|
if in_data && .Decode_CDATA not_in options {
|
||||||
/*
|
// Unboxed, but undecoded.
|
||||||
Unboxed, but undecoded.
|
|
||||||
*/
|
|
||||||
write_rune(&builder, t.r)
|
write_rune(&builder, t.r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.r == '&' {
|
if t.r == '&' {
|
||||||
if entity, entity_err := _extract_xml_entity(&t); entity_err != .None {
|
if entity, entity_err := _extract_xml_entity(&t); entity_err != .None {
|
||||||
/*
|
// We read to the end of the string without closing the entity. Pass through as-is.
|
||||||
We read to the end of the string without closing the entity.
|
|
||||||
Pass through as-is.
|
|
||||||
*/
|
|
||||||
write_string(&builder, entity)
|
write_string(&builder, entity)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if .No_Entity_Decode not_in options {
|
if .No_Entity_Decode not_in options {
|
||||||
if decoded, ok := xml_decode_entity(entity); ok {
|
if decoded, ok := xml_decode_entity(entity); ok {
|
||||||
write_rune(&builder, decoded)
|
write_rune(&builder, decoded)
|
||||||
@@ -166,19 +147,41 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Literal passthrough because the decode failed or we want entities not decoded.
|
||||||
Literal passthrough because the decode failed or we want entities not decoded.
|
|
||||||
*/
|
|
||||||
write_string(&builder, "&")
|
write_string(&builder, "&")
|
||||||
write_string(&builder, entity)
|
write_string(&builder, entity)
|
||||||
write_string(&builder, ";")
|
write_string(&builder, ";")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
write_rune(&builder, t.r)
|
// Handle AV Normalization: https://www.w3.org/TR/2006/REC-xml11-20060816/#AVNormalize
|
||||||
|
if .Normalize_Whitespace in options {
|
||||||
|
switch t.r {
|
||||||
|
case ' ', '\r', '\n', '\t':
|
||||||
|
if prev != ' ' {
|
||||||
|
write_rune(&builder, ' ')
|
||||||
|
prev = ' '
|
||||||
|
}
|
||||||
|
case:
|
||||||
|
write_rune(&builder, t.r)
|
||||||
|
prev = t.r
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-line-ends
|
||||||
|
switch t.r {
|
||||||
|
case '\n', 0x85, 0x2028:
|
||||||
|
write_rune(&builder, '\n')
|
||||||
|
case '\r': // Do nothing until next character
|
||||||
|
case:
|
||||||
|
if prev == '\r' { // Turn a single carriage return into a \n
|
||||||
|
write_rune(&builder, '\n')
|
||||||
|
}
|
||||||
|
write_rune(&builder, t.r)
|
||||||
|
}
|
||||||
|
prev = t.r
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.clone(strings.to_string(builder), allocator), err
|
return strings.clone(strings.to_string(builder), allocator), err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,24 +256,18 @@ xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) {
|
|||||||
return rune(val), true
|
return rune(val), true
|
||||||
|
|
||||||
case:
|
case:
|
||||||
/*
|
// Named entity.
|
||||||
Named entity.
|
|
||||||
*/
|
|
||||||
return named_xml_entity_to_rune(entity)
|
return named_xml_entity_to_rune(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Private XML helper to extract `&<stuff>;` entity.
|
||||||
Private XML helper to extract `&<stuff>;` entity.
|
|
||||||
*/
|
|
||||||
@(private="file")
|
@(private="file")
|
||||||
_extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
|
_extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
|
||||||
assert(t != nil && t.r == '&')
|
assert(t != nil && t.r == '&')
|
||||||
|
|
||||||
/*
|
// All of these would be in the ASCII range.
|
||||||
All of these would be in the ASCII range.
|
// Even if one is not, it doesn't matter. All characters we need to compare to extract are.
|
||||||
Even if one is not, it doesn't matter. All characters we need to compare to extract are.
|
|
||||||
*/
|
|
||||||
|
|
||||||
length := len(t.src)
|
length := len(t.src)
|
||||||
found := false
|
found := false
|
||||||
@@ -292,9 +289,7 @@ _extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
|
|||||||
return string(t.src[t.offset : t.read_offset]), .Invalid_Entity_Encoding
|
return string(t.src[t.offset : t.read_offset]), .Invalid_Entity_Encoding
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Private XML helper for CDATA and comments.
|
||||||
Private XML helper for CDATA and comments.
|
|
||||||
*/
|
|
||||||
@(private="file")
|
@(private="file")
|
||||||
_handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: XML_Decode_Options) -> (in_data: bool, err: Error) {
|
_handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: XML_Decode_Options) -> (in_data: bool, err: Error) {
|
||||||
assert(t != nil && t.r == '<')
|
assert(t != nil && t.r == '<')
|
||||||
@@ -304,20 +299,14 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
|
|||||||
t.read_offset += len(CDATA_START) - 1
|
t.read_offset += len(CDATA_START) - 1
|
||||||
|
|
||||||
if .Unbox_CDATA in options && .Decode_CDATA in options {
|
if .Unbox_CDATA in options && .Decode_CDATA in options {
|
||||||
/*
|
// We're unboxing _and_ decoding CDATA
|
||||||
We're unboxing _and_ decoding CDATA
|
|
||||||
*/
|
|
||||||
return true, .None
|
return true, .None
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// CDATA is passed through.
|
||||||
CDATA is passed through.
|
|
||||||
*/
|
|
||||||
offset := t.offset
|
offset := t.offset
|
||||||
|
|
||||||
/*
|
// Scan until end of CDATA.
|
||||||
Scan until end of CDATA.
|
|
||||||
*/
|
|
||||||
for {
|
for {
|
||||||
advance(t) or_return
|
advance(t) or_return
|
||||||
if t.r < 0 { return true, .CDATA_Not_Terminated }
|
if t.r < 0 { return true, .CDATA_Not_Terminated }
|
||||||
@@ -341,14 +330,10 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
|
|||||||
|
|
||||||
} else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
|
} else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
|
||||||
t.read_offset += len(COMMENT_START)
|
t.read_offset += len(COMMENT_START)
|
||||||
/*
|
// Comment is passed through by default.
|
||||||
Comment is passed through by default.
|
|
||||||
*/
|
|
||||||
offset := t.offset
|
offset := t.offset
|
||||||
|
|
||||||
/*
|
// Scan until end of Comment.
|
||||||
Scan until end of Comment.
|
|
||||||
*/
|
|
||||||
for {
|
for {
|
||||||
advance(t) or_return
|
advance(t) or_return
|
||||||
if t.r < 0 { return true, .Comment_Not_Terminated }
|
if t.r < 0 { return true, .Comment_Not_Terminated }
|
||||||
|
|||||||
+4816
-4816
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,8 @@ package encoding_hex
|
|||||||
|
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
|
||||||
encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds_check {
|
encode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> []byte #no_bounds_check {
|
||||||
dst := make([]byte, len(src) * 2, allocator)
|
dst := make([]byte, len(src) * 2, allocator, loc)
|
||||||
for i, j := 0, 0; i < len(src); i += 1 {
|
for i, j := 0, 0; i < len(src); i += 1 {
|
||||||
v := src[i]
|
v := src[i]
|
||||||
dst[j] = HEXTABLE[v>>4]
|
dst[j] = HEXTABLE[v>>4]
|
||||||
@@ -15,12 +15,12 @@ encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
decode :: proc(src: []byte, allocator := context.allocator) -> (dst: []byte, ok: bool) #no_bounds_check {
|
decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (dst: []byte, ok: bool) #no_bounds_check {
|
||||||
if len(src) % 2 == 1 {
|
if len(src) % 2 == 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dst = make([]byte, len(src) / 2, allocator)
|
dst = make([]byte, len(src) / 2, allocator, loc)
|
||||||
for i, j := 0, 1; j < len(src); j += 2 {
|
for i, j := 0, 1; j < len(src); j += 2 {
|
||||||
p := src[j-1]
|
p := src[j-1]
|
||||||
q := src[j]
|
q := src[j]
|
||||||
@@ -69,5 +69,4 @@ hex_digit :: proc(char: byte) -> (u8, bool) {
|
|||||||
case 'A' ..= 'F': return char - 'A' + 10, true
|
case 'A' ..= 'F': return char - 'A' + 10, true
|
||||||
case: return 0, false
|
case: return 0, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+16
-15
@@ -160,34 +160,35 @@ CONVENTION_SOFT_TRANSFORM :: "transform"
|
|||||||
|
|
||||||
/* destroy procedures */
|
/* destroy procedures */
|
||||||
|
|
||||||
meta_destroy :: proc(meta: Meta, allocator := context.allocator) {
|
meta_destroy :: proc(meta: Meta, allocator := context.allocator, loc := #caller_location) {
|
||||||
if nested, ok := meta.value.([]Meta); ok {
|
if nested, ok := meta.value.([]Meta); ok {
|
||||||
for m in nested {
|
for m in nested {
|
||||||
meta_destroy(m)
|
meta_destroy(m, loc=loc)
|
||||||
}
|
}
|
||||||
delete(nested, allocator)
|
delete(nested, allocator, loc=loc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes_destroy :: proc(nodes: []Node, allocator := context.allocator) {
|
nodes_destroy :: proc(nodes: []Node, allocator := context.allocator, loc := #caller_location) {
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
for meta in node.meta_data {
|
for meta in node.meta_data {
|
||||||
meta_destroy(meta)
|
meta_destroy(meta, loc=loc)
|
||||||
}
|
}
|
||||||
delete(node.meta_data, allocator)
|
delete(node.meta_data, allocator, loc=loc)
|
||||||
|
|
||||||
switch n in node.content {
|
switch n in node.content {
|
||||||
case Node_Geometry:
|
case Node_Geometry:
|
||||||
delete(n.corner_stack, allocator)
|
delete(n.corner_stack, allocator, loc=loc)
|
||||||
delete(n.edge_stack, allocator)
|
delete(n.vertex_stack, allocator, loc=loc)
|
||||||
delete(n.face_stack, allocator)
|
delete(n.edge_stack, allocator, loc=loc)
|
||||||
|
delete(n.face_stack, allocator, loc=loc)
|
||||||
case Node_Image:
|
case Node_Image:
|
||||||
delete(n.image_stack, allocator)
|
delete(n.image_stack, allocator, loc=loc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(nodes, allocator)
|
delete(nodes, allocator, loc=loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
file_destroy :: proc(file: File) {
|
file_destroy :: proc(file: File, loc := #caller_location) {
|
||||||
nodes_destroy(file.nodes, file.allocator)
|
nodes_destroy(file.nodes, file.allocator, loc=loc)
|
||||||
delete(file.backing, file.allocator)
|
delete(file.backing, file.allocator, loc=loc)
|
||||||
}
|
}
|
||||||
+20
-22
@@ -11,24 +11,21 @@ Read_Error :: enum {
|
|||||||
Unable_To_Read_File,
|
Unable_To_Read_File,
|
||||||
}
|
}
|
||||||
|
|
||||||
read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) {
|
read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
data, ok := os.read_entire_file(filename)
|
data, ok := os.read_entire_file(filename, allocator, loc)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = .Unable_To_Read_File
|
err = .Unable_To_Read_File
|
||||||
|
delete(data, allocator, loc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer if !ok {
|
file, err = read(data, filename, print_error, allocator, loc)
|
||||||
delete(data)
|
file.backing = data
|
||||||
} else {
|
|
||||||
file.backing = data
|
|
||||||
}
|
|
||||||
file, err = read(data, filename, print_error, allocator)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
read :: proc(data: []byte, filename := "<input>", print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) {
|
read :: proc(data: []byte, filename := "<input>", print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
|
||||||
Reader :: struct {
|
Reader :: struct {
|
||||||
filename: string,
|
filename: string,
|
||||||
data: []byte,
|
data: []byte,
|
||||||
@@ -79,8 +76,8 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
|||||||
return string(data[:len]), nil
|
return string(data[:len]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
read_meta :: proc(r: ^Reader, capacity: u32le) -> (meta_data: []Meta, err: Read_Error) {
|
read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) {
|
||||||
meta_data = make([]Meta, int(capacity))
|
meta_data = make([]Meta, int(capacity), allocator=allocator)
|
||||||
count := 0
|
count := 0
|
||||||
defer meta_data = meta_data[:count]
|
defer meta_data = meta_data[:count]
|
||||||
for &m in meta_data {
|
for &m in meta_data {
|
||||||
@@ -111,10 +108,10 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
read_layer_stack :: proc(r: ^Reader, capacity: u32le) -> (layers: Layer_Stack, err: Read_Error) {
|
read_layer_stack :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (layers: Layer_Stack, err: Read_Error) {
|
||||||
stack_count := read_value(r, u32le) or_return
|
stack_count := read_value(r, u32le) or_return
|
||||||
layer_count := 0
|
layer_count := 0
|
||||||
layers = make(Layer_Stack, stack_count)
|
layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc)
|
||||||
defer layers = layers[:layer_count]
|
defer layers = layers[:layer_count]
|
||||||
for &layer in layers {
|
for &layer in layers {
|
||||||
layer.name = read_name(r) or_return
|
layer.name = read_name(r) or_return
|
||||||
@@ -170,7 +167,8 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
|||||||
|
|
||||||
node_count := 0
|
node_count := 0
|
||||||
file.header = header^
|
file.header = header^
|
||||||
file.nodes = make([]Node, header.internal_node_count)
|
file.nodes = make([]Node, header.internal_node_count, allocator=allocator, loc=loc)
|
||||||
|
file.allocator = allocator
|
||||||
defer if err != nil {
|
defer if err != nil {
|
||||||
nodes_destroy(file.nodes)
|
nodes_destroy(file.nodes)
|
||||||
file.nodes = nil
|
file.nodes = nil
|
||||||
@@ -198,15 +196,15 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
|||||||
case .Geometry:
|
case .Geometry:
|
||||||
g: Node_Geometry
|
g: Node_Geometry
|
||||||
|
|
||||||
g.vertex_count = read_value(r, u32le) or_return
|
g.vertex_count = read_value(r, u32le) or_return
|
||||||
g.vertex_stack = read_layer_stack(r, g.vertex_count) or_return
|
g.vertex_stack = read_layer_stack(r, g.vertex_count, loc=loc) or_return
|
||||||
g.edge_corner_count = read_value(r, u32le) or_return
|
g.edge_corner_count = read_value(r, u32le) or_return
|
||||||
g.corner_stack = read_layer_stack(r, g.edge_corner_count) or_return
|
g.corner_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return
|
||||||
if header.version > 2 {
|
if header.version > 2 {
|
||||||
g.edge_stack = read_layer_stack(r, g.edge_corner_count) or_return
|
g.edge_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return
|
||||||
}
|
}
|
||||||
g.face_count = read_value(r, u32le) or_return
|
g.face_count = read_value(r, u32le) or_return
|
||||||
g.face_stack = read_layer_stack(r, g.face_count) or_return
|
g.face_stack = read_layer_stack(r, g.face_count, loc=loc) or_return
|
||||||
|
|
||||||
node.content = g
|
node.content = g
|
||||||
|
|
||||||
@@ -233,4 +231,4 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
|||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ Marshal_Options :: struct {
|
|||||||
spec: Specification,
|
spec: Specification,
|
||||||
|
|
||||||
// Use line breaks & tabs/spaces
|
// Use line breaks & tabs/spaces
|
||||||
pretty: bool,
|
pretty: bool,
|
||||||
|
|
||||||
// Use spaces for indentation instead of tabs
|
// Use spaces for indentation instead of tabs
|
||||||
use_spaces: bool,
|
use_spaces: bool,
|
||||||
@@ -34,7 +34,7 @@ Marshal_Options :: struct {
|
|||||||
spaces: int,
|
spaces: int,
|
||||||
|
|
||||||
// Output uint as hex in JSON5 & MJSON
|
// Output uint as hex in JSON5 & MJSON
|
||||||
write_uint_as_hex: bool,
|
write_uint_as_hex: bool,
|
||||||
|
|
||||||
// If spec is MJSON and this is true, then keys will be quoted.
|
// If spec is MJSON and this is true, then keys will be quoted.
|
||||||
//
|
//
|
||||||
@@ -62,8 +62,8 @@ Marshal_Options :: struct {
|
|||||||
mjson_skipped_first_braces_end: bool,
|
mjson_skipped_first_braces_end: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator) -> (data: []byte, err: Marshal_Error) {
|
marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Marshal_Error) {
|
||||||
b := strings.builder_make(allocator)
|
b := strings.builder_make(allocator, loc)
|
||||||
defer if err != nil {
|
defer if err != nil {
|
||||||
strings.builder_destroy(&b)
|
strings.builder_destroy(&b)
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
// allow uints to be printed as hex
|
// allow uints to be printed as hex
|
||||||
if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) {
|
if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) {
|
||||||
switch i in a {
|
switch i in a {
|
||||||
case u8, u16, u32, u64, u128:
|
case u8, u16, u32, u64, u128:
|
||||||
s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
|
s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
|
||||||
|
|
||||||
case:
|
case:
|
||||||
@@ -239,7 +239,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
case runtime.Type_Info_Array:
|
case runtime.Type_Info_Array:
|
||||||
opt_write_start(w, opt, '[') or_return
|
opt_write_start(w, opt, '[') or_return
|
||||||
for i in 0..<info.count {
|
for i in 0..<info.count {
|
||||||
opt_write_iteration(w, opt, i) or_return
|
opt_write_iteration(w, opt, i == 0) or_return
|
||||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||||
}
|
}
|
||||||
@@ -248,7 +248,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
case runtime.Type_Info_Enumerated_Array:
|
case runtime.Type_Info_Enumerated_Array:
|
||||||
opt_write_start(w, opt, '[') or_return
|
opt_write_start(w, opt, '[') or_return
|
||||||
for i in 0..<info.count {
|
for i in 0..<info.count {
|
||||||
opt_write_iteration(w, opt, i) or_return
|
opt_write_iteration(w, opt, i == 0) or_return
|
||||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||||
}
|
}
|
||||||
@@ -258,7 +258,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
opt_write_start(w, opt, '[') or_return
|
opt_write_start(w, opt, '[') or_return
|
||||||
array := cast(^mem.Raw_Dynamic_Array)v.data
|
array := cast(^mem.Raw_Dynamic_Array)v.data
|
||||||
for i in 0..<array.len {
|
for i in 0..<array.len {
|
||||||
opt_write_iteration(w, opt, i) or_return
|
opt_write_iteration(w, opt, i == 0) or_return
|
||||||
data := uintptr(array.data) + uintptr(i*info.elem_size)
|
data := uintptr(array.data) + uintptr(i*info.elem_size)
|
||||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||||
}
|
}
|
||||||
@@ -268,7 +268,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
opt_write_start(w, opt, '[') or_return
|
opt_write_start(w, opt, '[') or_return
|
||||||
slice := cast(^mem.Raw_Slice)v.data
|
slice := cast(^mem.Raw_Slice)v.data
|
||||||
for i in 0..<slice.len {
|
for i in 0..<slice.len {
|
||||||
opt_write_iteration(w, opt, i) or_return
|
opt_write_iteration(w, opt, i == 0) or_return
|
||||||
data := uintptr(slice.data) + uintptr(i*info.elem_size)
|
data := uintptr(slice.data) + uintptr(i*info.elem_size)
|
||||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||||
}
|
}
|
||||||
@@ -290,7 +290,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
for bucket_index in 0..<map_cap {
|
for bucket_index in 0..<map_cap {
|
||||||
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
|
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
|
||||||
|
|
||||||
opt_write_iteration(w, opt, i) or_return
|
opt_write_iteration(w, opt, i == 0) or_return
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
|
key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
|
||||||
@@ -356,7 +356,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
slice.sort_by(sorted[:], proc(i, j: Entry) -> bool { return i.key < j.key })
|
slice.sort_by(sorted[:], proc(i, j: Entry) -> bool { return i.key < j.key })
|
||||||
|
|
||||||
for s, i in sorted {
|
for s, i in sorted {
|
||||||
opt_write_iteration(w, opt, i) or_return
|
opt_write_iteration(w, opt, i == 0) or_return
|
||||||
opt_write_key(w, opt, s.key) or_return
|
opt_write_key(w, opt, s.key) or_return
|
||||||
marshal_to_writer(w, s.value, opt) or_return
|
marshal_to_writer(w, s.value, opt) or_return
|
||||||
}
|
}
|
||||||
@@ -387,17 +387,17 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
case runtime.Type_Info_Pointer,
|
case runtime.Type_Info_Pointer,
|
||||||
runtime.Type_Info_Multi_Pointer,
|
runtime.Type_Info_Multi_Pointer,
|
||||||
runtime.Type_Info_Procedure:
|
runtime.Type_Info_Procedure:
|
||||||
return (^rawptr)(v.data)^ == nil
|
return (^rawptr)(v.data)^ == nil
|
||||||
case runtime.Type_Info_Dynamic_Array:
|
case runtime.Type_Info_Dynamic_Array:
|
||||||
return (^runtime.Raw_Dynamic_Array)(v.data).len == 0
|
return (^runtime.Raw_Dynamic_Array)(v.data).len == 0
|
||||||
case runtime.Type_Info_Slice:
|
case runtime.Type_Info_Slice:
|
||||||
return (^runtime.Raw_Slice)(v.data).len == 0
|
return (^runtime.Raw_Slice)(v.data).len == 0
|
||||||
case runtime.Type_Info_Union,
|
case runtime.Type_Info_Union,
|
||||||
runtime.Type_Info_Bit_Set,
|
runtime.Type_Info_Bit_Set,
|
||||||
runtime.Type_Info_Soa_Pointer:
|
runtime.Type_Info_Soa_Pointer:
|
||||||
return reflect.is_nil(v)
|
return reflect.is_nil(v)
|
||||||
case runtime.Type_Info_Map:
|
case runtime.Type_Info_Map:
|
||||||
return (^runtime.Raw_Map)(v.data).len == 0
|
return (^runtime.Raw_Map)(v.data).len == 0
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -405,6 +405,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
marshal_struct_fields :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: Marshal_Error) {
|
marshal_struct_fields :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: Marshal_Error) {
|
||||||
ti := runtime.type_info_base(type_info_of(v.id))
|
ti := runtime.type_info_base(type_info_of(v.id))
|
||||||
info := ti.variant.(runtime.Type_Info_Struct)
|
info := ti.variant.(runtime.Type_Info_Struct)
|
||||||
|
first_iteration := true
|
||||||
for name, i in info.names {
|
for name, i in info.names {
|
||||||
omitempty := false
|
omitempty := false
|
||||||
|
|
||||||
@@ -424,7 +425,8 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
opt_write_iteration(w, opt, i) or_return
|
opt_write_iteration(w, opt, first_iteration) or_return
|
||||||
|
first_iteration = false
|
||||||
if json_name != "" {
|
if json_name != "" {
|
||||||
opt_write_key(w, opt, json_name) or_return
|
opt_write_key(w, opt, json_name) or_return
|
||||||
} else {
|
} else {
|
||||||
@@ -588,10 +590,10 @@ opt_write_start :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// insert comma separation and write indentations
|
// insert comma separation and write indentations
|
||||||
opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int) -> (err: io.Error) {
|
opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, first_iteration: bool) -> (err: io.Error) {
|
||||||
switch opt.spec {
|
switch opt.spec {
|
||||||
case .JSON, .JSON5:
|
case .JSON, .JSON5:
|
||||||
if iteration > 0 {
|
if !first_iteration {
|
||||||
io.write_byte(w, ',') or_return
|
io.write_byte(w, ',') or_return
|
||||||
|
|
||||||
if opt.pretty {
|
if opt.pretty {
|
||||||
@@ -601,8 +603,8 @@ opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int)
|
|||||||
|
|
||||||
opt_write_indentation(w, opt) or_return
|
opt_write_indentation(w, opt) or_return
|
||||||
|
|
||||||
case .MJSON:
|
case .MJSON:
|
||||||
if iteration > 0 {
|
if !first_iteration {
|
||||||
// on pretty no commas necessary
|
// on pretty no commas necessary
|
||||||
if opt.pretty {
|
if opt.pretty {
|
||||||
io.write_byte(w, '\n') or_return
|
io.write_byte(w, '\n') or_return
|
||||||
|
|||||||
@@ -28,27 +28,27 @@ make_parser_from_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, par
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) {
|
parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) {
|
||||||
return parse_string(string(data), spec, parse_integers, allocator)
|
return parse_string(string(data), spec, parse_integers, allocator, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) {
|
parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
p := make_parser_from_string(data, spec, parse_integers, allocator)
|
p := make_parser_from_string(data, spec, parse_integers, allocator)
|
||||||
|
|
||||||
switch p.spec {
|
switch p.spec {
|
||||||
case .JSON:
|
case .JSON:
|
||||||
return parse_object(&p)
|
return parse_object(&p, loc)
|
||||||
case .JSON5:
|
case .JSON5:
|
||||||
return parse_value(&p)
|
return parse_value(&p, loc)
|
||||||
case .SJSON:
|
case .SJSON:
|
||||||
#partial switch p.curr_token.kind {
|
#partial switch p.curr_token.kind {
|
||||||
case .Ident, .String:
|
case .Ident, .String:
|
||||||
return parse_object_body(&p, .EOF)
|
return parse_object_body(&p, .EOF, loc)
|
||||||
}
|
}
|
||||||
return parse_value(&p)
|
return parse_value(&p, loc)
|
||||||
}
|
}
|
||||||
return parse_object(&p)
|
return parse_object(&p, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
token_end_pos :: proc(tok: Token) -> Pos {
|
token_end_pos :: proc(tok: Token) -> Pos {
|
||||||
@@ -106,7 +106,7 @@ parse_comma :: proc(p: ^Parser) -> (do_break: bool) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
parse_value :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
|
||||||
err = .None
|
err = .None
|
||||||
token := p.curr_token
|
token := p.curr_token
|
||||||
#partial switch token.kind {
|
#partial switch token.kind {
|
||||||
@@ -142,13 +142,13 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
|||||||
|
|
||||||
case .String:
|
case .String:
|
||||||
advance_token(p)
|
advance_token(p)
|
||||||
return unquote_string(token, p.spec, p.allocator)
|
return unquote_string(token, p.spec, p.allocator, loc)
|
||||||
|
|
||||||
case .Open_Brace:
|
case .Open_Brace:
|
||||||
return parse_object(p)
|
return parse_object(p, loc)
|
||||||
|
|
||||||
case .Open_Bracket:
|
case .Open_Bracket:
|
||||||
return parse_array(p)
|
return parse_array(p, loc)
|
||||||
|
|
||||||
case:
|
case:
|
||||||
if p.spec != .JSON {
|
if p.spec != .JSON {
|
||||||
@@ -176,7 +176,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
parse_array :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
|
||||||
err = .None
|
err = .None
|
||||||
expect_token(p, .Open_Bracket) or_return
|
expect_token(p, .Open_Bracket) or_return
|
||||||
|
|
||||||
@@ -184,14 +184,14 @@ parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
|||||||
array.allocator = p.allocator
|
array.allocator = p.allocator
|
||||||
defer if err != nil {
|
defer if err != nil {
|
||||||
for elem in array {
|
for elem in array {
|
||||||
destroy_value(elem)
|
destroy_value(elem, loc=loc)
|
||||||
}
|
}
|
||||||
delete(array)
|
delete(array, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
for p.curr_token.kind != .Close_Bracket {
|
for p.curr_token.kind != .Close_Bracket {
|
||||||
elem := parse_value(p) or_return
|
elem := parse_value(p, loc) or_return
|
||||||
append(&array, elem)
|
append(&array, elem, loc)
|
||||||
|
|
||||||
if parse_comma(p) {
|
if parse_comma(p) {
|
||||||
break
|
break
|
||||||
@@ -228,38 +228,39 @@ clone_string :: proc(s: string, allocator: mem.Allocator, loc := #caller_locatio
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator) -> (key: string, err: Error) {
|
parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator, loc := #caller_location) -> (key: string, err: Error) {
|
||||||
tok := p.curr_token
|
tok := p.curr_token
|
||||||
if p.spec != .JSON {
|
if p.spec != .JSON {
|
||||||
if allow_token(p, .Ident) {
|
if allow_token(p, .Ident) {
|
||||||
return clone_string(tok.text, key_allocator)
|
return clone_string(tok.text, key_allocator, loc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tok_err := expect_token(p, .String); tok_err != nil {
|
if tok_err := expect_token(p, .String); tok_err != nil {
|
||||||
err = .Expected_String_For_Object_Key
|
err = .Expected_String_For_Object_Key
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return unquote_string(tok, p.spec, key_allocator)
|
return unquote_string(tok, p.spec, key_allocator, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, err: Error) {
|
parse_object_body :: proc(p: ^Parser, end_token: Token_Kind, loc := #caller_location) -> (obj: Object, err: Error) {
|
||||||
obj.allocator = p.allocator
|
obj = make(Object, allocator=p.allocator, loc=loc)
|
||||||
|
|
||||||
defer if err != nil {
|
defer if err != nil {
|
||||||
for key, elem in obj {
|
for key, elem in obj {
|
||||||
delete(key, p.allocator)
|
delete(key, p.allocator, loc)
|
||||||
destroy_value(elem)
|
destroy_value(elem, loc=loc)
|
||||||
}
|
}
|
||||||
delete(obj)
|
delete(obj, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
for p.curr_token.kind != end_token {
|
for p.curr_token.kind != end_token {
|
||||||
key := parse_object_key(p, p.allocator) or_return
|
key := parse_object_key(p, p.allocator, loc) or_return
|
||||||
parse_colon(p) or_return
|
parse_colon(p) or_return
|
||||||
elem := parse_value(p) or_return
|
elem := parse_value(p, loc) or_return
|
||||||
|
|
||||||
if key in obj {
|
if key in obj {
|
||||||
err = .Duplicate_Object_Key
|
err = .Duplicate_Object_Key
|
||||||
delete(key, p.allocator)
|
delete(key, p.allocator, loc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +268,7 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er
|
|||||||
// inserting empty key/values into the object and for those we do not
|
// inserting empty key/values into the object and for those we do not
|
||||||
// want to allocate anything
|
// want to allocate anything
|
||||||
if key != "" {
|
if key != "" {
|
||||||
reserve_error := reserve(&obj, len(obj) + 1)
|
reserve_error := reserve(&obj, len(obj) + 1, loc)
|
||||||
if reserve_error == mem.Allocator_Error.Out_Of_Memory {
|
if reserve_error == mem.Allocator_Error.Out_Of_Memory {
|
||||||
return nil, .Out_Of_Memory
|
return nil, .Out_Of_Memory
|
||||||
}
|
}
|
||||||
@@ -281,9 +282,9 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er
|
|||||||
return obj, .None
|
return obj, .None
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
parse_object :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
|
||||||
expect_token(p, .Open_Brace) or_return
|
expect_token(p, .Open_Brace) or_return
|
||||||
obj := parse_object_body(p, .Close_Brace) or_return
|
obj := parse_object_body(p, .Close_Brace, loc) or_return
|
||||||
expect_token(p, .Close_Brace) or_return
|
expect_token(p, .Close_Brace) or_return
|
||||||
return obj, .None
|
return obj, .None
|
||||||
}
|
}
|
||||||
@@ -480,4 +481,4 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a
|
|||||||
}
|
}
|
||||||
|
|
||||||
return string(b[:w]), nil
|
return string(b[:w]), nil
|
||||||
}
|
}
|
||||||
@@ -89,22 +89,22 @@ Error :: enum {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
destroy_value :: proc(value: Value, allocator := context.allocator) {
|
destroy_value :: proc(value: Value, allocator := context.allocator, loc := #caller_location) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
#partial switch v in value {
|
#partial switch v in value {
|
||||||
case Object:
|
case Object:
|
||||||
for key, elem in v {
|
for key, elem in v {
|
||||||
delete(key)
|
delete(key, loc=loc)
|
||||||
destroy_value(elem)
|
destroy_value(elem, loc=loc)
|
||||||
}
|
}
|
||||||
delete(v)
|
delete(v, loc=loc)
|
||||||
case Array:
|
case Array:
|
||||||
for elem in v {
|
for elem in v {
|
||||||
destroy_value(elem)
|
destroy_value(elem, loc=loc)
|
||||||
}
|
}
|
||||||
delete(v)
|
delete(v, loc=loc)
|
||||||
case String:
|
case String:
|
||||||
delete(v)
|
delete(v, loc=loc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2024, Feoramund
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
// A RFC 4122 Universally Unique Identifier
|
||||||
|
Identifier :: distinct [16]u8
|
||||||
|
|
||||||
|
EXPECTED_LENGTH :: 8 + 4 + 4 + 4 + 12 + 4
|
||||||
|
|
||||||
|
VERSION_BYTE_INDEX :: 6
|
||||||
|
VARIANT_BYTE_INDEX :: 8
|
||||||
|
|
||||||
|
// The number of 100-nanosecond intervals between 1582-10-15 and 1970-01-01.
|
||||||
|
HNS_INTERVALS_BETWEEN_GREG_AND_UNIX :: 141427 * 24 * 60 * 60 * 1000 * 1000 * 10
|
||||||
|
|
||||||
|
VERSION_7_TIME_MASK :: 0xffffffff_ffff0000_00000000_00000000
|
||||||
|
VERSION_7_TIME_SHIFT :: 80
|
||||||
|
VERSION_7_COUNTER_MASK :: 0x00000000_00000fff_00000000_00000000
|
||||||
|
VERSION_7_COUNTER_SHIFT :: 64
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
NO_CSPRNG_ERROR :: "The context random generator is not cryptographic. See the documentation for an example of how to set one up."
|
||||||
|
@(private)
|
||||||
|
BIG_CLOCK_ERROR :: "The clock sequence can only hold 14 bits of data, therefore no number greater than 16,383 (0x3FFF)."
|
||||||
|
@(private)
|
||||||
|
VERSION_7_BIG_COUNTER_ERROR :: "This implementation of the version 7 UUID counter can only hold 12 bits of data, therefore no number greater than 4,095 (0xFFF)."
|
||||||
|
|
||||||
|
Read_Error :: enum {
|
||||||
|
None,
|
||||||
|
Invalid_Length,
|
||||||
|
Invalid_Hexadecimal,
|
||||||
|
Invalid_Separator,
|
||||||
|
}
|
||||||
|
|
||||||
|
Variant_Type :: enum {
|
||||||
|
Unknown,
|
||||||
|
Reserved_Apollo_NCS, // 0b0xx
|
||||||
|
RFC_4122, // 0b10x
|
||||||
|
Reserved_Microsoft_COM, // 0b110
|
||||||
|
Reserved_Future, // 0b111
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name string is a fully-qualified domain name.
|
||||||
|
@(rodata)
|
||||||
|
Namespace_DNS := Identifier {
|
||||||
|
0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1,
|
||||||
|
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name string is a URL.
|
||||||
|
@(rodata)
|
||||||
|
Namespace_URL := Identifier {
|
||||||
|
0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1,
|
||||||
|
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name string is an ISO OID.
|
||||||
|
@(rodata)
|
||||||
|
Namespace_OID := Identifier {
|
||||||
|
0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1,
|
||||||
|
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name string is an X.500 DN (in DER or a text output format).
|
||||||
|
@(rodata)
|
||||||
|
Namespace_X500 := Identifier {
|
||||||
|
0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1,
|
||||||
|
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
package uuid implements Universally Unique Identifiers according to the
|
||||||
|
standard originally outlined in RFC 4122 with additions from RFC 9562.
|
||||||
|
|
||||||
|
The UUIDs are textually represented and read in the following string format:
|
||||||
|
`00000000-0000-v000-V000-000000000000`
|
||||||
|
|
||||||
|
`v` is where the version bits reside, and `V` is where the variant bits reside.
|
||||||
|
The meaning of the other bits is version-dependent.
|
||||||
|
|
||||||
|
Outside of string representations, UUIDs are represented in memory by a 128-bit
|
||||||
|
structure organized as an array of 16 bytes.
|
||||||
|
|
||||||
|
|
||||||
|
Of the UUID versions which may make use of random number generation, a
|
||||||
|
requirement is placed upon them that the underlying generator be
|
||||||
|
cryptographically-secure, per RFC 9562's suggestion.
|
||||||
|
|
||||||
|
- Version 1 without a node argument.
|
||||||
|
- Version 4 in all cases.
|
||||||
|
- Version 6 without either a clock or node argument.
|
||||||
|
- Version 7 in all cases.
|
||||||
|
|
||||||
|
Here's an example of how to set up one:
|
||||||
|
|
||||||
|
import "core:crypto"
|
||||||
|
import "core:encoding/uuid"
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
my_uuid: uuid.Identifier
|
||||||
|
|
||||||
|
{
|
||||||
|
// This scope will have a CSPRNG.
|
||||||
|
context.random_generator = crypto.random_generator()
|
||||||
|
my_uuid = uuid.generate_v7()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back to the default random number generator.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
For more information on the specifications, see here:
|
||||||
|
- https://www.rfc-editor.org/rfc/rfc4122.html
|
||||||
|
- https://www.rfc-editor.org/rfc/rfc9562.html
|
||||||
|
*/
|
||||||
|
package uuid
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:crypto/hash"
|
||||||
|
import "core:math/rand"
|
||||||
|
import "core:time"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generate a version 1 UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- clock_seq: The clock sequence, a number which must be initialized to a random number once in the lifetime of a system.
|
||||||
|
- node: An optional 48-bit spatially unique identifier, specified to be the IEEE 802 address of the system.
|
||||||
|
If one is not provided or available, 48 bits of random state will take its place.
|
||||||
|
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
*/
|
||||||
|
generate_v1 :: proc(clock_seq: u16, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||||
|
assert(clock_seq <= 0x3FFF, BIG_CLOCK_ERROR)
|
||||||
|
unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100
|
||||||
|
|
||||||
|
uuid_timestamp := cast(u64le)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
|
||||||
|
uuid_timestamp_octets := transmute([8]u8)uuid_timestamp
|
||||||
|
|
||||||
|
result[0] = uuid_timestamp_octets[0]
|
||||||
|
result[1] = uuid_timestamp_octets[1]
|
||||||
|
result[2] = uuid_timestamp_octets[2]
|
||||||
|
result[3] = uuid_timestamp_octets[3]
|
||||||
|
result[4] = uuid_timestamp_octets[4]
|
||||||
|
result[5] = uuid_timestamp_octets[5]
|
||||||
|
|
||||||
|
result[6] = uuid_timestamp_octets[6] >> 4
|
||||||
|
result[7] = uuid_timestamp_octets[6] << 4 | uuid_timestamp_octets[7]
|
||||||
|
|
||||||
|
if realized_node, ok := node.?; ok {
|
||||||
|
mutable_node := realized_node
|
||||||
|
runtime.mem_copy_non_overlapping(&result[10], &mutable_node[0], 6)
|
||||||
|
} else {
|
||||||
|
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||||
|
bytes_generated := rand.read(result[10:])
|
||||||
|
assert(bytes_generated == 6, "RNG failed to generate 6 bytes for UUID v1.")
|
||||||
|
}
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x10
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
result[8] |= cast(u8)(clock_seq & 0x3F00 >> 8)
|
||||||
|
result[9] = cast(u8)clock_seq
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generate a version 4 UUID.
|
||||||
|
|
||||||
|
This UUID will be pseudorandom, save for 6 pre-determined version and variant bits.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
*/
|
||||||
|
generate_v4 :: proc() -> (result: Identifier) {
|
||||||
|
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||||
|
bytes_generated := rand.read(result[:])
|
||||||
|
assert(bytes_generated == 16, "RNG failed to generate 16 bytes for UUID v4.")
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x40
|
||||||
|
|
||||||
|
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generate a version 6 UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- clock_seq: The clock sequence from version 1, now made optional.
|
||||||
|
If unspecified, it will be replaced with random bits.
|
||||||
|
- node: An optional 48-bit spatially unique identifier, specified to be the IEEE 802 address of the system.
|
||||||
|
If one is not provided or available, 48 bits of random state will take its place.
|
||||||
|
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
*/
|
||||||
|
generate_v6 :: proc(clock_seq: Maybe(u16) = nil, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||||
|
unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100
|
||||||
|
|
||||||
|
uuid_timestamp := cast(u128be)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
|
||||||
|
|
||||||
|
result = transmute(Identifier)(
|
||||||
|
uuid_timestamp & 0x0FFFFFFF_FFFFF000 << 68 |
|
||||||
|
uuid_timestamp & 0x00000000_00000FFF << 64
|
||||||
|
)
|
||||||
|
|
||||||
|
if realized_clock_seq, ok := clock_seq.?; ok {
|
||||||
|
assert(realized_clock_seq <= 0x3FFF, BIG_CLOCK_ERROR)
|
||||||
|
result[8] |= cast(u8)(realized_clock_seq & 0x3F00 >> 8)
|
||||||
|
result[9] = cast(u8)realized_clock_seq
|
||||||
|
} else {
|
||||||
|
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||||
|
temporary: [2]u8
|
||||||
|
bytes_generated := rand.read(temporary[:])
|
||||||
|
assert(bytes_generated == 2, "RNG failed to generate 2 bytes for UUID v1.")
|
||||||
|
result[8] |= temporary[0] & 0x3F
|
||||||
|
result[9] = temporary[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if realized_node, ok := node.?; ok {
|
||||||
|
mutable_node := realized_node
|
||||||
|
runtime.mem_copy_non_overlapping(&result[10], &mutable_node[0], 6)
|
||||||
|
} else {
|
||||||
|
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||||
|
bytes_generated := rand.read(result[10:])
|
||||||
|
assert(bytes_generated == 6, "RNG failed to generate 6 bytes for UUID v1.")
|
||||||
|
}
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x60
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generate a version 7 UUID.
|
||||||
|
|
||||||
|
This UUID will be pseudorandom, save for 6 pre-determined version and variant
|
||||||
|
bits and a 48-bit timestamp.
|
||||||
|
|
||||||
|
It is designed with time-based sorting in mind, such as for database usage, as
|
||||||
|
the highest bits are allocated from the timestamp of when it is created.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
*/
|
||||||
|
generate_v7_basic :: proc(timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||||
|
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||||
|
unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6
|
||||||
|
|
||||||
|
result = transmute(Identifier)(cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT)
|
||||||
|
|
||||||
|
bytes_generated := rand.read(result[6:])
|
||||||
|
assert(bytes_generated == 10, "RNG failed to generate 10 bytes for UUID v7.")
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x70
|
||||||
|
|
||||||
|
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generate a version 7 UUID that has an incremented counter.
|
||||||
|
|
||||||
|
This UUID will be pseudorandom, save for 6 pre-determined version and variant
|
||||||
|
bits, a 48-bit timestamp, and 12 bits of counter state.
|
||||||
|
|
||||||
|
It is designed with time-based sorting in mind, such as for database usage, as
|
||||||
|
the highest bits are allocated from the timestamp of when it is created.
|
||||||
|
|
||||||
|
This procedure is preferable if you are generating hundreds or thousands of
|
||||||
|
UUIDs as a batch within the span of a millisecond. Do note that the counter
|
||||||
|
only has 12 bits of state, thus `counter` cannot exceed the number 4,095.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:uuid"
|
||||||
|
|
||||||
|
// Create a batch of UUIDs all at once.
|
||||||
|
batch: [dynamic]uuid.Identifier
|
||||||
|
|
||||||
|
for i: u16 = 0; i < 1000; i += 1 {
|
||||||
|
my_uuid := uuid.generate_v7_counter(i)
|
||||||
|
append(&batch, my_uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- counter: A 12-bit value which should be incremented each time a UUID is generated in a batch.
|
||||||
|
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
*/
|
||||||
|
generate_v7_with_counter :: proc(counter: u16, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||||
|
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||||
|
assert(counter <= 0x0fff, VERSION_7_BIG_COUNTER_ERROR)
|
||||||
|
unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6
|
||||||
|
|
||||||
|
result = transmute(Identifier)(
|
||||||
|
cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT |
|
||||||
|
cast(u128be)counter << VERSION_7_COUNTER_SHIFT
|
||||||
|
)
|
||||||
|
|
||||||
|
bytes_generated := rand.read(result[8:])
|
||||||
|
assert(bytes_generated == 8, "RNG failed to generate 8 bytes for UUID v7.")
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x70
|
||||||
|
|
||||||
|
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_v7 :: proc {
|
||||||
|
generate_v7_basic,
|
||||||
|
generate_v7_with_counter,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generate a version 8 UUID using a specific hashing algorithm.
|
||||||
|
|
||||||
|
This UUID is generated by hashing a name with a namespace.
|
||||||
|
|
||||||
|
Note that all version 8 UUIDs are for experimental or vendor-specific use
|
||||||
|
cases, per the specification. This use case in particular is for offering a
|
||||||
|
non-legacy alternative to UUID versions 3 and 5.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||||
|
This can be any one of the `Namespace_*` values provided in this package.
|
||||||
|
- name: The byte slice which will be hashed with the namespace.
|
||||||
|
- algorithm: A hashing algorithm from `core:crypto/hash`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
import "core:crypto/hash"
|
||||||
|
import "core:encoding/uuid"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
|
||||||
|
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
|
||||||
|
fmt.println(my_uuid_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
3730f688-4bff-8dce-9cbf-74a3960c5703
|
||||||
|
|
||||||
|
*/
|
||||||
|
generate_v8_hash_bytes :: proc(
|
||||||
|
namespace: Identifier,
|
||||||
|
name: []byte,
|
||||||
|
algorithm: hash.Algorithm,
|
||||||
|
) -> (
|
||||||
|
result: Identifier,
|
||||||
|
) {
|
||||||
|
// 128 bytes should be enough for the foreseeable future.
|
||||||
|
digest: [128]byte
|
||||||
|
|
||||||
|
assert(hash.DIGEST_SIZES[algorithm] >= 16, "Per RFC 9562, the hashing algorithm used must generate a digest of 128 bits or larger.")
|
||||||
|
assert(hash.DIGEST_SIZES[algorithm] < len(digest), "Digest size is too small for this algorithm. The buffer must be increased.")
|
||||||
|
|
||||||
|
hash_context: hash.Context
|
||||||
|
hash.init(&hash_context, algorithm)
|
||||||
|
|
||||||
|
mutable_namespace := namespace
|
||||||
|
hash.update(&hash_context, mutable_namespace[:])
|
||||||
|
hash.update(&hash_context, name[:])
|
||||||
|
hash.final(&hash_context, digest[:])
|
||||||
|
|
||||||
|
runtime.mem_copy_non_overlapping(&result, &digest, 16)
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generate a version 8 UUID using a specific hashing algorithm.
|
||||||
|
|
||||||
|
This UUID is generated by hashing a name with a namespace.
|
||||||
|
|
||||||
|
Note that all version 8 UUIDs are for experimental or vendor-specific use
|
||||||
|
cases, per the specification. This use case in particular is for offering a
|
||||||
|
non-legacy alternative to UUID versions 3 and 5.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||||
|
This can be any one of the `Namespace_*` values provided in this package.
|
||||||
|
- name: The string which will be hashed with the namespace.
|
||||||
|
- algorithm: A hashing algorithm from `core:crypto/hash`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
import "core:crypto/hash"
|
||||||
|
import "core:encoding/uuid"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
|
||||||
|
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
|
||||||
|
fmt.println(my_uuid_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
3730f688-4bff-8dce-9cbf-74a3960c5703
|
||||||
|
|
||||||
|
*/
|
||||||
|
generate_v8_hash_string :: proc(
|
||||||
|
namespace: Identifier,
|
||||||
|
name: string,
|
||||||
|
algorithm: hash.Algorithm,
|
||||||
|
) -> (
|
||||||
|
result: Identifier,
|
||||||
|
) {
|
||||||
|
return generate_v8_hash_bytes(namespace, transmute([]byte)name, algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_v8_hash :: proc {
|
||||||
|
generate_v8_hash_bytes,
|
||||||
|
generate_v8_hash_string,
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
package uuid/legacy implements versions 3 and 5 of UUID generation, both of
|
||||||
|
which are using hashing algorithms (MD5 and SHA1, respectively) that are known
|
||||||
|
these days to no longer be secure.
|
||||||
|
*/
|
||||||
|
package uuid_legacy
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:crypto/legacy/md5"
|
||||||
|
import "core:crypto/legacy/sha1"
|
||||||
|
import "core:encoding/uuid"
|
||||||
|
|
||||||
|
Identifier :: uuid.Identifier
|
||||||
|
VERSION_BYTE_INDEX :: uuid.VERSION_BYTE_INDEX
|
||||||
|
VARIANT_BYTE_INDEX :: uuid.VARIANT_BYTE_INDEX
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generate a version 3 UUID.
|
||||||
|
|
||||||
|
This UUID is generated with a MD5 hash of a name and a namespace.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||||
|
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||||
|
- name: The byte slice which will be hashed with the namespace.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
*/
|
||||||
|
generate_v3_bytes :: proc(
|
||||||
|
namespace: Identifier,
|
||||||
|
name: []byte,
|
||||||
|
) -> (
|
||||||
|
result: Identifier,
|
||||||
|
) {
|
||||||
|
namespace := namespace
|
||||||
|
|
||||||
|
ctx: md5.Context
|
||||||
|
md5.init(&ctx)
|
||||||
|
md5.update(&ctx, namespace[:])
|
||||||
|
md5.update(&ctx, name)
|
||||||
|
md5.final(&ctx, result[:])
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x30
|
||||||
|
|
||||||
|
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generate a version 3 UUID.
|
||||||
|
|
||||||
|
This UUID is generated with a MD5 hash of a name and a namespace.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||||
|
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||||
|
- name: The string which will be hashed with the namespace.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
*/
|
||||||
|
generate_v3_string :: proc(
|
||||||
|
namespace: Identifier,
|
||||||
|
name: string,
|
||||||
|
) -> (
|
||||||
|
result: Identifier,
|
||||||
|
) {
|
||||||
|
return generate_v3_bytes(namespace, transmute([]byte)name)
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_v3 :: proc {
|
||||||
|
generate_v3_bytes,
|
||||||
|
generate_v3_string,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generate a version 5 UUID.
|
||||||
|
|
||||||
|
This UUID is generated with a SHA1 hash of a name and a namespace.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||||
|
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||||
|
- name: The byte slice which will be hashed with the namespace.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
*/
|
||||||
|
generate_v5_bytes :: proc(
|
||||||
|
namespace: Identifier,
|
||||||
|
name: []byte,
|
||||||
|
) -> (
|
||||||
|
result: Identifier,
|
||||||
|
) {
|
||||||
|
namespace := namespace
|
||||||
|
digest: [sha1.DIGEST_SIZE]byte
|
||||||
|
|
||||||
|
ctx: sha1.Context
|
||||||
|
sha1.init(&ctx)
|
||||||
|
sha1.update(&ctx, namespace[:])
|
||||||
|
sha1.update(&ctx, name)
|
||||||
|
sha1.final(&ctx, digest[:])
|
||||||
|
|
||||||
|
runtime.mem_copy_non_overlapping(&result, &digest, 16)
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x50
|
||||||
|
|
||||||
|
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generate a version 5 UUID.
|
||||||
|
|
||||||
|
This UUID is generated with a SHA1 hash of a name and a namespace.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||||
|
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||||
|
- name: The string which will be hashed with the namespace.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
*/
|
||||||
|
generate_v5_string :: proc(
|
||||||
|
namespace: Identifier,
|
||||||
|
name: string,
|
||||||
|
) -> (
|
||||||
|
result: Identifier,
|
||||||
|
) {
|
||||||
|
return generate_v5_bytes(namespace, transmute([]byte)name)
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_v5 :: proc {
|
||||||
|
generate_v5_bytes,
|
||||||
|
generate_v5_string,
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:time"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Convert a string to a UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- str: A string in the 8-4-4-4-12 format.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- id: The converted identifier, or `nil` if there is an error.
|
||||||
|
- error: A description of the error, or `nil` if successful.
|
||||||
|
*/
|
||||||
|
read :: proc "contextless" (str: string) -> (id: Identifier, error: Read_Error) #no_bounds_check {
|
||||||
|
// Only exact-length strings are acceptable.
|
||||||
|
if len(str) != EXPECTED_LENGTH {
|
||||||
|
return {}, .Invalid_Length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ahead to see if the separators are in the right places.
|
||||||
|
if str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-' {
|
||||||
|
return {}, .Invalid_Separator
|
||||||
|
}
|
||||||
|
|
||||||
|
read_nibble :: proc "contextless" (nibble: u8) -> u8 {
|
||||||
|
switch nibble {
|
||||||
|
case '0' ..= '9':
|
||||||
|
return nibble - '0'
|
||||||
|
case 'A' ..= 'F':
|
||||||
|
return nibble - 'A' + 10
|
||||||
|
case 'a' ..= 'f':
|
||||||
|
return nibble - 'a' + 10
|
||||||
|
case:
|
||||||
|
// Return an error value.
|
||||||
|
return 0xFF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index := 0
|
||||||
|
octet_index := 0
|
||||||
|
|
||||||
|
CHUNKS :: [5]int{8, 4, 4, 4, 12}
|
||||||
|
|
||||||
|
for chunk in CHUNKS {
|
||||||
|
for i := index; i < index + chunk; i += 2 {
|
||||||
|
high := read_nibble(str[i])
|
||||||
|
low := read_nibble(str[i + 1])
|
||||||
|
|
||||||
|
if high | low > 0xF {
|
||||||
|
return {}, .Invalid_Hexadecimal
|
||||||
|
}
|
||||||
|
|
||||||
|
id[octet_index] = low | high << 4
|
||||||
|
octet_index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
index += chunk + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the version of a UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- number: The version number.
|
||||||
|
*/
|
||||||
|
version :: proc "contextless" (id: Identifier) -> (number: int) #no_bounds_check {
|
||||||
|
return cast(int)(id[VERSION_BYTE_INDEX] & 0xF0 >> 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the variant of a UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- variant: The variant type.
|
||||||
|
*/
|
||||||
|
variant :: proc "contextless" (id: Identifier) -> (variant: Variant_Type) #no_bounds_check {
|
||||||
|
switch {
|
||||||
|
case id[VARIANT_BYTE_INDEX] & 0x80 == 0:
|
||||||
|
return .Reserved_Apollo_NCS
|
||||||
|
case id[VARIANT_BYTE_INDEX] & 0xC0 == 0x80:
|
||||||
|
return .RFC_4122
|
||||||
|
case id[VARIANT_BYTE_INDEX] & 0xE0 == 0xC0:
|
||||||
|
return .Reserved_Microsoft_COM
|
||||||
|
case id[VARIANT_BYTE_INDEX] & 0xF0 == 0xE0:
|
||||||
|
return .Reserved_Future
|
||||||
|
case:
|
||||||
|
return .Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the clock sequence of a version 1 or version 6 UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- clock_seq: The 14-bit clock sequence field.
|
||||||
|
*/
|
||||||
|
clock_seq :: proc "contextless" (id: Identifier) -> (clock_seq: u16) {
|
||||||
|
return cast(u16)id[9] | cast(u16)id[8] & 0x3F << 8
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the node of a version 1 or version 6 UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- node: The 48-bit spatially unique identifier.
|
||||||
|
*/
|
||||||
|
node :: proc "contextless" (id: Identifier) -> (node: [6]u8) {
|
||||||
|
mutable_id := id
|
||||||
|
runtime.mem_copy_non_overlapping(&node, &mutable_id[10], 6)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the raw timestamp of a version 1 UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
|
||||||
|
*/
|
||||||
|
raw_time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
|
||||||
|
timestamp_octets: [8]u8
|
||||||
|
|
||||||
|
timestamp_octets[0] = id[0]
|
||||||
|
timestamp_octets[1] = id[1]
|
||||||
|
timestamp_octets[2] = id[2]
|
||||||
|
timestamp_octets[3] = id[3]
|
||||||
|
timestamp_octets[4] = id[4]
|
||||||
|
timestamp_octets[5] = id[5]
|
||||||
|
|
||||||
|
timestamp_octets[6] = id[6] << 4 | id[7] >> 4
|
||||||
|
timestamp_octets[7] = id[7] & 0xF
|
||||||
|
|
||||||
|
return cast(u64)transmute(u64le)timestamp_octets
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the timestamp of a version 1 UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- timestamp: The timestamp of the UUID.
|
||||||
|
*/
|
||||||
|
time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||||
|
return time.from_nanoseconds(cast(i64)(raw_time_v1(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the raw timestamp of a version 6 UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
|
||||||
|
*/
|
||||||
|
raw_time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
|
||||||
|
temporary := transmute(u128be)id
|
||||||
|
|
||||||
|
timestamp |= cast(u64)(temporary & 0xFFFFFFFF_FFFF0000_00000000_00000000 >> 68)
|
||||||
|
timestamp |= cast(u64)(temporary & 0x00000000_00000FFF_00000000_00000000 >> 64)
|
||||||
|
|
||||||
|
return timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the timestamp of a version 6 UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
|
||||||
|
*/
|
||||||
|
time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||||
|
return time.from_nanoseconds(cast(i64)(raw_time_v6(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the raw timestamp of a version 7 UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- timestamp: The timestamp, in milliseconds since the UNIX epoch.
|
||||||
|
*/
|
||||||
|
raw_time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
|
||||||
|
time_bits := transmute(u128be)id & VERSION_7_TIME_MASK
|
||||||
|
return cast(u64)(time_bits >> VERSION_7_TIME_SHIFT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the timestamp of a version 7 UUID.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- timestamp: The timestamp, in milliseconds since the UNIX epoch.
|
||||||
|
*/
|
||||||
|
time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||||
|
return time.from_nanoseconds(cast(i64)raw_time_v7(id) * 1e6)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the 12-bit counter value of a version 7 UUID.
|
||||||
|
|
||||||
|
The UUID must have been generated with a counter, otherwise this procedure will
|
||||||
|
return random bits.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- counter: The 12-bit counter value.
|
||||||
|
*/
|
||||||
|
counter_v7 :: proc "contextless" (id: Identifier) -> (counter: u16) {
|
||||||
|
counter_bits := transmute(u128be)id & VERSION_7_COUNTER_MASK
|
||||||
|
return cast(u16)(counter_bits >> VERSION_7_COUNTER_SHIFT)
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Stamp a 128-bit integer as being a valid version 8 UUID.
|
||||||
|
|
||||||
|
Per the specification, all version 8 UUIDs are either for experimental or
|
||||||
|
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||||
|
into custom UUIDs.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- integer: Any integer type.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: A valid version 8 UUID.
|
||||||
|
*/
|
||||||
|
stamp_v8_int :: proc(#any_int integer: u128) -> (result: Identifier) {
|
||||||
|
result = transmute(Identifier)cast(u128be)integer
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Stamp an array of 16 bytes as being a valid version 8 UUID.
|
||||||
|
|
||||||
|
Per the specification, all version 8 UUIDs are either for experimental or
|
||||||
|
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||||
|
into custom UUIDs.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- array: An array of 16 bytes.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: A valid version 8 UUID.
|
||||||
|
*/
|
||||||
|
stamp_v8_array :: proc(array: [16]u8) -> (result: Identifier) {
|
||||||
|
result = Identifier(array)
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Stamp a slice of bytes as being a valid version 8 UUID.
|
||||||
|
|
||||||
|
If the slice is less than 16 bytes long, the data available will be used.
|
||||||
|
If it is longer than 16 bytes, only the first 16 will be used.
|
||||||
|
|
||||||
|
This procedure does not modify the underlying slice.
|
||||||
|
|
||||||
|
Per the specification, all version 8 UUIDs are either for experimental or
|
||||||
|
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||||
|
into custom UUIDs.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- slice: A slice of bytes.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: A valid version 8 UUID.
|
||||||
|
*/
|
||||||
|
stamp_v8_slice :: proc(slice: []u8) -> (result: Identifier) {
|
||||||
|
runtime.mem_copy_non_overlapping(&result, &slice[0], min(16, len(slice)))
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stamp_v8 :: proc {
|
||||||
|
stamp_v8_int,
|
||||||
|
stamp_v8_array,
|
||||||
|
stamp_v8_slice,
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:io"
|
||||||
|
import "core:strconv"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Write a UUID in the 8-4-4-4-12 format.
|
||||||
|
|
||||||
|
This procedure performs error checking with every byte written.
|
||||||
|
|
||||||
|
If you can guarantee beforehand that your stream has enough space to hold the
|
||||||
|
UUID (32 bytes), then it is better to use `unsafe_write` instead as that will
|
||||||
|
be faster.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- w: A writable stream.
|
||||||
|
- id: The identifier to convert.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- error: An `io` error, if one occurred, otherwise `nil`.
|
||||||
|
*/
|
||||||
|
write :: proc(w: io.Writer, id: Identifier) -> (error: io.Error) #no_bounds_check {
|
||||||
|
write_octet :: proc (w: io.Writer, octet: u8) -> io.Error #no_bounds_check {
|
||||||
|
high_nibble := octet >> 4
|
||||||
|
low_nibble := octet & 0xF
|
||||||
|
|
||||||
|
io.write_byte(w, strconv.digits[high_nibble]) or_return
|
||||||
|
io.write_byte(w, strconv.digits[low_nibble]) or_return
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for index in 0 ..< 4 { write_octet(w, id[index]) or_return }
|
||||||
|
io.write_byte(w, '-') or_return
|
||||||
|
for index in 4 ..< 6 { write_octet(w, id[index]) or_return }
|
||||||
|
io.write_byte(w, '-') or_return
|
||||||
|
for index in 6 ..< 8 { write_octet(w, id[index]) or_return }
|
||||||
|
io.write_byte(w, '-') or_return
|
||||||
|
for index in 8 ..< 10 { write_octet(w, id[index]) or_return }
|
||||||
|
io.write_byte(w, '-') or_return
|
||||||
|
for index in 10 ..< 16 { write_octet(w, id[index]) or_return }
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Write a UUID in the 8-4-4-4-12 format.
|
||||||
|
|
||||||
|
This procedure performs no error checking on the underlying stream.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- w: A writable stream.
|
||||||
|
- id: The identifier to convert.
|
||||||
|
*/
|
||||||
|
unsafe_write :: proc(w: io.Writer, id: Identifier) #no_bounds_check {
|
||||||
|
write_octet :: proc (w: io.Writer, octet: u8) #no_bounds_check {
|
||||||
|
high_nibble := octet >> 4
|
||||||
|
low_nibble := octet & 0xF
|
||||||
|
|
||||||
|
io.write_byte(w, strconv.digits[high_nibble])
|
||||||
|
io.write_byte(w, strconv.digits[low_nibble])
|
||||||
|
}
|
||||||
|
|
||||||
|
for index in 0 ..< 4 { write_octet(w, id[index]) }
|
||||||
|
io.write_byte(w, '-')
|
||||||
|
for index in 4 ..< 6 { write_octet(w, id[index]) }
|
||||||
|
io.write_byte(w, '-')
|
||||||
|
for index in 6 ..< 8 { write_octet(w, id[index]) }
|
||||||
|
io.write_byte(w, '-')
|
||||||
|
for index in 8 ..< 10 { write_octet(w, id[index]) }
|
||||||
|
io.write_byte(w, '-')
|
||||||
|
for index in 10 ..< 16 { write_octet(w, id[index]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Convert a UUID to a string in the 8-4-4-4-12 format.
|
||||||
|
|
||||||
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier to convert.
|
||||||
|
- allocator: (default: context.allocator)
|
||||||
|
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- str: The allocated and converted string.
|
||||||
|
- error: An optional allocator error if one occured, `nil` otherwise.
|
||||||
|
*/
|
||||||
|
to_string_allocated :: proc(
|
||||||
|
id: Identifier,
|
||||||
|
allocator := context.allocator,
|
||||||
|
loc := #caller_location,
|
||||||
|
) -> (
|
||||||
|
str: string,
|
||||||
|
error: runtime.Allocator_Error,
|
||||||
|
) #optional_allocator_error {
|
||||||
|
buf := make([]byte, EXPECTED_LENGTH, allocator, loc) or_return
|
||||||
|
builder := strings.builder_from_bytes(buf[:])
|
||||||
|
unsafe_write(strings.to_writer(&builder), id)
|
||||||
|
return strings.to_string(builder), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Convert a UUID to a string in the 8-4-4-4-12 format.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- id: The identifier to convert.
|
||||||
|
- buffer: A byte buffer to store the result. Must be at least 32 bytes large.
|
||||||
|
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- str: The converted string which will be stored in `buffer`.
|
||||||
|
*/
|
||||||
|
to_string_buffer :: proc(
|
||||||
|
id: Identifier,
|
||||||
|
buffer: []byte,
|
||||||
|
loc := #caller_location,
|
||||||
|
) -> (
|
||||||
|
str: string,
|
||||||
|
) {
|
||||||
|
assert(len(buffer) >= EXPECTED_LENGTH, "The buffer provided is not at least 32 bytes large.", loc)
|
||||||
|
builder := strings.builder_from_bytes(buffer)
|
||||||
|
unsafe_write(strings.to_writer(&builder), id)
|
||||||
|
return strings.to_string(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
to_string :: proc {
|
||||||
|
to_string_allocated,
|
||||||
|
to_string_buffer,
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error
|
|||||||
written += fmt.wprintf(writer, "[DOCTYPE] %v\n", doc.doctype.ident)
|
written += fmt.wprintf(writer, "[DOCTYPE] %v\n", doc.doctype.ident)
|
||||||
|
|
||||||
if len(doc.doctype.rest) > 0 {
|
if len(doc.doctype.rest) > 0 {
|
||||||
fmt.wprintf(writer, "\t%v\n", doc.doctype.rest)
|
fmt.wprintf(writer, "\t%v\n", doc.doctype.rest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +42,10 @@ print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(doc.elements) > 0 {
|
if len(doc.elements) > 0 {
|
||||||
fmt.wprintln(writer, " --- ")
|
fmt.wprintln(writer, " --- ")
|
||||||
print_element(writer, doc, 0)
|
print_element(writer, doc, 0)
|
||||||
fmt.wprintln(writer, " --- ")
|
fmt.wprintln(writer, " --- ")
|
||||||
}
|
}
|
||||||
|
|
||||||
return written, .None
|
return written, .None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,9 +218,7 @@ scan_identifier :: proc(t: ^Tokenizer) -> string {
|
|||||||
for is_valid_identifier_rune(t.ch) {
|
for is_valid_identifier_rune(t.ch) {
|
||||||
advance_rune(t)
|
advance_rune(t)
|
||||||
if t.ch == ':' {
|
if t.ch == ':' {
|
||||||
/*
|
// A namespaced attr can have at most two parts, `namespace:ident`.
|
||||||
A namespaced attr can have at most two parts, `namespace:ident`.
|
|
||||||
*/
|
|
||||||
if namespaced {
|
if namespaced {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -268,14 +266,10 @@ scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) {
|
|||||||
return string(t.src[offset : t.offset - 1]), .None
|
return string(t.src[offset : t.offset - 1]), .None
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Skip CDATA
|
||||||
Skip CDATA
|
|
||||||
*/
|
|
||||||
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
|
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
|
||||||
if t.read_offset + len(CDATA_START) >= len(t.src) {
|
if t.read_offset + len(CDATA_START) >= len(t.src) {
|
||||||
/*
|
// Can't be the start of a CDATA tag.
|
||||||
Can't be the start of a CDATA tag.
|
|
||||||
*/
|
|
||||||
return .None
|
return .None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,9 +284,7 @@ skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
|
|||||||
return .Premature_EOF
|
return .Premature_EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Scan until the end of a CDATA tag.
|
||||||
Scan until the end of a CDATA tag.
|
|
||||||
*/
|
|
||||||
if t.read_offset + len(CDATA_END) < len(t.src) {
|
if t.read_offset + len(CDATA_END) < len(t.src) {
|
||||||
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
||||||
t.read_offset += len(CDATA_END)
|
t.read_offset += len(CDATA_END)
|
||||||
@@ -319,14 +311,10 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close
|
|||||||
case '<':
|
case '<':
|
||||||
if peek_byte(t) == '!' {
|
if peek_byte(t) == '!' {
|
||||||
if peek_byte(t, 1) == '[' {
|
if peek_byte(t, 1) == '[' {
|
||||||
/*
|
// Might be the start of a CDATA tag.
|
||||||
Might be the start of a CDATA tag.
|
|
||||||
*/
|
|
||||||
skip_cdata(t) or_return
|
skip_cdata(t) or_return
|
||||||
} else if peek_byte(t, 1) == '-' && peek_byte(t, 2) == '-' {
|
} else if peek_byte(t, 1) == '-' && peek_byte(t, 2) == '-' {
|
||||||
/*
|
// Comment start. Eat comment.
|
||||||
Comment start. Eat comment.
|
|
||||||
*/
|
|
||||||
t.read_offset += 3
|
t.read_offset += 3
|
||||||
_ = scan_comment(t) or_return
|
_ = scan_comment(t) or_return
|
||||||
}
|
}
|
||||||
@@ -342,17 +330,13 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close
|
|||||||
}
|
}
|
||||||
|
|
||||||
if t.ch == close {
|
if t.ch == close {
|
||||||
/*
|
// If it's not a CDATA or comment, it's the end of this body.
|
||||||
If it's not a CDATA or comment, it's the end of this body.
|
|
||||||
*/
|
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
advance_rune(t)
|
advance_rune(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Strip trailing whitespace.
|
||||||
Strip trailing whitespace.
|
|
||||||
*/
|
|
||||||
lit := string(t.src[offset : t.offset])
|
lit := string(t.src[offset : t.offset])
|
||||||
|
|
||||||
end := len(lit)
|
end := len(lit)
|
||||||
@@ -369,11 +353,6 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close
|
|||||||
if consume_close {
|
if consume_close {
|
||||||
advance_rune(t)
|
advance_rune(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
TODO: Handle decoding escape characters and unboxing CDATA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
return lit, err
|
return lit, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +363,7 @@ peek :: proc(t: ^Tokenizer) -> (token: Token) {
|
|||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
scan :: proc(t: ^Tokenizer) -> Token {
|
scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token {
|
||||||
skip_whitespace(t)
|
skip_whitespace(t)
|
||||||
|
|
||||||
offset := t.offset
|
offset := t.offset
|
||||||
@@ -418,7 +397,7 @@ scan :: proc(t: ^Tokenizer) -> Token {
|
|||||||
case '"', '\'':
|
case '"', '\'':
|
||||||
kind = .Invalid
|
kind = .Invalid
|
||||||
|
|
||||||
lit, err = scan_string(t, t.offset, ch, true, false)
|
lit, err = scan_string(t, t.offset, ch, true, multiline_string)
|
||||||
if err == .None {
|
if err == .None {
|
||||||
kind = .String
|
kind = .String
|
||||||
}
|
}
|
||||||
@@ -435,4 +414,4 @@ scan :: proc(t: ^Tokenizer) -> Token {
|
|||||||
lit = string(t.src[offset : t.offset])
|
lit = string(t.src[offset : t.offset])
|
||||||
}
|
}
|
||||||
return Token{kind, lit, pos}
|
return Token{kind, lit, pos}
|
||||||
}
|
}
|
||||||
@@ -203,9 +203,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
|
|||||||
|
|
||||||
doc.elements = make([dynamic]Element, 1024, 1024, allocator)
|
doc.elements = make([dynamic]Element, 1024, 1024, allocator)
|
||||||
|
|
||||||
// strings.intern_init(&doc.intern, allocator, allocator)
|
err = .Unexpected_Token
|
||||||
|
|
||||||
err = .Unexpected_Token
|
|
||||||
element, parent: Element_ID
|
element, parent: Element_ID
|
||||||
open: Token
|
open: Token
|
||||||
|
|
||||||
@@ -259,8 +257,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
|
|||||||
case .Slash:
|
case .Slash:
|
||||||
// Empty tag. Close it.
|
// Empty tag. Close it.
|
||||||
expect(t, .Gt) or_return
|
expect(t, .Gt) or_return
|
||||||
parent = doc.elements[element].parent
|
parent = doc.elements[element].parent
|
||||||
element = parent
|
element = parent
|
||||||
|
|
||||||
case:
|
case:
|
||||||
error(t, t.offset, "Expected close tag, got: %#v\n", end_token)
|
error(t, t.offset, "Expected close tag, got: %#v\n", end_token)
|
||||||
@@ -276,8 +274,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
|
|||||||
error(t, t.offset, "Mismatched Closing Tag. Expected %v, got %v\n", doc.elements[element].ident, ident.text)
|
error(t, t.offset, "Mismatched Closing Tag. Expected %v, got %v\n", doc.elements[element].ident, ident.text)
|
||||||
return doc, .Mismatched_Closing_Tag
|
return doc, .Mismatched_Closing_Tag
|
||||||
}
|
}
|
||||||
parent = doc.elements[element].parent
|
parent = doc.elements[element].parent
|
||||||
element = parent
|
element = parent
|
||||||
|
|
||||||
} else if open.kind == .Exclaim {
|
} else if open.kind == .Exclaim {
|
||||||
// <!
|
// <!
|
||||||
@@ -463,8 +461,8 @@ validate_options :: proc(options: Options) -> (validated: Options, err: Error) {
|
|||||||
return validated, .None
|
return validated, .None
|
||||||
}
|
}
|
||||||
|
|
||||||
expect :: proc(t: ^Tokenizer, kind: Token_Kind) -> (tok: Token, err: Error) {
|
expect :: proc(t: ^Tokenizer, kind: Token_Kind, multiline_string := false) -> (tok: Token, err: Error) {
|
||||||
tok = scan(t)
|
tok = scan(t, multiline_string=multiline_string)
|
||||||
if tok.kind == kind { return tok, .None }
|
if tok.kind == kind { return tok, .None }
|
||||||
|
|
||||||
error(t, t.offset, "Expected \"%v\", got \"%v\".", kind, tok.kind)
|
error(t, t.offset, "Expected \"%v\", got \"%v\".", kind, tok.kind)
|
||||||
@@ -480,7 +478,13 @@ parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: E
|
|||||||
offset = t.offset - len(key.text)
|
offset = t.offset - len(key.text)
|
||||||
|
|
||||||
_ = expect(t, .Eq) or_return
|
_ = expect(t, .Eq) or_return
|
||||||
value := expect(t, .String) or_return
|
value := expect(t, .String, multiline_string=true) or_return
|
||||||
|
|
||||||
|
normalized, normalize_err := entity.decode_xml(value.text, {.Normalize_Whitespace}, doc.allocator)
|
||||||
|
if normalize_err == .None {
|
||||||
|
append(&doc.strings_to_free, normalized)
|
||||||
|
value.text = normalized
|
||||||
|
}
|
||||||
|
|
||||||
attr.key = key.text
|
attr.key = key.text
|
||||||
attr.val = value.text
|
attr.val = value.text
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2024, Feoramund
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import "core:time"
|
||||||
|
|
||||||
|
// Set to true to compile with support for core named types disabled, as a
|
||||||
|
// fallback in the event your platform does not support one of the types, or
|
||||||
|
// you have no need for them and want a smaller binary.
|
||||||
|
NO_CORE_NAMED_TYPES :: #config(ODIN_CORE_FLAGS_NO_CORE_NAMED_TYPES, false)
|
||||||
|
|
||||||
|
// Override support for parsing `time` types.
|
||||||
|
IMPORTING_TIME :: #config(ODIN_CORE_FLAGS_USE_TIME, time.IS_SUPPORTED)
|
||||||
|
|
||||||
|
// Override support for parsing `net` types.
|
||||||
|
// TODO: Update this when the BSDs are supported.
|
||||||
|
IMPORTING_NET :: #config(ODIN_CORE_FLAGS_USE_NET, ODIN_OS == .Windows || ODIN_OS == .Linux || ODIN_OS == .Darwin)
|
||||||
|
|
||||||
|
TAG_ARGS :: "args"
|
||||||
|
SUBTAG_NAME :: "name"
|
||||||
|
SUBTAG_POS :: "pos"
|
||||||
|
SUBTAG_REQUIRED :: "required"
|
||||||
|
SUBTAG_HIDDEN :: "hidden"
|
||||||
|
SUBTAG_VARIADIC :: "variadic"
|
||||||
|
SUBTAG_FILE :: "file"
|
||||||
|
SUBTAG_PERMS :: "perms"
|
||||||
|
SUBTAG_INDISTINCT :: "indistinct"
|
||||||
|
|
||||||
|
TAG_USAGE :: "usage"
|
||||||
|
|
||||||
|
UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>"
|
||||||
|
|
||||||
|
INTERNAL_VARIADIC_FLAG :: "varg"
|
||||||
|
|
||||||
|
RESERVED_HELP_FLAG :: "help"
|
||||||
|
RESERVED_HELP_FLAG_SHORT :: "h"
|
||||||
|
|
||||||
|
// If there are more than this number of flags in total, only the required and
|
||||||
|
// positional flags will be shown in the one-line usage summary.
|
||||||
|
ONE_LINE_FLAG_CUTOFF_COUNT :: 16
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
package flags implements a command-line argument parser.
|
||||||
|
|
||||||
|
It works by using Odin's run-time type information to determine where and how
|
||||||
|
to store data on a struct provided by the program. Type conversion is handled
|
||||||
|
automatically and errors are reported with useful messages.
|
||||||
|
|
||||||
|
|
||||||
|
Command-Line Syntax:
|
||||||
|
|
||||||
|
Arguments are treated differently depending on how they're formatted.
|
||||||
|
The format is similar to the Odin binary's way of handling compiler flags.
|
||||||
|
|
||||||
|
```
|
||||||
|
type handling
|
||||||
|
------------ ------------------------
|
||||||
|
<positional> depends on struct layout
|
||||||
|
-<flag> set a bool true
|
||||||
|
-<flag:option> set flag to option
|
||||||
|
-<flag=option> set flag to option, alternative syntax
|
||||||
|
-<map>:<key>=<value> set map[key] to value
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Struct Tags:
|
||||||
|
|
||||||
|
Users of the `core:encoding/json` package may be familiar with using tags to
|
||||||
|
annotate struct metadata. The same technique is used here to annotate where
|
||||||
|
arguments should go and which are required.
|
||||||
|
|
||||||
|
Under the `args` tag, there are the following subtags:
|
||||||
|
|
||||||
|
- `name=S`: set `S` as the flag's name.
|
||||||
|
- `pos=N`: place positional argument `N` into this flag.
|
||||||
|
- `hidden`: hide this flag from the usage documentation.
|
||||||
|
- `required`: cause verification to fail if this argument is not set.
|
||||||
|
- `variadic`: take all remaining arguments when set, UNIX-style only.
|
||||||
|
- `file`: for `os.Handle` types, file open mode.
|
||||||
|
- `perms`: for `os.Handle` types, file open permissions.
|
||||||
|
- `indistinct`: allow the setting of distinct types by their base type.
|
||||||
|
|
||||||
|
`required` may be given a range specifier in the following formats:
|
||||||
|
```
|
||||||
|
min
|
||||||
|
<max
|
||||||
|
min<max
|
||||||
|
```
|
||||||
|
|
||||||
|
`max` is not inclusive in this range, as noted by the less-than `<` sign, so if
|
||||||
|
you want to require 3 and only 3 arguments in a dynamic array, you would
|
||||||
|
specify `required=3<4`.
|
||||||
|
|
||||||
|
|
||||||
|
`variadic` may be given a number (`variadic=N`) above 1 to limit how many extra
|
||||||
|
arguments it consumes.
|
||||||
|
|
||||||
|
|
||||||
|
`file` determines the file open mode for an `os.Handle`.
|
||||||
|
It accepts a string of flags that can be mixed together:
|
||||||
|
- r: read
|
||||||
|
- w: write
|
||||||
|
- c: create, create the file if it doesn't exist
|
||||||
|
- a: append, add any new writes to the end of the file
|
||||||
|
- t: truncate, erase the file on open
|
||||||
|
|
||||||
|
|
||||||
|
`perms` determines the file open permissions for an `os.Handle`.
|
||||||
|
|
||||||
|
The permissions are represented by three numbers in octal format. The first
|
||||||
|
number is the owner, the second is the group, and the third is other. Read is
|
||||||
|
represented by 4, write by 2, and execute by 1.
|
||||||
|
|
||||||
|
These numbers are added together to get combined permissions. For example, 644
|
||||||
|
represents read/write for the owner, read for the group, and read for other.
|
||||||
|
|
||||||
|
Note that this may only have effect on UNIX-like platforms. By default, `perms`
|
||||||
|
is set to 444 when only reading and 644 when writing.
|
||||||
|
|
||||||
|
|
||||||
|
`indistinct` tells the parser that it's okay to treat distinct types as their
|
||||||
|
underlying base type. Normally, the parser will hand those types off to the
|
||||||
|
custom type setter (more about that later) if one is available, if it doesn't
|
||||||
|
know how to handle the type.
|
||||||
|
|
||||||
|
|
||||||
|
Usage Tag:
|
||||||
|
|
||||||
|
There is also the `usage` tag, which is a plain string to be printed alongside
|
||||||
|
the flag in the usage output. If `usage` contains a newline, it will be
|
||||||
|
properly aligned when printed.
|
||||||
|
|
||||||
|
All surrounding whitespace is trimmed when formatting with multiple lines.
|
||||||
|
|
||||||
|
|
||||||
|
Supported Flag Data Types:
|
||||||
|
|
||||||
|
- all booleans
|
||||||
|
- all integers
|
||||||
|
- all floats
|
||||||
|
- all enums
|
||||||
|
- all complex numbers
|
||||||
|
- all quaternions
|
||||||
|
- all bit_sets
|
||||||
|
- `string` and `cstring`
|
||||||
|
- `rune`
|
||||||
|
- `os.Handle`
|
||||||
|
- `time.Time`
|
||||||
|
- `datetime.DateTime`
|
||||||
|
- `net.Host_Or_Endpoint`,
|
||||||
|
- additional custom types, see Custom Types below
|
||||||
|
- `dynamic` arrays with element types of the above
|
||||||
|
- `map[string]`s or `map[cstring]`s with value types of the above
|
||||||
|
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
|
||||||
|
The parser will ensure `required` arguments are set, if no errors occurred
|
||||||
|
during parsing. This is on by default.
|
||||||
|
|
||||||
|
Additionally, you may call `register_flag_checker` to set your own argument
|
||||||
|
validation procedure that will be called after the default checker.
|
||||||
|
|
||||||
|
|
||||||
|
Strict:
|
||||||
|
|
||||||
|
The parser will return on the first error and stop parsing. This is on by
|
||||||
|
default. Otherwise, all arguments that can be parsed, will be, and only the
|
||||||
|
last error is returned.
|
||||||
|
|
||||||
|
|
||||||
|
Error Messages:
|
||||||
|
|
||||||
|
All error message strings are allocated using the context's `temp_allocator`,
|
||||||
|
so if you need them to persist, make sure to clone the underlying `message`.
|
||||||
|
|
||||||
|
|
||||||
|
Help:
|
||||||
|
|
||||||
|
By default, `-h` and `-help` are reserved flags which raise their own error
|
||||||
|
type when set, allowing the program to handle the request differently from
|
||||||
|
other errors.
|
||||||
|
|
||||||
|
|
||||||
|
Custom Types:
|
||||||
|
|
||||||
|
You may specify your own type setter for program-specific structs and other
|
||||||
|
named types. Call `register_type_setter` with an appropriate proc before
|
||||||
|
calling any of the parsing procs.
|
||||||
|
|
||||||
|
A compliant `Custom_Type_Setter` must return three values:
|
||||||
|
- an error message if one occurred,
|
||||||
|
- a boolean indicating if the proc handles the type, and
|
||||||
|
- an `Allocator_Error` if any occurred.
|
||||||
|
|
||||||
|
If the setter does not handle the type, simply return without setting any of
|
||||||
|
the values.
|
||||||
|
|
||||||
|
|
||||||
|
UNIX-style:
|
||||||
|
|
||||||
|
This package also supports parsing arguments in a limited flavor of UNIX.
|
||||||
|
Odin and UNIX style are mutually exclusive, and which one to be used is chosen
|
||||||
|
at parse time.
|
||||||
|
|
||||||
|
```
|
||||||
|
--flag
|
||||||
|
--flag=argument
|
||||||
|
--flag argument
|
||||||
|
--flag argument repeating-argument
|
||||||
|
```
|
||||||
|
|
||||||
|
`-flag` may also be substituted for `--flag`.
|
||||||
|
|
||||||
|
Do note that map flags are not currently supported in this parsing style.
|
||||||
|
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
A complete example is given in the `example` subdirectory.
|
||||||
|
*/
|
||||||
|
package flags
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
Parse_Error_Reason :: enum {
|
||||||
|
None,
|
||||||
|
// An extra positional argument was given, and there is no `varg` field.
|
||||||
|
Extra_Positional,
|
||||||
|
// The underlying type does not support the string value it is being set to.
|
||||||
|
Bad_Value,
|
||||||
|
// No flag was given by the user.
|
||||||
|
No_Flag,
|
||||||
|
// No value was given by the user.
|
||||||
|
No_Value,
|
||||||
|
// The flag on the struct is missing.
|
||||||
|
Missing_Flag,
|
||||||
|
// The type itself isn't supported.
|
||||||
|
Unsupported_Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raised during parsing, naturally.
|
||||||
|
Parse_Error :: struct {
|
||||||
|
reason: Unified_Parse_Error_Reason,
|
||||||
|
message: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raised during parsing.
|
||||||
|
// Provides more granular information than what just a string could hold.
|
||||||
|
Open_File_Error :: struct {
|
||||||
|
filename: string,
|
||||||
|
errno: os.Errno,
|
||||||
|
mode: int,
|
||||||
|
perms: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raised during parsing.
|
||||||
|
Help_Request :: distinct bool
|
||||||
|
|
||||||
|
|
||||||
|
// Raised after parsing, during validation.
|
||||||
|
Validation_Error :: struct {
|
||||||
|
message: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
Error :: union {
|
||||||
|
Parse_Error,
|
||||||
|
Open_File_Error,
|
||||||
|
Help_Request,
|
||||||
|
Validation_Error,
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//+build freebsd, netbsd, openbsd
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
|
||||||
|
Unified_Parse_Error_Reason :: union #shared_nil {
|
||||||
|
Parse_Error_Reason,
|
||||||
|
runtime.Allocator_Error,
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
//+build !freebsd !netbsd !openbsd
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:net"
|
||||||
|
|
||||||
|
Unified_Parse_Error_Reason :: union #shared_nil {
|
||||||
|
Parse_Error_Reason,
|
||||||
|
runtime.Allocator_Error,
|
||||||
|
net.Parse_Endpoint_Error,
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package core_flags_example
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:flags"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:net"
|
||||||
|
import "core:os"
|
||||||
|
import "core:time/datetime"
|
||||||
|
|
||||||
|
|
||||||
|
Fixed_Point1_1 :: struct {
|
||||||
|
integer: u8,
|
||||||
|
fractional: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
Optimization_Level :: enum {
|
||||||
|
Slow,
|
||||||
|
Fast,
|
||||||
|
Warp_Speed,
|
||||||
|
Ludicrous_Speed,
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's simple but powerful.
|
||||||
|
my_custom_type_setter :: proc(
|
||||||
|
data: rawptr,
|
||||||
|
data_type: typeid,
|
||||||
|
unparsed_value: string,
|
||||||
|
args_tag: string,
|
||||||
|
) -> (
|
||||||
|
error: string,
|
||||||
|
handled: bool,
|
||||||
|
alloc_error: runtime.Allocator_Error,
|
||||||
|
) {
|
||||||
|
if data_type == Fixed_Point1_1 {
|
||||||
|
handled = true
|
||||||
|
ptr := cast(^Fixed_Point1_1)data
|
||||||
|
|
||||||
|
// precision := flags.get_subtag(args_tag, "precision")
|
||||||
|
|
||||||
|
if len(unparsed_value) == 3 {
|
||||||
|
ptr.integer = unparsed_value[0] - '0'
|
||||||
|
ptr.fractional = unparsed_value[2] - '0'
|
||||||
|
} else {
|
||||||
|
error = "Incorrect format. Must be in the form of `i.f`."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform sanity checking here in the type parsing phase.
|
||||||
|
//
|
||||||
|
// The validation phase is flag-specific.
|
||||||
|
if !(0 <= ptr.integer && ptr.integer < 10) || !(0 <= ptr.fractional && ptr.fractional < 10) {
|
||||||
|
error = "Incorrect format. Must be between `0.0` and `9.9`."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
my_custom_flag_checker :: proc(
|
||||||
|
model: rawptr,
|
||||||
|
name: string,
|
||||||
|
value: any,
|
||||||
|
args_tag: string,
|
||||||
|
) -> (error: string) {
|
||||||
|
if name == "iterations" {
|
||||||
|
v := value.(int)
|
||||||
|
if !(1 <= v && v < 5) {
|
||||||
|
error = "Iterations only supports 1 ..< 5."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Distinct_Int :: distinct int
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
Options :: struct {
|
||||||
|
|
||||||
|
file: os.Handle `args:"pos=0,required,file=r" usage:"Input file."`,
|
||||||
|
output: os.Handle `args:"pos=1,file=cw" usage:"Output file."`,
|
||||||
|
|
||||||
|
hub: net.Host_Or_Endpoint `usage:"Internet address to contact for updates."`,
|
||||||
|
schedule: datetime.DateTime `usage:"Launch tasks at this time."`,
|
||||||
|
|
||||||
|
opt: Optimization_Level `usage:"Optimization level."`,
|
||||||
|
todo: [dynamic]string `usage:"Todo items."`,
|
||||||
|
|
||||||
|
accuracy: Fixed_Point1_1 `args:"required" usage:"Lenience in FLOP calculations."`,
|
||||||
|
iterations: int `usage:"Run this many times."`,
|
||||||
|
|
||||||
|
// Note how the parser will transform this flag's name into `special-int`.
|
||||||
|
special_int: Distinct_Int `args:"indistinct" usage:"Able to set distinct types."`,
|
||||||
|
|
||||||
|
quat: quaternion256,
|
||||||
|
|
||||||
|
bits: bit_set[0..<8],
|
||||||
|
|
||||||
|
// Many different requirement styles:
|
||||||
|
|
||||||
|
// gadgets: [dynamic]string `args:"required=1" usage:"gadgets"`,
|
||||||
|
// widgets: [dynamic]string `args:"required=<3" usage:"widgets"`,
|
||||||
|
// foos: [dynamic]string `args:"required=2<4"`,
|
||||||
|
// bars: [dynamic]string `args:"required=3<4"`,
|
||||||
|
// bots: [dynamic]string `args:"required"`,
|
||||||
|
|
||||||
|
// (Maps) Only available in Odin style:
|
||||||
|
|
||||||
|
// assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`,
|
||||||
|
|
||||||
|
// (Variadic) Only available in UNIX style:
|
||||||
|
|
||||||
|
// bots: [dynamic]string `args:"variadic=2,required"`,
|
||||||
|
|
||||||
|
verbose: bool `usage:"Show verbose output."`,
|
||||||
|
debug: bool `args:"hidden" usage:"print debug info"`,
|
||||||
|
|
||||||
|
varg: [dynamic]string `usage:"Any extra arguments go here."`,
|
||||||
|
}
|
||||||
|
|
||||||
|
opt: Options
|
||||||
|
style : flags.Parsing_Style = .Odin
|
||||||
|
|
||||||
|
flags.register_type_setter(my_custom_type_setter)
|
||||||
|
flags.register_flag_checker(my_custom_flag_checker)
|
||||||
|
flags.parse_or_exit(&opt, os.args, style)
|
||||||
|
|
||||||
|
fmt.printfln("%#v", opt)
|
||||||
|
|
||||||
|
if opt.output != 0 {
|
||||||
|
os.write_string(opt.output, "Hellope!\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
//+private
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import "base:intrinsics"
|
||||||
|
@require import "base:runtime"
|
||||||
|
import "core:container/bit_array"
|
||||||
|
@require import "core:fmt"
|
||||||
|
@require import "core:mem"
|
||||||
|
import "core:reflect"
|
||||||
|
@require import "core:strconv"
|
||||||
|
@require import "core:strings"
|
||||||
|
|
||||||
|
// Push a positional argument onto a data struct, checking for specified
|
||||||
|
// positionals first before adding it to a fallback field.
|
||||||
|
@(optimization_mode="size")
|
||||||
|
push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: string) -> (error: Error) {
|
||||||
|
if bit_array.get(&parser.filled_pos, parser.filled_pos.max_index) {
|
||||||
|
// The max index is set, which means we're out of space.
|
||||||
|
// Add one free bit by setting the index above to false.
|
||||||
|
bit_array.set(&parser.filled_pos, 1 + parser.filled_pos.max_index, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pos: int = ---
|
||||||
|
{
|
||||||
|
iter := bit_array.make_iterator(&parser.filled_pos)
|
||||||
|
ok: bool
|
||||||
|
pos, ok = bit_array.iterate_by_unset(&iter)
|
||||||
|
|
||||||
|
// This may be an allocator error.
|
||||||
|
assert(ok, "Unable to find a free spot in the positional bit_array.")
|
||||||
|
}
|
||||||
|
|
||||||
|
field, index, has_pos_assigned := get_field_by_pos(model, pos)
|
||||||
|
|
||||||
|
if !has_pos_assigned {
|
||||||
|
when intrinsics.type_has_field(T, INTERNAL_VARIADIC_FLAG) {
|
||||||
|
// Add it to the fallback array.
|
||||||
|
field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG)
|
||||||
|
} else {
|
||||||
|
return Parse_Error {
|
||||||
|
.Extra_Positional,
|
||||||
|
fmt.tprintf("Got extra positional argument `%s` with nowhere to store it.", arg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := cast(rawptr)(cast(uintptr)model + field.offset)
|
||||||
|
args_tag, _ := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||||
|
field_name := get_field_name(field)
|
||||||
|
error = parse_and_set_pointer_by_type(ptr, arg, field.type, args_tag)
|
||||||
|
#partial switch &specific_error in error {
|
||||||
|
case Parse_Error:
|
||||||
|
specific_error.message = fmt.tprintf("Unable to set positional #%i (%s) of type %v to `%s`.%s%s",
|
||||||
|
pos,
|
||||||
|
field_name,
|
||||||
|
field.type,
|
||||||
|
arg,
|
||||||
|
" " if len(specific_error.message) > 0 else "",
|
||||||
|
specific_error.message)
|
||||||
|
case nil:
|
||||||
|
bit_array.set(&parser.filled_pos, pos)
|
||||||
|
bit_array.set(&parser.fields_set, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
register_field :: proc(parser: ^Parser, field: reflect.Struct_Field, index: int) {
|
||||||
|
if pos, ok := get_field_pos(field); ok {
|
||||||
|
bit_array.set(&parser.filled_pos, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
bit_array.set(&parser.fields_set, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a `-flag` argument, Odin-style.
|
||||||
|
@(optimization_mode="size")
|
||||||
|
set_odin_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (error: Error) {
|
||||||
|
// We make a special case for help requests.
|
||||||
|
switch name {
|
||||||
|
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||||
|
return Help_Request{}
|
||||||
|
}
|
||||||
|
|
||||||
|
field, index := get_field_by_name(model, name) or_return
|
||||||
|
|
||||||
|
#partial switch specific_type_info in field.type.variant {
|
||||||
|
case runtime.Type_Info_Boolean:
|
||||||
|
ptr := cast(^bool)(cast(uintptr)model + field.offset)
|
||||||
|
ptr^ = true
|
||||||
|
case:
|
||||||
|
return Parse_Error {
|
||||||
|
.Bad_Value,
|
||||||
|
fmt.tprintf("Unable to set `%s` of type %v to true.", name, field.type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register_field(parser, field, index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a `-flag` argument, UNIX-style.
|
||||||
|
@(optimization_mode="size")
|
||||||
|
set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args: int, error: Error) {
|
||||||
|
// We make a special case for help requests.
|
||||||
|
switch name {
|
||||||
|
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||||
|
return 0, Help_Request{}
|
||||||
|
}
|
||||||
|
|
||||||
|
field, index := get_field_by_name(model, name) or_return
|
||||||
|
|
||||||
|
#partial switch specific_type_info in field.type.variant {
|
||||||
|
case runtime.Type_Info_Boolean:
|
||||||
|
ptr := cast(^bool)(cast(uintptr)model + field.offset)
|
||||||
|
ptr^ = true
|
||||||
|
case runtime.Type_Info_Dynamic_Array:
|
||||||
|
future_args = 1
|
||||||
|
if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
||||||
|
if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic {
|
||||||
|
// Variadic arrays may specify how many arguments they consume at once.
|
||||||
|
// Otherwise, they take everything that's left.
|
||||||
|
if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
|
||||||
|
future_args = cast(int)value
|
||||||
|
} else {
|
||||||
|
future_args = max(int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case:
|
||||||
|
// `--flag`, waiting on its value.
|
||||||
|
future_args = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
register_field(parser, field, index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a `-flag:option` argument.
|
||||||
|
@(optimization_mode="size")
|
||||||
|
set_option :: proc(model: ^$T, parser: ^Parser, name, option: string) -> (error: Error) {
|
||||||
|
field, index := get_field_by_name(model, name) or_return
|
||||||
|
|
||||||
|
if len(option) == 0 {
|
||||||
|
return Parse_Error {
|
||||||
|
.No_Value,
|
||||||
|
fmt.tprintf("Setting `%s` to an empty value is meaningless.", name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guard against incorrect syntax.
|
||||||
|
#partial switch specific_type_info in field.type.variant {
|
||||||
|
case runtime.Type_Info_Map:
|
||||||
|
return Parse_Error {
|
||||||
|
.No_Value,
|
||||||
|
fmt.tprintf("Unable to set `%s` of type %v to `%s`. Are you missing an `=`? The correct format is `map:key=value`.", name, field.type, option),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := cast(rawptr)(cast(uintptr)model + field.offset)
|
||||||
|
args_tag := reflect.struct_tag_get(field.tag, TAG_ARGS)
|
||||||
|
error = parse_and_set_pointer_by_type(ptr, option, field.type, args_tag)
|
||||||
|
#partial switch &specific_error in error {
|
||||||
|
case Parse_Error:
|
||||||
|
specific_error.message = fmt.tprintf("Unable to set `%s` of type %v to `%s`.%s%s",
|
||||||
|
name,
|
||||||
|
field.type,
|
||||||
|
option,
|
||||||
|
" " if len(specific_error.message) > 0 else "",
|
||||||
|
specific_error.message)
|
||||||
|
case nil:
|
||||||
|
register_field(parser, field, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a `-map:key=value` argument.
|
||||||
|
@(optimization_mode="size")
|
||||||
|
set_key_value :: proc(model: ^$T, parser: ^Parser, name, key, value: string) -> (error: Error) {
|
||||||
|
field, index := get_field_by_name(model, name) or_return
|
||||||
|
|
||||||
|
#partial switch specific_type_info in field.type.variant {
|
||||||
|
case runtime.Type_Info_Map:
|
||||||
|
key := key
|
||||||
|
key_ptr := cast(rawptr)&key
|
||||||
|
key_cstr: cstring
|
||||||
|
if reflect.is_cstring(specific_type_info.key) {
|
||||||
|
// We clone the key here, because it's liable to be a slice of an
|
||||||
|
// Odin string, and we need to put a NUL terminator in it.
|
||||||
|
key_cstr = strings.clone_to_cstring(key)
|
||||||
|
key_ptr = &key_cstr
|
||||||
|
}
|
||||||
|
defer if key_cstr != nil {
|
||||||
|
delete(key_cstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw_map := (^runtime.Raw_Map)(cast(uintptr)model + field.offset)
|
||||||
|
|
||||||
|
hash := specific_type_info.map_info.key_hasher(key_ptr, runtime.map_seed(raw_map^))
|
||||||
|
|
||||||
|
backing_alloc := false
|
||||||
|
elem_backing: []byte
|
||||||
|
value_ptr: rawptr
|
||||||
|
|
||||||
|
if raw_map.allocator.procedure == nil {
|
||||||
|
raw_map.allocator = context.allocator
|
||||||
|
} else {
|
||||||
|
value_ptr = runtime.__dynamic_map_get(raw_map,
|
||||||
|
specific_type_info.map_info,
|
||||||
|
hash,
|
||||||
|
key_ptr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value_ptr == nil {
|
||||||
|
alloc_error: runtime.Allocator_Error = ---
|
||||||
|
elem_backing, alloc_error = mem.alloc_bytes(specific_type_info.value.size, specific_type_info.value.align)
|
||||||
|
if elem_backing == nil {
|
||||||
|
return Parse_Error {
|
||||||
|
alloc_error,
|
||||||
|
"Failed to allocate element backing for map value.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backing_alloc = true
|
||||||
|
value_ptr = raw_data(elem_backing)
|
||||||
|
}
|
||||||
|
|
||||||
|
args_tag, _ := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||||
|
error = parse_and_set_pointer_by_type(value_ptr, value, specific_type_info.value, args_tag)
|
||||||
|
#partial switch &specific_error in error {
|
||||||
|
case Parse_Error:
|
||||||
|
specific_error.message = fmt.tprintf("Unable to set `%s` of type %v with key=value: `%s`=`%s`.%s%s",
|
||||||
|
name,
|
||||||
|
field.type,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
" " if len(specific_error.message) > 0 else "",
|
||||||
|
specific_error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if backing_alloc {
|
||||||
|
runtime.__dynamic_map_set(raw_map,
|
||||||
|
specific_type_info.map_info,
|
||||||
|
hash,
|
||||||
|
key_ptr,
|
||||||
|
value_ptr,
|
||||||
|
)
|
||||||
|
|
||||||
|
delete(elem_backing)
|
||||||
|
}
|
||||||
|
|
||||||
|
register_field(parser, field, index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return Parse_Error {
|
||||||
|
.Bad_Value,
|
||||||
|
fmt.tprintf("Unable to set `%s` of type %v with key=value: `%s`=`%s`.", name, field.type, key, value),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
//+private
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import "core:container/bit_array"
|
||||||
|
import "core:strconv"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
// Used to group state together.
|
||||||
|
Parser :: struct {
|
||||||
|
// `fields_set` tracks which arguments have been set.
|
||||||
|
// It uses their struct field index.
|
||||||
|
fields_set: bit_array.Bit_Array,
|
||||||
|
|
||||||
|
// `filled_pos` tracks which arguments have been filled into positional
|
||||||
|
// spots, much like how `fmt` treats them.
|
||||||
|
filled_pos: bit_array.Bit_Array,
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_one_odin_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (error: Error) {
|
||||||
|
arg := arg
|
||||||
|
|
||||||
|
if strings.has_prefix(arg, "-") {
|
||||||
|
arg = arg[1:]
|
||||||
|
|
||||||
|
flag: string
|
||||||
|
assignment_rune: rune
|
||||||
|
find_assignment: for r, i in arg {
|
||||||
|
switch r {
|
||||||
|
case ':', '=':
|
||||||
|
assignment_rune = r
|
||||||
|
flag = arg[:i]
|
||||||
|
arg = arg[1 + i:]
|
||||||
|
break find_assignment
|
||||||
|
case:
|
||||||
|
continue find_assignment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if assignment_rune == 0 {
|
||||||
|
if len(arg) == 0 {
|
||||||
|
return Parse_Error {
|
||||||
|
.No_Flag,
|
||||||
|
"No flag was given.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -flag
|
||||||
|
set_odin_flag(model, parser, arg) or_return
|
||||||
|
|
||||||
|
} else if assignment_rune == ':' {
|
||||||
|
// -flag:option <OR> -map:key=value
|
||||||
|
error = set_option(model, parser, flag, arg)
|
||||||
|
|
||||||
|
if error != nil {
|
||||||
|
// -flag:option did not work, so this may be a -map:key=value set.
|
||||||
|
find_equals: for r, i in arg {
|
||||||
|
if r == '=' {
|
||||||
|
key := arg[:i]
|
||||||
|
arg = arg[1 + i:]
|
||||||
|
error = set_key_value(model, parser, flag, key, arg)
|
||||||
|
break find_equals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// -flag=option, alternative syntax
|
||||||
|
set_option(model, parser, flag, arg) or_return
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// positional
|
||||||
|
error = push_positional(model, parser, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_one_unix_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (
|
||||||
|
future_args: int,
|
||||||
|
current_flag: string,
|
||||||
|
error: Error,
|
||||||
|
) {
|
||||||
|
arg := arg
|
||||||
|
|
||||||
|
if strings.has_prefix(arg, "-") {
|
||||||
|
// -flag
|
||||||
|
arg = arg[1:]
|
||||||
|
|
||||||
|
if strings.has_prefix(arg, "-") {
|
||||||
|
// Allow `--` to function as `-`.
|
||||||
|
arg = arg[1:]
|
||||||
|
|
||||||
|
if len(arg) == 0 {
|
||||||
|
// `--`, and only `--`.
|
||||||
|
// Everything from now on will be treated as an argument.
|
||||||
|
future_args = max(int)
|
||||||
|
current_flag = INTERNAL_VARIADIC_FLAG
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flag: string
|
||||||
|
find_assignment: for r, i in arg {
|
||||||
|
if r == '=' {
|
||||||
|
// --flag=option
|
||||||
|
flag = arg[:i]
|
||||||
|
arg = arg[1 + i:]
|
||||||
|
error = set_option(model, parser, flag, arg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --flag option, potentially
|
||||||
|
future_args = set_unix_flag(model, parser, arg) or_return
|
||||||
|
current_flag = arg
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// positional
|
||||||
|
error = push_positional(model, parser, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a number of requirements specifier.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// `min`
|
||||||
|
// `<max`
|
||||||
|
// `min<max`
|
||||||
|
parse_requirements :: proc(str: string) -> (minimum, maximum: int, ok: bool) {
|
||||||
|
if len(str) == 0 {
|
||||||
|
return 1, max(int), true
|
||||||
|
}
|
||||||
|
|
||||||
|
if less_than := strings.index_byte(str, '<'); less_than != -1 {
|
||||||
|
if len(str) == 1 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
#no_bounds_check left := str[:less_than]
|
||||||
|
#no_bounds_check right := str[1 + less_than:]
|
||||||
|
|
||||||
|
if left_value, parse_ok := strconv.parse_u64_of_base(left, 10); parse_ok {
|
||||||
|
minimum = cast(int)left_value
|
||||||
|
} else if len(left) > 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if right_value, parse_ok := strconv.parse_u64_of_base(right, 10); parse_ok {
|
||||||
|
maximum = cast(int)right_value
|
||||||
|
} else if len(right) > 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
} else {
|
||||||
|
maximum = max(int)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if value, parse_ok := strconv.parse_u64_of_base(str, 10); parse_ok {
|
||||||
|
minimum = cast(int)value
|
||||||
|
maximum = max(int)
|
||||||
|
} else {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,529 @@
|
|||||||
|
//+private
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import "base:intrinsics"
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:mem"
|
||||||
|
import "core:os"
|
||||||
|
import "core:reflect"
|
||||||
|
import "core:strconv"
|
||||||
|
import "core:strings"
|
||||||
|
@require import "core:time"
|
||||||
|
@require import "core:time/datetime"
|
||||||
|
import "core:unicode/utf8"
|
||||||
|
|
||||||
|
@(optimization_mode="size")
|
||||||
|
parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info) -> bool {
|
||||||
|
bounded_int :: proc(value, min, max: i128) -> (result: i128, ok: bool) {
|
||||||
|
return value, min <= value && value <= max
|
||||||
|
}
|
||||||
|
|
||||||
|
bounded_uint :: proc(value, max: u128) -> (result: u128, ok: bool) {
|
||||||
|
return value, value <= max
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(Feoramund): This procedure has been written with the goal in mind
|
||||||
|
// of generating the least amount of assembly, given that this library is
|
||||||
|
// likely to be called once and forgotten.
|
||||||
|
//
|
||||||
|
// I've rewritten the switch tables below in 3 different ways, and the
|
||||||
|
// current one generates the least amount of code for me on Linux AMD64.
|
||||||
|
//
|
||||||
|
// The other two ways were:
|
||||||
|
//
|
||||||
|
// - the original implementation: use of parametric polymorphism which led
|
||||||
|
// to dozens of functions generated, one for each type.
|
||||||
|
//
|
||||||
|
// - a `value, ok` assignment statement with the `or_return` done at the
|
||||||
|
// end of the switch, instead of inline.
|
||||||
|
//
|
||||||
|
// This seems to be the smallest way for now.
|
||||||
|
|
||||||
|
#partial switch specific_type_info in type_info.variant {
|
||||||
|
case runtime.Type_Info_Integer:
|
||||||
|
if specific_type_info.signed {
|
||||||
|
value := strconv.parse_i128(str) or_return
|
||||||
|
switch type_info.id {
|
||||||
|
case i8: (^i8) (ptr)^ = cast(i8) bounded_int(value, cast(i128)min(i8), cast(i128)max(i8) ) or_return
|
||||||
|
case i16: (^i16) (ptr)^ = cast(i16) bounded_int(value, cast(i128)min(i16), cast(i128)max(i16) ) or_return
|
||||||
|
case i32: (^i32) (ptr)^ = cast(i32) bounded_int(value, cast(i128)min(i32), cast(i128)max(i32) ) or_return
|
||||||
|
case i64: (^i64) (ptr)^ = cast(i64) bounded_int(value, cast(i128)min(i64), cast(i128)max(i64) ) or_return
|
||||||
|
case i128: (^i128) (ptr)^ = value
|
||||||
|
|
||||||
|
case int: (^int) (ptr)^ = cast(int) bounded_int(value, cast(i128)min(int), cast(i128)max(int) ) or_return
|
||||||
|
|
||||||
|
case i16le: (^i16le) (ptr)^ = cast(i16le) bounded_int(value, cast(i128)min(i16le), cast(i128)max(i16le) ) or_return
|
||||||
|
case i32le: (^i32le) (ptr)^ = cast(i32le) bounded_int(value, cast(i128)min(i32le), cast(i128)max(i32le) ) or_return
|
||||||
|
case i64le: (^i64le) (ptr)^ = cast(i64le) bounded_int(value, cast(i128)min(i64le), cast(i128)max(i64le) ) or_return
|
||||||
|
case i128le: (^i128le)(ptr)^ = cast(i128le) bounded_int(value, cast(i128)min(i128le), cast(i128)max(i128le)) or_return
|
||||||
|
|
||||||
|
case i16be: (^i16be) (ptr)^ = cast(i16be) bounded_int(value, cast(i128)min(i16be), cast(i128)max(i16be) ) or_return
|
||||||
|
case i32be: (^i32be) (ptr)^ = cast(i32be) bounded_int(value, cast(i128)min(i32be), cast(i128)max(i32be) ) or_return
|
||||||
|
case i64be: (^i64be) (ptr)^ = cast(i64be) bounded_int(value, cast(i128)min(i64be), cast(i128)max(i64be) ) or_return
|
||||||
|
case i128be: (^i128be)(ptr)^ = cast(i128be) bounded_int(value, cast(i128)min(i128be), cast(i128)max(i128be)) or_return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value := strconv.parse_u128(str) or_return
|
||||||
|
switch type_info.id {
|
||||||
|
case u8: (^u8) (ptr)^ = cast(u8) bounded_uint(value, cast(u128)max(u8) ) or_return
|
||||||
|
case u16: (^u16) (ptr)^ = cast(u16) bounded_uint(value, cast(u128)max(u16) ) or_return
|
||||||
|
case u32: (^u32) (ptr)^ = cast(u32) bounded_uint(value, cast(u128)max(u32) ) or_return
|
||||||
|
case u64: (^u64) (ptr)^ = cast(u64) bounded_uint(value, cast(u128)max(u64) ) or_return
|
||||||
|
case u128: (^u128) (ptr)^ = value
|
||||||
|
|
||||||
|
case uint: (^uint) (ptr)^ = cast(uint) bounded_uint(value, cast(u128)max(uint) ) or_return
|
||||||
|
case uintptr: (^uintptr)(ptr)^ = cast(uintptr) bounded_uint(value, cast(u128)max(uintptr)) or_return
|
||||||
|
|
||||||
|
case u16le: (^u16le) (ptr)^ = cast(u16le) bounded_uint(value, cast(u128)max(u16le) ) or_return
|
||||||
|
case u32le: (^u32le) (ptr)^ = cast(u32le) bounded_uint(value, cast(u128)max(u32le) ) or_return
|
||||||
|
case u64le: (^u64le) (ptr)^ = cast(u64le) bounded_uint(value, cast(u128)max(u64le) ) or_return
|
||||||
|
case u128le: (^u128le) (ptr)^ = cast(u128le) bounded_uint(value, cast(u128)max(u128le) ) or_return
|
||||||
|
|
||||||
|
case u16be: (^u16be) (ptr)^ = cast(u16be) bounded_uint(value, cast(u128)max(u16be) ) or_return
|
||||||
|
case u32be: (^u32be) (ptr)^ = cast(u32be) bounded_uint(value, cast(u128)max(u32be) ) or_return
|
||||||
|
case u64be: (^u64be) (ptr)^ = cast(u64be) bounded_uint(value, cast(u128)max(u64be) ) or_return
|
||||||
|
case u128be: (^u128be) (ptr)^ = cast(u128be) bounded_uint(value, cast(u128)max(u128be) ) or_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case runtime.Type_Info_Rune:
|
||||||
|
if utf8.rune_count_in_string(str) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
(^rune)(ptr)^ = utf8.rune_at_pos(str, 0)
|
||||||
|
|
||||||
|
case runtime.Type_Info_Float:
|
||||||
|
value := strconv.parse_f64(str) or_return
|
||||||
|
switch type_info.id {
|
||||||
|
case f16: (^f16) (ptr)^ = cast(f16) value
|
||||||
|
case f32: (^f32) (ptr)^ = cast(f32) value
|
||||||
|
case f64: (^f64) (ptr)^ = value
|
||||||
|
|
||||||
|
case f16le: (^f16le)(ptr)^ = cast(f16le) value
|
||||||
|
case f32le: (^f32le)(ptr)^ = cast(f32le) value
|
||||||
|
case f64le: (^f64le)(ptr)^ = cast(f64le) value
|
||||||
|
|
||||||
|
case f16be: (^f16be)(ptr)^ = cast(f16be) value
|
||||||
|
case f32be: (^f32be)(ptr)^ = cast(f32be) value
|
||||||
|
case f64be: (^f64be)(ptr)^ = cast(f64be) value
|
||||||
|
}
|
||||||
|
|
||||||
|
case runtime.Type_Info_Complex:
|
||||||
|
value := strconv.parse_complex128(str) or_return
|
||||||
|
switch type_info.id {
|
||||||
|
case complex32: (^complex32) (ptr)^ = (complex32)(value)
|
||||||
|
case complex64: (^complex64) (ptr)^ = (complex64)(value)
|
||||||
|
case complex128: (^complex128)(ptr)^ = value
|
||||||
|
}
|
||||||
|
|
||||||
|
case runtime.Type_Info_Quaternion:
|
||||||
|
value := strconv.parse_quaternion256(str) or_return
|
||||||
|
switch type_info.id {
|
||||||
|
case quaternion64: (^quaternion64) (ptr)^ = (quaternion64)(value)
|
||||||
|
case quaternion128: (^quaternion128)(ptr)^ = (quaternion128)(value)
|
||||||
|
case quaternion256: (^quaternion256)(ptr)^ = value
|
||||||
|
}
|
||||||
|
|
||||||
|
case runtime.Type_Info_String:
|
||||||
|
if specific_type_info.is_cstring {
|
||||||
|
cstr_ptr := (^cstring)(ptr)
|
||||||
|
if cstr_ptr != nil {
|
||||||
|
// Prevent memory leaks from us setting this value multiple times.
|
||||||
|
delete(cstr_ptr^)
|
||||||
|
}
|
||||||
|
cstr_ptr^ = strings.clone_to_cstring(str)
|
||||||
|
} else {
|
||||||
|
(^string)(ptr)^ = str
|
||||||
|
}
|
||||||
|
|
||||||
|
case runtime.Type_Info_Boolean:
|
||||||
|
value := strconv.parse_bool(str) or_return
|
||||||
|
switch type_info.id {
|
||||||
|
case bool: (^bool)(ptr)^ = value
|
||||||
|
case b8: (^b8) (ptr)^ = b8(value)
|
||||||
|
case b16: (^b16) (ptr)^ = b16(value)
|
||||||
|
case b32: (^b32) (ptr)^ = b32(value)
|
||||||
|
case b64: (^b64) (ptr)^ = b64(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case runtime.Type_Info_Bit_Set:
|
||||||
|
// Parse a string of 1's and 0's, from left to right,
|
||||||
|
// least significant bit to most significant bit.
|
||||||
|
value: u128
|
||||||
|
|
||||||
|
// NOTE: `upper` is inclusive, i.e: `0..=31`
|
||||||
|
max_bit_index := u128(1 + specific_type_info.upper - specific_type_info.lower)
|
||||||
|
bit_index := u128(0)
|
||||||
|
#no_bounds_check for string_index in 0..<uint(len(str)) {
|
||||||
|
if bit_index == max_bit_index {
|
||||||
|
// The string's too long for this bit_set.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch str[string_index] {
|
||||||
|
case '1':
|
||||||
|
value |= 1 << bit_index
|
||||||
|
bit_index += 1
|
||||||
|
case '0':
|
||||||
|
bit_index += 1
|
||||||
|
continue
|
||||||
|
case '_':
|
||||||
|
continue
|
||||||
|
case:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if specific_type_info.underlying != nil {
|
||||||
|
set_unbounded_integer_by_type(ptr, value, specific_type_info.underlying.id)
|
||||||
|
} else {
|
||||||
|
switch 8*type_info.size {
|
||||||
|
case 8: (^u8) (ptr)^ = cast(u8) value
|
||||||
|
case 16: (^u16) (ptr)^ = cast(u16) value
|
||||||
|
case 32: (^u32) (ptr)^ = cast(u32) value
|
||||||
|
case 64: (^u64) (ptr)^ = cast(u64) value
|
||||||
|
case 128: (^u128)(ptr)^ = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case:
|
||||||
|
fmt.panicf("Unsupported base data type: %v", specific_type_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// This proc exists to make error handling easier, since everything in the base
|
||||||
|
// type one above works on booleans. It's a simple parsing error if it's false.
|
||||||
|
//
|
||||||
|
// However, here we have to be more careful about how we handle errors,
|
||||||
|
// especially with files.
|
||||||
|
//
|
||||||
|
// We want to provide as informative as an error as we can.
|
||||||
|
@(optimization_mode="size", disabled=NO_CORE_NAMED_TYPES)
|
||||||
|
parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: typeid, arg_tag: string, out_error: ^Error) {
|
||||||
|
// Core types currently supported:
|
||||||
|
//
|
||||||
|
// - os.Handle
|
||||||
|
// - time.Time
|
||||||
|
// - datetime.DateTime
|
||||||
|
// - net.Host_Or_Endpoint
|
||||||
|
|
||||||
|
GENERIC_RFC_3339_ERROR :: "Invalid RFC 3339 string. Try this format: `yyyy-mm-ddThh:mm:ssZ`, for example `2024-02-29T16:30:00Z`."
|
||||||
|
|
||||||
|
out_error^ = nil
|
||||||
|
|
||||||
|
if data_type == os.Handle {
|
||||||
|
// NOTE: `os` is hopefully available everywhere, even if it might panic on some calls.
|
||||||
|
wants_read := false
|
||||||
|
wants_write := false
|
||||||
|
mode: int
|
||||||
|
|
||||||
|
if file, ok := get_struct_subtag(arg_tag, SUBTAG_FILE); ok {
|
||||||
|
for i in 0..<len(file) {
|
||||||
|
#no_bounds_check switch file[i] {
|
||||||
|
case 'r': wants_read = true
|
||||||
|
case 'w': wants_write = true
|
||||||
|
case 'c': mode |= os.O_CREATE
|
||||||
|
case 'a': mode |= os.O_APPEND
|
||||||
|
case 't': mode |= os.O_TRUNC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sane default.
|
||||||
|
// owner/group/other: r--r--r--
|
||||||
|
perms: int = 0o444
|
||||||
|
|
||||||
|
if wants_read && wants_write {
|
||||||
|
mode |= os.O_RDWR
|
||||||
|
perms |= 0o200
|
||||||
|
} else if wants_write {
|
||||||
|
mode |= os.O_WRONLY
|
||||||
|
perms |= 0o200
|
||||||
|
} else {
|
||||||
|
mode |= os.O_RDONLY
|
||||||
|
}
|
||||||
|
|
||||||
|
if permstr, ok := get_struct_subtag(arg_tag, SUBTAG_PERMS); ok {
|
||||||
|
if value, parse_ok := strconv.parse_u64_of_base(permstr, 8); parse_ok {
|
||||||
|
perms = int(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle, errno := os.open(str, mode, perms)
|
||||||
|
if errno != 0 {
|
||||||
|
// NOTE(Feoramund): os.Errno is system-dependent, and there's
|
||||||
|
// currently no good way to translate them all into strings.
|
||||||
|
//
|
||||||
|
// The upcoming `os2` package will hopefully solve this.
|
||||||
|
//
|
||||||
|
// We can at least provide the number for now, so the user can look
|
||||||
|
// it up.
|
||||||
|
out_error^ = Open_File_Error {
|
||||||
|
str,
|
||||||
|
errno,
|
||||||
|
mode,
|
||||||
|
perms,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
(^os.Handle)(ptr)^ = handle
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
when IMPORTING_TIME {
|
||||||
|
if data_type == time.Time {
|
||||||
|
// NOTE: The leap second data is discarded.
|
||||||
|
res, consumed := time.rfc3339_to_time_utc(str)
|
||||||
|
if consumed == 0 {
|
||||||
|
// The RFC 3339 parsing facilities provide no indication as to what
|
||||||
|
// went wrong, so just treat it as a regular parsing error.
|
||||||
|
out_error^ = Parse_Error {
|
||||||
|
.Bad_Value,
|
||||||
|
GENERIC_RFC_3339_ERROR,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
(^time.Time)(ptr)^ = res
|
||||||
|
return
|
||||||
|
} else if data_type == datetime.DateTime {
|
||||||
|
// NOTE: The UTC offset and leap second data are discarded.
|
||||||
|
res, _, _, consumed := time.rfc3339_to_components(str)
|
||||||
|
if consumed == 0 {
|
||||||
|
out_error^ = Parse_Error {
|
||||||
|
.Bad_Value,
|
||||||
|
GENERIC_RFC_3339_ERROR,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
(^datetime.DateTime)(ptr)^ = res
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when IMPORTING_NET {
|
||||||
|
if try_net_parse_workaround(data_type, str, ptr, out_error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out_error ^= Parse_Error {
|
||||||
|
// The caller will add more details.
|
||||||
|
.Unsupported_Type,
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(optimization_mode="size")
|
||||||
|
set_unbounded_integer_by_type :: proc(ptr: rawptr, value: $T, data_type: typeid) where intrinsics.type_is_integer(T) {
|
||||||
|
switch data_type {
|
||||||
|
case i8: (^i8) (ptr)^ = cast(i8) value
|
||||||
|
case i16: (^i16) (ptr)^ = cast(i16) value
|
||||||
|
case i32: (^i32) (ptr)^ = cast(i32) value
|
||||||
|
case i64: (^i64) (ptr)^ = cast(i64) value
|
||||||
|
case i128: (^i128) (ptr)^ = cast(i128) value
|
||||||
|
|
||||||
|
case int: (^int) (ptr)^ = cast(int) value
|
||||||
|
|
||||||
|
case i16le: (^i16le) (ptr)^ = cast(i16le) value
|
||||||
|
case i32le: (^i32le) (ptr)^ = cast(i32le) value
|
||||||
|
case i64le: (^i64le) (ptr)^ = cast(i64le) value
|
||||||
|
case i128le: (^i128le) (ptr)^ = cast(i128le) value
|
||||||
|
|
||||||
|
case i16be: (^i16be) (ptr)^ = cast(i16be) value
|
||||||
|
case i32be: (^i32be) (ptr)^ = cast(i32be) value
|
||||||
|
case i64be: (^i64be) (ptr)^ = cast(i64be) value
|
||||||
|
case i128be: (^i128be) (ptr)^ = cast(i128be) value
|
||||||
|
|
||||||
|
case u8: (^u8) (ptr)^ = cast(u8) value
|
||||||
|
case u16: (^u16) (ptr)^ = cast(u16) value
|
||||||
|
case u32: (^u32) (ptr)^ = cast(u32) value
|
||||||
|
case u64: (^u64) (ptr)^ = cast(u64) value
|
||||||
|
case u128: (^u128) (ptr)^ = cast(u128) value
|
||||||
|
|
||||||
|
case uint: (^uint) (ptr)^ = cast(uint) value
|
||||||
|
case uintptr: (^uintptr)(ptr)^ = cast(uintptr) value
|
||||||
|
|
||||||
|
case u16le: (^u16le) (ptr)^ = cast(u16le) value
|
||||||
|
case u32le: (^u32le) (ptr)^ = cast(u32le) value
|
||||||
|
case u64le: (^u64le) (ptr)^ = cast(u64le) value
|
||||||
|
case u128le: (^u128le) (ptr)^ = cast(u128le) value
|
||||||
|
|
||||||
|
case u16be: (^u16be) (ptr)^ = cast(u16be) value
|
||||||
|
case u32be: (^u32be) (ptr)^ = cast(u32be) value
|
||||||
|
case u64be: (^u64be) (ptr)^ = cast(u64be) value
|
||||||
|
case u128be: (^u128be) (ptr)^ = cast(u128be) value
|
||||||
|
|
||||||
|
case rune: (^rune) (ptr)^ = cast(rune) value
|
||||||
|
|
||||||
|
case:
|
||||||
|
fmt.panicf("Unsupported integer backing type: %v", data_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(optimization_mode="size")
|
||||||
|
parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info, arg_tag: string) -> (error: Error) {
|
||||||
|
#partial switch specific_type_info in type_info.variant {
|
||||||
|
case runtime.Type_Info_Named:
|
||||||
|
if global_custom_type_setter != nil {
|
||||||
|
// The program gets to go first.
|
||||||
|
error_message, handled, alloc_error := global_custom_type_setter(ptr, type_info.id, str, arg_tag)
|
||||||
|
|
||||||
|
if alloc_error != nil {
|
||||||
|
// There was an allocation error. Bail out.
|
||||||
|
return Parse_Error {
|
||||||
|
alloc_error,
|
||||||
|
"Custom type setter encountered allocation error.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if handled {
|
||||||
|
// The program handled the type.
|
||||||
|
|
||||||
|
if len(error_message) != 0 {
|
||||||
|
// However, there was an error. Pass it along.
|
||||||
|
error = Parse_Error {
|
||||||
|
.Bad_Value,
|
||||||
|
error_message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Might be a named enum. Need to check here first, since we handle all enums.
|
||||||
|
if enum_type_info, is_enum := specific_type_info.base.variant.(runtime.Type_Info_Enum); is_enum {
|
||||||
|
if value, ok := reflect.enum_from_name_any(type_info.id, str); ok {
|
||||||
|
set_unbounded_integer_by_type(ptr, value, enum_type_info.base.id)
|
||||||
|
} else {
|
||||||
|
return Parse_Error {
|
||||||
|
.Bad_Value,
|
||||||
|
fmt.tprintf("Invalid value name. Valid names are: %s", enum_type_info.names),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error)
|
||||||
|
|
||||||
|
if error != nil {
|
||||||
|
// So far, it's none of the types that we recognize.
|
||||||
|
// Check to see if we can set it by base type, if allowed.
|
||||||
|
if _, is_indistinct := get_struct_subtag(arg_tag, SUBTAG_INDISTINCT); is_indistinct {
|
||||||
|
return parse_and_set_pointer_by_type(ptr, str, specific_type_info.base, arg_tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case runtime.Type_Info_Dynamic_Array:
|
||||||
|
ptr := cast(^runtime.Raw_Dynamic_Array)ptr
|
||||||
|
|
||||||
|
// Try to convert the value first.
|
||||||
|
elem_backing, alloc_error := mem.alloc_bytes(specific_type_info.elem.size, specific_type_info.elem.align)
|
||||||
|
if alloc_error != nil {
|
||||||
|
return Parse_Error {
|
||||||
|
alloc_error,
|
||||||
|
"Failed to allocate element backing for dynamic array.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer delete(elem_backing)
|
||||||
|
parse_and_set_pointer_by_type(raw_data(elem_backing), str, specific_type_info.elem, arg_tag) or_return
|
||||||
|
|
||||||
|
if !runtime.__dynamic_array_resize(ptr, specific_type_info.elem.size, specific_type_info.elem.align, ptr.len + 1) {
|
||||||
|
// NOTE: This is purely an assumption that it's OOM.
|
||||||
|
// Regardless, the resize failed.
|
||||||
|
return Parse_Error {
|
||||||
|
runtime.Allocator_Error.Out_Of_Memory,
|
||||||
|
"Failed to resize dynamic array.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subptr := rawptr(
|
||||||
|
uintptr(ptr.data) +
|
||||||
|
uintptr((ptr.len - 1) * specific_type_info.elem.size))
|
||||||
|
mem.copy(subptr, raw_data(elem_backing), len(elem_backing))
|
||||||
|
|
||||||
|
case runtime.Type_Info_Enum:
|
||||||
|
// This is a nameless enum.
|
||||||
|
// The code here is virtually the same as above for named enums.
|
||||||
|
if value, ok := reflect.enum_from_name_any(type_info.id, str); ok {
|
||||||
|
set_unbounded_integer_by_type(ptr, value, specific_type_info.base.id)
|
||||||
|
} else {
|
||||||
|
return Parse_Error {
|
||||||
|
.Bad_Value,
|
||||||
|
fmt.tprintf("Invalid value name. Valid names are: %s", specific_type_info.names),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case:
|
||||||
|
if !parse_and_set_pointer_by_base_type(ptr, str, type_info) {
|
||||||
|
return Parse_Error {
|
||||||
|
// The caller will add more details.
|
||||||
|
.Bad_Value,
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
get_struct_subtag :: get_subtag
|
||||||
|
|
||||||
|
get_field_name :: proc(field: reflect.Struct_Field) -> string {
|
||||||
|
if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
||||||
|
if name_subtag, name_ok := get_struct_subtag(args_tag, SUBTAG_NAME); name_ok {
|
||||||
|
return name_subtag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name, _ := strings.replace_all(field.name, "_", "-", context.temp_allocator)
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
get_field_pos :: proc(field: reflect.Struct_Field) -> (int, bool) {
|
||||||
|
if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
||||||
|
if pos_subtag, pos_ok := get_struct_subtag(args_tag, SUBTAG_POS); pos_ok {
|
||||||
|
if value, parse_ok := strconv.parse_u64_of_base(pos_subtag, 10); parse_ok {
|
||||||
|
return int(value), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a struct field by its field name or `name` subtag.
|
||||||
|
get_field_by_name :: proc(model: ^$T, name: string) -> (result: reflect.Struct_Field, index: int, error: Error) {
|
||||||
|
for field, i in reflect.struct_fields_zipped(T) {
|
||||||
|
if get_field_name(field) == name {
|
||||||
|
return field, i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error = Parse_Error {
|
||||||
|
.Missing_Flag,
|
||||||
|
fmt.tprintf("Unable to find any flag named `%s`.", name),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a struct field by its `pos` subtag.
|
||||||
|
get_field_by_pos :: proc(model: ^$T, pos: int) -> (result: reflect.Struct_Field, index: int, ok: bool) {
|
||||||
|
for field, i in reflect.struct_fields_zipped(T) {
|
||||||
|
args_tag := reflect.struct_tag_lookup(field.tag, TAG_ARGS) or_continue
|
||||||
|
pos_subtag := get_struct_subtag(args_tag, SUBTAG_POS) or_continue
|
||||||
|
|
||||||
|
value, parse_ok := strconv.parse_u64_of_base(pos_subtag, 10)
|
||||||
|
if parse_ok && cast(int)value == pos {
|
||||||
|
return field, i, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//+private
|
||||||
|
//+build !freebsd !netbsd !openbsd
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import "core:net"
|
||||||
|
|
||||||
|
// This proc exists purely as a workaround for import restrictions.
|
||||||
|
// Returns true if caller should return early.
|
||||||
|
try_net_parse_workaround :: #force_inline proc (
|
||||||
|
data_type: typeid,
|
||||||
|
str: string,
|
||||||
|
ptr: rawptr,
|
||||||
|
out_error: ^Error,
|
||||||
|
) -> bool {
|
||||||
|
if data_type == net.Host_Or_Endpoint {
|
||||||
|
addr, net_error := net.parse_hostname_or_endpoint(str)
|
||||||
|
if net_error != nil {
|
||||||
|
// We pass along `net.Error` here.
|
||||||
|
out_error^ = Parse_Error {
|
||||||
|
net_error,
|
||||||
|
"Invalid Host/Endpoint.",
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
(cast(^net.Host_Or_Endpoint)ptr)^ = addr
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
//+private
|
||||||
|
package flags
|
||||||
|
|
||||||
|
@require import "base:runtime"
|
||||||
|
@require import "core:container/bit_array"
|
||||||
|
@require import "core:fmt"
|
||||||
|
@require import "core:mem"
|
||||||
|
@require import "core:os"
|
||||||
|
@require import "core:reflect"
|
||||||
|
@require import "core:strconv"
|
||||||
|
@require import "core:strings"
|
||||||
|
|
||||||
|
// This proc is used to assert that `T` meets the expectations of the library.
|
||||||
|
@(optimization_mode="size", disabled=ODIN_DISABLE_ASSERT)
|
||||||
|
validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_location) {
|
||||||
|
positionals_assigned_so_far: bit_array.Bit_Array
|
||||||
|
defer bit_array.destroy(&positionals_assigned_so_far)
|
||||||
|
|
||||||
|
check_fields: for field in reflect.struct_fields_zipped(T) {
|
||||||
|
if style == .Unix {
|
||||||
|
#partial switch specific_type_info in field.type.variant {
|
||||||
|
case runtime.Type_Info_Map:
|
||||||
|
fmt.panicf("%T.%s is a map type, and these are not supported in UNIX-style parsing mode.",
|
||||||
|
model_type, field.name, loc = loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name_is_safe := true
|
||||||
|
defer {
|
||||||
|
fmt.assertf(name_is_safe, "%T.%s is using a reserved name.",
|
||||||
|
model_type, field.name, loc = loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch field.name {
|
||||||
|
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||||
|
name_is_safe = false
|
||||||
|
}
|
||||||
|
|
||||||
|
args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||||
|
if !ok {
|
||||||
|
// If it has no args tag, then we've checked all we need to.
|
||||||
|
// Most of this proc is validating that the subtags are sane.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if name, has_name := get_struct_subtag(args_tag, SUBTAG_NAME); has_name {
|
||||||
|
fmt.assertf(len(name) > 0, "%T.%s has a zero-length `%s`.",
|
||||||
|
model_type, field.name, SUBTAG_NAME, loc = loc)
|
||||||
|
|
||||||
|
fmt.assertf(strings.index(name, " ") == -1, "%T.%s has a `%s` with spaces in it.",
|
||||||
|
model_type, field.name, SUBTAG_NAME, loc = loc)
|
||||||
|
|
||||||
|
switch name {
|
||||||
|
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||||
|
name_is_safe = false
|
||||||
|
continue check_fields
|
||||||
|
case:
|
||||||
|
name_is_safe = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS); has_pos {
|
||||||
|
#partial switch specific_type_info in field.type.variant {
|
||||||
|
case runtime.Type_Info_Map:
|
||||||
|
fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.",
|
||||||
|
model_type, field.name, SUBTAG_POS, loc = loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pos_value, pos_ok := strconv.parse_u64_of_base(pos_str, 10)
|
||||||
|
fmt.assertf(pos_ok, "%T.%s has `%s` defined as %q but cannot be parsed a base-10 integer >= 0.",
|
||||||
|
model_type, field.name, SUBTAG_POS, pos_str, loc = loc)
|
||||||
|
fmt.assertf(!bit_array.get(&positionals_assigned_so_far, pos_value), "%T.%s has `%s` set to #%i, but that position has already been assigned to another flag.",
|
||||||
|
model_type, field.name, SUBTAG_POS, pos_value, loc = loc)
|
||||||
|
bit_array.set(&positionals_assigned_so_far, pos_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
required_min, required_max: int
|
||||||
|
if requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED); is_required {
|
||||||
|
fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.",
|
||||||
|
model_type, field.name, loc = loc)
|
||||||
|
|
||||||
|
fmt.assertf(field.name != INTERNAL_VARIADIC_FLAG, "%T.%s is defined as required. This is disallowed.",
|
||||||
|
model_type, field.name, loc = loc)
|
||||||
|
|
||||||
|
if len(requirement) > 0 {
|
||||||
|
if required_min, required_max, ok = parse_requirements(requirement); ok {
|
||||||
|
#partial switch specific_type_info in field.type.variant {
|
||||||
|
case runtime.Type_Info_Dynamic_Array:
|
||||||
|
fmt.assertf(required_min != required_max, "%T.%s has `%s` defined as %q, but the minimum and maximum are the same. Increase the maximum by 1 for an exact number of arguments: (%i<%i)",
|
||||||
|
model_type,
|
||||||
|
field.name,
|
||||||
|
SUBTAG_REQUIRED,
|
||||||
|
requirement,
|
||||||
|
required_min,
|
||||||
|
1 + required_max,
|
||||||
|
loc = loc)
|
||||||
|
|
||||||
|
fmt.assertf(required_min < required_max, "%T.%s has `%s` defined as %q, but the minimum and maximum are swapped.",
|
||||||
|
model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc)
|
||||||
|
|
||||||
|
case:
|
||||||
|
fmt.panicf("%T.%s has `%s` defined as %q, but ranges are only supported on dynamic arrays.",
|
||||||
|
model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.panicf("%T.%s has `%s` defined as %q, but it cannot be parsed as a valid range.",
|
||||||
|
model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if length, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic {
|
||||||
|
if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok {
|
||||||
|
fmt.assertf(value > 0,
|
||||||
|
"%T.%s has `%s` set to %i. It must be greater than zero.",
|
||||||
|
model_type, field.name, value, SUBTAG_VARIADIC, loc = loc)
|
||||||
|
fmt.assertf(value != 1,
|
||||||
|
"%T.%s has `%s` set to 1. This has no effect.",
|
||||||
|
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
#partial switch specific_type_info in field.type.variant {
|
||||||
|
case runtime.Type_Info_Dynamic_Array:
|
||||||
|
fmt.assertf(style != .Odin,
|
||||||
|
"%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.",
|
||||||
|
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
|
||||||
|
case:
|
||||||
|
fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.",
|
||||||
|
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowed_to_define_file_perms: bool = ---
|
||||||
|
#partial switch specific_type_info in field.type.variant {
|
||||||
|
case runtime.Type_Info_Map:
|
||||||
|
allowed_to_define_file_perms = specific_type_info.value.id == os.Handle
|
||||||
|
case runtime.Type_Info_Dynamic_Array:
|
||||||
|
allowed_to_define_file_perms = specific_type_info.elem.id == os.Handle
|
||||||
|
case:
|
||||||
|
allowed_to_define_file_perms = field.type.id == os.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, has_file := get_struct_subtag(args_tag, SUBTAG_FILE); has_file {
|
||||||
|
fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.",
|
||||||
|
model_type, field.name, SUBTAG_FILE, loc = loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, has_perms := get_struct_subtag(args_tag, SUBTAG_PERMS); has_perms {
|
||||||
|
fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.",
|
||||||
|
model_type, field.name, SUBTAG_PERMS, loc = loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
#partial switch specific_type_info in field.type.variant {
|
||||||
|
case runtime.Type_Info_Map:
|
||||||
|
fmt.assertf(reflect.is_string(specific_type_info.key), "%T.%s is defined as a map[%T]. Only string types are currently supported as map keys.",
|
||||||
|
model_type,
|
||||||
|
field.name,
|
||||||
|
specific_type_info.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that all the required arguments are set and that the set arguments
|
||||||
|
// are up to the program's expectations.
|
||||||
|
@(optimization_mode="size")
|
||||||
|
validate_arguments :: proc(model: ^$T, parser: ^Parser) -> Error {
|
||||||
|
check_fields: for field, index in reflect.struct_fields_zipped(T) {
|
||||||
|
was_set := bit_array.get(&parser.fields_set, index)
|
||||||
|
|
||||||
|
field_name := get_field_name(field)
|
||||||
|
args_tag := reflect.struct_tag_get(field.tag, TAG_ARGS)
|
||||||
|
requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED)
|
||||||
|
|
||||||
|
required_min, required_max: int
|
||||||
|
has_requirements: bool
|
||||||
|
if is_required {
|
||||||
|
required_min, required_max, has_requirements = parse_requirements(requirement)
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_requirements && required_min == 0 {
|
||||||
|
// Allow `0<n` or `<n` to bypass the required condition.
|
||||||
|
is_required = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, is_array := field.type.variant.(runtime.Type_Info_Dynamic_Array); is_array && has_requirements {
|
||||||
|
// If it's an array, make sure it meets the required number of arguments.
|
||||||
|
ptr := cast(^runtime.Raw_Dynamic_Array)(cast(uintptr)model + field.offset)
|
||||||
|
if required_min == required_max - 1 && ptr.len != required_min {
|
||||||
|
return Validation_Error {
|
||||||
|
fmt.tprintf("The flag `%s` had %i option%s set, but it requires exactly %i.",
|
||||||
|
field_name,
|
||||||
|
ptr.len,
|
||||||
|
"" if ptr.len == 1 else "s",
|
||||||
|
required_min),
|
||||||
|
}
|
||||||
|
} else if required_min > ptr.len || ptr.len >= required_max {
|
||||||
|
if required_max == max(int) {
|
||||||
|
return Validation_Error {
|
||||||
|
fmt.tprintf("The flag `%s` had %i option%s set, but it requires at least %i.",
|
||||||
|
field_name,
|
||||||
|
ptr.len,
|
||||||
|
"" if ptr.len == 1 else "s",
|
||||||
|
required_min),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Validation_Error {
|
||||||
|
fmt.tprintf("The flag `%s` had %i option%s set, but it requires at least %i and at most %i.",
|
||||||
|
field_name,
|
||||||
|
ptr.len,
|
||||||
|
"" if ptr.len == 1 else "s",
|
||||||
|
required_min,
|
||||||
|
required_max - 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !was_set {
|
||||||
|
if is_required {
|
||||||
|
return Validation_Error {
|
||||||
|
fmt.tprintf("The required flag `%s` was not set.", field_name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not set, not required; moving on.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// All default checks have passed. The program gets a look at it now.
|
||||||
|
|
||||||
|
if global_custom_flag_checker != nil {
|
||||||
|
ptr := cast(rawptr)(cast(uintptr)model + field.offset)
|
||||||
|
error := global_custom_flag_checker(model,
|
||||||
|
field.name,
|
||||||
|
mem.make_any(ptr, field.type.id),
|
||||||
|
args_tag)
|
||||||
|
|
||||||
|
if len(error) > 0 {
|
||||||
|
// The program reported an error message.
|
||||||
|
return Validation_Error { error }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
@require import "core:container/bit_array"
|
||||||
|
@require import "core:fmt"
|
||||||
|
|
||||||
|
Parsing_Style :: enum {
|
||||||
|
// Odin-style: `-flag`, `-flag:option`, `-map:key=value`
|
||||||
|
Odin,
|
||||||
|
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument`
|
||||||
|
Unix,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parse a slice of command-line arguments into an annotated struct.
|
||||||
|
|
||||||
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
By default, this proc will only allocate memory outside of its lifetime if it
|
||||||
|
has to append to a dynamic array, set a map value, or set a cstring.
|
||||||
|
|
||||||
|
The program is expected to free any allocations on `model` as a result of parsing.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- model: A pointer to an annotated struct with flag definitions.
|
||||||
|
- args: A slice of strings, usually `os.args[1:]`.
|
||||||
|
- style: The argument parsing style.
|
||||||
|
- validate_args: If `true`, will ensure that all required arguments are set if no errors occurred.
|
||||||
|
- strict: If `true`, will return on first error. Otherwise, parsing continues.
|
||||||
|
- allocator: (default: context.allocator)
|
||||||
|
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- error: A union of errors; parsing, file open, a help request, or validation.
|
||||||
|
*/
|
||||||
|
@(optimization_mode="size")
|
||||||
|
parse :: proc(
|
||||||
|
model: ^$T,
|
||||||
|
args: []string,
|
||||||
|
style: Parsing_Style = .Odin,
|
||||||
|
validate_args: bool = true,
|
||||||
|
strict: bool = true,
|
||||||
|
allocator := context.allocator,
|
||||||
|
loc := #caller_location,
|
||||||
|
) -> (error: Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
validate_structure(model^, style, loc)
|
||||||
|
|
||||||
|
parser: Parser
|
||||||
|
defer {
|
||||||
|
bit_array.destroy(&parser.filled_pos)
|
||||||
|
bit_array.destroy(&parser.fields_set)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch style {
|
||||||
|
case .Odin:
|
||||||
|
for arg in args {
|
||||||
|
error = parse_one_odin_arg(model, &parser, arg)
|
||||||
|
if strict && error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .Unix:
|
||||||
|
// Support for `-flag argument (repeating-argument ...)`
|
||||||
|
future_args: int
|
||||||
|
current_flag: string
|
||||||
|
|
||||||
|
for i := 0; i < len(args); i += 1 {
|
||||||
|
#no_bounds_check arg := args[i]
|
||||||
|
future_args, current_flag, error = parse_one_unix_arg(model, &parser, arg)
|
||||||
|
if strict && error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for starting_future_args := future_args; future_args > 0; future_args -= 1 {
|
||||||
|
i += 1
|
||||||
|
if i == len(args) {
|
||||||
|
if future_args == starting_future_args {
|
||||||
|
return Parse_Error {
|
||||||
|
.No_Value,
|
||||||
|
fmt.tprintf("Expected a value for `%s` but none was given.", current_flag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
#no_bounds_check arg = args[i]
|
||||||
|
|
||||||
|
error = set_option(model, &parser, current_flag, arg)
|
||||||
|
if strict && error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if error == nil && validate_args {
|
||||||
|
return validate_arguments(model, &parser)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Handle setting custom data types.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- data: A raw pointer to the field where the data will go.
|
||||||
|
- data_type: Type information on the underlying field.
|
||||||
|
- unparsed_value: The unparsed string that the flag is being set to.
|
||||||
|
- args_tag: The `args` tag from the struct's field.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- error: An error message, or an empty string if no error occurred.
|
||||||
|
- handled: A boolean indicating if the setter handles this type.
|
||||||
|
- alloc_error: If an allocation error occurred, return it here.
|
||||||
|
*/
|
||||||
|
Custom_Type_Setter :: #type proc(
|
||||||
|
data: rawptr,
|
||||||
|
data_type: typeid,
|
||||||
|
unparsed_value: string,
|
||||||
|
args_tag: string,
|
||||||
|
) -> (
|
||||||
|
error: string,
|
||||||
|
handled: bool,
|
||||||
|
alloc_error: runtime.Allocator_Error,
|
||||||
|
)
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
global_custom_type_setter: Custom_Type_Setter
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the global custom type setter.
|
||||||
|
|
||||||
|
Note that only one can be active at a time.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- setter: The type setter. Pass `nil` to disable any previously set setter.
|
||||||
|
*/
|
||||||
|
register_type_setter :: proc(setter: Custom_Type_Setter) {
|
||||||
|
global_custom_type_setter = setter
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user