mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-07 20:11:37 -07:00
Compare commits
1270 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 74458ab096 | |||
| c39b1a31db | |||
| 635c7fa153 | |||
| b7ac0a9e8d | |||
| 3f3ae4b2b6 | |||
| c2423dc07f | |||
| 1296630160 | |||
| 63eec25044 | |||
| 7a9b0731cf | |||
| d45661c405 | |||
| 002bec256a | |||
| 000861cba8 | |||
| 36473b2774 | |||
| 4188f50105 | |||
| 3e3b9ae2df | |||
| e89f0de232 | |||
| 4858e16a11 | |||
| 902a6db0e1 | |||
| 19ae6122c7 | |||
| b82b91ea08 | |||
| 636f0d7063 | |||
| ed73441a4c | |||
| 4c2be6cd49 | |||
| f3f51bd643 | |||
| 4c5672119a | |||
| 8482f943ea | |||
| 15aaf7dfa0 | |||
| 768abf83f6 | |||
| ca76d53452 | |||
| b0904d6598 | |||
| b0a09f7b9e | |||
| eb4891bcc8 | |||
| 803fd8f037 | |||
| 67bdb5b1a3 | |||
| acc635b535 | |||
| 4e8ce87792 | |||
| 2c8daa25dc | |||
| 054ee0a8b5 | |||
| d0cadaf1a6 | |||
| 317db2758a | |||
| 25102d4792 | |||
| d39f1c461e | |||
| 7a6fc3a93b | |||
| 83c002c197 | |||
| fc47b5dee0 | |||
| 6c2e0b09ba | |||
| 3d4698debe | |||
| 4a25cfb27c | |||
| 294bd6a446 | |||
| d0109db23b | |||
| ee3ee66aae | |||
| f74e281efa | |||
| c0cd02883f | |||
| d9adb0fd6b | |||
| 6363013dd8 | |||
| 934131abf8 | |||
| 4e5337412a | |||
| 00f2e911a7 | |||
| c82d7d3d87 | |||
| ecfea027a0 | |||
| 96be494730 | |||
| 12c8db927b | |||
| 027ea587fc | |||
| 026900c7f0 | |||
| ffa87f55c4 | |||
| c9eed04b51 | |||
| b50b6b9f33 | |||
| 8fd5bef0bd | |||
| d6b49994a2 | |||
| 776927709b | |||
| 96e033b22c | |||
| 3469178dc1 | |||
| af1b3b6368 | |||
| d56789e5a7 | |||
| aeacf3a9d8 | |||
| 4ba486baa2 | |||
| f1ffd90294 | |||
| 777aa8b118 | |||
| cb9e16f4df | |||
| 2908923db9 | |||
| 8c1dfabb6b | |||
| 7fe36de069 | |||
| 27d556735a | |||
| b70d211f21 | |||
| b3e3b6c656 | |||
| 1734286252 | |||
| c8c076f970 | |||
| e40b3ad338 | |||
| afec321db2 | |||
| 6e9f9e6f3c | |||
| f504b200a9 | |||
| 82e840a0ca | |||
| 82765ca96e | |||
| 5387ec5f29 | |||
| f2908cbc5a | |||
| 5337b0b471 | |||
| e51afc3509 | |||
| 2c004dbcc9 | |||
| e128ed7d26 | |||
| 9064ebfe97 | |||
| 4f7bbe0e4a | |||
| 208f168564 | |||
| 737bccbd5e | |||
| f5431a046d | |||
| 5a9422b6bc | |||
| d30198c99a | |||
| 0c8d59dd20 | |||
| a460d140fe | |||
| 881d18ee88 | |||
| d73a4aa34b | |||
| 5c298c1501 | |||
| a83ca2120e | |||
| 81799f7f78 | |||
| 7973f7e750 | |||
| 3dc62a67e0 | |||
| 081e36c909 | |||
| 3a1d364f59 | |||
| e50648279d | |||
| 929af320da | |||
| 8e7c7eeeba | |||
| b739044e69 | |||
| 9e0107c9fc | |||
| 107e016508 | |||
| 22d16c20f8 | |||
| 697c839c84 | |||
| de8bd88d2a | |||
| 595efba747 | |||
| c041d15569 | |||
| 57b20e634b | |||
| e285796fc1 | |||
| a19494d3a7 | |||
| d2a362fd52 | |||
| 0f3562ef02 | |||
| 03f683f9e7 | |||
| cecadce86d | |||
| a7c3906003 | |||
| 70dc0c15fd | |||
| 9eeed9d5bd | |||
| a054c2934e | |||
| 38102f14c1 | |||
| 0997df4fcf | |||
| a5a56e061c | |||
| 8b007ad55a | |||
| 57dd5ec4db | |||
| 5b621d5be1 | |||
| 7aee762f3a | |||
| 4ee50c5a35 | |||
| 28f440dd9e | |||
| 43b78e51a4 | |||
| 84f9fb706b | |||
| 812823cad8 | |||
| 0655260378 | |||
| cfc3723879 | |||
| 4c3281b3f2 | |||
| ff94c605e0 | |||
| cb0a59bb2c | |||
| 076700bd22 | |||
| bcccc8338f | |||
| 425dec8bb8 | |||
| 838554460b | |||
| 659c3c528d | |||
| 60aeab3c38 | |||
| 5e3cf45df3 | |||
| 4633591918 | |||
| 0e6a8b7c72 | |||
| 147848ca20 | |||
| cde002c579 | |||
| f23d93ba89 | |||
| c97a8418dc | |||
| 4aca9372a6 | |||
| 4912ecc3ea | |||
| c1c8ceafc2 | |||
| 9c0a3b6c60 | |||
| 7b539e3025 | |||
| b2b0043875 | |||
| 53e0d182af | |||
| a6fa41e290 | |||
| edba99d636 | |||
| 35674959f2 | |||
| dc8b7a0eb8 | |||
| 8d1f46d837 | |||
| a2117d23b2 | |||
| a58e4d0359 | |||
| 576914aee1 | |||
| 8171f8209a | |||
| 64ff05303c | |||
| 6caab6225d | |||
| 326411498a | |||
| d50fcf0020 | |||
| d354d36a3b | |||
| 483a72ac61 | |||
| dbec4b0d0e | |||
| 4cb489b9e4 | |||
| 28ec50d567 | |||
| 73beed0477 | |||
| e0ecdd4b24 | |||
| 0cb1a578d0 | |||
| 5168cf03a9 | |||
| b886ae6515 | |||
| 277a973b98 | |||
| 0ec4d97bfd | |||
| e6236e5c3e | |||
| e201280844 | |||
| 8e50a6c61b | |||
| 7e6f5f89d0 | |||
| 97acc57649 | |||
| 83c8c48ed7 | |||
| 0815b4d59f | |||
| a0135080b3 | |||
| f45e8e5d47 | |||
| 98ba4beede | |||
| a9304f2fef | |||
| a0697ab057 | |||
| 2e895c72d3 | |||
| 674ebe395f | |||
| 96eecaab54 | |||
| b1ae5bc9fe | |||
| 7258588ed5 | |||
| d913155972 | |||
| 9746e25784 | |||
| d26cfd2141 | |||
| 21f2c06f4b | |||
| 727a25f41f | |||
| 3f27cb2309 | |||
| f213622982 | |||
| 4aad835a66 | |||
| 4af8a64580 | |||
| c9c3611b1d | |||
| 220dfd7440 | |||
| bce8819ed5 | |||
| 5f2b220a85 | |||
| f174c805a9 | |||
| 25869b7504 | |||
| ecd81e8a53 | |||
| d7f9f7f170 | |||
| 08f5259d77 | |||
| a9f744cb64 | |||
| b02e42c6dc | |||
| cb0273b5d7 | |||
| 8dbf45a65a | |||
| 9f64de9568 | |||
| 0ebe9ba487 | |||
| efe00e1aa6 | |||
| 2bdbce55f9 | |||
| 9614ca92f0 | |||
| d30e59f539 | |||
| a3afe617c2 | |||
| 69daac583e | |||
| b28d4b753b | |||
| e6ab4f4856 | |||
| c8ab1b7ee1 | |||
| 9f10487678 | |||
| 2542983d70 | |||
| d492fb3501 | |||
| 1829aa1638 | |||
| 4cb4173ced | |||
| 00e704b216 | |||
| 227ee0f705 | |||
| 17f47a7ab0 | |||
| 4e8bc0786d | |||
| 3d3ccf061f | |||
| 3a8adc6721 | |||
| e1748a5dd1 | |||
| 59b4c889d3 | |||
| b6408d1b3f | |||
| 3db3047f47 | |||
| 7420fbd95b | |||
| 7c990b3833 | |||
| 9c059f1a12 | |||
| fb167d1d0a | |||
| 0992239d86 | |||
| 9eb3da0474 | |||
| e91f8feedf | |||
| 22a0c3fce1 | |||
| 6c7e5748a8 | |||
| 0b0c6da8b0 | |||
| 78826071c0 | |||
| e61b73d7ad | |||
| f886632bf1 | |||
| eafa5098aa | |||
| 0571b80d37 | |||
| 80c10644dd | |||
| 041625381c | |||
| 48f56d728b | |||
| 872d391cfb | |||
| 3e6ec65dd9 | |||
| 157c87b2a2 | |||
| d3081bd889 | |||
| 2ae5bf4395 | |||
| f28547cae1 | |||
| 5332705e31 | |||
| bfb082cda4 | |||
| 37d04198ab | |||
| ae9d540c1c | |||
| c90b7c38f1 | |||
| 9e376fbda7 | |||
| 00739bf06d | |||
| e8148055ad | |||
| dd0a20ab45 | |||
| babbc304b8 | |||
| a8b44f33bd | |||
| bd48561688 | |||
| 23842a8950 | |||
| 1676c643df | |||
| e2bfb024de | |||
| 4d06a54c0c | |||
| 0a8e6169d7 | |||
| 04ae87eaef | |||
| b313d09c2c | |||
| c0d2359a91 | |||
| 51a2f09032 | |||
| f60e8031f2 | |||
| ea42613fec | |||
| b89bb87759 | |||
| 2cbb3d5a24 | |||
| 9e288b7ce8 | |||
| 776b48c10d | |||
| 199dae6cd5 | |||
| e58f45bef7 | |||
| abe122ecb7 | |||
| f8744d87b0 | |||
| 3e7f6b8751 | |||
| 6ffe814ca7 | |||
| eec9be71f6 | |||
| 888913c739 | |||
| 33d96fd28a | |||
| f1e8738af2 | |||
| 9c52a11b1b | |||
| d89c4606bd | |||
| c6903fbcd5 | |||
| 13c8149046 | |||
| 665db0f778 | |||
| 173286de65 | |||
| f2ecda8fec | |||
| 6f1222e9bf | |||
| 84a424f21e | |||
| 9b7710488b | |||
| 9f413862e9 | |||
| b8802d7df7 | |||
| b13dad02a4 | |||
| f045f8d805 | |||
| 30b7c8ad66 | |||
| 89222a0ab2 | |||
| 21e637d2b3 | |||
| 6c196931d2 | |||
| d7195b0798 | |||
| b40998de9e | |||
| 8c0c327df9 | |||
| 50cbb8a1fc | |||
| ff9d058392 | |||
| 1acc8f438b | |||
| c53426fcb4 | |||
| 5187db525f | |||
| a8bd340267 | |||
| ca1f419dc2 | |||
| fae60a6b88 | |||
| 108558ddfc | |||
| 65b8cfae82 | |||
| 4055c31cf0 | |||
| e88af4e458 | |||
| 79eb7b52d9 | |||
| 83b2bf44c4 | |||
| 670f18ad1b | |||
| 9a81716936 | |||
| f013499eea | |||
| d04f732e68 | |||
| 35fd8e7f68 | |||
| 38ff2a3ed9 | |||
| bd502d16bc | |||
| a11e17fbc3 | |||
| 8b3b659433 | |||
| f33228fd6e | |||
| df5b693de8 | |||
| ab98108441 | |||
| 6f80174f84 | |||
| 0ebe81fce2 | |||
| c75dd14308 | |||
| c166b6a21d | |||
| 6ed5cbee12 | |||
| 5b200ccdf8 | |||
| cf0f0c4b31 | |||
| 1399ecb41c | |||
| 339d6cfd41 | |||
| 7bded4f189 | |||
| c7269b9ef0 | |||
| 85688015aa | |||
| 3c6cc575c6 | |||
| 0564cb6483 | |||
| 4be92c7eb8 | |||
| 88e9eb7d0c | |||
| d19fc54c3d | |||
| 5d4291d9fa | |||
| b70cd03e9e | |||
| e91e5e1fe9 | |||
| ae57a49915 | |||
| bfcb527b42 | |||
| a343fb171d | |||
| fa2296a124 | |||
| a996cfc536 | |||
| c1d55b9296 | |||
| 5c18cca1ca | |||
| 78e6cd0c60 | |||
| 176954a6d8 | |||
| ad6b3bd95f | |||
| 762895bc45 | |||
| 592e9afa5f | |||
| a2e0373934 | |||
| fb49841b1d | |||
| 01ea0d6f1e | |||
| bb7f291f5f | |||
| 174fa9b490 | |||
| dda2ed290a | |||
| ee9908b09e | |||
| 66de1856e3 | |||
| ba5f7c4e2a | |||
| 487bd3d942 | |||
| 4fac7a8f27 | |||
| 25dae06b6a | |||
| a1f15c2c69 | |||
| 516f6647b4 | |||
| a7840d50e2 | |||
| cb10af08cb | |||
| 4e49d24df9 | |||
| 68222cb8ab | |||
| 912d29af83 | |||
| f3868ac932 | |||
| 5b42dd7707 | |||
| 51707032d1 | |||
| a0babefe55 | |||
| f3aefbc443 | |||
| a6c779b50e | |||
| 9fa41a97b9 | |||
| cef022539e | |||
| f6dfa33697 | |||
| bc3bf939e0 | |||
| f5e5eac3b9 | |||
| d50786bd30 | |||
| 0ccbea17aa | |||
| 136d50a745 | |||
| babfba5e8f | |||
| 846f8377b2 | |||
| 77d4409549 | |||
| 7f3540b7f5 | |||
| 3ad2cde833 | |||
| 910799cc5f | |||
| 6c0192083e | |||
| c60d7842cd | |||
| d7eaf0f87b | |||
| bb4329711c | |||
| 618d3bf62f | |||
| cf8a4b9812 | |||
| 4db533ff71 | |||
| f28e3276e7 | |||
| 026540040d | |||
| 8518d3b232 | |||
| 1c1f5e2231 | |||
| bdedfb1071 | |||
| 92ed9e0b94 | |||
| 5c10b35df7 | |||
| 0668811397 | |||
| 6bb6344208 | |||
| 2f7bd154a2 | |||
| 20c5033b38 | |||
| 20fe6d102a | |||
| 4e30a64d9f | |||
| c48ef7d70b | |||
| e079a7009d | |||
| f383bf3136 | |||
| 609ddf28b7 | |||
| 34f1bda57c | |||
| f137b927b6 | |||
| 2185dada56 | |||
| 0b08080119 | |||
| 432b2b19e9 | |||
| 952f294bce | |||
| c23274adb0 | |||
| 833f9dd037 | |||
| 1ff8b97dae | |||
| 70451f9335 | |||
| 1f438d4e6c | |||
| 421d45a7a7 | |||
| 20e7b5c88a | |||
| 7092273a8f | |||
| d0e8a735ba | |||
| 208226dba2 | |||
| f308f37ba1 | |||
| c2610cb75e | |||
| 59e9df2609 | |||
| 66b5a35ec3 | |||
| f3f6c12a7c | |||
| e331b0647e | |||
| 35502816c7 | |||
| 7ec0236fbf | |||
| 0fd43c1a0b | |||
| 06337129d8 | |||
| 337780497d | |||
| 10deb2e88b | |||
| b95ca80f85 | |||
| 83d880a94a | |||
| cde6a2f7a5 | |||
| c2f5cbdeb4 | |||
| 8e57511ffa | |||
| 12d19d21c4 | |||
| 7002c94a63 | |||
| 57e69ea392 | |||
| 09f936b04d | |||
| 140c00aa0c | |||
| 808ea30b48 | |||
| 63d6c08d90 | |||
| 10e4de3c01 | |||
| 8ac12886ed | |||
| 63cc8a80a0 | |||
| 1549d01bf7 | |||
| b168bf9460 | |||
| 0203bb657e | |||
| 53f0c6ef1a | |||
| 4c4480104d | |||
| 5c72974167 | |||
| f21e9ee712 | |||
| 81dd727f75 | |||
| 3b54015e80 | |||
| b032d5af87 | |||
| 209a155608 | |||
| d8e77cd738 | |||
| 95d4ce4aa3 | |||
| 39393cca92 | |||
| acadbe050c | |||
| 8fcf2f5dca | |||
| 831a86599e | |||
| 233b32fd3e | |||
| 3c5124ce68 | |||
| a8d78660ee | |||
| cc1df9591f | |||
| 54a326f046 | |||
| 3d9d85121d | |||
| a31d23a32a | |||
| 084f431aa5 | |||
| 7002f0a7d7 | |||
| 3ec70c5517 | |||
| d9f293b281 | |||
| 8c1499dbc2 | |||
| 7d2eedee73 | |||
| eba35a8f7d | |||
| e967f2ca2c | |||
| 438713af20 | |||
| 568869077e | |||
| f25a3f2a7d | |||
| 5609221831 | |||
| f3432e6bb5 | |||
| 43b350c590 | |||
| c2c66aad60 | |||
| d7681d5b06 | |||
| c902615192 | |||
| 2895830ce6 | |||
| 1eef9552b4 | |||
| 577fa2d29b | |||
| 72fcf16a39 | |||
| b9d523e0b2 | |||
| f3d225ca4f | |||
| d84d2f85e8 | |||
| 10f1d8c604 | |||
| 184d1c57b1 | |||
| dfbe68bcfe | |||
| 3049e07f72 | |||
| da54d0ec8c | |||
| b57edb89eb | |||
| e43eccbb91 | |||
| e48f41165c | |||
| 9eb4cbcbd2 | |||
| 2612f241c9 | |||
| 0f1153fae2 | |||
| b84561f2b8 | |||
| f7e78e2671 | |||
| d10a2bc5d5 | |||
| 94fda3d48d | |||
| 5cf4f565d6 | |||
| c20b5cbd10 | |||
| 115612620f | |||
| a5bf3b0bc5 | |||
| 5c647e2f61 | |||
| 06884da42b | |||
| 6e7179d8f3 | |||
| e85f1dd9fb | |||
| 9ac94e621b | |||
| db8d119cad | |||
| 836c325021 | |||
| 3bb31093fa | |||
| 214b43974d | |||
| 55556aea77 | |||
| 223897d224 | |||
| 542e45de26 | |||
| 1fa9488a4d | |||
| b1196bd659 | |||
| 57167be2a6 | |||
| 846930a07f | |||
| 0cc67ff5e3 | |||
| a86574da84 | |||
| 5a6836ab99 | |||
| 43432f92ec | |||
| d1499f3f78 | |||
| fff23e2bbb | |||
| 33895b6d92 | |||
| e10105a780 | |||
| 5451c9672d | |||
| 4eba2bb8d9 | |||
| 2a58bceb56 | |||
| fdcf08410c | |||
| 5142955f00 | |||
| 500150b12a | |||
| 50ddd8dd26 | |||
| 6c6de2a07d | |||
| 01912b6ba5 | |||
| be2c7b5c9b | |||
| ed60ed3bae | |||
| 23cb96de02 | |||
| a2c771876e | |||
| b5b329378f | |||
| f7b18cd86e | |||
| d74e4b427d | |||
| 22dc020647 | |||
| e8485ee7e7 | |||
| c516fb947f | |||
| 3aa0a733f3 | |||
| 4e080057fb | |||
| 9c1f270bd5 | |||
| e46d87b221 | |||
| 5bc866e420 | |||
| 5af7004f44 | |||
| 01e8e682c0 | |||
| 2ef6544ca2 | |||
| 9921ac01cc | |||
| 7057f5fc11 | |||
| f17a9dd5e7 | |||
| ec3394b8da | |||
| 0cca42a1f4 | |||
| 85edcf9cc2 | |||
| 3b842ffe29 | |||
| 6c0e2e2a53 | |||
| 42371f7aea | |||
| 286f782e5e | |||
| 58fc305b11 | |||
| 7e0c359f99 | |||
| f50399e394 | |||
| 7bc21c6691 | |||
| dd56c85e55 | |||
| 9e2a847ebc | |||
| daef39a206 | |||
| 5d496cdcda | |||
| f27f595549 | |||
| 536e0a8c29 | |||
| bc18310107 | |||
| 3fdb3dd767 | |||
| d224679619 | |||
| 2dd181e663 | |||
| f002857edc | |||
| 97739da85a | |||
| 6c14586fff | |||
| 0c45a46aab | |||
| d1fc9d3073 | |||
| 2fb351bf04 | |||
| dc832ad49f | |||
| eef44b11f3 | |||
| bb4f108487 | |||
| ccb38c3dc6 | |||
| cc81057d21 | |||
| 8b4b81fdeb | |||
| 4cb46f5631 | |||
| f4ad4c7aa6 | |||
| 37dda30c49 | |||
| 8fb718245a | |||
| a4cb6f96ea | |||
| 56e3b7cb7d | |||
| ae1f5d2181 | |||
| b4df272eb5 | |||
| dca2fbccff | |||
| d48d3bfa87 | |||
| 8559790bd8 | |||
| 37c6279031 | |||
| 0d4e710b96 | |||
| 205aa10b88 | |||
| 6f1cc3946b | |||
| 253ecd55a0 | |||
| c9e31dc90d | |||
| 3d06322d4a | |||
| 85e6efdf16 | |||
| 6b89ff43ea | |||
| 4cdc55af91 | |||
| 4b289f904c | |||
| 53c70da0b8 | |||
| 6f20b5bb59 | |||
| 96ab17ecfc | |||
| 18bde22b26 | |||
| e61aad925b | |||
| 5d190b15d7 | |||
| fe442d7c0c | |||
| 97d1a67871 | |||
| bac96cf2ad | |||
| 7e0cc0af25 | |||
| 1d29d9be25 | |||
| 0e91e63043 | |||
| 0cf37bde8b | |||
| 173799527a | |||
| 5931e2383b | |||
| e4743b15b1 | |||
| 9f95d6fa65 | |||
| 982a1aebb3 | |||
| cec049b7d3 | |||
| dc323cfa1d | |||
| 0afa226a93 | |||
| 1146604344 | |||
| 89c2e1a5fa | |||
| 971d498e79 | |||
| 6e7a50c02f | |||
| 6aaab4988e | |||
| d9b0c05acf | |||
| 47f637d23b | |||
| 59f55a2119 | |||
| 8bac82320f | |||
| 14bf20320a | |||
| b99940f33a | |||
| 81495068b9 | |||
| 6985181961 | |||
| 97717d65ef | |||
| 8023c8abc7 | |||
| 2d3f59d9a7 | |||
| be8de4a1ff | |||
| 18ad6c33ef | |||
| 0e27b27b81 | |||
| 10a311092b | |||
| 18463d68d4 | |||
| 74d3bcec05 | |||
| df233aee94 | |||
| 335b724209 | |||
| b2fdb881eb | |||
| 6ade9acc4d | |||
| 2081f8fcd6 | |||
| 964ab4814c | |||
| 7a032cf9f9 | |||
| 694c13fe86 | |||
| 8bd16c32f3 | |||
| be9b935953 | |||
| 9e69452327 | |||
| 234d529867 | |||
| dd8b71e353 | |||
| d24bebdb9e | |||
| 41a18f078d | |||
| 3978e7e1ca | |||
| b758c696f2 | |||
| d6a8216ce4 | |||
| 2720f64c06 | |||
| de2ebdd5cc | |||
| 4e39629a9a | |||
| 78a8da5fea | |||
| d5886c1572 | |||
| 7cc759a855 | |||
| dd6337224f | |||
| d2bac0c35e | |||
| 8c7f3fd1e6 | |||
| ae3deea153 | |||
| 40bea95fb0 | |||
| 0ad448f1c7 | |||
| 4911df9f99 | |||
| a223340c44 | |||
| 9c9c2b483c | |||
| 819345caa6 | |||
| 11ceb3973d | |||
| 36263399a0 | |||
| ff0f0c447f | |||
| aa681932a9 | |||
| 09e1c0fa27 | |||
| 957ef8e8fe | |||
| 2e11a8da5b | |||
| e9cfcf9ecc | |||
| 789ab99c4d | |||
| 0297db6f2e | |||
| 9ce64916e6 | |||
| 1289c96e2c | |||
| ba23bfb7b9 | |||
| 2fae6eda23 | |||
| e53ba3b116 | |||
| 1ed84a064b | |||
| 79019c7a09 | |||
| a1002e6960 | |||
| 62139cb5a4 | |||
| 127b0ba65e | |||
| 80878264b6 | |||
| 9fcba99ca2 | |||
| 03c9212600 | |||
| 5650087aa3 | |||
| 67689ecb21 | |||
| cd13dedb36 | |||
| 10cd294cf2 | |||
| d6cfb60506 | |||
| df0df73540 | |||
| 33f1418dec | |||
| 305510bea0 | |||
| beb698f31d | |||
| 6df21d6a9f | |||
| c5982e52d5 | |||
| bd73b2845b | |||
| da0f722aad | |||
| 904f0407f8 | |||
| fbbb0d7610 | |||
| 3a9b0a22e7 | |||
| c4e0d1efa1 | |||
| 9349dfba8f | |||
| 9692496989 | |||
| 5bc8164274 | |||
| a6cef2e50e | |||
| d262eda91c | |||
| 40f0f5ad8d | |||
| 1c03e68057 | |||
| f1c1cfb6d2 | |||
| ba5e33bc35 | |||
| 80df9fbc65 | |||
| b68ab0dd6d | |||
| 9cf7a31068 | |||
| 5e11ad2e1e | |||
| 07d1a42768 | |||
| ec8221cb5d | |||
| a5342a0126 | |||
| c81fd2e5dd | |||
| 3bd7122959 | |||
| 530401e5ee | |||
| a412d34574 | |||
| ee67a0b9a1 | |||
| ca6a1db757 | |||
| 1fb76ad768 | |||
| e01662c139 | |||
| 63331ef731 | |||
| a40a53b104 | |||
| 9f8d90f466 | |||
| 3d2856db31 | |||
| f4723aea4c | |||
| 76d48b38d3 | |||
| 3cab2592c3 | |||
| 5422a3b17e | |||
| b44b6e7e50 | |||
| 849efff070 | |||
| b022167df1 | |||
| ac9a358c65 | |||
| e799476f90 | |||
| b4f8efcbe6 | |||
| f026753692 | |||
| 71b1cce517 | |||
| d8f0da164b | |||
| 591732f347 | |||
| eee97f7f62 | |||
| 3dd9da1b66 | |||
| e8c0be23f2 | |||
| a30b9b17b3 | |||
| 29b2c04766 | |||
| d869ba7bcd | |||
| 581255bf23 | |||
| b51358a01c | |||
| 323e7a2d02 | |||
| 7654afc2db | |||
| ded8342f3f | |||
| 240fb9b953 | |||
| 4997a43763 | |||
| aa4eb35671 | |||
| d99ba9c073 | |||
| fdd24f787f | |||
| b6abd691f4 | |||
| 8d370fabdd | |||
| df4a0c62ad | |||
| e3e3309a9b | |||
| 7428e52264 | |||
| 20b70c3b7b | |||
| 4247ba67ed | |||
| e738e93da0 | |||
| b78f3a8069 | |||
| 939973acd7 | |||
| ed4cb72b19 | |||
| 70cbffd58b | |||
| 6d0ba8d189 | |||
| b6f3fa6ee1 | |||
| 91037766d2 | |||
| 8bf73950fa | |||
| 4f4793817c | |||
| 0a0440a6e8 | |||
| b9dc81d808 | |||
| 8e7ddccf00 | |||
| a5773f1657 | |||
| 44316401c9 | |||
| b05fbaacda | |||
| 1b4d5b73ab | |||
| d3fbf36df7 | |||
| de819cff94 | |||
| cfae39c29d | |||
| 989641a616 | |||
| fc3f62e3ed | |||
| 6b7c04e046 | |||
| cfeb16f917 | |||
| 9a2d9002e6 | |||
| ea0b02d9b9 | |||
| 0d621511e5 | |||
| e53c858855 | |||
| 8a9f7fc684 | |||
| 51db46551e | |||
| 600b79276a | |||
| a040be957f | |||
| f6fa553572 | |||
| 8310436350 | |||
| f92ffe60e7 | |||
| de72754d7a | |||
| bf712e9355 | |||
| ab9457346d | |||
| 15b440c4f1 | |||
| 1a2c36e482 | |||
| 56737c1431 | |||
| 9ae566adcc | |||
| f2f1330238 | |||
| c4a7739d13 | |||
| 8a8b5c753f | |||
| ad90f416a5 | |||
| 698fcb7813 | |||
| aadb4db211 | |||
| 426a6a9528 | |||
| 50b9c48609 | |||
| 767ed21bfe | |||
| bb9165edd2 | |||
| ad0a413b40 | |||
| 7f6c6945ae | |||
| ca549939f3 | |||
| cdb003bf23 | |||
| a4d2ff05a9 | |||
| 48012ec73c | |||
| e7dc00b758 | |||
| ef1fbbbce6 | |||
| 2a59aebe5b | |||
| 59025b75ba | |||
| 2289b7a33d | |||
| 79ec172797 | |||
| 2e6ad2a711 | |||
| 6be05f315d | |||
| b5aa50aaa4 | |||
| ab91fa6ad5 | |||
| 376327c87b | |||
| f8f91e52e0 | |||
| f4daf46ff4 | |||
| e80bee6867 | |||
| d10d54710c | |||
| 1ec997461d | |||
| ec5fc10988 | |||
| a232c0888c | |||
| cb5a6b531a | |||
| c930a3b4c8 | |||
| 4c14e92952 | |||
| 850d4a1e1b | |||
| 88de3a1c06 | |||
| dc012ed6dd | |||
| c21c993646 | |||
| c3a292a8c7 | |||
| 4044a577cc | |||
| 581d53b96b | |||
| 2bc89260f1 | |||
| c78b83f142 | |||
| e28525e28c | |||
| 73f9d12d47 | |||
| b21cf05d44 | |||
| 107bede9fd | |||
| 75cbb09744 | |||
| 76cf667a29 | |||
| 78ee97ec74 | |||
| c686133172 | |||
| bfcd7a35bf | |||
| 4484a3433d | |||
| 0c4f905d82 | |||
| 77de7ebde5 | |||
| 2ec3fa93b4 | |||
| 9f2d710c35 | |||
| 22b961ea53 | |||
| 9ea45d35db | |||
| 06e8476efc | |||
| 94dbac9a64 | |||
| 97a183f412 | |||
| b2f5b73532 | |||
| 1eac3482a6 | |||
| 6636376a81 | |||
| ed6bf28004 | |||
| 6bc0c611ab | |||
| ba1930eb01 | |||
| 203382461b | |||
| 6456618891 | |||
| 4eb4ae6305 | |||
| 72ae061769 | |||
| 46161f7e19 | |||
| 0c55596f0f | |||
| 5f3bfa66c5 | |||
| 561b725b0e | |||
| 3a4630e6b4 | |||
| abf0fd7efc | |||
| a632db3618 | |||
| a3c81374be | |||
| 6a3ec5eb36 | |||
| 740ba6ad47 | |||
| df32b5b46c | |||
| 085fa199ea | |||
| 412c9a99d5 | |||
| 6e701ef36d | |||
| 24c48d22bc | |||
| 3cb8bb6672 | |||
| b1c2c0ea7a | |||
| 2c498c132e | |||
| 880d330cca | |||
| a2a503847a | |||
| 58f4d533b7 | |||
| d2ff6f424d | |||
| 92f985abd5 | |||
| 3ce17607c6 | |||
| 76277f83c1 | |||
| 2b7529977e | |||
| f4125d2d88 | |||
| 87e50e5e4d | |||
| 86a1c34c3a | |||
| 3f3cc342b4 | |||
| 3bf820cf99 | |||
| f2b4087d80 | |||
| 3b6d72bb94 | |||
| 9080fa4a9d | |||
| 5616ff9a40 | |||
| 73b81184fa | |||
| f8d3f86d8b | |||
| 2f9a410a45 | |||
| 8661457512 | |||
| 5d7b92d391 | |||
| 1d8bc3e917 | |||
| a2ad16b609 | |||
| a3b1ac3133 | |||
| e7b96cf286 | |||
| 01181517dc | |||
| f702c782f1 | |||
| 7203560b06 | |||
| 1baeb9406f | |||
| 17e36bd5e1 | |||
| b6b3377786 | |||
| 13cb894b30 | |||
| 3f935bea25 | |||
| 3e66eec735 | |||
| 277e0ac124 | |||
| 2ccfaa7d4e | |||
| 4bd5de34ea | |||
| 374e71e9b0 | |||
| 07bb93bb5d | |||
| e252d3bedf | |||
| b9efd09d17 | |||
| 507b718cb3 | |||
| 82f9cbecf8 | |||
| a8ac59a6e7 | |||
| 3d389ee028 | |||
| 10c5825715 | |||
| f89ebce807 | |||
| 64601ac439 | |||
| edce27812f | |||
| 193822b45d | |||
| 43640a8b59 | |||
| 0446d9721b | |||
| bae13b6387 | |||
| e48c0eee74 | |||
| 47e9857eb7 | |||
| 559fcfa291 | |||
| 84cee5d9d5 | |||
| 6d354524e2 | |||
| ae6441182d | |||
| a68f0b2d72 | |||
| fdbbf24271 | |||
| df233f72a9 | |||
| bff5a67f79 | |||
| 4f9df50dc1 | |||
| 34187424b8 | |||
| 50503cb405 | |||
| 5e04ddd653 | |||
| 4f5203e661 | |||
| d9ca4eb4d6 | |||
| 5534c031b3 | |||
| 19dc84e300 | |||
| a932168f50 | |||
| 36c22393a4 | |||
| 6d6e840bc2 | |||
| 4b1822ade8 | |||
| b21e7e4518 | |||
| 1f4e5e919f | |||
| c293e88f2e | |||
| 1d147ba993 | |||
| 6ea9ba16e7 | |||
| 286549693e | |||
| 34727f99e3 | |||
| ffe6d81ecd | |||
| 8605833781 | |||
| 4474144c24 | |||
| ef3f448861 | |||
| a882260db6 | |||
| 633157f4f8 | |||
| 9fa69c3d3b | |||
| 743a461aa9 | |||
| fc0291d745 | |||
| 77eaf8e1e4 | |||
| a7adb2fb6e | |||
| 036900da51 | |||
| ed4c9335db | |||
| ca67cf032c | |||
| f907516cbd | |||
| c12c7d5370 | |||
| f7c8b40ea2 | |||
| 15f9795ab0 | |||
| 8982ae34e3 | |||
| e6d3e893a5 | |||
| e008b5a160 | |||
| 3da8fa9b27 | |||
| 32ba5e7ad2 | |||
| 52df80dccd | |||
| 7f845bb165 | |||
| 0e6de5673b | |||
| 7a7b87181d | |||
| c6dc517004 | |||
| 0b61215f7b | |||
| b91c0ec715 | |||
| bad295cf69 | |||
| d2bc41a2df | |||
| 7dbcaf792d | |||
| 2652c2d7a5 | |||
| a2250a5d49 | |||
| 7f8a9587e0 | |||
| 1306c53fb1 | |||
| 3bd1ac4c82 | |||
| dc8d28c383 | |||
| 7adaa4dc2b | |||
| 6d1a91f5b3 | |||
| 17eebf338c | |||
| c543ecd64c | |||
| 34a9f55f37 | |||
| 9aea990184 | |||
| d5b0632e4f | |||
| db169a4334 | |||
| f5cc8bd7bf | |||
| 005d52cab7 | |||
| d1477bcfa7 | |||
| 3092fb2ff3 | |||
| 5eebdebec8 | |||
| 8e4d6b3e5d | |||
| ea9c2fed57 | |||
| ba412fd87b | |||
| 0278ac85a0 | |||
| ff60b752bd | |||
| 832003dd4b | |||
| 9848e883c7 | |||
| 64705ddd1d | |||
| 2a41814985 | |||
| 26ffec845b | |||
| 52e60526ef | |||
| 76b10b5f5d | |||
| b94a7a87fa | |||
| 2b43387a9d | |||
| e76a5d8e12 | |||
| 6d7217f37a | |||
| 17dab04422 | |||
| 29e660b16f | |||
| 31959b0751 | |||
| 1f19610fd6 | |||
| 8f897de267 | |||
| 2855ff6df3 | |||
| deed20dea6 | |||
| a6c5143993 | |||
| 758d1e2a03 | |||
| ce057ff755 | |||
| ad719e7c3a | |||
| bff3426d25 | |||
| 4315033220 | |||
| 1cd89b2da3 | |||
| 658a605c75 | |||
| 7e8b9862b9 | |||
| 07062324d7 | |||
| 2e8f2e6dbc | |||
| 1abd95094d | |||
| 913d802e33 | |||
| bee475c38a | |||
| b4ca99ead9 | |||
| dfe2c0a600 | |||
| fad851d80c | |||
| 832961d539 | |||
| 499c657ffa | |||
| e51bb4ef12 | |||
| 8c6f39a68d | |||
| 09f5713cf8 | |||
| 26c0c6a525 | |||
| 6d9f84ba03 | |||
| 8af08f2153 | |||
| 2944969ca0 | |||
| bd1b54e0db | |||
| fcab5508be | |||
| 0b05650366 | |||
| 96e36c7c39 | |||
| 16c6dbcbe5 | |||
| 92a78c83d9 | |||
| 6b2302fa8b | |||
| ee28945e09 | |||
| 3dcea60f5b | |||
| f126e05034 | |||
| 68b74eb7c7 | |||
| 3b4199a669 | |||
| 562901aedf | |||
| 793117ed63 | |||
| 5b783d6376 | |||
| d3f3528d1d | |||
| 3145935d6b | |||
| 25430333ba | |||
| 2ca2b32dd0 | |||
| a5dde78f08 | |||
| fd415f0b45 | |||
| 507722954c | |||
| 524d23d45d | |||
| e9ee6f5291 | |||
| 37b4e0de6c | |||
| 737f440c7f | |||
| cba3f1e374 | |||
| 410b85b5c7 | |||
| 0ae012ba08 | |||
| fc4eb4152c | |||
| 4f3b5d8dcb | |||
| 15d783e920 | |||
| 09e4fff5b1 | |||
| 2d89faa17c | |||
| 882116e358 | |||
| f3adbae1ed | |||
| 278e239973 | |||
| fda803b46a | |||
| 01162e08b5 | |||
| a7ae197a55 | |||
| 37bba4c0a6 | |||
| ab9d1f99fd | |||
| 45124e4d5c | |||
| 32988b0363 | |||
| 4a04a32e0a | |||
| 196bd735d4 | |||
| 493bc653b5 | |||
| 3d209798c9 | |||
| 4b9324ff76 | |||
| 6630d703f8 | |||
| 9c3cdc4620 | |||
| 345032f804 | |||
| 3e5c60f746 | |||
| 0fa487f468 | |||
| 1bec9e5331 | |||
| bea2f36443 | |||
| df23cf47c6 | |||
| ad6ea3d6aa | |||
| a315e7c962 | |||
| 42364f2fce | |||
| 8f600798ef | |||
| f28c268d97 | |||
| f1cff20249 | |||
| 865d88dd56 | |||
| 069c05669f | |||
| 516065d7c2 | |||
| 6e61abc7d0 | |||
| 7ec88d2430 | |||
| d7200f6144 | |||
| d65d6edb0e | |||
| 3d72e80ccf | |||
| 2dd67dba89 | |||
| 5807214406 | |||
| 23baf56c87 | |||
| beff90e1d1 | |||
| ec63d0bbd2 | |||
| 32eab04d66 | |||
| 682783a2aa | |||
| 46a4927aca | |||
| 9b2e67df67 | |||
| b5c828fe4e |
@@ -1,3 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: odin-lang
|
||||
patreon: gingerbill
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: CI
|
||||
on: [push, pull_request]
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
build_linux:
|
||||
@@ -7,9 +7,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Download LLVM, botan
|
||||
run: sudo apt-get install llvm-11 clang-11 llvm libbotan-2-dev botan
|
||||
run: sudo apt-get install llvm-11 clang-11 libbotan-2-dev botan
|
||||
- name: build odin
|
||||
run: make release
|
||||
run: ./build_odin.sh release
|
||||
- name: Odin version
|
||||
run: ./odin version
|
||||
timeout-minutes: 1
|
||||
@@ -38,6 +38,17 @@ jobs:
|
||||
cd tests/vendor
|
||||
make
|
||||
timeout-minutes: 10
|
||||
- name: Odin issues tests
|
||||
run: |
|
||||
cd tests/issues
|
||||
./run.sh
|
||||
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 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
|
||||
@@ -52,7 +63,7 @@ jobs:
|
||||
TMP_PATH=$(xcrun --show-sdk-path)/user/include
|
||||
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
|
||||
- name: build odin
|
||||
run: make release
|
||||
run: ./build_odin.sh release
|
||||
- name: Odin version
|
||||
run: ./odin version
|
||||
timeout-minutes: 1
|
||||
@@ -81,6 +92,17 @@ jobs:
|
||||
cd tests/vendor
|
||||
make
|
||||
timeout-minutes: 10
|
||||
- name: Odin issues tests
|
||||
run: |
|
||||
cd tests/issues
|
||||
./run.sh
|
||||
timeout-minutes: 10
|
||||
- name: Odin check examples/all for Darwin arm64
|
||||
run: ./odin check examples/all -vet -strict-style -target:darwin_arm64
|
||||
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
|
||||
build_windows:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
@@ -141,3 +163,16 @@ jobs:
|
||||
cd tests\core\math\big
|
||||
call build.bat
|
||||
timeout-minutes: 10
|
||||
- name: Odin issues tests
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||
cd tests\issues
|
||||
call run.bat
|
||||
timeout-minutes: 10
|
||||
- name: Odin check examples/all for Windows 32bits
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||
odin check examples/all -strict-style -target:windows_i386
|
||||
timeout-minutes: 10
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||
odin run examples/demo/demo.odin
|
||||
odin run examples/demo
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
rm bin/llvm/windows/LLVM-C.lib
|
||||
@@ -41,11 +41,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: (Linux) Download LLVM
|
||||
run: sudo apt-get install llvm-11 clang-11 llvm
|
||||
run: sudo apt-get install llvm-11 clang-11
|
||||
- name: build odin
|
||||
run: make nightly
|
||||
- name: Odin run
|
||||
run: ./odin run examples/demo/demo.odin
|
||||
run: ./odin run examples/demo
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
mkdir dist
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- name: build odin
|
||||
run: make nightly
|
||||
- name: Odin run
|
||||
run: ./odin run examples/demo/demo.odin
|
||||
run: ./odin run examples/demo
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
mkdir dist
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
run: |
|
||||
echo Authorizing B2 account
|
||||
b2 authorize-account "$APPID" "$APPKEY"
|
||||
|
||||
|
||||
echo Uploading artifcates to B2
|
||||
chmod +x ./ci/upload_create_nightly.sh
|
||||
./ci/upload_create_nightly.sh "$BUCKET" windows-amd64 windows_artifacts/
|
||||
@@ -141,7 +141,7 @@ jobs:
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
name: "Close Stale Issues & PRs"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 21 * * *"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close Stale Issues
|
||||
uses: actions/stale@v4.1.0
|
||||
with:
|
||||
# stale-issue-message: |
|
||||
# Hello!
|
||||
#
|
||||
# I am marking this issue as stale as it has not received any engagement from the community or maintainers 120 days. That does not imply that the issue has no merit! If you feel strongly about this issue
|
||||
# - open a PR referencing and resolving the issue;
|
||||
# - leave a comment on it and discuss ideas how you could contribute towards resolving it;
|
||||
# - leave a comment and describe in detail why this issue is critical for your use case;
|
||||
# - open a new issue with updated details and a plan on resolving the issue.
|
||||
#
|
||||
# The motivation for this automation is to help prioritize issues in the backlog and not ignore, reject, or belittle anyone..
|
||||
#
|
||||
# stale-pr-message: |
|
||||
# Hello!
|
||||
#
|
||||
# I am marking this PR as stale as it has not received any engagement from the community or maintainers 120 days. That does not imply that the issue has no merit! If you feel strongly about this issue
|
||||
# - leave a comment on it and discuss ideas how you could contribute towards resolving it;
|
||||
# - leave a comment and describe in detail why this issue is critical for your use case;
|
||||
#
|
||||
# The motivation for this automation is to help prioritize issues in the backlog and not ignore, reject, or belittle anyone..
|
||||
|
||||
days-before-stale: 120
|
||||
days-before-close: 30
|
||||
exempt-draft-pr: true
|
||||
ascending: true
|
||||
operations-per-run: 1000
|
||||
exempt-issue-labels: "ignore"
|
||||
@@ -269,6 +269,8 @@ bin/
|
||||
# - Linux/MacOS
|
||||
odin
|
||||
odin.dSYM
|
||||
*.bin
|
||||
demo.bin
|
||||
|
||||
# shared collection
|
||||
shared/
|
||||
@@ -279,3 +281,5 @@ shared/
|
||||
*.ll
|
||||
|
||||
*.sublime-workspace
|
||||
examples/bug/
|
||||
build.sh
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2016-2021 Ginger Bill. All rights reserved.
|
||||
Copyright (c) 2016-2022 Ginger Bill. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,84 +1,19 @@
|
||||
GIT_SHA=$(shell git rev-parse --short HEAD)
|
||||
DISABLED_WARNINGS=-Wno-switch -Wno-macro-redefined -Wno-unused-value
|
||||
LDFLAGS=-pthread -lm -lstdc++
|
||||
CFLAGS=-std=c++14 -DGIT_SHA=\"$(GIT_SHA)\"
|
||||
CFLAGS:=$(CFLAGS) -DODIN_VERSION_RAW=\"dev-$(shell date +"%Y-%m")\"
|
||||
CC=clang
|
||||
|
||||
OS=$(shell uname)
|
||||
|
||||
ifeq ($(OS), Darwin)
|
||||
|
||||
ARCH=$(shell uname -m)
|
||||
LLVM_CONFIG=llvm-config
|
||||
|
||||
# allow for arm only llvm's with version 13
|
||||
ifeq ($(ARCH), arm64)
|
||||
LLVM_VERSIONS = "13.%.%"
|
||||
else
|
||||
# allow for x86 / amd64 all llvm versions begining from 11
|
||||
LLVM_VERSIONS = "13.%.%" "12.0.1" "11.1.0"
|
||||
endif
|
||||
|
||||
LLVM_VERSION_PATTERN_SEPERATOR = )|(
|
||||
LLVM_VERSION_PATTERNS_ESCAPED_DOT = $(subst .,\.,$(LLVM_VERSIONS))
|
||||
LLVM_VERSION_PATTERNS_REPLACE_PERCENT = $(subst %,.*,$(LLVM_VERSION_PATTERNS_ESCAPED_DOT))
|
||||
LLVM_VERSION_PATTERN_REMOVE_ELEMENTS = $(subst " ",$(LLVM_VERSION_PATTERN_SEPERATOR),$(LLVM_VERSION_PATTERNS_REPLACE_PERCENT))
|
||||
LLMV_VERSION_PATTERN_REMOVE_SINGLE_STR = $(subst ",,$(LLVM_VERSION_PATTERN_REMOVE_ELEMENTS))
|
||||
LLVM_VERSION_PATTERN = "^(($(LLMV_VERSION_PATTERN_REMOVE_SINGLE_STR)))"
|
||||
|
||||
ifeq ($(shell $(LLVM_CONFIG) --version | grep -E $(LLVM_VERSION_PATTERN)),)
|
||||
ifeq ($(ARCH), arm64)
|
||||
$(error "Requirement: llvm-config must be base version 13 for arm64")
|
||||
else
|
||||
$(error "Requirement: llvm-config must be base version greater than 11 for amd64/x86")
|
||||
endif
|
||||
endif
|
||||
|
||||
LDFLAGS:=$(LDFLAGS) -liconv -ldl
|
||||
CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
|
||||
LDFLAGS:=$(LDFLAGS) -lLLVM-C
|
||||
endif
|
||||
ifeq ($(OS), Linux)
|
||||
LLVM_CONFIG=llvm-config-11
|
||||
ifneq ($(shell which llvm-config-11 2>/dev/null),)
|
||||
LLVM_CONFIG=llvm-config-11
|
||||
else ifneq ($(shell which llvm-config-11-64 2>/dev/null),)
|
||||
LLVM_CONFIG=llvm-config-11-64
|
||||
else
|
||||
ifeq ($(shell $(LLVM_CONFIG) --version | grep '^11\.'),)
|
||||
$(error "Requirement: llvm-config must be version 11")
|
||||
endif
|
||||
endif
|
||||
|
||||
LDFLAGS:=$(LDFLAGS) -ldl
|
||||
CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
|
||||
LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs)
|
||||
endif
|
||||
ifeq ($(OS), OpenBSD)
|
||||
LLVM_CONFIG=/usr/local/bin/llvm-config
|
||||
|
||||
LDFLAGS:=$(LDFLAGS) -liconv
|
||||
CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
|
||||
LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs)
|
||||
endif
|
||||
|
||||
all: debug demo
|
||||
all: debug
|
||||
|
||||
demo:
|
||||
./odin run examples/demo/demo.odin
|
||||
./odin run examples/demo/demo.odin -file
|
||||
|
||||
report:
|
||||
./odin report
|
||||
|
||||
debug:
|
||||
$(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -g $(LDFLAGS) -o odin
|
||||
./build_odin.sh debug
|
||||
|
||||
release:
|
||||
$(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -O3 $(LDFLAGS) -o odin
|
||||
./build_odin.sh release
|
||||
|
||||
release_native:
|
||||
$(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -O3 -march=native $(LDFLAGS) -o odin
|
||||
./build_odin.sh release-native
|
||||
|
||||
nightly:
|
||||
$(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -DNIGHTLY -O3 $(LDFLAGS) -o odin
|
||||
./build_odin.sh nightly
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<img src="https://img.shields.io/badge/platforms-Windows%20|%20Linux%20|%20macOS-green.svg">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://discord.gg/hnwN2Rj">
|
||||
<a href="https://discord.gg/odinlang">
|
||||
<img src="https://img.shields.io/discord/568138951836172421?logo=discord">
|
||||
</a>
|
||||
<a href="https://github.com/odin-lang/odin/actions">
|
||||
@@ -58,6 +58,10 @@ main :: proc() {
|
||||
|
||||
Instructions for downloading and installing the Odin compiler and libraries.
|
||||
|
||||
#### [Nightly Builds](https://odin-lang.org/docs/nightly/)
|
||||
|
||||
Get the latest nightly builds of Odin.
|
||||
|
||||
### Learning Odin
|
||||
|
||||
#### [Overview of Odin](https://odin-lang.org/docs/overview)
|
||||
@@ -68,6 +72,10 @@ An overview of the Odin programming language.
|
||||
|
||||
Answers to common questions about Odin.
|
||||
|
||||
#### [Packages](https://pkg.odin-lang.org/)
|
||||
|
||||
Documentation for all the official packages part of the [core](https://pkg.odin-lang.org/core/) and [vendor](https://pkg.odin-lang.org/vendor/) library collections.
|
||||
|
||||
#### [The Odin Wiki](https://github.com/odin-lang/Odin/wiki)
|
||||
|
||||
A wiki maintained by the Odin community.
|
||||
@@ -76,12 +84,6 @@ A wiki maintained by the Odin community.
|
||||
|
||||
Get live support and talk with other odiners on the Odin Discord.
|
||||
|
||||
### References
|
||||
|
||||
#### [Language Specification](https://odin-lang.org/docs/spec/)
|
||||
|
||||
The official Odin Language specification.
|
||||
|
||||
### Articles
|
||||
|
||||
#### [The Odin Blog](https://odin-lang.org/news/)
|
||||
|
||||
@@ -58,7 +58,7 @@ set libs= ^
|
||||
set linker_flags= -incremental:no -opt:ref -subsystem:console
|
||||
|
||||
if %release_mode% EQU 0 ( rem Debug
|
||||
set linker_flags=%linker_flags% -debug
|
||||
set linker_flags=%linker_flags% -debug /NATVIS:src\odin_compiler.natvis
|
||||
) else ( rem Release
|
||||
set linker_flags=%linker_flags% -debug
|
||||
)
|
||||
|
||||
Executable
+162
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
: ${CXX=clang++}
|
||||
: ${CPPFLAGS=}
|
||||
: ${CXXFLAGS=}
|
||||
: ${LDFLAGS=}
|
||||
: ${ODIN_VERSION=dev-$(date +"%Y-%m")}
|
||||
|
||||
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"$ODIN_VERSION\""
|
||||
CXXFLAGS="$CXXFLAGS -std=c++14"
|
||||
LDFLAGS="$LDFLAGS -pthread -lm -lstdc++"
|
||||
|
||||
GIT_SHA=$(git rev-parse --short HEAD || :)
|
||||
if [ "$GIT_SHA" ]; then CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""; fi
|
||||
|
||||
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
|
||||
OS=$(uname)
|
||||
|
||||
panic() {
|
||||
printf "%s\n" "$1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
version() { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }
|
||||
|
||||
config_darwin() {
|
||||
ARCH=$(uname -m)
|
||||
: ${LLVM_CONFIG=llvm-config}
|
||||
|
||||
# allow for arm only llvm's with version 13
|
||||
if [ ARCH == arm64 ]; then
|
||||
MIN_LLVM_VERSION=("13.0.0")
|
||||
else
|
||||
# allow for x86 / amd64 all llvm versions beginning from 11
|
||||
MIN_LLVM_VERSION=("11.1.0")
|
||||
fi
|
||||
|
||||
if [ $(version $($LLVM_CONFIG --version)) -lt $(version $MIN_LLVM_VERSION) ]; then
|
||||
if [ ARCH == arm64 ]; then
|
||||
panic "Requirement: llvm-config must be base version 13 for arm64"
|
||||
else
|
||||
panic "Requirement: llvm-config must be base version greater than 11 for amd64/x86"
|
||||
fi
|
||||
fi
|
||||
|
||||
LDFLAGS="$LDFLAGS -liconv -ldl"
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS -lLLVM-C"
|
||||
}
|
||||
|
||||
config_freebsd() {
|
||||
: ${LLVM_CONFIG=/usr/local/bin/llvm-config11}
|
||||
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
|
||||
}
|
||||
|
||||
config_openbsd() {
|
||||
: ${LLVM_CONFIG=/usr/local/bin/llvm-config}
|
||||
|
||||
LDFLAGS="$LDFLAGS -liconv"
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
|
||||
}
|
||||
|
||||
config_linux() {
|
||||
: ${LLVM_CONFIG=}
|
||||
|
||||
if [ ! "$LLVM_CONFIG" ]; then
|
||||
if which llvm-config > /dev/null 2>&1; then
|
||||
LLVM_CONFIG=llvm-config
|
||||
elif which llvm-config-11 > /dev/null 2>&1; then
|
||||
LLVM_CONFIG=llvm-config-11
|
||||
elif which llvm-config-11-64 > /dev/null 2>&1; then
|
||||
LLVM_CONFIG=llvm-config-11-64
|
||||
else
|
||||
panic "Unable to find LLVM-config"
|
||||
fi
|
||||
fi
|
||||
|
||||
MIN_LLVM_VERSION=("11.0.0")
|
||||
if [ $(version $($LLVM_CONFIG --version)) -lt $(version $MIN_LLVM_VERSION) ]; then
|
||||
echo "Tried to use " $(which $LLVM_CONFIG) "version" $($LLVM_CONFIG --version)
|
||||
panic "Requirement: llvm-config must be base version greater than 11"
|
||||
fi
|
||||
|
||||
LDFLAGS="$LDFLAGS -ldl"
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
|
||||
}
|
||||
|
||||
build_odin() {
|
||||
case $1 in
|
||||
debug)
|
||||
EXTRAFLAGS="-g"
|
||||
;;
|
||||
release)
|
||||
EXTRAFLAGS="-O3"
|
||||
;;
|
||||
release-native)
|
||||
EXTRAFLAGS="-O3 -march=native"
|
||||
;;
|
||||
nightly)
|
||||
EXTRAFLAGS="-DNIGHTLY -O3"
|
||||
;;
|
||||
*)
|
||||
panic "Build mode unsupported!"
|
||||
esac
|
||||
|
||||
set -x
|
||||
$CXX src/main.cpp src/libtommath.cpp $DISABLED_WARNINGS $CPPFLAGS $CXXFLAGS $EXTRAFLAGS $LDFLAGS -o odin
|
||||
set +x
|
||||
}
|
||||
|
||||
run_demo() {
|
||||
./odin run examples/demo/demo.odin -file
|
||||
}
|
||||
|
||||
case $OS in
|
||||
Linux)
|
||||
config_linux
|
||||
;;
|
||||
Darwin)
|
||||
config_darwin
|
||||
;;
|
||||
OpenBSD)
|
||||
config_openbsd
|
||||
;;
|
||||
FreeBSD)
|
||||
config_freebsd
|
||||
;;
|
||||
*)
|
||||
panic "Platform unsupported!"
|
||||
esac
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
build_odin debug
|
||||
run_demo
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ $# -eq 1 ]]; then
|
||||
case $1 in
|
||||
report)
|
||||
if [[ ! -f "./odin" ]]; then
|
||||
build_odin debug
|
||||
fi
|
||||
|
||||
./odin report
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
build_odin $1
|
||||
;;
|
||||
esac
|
||||
|
||||
run_demo
|
||||
exit 0
|
||||
else
|
||||
panic "Too many arguments!"
|
||||
fi
|
||||
@@ -8,6 +8,7 @@ import "core:intrinsics"
|
||||
|
||||
// Extra errors returns by scanning procedures
|
||||
Scanner_Extra_Error :: enum i32 {
|
||||
None,
|
||||
Negative_Advance,
|
||||
Advanced_Too_Far,
|
||||
Bad_Read_Count,
|
||||
@@ -15,7 +16,7 @@ Scanner_Extra_Error :: enum i32 {
|
||||
Too_Short,
|
||||
}
|
||||
|
||||
Scanner_Error :: union {
|
||||
Scanner_Error :: union #shared_nil {
|
||||
io.Error,
|
||||
Scanner_Extra_Error,
|
||||
}
|
||||
@@ -65,10 +66,10 @@ scanner_destroy :: proc(s: ^Scanner) {
|
||||
}
|
||||
|
||||
|
||||
// Returns the first non-EOF error that was encounted by the scanner
|
||||
// Returns the first non-EOF error that was encountered by the scanner
|
||||
scanner_error :: proc(s: ^Scanner) -> Scanner_Error {
|
||||
switch s._err {
|
||||
case .EOF, .None:
|
||||
case .EOF, nil:
|
||||
return nil
|
||||
}
|
||||
return s._err
|
||||
@@ -93,10 +94,6 @@ scanner_text :: proc(s: ^Scanner) -> string {
|
||||
// scanner_scan advances the scanner
|
||||
scanner_scan :: proc(s: ^Scanner) -> bool {
|
||||
set_err :: proc(s: ^Scanner, err: Scanner_Error) {
|
||||
err := err
|
||||
if err == .None {
|
||||
err = nil
|
||||
}
|
||||
switch s._err {
|
||||
case nil, .EOF:
|
||||
s._err = err
|
||||
|
||||
+66
-66
@@ -1,90 +1,90 @@
|
||||
// This is purely for documentation
|
||||
package builtin
|
||||
|
||||
nil :: nil;
|
||||
false :: 0!=0;
|
||||
true :: 0==0;
|
||||
nil :: nil
|
||||
false :: 0!=0
|
||||
true :: 0==0
|
||||
|
||||
ODIN_OS :: ODIN_OS;
|
||||
ODIN_ARCH :: ODIN_ARCH;
|
||||
ODIN_ENDIAN :: ODIN_ENDIAN;
|
||||
ODIN_VENDOR :: ODIN_VENDOR;
|
||||
ODIN_VERSION :: ODIN_VERSION;
|
||||
ODIN_ROOT :: ODIN_ROOT;
|
||||
ODIN_DEBUG :: ODIN_DEBUG;
|
||||
ODIN_OS :: ODIN_OS
|
||||
ODIN_ARCH :: ODIN_ARCH
|
||||
ODIN_ENDIAN :: ODIN_ENDIAN
|
||||
ODIN_VENDOR :: ODIN_VENDOR
|
||||
ODIN_VERSION :: ODIN_VERSION
|
||||
ODIN_ROOT :: ODIN_ROOT
|
||||
ODIN_DEBUG :: ODIN_DEBUG
|
||||
|
||||
byte :: u8; // alias
|
||||
byte :: u8 // alias
|
||||
|
||||
bool :: bool;
|
||||
b8 :: b8;
|
||||
b16 :: b16;
|
||||
b32 :: b32;
|
||||
b64 :: b64;
|
||||
bool :: bool
|
||||
b8 :: b8
|
||||
b16 :: b16
|
||||
b32 :: b32
|
||||
b64 :: b64
|
||||
|
||||
i8 :: i8;
|
||||
u8 :: u8;
|
||||
i16 :: i16;
|
||||
u16 :: u16;
|
||||
i32 :: i32;
|
||||
u32 :: u32;
|
||||
i64 :: i64;
|
||||
u64 :: u64;
|
||||
i8 :: i8
|
||||
u8 :: u8
|
||||
i16 :: i16
|
||||
u16 :: u16
|
||||
i32 :: i32
|
||||
u32 :: u32
|
||||
i64 :: i64
|
||||
u64 :: u64
|
||||
|
||||
i128 :: i128;
|
||||
u128 :: u128;
|
||||
i128 :: i128
|
||||
u128 :: u128
|
||||
|
||||
rune :: rune;
|
||||
rune :: rune
|
||||
|
||||
f16 :: f16;
|
||||
f32 :: f32;
|
||||
f64 :: f64;
|
||||
f16 :: f16
|
||||
f32 :: f32
|
||||
f64 :: f64
|
||||
|
||||
complex32 :: complex32;
|
||||
complex64 :: complex64;
|
||||
complex128 :: complex128;
|
||||
complex32 :: complex32
|
||||
complex64 :: complex64
|
||||
complex128 :: complex128
|
||||
|
||||
quaternion64 :: quaternion64;
|
||||
quaternion128 :: quaternion128;
|
||||
quaternion256 :: quaternion256;
|
||||
quaternion64 :: quaternion64
|
||||
quaternion128 :: quaternion128
|
||||
quaternion256 :: quaternion256
|
||||
|
||||
int :: int;
|
||||
uint :: uint;
|
||||
uintptr :: uintptr;
|
||||
int :: int
|
||||
uint :: uint
|
||||
uintptr :: uintptr
|
||||
|
||||
rawptr :: rawptr;
|
||||
string :: string;
|
||||
cstring :: cstring;
|
||||
any :: any;
|
||||
rawptr :: rawptr
|
||||
string :: string
|
||||
cstring :: cstring
|
||||
any :: any
|
||||
|
||||
typeid :: typeid;
|
||||
typeid :: typeid
|
||||
|
||||
// Endian Specific Types
|
||||
i16le :: i16le;
|
||||
u16le :: u16le;
|
||||
i32le :: i32le;
|
||||
u32le :: u32le;
|
||||
i64le :: i64le;
|
||||
u64le :: u64le;
|
||||
i128le :: i128le;
|
||||
u128le :: u128le;
|
||||
i16le :: i16le
|
||||
u16le :: u16le
|
||||
i32le :: i32le
|
||||
u32le :: u32le
|
||||
i64le :: i64le
|
||||
u64le :: u64le
|
||||
i128le :: i128le
|
||||
u128le :: u128le
|
||||
|
||||
i16be :: i16be;
|
||||
u16be :: u16be;
|
||||
i32be :: i32be;
|
||||
u32be :: u32be;
|
||||
i64be :: i64be;
|
||||
u64be :: u64be;
|
||||
i128be :: i128be;
|
||||
u128be :: u128be;
|
||||
i16be :: i16be
|
||||
u16be :: u16be
|
||||
i32be :: i32be
|
||||
u32be :: u32be
|
||||
i64be :: i64be
|
||||
u64be :: u64be
|
||||
i128be :: i128be
|
||||
u128be :: u128be
|
||||
|
||||
|
||||
f16le :: f16le;
|
||||
f32le :: f32le;
|
||||
f64le :: f64le;
|
||||
f16le :: f16le
|
||||
f32le :: f32le
|
||||
f64le :: f64le
|
||||
|
||||
f16be :: f16be;
|
||||
f32be :: f32be;
|
||||
f64be :: f64be;
|
||||
f16be :: f16be
|
||||
f32be :: f32be
|
||||
f64be :: f64be
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -161,6 +161,10 @@ buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) {
|
||||
return copy(b.buf[m:], p), nil
|
||||
}
|
||||
|
||||
buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.Error) {
|
||||
return buffer_write(b, ([^]byte)(ptr)[:size])
|
||||
}
|
||||
|
||||
buffer_write_string :: proc(b: ^Buffer, s: string) -> (n: int, err: io.Error) {
|
||||
b.last_read = .Invalid
|
||||
m, ok := _buffer_try_grow(b, len(s))
|
||||
@@ -229,6 +233,10 @@ buffer_read :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) {
|
||||
return
|
||||
}
|
||||
|
||||
buffer_read_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.Error) {
|
||||
return buffer_read(b, ([^]byte)(ptr)[:size])
|
||||
}
|
||||
|
||||
buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
|
||||
b.last_read = .Invalid
|
||||
|
||||
|
||||
+45
-1
@@ -10,7 +10,14 @@ clone :: proc(s: []byte, allocator := context.allocator, loc := #caller_location
|
||||
return c[:len(s)]
|
||||
}
|
||||
|
||||
ptr_from_slice :: proc(str: []byte) -> ^byte {
|
||||
clone_safe :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: mem.Allocator_Error) {
|
||||
c := make([]byte, len(s), allocator, loc) or_return
|
||||
copy(c, s)
|
||||
return c[:len(s)], nil
|
||||
}
|
||||
|
||||
ptr_from_slice :: ptr_from_bytes
|
||||
ptr_from_bytes :: proc(str: []byte) -> ^byte {
|
||||
d := transmute(mem.Raw_String)str
|
||||
return d.data
|
||||
}
|
||||
@@ -134,6 +141,25 @@ join :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> []byte
|
||||
return b
|
||||
}
|
||||
|
||||
join_safe :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> (data: []byte, err: mem.Allocator_Error) {
|
||||
if len(a) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
n := len(sep) * (len(a) - 1)
|
||||
for s in a {
|
||||
n += len(s)
|
||||
}
|
||||
|
||||
b := make([]byte, n, allocator) or_return
|
||||
i := copy(b, a[0])
|
||||
for s in a[1:] {
|
||||
i += copy(b[i:], sep)
|
||||
i += copy(b[i:], s)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
concatenate :: proc(a: [][]byte, allocator := context.allocator) -> []byte {
|
||||
if len(a) == 0 {
|
||||
return nil
|
||||
@@ -151,6 +177,24 @@ concatenate :: proc(a: [][]byte, allocator := context.allocator) -> []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
concatenate_safe :: proc(a: [][]byte, allocator := context.allocator) -> (data: []byte, err: mem.Allocator_Error) {
|
||||
if len(a) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
n := 0
|
||||
for s in a {
|
||||
n += len(s)
|
||||
}
|
||||
b := make([]byte, n, allocator) or_return
|
||||
i := 0
|
||||
for s in a {
|
||||
i += copy(b[i:], s)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
|
||||
@private
|
||||
_split :: proc(s, sep: []byte, sep_save, n: int, allocator := context.allocator) -> [][]byte {
|
||||
s, n := s, n
|
||||
|
||||
@@ -519,7 +519,7 @@ join_adjacent_string_literals :: proc(cpp: ^Preprocessor, initial_tok: ^Token) {
|
||||
|
||||
|
||||
quote_string :: proc(s: string) -> []byte {
|
||||
b := strings.make_builder(0, len(s)+2)
|
||||
b := strings.builder_make(0, len(s)+2)
|
||||
io.write_quoted_string(strings.to_writer(&b), s, '"')
|
||||
return b.buf[:]
|
||||
}
|
||||
@@ -1276,7 +1276,7 @@ preprocess_internal :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
|
||||
if start.file != nil {
|
||||
dir = filepath.dir(start.file.name)
|
||||
}
|
||||
path := filepath.join(dir, filename)
|
||||
path := filepath.join({dir, filename})
|
||||
if os.exists(path) {
|
||||
tok = include_file(cpp, tok, path, start.next.next)
|
||||
continue
|
||||
|
||||
+11
-11
@@ -211,19 +211,19 @@ _signbitf :: #force_inline proc(x: float) -> int {
|
||||
return int(transmute(uint32_t)x >> 31)
|
||||
}
|
||||
|
||||
isfinite :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) {
|
||||
isfinite :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) {
|
||||
return fpclassify(x) == FP_INFINITE
|
||||
}
|
||||
|
||||
isinf :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) {
|
||||
isinf :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) {
|
||||
return fpclassify(x) > FP_INFINITE
|
||||
}
|
||||
|
||||
isnan :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) {
|
||||
isnan :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) {
|
||||
return fpclassify(x) == FP_NAN
|
||||
}
|
||||
|
||||
isnormal :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) {
|
||||
isnormal :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) {
|
||||
return fpclassify(x) == FP_NORMAL
|
||||
}
|
||||
|
||||
@@ -231,27 +231,27 @@ isnormal :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) {
|
||||
// implemented as the relational comparisons, as that would produce an invalid
|
||||
// "sticky" state that propagates and affects maths results. These need
|
||||
// to be implemented natively in Odin assuming isunordered to prevent that.
|
||||
isgreater :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) {
|
||||
isgreater :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) {
|
||||
return !isunordered(x, y) && x > y
|
||||
}
|
||||
|
||||
isgreaterequal :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) {
|
||||
isgreaterequal :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) {
|
||||
return !isunordered(x, y) && x >= y
|
||||
}
|
||||
|
||||
isless :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) {
|
||||
isless :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) {
|
||||
return !isunordered(x, y) && x < y
|
||||
}
|
||||
|
||||
islessequal :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) {
|
||||
islessequal :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) {
|
||||
return !isunordered(x, y) && x <= y
|
||||
}
|
||||
|
||||
islessgreater :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) {
|
||||
islessgreater :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) {
|
||||
return !isunordered(x, y) && x <= y
|
||||
}
|
||||
|
||||
isunordered :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) {
|
||||
isunordered :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) {
|
||||
if isnan(x) {
|
||||
// Force evaluation of y to propagate exceptions for ordering semantics.
|
||||
// To ensure correct semantics of IEEE 754 this cannot be compiled away.
|
||||
@@ -331,7 +331,7 @@ fmin :: proc{libc_fmin, libc_fminf}
|
||||
fma :: proc{libc_fma, libc_fmaf}
|
||||
|
||||
// But retain the 'f' suffix-variant functions as well so they can be used,
|
||||
// a trick is used here where we use explicit procedrual overloading of one
|
||||
// a trick is used here where we use explicit procedural overloading of one
|
||||
// procedure. This is done because the foreign block is marked @(private) and
|
||||
// aliasing functions does not remove privateness from the entity.
|
||||
acosf :: proc{libc_acosf}
|
||||
|
||||
+111
-146
@@ -47,29 +47,30 @@ kill_dependency :: #force_inline proc(value: $T) -> T {
|
||||
|
||||
// 7.17.4 Fences
|
||||
atomic_thread_fence :: #force_inline proc(order: memory_order) {
|
||||
switch (order) {
|
||||
case .relaxed:
|
||||
return
|
||||
case .consume:
|
||||
intrinsics.atomic_fence_acq()
|
||||
case .acquire:
|
||||
intrinsics.atomic_fence_acq()
|
||||
case .release:
|
||||
intrinsics.atomic_fence_rel()
|
||||
case .acq_rel:
|
||||
intrinsics.atomic_fence_acqrel()
|
||||
case .seq_cst:
|
||||
intrinsics.atomic_fence_acqrel()
|
||||
assert(order != .relaxed)
|
||||
assert(order != .consume)
|
||||
#partial switch order {
|
||||
case .acquire: intrinsics.atomic_thread_fence(.Acquire)
|
||||
case .release: intrinsics.atomic_thread_fence(.Release)
|
||||
case .acq_rel: intrinsics.atomic_thread_fence(.Acq_Rel)
|
||||
case .seq_cst: intrinsics.atomic_thread_fence(.Seq_Cst)
|
||||
}
|
||||
}
|
||||
|
||||
atomic_signal_fence :: #force_inline proc(order: memory_order) {
|
||||
atomic_thread_fence(order)
|
||||
assert(order != .relaxed)
|
||||
assert(order != .consume)
|
||||
#partial switch order {
|
||||
case .acquire: intrinsics.atomic_signal_fence(.Acquire)
|
||||
case .release: intrinsics.atomic_signal_fence(.Release)
|
||||
case .acq_rel: intrinsics.atomic_signal_fence(.Acq_Rel)
|
||||
case .seq_cst: intrinsics.atomic_signal_fence(.Seq_Cst)
|
||||
}
|
||||
}
|
||||
|
||||
// 7.17.5 Lock-free property
|
||||
atomic_is_lock_free :: #force_inline proc(obj: ^$T) -> bool {
|
||||
return size_of(T) <= 8 && (intrinsics.type_is_integer(T) || intrinsics.type_is_pointer(T))
|
||||
return intrinsics.atomic_type_is_lock_free(T)
|
||||
}
|
||||
|
||||
// 7.17.6 Atomic integer types
|
||||
@@ -121,13 +122,10 @@ atomic_store_explicit :: #force_inline proc(object: ^$T, desired: T, order: memo
|
||||
assert(order != .acquire)
|
||||
assert(order != .acq_rel)
|
||||
|
||||
#partial switch (order) {
|
||||
case .relaxed:
|
||||
intrinsics.atomic_store_relaxed(object, desired)
|
||||
case .release:
|
||||
intrinsics.atomic_store_rel(object, desired)
|
||||
case .seq_cst:
|
||||
intrinsics.atomic_store(object, desired)
|
||||
#partial switch order {
|
||||
case .relaxed: intrinsics.atomic_store_explicit(object, desired, .Relaxed)
|
||||
case .release: intrinsics.atomic_store_explicit(object, desired, .Release)
|
||||
case .seq_cst: intrinsics.atomic_store_explicit(object, desired, .Seq_Cst)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,36 +137,26 @@ atomic_load_explicit :: #force_inline proc(object: ^$T, order: memory_order) {
|
||||
assert(order != .release)
|
||||
assert(order != .acq_rel)
|
||||
|
||||
#partial switch (order) {
|
||||
case .relaxed:
|
||||
return intrinsics.atomic_load_relaxed(object)
|
||||
case .consume:
|
||||
return intrinsics.atomic_load_acq(object)
|
||||
case .acquire:
|
||||
return intrinsics.atomic_load_acq(object)
|
||||
case .seq_cst:
|
||||
return intrinsics.atomic_load(object)
|
||||
#partial switch order {
|
||||
case .relaxed: return intrinsics.atomic_load_explicit(object, .Relaxed)
|
||||
case .consume: return intrinsics.atomic_load_explicit(object, .Consume)
|
||||
case .acquire: return intrinsics.atomic_load_explicit(object, .Acquire)
|
||||
case .seq_cst: return intrinsics.atomic_load_explicit(object, .Seq_Cst)
|
||||
}
|
||||
}
|
||||
|
||||
atomic_exchange :: #force_inline proc(object: ^$T, desired: T) -> T {
|
||||
return intrinsics.atomic_xchg(object, desired)
|
||||
return intrinsics.atomic_exchange(object, desired)
|
||||
}
|
||||
|
||||
atomic_exchange_explicit :: #force_inline proc(object: ^$T, desired: T, order: memory_order) -> T {
|
||||
switch (order) {
|
||||
case .relaxed:
|
||||
return intrinsics.atomic_xchg_relaxed(object, desired)
|
||||
case .consume:
|
||||
return intrinsics.atomic_xchg_acq(object, desired)
|
||||
case .acquire:
|
||||
return intrinsics.atomic_xchg_acq(object, desired)
|
||||
case .release:
|
||||
return intrinsics.atomic_xchg_rel(object, desired)
|
||||
case .acq_rel:
|
||||
return intrinsics.atomic_xchg_acqrel(object, desired)
|
||||
case .seq_cst:
|
||||
return intrinsics.atomic_xchg(object, desired)
|
||||
switch order {
|
||||
case .relaxed: return intrinsics.atomic_exchange_explicit(object, desired, .Relaxed)
|
||||
case .consume: return intrinsics.atomic_exchange_explicit(object, desired, .Consume)
|
||||
case .acquire: return intrinsics.atomic_exchange_explicit(object, desired, .Acquire)
|
||||
case .release: return intrinsics.atomic_exchange_explicit(object, desired, .Release)
|
||||
case .acq_rel: return intrinsics.atomic_exchange_explicit(object, desired, .Acq_Rel)
|
||||
case .seq_cst: return intrinsics.atomic_exchange_explicit(object, desired, .Seq_Cst)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -189,102 +177,104 @@ atomic_exchange_explicit :: #force_inline proc(object: ^$T, desired: T, order: m
|
||||
// [success = seq_cst, failure = acquire] => failacq
|
||||
// [success = acquire, failure = relaxed] => acq_failrelaxed
|
||||
// [success = acq_rel, failure = relaxed] => acqrel_failrelaxed
|
||||
atomic_compare_exchange_strong :: #force_inline proc(object, expected: ^$T, desired: T) {
|
||||
value, ok := intrinsics.atomic_cxchg(object, expected^, desired)
|
||||
atomic_compare_exchange_strong :: #force_inline proc(object, expected: ^$T, desired: T) -> bool {
|
||||
value, ok := intrinsics.atomic_compare_exchange_strong(object, expected^, desired)
|
||||
if !ok { expected^ = value }
|
||||
return ok
|
||||
}
|
||||
|
||||
atomic_compare_exchange_strong_explicit :: #force_inline proc(object, expected: ^$T, desired: T, success, failure: memory_order) {
|
||||
atomic_compare_exchange_strong_explicit :: #force_inline proc(object, expected: ^$T, desired: T, success, failure: memory_order) -> bool {
|
||||
assert(failure != .release)
|
||||
assert(failure != .acq_rel)
|
||||
|
||||
value: T; ok: bool
|
||||
#partial switch (failure) {
|
||||
#partial switch failure {
|
||||
case .seq_cst:
|
||||
assert(success != .relaxed)
|
||||
#partial switch (success) {
|
||||
#partial switch success {
|
||||
case .seq_cst:
|
||||
value, ok := intrinsics.atomic_cxchg(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Seq_Cst, .Seq_Cst)
|
||||
case .acquire:
|
||||
value, ok := intrinsics.atomic_cxchg_acq(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Acquire, .Seq_Cst)
|
||||
case .consume:
|
||||
value, ok := intrinsics.atomic_cxchg_acq(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Consume, .Seq_Cst)
|
||||
case .release:
|
||||
value, ok := intrinsics.atomic_cxchg_rel(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Release, .Seq_Cst)
|
||||
case .acq_rel:
|
||||
value, ok := intrinsics.atomic_cxchg_acqrel(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Acq_Rel, .Seq_Cst)
|
||||
}
|
||||
case .relaxed:
|
||||
assert(success != .release)
|
||||
#partial switch (success) {
|
||||
#partial switch success {
|
||||
case .relaxed:
|
||||
value, ok := intrinsics.atomic_cxchg_relaxed(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Relaxed, .Relaxed)
|
||||
case .seq_cst:
|
||||
value, ok := intrinsics.atomic_cxchg_failrelaxed(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Seq_Cst, .Relaxed)
|
||||
case .acquire:
|
||||
value, ok := intrinsics.atomic_cxchg_acq_failrelaxed(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Acquire, .Relaxed)
|
||||
case .consume:
|
||||
value, ok := intrinsics.atomic_cxchg_acq_failrelaxed(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Consume, .Relaxed)
|
||||
case .acq_rel:
|
||||
value, ok := intrinsics.atomic_cxchg_acqrel_failrelaxed(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Acq_Rel, .Relaxed)
|
||||
}
|
||||
case .consume:
|
||||
fallthrough
|
||||
assert(success == .seq_cst)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Seq_Cst, .Consume)
|
||||
case .acquire:
|
||||
assert(success == .seq_cst)
|
||||
value, ok := intrinsics.atomic_cxchg_failacq(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Seq_Cst, .Acquire)
|
||||
|
||||
}
|
||||
if !ok { expected^ = value }
|
||||
return ok
|
||||
}
|
||||
|
||||
atomic_compare_exchange_weak :: #force_inline proc(object, expected: ^$T, desired: T) {
|
||||
value, ok := intrinsics.atomic_cxchgweak(object, expected^, desired)
|
||||
atomic_compare_exchange_weak :: #force_inline proc(object, expected: ^$T, desired: T) -> bool {
|
||||
value, ok := intrinsics.atomic_compare_exchange_weak(object, expected^, desired)
|
||||
if !ok { expected^ = value }
|
||||
return ok
|
||||
}
|
||||
|
||||
atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desited: T, success, failure: memory_order) {
|
||||
atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desited: T, success, failure: memory_order) -> bool {
|
||||
assert(failure != .release)
|
||||
assert(failure != .acq_rel)
|
||||
|
||||
value: T; ok: bool
|
||||
#partial switch (failure) {
|
||||
#partial switch failure {
|
||||
case .seq_cst:
|
||||
assert(success != .relaxed)
|
||||
#partial switch (success) {
|
||||
#partial switch success {
|
||||
case .seq_cst:
|
||||
value, ok := intrinsics.atomic_cxchgweak(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Seq_Cst, .Seq_Cst)
|
||||
case .acquire:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acq(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Acquire, .Seq_Cst)
|
||||
case .consume:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acq(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Consume, .Seq_Cst)
|
||||
case .release:
|
||||
value, ok := intrinsics.atomic_cxchgweak_rel(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Release, .Seq_Cst)
|
||||
case .acq_rel:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acqrel(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Acq_Rel, .Seq_Cst)
|
||||
}
|
||||
case .relaxed:
|
||||
assert(success != .release)
|
||||
#partial switch (success) {
|
||||
#partial switch success {
|
||||
case .relaxed:
|
||||
value, ok := intrinsics.atomic_cxchgweak_relaxed(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Relaxed, .Relaxed)
|
||||
case .seq_cst:
|
||||
value, ok := intrinsics.atomic_cxchgweak_failrelaxed(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Seq_Cst, .Relaxed)
|
||||
case .acquire:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acq_failrelaxed(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Acquire, .Relaxed)
|
||||
case .consume:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acq_failrelaxed(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Consume, .Relaxed)
|
||||
case .acq_rel:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acqrel_failrelaxed(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Acq_Rel, .Relaxed)
|
||||
}
|
||||
case .consume:
|
||||
fallthrough
|
||||
assert(success == .seq_cst)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Seq_Cst, .Consume)
|
||||
case .acquire:
|
||||
assert(success == .seq_cst)
|
||||
value, ok := intrinsics.atomic_cxchgweak_failacq(object, expected^, desired)
|
||||
value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Seq_Cst, .Acquire)
|
||||
|
||||
}
|
||||
if !ok { expected^ = value }
|
||||
@@ -297,19 +287,14 @@ atomic_fetch_add :: #force_inline proc(object: ^$T, operand: T) -> T {
|
||||
}
|
||||
|
||||
atomic_fetch_add_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T {
|
||||
switch (order) {
|
||||
case .relaxed:
|
||||
return intrinsics.atomic_add_relaxed(object, operand)
|
||||
case .consume:
|
||||
return intrinsics.atomic_add_acq(object, operand)
|
||||
case .acquire:
|
||||
return intrinsics.atomic_add_acq(object, operand)
|
||||
case .release:
|
||||
return intrinsics.atomic_add_rel(object, operand)
|
||||
case .acq_rel:
|
||||
return intrinsics.atomic_add_acqrel(object, operand)
|
||||
case .seq_cst:
|
||||
return intrinsics.atomic_add(object, operand)
|
||||
switch order {
|
||||
case .relaxed: return intrinsics.atomic_add_explicit(object, operand, .Relaxed)
|
||||
case .consume: return intrinsics.atomic_add_explicit(object, operand, .Consume)
|
||||
case .acquire: return intrinsics.atomic_add_explicit(object, operand, .Acquire)
|
||||
case .release: return intrinsics.atomic_add_explicit(object, operand, .Release)
|
||||
case .acq_rel: return intrinsics.atomic_add_explicit(object, operand, .Acq_Rel)
|
||||
case: fallthrough
|
||||
case .seq_cst: return intrinsics.atomic_add_explicit(object, operand, .Seq_Cst)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,19 +303,14 @@ atomic_fetch_sub :: #force_inline proc(object: ^$T, operand: T) -> T {
|
||||
}
|
||||
|
||||
atomic_fetch_sub_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T {
|
||||
switch (order) {
|
||||
case .relaxed:
|
||||
return intrinsics.atomic_sub_relaxed(object, operand)
|
||||
case .consume:
|
||||
return intrinsics.atomic_sub_acq(object, operand)
|
||||
case .acquire:
|
||||
return intrinsics.atomic_sub_acq(object, operand)
|
||||
case .release:
|
||||
return intrinsics.atomic_sub_rel(object, operand)
|
||||
case .acq_rel:
|
||||
return intrinsics.atomic_sub_acqrel(object, operand)
|
||||
case .seq_cst:
|
||||
return intrinsics.atomic_sub(object, operand)
|
||||
switch order {
|
||||
case .relaxed: return intrinsics.atomic_sub_explicit(object, operand, .Relaxed)
|
||||
case .consume: return intrinsics.atomic_sub_explicit(object, operand, .Consume)
|
||||
case .acquire: return intrinsics.atomic_sub_explicit(object, operand, .Acquire)
|
||||
case .release: return intrinsics.atomic_sub_explicit(object, operand, .Release)
|
||||
case .acq_rel: return intrinsics.atomic_sub_explicit(object, operand, .Acq_Rel)
|
||||
case: fallthrough
|
||||
case .seq_cst: return intrinsics.atomic_sub_explicit(object, operand, .Seq_Cst)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,19 +319,14 @@ atomic_fetch_or :: #force_inline proc(object: ^$T, operand: T) -> T {
|
||||
}
|
||||
|
||||
atomic_fetch_or_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T {
|
||||
switch (order) {
|
||||
case .relaxed:
|
||||
return intrinsics.atomic_or_relaxed(object, operand)
|
||||
case .consume:
|
||||
return intrinsics.atomic_or_acq(object, operand)
|
||||
case .acquire:
|
||||
return intrinsics.atomic_or_acq(object, operand)
|
||||
case .release:
|
||||
return intrinsics.atomic_or_rel(object, operand)
|
||||
case .acq_rel:
|
||||
return intrinsics.atomic_or_acqrel(object, operand)
|
||||
case .seq_cst:
|
||||
return intrinsics.atomic_or(object, operand)
|
||||
switch order {
|
||||
case .relaxed: return intrinsics.atomic_or_explicit(object, operand, .Relaxed)
|
||||
case .consume: return intrinsics.atomic_or_explicit(object, operand, .Consume)
|
||||
case .acquire: return intrinsics.atomic_or_explicit(object, operand, .Acquire)
|
||||
case .release: return intrinsics.atomic_or_explicit(object, operand, .Release)
|
||||
case .acq_rel: return intrinsics.atomic_or_explicit(object, operand, .Acq_Rel)
|
||||
case: fallthrough
|
||||
case .seq_cst: return intrinsics.atomic_or_explicit(object, operand, .Seq_Cst)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,19 +335,14 @@ atomic_fetch_xor :: #force_inline proc(object: ^$T, operand: T) -> T {
|
||||
}
|
||||
|
||||
atomic_fetch_xor_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T {
|
||||
switch (order) {
|
||||
case .relaxed:
|
||||
return intrinsics.atomic_xor_relaxed(object, operand)
|
||||
case .consume:
|
||||
return intrinsics.atomic_xor_acq(object, operand)
|
||||
case .acquire:
|
||||
return intrinsics.atomic_xor_acq(object, operand)
|
||||
case .release:
|
||||
return intrinsics.atomic_xor_rel(object, operand)
|
||||
case .acq_rel:
|
||||
return intrinsics.atomic_xor_acqrel(object, operand)
|
||||
case .seq_cst:
|
||||
return intrinsics.atomic_xor(object, operand)
|
||||
switch order {
|
||||
case .relaxed: return intrinsics.atomic_xor_explicit(object, operand, .Relaxed)
|
||||
case .consume: return intrinsics.atomic_xor_explicit(object, operand, .Consume)
|
||||
case .acquire: return intrinsics.atomic_xor_explicit(object, operand, .Acquire)
|
||||
case .release: return intrinsics.atomic_xor_explicit(object, operand, .Release)
|
||||
case .acq_rel: return intrinsics.atomic_xor_explicit(object, operand, .Acq_Rel)
|
||||
case: fallthrough
|
||||
case .seq_cst: return intrinsics.atomic_xor_explicit(object, operand, .Seq_Cst)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,19 +350,14 @@ atomic_fetch_and :: #force_inline proc(object: ^$T, operand: T) -> T {
|
||||
return intrinsics.atomic_and(object, operand)
|
||||
}
|
||||
atomic_fetch_and_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T {
|
||||
switch (order) {
|
||||
case .relaxed:
|
||||
return intrinsics.atomic_and_relaxed(object, operand)
|
||||
case .consume:
|
||||
return intrinsics.atomic_and_acq(object, operand)
|
||||
case .acquire:
|
||||
return intrinsics.atomic_and_acq(object, operand)
|
||||
case .release:
|
||||
return intrinsics.atomic_and_rel(object, operand)
|
||||
case .acq_rel:
|
||||
return intrinsics.atomic_and_acqrel(object, operand)
|
||||
case .seq_cst:
|
||||
return intrinsics.atomic_and(object, operand)
|
||||
switch order {
|
||||
case .relaxed: return intrinsics.atomic_and_explicit(object, operand, .Relaxed)
|
||||
case .consume: return intrinsics.atomic_and_explicit(object, operand, .Consume)
|
||||
case .acquire: return intrinsics.atomic_and_explicit(object, operand, .Acquire)
|
||||
case .release: return intrinsics.atomic_and_explicit(object, operand, .Release)
|
||||
case .acq_rel: return intrinsics.atomic_and_explicit(object, operand, .Acq_Rel)
|
||||
case: fallthrough
|
||||
case .seq_cst: return intrinsics.atomic_and_explicit(object, operand, .Seq_Cst)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-2
@@ -79,7 +79,32 @@ when ODIN_OS == .Linux {
|
||||
}
|
||||
|
||||
when ODIN_OS == .OpenBSD {
|
||||
fpos_t :: i64
|
||||
fpos_t :: distinct i64
|
||||
|
||||
_IOFBF :: 0
|
||||
_IOLBF :: 1
|
||||
_IONBF :: 1
|
||||
|
||||
BUFSIZ :: 1024
|
||||
|
||||
EOF :: int(-1)
|
||||
|
||||
FOPEN_MAX :: 20
|
||||
FILENAME_MAX :: 1024
|
||||
|
||||
SEEK_SET :: 0
|
||||
SEEK_CUR :: 1
|
||||
SEEK_END :: 2
|
||||
|
||||
foreign libc {
|
||||
stderr: ^FILE
|
||||
stdin: ^FILE
|
||||
stdout: ^FILE
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .FreeBSD {
|
||||
fpos_t :: distinct i64
|
||||
|
||||
_IOFBF :: 0
|
||||
_IOLBF :: 1
|
||||
@@ -171,7 +196,7 @@ foreign libc {
|
||||
getc :: proc(stream: ^FILE) -> int ---
|
||||
getchar :: proc() -> int ---
|
||||
putc :: proc(c: int, stream: ^FILE) -> int ---
|
||||
putchar :: proc() -> int ---
|
||||
putchar :: proc(c: int) -> int ---
|
||||
puts :: proc(s: cstring) -> int ---
|
||||
ungetc :: proc(c: int, stream: ^FILE) -> int ---
|
||||
fread :: proc(ptr: rawptr, size: size_t, nmemb: size_t, stream: ^FILE) -> size_t ---
|
||||
|
||||
@@ -8,7 +8,7 @@ tss_dtor_t :: proc "c" (rawptr)
|
||||
when ODIN_OS == .Windows {
|
||||
foreign import libc {
|
||||
"system:libucrt.lib",
|
||||
"system:msvcprt.lib"
|
||||
"system:msvcprt.lib",
|
||||
}
|
||||
|
||||
thrd_success :: 0 // _Thrd_success
|
||||
@@ -77,7 +77,7 @@ when ODIN_OS == .Windows {
|
||||
when ODIN_OS == .Linux {
|
||||
foreign import libc {
|
||||
"system:c",
|
||||
"system:pthread"
|
||||
"system:pthread",
|
||||
}
|
||||
|
||||
thrd_success :: 0
|
||||
|
||||
@@ -13,21 +13,23 @@ when ODIN_OS == .Windows {
|
||||
when ODIN_OS == .Windows {
|
||||
wctrans_t :: distinct wchar_t
|
||||
wctype_t :: distinct ushort
|
||||
}
|
||||
|
||||
when ODIN_OS == .Linux {
|
||||
} else when ODIN_OS == .Linux {
|
||||
wctrans_t :: distinct intptr_t
|
||||
wctype_t :: distinct ulong
|
||||
}
|
||||
|
||||
when ODIN_OS == .Darwin {
|
||||
} else when ODIN_OS == .Darwin {
|
||||
wctrans_t :: distinct int
|
||||
wctype_t :: distinct u32
|
||||
}
|
||||
|
||||
when ODIN_OS == .OpenBSD {
|
||||
} else when ODIN_OS == .OpenBSD {
|
||||
wctrans_t :: distinct rawptr
|
||||
wctype_t :: distinct rawptr
|
||||
|
||||
} else when ODIN_OS == .FreeBSD {
|
||||
wctrans_t :: distinct int
|
||||
wctype_t :: distinct ulong
|
||||
|
||||
}
|
||||
|
||||
@(default_calling_convention="c")
|
||||
|
||||
@@ -47,7 +47,7 @@ when size_of(uintptr) == 8 {
|
||||
}
|
||||
|
||||
|
||||
Error :: union {
|
||||
Error :: union #shared_nil {
|
||||
General_Error,
|
||||
Deflate_Error,
|
||||
ZLIB_Error,
|
||||
@@ -58,6 +58,7 @@ Error :: union {
|
||||
}
|
||||
|
||||
General_Error :: enum {
|
||||
None = 0,
|
||||
File_Not_Found,
|
||||
Cannot_Open_File,
|
||||
File_Too_Short,
|
||||
@@ -76,6 +77,7 @@ General_Error :: enum {
|
||||
}
|
||||
|
||||
GZIP_Error :: enum {
|
||||
None = 0,
|
||||
Invalid_GZIP_Signature,
|
||||
Reserved_Flag_Set,
|
||||
Invalid_Extra_Data,
|
||||
@@ -100,6 +102,7 @@ GZIP_Error :: enum {
|
||||
}
|
||||
|
||||
ZIP_Error :: enum {
|
||||
None = 0,
|
||||
Invalid_ZIP_File_Signature,
|
||||
Unexpected_Signature,
|
||||
Insert_Next_Disk,
|
||||
@@ -107,6 +110,7 @@ ZIP_Error :: enum {
|
||||
}
|
||||
|
||||
ZLIB_Error :: enum {
|
||||
None = 0,
|
||||
Unsupported_Window_Size,
|
||||
FDICT_Unsupported,
|
||||
Unsupported_Compression_Level,
|
||||
@@ -114,6 +118,7 @@ ZLIB_Error :: enum {
|
||||
}
|
||||
|
||||
Deflate_Error :: enum {
|
||||
None = 0,
|
||||
Huffman_Bad_Sizes,
|
||||
Huffman_Bad_Code_Lengths,
|
||||
Inflate_Error,
|
||||
@@ -123,7 +128,6 @@ Deflate_Error :: enum {
|
||||
BType_3,
|
||||
}
|
||||
|
||||
|
||||
// General I/O context for ZLIB, LZW, etc.
|
||||
Context_Memory_Input :: struct #packed {
|
||||
input_data: []u8,
|
||||
@@ -139,7 +143,12 @@ Context_Memory_Input :: struct #packed {
|
||||
size_packed: i64,
|
||||
size_unpacked: i64,
|
||||
}
|
||||
#assert(size_of(Context_Memory_Input) == 64)
|
||||
when size_of(rawptr) == 8 {
|
||||
#assert(size_of(Context_Memory_Input) == 64)
|
||||
} else {
|
||||
// e.g. `-target:windows_i386`
|
||||
#assert(size_of(Context_Memory_Input) == 52)
|
||||
}
|
||||
|
||||
Context_Stream_Input :: struct #packed {
|
||||
input_data: []u8,
|
||||
@@ -174,8 +183,6 @@ Context_Stream_Input :: struct #packed {
|
||||
This simplifies end-of-stream handling where bits may be left in the bit buffer.
|
||||
*/
|
||||
|
||||
// TODO: Make these return compress.Error errors.
|
||||
|
||||
input_size_from_memory :: proc(z: ^Context_Memory_Input) -> (res: i64, err: Error) {
|
||||
return i64(len(z.input_data)), nil
|
||||
}
|
||||
@@ -287,6 +294,24 @@ peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T)
|
||||
|
||||
#no_bounds_check {
|
||||
if len(z.input_data) >= size + offset {
|
||||
buf := z.input_data[offset:][:size]
|
||||
return (^T)(&buf[0])^, .None
|
||||
}
|
||||
}
|
||||
|
||||
if len(z.input_data) == 0 {
|
||||
return T{}, .EOF
|
||||
} else {
|
||||
return T{}, .Short_Buffer
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T)
|
||||
@@ -314,7 +339,44 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid
|
||||
return res, .None
|
||||
}
|
||||
|
||||
peek_data :: proc{peek_data_from_memory, peek_data_from_stream}
|
||||
@(optimization_mode="speed")
|
||||
peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T)
|
||||
|
||||
// Get current position to return to.
|
||||
cur_pos, e1 := z.input->impl_seek(0, .Current)
|
||||
if e1 != .None {
|
||||
return T{}, e1
|
||||
}
|
||||
|
||||
// Seek to offset.
|
||||
pos, e2 := z.input->impl_seek(offset, .Start)
|
||||
if e2 != .None {
|
||||
return T{}, e2
|
||||
}
|
||||
|
||||
r, e3 := io.to_reader_at(z.input)
|
||||
if !e3 {
|
||||
return T{}, .Empty
|
||||
}
|
||||
when size <= 128 {
|
||||
b: [size]u8
|
||||
} else {
|
||||
b := make([]u8, size, context.temp_allocator)
|
||||
}
|
||||
_, e4 := io.read_at(r, b[:], pos)
|
||||
if e4 != .None {
|
||||
return T{}, .Empty
|
||||
}
|
||||
|
||||
// Return read head to original position.
|
||||
z.input->impl_seek(cur_pos, .Start)
|
||||
|
||||
res = (^T)(&b[0])^
|
||||
return res, .None
|
||||
}
|
||||
|
||||
peek_data :: proc{peek_data_from_memory, peek_data_from_stream, peek_data_at_offset_from_memory, peek_data_at_offset_from_stream}
|
||||
|
||||
|
||||
|
||||
@@ -473,4 +535,4 @@ discard_to_next_byte_lsb_from_stream :: proc(z: ^Context_Stream_Input) {
|
||||
consume_bits_lsb(z, discard)
|
||||
}
|
||||
|
||||
discard_to_next_byte_lsb :: proc{discard_to_next_byte_lsb_from_memory, discard_to_next_byte_lsb_from_stream};
|
||||
discard_to_next_byte_lsb :: proc{discard_to_next_byte_lsb_from_memory, discard_to_next_byte_lsb_from_stream}
|
||||
|
||||
@@ -45,7 +45,7 @@ main :: proc() {
|
||||
|
||||
if len(args) < 2 {
|
||||
stderr("No input file specified.\n")
|
||||
err := load(slice=TEST, buf=&buf, known_gzip_size=len(TEST))
|
||||
err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST))
|
||||
if err == nil {
|
||||
stdout("Displaying test vector: ")
|
||||
stdout(bytes.buffer_to_string(&buf))
|
||||
|
||||
@@ -100,9 +100,9 @@ E_GZIP :: compress.GZIP_Error
|
||||
E_ZLIB :: compress.ZLIB_Error
|
||||
E_Deflate :: compress.Deflate_Error
|
||||
|
||||
GZIP_MAX_PAYLOAD_SIZE :: int(max(u32le))
|
||||
GZIP_MAX_PAYLOAD_SIZE :: i64(max(u32le))
|
||||
|
||||
load :: proc{load_from_slice, load_from_file, load_from_context}
|
||||
load :: proc{load_from_bytes, load_from_file, load_from_context}
|
||||
|
||||
load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
@@ -112,16 +112,16 @@ load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_siz
|
||||
|
||||
err = E_General.File_Not_Found
|
||||
if ok {
|
||||
err = load_from_slice(data, buf, len(data), expected_output_size)
|
||||
err = load_from_bytes(data, buf, len(data), expected_output_size)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
load_from_slice :: proc(slice: []u8, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
|
||||
load_from_bytes :: proc(data: []byte, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
|
||||
buf := buf
|
||||
|
||||
z := &compress.Context_Memory_Input{
|
||||
input_data = slice,
|
||||
input_data = data,
|
||||
output = buf,
|
||||
}
|
||||
return load_from_context(z, buf, known_gzip_size, expected_output_size, allocator)
|
||||
@@ -136,7 +136,7 @@ load_from_context :: proc(z: ^$C, buf: ^bytes.Buffer, known_gzip_size := -1, exp
|
||||
|
||||
z.output = buf
|
||||
|
||||
if expected_output_size > GZIP_MAX_PAYLOAD_SIZE {
|
||||
if i64(expected_output_size) > i64(GZIP_MAX_PAYLOAD_SIZE) {
|
||||
return E_GZIP.Payload_Size_Exceeds_Max_Payload
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
This file was generated, so don't edit this by hand.
|
||||
Transliterated from https://github.com/Ed-von-Schleck/shoco/blob/master/shoco_model.h,
|
||||
which is an English word model.
|
||||
*/
|
||||
|
||||
// package shoco is an implementation of the shoco short string compressor
|
||||
package shoco
|
||||
|
||||
DEFAULT_MODEL :: Shoco_Model {
|
||||
min_char = 39,
|
||||
max_char = 122,
|
||||
characters_by_id = {
|
||||
'e', 'a', 'i', 'o', 't', 'h', 'n', 'r', 's', 'l', 'u', 'c', 'w', 'm', 'd', 'b', 'p', 'f', 'g', 'v', 'y', 'k', '-', 'H', 'M', 'T', '\'', 'B', 'x', 'I', 'W', 'L',
|
||||
},
|
||||
ids_by_character = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 27, -1, -1, -1, -1, -1, 23, 29, -1, -1, 31, 24, -1, -1, -1, -1, -1, -1, 25, -1, -1, 30, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 15, 11, 14, 0, 17, 18, 5, 2, -1, 21, 9, 13, 6, 3, 16, -1, 7, 8, 4, 10, 19, 12, 28, 20, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
},
|
||||
successors_by_bigram = {
|
||||
7, 4, 12, -1, 6, -1, 1, 0, 3, 5, -1, 9, -1, 8, 2, -1, 15, 14, -1, 10, 11, -1, -1, -1, -1, -1, -1, -1, 13, -1, -1, -1,
|
||||
1, -1, 6, -1, 1, -1, 0, 3, 2, 4, 15, 11, -1, 9, 5, 10, 13, -1, 12, 8, 7, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
9, 11, -1, 4, 2, -1, 0, 8, 1, 5, -1, 6, -1, 3, 7, 15, -1, 12, 10, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, 14, 7, 5, -1, 1, 2, 8, 9, 0, 15, 6, 4, 11, -1, 12, 3, -1, 10, -1, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
2, 4, 3, 1, 5, 0, -1, 6, 10, 9, 7, 12, 11, -1, -1, -1, -1, 13, -1, -1, 8, -1, 15, -1, -1, -1, 14, -1, -1, -1, -1, -1,
|
||||
0, 1, 2, 3, 4, -1, -1, 5, 9, 10, 6, -1, -1, 8, 15, 11, -1, 14, -1, -1, 7, -1, 13, -1, -1, -1, 12, -1, -1, -1, -1, -1,
|
||||
2, 8, 7, 4, 3, -1, 9, -1, 6, 11, -1, 5, -1, -1, 0, -1, -1, 14, 1, 15, 10, 12, -1, -1, -1, -1, 13, -1, -1, -1, -1, -1,
|
||||
0, 3, 1, 2, 6, -1, 9, 8, 4, 12, 13, 10, -1, 11, 7, -1, -1, 15, 14, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
0, 6, 3, 4, 1, 2, -1, -1, 5, 10, 7, 9, 11, 12, -1, -1, 8, 14, -1, -1, 15, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
0, 6, 2, 5, 9, -1, -1, -1, 10, 1, 8, -1, 12, 14, 4, -1, 15, 7, -1, 13, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
8, 10, 9, 15, 1, -1, 4, 0, 3, 2, -1, 6, -1, 12, 11, 13, 7, 14, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
1, 3, 6, 0, 4, 2, -1, 7, 13, 8, 9, 11, -1, -1, 15, -1, -1, -1, -1, -1, 10, 5, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
3, 0, 1, 4, -1, 2, 5, 6, 7, 8, -1, 14, -1, -1, 9, 15, -1, 12, -1, -1, -1, 10, 11, -1, -1, -1, 13, -1, -1, -1, -1, -1,
|
||||
0, 1, 3, 2, 15, -1, 12, -1, 7, 14, 4, -1, -1, 9, -1, 8, 5, 10, -1, -1, 6, -1, 13, -1, -1, -1, 11, -1, -1, -1, -1, -1,
|
||||
0, 3, 1, 2, -1, -1, 12, 6, 4, 9, 7, -1, -1, 14, 8, -1, -1, 15, 11, 13, 5, -1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
0, 5, 7, 2, 10, 13, -1, 6, 8, 1, 3, -1, -1, 14, 15, 11, -1, -1, -1, 12, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
0, 2, 6, 3, 7, 10, -1, 1, 9, 4, 8, -1, -1, 15, -1, 12, 5, -1, -1, -1, 11, -1, 13, -1, -1, -1, 14, -1, -1, -1, -1, -1,
|
||||
1, 3, 4, 0, 7, -1, 12, 2, 11, 8, 6, 13, -1, -1, -1, -1, -1, 5, -1, -1, 10, 15, 9, -1, -1, -1, 14, -1, -1, -1, -1, -1,
|
||||
1, 3, 5, 2, 13, 0, 9, 4, 7, 6, 8, -1, -1, 15, -1, 11, -1, -1, 10, -1, 14, -1, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
0, 2, 1, 3, -1, -1, -1, 6, -1, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
1, 11, 4, 0, 3, -1, 13, 12, 2, 7, -1, -1, 15, 10, 5, 8, 14, -1, -1, -1, -1, -1, 9, -1, -1, -1, 6, -1, -1, -1, -1, -1,
|
||||
0, 9, 2, 14, 15, 4, 1, 13, 3, 5, -1, -1, 10, -1, -1, -1, -1, 6, 12, -1, 7, -1, 8, -1, -1, -1, 11, -1, -1, -1, -1, -1,
|
||||
-1, 2, 14, -1, 1, 5, 8, 7, 4, 12, -1, 6, 9, 11, 13, 3, 10, 15, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
0, 1, 3, 2, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
4, 3, 1, 5, -1, -1, -1, 0, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
2, 8, 4, 1, -1, 0, -1, 6, -1, -1, 5, -1, 7, -1, -1, -1, -1, -1, -1, -1, 10, -1, -1, 9, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
12, 5, -1, -1, 1, -1, -1, 7, 0, 3, -1, 2, -1, 4, 6, -1, -1, -1, -1, 8, -1, -1, 15, -1, 13, 9, -1, -1, -1, -1, -1, 11,
|
||||
1, 3, 2, 4, -1, -1, -1, 5, -1, 7, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, 8, -1, -1,
|
||||
5, 3, 4, 12, 1, 6, -1, -1, -1, -1, 8, 2, -1, -1, -1, -1, 0, 9, -1, -1, 11, -1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, 0, -1, 1, 12, 3, -1, -1, -1, -1, 5, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, 6, -1, 10,
|
||||
2, 3, 1, 4, -1, 0, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1,
|
||||
5, 1, 3, 0, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 9, -1, -1, 6, -1, 7,
|
||||
},
|
||||
successors_reversed = {
|
||||
's', 't', 'c', 'l', 'm', 'a', 'd', 'r', 'v', 'T', 'A', 'L', 'e', 'M', 'Y', '-',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'-', 't', 'a', 'b', 's', 'h', 'c', 'r', 'n', 'w', 'p', 'm', 'l', 'd', 'i', 'f',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'u', 'e', 'i', 'a', 'o', 'r', 'y', 'l', 'I', 'E', 'R', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'e', 'a', 'o', 'i', 'u', 'A', 'y', 'E', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
't', 'n', 'f', 's', '\'', 'm', 'I', 'N', 'A', 'E', 'L', 'Z', 'r', 'V', 'R', 'C',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'o', 'a', 'y', 'i', 'u', 'e', 'I', 'L', 'D', '\'', 'E', 'Y', '\x00', '\x00', '\x00', '\x00',
|
||||
'r', 'i', 'y', 'a', 'e', 'o', 'u', 'Y', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'h', 'o', 'e', 'E', 'i', 'u', 'r', 'w', 'a', 'H', 'y', 'R', 'Z', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'h', 'i', 'e', 'a', 'o', 'r', 'I', 'y', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'n', 't', 's', 'r', 'l', 'd', 'i', 'y', 'v', 'm', 'b', 'c', 'g', 'p', 'k', 'u',
|
||||
'e', 'l', 'o', 'u', 'y', 'a', 'r', 'i', 's', 'j', 't', 'b', 'v', 'h', 'm', 'd',
|
||||
'o', 'e', 'h', 'a', 't', 'k', 'i', 'r', 'l', 'u', 'y', 'c', 'q', 's', '-', 'd',
|
||||
'e', 'i', 'o', 'a', 's', 'y', 'r', 'u', 'd', 'l', '-', 'g', 'n', 'v', 'm', 'f',
|
||||
'r', 'n', 'd', 's', 'a', 'l', 't', 'e', 'm', 'c', 'v', 'y', 'i', 'x', 'f', 'p',
|
||||
'o', 'e', 'r', 'a', 'i', 'f', 'u', 't', 'l', '-', 'y', 's', 'n', 'c', '\'', 'k',
|
||||
'h', 'e', 'o', 'a', 'r', 'i', 'l', 's', 'u', 'n', 'g', 'b', '-', 't', 'y', 'm',
|
||||
'e', 'a', 'i', 'o', 't', 'r', 'u', 'y', 'm', 's', 'l', 'b', '\'', '-', 'f', 'd',
|
||||
'n', 's', 't', 'm', 'o', 'l', 'c', 'd', 'r', 'e', 'g', 'a', 'f', 'v', 'z', 'b',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'e', 'n', 'i', 's', 'h', 'l', 'f', 'y', '-', 'a', 'w', '\'', 'g', 'r', 'o', 't',
|
||||
'e', 'l', 'i', 'y', 'd', 'o', 'a', 'f', 'u', 't', 's', 'k', 'w', 'v', 'm', 'p',
|
||||
'e', 'a', 'o', 'i', 'u', 'p', 'y', 's', 'b', 'm', 'f', '\'', 'n', '-', 'l', 't',
|
||||
'd', 'g', 'e', 't', 'o', 'c', 's', 'i', 'a', 'n', 'y', 'l', 'k', '\'', 'f', 'v',
|
||||
'u', 'n', 'r', 'f', 'm', 't', 'w', 'o', 's', 'l', 'v', 'd', 'p', 'k', 'i', 'c',
|
||||
'e', 'r', 'a', 'o', 'l', 'p', 'i', 't', 'u', 's', 'h', 'y', 'b', '-', '\'', 'm',
|
||||
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'e', 'i', 'o', 'a', 's', 'y', 't', 'd', 'r', 'n', 'c', 'm', 'l', 'u', 'g', 'f',
|
||||
'e', 't', 'h', 'i', 'o', 's', 'a', 'u', 'p', 'c', 'l', 'w', 'm', 'k', 'f', 'y',
|
||||
'h', 'o', 'e', 'i', 'a', 't', 'r', 'u', 'y', 'l', 's', 'w', 'c', 'f', '\'', '-',
|
||||
'r', 't', 'l', 's', 'n', 'g', 'c', 'p', 'e', 'i', 'a', 'd', 'm', 'b', 'f', 'o',
|
||||
'e', 'i', 'a', 'o', 'y', 'u', 'r', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
'a', 'i', 'h', 'e', 'o', 'n', 'r', 's', 'l', 'd', 'k', '-', 'f', '\'', 'c', 'b',
|
||||
'p', 't', 'c', 'a', 'i', 'e', 'h', 'q', 'u', 'f', '-', 'y', 'o', '\x00', '\x00', '\x00',
|
||||
'o', 'e', 's', 't', 'i', 'd', '\'', 'l', 'b', '-', 'm', 'a', 'r', 'n', 'p', 'w',
|
||||
},
|
||||
|
||||
character_count = 32,
|
||||
successor_count = 16,
|
||||
|
||||
max_successor_n = 7,
|
||||
packs = {
|
||||
{ 0x80000000, 1, 2, { 26, 24, 24, 24, 24, 24, 24, 24 }, { 15, 3, 0, 0, 0, 0, 0, 0 }, 0xc0, 0x80 },
|
||||
{ 0xc0000000, 2, 4, { 25, 22, 19, 16, 16, 16, 16, 16 }, { 15, 7, 7, 7, 0, 0, 0, 0 }, 0xe0, 0xc0 },
|
||||
{ 0xe0000000, 4, 8, { 23, 19, 15, 11, 8, 5, 2, 0 }, { 31, 15, 15, 15, 7, 7, 7, 3 }, 0xf0, 0xe0 },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
An implementation of [shoco](https://github.com/Ed-von-Schleck/shoco) by Christian Schramm.
|
||||
*/
|
||||
|
||||
// package shoco is an implementation of the shoco short string compressor
|
||||
package shoco
|
||||
|
||||
import "core:intrinsics"
|
||||
import "core:compress"
|
||||
|
||||
Shoco_Pack :: struct {
|
||||
word: u32,
|
||||
bytes_packed: i8,
|
||||
bytes_unpacked: i8,
|
||||
offsets: [8]u16,
|
||||
masks: [8]i16,
|
||||
header_mask: u8,
|
||||
header: u8,
|
||||
}
|
||||
|
||||
Shoco_Model :: struct {
|
||||
min_char: u8,
|
||||
max_char: u8,
|
||||
characters_by_id: []u8,
|
||||
ids_by_character: [256]i16,
|
||||
successors_by_bigram: []i8,
|
||||
successors_reversed: []u8,
|
||||
|
||||
character_count: u8,
|
||||
successor_count: u8,
|
||||
max_successor_n: i8,
|
||||
packs: []Shoco_Pack,
|
||||
}
|
||||
|
||||
compress_bound :: proc(uncompressed_size: int) -> (worst_case_compressed_size: int) {
|
||||
// Worst case compression happens when input is non-ASCII (128-255)
|
||||
// Encoded as 0x00 + the byte in question.
|
||||
return uncompressed_size * 2
|
||||
}
|
||||
|
||||
decompress_bound :: proc(compressed_size: int, model := DEFAULT_MODEL) -> (maximum_decompressed_size: int) {
|
||||
// Best case compression is 2:1
|
||||
most: f64
|
||||
for pack in model.packs {
|
||||
val := f64(compressed_size) / f64(pack.bytes_packed) * f64(pack.bytes_unpacked)
|
||||
most = max(most, val)
|
||||
}
|
||||
return int(most)
|
||||
}
|
||||
|
||||
find_best_encoding :: proc(indices: []i16, n_consecutive: i8, model := DEFAULT_MODEL) -> (res: int) {
|
||||
for p := len(model.packs); p > 0; p -= 1 {
|
||||
pack := model.packs[p - 1]
|
||||
if n_consecutive >= pack.bytes_unpacked {
|
||||
have_index := true
|
||||
for i := 0; i < int(pack.bytes_unpacked); i += 1 {
|
||||
if indices[i] > pack.masks[i] {
|
||||
have_index = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if have_index {
|
||||
return p - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
validate_model :: proc(model: Shoco_Model) -> (int, compress.Error) {
|
||||
if len(model.characters_by_id) != int(model.character_count) {
|
||||
return 0, .Unknown_Compression_Method
|
||||
}
|
||||
|
||||
if len(model.successors_by_bigram) != int(model.character_count) * int(model.character_count) {
|
||||
return 0, .Unknown_Compression_Method
|
||||
}
|
||||
|
||||
if len(model.successors_reversed) != int(model.successor_count) * int(model.max_char - model.min_char) {
|
||||
return 0, .Unknown_Compression_Method
|
||||
}
|
||||
|
||||
// Model seems legit.
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Decompresses into provided buffer.
|
||||
decompress_slice_to_output_buffer :: proc(input: []u8, output: []u8, model := DEFAULT_MODEL) -> (size: int, err: compress.Error) {
|
||||
inp, inp_end := 0, len(input)
|
||||
out, out_end := 0, len(output)
|
||||
|
||||
validate_model(model) or_return
|
||||
|
||||
for inp < inp_end {
|
||||
val := transmute(i8)input[inp]
|
||||
mark := int(-1)
|
||||
|
||||
for val < 0 {
|
||||
val <<= 1
|
||||
mark += 1
|
||||
}
|
||||
|
||||
if mark > len(model.packs) {
|
||||
return out, .Unknown_Compression_Method
|
||||
}
|
||||
|
||||
if mark < 0 {
|
||||
if out >= out_end {
|
||||
return out, .Output_Too_Short
|
||||
}
|
||||
|
||||
// Ignore the sentinel value for non-ASCII chars
|
||||
if input[inp] == 0x00 {
|
||||
inp += 1
|
||||
if inp >= inp_end {
|
||||
return out, .Stream_Too_Short
|
||||
}
|
||||
}
|
||||
output[out] = input[inp]
|
||||
inp, out = inp + 1, out + 1
|
||||
|
||||
} else {
|
||||
pack := model.packs[mark]
|
||||
|
||||
if out + int(pack.bytes_unpacked) > out_end {
|
||||
return out, .Output_Too_Short
|
||||
} else if inp + int(pack.bytes_packed) > inp_end {
|
||||
return out, .Stream_Too_Short
|
||||
}
|
||||
|
||||
code := intrinsics.unaligned_load((^u32)(&input[inp]))
|
||||
when ODIN_ENDIAN == .Little {
|
||||
code = intrinsics.byte_swap(code)
|
||||
}
|
||||
|
||||
// Unpack the leading char
|
||||
offset := pack.offsets[0]
|
||||
mask := pack.masks[0]
|
||||
|
||||
last_chr := model.characters_by_id[(code >> offset) & u32(mask)]
|
||||
output[out] = last_chr
|
||||
|
||||
// Unpack the successor chars
|
||||
for i := 1; i < int(pack.bytes_unpacked); i += 1 {
|
||||
offset = pack.offsets[i]
|
||||
mask = pack.masks[i]
|
||||
|
||||
index_major := u32(last_chr - model.min_char) * u32(model.successor_count)
|
||||
index_minor := (code >> offset) & u32(mask)
|
||||
|
||||
last_chr = model.successors_reversed[index_major + index_minor]
|
||||
|
||||
output[out + i] = last_chr
|
||||
}
|
||||
|
||||
out += int(pack.bytes_unpacked)
|
||||
inp += int(pack.bytes_packed)
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
decompress_slice_to_string :: proc(input: []u8, model := DEFAULT_MODEL, allocator := context.allocator) -> (res: string, err: compress.Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if len(input) == 0 {
|
||||
return "", .Stream_Too_Short
|
||||
}
|
||||
|
||||
max_output_size := decompress_bound(len(input), model)
|
||||
|
||||
buf: [dynamic]u8
|
||||
if !resize(&buf, max_output_size) {
|
||||
return "", .Out_Of_Memory
|
||||
}
|
||||
|
||||
length, result := decompress_slice_to_output_buffer(input, buf[:])
|
||||
resize(&buf, length)
|
||||
return string(buf[:]), result
|
||||
}
|
||||
decompress :: proc{decompress_slice_to_output_buffer, decompress_slice_to_string}
|
||||
|
||||
compress_string_to_buffer :: proc(input: string, output: []u8, model := DEFAULT_MODEL, allocator := context.allocator) -> (size: int, err: compress.Error) {
|
||||
inp, inp_end := 0, len(input)
|
||||
out, out_end := 0, len(output)
|
||||
output := output
|
||||
|
||||
validate_model(model) or_return
|
||||
|
||||
indices := make([]i16, model.max_successor_n + 1)
|
||||
defer delete(indices)
|
||||
|
||||
last_resort := false
|
||||
|
||||
encode: for inp < inp_end {
|
||||
if last_resort {
|
||||
last_resort = false
|
||||
|
||||
if input[inp] & 0x80 == 0x80 {
|
||||
// Non-ASCII case
|
||||
if out + 2 > out_end {
|
||||
return out, .Output_Too_Short
|
||||
}
|
||||
|
||||
// Put in a sentinel byte
|
||||
output[out] = 0x00
|
||||
out += 1
|
||||
} else {
|
||||
// An ASCII byte
|
||||
if out + 1 > out_end {
|
||||
return out, .Output_Too_Short
|
||||
}
|
||||
}
|
||||
output[out] = input[inp]
|
||||
out, inp = out + 1, inp + 1
|
||||
} else {
|
||||
// Find the longest string of known successors
|
||||
indices[0] = model.ids_by_character[input[inp]]
|
||||
last_chr_index := indices[0]
|
||||
|
||||
if last_chr_index < 0 {
|
||||
last_resort = true
|
||||
continue encode
|
||||
}
|
||||
|
||||
rest := inp_end - inp
|
||||
n_consecutive: i8 = 1
|
||||
for ; n_consecutive <= model.max_successor_n; n_consecutive += 1 {
|
||||
if inp_end > 0 && int(n_consecutive) == rest {
|
||||
break
|
||||
}
|
||||
|
||||
current_index := model.ids_by_character[input[inp + int(n_consecutive)]]
|
||||
if current_index < 0 { // '\0' is always -1
|
||||
break
|
||||
}
|
||||
|
||||
successor_index := model.successors_by_bigram[last_chr_index * i16(model.character_count) + current_index]
|
||||
if successor_index < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
indices[n_consecutive] = i16(successor_index)
|
||||
last_chr_index = current_index
|
||||
}
|
||||
|
||||
if n_consecutive < 2 {
|
||||
last_resort = true
|
||||
continue encode
|
||||
}
|
||||
|
||||
pack_n := find_best_encoding(indices, n_consecutive)
|
||||
if pack_n >= 0 {
|
||||
if out + int(model.packs[pack_n].bytes_packed) > out_end {
|
||||
return out, .Output_Too_Short
|
||||
}
|
||||
|
||||
pack := model.packs[pack_n]
|
||||
code := pack.word
|
||||
|
||||
for i := 0; i < int(pack.bytes_unpacked); i += 1 {
|
||||
code |= u32(indices[i]) << pack.offsets[i]
|
||||
}
|
||||
|
||||
// In the little-endian world, we need to swap what's in the register to match the memory representation.
|
||||
when ODIN_ENDIAN == .Little {
|
||||
code = intrinsics.byte_swap(code)
|
||||
}
|
||||
out_ptr := raw_data(output[out:])
|
||||
|
||||
switch pack.bytes_packed {
|
||||
case 4:
|
||||
intrinsics.unaligned_store(transmute(^u32)out_ptr, code)
|
||||
case 2:
|
||||
intrinsics.unaligned_store(transmute(^u16)out_ptr, u16(code))
|
||||
case 1:
|
||||
intrinsics.unaligned_store(transmute(^u8)out_ptr, u8(code))
|
||||
case:
|
||||
return out, .Unknown_Compression_Method
|
||||
}
|
||||
|
||||
out += int(pack.bytes_packed)
|
||||
inp += int(pack.bytes_unpacked)
|
||||
} else {
|
||||
last_resort = true
|
||||
continue encode
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
compress_string :: proc(input: string, model := DEFAULT_MODEL, allocator := context.allocator) -> (output: []u8, err: compress.Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if len(input) == 0 {
|
||||
return {}, .Stream_Too_Short
|
||||
}
|
||||
|
||||
max_output_size := compress_bound(len(input))
|
||||
|
||||
buf: [dynamic]u8
|
||||
if !resize(&buf, max_output_size) {
|
||||
return {}, .Out_Of_Memory
|
||||
}
|
||||
|
||||
length, result := compress_string_to_buffer(input, buf[:])
|
||||
resize(&buf, length)
|
||||
return buf[:length], result
|
||||
}
|
||||
compress :: proc{compress_string_to_buffer, compress_string}
|
||||
@@ -47,10 +47,10 @@ Options :: struct {
|
||||
level: u8,
|
||||
}
|
||||
|
||||
Error :: compress.Error
|
||||
E_General :: compress.General_Error
|
||||
E_ZLIB :: compress.ZLIB_Error
|
||||
E_Deflate :: compress.Deflate_Error
|
||||
Error :: compress.Error
|
||||
General_Error :: compress.General_Error
|
||||
ZLIB_Error :: compress.ZLIB_Error
|
||||
Deflate_Error :: compress.Deflate_Error
|
||||
|
||||
DEFLATE_MAX_CHUNK_SIZE :: 65535
|
||||
DEFLATE_MAX_LITERAL_SIZE :: 65535
|
||||
@@ -258,7 +258,7 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
|
||||
for i in 1 ..< HUFFMAN_MAX_BITS {
|
||||
if sizes[i] > (1 << uint(i)) {
|
||||
return E_Deflate.Huffman_Bad_Sizes
|
||||
return .Huffman_Bad_Sizes
|
||||
}
|
||||
}
|
||||
code := int(0)
|
||||
@@ -270,7 +270,7 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
code = code + sizes[i]
|
||||
if sizes[i] != 0 {
|
||||
if code - 1 >= (1 << u16(i)) {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths
|
||||
return .Huffman_Bad_Code_Lengths
|
||||
}
|
||||
}
|
||||
z.maxcode[i] = code << (HUFFMAN_MAX_BITS - uint(i))
|
||||
@@ -314,15 +314,15 @@ decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Erro
|
||||
s += 1
|
||||
}
|
||||
if s >= 16 {
|
||||
return 0, E_Deflate.Bad_Huffman_Code
|
||||
return 0, .Bad_Huffman_Code
|
||||
}
|
||||
// code size is s, so:
|
||||
b := (k >> (16-s)) - int(t.firstcode[s]) + int(t.firstsymbol[s])
|
||||
if b >= size_of(t.size) {
|
||||
return 0, E_Deflate.Bad_Huffman_Code
|
||||
return 0, .Bad_Huffman_Code
|
||||
}
|
||||
if t.size[b] != s {
|
||||
return 0, E_Deflate.Bad_Huffman_Code
|
||||
return 0, .Bad_Huffman_Code
|
||||
}
|
||||
|
||||
compress.consume_bits_lsb(z, s)
|
||||
@@ -335,11 +335,11 @@ decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Erro
|
||||
decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
|
||||
if z.num_bits < 16 {
|
||||
if z.num_bits > 63 {
|
||||
return 0, E_ZLIB.Code_Buffer_Malformed
|
||||
return 0, .Code_Buffer_Malformed
|
||||
}
|
||||
compress.refill_lsb(z)
|
||||
if z.num_bits > 63 {
|
||||
return 0, E_General.Stream_Too_Short
|
||||
return 0, .Stream_Too_Short
|
||||
}
|
||||
}
|
||||
#no_bounds_check b := t.fast[z.code_buffer & ZFAST_MASK]
|
||||
@@ -361,7 +361,7 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err:
|
||||
if value < 256 {
|
||||
e := write_byte(z, u8(value))
|
||||
if e != .None {
|
||||
return E_General.Output_Too_Short
|
||||
return .Output_Too_Short
|
||||
}
|
||||
} else {
|
||||
if value == 256 {
|
||||
@@ -377,7 +377,7 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err:
|
||||
|
||||
value, e = decode_huffman(z, z_offset)
|
||||
if e != nil {
|
||||
return E_Deflate.Bad_Huffman_Code
|
||||
return .Bad_Huffman_Code
|
||||
}
|
||||
|
||||
distance := Z_DIST_BASE[value]
|
||||
@@ -387,7 +387,7 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err:
|
||||
|
||||
if z.bytes_written < i64(distance) {
|
||||
// Distance is longer than we've decoded so far.
|
||||
return E_Deflate.Bad_Distance
|
||||
return .Bad_Distance
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -405,14 +405,14 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err:
|
||||
c := z.output.buf[z.bytes_written - i64(distance)]
|
||||
e := repl_byte(z, length, c)
|
||||
if e != .None {
|
||||
return E_General.Output_Too_Short
|
||||
return .Output_Too_Short
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if length > 0 {
|
||||
e := repl_bytes(z, length, distance)
|
||||
if e != .None {
|
||||
return E_General.Output_Too_Short
|
||||
return .Output_Too_Short
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -432,25 +432,25 @@ inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := f
|
||||
if !raw {
|
||||
size, size_err := compress.input_size(ctx)
|
||||
if size < 6 || size_err != nil {
|
||||
return E_General.Stream_Too_Short
|
||||
return .Stream_Too_Short
|
||||
}
|
||||
|
||||
cmf, _ := compress.read_u8(ctx)
|
||||
|
||||
method := Compression_Method(cmf & 0xf)
|
||||
if method != .DEFLATE {
|
||||
return E_General.Unknown_Compression_Method
|
||||
return .Unknown_Compression_Method
|
||||
}
|
||||
|
||||
if cinfo := (cmf >> 4) & 0xf; cinfo > 7 {
|
||||
return E_ZLIB.Unsupported_Window_Size
|
||||
return .Unsupported_Window_Size
|
||||
}
|
||||
flg, _ := compress.read_u8(ctx)
|
||||
|
||||
fcheck := flg & 0x1f
|
||||
fcheck_computed := (cmf << 8 | flg) & 0x1f
|
||||
if fcheck != fcheck_computed {
|
||||
return E_General.Checksum_Failed
|
||||
return .Checksum_Failed
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -458,7 +458,7 @@ inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := f
|
||||
They're application specific and PNG doesn't use them.
|
||||
*/
|
||||
if fdict := (flg >> 5) & 1; fdict != 0 {
|
||||
return E_ZLIB.FDICT_Unsupported
|
||||
return .FDICT_Unsupported
|
||||
}
|
||||
|
||||
// flevel := Compression_Level((flg >> 6) & 3);
|
||||
@@ -485,7 +485,7 @@ inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := f
|
||||
output_hash := hash.adler32(ctx.output.buf[:])
|
||||
|
||||
if output_hash != u32(adler) {
|
||||
return E_General.Checksum_Failed
|
||||
return .Checksum_Failed
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -555,7 +555,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all
|
||||
|
||||
|
||||
if ~uncompressed_len != length_check {
|
||||
return E_Deflate.Len_Nlen_Mismatch
|
||||
return .Len_Nlen_Mismatch
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -571,7 +571,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all
|
||||
assert(uncompressed_len == 0)
|
||||
|
||||
case 3:
|
||||
return E_Deflate.BType_3
|
||||
return .BType_3
|
||||
case:
|
||||
// fmt.printf("Err: %v | Final: %v | Type: %v\n", err, final, type)
|
||||
if type == 1 {
|
||||
@@ -604,7 +604,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all
|
||||
c = decode_huffman(z, codelength_ht) or_return
|
||||
|
||||
if c < 0 || c >= 19 {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths
|
||||
return .Huffman_Bad_Code_Lengths
|
||||
}
|
||||
if c < 16 {
|
||||
lencodes[n] = u8(c)
|
||||
@@ -616,7 +616,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all
|
||||
case 16:
|
||||
c = u16(compress.read_bits_no_refill_lsb(z, 2) + 3)
|
||||
if n == 0 {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths
|
||||
return .Huffman_Bad_Code_Lengths
|
||||
}
|
||||
fill = lencodes[n - 1]
|
||||
case 17:
|
||||
@@ -624,11 +624,11 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all
|
||||
case 18:
|
||||
c = u16(compress.read_bits_no_refill_lsb(z, 7) + 11)
|
||||
case:
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths
|
||||
return .Huffman_Bad_Code_Lengths
|
||||
}
|
||||
|
||||
if ntot - n < u32(c) {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths
|
||||
return .Huffman_Bad_Code_Lengths
|
||||
}
|
||||
|
||||
nc := n + u32(c)
|
||||
@@ -639,7 +639,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all
|
||||
}
|
||||
|
||||
if n != ntot {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths
|
||||
return .Huffman_Bad_Code_Lengths
|
||||
}
|
||||
|
||||
build_huffman(z_repeat, lencodes[:hlit]) or_return
|
||||
@@ -677,4 +677,4 @@ inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := fals
|
||||
return inflate_raw(z=&ctx, expected_output_size=expected_output_size)
|
||||
}
|
||||
|
||||
inflate :: proc{inflate_from_context, inflate_from_byte_array};
|
||||
inflate :: proc{inflate_from_context, inflate_from_byte_array}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dynamic_bit_array
|
||||
|
||||
import "core:intrinsics"
|
||||
import "core:mem"
|
||||
|
||||
/*
|
||||
Note that these constants are dependent on the backing being a u64.
|
||||
@@ -15,15 +16,16 @@ INDEX_MASK :: 63
|
||||
NUM_BITS :: 64
|
||||
|
||||
Bit_Array :: struct {
|
||||
bits: [dynamic]u64,
|
||||
bias: int,
|
||||
max_index: int,
|
||||
bits: [dynamic]u64,
|
||||
bias: int,
|
||||
max_index: int,
|
||||
free_pointer: bool,
|
||||
}
|
||||
|
||||
Bit_Array_Iterator :: struct {
|
||||
array: ^Bit_Array,
|
||||
array: ^Bit_Array,
|
||||
word_idx: int,
|
||||
bit_idx: uint,
|
||||
bit_idx: uint,
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -183,10 +185,37 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, allocator := context.allocator
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
In:
|
||||
- ba: ^Bit_Array - a pointer to the Bit Array
|
||||
- index: The bit index. Can be an enum member.
|
||||
|
||||
Out:
|
||||
- ok: Whether or not we managed to unset requested bit.
|
||||
|
||||
`unset` automatically resizes the Bit Array to accommodate the requested index if needed.
|
||||
*/
|
||||
unset :: proc(ba: ^Bit_Array, #any_int index: uint, allocator := context.allocator) -> (ok: bool) {
|
||||
|
||||
idx := int(index) - ba.bias
|
||||
|
||||
if ba == nil || int(index) < ba.bias { return false }
|
||||
context.allocator = allocator
|
||||
|
||||
leg_index := idx >> INDEX_SHIFT
|
||||
bit_index := idx & INDEX_MASK
|
||||
|
||||
resize_if_needed(ba, leg_index) or_return
|
||||
|
||||
ba.max_index = max(idx, ba.max_index)
|
||||
ba.bits[leg_index] &= ~(1 << uint(bit_index))
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative).
|
||||
*/
|
||||
create :: proc(max_index: int, min_index := 0, allocator := context.allocator) -> (res: Bit_Array, ok: bool) #optional_ok {
|
||||
create :: proc(max_index: int, min_index := 0, allocator := context.allocator) -> (res: ^Bit_Array, ok: bool) #optional_ok {
|
||||
context.allocator = allocator
|
||||
size_in_bits := max_index - min_index
|
||||
|
||||
@@ -194,11 +223,11 @@ create :: proc(max_index: int, min_index := 0, allocator := context.allocator) -
|
||||
|
||||
legs := size_in_bits >> INDEX_SHIFT
|
||||
|
||||
res = Bit_Array{
|
||||
bias = min_index,
|
||||
max_index = max_index,
|
||||
}
|
||||
return res, resize_if_needed(&res, legs)
|
||||
res = new(Bit_Array)
|
||||
res.bias = min_index
|
||||
res.max_index = max_index
|
||||
res.free_pointer = true
|
||||
return res, resize_if_needed(res, legs)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -206,7 +235,7 @@ create :: proc(max_index: int, min_index := 0, allocator := context.allocator) -
|
||||
*/
|
||||
clear :: proc(ba: ^Bit_Array) {
|
||||
if ba == nil { return }
|
||||
ba.bits = {}
|
||||
mem.zero_slice(ba.bits[:])
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -215,6 +244,9 @@ clear :: proc(ba: ^Bit_Array) {
|
||||
destroy :: proc(ba: ^Bit_Array) {
|
||||
if ba == nil { return }
|
||||
delete(ba.bits)
|
||||
if ba.free_pointer { // Only free if this Bit_Array was created using `create`, not when on the stack.
|
||||
free(ba)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -21,6 +21,7 @@ package dynamic_bit_array
|
||||
// returns `false`, `false`, because this Bit Array wasn't created to allow negative indices.
|
||||
was_set, was_retrieved := get(&bits, -1)
|
||||
fmt.println(was_set, was_retrieved)
|
||||
destroy(&bits)
|
||||
}
|
||||
|
||||
-- A Bit Array can optionally allow for negative indices, if the mininum value was given during creation:
|
||||
@@ -40,13 +41,13 @@ package dynamic_bit_array
|
||||
using bit_array
|
||||
|
||||
bits := create(int(max(Foo)), int(min(Foo)))
|
||||
defer destroy(&bits)
|
||||
defer destroy(bits)
|
||||
|
||||
fmt.printf("Set(Bar): %v\n", set(&bits, Foo.Bar))
|
||||
fmt.printf("Get(Bar): %v, %v\n", get(&bits, Foo.Bar))
|
||||
fmt.printf("Set(Negative_Test): %v\n", set(&bits, Foo.Negative_Test))
|
||||
fmt.printf("Get(Leaves): %v, %v\n", get(&bits, Foo.Leaves))
|
||||
fmt.printf("Get(Negative_Test): %v, %v\n", get(&bits, Foo.Negative_Test))
|
||||
fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar))
|
||||
fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar))
|
||||
fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test))
|
||||
fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves))
|
||||
fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test))
|
||||
fmt.printf("Freed.\n")
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,173 @@
|
||||
package container_intrusive_list
|
||||
|
||||
import "core:intrinsics"
|
||||
|
||||
// An intrusive doubly-linked list
|
||||
//
|
||||
// As this is an intrusive container, a `Node` must be embedded in your own
|
||||
// structure which is conventionally called a "link". The use of `push_front`
|
||||
// and `push_back` take the address of this node. Retrieving the data
|
||||
// associated with the node requires finding the relative offset of the node
|
||||
// of the parent structure. The parent type and field name are given to
|
||||
// `iterator_*` procedures, or to the built-in `container_of` procedure.
|
||||
//
|
||||
// This data structure is two-pointers in size:
|
||||
// 8 bytes on 32-bit platforms and 16 bytes on 64-bit platforms
|
||||
List :: struct {
|
||||
head: ^Node,
|
||||
tail: ^Node,
|
||||
}
|
||||
|
||||
|
||||
Node :: struct {
|
||||
next, prev: ^Node,
|
||||
}
|
||||
|
||||
push_front :: proc(list: ^List, node: ^Node) {
|
||||
if list.head != nil {
|
||||
list.head.prev = node
|
||||
node.prev, node.next = nil, list.head
|
||||
list.head = node
|
||||
} else {
|
||||
list.head, list.tail = node, node
|
||||
node.prev, node.next = nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
push_back :: proc(list: ^List, node: ^Node) {
|
||||
if list.tail != nil {
|
||||
list.tail.next = node
|
||||
node.prev, node.next = list.tail, nil
|
||||
list.tail = node
|
||||
} else {
|
||||
list.head, list.tail = node, node
|
||||
node.prev, node.next = nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
remove :: proc(list: ^List, node: ^Node) {
|
||||
if node != nil {
|
||||
if node.next != nil {
|
||||
node.next.prev = node.prev
|
||||
}
|
||||
if node.prev != nil {
|
||||
node.prev.next = node.next
|
||||
}
|
||||
if list.head == node {
|
||||
list.head = node.next
|
||||
}
|
||||
if list.tail == node {
|
||||
list.tail = node.prev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
|
||||
for node := list.head; node != nil; {
|
||||
next := node.next
|
||||
if to_erase(node) {
|
||||
if node.next != nil {
|
||||
node.next.prev = node.prev
|
||||
}
|
||||
if node.prev != nil {
|
||||
node.prev.next = node.next
|
||||
}
|
||||
if list.head == node {
|
||||
list.head = node.next
|
||||
}
|
||||
if list.tail == node {
|
||||
list.tail = node.prev
|
||||
}
|
||||
}
|
||||
node = next
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
is_empty :: proc(list: ^List) -> bool {
|
||||
return list.head == nil
|
||||
}
|
||||
|
||||
pop_front :: proc(list: ^List) -> ^Node {
|
||||
link := list.head
|
||||
if link == nil {
|
||||
return nil
|
||||
}
|
||||
if link.next != nil {
|
||||
link.next.prev = link.prev
|
||||
}
|
||||
if link.prev != nil {
|
||||
link.prev.next = link.next
|
||||
}
|
||||
if link == list.head {
|
||||
list.head = link.next
|
||||
}
|
||||
if link == list.tail {
|
||||
list.tail = link.prev
|
||||
}
|
||||
return link
|
||||
|
||||
}
|
||||
pop_back :: proc(list: ^List) -> ^Node {
|
||||
link := list.tail
|
||||
if link == nil {
|
||||
return nil
|
||||
}
|
||||
if link.next != nil {
|
||||
link.next.prev = link.prev
|
||||
}
|
||||
if link.prev != nil {
|
||||
link.prev.next = link.next
|
||||
}
|
||||
if link == list.head {
|
||||
list.head = link.next
|
||||
}
|
||||
if link == list.tail {
|
||||
list.tail = link.prev
|
||||
}
|
||||
return link
|
||||
}
|
||||
|
||||
|
||||
Iterator :: struct($T: typeid) {
|
||||
curr: ^Node,
|
||||
offset: uintptr,
|
||||
}
|
||||
|
||||
iterator_head :: proc(list: List, $T: typeid, $field_name: string) -> Iterator(T)
|
||||
where intrinsics.type_has_field(T, field_name),
|
||||
intrinsics.type_field_type(T, field_name) == Node {
|
||||
return {list.head, offset_of_by_string(T, field_name)}
|
||||
}
|
||||
|
||||
iterator_tail :: proc(list: List, $T: typeid, $field_name: string) -> Iterator(T)
|
||||
where intrinsics.type_has_field(T, field_name),
|
||||
intrinsics.type_field_type(T, field_name) == Node {
|
||||
return {list.tail, offset_of_by_string(T, field_name)}
|
||||
}
|
||||
|
||||
iterator_from_node :: proc(node: ^Node, $T: typeid, $field_name: string) -> Iterator(T)
|
||||
where intrinsics.type_has_field(T, field_name),
|
||||
intrinsics.type_field_type(T, field_name) == Node {
|
||||
return {node, offset_of_by_string(T, field_name)}
|
||||
}
|
||||
|
||||
iterate_next :: proc(it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
|
||||
node := it.curr
|
||||
if node == nil {
|
||||
return nil, false
|
||||
}
|
||||
it.curr = node.next
|
||||
|
||||
return (^T)(uintptr(node) - it.offset), true
|
||||
}
|
||||
|
||||
iterate_prev :: proc(it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
|
||||
node := it.curr
|
||||
if node == nil {
|
||||
return nil, false
|
||||
}
|
||||
it.curr = node.prev
|
||||
|
||||
return (^T)(uintptr(node) - it.offset), true
|
||||
}
|
||||
@@ -60,19 +60,27 @@ clear :: proc(c: ^$C/Cache($Key, $Value), call_on_remove: bool) {
|
||||
set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Allocator_Error {
|
||||
if e, ok := c.entries[key]; ok {
|
||||
e.value = value
|
||||
_pop_node(c, e)
|
||||
_push_front_node(c, e)
|
||||
return nil
|
||||
}
|
||||
|
||||
e := new(Node(Key, Value), c.node_allocator) or_return
|
||||
e.key = key
|
||||
e.value = value
|
||||
|
||||
_push_front_node(c, e)
|
||||
if c.count > c.capacity {
|
||||
_remove_node(c, c.tail)
|
||||
e : ^Node(Key, Value) = nil
|
||||
assert(c.count <= c.capacity)
|
||||
if c.count == c.capacity {
|
||||
e = c.tail
|
||||
_remove_node(c, e)
|
||||
}
|
||||
else {
|
||||
c.count += 1
|
||||
e = new(Node(Key, Value), c.node_allocator) or_return
|
||||
}
|
||||
|
||||
e.key = key
|
||||
e.value = value
|
||||
_push_front_node(c, e)
|
||||
c.entries[key] = e
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -122,6 +130,8 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool {
|
||||
return false
|
||||
}
|
||||
_remove_node(c, e)
|
||||
free(node, c.node_allocator)
|
||||
c.count -= 1
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -143,14 +153,9 @@ _remove_node :: proc(c: ^$C/Cache($Key, $Value), node: ^Node(Key, Value)) {
|
||||
node.prev = nil
|
||||
node.next = nil
|
||||
|
||||
c.count -= 1
|
||||
|
||||
delete_key(&c.entries, node.key)
|
||||
|
||||
_call_on_remove(c, node)
|
||||
|
||||
free(node, c.node_allocator)
|
||||
|
||||
}
|
||||
|
||||
@(private)
|
||||
@@ -171,8 +176,6 @@ _push_front_node :: proc(c: ^$C/Cache($Key, $Value), e: ^Node(Key, Value)) {
|
||||
c.tail = e
|
||||
}
|
||||
e.prev = nil
|
||||
|
||||
c.count += 1
|
||||
}
|
||||
|
||||
@(private)
|
||||
@@ -180,6 +183,12 @@ _pop_node :: proc(c: ^$C/Cache($Key, $Value), e: ^Node(Key, Value)) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
if c.head == e {
|
||||
c.head = e.next
|
||||
}
|
||||
if c.tail == e {
|
||||
c.tail = e.prev
|
||||
}
|
||||
if e.prev != nil {
|
||||
e.prev.next = e.next
|
||||
}
|
||||
|
||||
@@ -85,7 +85,6 @@ _shift_down :: proc(pq: ^$Q/Priority_Queue($T), i0, n: int) -> bool {
|
||||
_shift_up :: proc(pq: ^$Q/Priority_Queue($T), j: int) {
|
||||
j := j
|
||||
queue := pq.queue[:]
|
||||
n := builtin.len(queue)
|
||||
for 0 <= j {
|
||||
i := (j-1)/2
|
||||
if i == j || !pq.less(queue[j], queue[i]) {
|
||||
|
||||
@@ -69,6 +69,23 @@ get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T {
|
||||
idx := (uint(i)+q.offset)%builtin.len(q.data)
|
||||
return q.data[idx]
|
||||
}
|
||||
|
||||
front :: proc(q: ^$Q/Queue($T)) -> T {
|
||||
return q.data[q.offset]
|
||||
}
|
||||
front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
|
||||
return &q.data[q.offset]
|
||||
}
|
||||
|
||||
back :: proc(q: ^$Q/Queue($T)) -> T {
|
||||
idx := (q.offset+uint(q.len))%builtin.len(q.data)
|
||||
return q.data[idx]
|
||||
}
|
||||
back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
|
||||
idx := (q.offset+uint(q.len))%builtin.len(q.data)
|
||||
return &q.data[idx]
|
||||
}
|
||||
|
||||
set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) {
|
||||
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data))
|
||||
|
||||
@@ -82,6 +99,18 @@ get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^
|
||||
return &q.data[idx]
|
||||
}
|
||||
|
||||
peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
|
||||
runtime.bounds_check_error_loc(loc, 0, builtin.len(q.data))
|
||||
idx := q.offset%builtin.len(q.data)
|
||||
return &q.data[idx]
|
||||
}
|
||||
|
||||
peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
|
||||
runtime.bounds_check_error_loc(loc, int(q.len - 1), builtin.len(q.data))
|
||||
idx := (uint(q.len - 1)+q.offset)%builtin.len(q.data)
|
||||
return &q.data[idx]
|
||||
}
|
||||
|
||||
// Push an element to the back of the queue
|
||||
push_back :: proc(q: ^$Q/Queue($T), elem: T) -> bool {
|
||||
if space(q^) == 0 {
|
||||
|
||||
@@ -25,14 +25,14 @@ slice :: proc(a: ^$A/Small_Array($N, $T)) -> []T {
|
||||
}
|
||||
|
||||
|
||||
get :: proc(a: $A/Small_Array($N, $T), index: int, loc := #caller_location) -> T {
|
||||
get :: proc(a: $A/Small_Array($N, $T), index: int) -> T {
|
||||
return a.data[index]
|
||||
}
|
||||
get_ptr :: proc(a: $A/Small_Array($N, $T), index: int, loc := #caller_location) -> ^T {
|
||||
get_ptr :: proc(a: ^$A/Small_Array($N, $T), index: int) -> ^T {
|
||||
return &a.data[index]
|
||||
}
|
||||
|
||||
set :: proc(a: ^$A/Small_Array($N, $T), index: int, item: T, loc := #caller_location) {
|
||||
set :: proc(a: ^$A/Small_Array($N, $T), index: int, item: T) {
|
||||
a.data[index] = item
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ pop_back_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
|
||||
return
|
||||
}
|
||||
|
||||
pop_front_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (T, bool) {
|
||||
pop_front_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
|
||||
if N > 0 && a.len > 0 {
|
||||
item = a.data[0]
|
||||
s := slice(a)
|
||||
@@ -114,4 +114,4 @@ push_back_elems :: proc(a: ^$A/Small_Array($N, $T), items: ..T) {
|
||||
append_elem :: push_back
|
||||
append_elems :: push_back_elems
|
||||
push :: proc{push_back, push_back_elems}
|
||||
append :: proc{push_back, push_back_elems}
|
||||
append :: proc{push_back, push_back_elems}
|
||||
|
||||
@@ -81,7 +81,7 @@ The crypto package is not thread-safe at the moment. This may change in the futu
|
||||
### Disclaimer
|
||||
The algorithms were ported out of curiosity and due to interest in the field.
|
||||
We have not had any of the code verified by a third party or tested/fuzzed by any automatic means.
|
||||
Whereever we were able to find official test vectors, those were used to verify the implementation.
|
||||
Wherever we were able to find official test vectors, those were used to verify the implementation.
|
||||
We do not recommend using them in a production environment, without any additional testing and/or verification.
|
||||
|
||||
### ToDo
|
||||
|
||||
@@ -30,6 +30,6 @@ equivalence.
|
||||
|
||||
For the most part, alterations to the base fiat-crypto generated code was
|
||||
kept to a minimum, to aid auditability. This results in a somewhat
|
||||
ideosyncratic style, and in some cases minor performance penalties.
|
||||
idiosyncratic style, and in some cases minor performance penalties.
|
||||
|
||||
[1]: https://github.com/mit-plv/fiat-crypto
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package crypto
|
||||
|
||||
when ODIN_OS != .Linux && ODIN_OS != .OpenBSD {
|
||||
when ODIN_OS != .Linux && ODIN_OS != .OpenBSD && ODIN_OS != .Windows {
|
||||
_rand_bytes :: proc (dst: []byte) {
|
||||
unimplemented("crypto: rand_bytes not supported on this OS")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package crypto
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:os"
|
||||
import "core:fmt"
|
||||
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG))
|
||||
if ret != os.ERROR_NONE {
|
||||
switch ret {
|
||||
case os.ERROR_INVALID_HANDLE:
|
||||
// The handle to the first parameter is invalid.
|
||||
// This should not happen here, since we explicitly pass nil to it
|
||||
panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm")
|
||||
case os.ERROR_INVALID_PARAMETER:
|
||||
// One of the parameters was invalid
|
||||
panic("crypto: BCryptGenRandom Invalid parameter")
|
||||
case:
|
||||
// Unknown error
|
||||
panic(fmt.tprintf("crypto: BCryptGenRandom failed: %d\n", ret))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -419,8 +419,10 @@ update :: proc(ctx: ^$T, data: []byte) {
|
||||
sha2_transf(ctx, shifted_message, block_nb)
|
||||
|
||||
rem_len = new_len % CURR_BLOCK_SIZE
|
||||
when T == Sha256_Context {copy(ctx.block[:], shifted_message[block_nb << 6:rem_len])}
|
||||
else when T == Sha512_Context {copy(ctx.block[:], shifted_message[block_nb << 7:rem_len])}
|
||||
if rem_len > 0 {
|
||||
when T == Sha256_Context {copy(ctx.block[:], shifted_message[block_nb << 6:rem_len])}
|
||||
else when T == Sha512_Context {copy(ctx.block[:], shifted_message[block_nb << 7:rem_len])}
|
||||
}
|
||||
|
||||
ctx.length = rem_len
|
||||
when T == Sha256_Context {ctx.tot_len += (block_nb + 1) << 6}
|
||||
|
||||
@@ -14,7 +14,6 @@ package siphash
|
||||
|
||||
import "core:crypto"
|
||||
import "core:crypto/util"
|
||||
import "core:mem"
|
||||
|
||||
/*
|
||||
High level API
|
||||
@@ -234,7 +233,7 @@ init :: proc(ctx: ^Context, key: []byte, c_rounds, d_rounds: int) {
|
||||
}
|
||||
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
assert(ctx.is_initialized, "crypto/siphash: Context is not initalized")
|
||||
assert(ctx.is_initialized, "crypto/siphash: Context is not initialized")
|
||||
ctx.last_block = len(data) / 8 * 8
|
||||
ctx.buf = data
|
||||
i := 0
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
package dynlib
|
||||
|
||||
Library :: distinct rawptr
|
||||
|
||||
load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
return _load_library(path, global_symbols)
|
||||
}
|
||||
|
||||
unload_library :: proc(library: Library) -> bool {
|
||||
return _unload_library(library)
|
||||
}
|
||||
|
||||
symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) #optional_ok {
|
||||
return _symbol_address(library, symbol)
|
||||
}
|
||||
|
||||
+15
-14
@@ -1,23 +1,24 @@
|
||||
// +build linux, darwin, freebsd, openbsd
|
||||
//+build linux, darwin, freebsd, openbsd
|
||||
//+private
|
||||
package dynlib
|
||||
|
||||
import "core:os"
|
||||
|
||||
load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
flags := os.RTLD_NOW
|
||||
if global_symbols {
|
||||
flags |= os.RTLD_GLOBAL
|
||||
}
|
||||
lib := os.dlopen(path, flags)
|
||||
return Library(lib), lib != nil
|
||||
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
flags := os.RTLD_NOW
|
||||
if global_symbols {
|
||||
flags |= os.RTLD_GLOBAL
|
||||
}
|
||||
lib := os.dlopen(path, flags)
|
||||
return Library(lib), lib != nil
|
||||
}
|
||||
|
||||
unload_library :: proc(library: Library) {
|
||||
os.dlclose(rawptr(library))
|
||||
_unload_library :: proc(library: Library) -> bool {
|
||||
return os.dlclose(rawptr(library))
|
||||
}
|
||||
|
||||
symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
||||
ptr = os.dlsym(rawptr(library), symbol)
|
||||
found = ptr != nil
|
||||
return
|
||||
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
||||
ptr = os.dlsym(rawptr(library), symbol)
|
||||
found = ptr != nil
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// +build windows
|
||||
//+build windows
|
||||
//+private
|
||||
package dynlib
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:strings"
|
||||
|
||||
load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
// NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL
|
||||
|
||||
wide_path := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
@@ -12,12 +13,12 @@ load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
return handle, handle != nil
|
||||
}
|
||||
|
||||
unload_library :: proc(library: Library) -> bool {
|
||||
_unload_library :: proc(library: Library) -> bool {
|
||||
ok := win32.FreeLibrary(cast(win32.HMODULE)library)
|
||||
return bool(ok)
|
||||
}
|
||||
|
||||
symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
||||
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
||||
c_str := strings.clone_to_cstring(symbol, context.temp_allocator)
|
||||
ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str)
|
||||
found = ptr != nil
|
||||
|
||||
@@ -34,6 +34,10 @@ Reader :: struct {
|
||||
// If lazy_quotes is true, a quote may appear in an unquoted field and a non-doubled quote may appear in a quoted field
|
||||
lazy_quotes: bool,
|
||||
|
||||
// multiline_fields, when set to true, will treat a field starting with a " as a multiline string
|
||||
// therefore, instead of reading until the next \n, it'll read until the next "
|
||||
multiline_fields: bool,
|
||||
|
||||
// reuse_record controls whether calls to 'read' may return a slice using the backing buffer
|
||||
// for performance
|
||||
// By default, each call to 'read' returns a newly allocated slice
|
||||
@@ -194,32 +198,72 @@ is_valid_delim :: proc(r: rune) -> bool {
|
||||
@private
|
||||
_read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.allocator) -> ([]string, Error) {
|
||||
read_line :: proc(r: ^Reader) -> ([]byte, io.Error) {
|
||||
line, err := bufio.reader_read_slice(&r.r, '\n')
|
||||
if err == .Buffer_Full {
|
||||
clear(&r.raw_buffer)
|
||||
append(&r.raw_buffer, ..line)
|
||||
for err == .Buffer_Full {
|
||||
line, err = bufio.reader_read_slice(&r.r, '\n')
|
||||
if !r.multiline_fields {
|
||||
line, err := bufio.reader_read_slice(&r.r, '\n')
|
||||
if err == .Buffer_Full {
|
||||
clear(&r.raw_buffer)
|
||||
append(&r.raw_buffer, ..line)
|
||||
for err == .Buffer_Full {
|
||||
line, err = bufio.reader_read_slice(&r.r, '\n')
|
||||
append(&r.raw_buffer, ..line)
|
||||
}
|
||||
line = r.raw_buffer[:]
|
||||
}
|
||||
line = r.raw_buffer[:]
|
||||
}
|
||||
if len(line) > 0 && err == .EOF {
|
||||
err = nil
|
||||
if line[len(line)-1] == '\r' {
|
||||
line = line[:len(line)-1]
|
||||
if len(line) > 0 && err == .EOF {
|
||||
err = nil
|
||||
if line[len(line)-1] == '\r' {
|
||||
line = line[:len(line)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
r.line_count += 1
|
||||
r.line_count += 1
|
||||
|
||||
// normalize \r\n to \n
|
||||
n := len(line)
|
||||
for n >= 2 && string(line[n-2:]) == "\r\n" {
|
||||
line[n-2] = '\n'
|
||||
line = line[:n-1]
|
||||
}
|
||||
// normalize \r\n to \n
|
||||
n := len(line)
|
||||
for n >= 2 && string(line[n-2:]) == "\r\n" {
|
||||
line[n-2] = '\n'
|
||||
line = line[:n-1]
|
||||
}
|
||||
return line, err
|
||||
|
||||
return line, err
|
||||
} else {
|
||||
// Reading a "line" that can possibly contain multiline fields.
|
||||
// Unfortunately, this means we need to read a character at a time.
|
||||
|
||||
err: io.Error
|
||||
cur: rune
|
||||
is_quoted: bool
|
||||
|
||||
field_length := 0
|
||||
|
||||
clear(&r.raw_buffer)
|
||||
|
||||
read_loop: for err == .None {
|
||||
cur, _, err = bufio.reader_read_rune(&r.r)
|
||||
|
||||
if err != .None { break read_loop }
|
||||
|
||||
switch cur {
|
||||
case '"':
|
||||
is_quoted = field_length == 0
|
||||
field_length += 1
|
||||
|
||||
case '\n', '\r':
|
||||
if !is_quoted { break read_loop }
|
||||
|
||||
case r.comma:
|
||||
field_length = 0
|
||||
|
||||
case:
|
||||
field_length += 1
|
||||
}
|
||||
|
||||
rune_buf, rune_len := utf8.encode_rune(cur)
|
||||
append(&r.raw_buffer, ..rune_buf[:rune_len])
|
||||
}
|
||||
|
||||
return r.raw_buffer[:], err
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
length_newline :: proc(b: []byte) -> int {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Package endian implements sa simple translation between bytes and numbers with
|
||||
specific endian encodings.
|
||||
|
||||
buf: [100]u8
|
||||
put_u16(buf[:], .Little, 16) or_return
|
||||
|
||||
You may ask yourself, why isn't `byte_order` platform Endianness by default, so we can write:
|
||||
put_u16(buf[:], 16) or_return
|
||||
|
||||
The answer is that very few file formats are written in native/platform endianness. Most of them specify the endianness of
|
||||
each of their fields, or use a header field which specifies it for the entire file.
|
||||
|
||||
e.g. a file which specifies it at the top for all fields could do this:
|
||||
file_order := .Little if buf[0] == 0 else .Big
|
||||
field := get_u16(buf[1:], file_order) or_return
|
||||
|
||||
If on the other hand a field is *always* Big-Endian, you're wise to explicitly state it for the benefit of the reader,
|
||||
be that your future self or someone else.
|
||||
|
||||
field := get_u16(buf[:], .Big) or_return
|
||||
*/
|
||||
package encoding_endian
|
||||
@@ -0,0 +1,153 @@
|
||||
package encoding_endian
|
||||
|
||||
Byte_Order :: enum u8 {
|
||||
Little,
|
||||
Big,
|
||||
}
|
||||
|
||||
PLATFORM_BYTE_ORDER :: Byte_Order.Little when ODIN_ENDIAN == .Little else Byte_Order.Big
|
||||
|
||||
get_u16 :: proc(b: []byte, order: Byte_Order) -> (v: u16, ok: bool) {
|
||||
if len(b) < 2 {
|
||||
return 0, false
|
||||
}
|
||||
#no_bounds_check if order == .Little {
|
||||
v = u16(b[0]) | u16(b[1])<<8
|
||||
} else {
|
||||
v = u16(b[1]) | u16(b[0])<<8
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
get_u32 :: proc(b: []byte, order: Byte_Order) -> (v: u32, ok: bool) {
|
||||
if len(b) < 4 {
|
||||
return 0, false
|
||||
}
|
||||
#no_bounds_check if order == .Little {
|
||||
v = u32(b[0]) | u32(b[1])<<8 | u32(b[2])<<16 | u32(b[3])<<24
|
||||
} else {
|
||||
v = u32(b[3]) | u32(b[2])<<8 | u32(b[1])<<16 | u32(b[0])<<24
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
|
||||
get_u64 :: proc(b: []byte, order: Byte_Order) -> (v: u64, ok: bool) {
|
||||
if len(b) < 8 {
|
||||
return 0, false
|
||||
}
|
||||
#no_bounds_check if order == .Little {
|
||||
v = u64(b[0]) | u64(b[1])<<8 | u64(b[2])<<16 | u64(b[3])<<24 |
|
||||
u64(b[4])<<32 | u64(b[5])<<40 | u64(b[6])<<48 | u64(b[7])<<56
|
||||
} else {
|
||||
v = u64(b[7]) | u64(b[6])<<8 | u64(b[5])<<16 | u64(b[4])<<24 |
|
||||
u64(b[3])<<32 | u64(b[2])<<40 | u64(b[1])<<48 | u64(b[0])<<56
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
|
||||
get_i16 :: proc(b: []byte, order: Byte_Order) -> (i16, bool) {
|
||||
v, ok := get_u16(b, order)
|
||||
return i16(v), ok
|
||||
}
|
||||
get_i32 :: proc(b: []byte, order: Byte_Order) -> (i32, bool) {
|
||||
v, ok := get_u32(b, order)
|
||||
return i32(v), ok
|
||||
}
|
||||
get_i64 :: proc(b: []byte, order: Byte_Order) -> (i64, bool) {
|
||||
v, ok := get_u64(b, order)
|
||||
return i64(v), ok
|
||||
}
|
||||
|
||||
get_f16 :: proc(b: []byte, order: Byte_Order) -> (f16, bool) {
|
||||
v, ok := get_u16(b, order)
|
||||
return transmute(f16)v, ok
|
||||
}
|
||||
get_f32 :: proc(b: []byte, order: Byte_Order) -> (f32, bool) {
|
||||
v, ok := get_u32(b, order)
|
||||
return transmute(f32)v, ok
|
||||
}
|
||||
get_f64 :: proc(b: []byte, order: Byte_Order) -> (f64, bool) {
|
||||
v, ok := get_u64(b, order)
|
||||
return transmute(f64)v, ok
|
||||
}
|
||||
|
||||
|
||||
put_u16 :: proc(b: []byte, order: Byte_Order, v: u16) -> bool {
|
||||
if len(b) < 2 {
|
||||
return false
|
||||
}
|
||||
#no_bounds_check if order == .Little {
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
} else {
|
||||
b[0] = byte(v >> 8)
|
||||
b[1] = byte(v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
put_u32 :: proc(b: []byte, order: Byte_Order, v: u32) -> bool {
|
||||
if len(b) < 4 {
|
||||
return false
|
||||
}
|
||||
#no_bounds_check if order == .Little {
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
} else {
|
||||
b[0] = byte(v >> 24)
|
||||
b[1] = byte(v >> 16)
|
||||
b[2] = byte(v >> 8)
|
||||
b[3] = byte(v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
put_u64 :: proc(b: []byte, order: Byte_Order, v: u64) -> bool {
|
||||
if len(b) < 8 {
|
||||
return false
|
||||
}
|
||||
#no_bounds_check if order == .Little {
|
||||
b[0] = byte(v >> 0)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
b[4] = byte(v >> 32)
|
||||
b[5] = byte(v >> 40)
|
||||
b[6] = byte(v >> 48)
|
||||
b[7] = byte(v >> 56)
|
||||
} else {
|
||||
b[0] = byte(v >> 56)
|
||||
b[1] = byte(v >> 48)
|
||||
b[2] = byte(v >> 40)
|
||||
b[3] = byte(v >> 32)
|
||||
b[4] = byte(v >> 24)
|
||||
b[5] = byte(v >> 16)
|
||||
b[6] = byte(v >> 8)
|
||||
b[7] = byte(v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
put_i16 :: proc(b: []byte, order: Byte_Order, v: i16) -> bool {
|
||||
return put_u16(b, order, u16(v))
|
||||
}
|
||||
|
||||
put_i32 :: proc(b: []byte, order: Byte_Order, v: i32) -> bool {
|
||||
return put_u32(b, order, u32(v))
|
||||
}
|
||||
|
||||
put_i64 :: proc(b: []byte, order: Byte_Order, v: i64) -> bool {
|
||||
return put_u64(b, order, u64(v))
|
||||
}
|
||||
|
||||
|
||||
put_f16 :: proc(b: []byte, order: Byte_Order, v: f16) -> bool {
|
||||
return put_u16(b, order, transmute(u16)v)
|
||||
}
|
||||
|
||||
put_f32 :: proc(b: []byte, order: Byte_Order, v: f32) -> bool {
|
||||
return put_u32(b, order, transmute(u32)v)
|
||||
}
|
||||
|
||||
put_f64 :: proc(b: []byte, order: Byte_Order, v: f64) -> bool {
|
||||
return put_u64(b, order, transmute(u64)v)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# License
|
||||
|
||||
By obtaining, using and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions.
|
||||
|
||||
Permission to copy, modify, and distribute this software and its documentation, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications:
|
||||
|
||||
The full text of this NOTICE in a location viewable to users of the redistributed or derivative work.
|
||||
Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software Short Notice should be included (hypertext is preferred, text is permitted) within the body of any redistributed or derivative code.
|
||||
|
||||
Notice of any changes or modifications to the files, including the date changes were made. (We recommend you provide URIs to the location from which the code is derived.)
|
||||
|
||||
# Disclaimers
|
||||
|
||||
THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
|
||||
|
||||
COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
|
||||
|
||||
The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the software without specific, written prior permission. Title to copyright in this software and any associated documentation will at all times remain with copyright holders.
|
||||
|
||||
# Notes
|
||||
This version: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
|
||||
@@ -0,0 +1,374 @@
|
||||
package unicode_entity
|
||||
/*
|
||||
A unicode entity encoder/decoder
|
||||
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
This code has several procedures to map unicode runes to/from different textual encodings.
|
||||
- SGML/XML/HTML entity
|
||||
-- &#<decimal>;
|
||||
-- &#x<hexadecimal>;
|
||||
-- &<entity name>; (If the lookup tables are compiled in).
|
||||
Reference: https://www.w3.org/2003/entities/2007xml/unicode.xml
|
||||
|
||||
- URL encode / decode %hex entity
|
||||
Reference: https://datatracker.ietf.org/doc/html/rfc3986/#section-2.1
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
*/
|
||||
|
||||
import "core:unicode/utf8"
|
||||
import "core:unicode"
|
||||
import "core:strings"
|
||||
|
||||
MAX_RUNE_CODEPOINT :: int(unicode.MAX_RUNE)
|
||||
|
||||
write_rune :: strings.write_rune
|
||||
write_string :: strings.write_string
|
||||
|
||||
Error :: enum u8 {
|
||||
None = 0,
|
||||
Tokenizer_Is_Nil,
|
||||
|
||||
Illegal_NUL_Character,
|
||||
Illegal_UTF_Encoding,
|
||||
Illegal_BOM,
|
||||
|
||||
CDATA_Not_Terminated,
|
||||
Comment_Not_Terminated,
|
||||
Invalid_Entity_Encoding,
|
||||
}
|
||||
|
||||
Tokenizer :: struct {
|
||||
r: rune,
|
||||
w: int,
|
||||
|
||||
src: string,
|
||||
offset: int,
|
||||
read_offset: int,
|
||||
}
|
||||
|
||||
CDATA_START :: "<![CDATA["
|
||||
CDATA_END :: "]]>"
|
||||
|
||||
COMMENT_START :: "<!--"
|
||||
COMMENT_END :: "-->"
|
||||
|
||||
/*
|
||||
Default: CDATA and comments are passed through unchanged.
|
||||
*/
|
||||
XML_Decode_Option :: enum u8 {
|
||||
/*
|
||||
Do not decode & entities. It decodes by default.
|
||||
If given, overrides `Decode_CDATA`.
|
||||
*/
|
||||
No_Entity_Decode,
|
||||
|
||||
/*
|
||||
CDATA is unboxed.
|
||||
*/
|
||||
Unbox_CDATA,
|
||||
|
||||
/*
|
||||
Unboxed CDATA is decoded as well.
|
||||
Ignored if `.Unbox_CDATA` is not given.
|
||||
*/
|
||||
Decode_CDATA,
|
||||
|
||||
/*
|
||||
Comments are stripped.
|
||||
*/
|
||||
Comment_Strip,
|
||||
}
|
||||
XML_Decode_Options :: bit_set[XML_Decode_Option; u8]
|
||||
|
||||
/*
|
||||
Decode a string that may include SGML/XML/HTML entities.
|
||||
The caller has to free the result.
|
||||
*/
|
||||
decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := context.allocator) -> (decoded: string, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
l := len(input)
|
||||
if l == 0 { return "", .None }
|
||||
|
||||
builder := strings.builder_make()
|
||||
defer strings.builder_destroy(&builder)
|
||||
|
||||
t := Tokenizer{src=input}
|
||||
in_data := false
|
||||
|
||||
loop: for {
|
||||
advance(&t) or_return
|
||||
if t.r < 0 { break loop }
|
||||
|
||||
/*
|
||||
Below here we're never inside a CDATA tag.
|
||||
At most we'll see the start of one, but that doesn't affect the logic.
|
||||
*/
|
||||
switch t.r {
|
||||
case '<':
|
||||
/*
|
||||
Might be the start of a CDATA tag or comment.
|
||||
|
||||
We don't need to check if we need to write a `<`, because if it isn't CDATA or a comment,
|
||||
it couldn't have been part of an XML tag body to be decoded here.
|
||||
|
||||
Keep in mind that we could already *be* inside a CDATA tag.
|
||||
If so, write `>` as a literal and continue.
|
||||
*/
|
||||
if in_data {
|
||||
write_rune(&builder, '<')
|
||||
continue
|
||||
}
|
||||
in_data = _handle_xml_special(&t, &builder, options) or_return
|
||||
|
||||
case ']':
|
||||
/*
|
||||
If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
|
||||
*/
|
||||
if in_data {
|
||||
if t.read_offset + len(CDATA_END) < len(t.src) {
|
||||
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
||||
in_data = false
|
||||
t.read_offset += len(CDATA_END) - 1
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
write_rune(&builder, ']')
|
||||
}
|
||||
|
||||
case:
|
||||
if in_data && .Decode_CDATA not_in options {
|
||||
/*
|
||||
Unboxed, but undecoded.
|
||||
*/
|
||||
write_rune(&builder, t.r)
|
||||
continue
|
||||
}
|
||||
|
||||
if t.r == '&' {
|
||||
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.
|
||||
*/
|
||||
write_string(&builder, entity)
|
||||
} else {
|
||||
|
||||
if .No_Entity_Decode not_in options {
|
||||
if decoded, ok := xml_decode_entity(entity); ok {
|
||||
write_rune(&builder, decoded)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Literal passthrough because the decode failed or we want entities not decoded.
|
||||
*/
|
||||
write_string(&builder, "&")
|
||||
write_string(&builder, entity)
|
||||
write_string(&builder, ";")
|
||||
}
|
||||
} else {
|
||||
write_rune(&builder, t.r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.clone(strings.to_string(builder), allocator), err
|
||||
}
|
||||
|
||||
advance :: proc(t: ^Tokenizer) -> (err: Error) {
|
||||
if t == nil { return .Tokenizer_Is_Nil }
|
||||
using t
|
||||
|
||||
#no_bounds_check {
|
||||
if read_offset < len(src) {
|
||||
offset = read_offset
|
||||
r, w = rune(src[read_offset]), 1
|
||||
switch {
|
||||
case r == 0:
|
||||
return .Illegal_NUL_Character
|
||||
case r >= utf8.RUNE_SELF:
|
||||
r, w = utf8.decode_rune_in_string(src[read_offset:])
|
||||
if r == utf8.RUNE_ERROR && w == 1 {
|
||||
return .Illegal_UTF_Encoding
|
||||
} else if r == utf8.RUNE_BOM && offset > 0 {
|
||||
return .Illegal_BOM
|
||||
}
|
||||
}
|
||||
read_offset += w
|
||||
return .None
|
||||
} else {
|
||||
offset = len(src)
|
||||
r = -1
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) {
|
||||
entity := entity
|
||||
if len(entity) == 0 { return -1, false }
|
||||
|
||||
switch entity[0] {
|
||||
case '#':
|
||||
base := 10
|
||||
val := 0
|
||||
entity = entity[1:]
|
||||
|
||||
if len(entity) == 0 { return -1, false }
|
||||
|
||||
if entity[0] == 'x' || entity[0] == 'X' {
|
||||
base = 16
|
||||
entity = entity[1:]
|
||||
}
|
||||
|
||||
for len(entity) > 0 {
|
||||
r := entity[0]
|
||||
switch r {
|
||||
case '0'..='9':
|
||||
val *= base
|
||||
val += int(r - '0')
|
||||
|
||||
case 'a'..='f':
|
||||
if base == 10 { return -1, false }
|
||||
val *= base
|
||||
val += int(r - 'a' + 10)
|
||||
|
||||
case 'A'..='F':
|
||||
if base == 10 { return -1, false }
|
||||
val *= base
|
||||
val += int(r - 'A' + 10)
|
||||
|
||||
case:
|
||||
return -1, false
|
||||
}
|
||||
|
||||
if val > MAX_RUNE_CODEPOINT { return -1, false }
|
||||
entity = entity[1:]
|
||||
}
|
||||
return rune(val), true
|
||||
|
||||
case:
|
||||
/*
|
||||
Named entity.
|
||||
*/
|
||||
return named_xml_entity_to_rune(entity)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Private XML helper to extract `&<stuff>;` entity.
|
||||
*/
|
||||
@(private="file")
|
||||
_extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
|
||||
assert(t != nil && t.r == '&')
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
using t
|
||||
|
||||
length := len(t.src)
|
||||
found := false
|
||||
|
||||
#no_bounds_check {
|
||||
for read_offset < length {
|
||||
if src[read_offset] == ';' {
|
||||
found = true
|
||||
read_offset += 1
|
||||
break
|
||||
}
|
||||
read_offset += 1
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
return string(src[offset + 1 : read_offset - 1]), .None
|
||||
}
|
||||
return string(src[offset : read_offset]), .Invalid_Entity_Encoding
|
||||
}
|
||||
|
||||
/*
|
||||
Private XML helper for CDATA and comments.
|
||||
*/
|
||||
@(private="file")
|
||||
_handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: XML_Decode_Options) -> (in_data: bool, err: Error) {
|
||||
assert(t != nil && t.r == '<')
|
||||
if t.read_offset + len(CDATA_START) >= len(t.src) { return false, .None }
|
||||
|
||||
if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START {
|
||||
t.read_offset += len(CDATA_START) - 1
|
||||
|
||||
if .Unbox_CDATA in options && .Decode_CDATA in options {
|
||||
/*
|
||||
We're unboxing _and_ decoding CDATA
|
||||
*/
|
||||
return true, .None
|
||||
}
|
||||
|
||||
/*
|
||||
CDATA is passed through.
|
||||
*/
|
||||
offset := t.offset
|
||||
|
||||
/*
|
||||
Scan until end of CDATA.
|
||||
*/
|
||||
for {
|
||||
advance(t) or_return
|
||||
if t.r < 0 { return true, .CDATA_Not_Terminated }
|
||||
|
||||
if t.read_offset + len(CDATA_END) < len(t.src) {
|
||||
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
||||
t.read_offset += len(CDATA_END) - 1
|
||||
|
||||
cdata := string(t.src[offset : t.read_offset])
|
||||
|
||||
if .Unbox_CDATA in options {
|
||||
cdata = cdata[len(CDATA_START):]
|
||||
cdata = cdata[:len(cdata) - len(CDATA_END)]
|
||||
}
|
||||
|
||||
write_string(builder, cdata)
|
||||
return false, .None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
|
||||
t.read_offset += len(COMMENT_START)
|
||||
/*
|
||||
Comment is passed through by default.
|
||||
*/
|
||||
offset := t.offset
|
||||
|
||||
/*
|
||||
Scan until end of Comment.
|
||||
*/
|
||||
for {
|
||||
advance(t) or_return
|
||||
if t.r < 0 { return true, .Comment_Not_Terminated }
|
||||
|
||||
if t.read_offset + len(COMMENT_END) < len(t.src) {
|
||||
if string(t.src[t.offset:][:len(COMMENT_END)]) == COMMENT_END {
|
||||
t.read_offset += len(COMMENT_END) - 1
|
||||
|
||||
if .Comment_Strip not_in options {
|
||||
comment := string(t.src[offset : t.read_offset])
|
||||
write_string(builder, comment)
|
||||
}
|
||||
return false, .None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return false, .None
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package unicode_entity_example
|
||||
|
||||
import "core:encoding/xml"
|
||||
import "core:strings"
|
||||
import "core:mem"
|
||||
import "core:fmt"
|
||||
import "core:time"
|
||||
|
||||
doc_print :: proc(doc: ^xml.Document) {
|
||||
buf: strings.Builder
|
||||
defer strings.builder_destroy(&buf)
|
||||
w := strings.to_writer(&buf)
|
||||
|
||||
xml.print(w, doc)
|
||||
fmt.println(strings.to_string(buf))
|
||||
}
|
||||
|
||||
_entities :: proc() {
|
||||
doc: ^xml.Document
|
||||
err: xml.Error
|
||||
|
||||
DOC :: #load("../../../../tests/core/assets/XML/unicode.xml")
|
||||
|
||||
OPTIONS :: xml.Options{
|
||||
flags = {
|
||||
.Ignore_Unsupported, .Intern_Comments,
|
||||
},
|
||||
expected_doctype = "",
|
||||
}
|
||||
|
||||
parse_duration: time.Duration
|
||||
|
||||
{
|
||||
time.SCOPED_TICK_DURATION(&parse_duration)
|
||||
doc, err = xml.parse(DOC, OPTIONS)
|
||||
}
|
||||
defer xml.destroy(doc)
|
||||
|
||||
doc_print(doc)
|
||||
|
||||
ms := time.duration_milliseconds(parse_duration)
|
||||
|
||||
speed := (f64(1000.0) / ms) * f64(len(DOC)) / 1_024.0 / 1_024.0
|
||||
|
||||
fmt.printf("Parse time: %.2f ms (%.2f MiB/s).\n", ms, speed)
|
||||
fmt.printf("Error: %v\n", err)
|
||||
}
|
||||
|
||||
_main :: proc() {
|
||||
using fmt
|
||||
|
||||
options := xml.Options{ flags = { .Ignore_Unsupported, .Intern_Comments, .Unbox_CDATA, .Decode_SGML_Entities }}
|
||||
|
||||
doc, _ := xml.parse(#load("test.html"), options)
|
||||
|
||||
defer xml.destroy(doc)
|
||||
doc_print(doc)
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
using fmt
|
||||
|
||||
track: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&track, context.allocator)
|
||||
context.allocator = mem.tracking_allocator(&track)
|
||||
|
||||
// _main()
|
||||
_entities()
|
||||
|
||||
if len(track.allocation_map) > 0 {
|
||||
println()
|
||||
for _, v in track.allocation_map {
|
||||
printf("%v Leaked %v bytes.\n", v.location, v.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Entity Reference Test</title>
|
||||
<style>
|
||||
body {
|
||||
background: #000; color: #eee;
|
||||
width: 40%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-size: 14pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Entity Reference Test</h1>
|
||||
<div id="test_cdata_in_comment" foo="">
|
||||
Foozle]! © <!-- <![CDATA[ ® ]]> -->42&;1234&
|
||||
</div>
|
||||
<!-- EXPECTED: Foozle]! © 42&;1234& -->
|
||||
<div id="test_cdata_unwrap_and_passthrough">
|
||||
Foozle]! © <![CDATA[BOX ® /BOX]]>42&;1234&
|
||||
</div>
|
||||
<!-- EXPECTED: Foozle]! © BOX ® /BOX42&;1234& -->
|
||||
<div>
|
||||
| | | fj ` \ ® ϱ ∳ ⁏
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -107,7 +107,7 @@ Node :: struct {
|
||||
/* Conventions */
|
||||
/* ------------
|
||||
Much of HxA's use is based on convention. HxA lets users store arbitrary data in its structure that can be parsed but whose semantic meaning does not need to be understood.
|
||||
A few conventions are hard, and some are soft. Hard convention that a user HAS to follow in order to produce a valid file. Hard conventions simplify parsing becaus the parser can make some assumptions. Soft convenbtions are basicly recomendations of how to store common data.
|
||||
A few conventions are hard, and some are soft. Hard convention that a user HAS to follow in order to produce a valid file. Hard conventions simplify parsing becaus the parser can make some assumptions. Soft convenbtions are basically recomendations of how to store common data.
|
||||
If you use HxA for something not covered by the conventions but need a convention for your use case. Please let us know so that we can add it!
|
||||
*/
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
read_value :: proc(r: ^Reader, $T: typeid) -> (value: T, err: Read_Error) {
|
||||
remaining := len(r.data) - r.offset
|
||||
if remaining < size_of(T) {
|
||||
if r.print_error {
|
||||
fmt.eprintf("file '%s' failed to read value at offset %v\n", r.filename, r.offset)
|
||||
}
|
||||
err = .Short_Read
|
||||
return
|
||||
}
|
||||
@@ -51,6 +54,10 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
read_array :: proc(r: ^Reader, $T: typeid, count: int) -> (value: []T, err: Read_Error) {
|
||||
remaining := len(r.data) - r.offset
|
||||
if remaining < size_of(T)*count {
|
||||
if r.print_error {
|
||||
fmt.eprintf("file '%s' failed to read array of %d elements at offset %v\n",
|
||||
r.filename, count, r.offset)
|
||||
}
|
||||
err = .Short_Read
|
||||
return
|
||||
}
|
||||
@@ -82,7 +89,8 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
type := read_value(r, Meta_Value_Type) or_return
|
||||
if type > max(Meta_Value_Type) {
|
||||
if r.print_error {
|
||||
fmt.eprintf("HxA Error: file '%s' has meta value type %d. Maximum value is ", r.filename, u8(type), u8(max(Meta_Value_Type)))
|
||||
fmt.eprintf("HxA Error: file '%s' has meta value type %d. Maximum value is %d\n",
|
||||
r.filename, u8(type), u8(max(Meta_Value_Type)))
|
||||
}
|
||||
err = .Invalid_Data
|
||||
return
|
||||
@@ -114,7 +122,8 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
type := read_value(r, Layer_Data_Type) or_return
|
||||
if type > max(type) {
|
||||
if r.print_error {
|
||||
fmt.eprintf("HxA Error: file '%s' has layer data type %d. Maximum value is ", r.filename, u8(type), u8(max(Layer_Data_Type)))
|
||||
fmt.eprintf("HxA Error: file '%s' has layer data type %d. Maximum value is %d\n",
|
||||
r.filename, u8(type), u8(max(Layer_Data_Type)))
|
||||
}
|
||||
err = .Invalid_Data
|
||||
return
|
||||
@@ -134,13 +143,23 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
}
|
||||
|
||||
if len(data) < size_of(Header) {
|
||||
if print_error {
|
||||
fmt.eprintf("HxA Error: file '%s' has no header\n", filename)
|
||||
}
|
||||
err = .Short_Read
|
||||
return
|
||||
}
|
||||
|
||||
context.allocator = allocator
|
||||
|
||||
header := cast(^Header)raw_data(data)
|
||||
assert(header.magic_number == MAGIC_NUMBER)
|
||||
if (header.magic_number != MAGIC_NUMBER) {
|
||||
if print_error {
|
||||
fmt.eprintf("HxA Error: file '%s' has invalid magic number 0x%x\n", filename, header.magic_number)
|
||||
}
|
||||
err = .Invalid_Data
|
||||
return
|
||||
}
|
||||
|
||||
r := &Reader{
|
||||
filename = filename,
|
||||
@@ -150,6 +169,7 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
}
|
||||
|
||||
node_count := 0
|
||||
file.header = header^
|
||||
file.nodes = make([]Node, header.internal_node_count)
|
||||
defer if err != nil {
|
||||
nodes_destroy(file.nodes)
|
||||
@@ -162,7 +182,8 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
type := read_value(r, Node_Type) or_return
|
||||
if type > max(Node_Type) {
|
||||
if r.print_error {
|
||||
fmt.eprintf("HxA Error: file '%s' has node type %d. Maximum value is ", r.filename, u8(type), u8(max(Node_Type)))
|
||||
fmt.eprintf("HxA Error: file '%s' has node type %d. Maximum value is %d\n",
|
||||
r.filename, u8(type), u8(max(Node_Type)))
|
||||
}
|
||||
err = .Invalid_Data
|
||||
return
|
||||
|
||||
@@ -84,7 +84,7 @@ write_internal :: proc(w: ^Writer, file: File) {
|
||||
|
||||
write_metadata :: proc(w: ^Writer, meta_data: []Meta) {
|
||||
for m in meta_data {
|
||||
name_len := max(len(m.name), 255)
|
||||
name_len := min(len(m.name), 255)
|
||||
write_value(w, u8(name_len))
|
||||
write_string(w, m.name[:name_len])
|
||||
|
||||
@@ -127,7 +127,7 @@ write_internal :: proc(w: ^Writer, file: File) {
|
||||
write_layer_stack :: proc(w: ^Writer, layers: Layer_Stack) {
|
||||
write_value(w, u32(len(layers)))
|
||||
for layer in layers {
|
||||
name_len := max(len(layer.name), 255)
|
||||
name_len := min(len(layer.name), 255)
|
||||
write_value(w, u8(name_len))
|
||||
write_string(w, layer .name[:name_len])
|
||||
|
||||
@@ -152,7 +152,7 @@ write_internal :: proc(w: ^Writer, file: File) {
|
||||
return
|
||||
}
|
||||
|
||||
write_value(w, &Header{
|
||||
write_value(w, Header{
|
||||
magic_number = MAGIC_NUMBER,
|
||||
version = LATEST_VERSION,
|
||||
internal_node_count = u32le(len(file.nodes)),
|
||||
|
||||
+222
-46
@@ -8,33 +8,63 @@ import "core:strings"
|
||||
import "core:io"
|
||||
|
||||
Marshal_Data_Error :: enum {
|
||||
None,
|
||||
Unsupported_Type,
|
||||
}
|
||||
|
||||
Marshal_Error :: union {
|
||||
Marshal_Error :: union #shared_nil {
|
||||
Marshal_Data_Error,
|
||||
io.Error,
|
||||
}
|
||||
|
||||
marshal :: proc(v: any, allocator := context.allocator) -> (data: []byte, err: Marshal_Error) {
|
||||
b := strings.make_builder(allocator)
|
||||
defer if err != .None {
|
||||
strings.destroy_builder(&b)
|
||||
// careful with MJSON maps & non quotes usage as keys without whitespace will lead to bad results
|
||||
Marshal_Options :: struct {
|
||||
// output based on spec
|
||||
spec: Specification,
|
||||
|
||||
// use line breaks & tab|spaces
|
||||
pretty: bool,
|
||||
|
||||
// spacing
|
||||
use_spaces: bool,
|
||||
spaces: int,
|
||||
|
||||
// state
|
||||
indentation: int,
|
||||
|
||||
// option to output uint in JSON5 & MJSON
|
||||
write_uint_as_hex: bool,
|
||||
|
||||
// mjson output options
|
||||
mjson_keys_use_quotes: bool,
|
||||
mjson_keys_use_equal_sign: bool,
|
||||
|
||||
// mjson state
|
||||
mjson_skipped_first_braces_start: bool,
|
||||
mjson_skipped_first_braces_end: bool,
|
||||
}
|
||||
|
||||
marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator) -> (data: []byte, err: Marshal_Error) {
|
||||
b := strings.builder_make(allocator)
|
||||
defer if err != nil {
|
||||
strings.builder_destroy(&b)
|
||||
}
|
||||
|
||||
marshal_to_builder(&b, v) or_return
|
||||
opt := opt
|
||||
marshal_to_builder(&b, v, &opt) or_return
|
||||
|
||||
if len(b.buf) != 0 {
|
||||
data = b.buf[:]
|
||||
}
|
||||
return data, .None
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
marshal_to_builder :: proc(b: ^strings.Builder, v: any) -> Marshal_Error {
|
||||
return marshal_to_writer(strings.to_writer(b), v)
|
||||
marshal_to_builder :: proc(b: ^strings.Builder, v: any, opt: ^Marshal_Options) -> Marshal_Error {
|
||||
return marshal_to_writer(strings.to_writer(b), v, opt)
|
||||
}
|
||||
|
||||
marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
|
||||
marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: Marshal_Error) {
|
||||
if v == nil {
|
||||
io.write_string(w, "null") or_return
|
||||
return
|
||||
@@ -48,13 +78,14 @@ marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
|
||||
unreachable()
|
||||
|
||||
case runtime.Type_Info_Integer:
|
||||
buf: [21]byte
|
||||
buf: [40]byte
|
||||
u: u128
|
||||
switch i in a {
|
||||
case i8: u = u128(i)
|
||||
case i16: u = u128(i)
|
||||
case i32: u = u128(i)
|
||||
case i64: u = u128(i)
|
||||
case i128: u = u128(i)
|
||||
case int: u = u128(i)
|
||||
case u8: u = u128(i)
|
||||
case u16: u = u128(i)
|
||||
@@ -81,7 +112,21 @@ marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
|
||||
case u128be: u = u128(i)
|
||||
}
|
||||
|
||||
s := strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
|
||||
s: string
|
||||
|
||||
// allow uints to be printed as hex
|
||||
if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) {
|
||||
switch i in a {
|
||||
case u8, u16, u32, u64, u128:
|
||||
s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
|
||||
|
||||
case:
|
||||
s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
|
||||
}
|
||||
} else {
|
||||
s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
|
||||
}
|
||||
|
||||
io.write_string(w, s) or_return
|
||||
|
||||
|
||||
@@ -146,6 +191,9 @@ marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
|
||||
case runtime.Type_Info_Multi_Pointer:
|
||||
return .Unsupported_Type
|
||||
|
||||
case runtime.Type_Info_Soa_Pointer:
|
||||
return .Unsupported_Type
|
||||
|
||||
case runtime.Type_Info_Procedure:
|
||||
return .Unsupported_Type
|
||||
|
||||
@@ -165,52 +213,48 @@ marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
|
||||
return .Unsupported_Type
|
||||
|
||||
case runtime.Type_Info_Array:
|
||||
io.write_byte(w, '[') or_return
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
for i in 0..<info.count {
|
||||
if i > 0 { io.write_string(w, ", ") or_return }
|
||||
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}) or_return
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
io.write_byte(w, ']') or_return
|
||||
opt_write_end(w, opt, ']') or_return
|
||||
|
||||
case runtime.Type_Info_Enumerated_Array:
|
||||
index := runtime.type_info_base(info.index).variant.(runtime.Type_Info_Enum)
|
||||
io.write_byte(w, '[') or_return
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
for i in 0..<info.count {
|
||||
if i > 0 { io.write_string(w, ", ") or_return }
|
||||
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}) or_return
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
io.write_byte(w, ']') or_return
|
||||
opt_write_end(w, opt, ']') or_return
|
||||
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
io.write_byte(w, '[') or_return
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
array := cast(^mem.Raw_Dynamic_Array)v.data
|
||||
for i in 0..<array.len {
|
||||
if i > 0 { io.write_string(w, ", ") or_return }
|
||||
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
data := uintptr(array.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}) or_return
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
io.write_byte(w, ']') or_return
|
||||
opt_write_end(w, opt, ']') or_return
|
||||
|
||||
case runtime.Type_Info_Slice:
|
||||
io.write_byte(w, '[') or_return
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
slice := cast(^mem.Raw_Slice)v.data
|
||||
for i in 0..<slice.len {
|
||||
if i > 0 { io.write_string(w, ", ") or_return }
|
||||
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
data := uintptr(slice.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}) or_return
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
io.write_byte(w, ']') or_return
|
||||
opt_write_end(w, opt, ']') or_return
|
||||
|
||||
case runtime.Type_Info_Map:
|
||||
m := (^mem.Raw_Map)(v.data)
|
||||
opt_write_start(w, opt, '{') or_return
|
||||
|
||||
io.write_byte(w, '{') or_return
|
||||
if m != nil {
|
||||
if info.generated_struct == nil {
|
||||
return .Unsupported_Type
|
||||
@@ -222,31 +266,50 @@ marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
|
||||
entry_size := ed.elem_size
|
||||
|
||||
for i in 0..<entries.len {
|
||||
if i > 0 { io.write_string(w, ", ") or_return }
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
|
||||
data := uintptr(entries.data) + uintptr(i*entry_size)
|
||||
key := rawptr(data + entry_type.offsets[2])
|
||||
value := rawptr(data + entry_type.offsets[3])
|
||||
|
||||
marshal_to_writer(w, any{key, info.key.id}) or_return
|
||||
io.write_string(w, ": ") or_return
|
||||
marshal_to_writer(w, any{value, info.value.id}) or_return
|
||||
// check for string type
|
||||
{
|
||||
v := any{key, info.key.id}
|
||||
ti := runtime.type_info_base(type_info_of(v.id))
|
||||
a := any{v.data, ti.id}
|
||||
name: string
|
||||
|
||||
#partial switch info in ti.variant {
|
||||
case runtime.Type_Info_String:
|
||||
switch s in a {
|
||||
case string: name = s
|
||||
case cstring: name = string(s)
|
||||
}
|
||||
opt_write_key(w, opt, name) or_return
|
||||
|
||||
case: return .Unsupported_Type
|
||||
}
|
||||
}
|
||||
|
||||
marshal_to_writer(w, any{value, info.value.id}, opt) or_return
|
||||
}
|
||||
}
|
||||
io.write_byte(w, '}') or_return
|
||||
|
||||
opt_write_end(w, opt, '}') or_return
|
||||
|
||||
case runtime.Type_Info_Struct:
|
||||
io.write_byte(w, '{') or_return
|
||||
opt_write_start(w, opt, '{') or_return
|
||||
|
||||
for name, i in info.names {
|
||||
if i > 0 { io.write_string(w, ", ") or_return }
|
||||
io.write_quoted_string(w, name) or_return
|
||||
io.write_string(w, ": ") or_return
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_key(w, opt, name) or_return
|
||||
|
||||
id := info.types[i].id
|
||||
data := rawptr(uintptr(v.data) + info.offsets[i])
|
||||
marshal_to_writer(w, any{data, id}) or_return
|
||||
marshal_to_writer(w, any{data, id}, opt) or_return
|
||||
}
|
||||
io.write_byte(w, '}') or_return
|
||||
|
||||
opt_write_end(w, opt, '}') or_return
|
||||
|
||||
case runtime.Type_Info_Union:
|
||||
tag_ptr := uintptr(v.data) + info.tag_offset
|
||||
@@ -269,11 +332,11 @@ marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
|
||||
io.write_string(w, "null") or_return
|
||||
} else {
|
||||
id := info.variants[tag-1].id
|
||||
return marshal_to_writer(w, any{v.data, id})
|
||||
return marshal_to_writer(w, any{v.data, id}, opt)
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Enum:
|
||||
return marshal_to_writer(w, any{v.data, info.base.id})
|
||||
return marshal_to_writer(w, any{v.data, info.base.id}, opt)
|
||||
|
||||
case runtime.Type_Info_Bit_Set:
|
||||
is_bit_set_different_endian_to_platform :: proc(ti: ^runtime.Type_Info) -> bool {
|
||||
@@ -329,3 +392,116 @@ marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// write key as quoted string or with optional quotes in mjson
|
||||
opt_write_key :: proc(w: io.Writer, opt: ^Marshal_Options, name: string) -> (err: io.Error) {
|
||||
switch opt.spec {
|
||||
case .JSON, .JSON5:
|
||||
io.write_quoted_string(w, name) or_return
|
||||
io.write_string(w, ": ") or_return
|
||||
|
||||
case .MJSON:
|
||||
if opt.mjson_keys_use_quotes {
|
||||
io.write_quoted_string(w, name) or_return
|
||||
} else {
|
||||
io.write_string(w, name) or_return
|
||||
}
|
||||
|
||||
if opt.mjson_keys_use_equal_sign {
|
||||
io.write_string(w, " = ") or_return
|
||||
} else {
|
||||
io.write_string(w, ": ") or_return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// insert start byte and increase indentation on pretty
|
||||
opt_write_start :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: io.Error) {
|
||||
// skip mjson starting braces
|
||||
if opt.spec == .MJSON && !opt.mjson_skipped_first_braces_start {
|
||||
opt.mjson_skipped_first_braces_start = true
|
||||
return
|
||||
}
|
||||
|
||||
io.write_byte(w, c) or_return
|
||||
opt.indentation += 1
|
||||
|
||||
if opt.pretty {
|
||||
io.write_byte(w, '\n') or_return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// insert comma seperation and write indentations
|
||||
opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int) -> (err: io.Error) {
|
||||
switch opt.spec {
|
||||
case .JSON, .JSON5:
|
||||
if iteration > 0 {
|
||||
io.write_string(w, ", ") or_return
|
||||
|
||||
if opt.pretty {
|
||||
io.write_byte(w, '\n') or_return
|
||||
}
|
||||
}
|
||||
|
||||
opt_write_indentation(w, opt) or_return
|
||||
|
||||
case .MJSON:
|
||||
if iteration > 0 {
|
||||
// on pretty no commas necessary
|
||||
if opt.pretty {
|
||||
io.write_byte(w, '\n') or_return
|
||||
} else {
|
||||
// comma seperation necessary for non pretty output!
|
||||
io.write_string(w, ", ") or_return
|
||||
}
|
||||
}
|
||||
|
||||
opt_write_indentation(w, opt) or_return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// decrease indent, write spacing and insert end byte
|
||||
opt_write_end :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: io.Error) {
|
||||
if opt.spec == .MJSON && opt.mjson_skipped_first_braces_start && !opt.mjson_skipped_first_braces_end {
|
||||
if opt.indentation == 0 {
|
||||
opt.mjson_skipped_first_braces_end = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
opt.indentation -= 1
|
||||
|
||||
if opt.pretty {
|
||||
io.write_byte(w, '\n') or_return
|
||||
opt_write_indentation(w, opt) or_return
|
||||
}
|
||||
|
||||
io.write_byte(w, c) or_return
|
||||
return
|
||||
}
|
||||
|
||||
// writes current indentation level based on options
|
||||
opt_write_indentation :: proc(w: io.Writer, opt: ^Marshal_Options) -> (err: io.Error) {
|
||||
if !opt.pretty {
|
||||
return
|
||||
}
|
||||
|
||||
if opt.use_spaces {
|
||||
spaces := opt.spaces == 0 ? 4 : opt.spaces
|
||||
for _ in 0..<opt.indentation * spaces {
|
||||
io.write_byte(w, ' ') or_return
|
||||
}
|
||||
} else {
|
||||
for _ in 0..<opt.indentation {
|
||||
io.write_byte(w, '\t') or_return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers
|
||||
return parse_object(&p)
|
||||
case .JSON5:
|
||||
return parse_value(&p)
|
||||
case .MJSON:
|
||||
case .SJSON:
|
||||
#partial switch p.curr_token.kind {
|
||||
case .Ident, .String:
|
||||
return parse_object_body(&p, .EOF)
|
||||
@@ -354,6 +354,12 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a
|
||||
|
||||
b := bytes_make(len(s) + 2*utf8.UTF_MAX, 1, allocator) or_return
|
||||
w := copy(b, s[0:i])
|
||||
|
||||
if len(b) == 0 && allocator.data == nil {
|
||||
// `unmarshal_count_array` calls us with a nil allocator
|
||||
return string(b[:w]), nil
|
||||
}
|
||||
|
||||
loop: for i < len(s) {
|
||||
c := s[i]
|
||||
switch {
|
||||
|
||||
@@ -33,8 +33,9 @@ package json
|
||||
Specification :: enum {
|
||||
JSON,
|
||||
JSON5, // https://json5.org/
|
||||
MJSON, // https://bitsquid.blogspot.com/2009/10/simplified-json-notation.html
|
||||
Bitsquid = MJSON,
|
||||
SJSON, // https://bitsquid.blogspot.com/2009/10/simplified-json-notation.html
|
||||
Bitsquid = SJSON,
|
||||
MJSON = SJSON,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) {
|
||||
variant := u.variants[0]
|
||||
v.id = variant.id
|
||||
ti = reflect.type_info_base(variant)
|
||||
if !(u.maybe && reflect.is_pointer(variant)) {
|
||||
if !reflect.is_pointer_internally(variant) {
|
||||
tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id}
|
||||
assign_int(tag, 1)
|
||||
}
|
||||
@@ -325,7 +325,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
|
||||
UNSUPPORTED_TYPE := Unsupported_Type_Error{v.id, p.curr_token}
|
||||
|
||||
if end_token == .Close_Brace {
|
||||
assert(expect_token(p, .Open_Brace) == nil)
|
||||
unmarshal_expect_token(p, .Open_Brace)
|
||||
}
|
||||
|
||||
v := v
|
||||
@@ -473,7 +473,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
|
||||
}
|
||||
|
||||
if end_token == .Close_Brace {
|
||||
assert(expect_token(p, .Close_Brace) == nil)
|
||||
unmarshal_expect_token(p, .Close_Brace)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Implementation of the LEB128 variable integer encoding as used by DWARF encoding and DEX files, among others.
|
||||
|
||||
Author of this Odin package: Jeroen van Rijn
|
||||
|
||||
Example:
|
||||
```odin
|
||||
import "core:encoding/varint"
|
||||
import "core:fmt"
|
||||
|
||||
main :: proc() {
|
||||
buf: [varint.LEB128_MAX_BYTES]u8
|
||||
|
||||
value := u128(42)
|
||||
|
||||
encode_size, encode_err := varint.encode_uleb128(buf[:], value)
|
||||
assert(encode_size == 1 && encode_err == .None)
|
||||
|
||||
fmt.printf("Encoded as %v\n", buf[:encode_size])
|
||||
decoded_val, decode_size, decode_err := varint.decode_uleb128(buf[:])
|
||||
|
||||
assert(decoded_val == value && decode_size == encode_size && decode_err == .None)
|
||||
fmt.printf("Decoded as %v, using %v byte%v\n", decoded_val, decode_size, "" if decode_size == 1 else "s")
|
||||
}
|
||||
```
|
||||
|
||||
*/
|
||||
package varint
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
*/
|
||||
|
||||
// package varint implements variable length integer encoding and decoding using
|
||||
// the LEB128 format as used by DWARF debug info, Android .dex and other file formats.
|
||||
package varint
|
||||
|
||||
// In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file.
|
||||
// Instead we'll set limits on the values we'll encode/decode
|
||||
// 18 * 7 bits = 126, which means that a possible 19th byte may at most be `0b0000_0011`.
|
||||
LEB128_MAX_BYTES :: 19
|
||||
|
||||
Error :: enum {
|
||||
None = 0,
|
||||
Buffer_Too_Small = 1,
|
||||
Value_Too_Large = 2,
|
||||
}
|
||||
|
||||
// Decode a slice of bytes encoding an unsigned LEB128 integer into value and number of bytes used.
|
||||
// Returns `size` == 0 for an invalid value, empty slice, or a varint > 18 bytes.
|
||||
decode_uleb128_buffer :: proc(buf: []u8) -> (val: u128, size: int, err: Error) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0, .Buffer_Too_Small
|
||||
}
|
||||
|
||||
for v in buf {
|
||||
val, size, err = decode_uleb128_byte(v, size, val)
|
||||
if err != .Buffer_Too_Small {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err == .Buffer_Too_Small {
|
||||
val, size = 0, 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Decodes an unsigned LEB128 integer into value a byte at a time.
|
||||
// Returns `.None` when decoded properly, `.Value_Too_Large` when they value
|
||||
// exceeds the limits of a u128, and `.Buffer_Too_Small` when it's not yet fully decoded.
|
||||
decode_uleb128_byte :: proc(input: u8, offset: int, accumulator: u128) -> (val: u128, size: int, err: Error) {
|
||||
size = offset + 1
|
||||
|
||||
// 18 * 7 bits = 126, which means that a possible 19th byte may at most be 0b0000_0011.
|
||||
if size > LEB128_MAX_BYTES || size == LEB128_MAX_BYTES && input > 0b0000_0011 {
|
||||
return 0, 0, .Value_Too_Large
|
||||
}
|
||||
|
||||
val = accumulator | u128(input & 0x7f) << uint(offset * 7)
|
||||
|
||||
if input < 128 {
|
||||
// We're done
|
||||
return
|
||||
}
|
||||
|
||||
// If the buffer runs out before the number ends, return an error.
|
||||
return val, size, .Buffer_Too_Small
|
||||
}
|
||||
decode_uleb128 :: proc {decode_uleb128_buffer, decode_uleb128_byte}
|
||||
|
||||
// Decode a slice of bytes encoding a signed LEB128 integer into value and number of bytes used.
|
||||
// Returns `size` == 0 for an invalid value, empty slice, or a varint > 18 bytes.
|
||||
decode_ileb128_buffer :: proc(buf: []u8) -> (val: i128, size: int, err: Error) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0, .Buffer_Too_Small
|
||||
}
|
||||
|
||||
for v in buf {
|
||||
val, size, err = decode_ileb128_byte(v, size, val)
|
||||
if err != .Buffer_Too_Small {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err == .Buffer_Too_Small {
|
||||
val, size = 0, 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Decode a a signed LEB128 integer into value and number of bytes used, one byte at a time.
|
||||
// Returns `size` == 0 for an invalid value, empty slice, or a varint > 18 bytes.
|
||||
decode_ileb128_byte :: proc(input: u8, offset: int, accumulator: i128) -> (val: i128, size: int, err: Error) {
|
||||
size = offset + 1
|
||||
shift := uint(offset * 7)
|
||||
|
||||
// 18 * 7 bits = 126, which including sign means we can have a 19th byte.
|
||||
if size > LEB128_MAX_BYTES || size == LEB128_MAX_BYTES && input > 0x7f {
|
||||
return 0, 0, .Value_Too_Large
|
||||
}
|
||||
|
||||
val = accumulator | i128(input & 0x7f) << shift
|
||||
|
||||
if input < 128 {
|
||||
if input & 0x40 == 0x40 {
|
||||
val |= max(i128) << (shift + 7)
|
||||
}
|
||||
return val, size, .None
|
||||
}
|
||||
return val, size, .Buffer_Too_Small
|
||||
}
|
||||
decode_ileb128 :: proc{decode_ileb128_buffer, decode_ileb128_byte}
|
||||
|
||||
// Encode `val` into `buf` as an unsigned LEB128 encoded series of bytes.
|
||||
// `buf` must be appropriately sized.
|
||||
encode_uleb128 :: proc(buf: []u8, val: u128) -> (size: int, err: Error) {
|
||||
val := val
|
||||
|
||||
for {
|
||||
size += 1
|
||||
|
||||
if size > len(buf) {
|
||||
return 0, .Buffer_Too_Small
|
||||
}
|
||||
|
||||
low := val & 0x7f
|
||||
val >>= 7
|
||||
|
||||
if val > 0 {
|
||||
low |= 0x80 // more bytes to follow
|
||||
}
|
||||
buf[size - 1] = u8(low)
|
||||
|
||||
if val == 0 { break }
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Encode `val` into `buf` as a signed LEB128 encoded series of bytes.
|
||||
// `buf` must be appropriately sized.
|
||||
encode_ileb128 :: proc(buf: []u8, val: i128) -> (size: int, err: Error) {
|
||||
SIGN_MASK :: i128(1) << 121 // sign extend mask
|
||||
|
||||
val, more := val, true
|
||||
|
||||
for more {
|
||||
size += 1
|
||||
|
||||
if size > len(buf) {
|
||||
return 0, .Buffer_Too_Small
|
||||
}
|
||||
|
||||
low := val & 0x7f
|
||||
val >>= 7
|
||||
|
||||
low = (low ~ SIGN_MASK) - SIGN_MASK
|
||||
|
||||
if (val == 0 && low & 0x40 != 0x40) || (val == -1 && low & 0x40 == 0x40) {
|
||||
more = false
|
||||
} else {
|
||||
low |= 0x80
|
||||
}
|
||||
|
||||
buf[size - 1] = u8(low)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
An XML 1.0 / 1.1 parser
|
||||
|
||||
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
A from-scratch XML implementation, loosely modeled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816).
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
*/
|
||||
package xml
|
||||
|
||||
import "core:io"
|
||||
import "core:fmt"
|
||||
|
||||
/*
|
||||
Just for debug purposes.
|
||||
*/
|
||||
print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error) {
|
||||
if doc == nil { return }
|
||||
using fmt
|
||||
|
||||
written += wprintf(writer, "[XML Prolog]\n")
|
||||
|
||||
for attr in doc.prologue {
|
||||
written += wprintf(writer, "\t%v: %v\n", attr.key, attr.val)
|
||||
}
|
||||
|
||||
written += wprintf(writer, "[Encoding] %v\n", doc.encoding)
|
||||
|
||||
if len(doc.doctype.ident) > 0 {
|
||||
written += wprintf(writer, "[DOCTYPE] %v\n", doc.doctype.ident)
|
||||
|
||||
if len(doc.doctype.rest) > 0 {
|
||||
wprintf(writer, "\t%v\n", doc.doctype.rest)
|
||||
}
|
||||
}
|
||||
|
||||
for comment in doc.comments {
|
||||
written += wprintf(writer, "[Pre-root comment] %v\n", comment)
|
||||
}
|
||||
|
||||
if len(doc.elements) > 0 {
|
||||
wprintln(writer, " --- ")
|
||||
print_element(writer, doc, 0)
|
||||
wprintln(writer, " --- ")
|
||||
}
|
||||
|
||||
return written, .None
|
||||
}
|
||||
|
||||
print_element :: proc(writer: io.Writer, doc: ^Document, element_id: Element_ID, indent := 0) -> (written: int, err: io.Error) {
|
||||
using fmt
|
||||
|
||||
tab :: proc(writer: io.Writer, indent: int) {
|
||||
for _ in 0..=indent {
|
||||
wprintf(writer, "\t")
|
||||
}
|
||||
}
|
||||
|
||||
tab(writer, indent)
|
||||
|
||||
element := doc.elements[element_id]
|
||||
|
||||
if element.kind == .Element {
|
||||
wprintf(writer, "<%v>\n", element.ident)
|
||||
if len(element.value) > 0 {
|
||||
tab(writer, indent + 1)
|
||||
wprintf(writer, "[Value] %v\n", element.value)
|
||||
}
|
||||
|
||||
for attr in element.attribs {
|
||||
tab(writer, indent + 1)
|
||||
wprintf(writer, "[Attr] %v: %v\n", attr.key, attr.val)
|
||||
}
|
||||
|
||||
for child in element.children {
|
||||
print_element(writer, doc, child, indent + 1)
|
||||
}
|
||||
} else if element.kind == .Comment {
|
||||
wprintf(writer, "[COMMENT] %v\n", element.value)
|
||||
}
|
||||
|
||||
return written, .None
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package xml_example
|
||||
|
||||
import "core:encoding/xml"
|
||||
import "core:mem"
|
||||
import "core:fmt"
|
||||
import "core:time"
|
||||
import "core:strings"
|
||||
import "core:hash"
|
||||
|
||||
N :: 1
|
||||
|
||||
example :: proc() {
|
||||
using fmt
|
||||
|
||||
docs: [N]^xml.Document
|
||||
errs: [N]xml.Error
|
||||
times: [N]time.Duration
|
||||
|
||||
defer for round in 0..<N {
|
||||
xml.destroy(docs[round])
|
||||
}
|
||||
|
||||
DOC :: #load("../../../../tests/core/assets/XML/unicode.xml")
|
||||
input := DOC
|
||||
|
||||
for round in 0..<N {
|
||||
start := time.tick_now()
|
||||
|
||||
docs[round], errs[round] = xml.parse(input, xml.Options{
|
||||
flags={.Ignore_Unsupported},
|
||||
expected_doctype = "",
|
||||
})
|
||||
|
||||
end := time.tick_now()
|
||||
times[round] = time.tick_diff(start, end)
|
||||
}
|
||||
|
||||
fastest := max(time.Duration)
|
||||
slowest := time.Duration(0)
|
||||
total := time.Duration(0)
|
||||
|
||||
for round in 0..<N {
|
||||
fastest = min(fastest, times[round])
|
||||
slowest = max(slowest, times[round])
|
||||
total += times[round]
|
||||
}
|
||||
|
||||
fastest_ms := time.duration_milliseconds(fastest)
|
||||
slowest_ms := time.duration_milliseconds(slowest)
|
||||
average_ms := time.duration_milliseconds(time.Duration(f64(total) / f64(N)))
|
||||
|
||||
fastest_speed := (f64(1000.0) / fastest_ms) * f64(len(DOC)) / 1_024.0 / 1_024.0
|
||||
slowest_speed := (f64(1000.0) / slowest_ms) * f64(len(DOC)) / 1_024.0 / 1_024.0
|
||||
average_speed := (f64(1000.0) / average_ms) * f64(len(DOC)) / 1_024.0 / 1_024.0
|
||||
|
||||
fmt.printf("N = %v\n", N)
|
||||
fmt.printf("[Fastest]: %v bytes in %.2f ms (%.2f MiB/s).\n", len(input), fastest_ms, fastest_speed)
|
||||
fmt.printf("[Slowest]: %v bytes in %.2f ms (%.2f MiB/s).\n", len(input), slowest_ms, slowest_speed)
|
||||
fmt.printf("[Average]: %v bytes in %.2f ms (%.2f MiB/s).\n", len(input), average_ms, average_speed)
|
||||
|
||||
if errs[0] != .None {
|
||||
printf("Load/Parse error: %v\n", errs[0])
|
||||
if errs[0] == .File_Error {
|
||||
println("\"unicode.xml\" not found. Did you run \"tests\\download_assets.py\"?")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
charlist, charlist_ok := xml.find_child_by_ident(docs[0], 0, "charlist")
|
||||
if !charlist_ok {
|
||||
eprintln("Could not locate top-level `<charlist>` tag.")
|
||||
return
|
||||
}
|
||||
|
||||
printf("Found `<charlist>` with %v children, %v elements total\n", len(docs[0].elements[charlist].children), docs[0].element_count)
|
||||
|
||||
crc32 := doc_hash(docs[0])
|
||||
printf("[%v] CRC32: 0x%08x\n", "🎉" if crc32 == 0xcaa042b9 else "🤬", crc32)
|
||||
|
||||
for round in 0..<N {
|
||||
defer xml.destroy(docs[round])
|
||||
}
|
||||
}
|
||||
|
||||
doc_hash :: proc(doc: ^xml.Document, print := false) -> (crc32: u32) {
|
||||
buf: strings.Builder
|
||||
defer strings.builder_destroy(&buf)
|
||||
w := strings.to_writer(&buf)
|
||||
|
||||
xml.print(w, doc)
|
||||
tree := strings.to_string(buf)
|
||||
if print { fmt.println(tree) }
|
||||
return hash.crc32(transmute([]u8)tree)
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
using fmt
|
||||
|
||||
track: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&track, context.allocator)
|
||||
context.allocator = mem.tracking_allocator(&track)
|
||||
|
||||
example()
|
||||
|
||||
if len(track.allocation_map) > 0 {
|
||||
println()
|
||||
for _, v in track.allocation_map {
|
||||
printf("%v Leaked %v bytes.\n", v.location, v.size)
|
||||
}
|
||||
}
|
||||
println("Done and cleaned up!")
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
An XML 1.0 / 1.1 parser
|
||||
|
||||
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
This file contains helper functions.
|
||||
*/
|
||||
package xml
|
||||
|
||||
// Find parent's nth child with a given ident.
|
||||
find_child_by_ident :: proc(doc: ^Document, parent_id: Element_ID, ident: string, nth := 0) -> (res: Element_ID, found: bool) {
|
||||
tag := doc.elements[parent_id]
|
||||
|
||||
count := 0
|
||||
for child_id in tag.children {
|
||||
child := doc.elements[child_id]
|
||||
/*
|
||||
Skip commments. They have no name.
|
||||
*/
|
||||
if child.kind != .Element { continue }
|
||||
|
||||
/*
|
||||
If the ident matches and it's the nth such child, return it.
|
||||
*/
|
||||
if child.ident == ident {
|
||||
if count == nth { return child_id, true }
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Find an attribute by key.
|
||||
find_attribute_val_by_key :: proc(doc: ^Document, parent_id: Element_ID, key: string) -> (val: string, found: bool) {
|
||||
tag := doc.elements[parent_id]
|
||||
|
||||
for attr in tag.attribs {
|
||||
/*
|
||||
If the ident matches, we're done. There can only ever be one attribute with the same name.
|
||||
*/
|
||||
if attr.key == key { return attr.val, true }
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
An XML 1.0 / 1.1 parser
|
||||
|
||||
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
A from-scratch XML implementation, loosely modeled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816).
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
*/
|
||||
package xml
|
||||
|
||||
import "core:fmt"
|
||||
import "core:unicode"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any)
|
||||
|
||||
Token :: struct {
|
||||
kind: Token_Kind,
|
||||
text: string,
|
||||
pos: Pos,
|
||||
}
|
||||
|
||||
Pos :: struct {
|
||||
file: string,
|
||||
offset: int, // starting at 0
|
||||
line: int, // starting at 1
|
||||
column: int, // starting at 1
|
||||
}
|
||||
|
||||
Token_Kind :: enum {
|
||||
Invalid,
|
||||
|
||||
Ident,
|
||||
Literal,
|
||||
Rune,
|
||||
String,
|
||||
|
||||
Double_Quote, // "
|
||||
Single_Quote, // '
|
||||
Colon, // :
|
||||
|
||||
Eq, // =
|
||||
Lt, // <
|
||||
Gt, // >
|
||||
Exclaim, // !
|
||||
Question, // ?
|
||||
Hash, // #
|
||||
Slash, // /
|
||||
Dash, // -
|
||||
|
||||
Open_Bracket, // [
|
||||
Close_Bracket, // ]
|
||||
|
||||
EOF,
|
||||
}
|
||||
|
||||
CDATA_START :: "<![CDATA["
|
||||
CDATA_END :: "]]>"
|
||||
|
||||
COMMENT_START :: "<!--"
|
||||
COMMENT_END :: "-->"
|
||||
|
||||
Tokenizer :: struct {
|
||||
// Immutable data
|
||||
path: string,
|
||||
src: string,
|
||||
err: Error_Handler,
|
||||
|
||||
// Tokenizing state
|
||||
ch: rune,
|
||||
offset: int,
|
||||
read_offset: int,
|
||||
line_offset: int,
|
||||
line_count: int,
|
||||
|
||||
// Mutable data
|
||||
error_count: int,
|
||||
}
|
||||
|
||||
init :: proc(t: ^Tokenizer, src: string, path: string, err: Error_Handler = default_error_handler) {
|
||||
t.src = src
|
||||
t.err = err
|
||||
t.ch = ' '
|
||||
t.offset = 0
|
||||
t.read_offset = 0
|
||||
t.line_offset = 0
|
||||
t.line_count = len(src) > 0 ? 1 : 0
|
||||
t.error_count = 0
|
||||
t.path = path
|
||||
|
||||
advance_rune(t)
|
||||
if t.ch == utf8.RUNE_BOM {
|
||||
advance_rune(t)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
offset_to_pos :: proc(t: ^Tokenizer, offset: int) -> Pos {
|
||||
line := t.line_count
|
||||
column := offset - t.line_offset + 1
|
||||
|
||||
return Pos {
|
||||
file = t.path,
|
||||
offset = offset,
|
||||
line = line,
|
||||
column = column,
|
||||
}
|
||||
}
|
||||
|
||||
default_error_handler :: proc(pos: Pos, msg: string, args: ..any) {
|
||||
fmt.eprintf("%s(%d:%d) ", pos.file, pos.line, pos.column)
|
||||
fmt.eprintf(msg, ..args)
|
||||
fmt.eprintf("\n")
|
||||
}
|
||||
|
||||
error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
|
||||
pos := offset_to_pos(t, offset)
|
||||
if t.err != nil {
|
||||
t.err(pos, msg, ..args)
|
||||
}
|
||||
t.error_count += 1
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
advance_rune :: proc(using t: ^Tokenizer) {
|
||||
#no_bounds_check {
|
||||
/*
|
||||
Already bounds-checked here.
|
||||
*/
|
||||
if read_offset < len(src) {
|
||||
offset = read_offset
|
||||
if ch == '\n' {
|
||||
line_offset = offset
|
||||
line_count += 1
|
||||
}
|
||||
r, w := rune(src[read_offset]), 1
|
||||
switch {
|
||||
case r == 0:
|
||||
error(t, t.offset, "illegal character NUL")
|
||||
case r >= utf8.RUNE_SELF:
|
||||
r, w = #force_inline utf8.decode_rune_in_string(src[read_offset:])
|
||||
if r == utf8.RUNE_ERROR && w == 1 {
|
||||
error(t, t.offset, "illegal UTF-8 encoding")
|
||||
} else if r == utf8.RUNE_BOM && offset > 0 {
|
||||
error(t, t.offset, "illegal byte order mark")
|
||||
}
|
||||
}
|
||||
read_offset += w
|
||||
ch = r
|
||||
} else {
|
||||
offset = len(src)
|
||||
if ch == '\n' {
|
||||
line_offset = offset
|
||||
line_count += 1
|
||||
}
|
||||
ch = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
peek_byte :: proc(t: ^Tokenizer, offset := 0) -> byte {
|
||||
if t.read_offset+offset < len(t.src) {
|
||||
#no_bounds_check return t.src[t.read_offset+offset]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
skip_whitespace :: proc(t: ^Tokenizer) {
|
||||
for {
|
||||
switch t.ch {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
advance_rune(t)
|
||||
case:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
is_letter :: proc(r: rune) -> bool {
|
||||
if r < utf8.RUNE_SELF {
|
||||
switch r {
|
||||
case '_':
|
||||
return true
|
||||
case 'A'..='Z', 'a'..='z':
|
||||
return true
|
||||
}
|
||||
}
|
||||
return unicode.is_letter(r)
|
||||
}
|
||||
|
||||
is_valid_identifier_rune :: proc(r: rune) -> bool {
|
||||
if r < utf8.RUNE_SELF {
|
||||
switch r {
|
||||
case '_', '-', ':': return true
|
||||
case 'A'..='Z', 'a'..='z': return true
|
||||
case '0'..='9': return true
|
||||
case -1: return false
|
||||
}
|
||||
}
|
||||
|
||||
if unicode.is_letter(r) || unicode.is_digit(r) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
scan_identifier :: proc(t: ^Tokenizer) -> string {
|
||||
offset := t.offset
|
||||
namespaced := false
|
||||
|
||||
for is_valid_identifier_rune(t.ch) {
|
||||
advance_rune(t)
|
||||
if t.ch == ':' {
|
||||
/*
|
||||
A namespaced attr can have at most two parts, `namespace:ident`.
|
||||
*/
|
||||
if namespaced {
|
||||
break
|
||||
}
|
||||
namespaced = true
|
||||
}
|
||||
}
|
||||
return string(t.src[offset : t.offset])
|
||||
}
|
||||
|
||||
/*
|
||||
A comment ends when we see -->, preceded by a character that's not a dash.
|
||||
"For compatibility, the string "--" (double-hyphen) must not occur within comments."
|
||||
|
||||
See: https://www.w3.org/TR/2006/REC-xml11-20060816/#dt-comment
|
||||
|
||||
Thanks to the length (4) of the comment start, we also have enough lookback,
|
||||
and the peek at the next byte asserts that there's at least one more character
|
||||
that's a `>`.
|
||||
*/
|
||||
scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) {
|
||||
offset := t.offset
|
||||
|
||||
for {
|
||||
advance_rune(t)
|
||||
ch := t.ch
|
||||
|
||||
if ch < 0 {
|
||||
error(t, offset, "[parse] Comment was not terminated\n")
|
||||
return "", .Unclosed_Comment
|
||||
}
|
||||
|
||||
if string(t.src[t.offset - 1:][:2]) == "--" {
|
||||
if peek_byte(t) == '>' {
|
||||
break
|
||||
} else {
|
||||
error(t, t.offset - 1, "Invalid -- sequence in comment.\n")
|
||||
return "", .Invalid_Sequence_In_Comment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(t, .Dash)
|
||||
expect(t, .Gt)
|
||||
|
||||
return string(t.src[offset : t.offset - 1]), .None
|
||||
}
|
||||
|
||||
/*
|
||||
Skip CDATA
|
||||
*/
|
||||
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
|
||||
if t.read_offset + len(CDATA_START) >= len(t.src) {
|
||||
/*
|
||||
Can't be the start of a CDATA tag.
|
||||
*/
|
||||
return .None
|
||||
}
|
||||
|
||||
if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START {
|
||||
t.read_offset += len(CDATA_START)
|
||||
offset := t.offset
|
||||
|
||||
cdata_scan: for {
|
||||
advance_rune(t)
|
||||
if t.ch < 0 {
|
||||
error(t, offset, "[scan_string] CDATA was not terminated\n")
|
||||
return .Premature_EOF
|
||||
}
|
||||
|
||||
/*
|
||||
Scan until the end of a CDATA tag.
|
||||
*/
|
||||
if t.read_offset + len(CDATA_END) < len(t.src) {
|
||||
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
||||
t.read_offset += len(CDATA_END)
|
||||
break cdata_scan
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close := false, multiline := true) -> (value: string, err: Error) {
|
||||
err = .None
|
||||
|
||||
loop: for {
|
||||
ch := t.ch
|
||||
|
||||
switch ch {
|
||||
case -1:
|
||||
error(t, t.offset, "[scan_string] Premature end of file.\n")
|
||||
return "", .Premature_EOF
|
||||
|
||||
case '<':
|
||||
if peek_byte(t) == '!' {
|
||||
if peek_byte(t, 1) == '[' {
|
||||
/*
|
||||
Might be the start of a CDATA tag.
|
||||
*/
|
||||
skip_cdata(t) or_return
|
||||
} else if peek_byte(t, 1) == '-' && peek_byte(t, 2) == '-' {
|
||||
/*
|
||||
Comment start. Eat comment.
|
||||
*/
|
||||
t.read_offset += 3
|
||||
_ = scan_comment(t) or_return
|
||||
}
|
||||
}
|
||||
|
||||
case '\n':
|
||||
if !multiline {
|
||||
error(t, offset, string(t.src[offset : t.offset]))
|
||||
error(t, offset, "[scan_string] Not terminated\n")
|
||||
err = .Invalid_Tag_Value
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
if t.ch == close {
|
||||
/*
|
||||
If it's not a CDATA or comment, it's the end of this body.
|
||||
*/
|
||||
break loop
|
||||
}
|
||||
advance_rune(t)
|
||||
}
|
||||
|
||||
/*
|
||||
Strip trailing whitespace.
|
||||
*/
|
||||
lit := string(t.src[offset : t.offset])
|
||||
|
||||
end := len(lit)
|
||||
eat: for ; end > 0; end -= 1 {
|
||||
ch := lit[end - 1]
|
||||
switch ch {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
case:
|
||||
break eat
|
||||
}
|
||||
}
|
||||
lit = lit[:end]
|
||||
|
||||
if consume_close {
|
||||
advance_rune(t)
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: Handle decoding escape characters and unboxing CDATA.
|
||||
*/
|
||||
|
||||
return lit, err
|
||||
}
|
||||
|
||||
peek :: proc(t: ^Tokenizer) -> (token: Token) {
|
||||
old := t^
|
||||
token = scan(t)
|
||||
t^ = old
|
||||
return token
|
||||
}
|
||||
|
||||
scan :: proc(t: ^Tokenizer) -> Token {
|
||||
skip_whitespace(t)
|
||||
|
||||
offset := t.offset
|
||||
|
||||
kind: Token_Kind
|
||||
err: Error
|
||||
lit: string
|
||||
pos := offset_to_pos(t, offset)
|
||||
|
||||
switch ch := t.ch; true {
|
||||
case is_letter(ch):
|
||||
lit = scan_identifier(t)
|
||||
kind = .Ident
|
||||
|
||||
case:
|
||||
advance_rune(t)
|
||||
switch ch {
|
||||
case -1:
|
||||
kind = .EOF
|
||||
|
||||
case '<': kind = .Lt
|
||||
case '>': kind = .Gt
|
||||
case '!': kind = .Exclaim
|
||||
case '?': kind = .Question
|
||||
case '=': kind = .Eq
|
||||
case '#': kind = .Hash
|
||||
case '/': kind = .Slash
|
||||
case '-': kind = .Dash
|
||||
case ':': kind = .Colon
|
||||
|
||||
case '"', '\'':
|
||||
kind = .Invalid
|
||||
|
||||
lit, err = scan_string(t, t.offset, ch, true, false)
|
||||
if err == .None {
|
||||
kind = .String
|
||||
}
|
||||
|
||||
case '\n':
|
||||
lit = "\n"
|
||||
|
||||
case:
|
||||
kind = .Invalid
|
||||
}
|
||||
}
|
||||
|
||||
if kind != .String && lit == "" {
|
||||
lit = string(t.src[offset : t.offset])
|
||||
}
|
||||
return Token{kind, lit, pos}
|
||||
}
|
||||
@@ -0,0 +1,713 @@
|
||||
/*
|
||||
An XML 1.0 / 1.1 parser
|
||||
|
||||
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
A from-scratch XML implementation, loosely modelled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816).
|
||||
|
||||
Features:
|
||||
- Supports enough of the XML 1.0/1.1 spec to handle the 99.9% of XML documents in common current usage.
|
||||
- Simple to understand and use. Small.
|
||||
|
||||
Caveats:
|
||||
- We do NOT support HTML in this package, as that may or may not be valid XML.
|
||||
If it works, great. If it doesn't, that's not considered a bug.
|
||||
|
||||
- We do NOT support UTF-16. If you have a UTF-16 XML file, please convert it to UTF-8 first. Also, our condolences.
|
||||
- <[!ELEMENT and <[!ATTLIST are not supported, and will be either ignored or return an error depending on the parser options.
|
||||
|
||||
MAYBE:
|
||||
- XML writer?
|
||||
- Serialize/deserialize Odin types?
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
*/
|
||||
package xml
|
||||
// An XML 1.0 / 1.1 parser
|
||||
|
||||
import "core:bytes"
|
||||
import "core:encoding/entity"
|
||||
import "core:intrinsics"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
|
||||
likely :: intrinsics.expect
|
||||
|
||||
DEFAULT_OPTIONS :: Options{
|
||||
flags = {.Ignore_Unsupported},
|
||||
expected_doctype = "",
|
||||
}
|
||||
|
||||
Option_Flag :: enum {
|
||||
/*
|
||||
If the caller says that input may be modified, we can perform in-situ parsing.
|
||||
If this flag isn't provided, the XML parser first duplicates the input so that it can.
|
||||
*/
|
||||
Input_May_Be_Modified,
|
||||
|
||||
/*
|
||||
Document MUST start with `<?xml` prologue.
|
||||
*/
|
||||
Must_Have_Prolog,
|
||||
|
||||
/*
|
||||
Document MUST have a `<!DOCTYPE`.
|
||||
*/
|
||||
Must_Have_DocType,
|
||||
|
||||
/*
|
||||
By default we skip comments. Use this option to intern a comment on a parented Element.
|
||||
*/
|
||||
Intern_Comments,
|
||||
|
||||
/*
|
||||
How to handle unsupported parts of the specification, like <! other than <!DOCTYPE and <![CDATA[
|
||||
*/
|
||||
Error_on_Unsupported,
|
||||
Ignore_Unsupported,
|
||||
|
||||
/*
|
||||
By default CDATA tags are passed-through as-is.
|
||||
This option unwraps them when encountered.
|
||||
*/
|
||||
Unbox_CDATA,
|
||||
|
||||
/*
|
||||
By default SGML entities like `>`, ` ` and ` ` are passed-through as-is.
|
||||
This option decodes them when encountered.
|
||||
*/
|
||||
Decode_SGML_Entities,
|
||||
|
||||
/*
|
||||
If a tag body has a comment, it will be stripped unless this option is given.
|
||||
*/
|
||||
Keep_Tag_Body_Comments,
|
||||
}
|
||||
Option_Flags :: bit_set[Option_Flag; u16]
|
||||
|
||||
Document :: struct {
|
||||
elements: [dynamic]Element,
|
||||
element_count: Element_ID,
|
||||
|
||||
prologue: Attributes,
|
||||
encoding: Encoding,
|
||||
|
||||
doctype: struct {
|
||||
/*
|
||||
We only scan the <!DOCTYPE IDENT part and skip the rest.
|
||||
*/
|
||||
ident: string,
|
||||
rest: string,
|
||||
},
|
||||
|
||||
/*
|
||||
If we encounter comments before the root node, and the option to intern comments is given, this is where they'll live.
|
||||
Otherwise they'll be in the element tree.
|
||||
*/
|
||||
comments: [dynamic]string,
|
||||
|
||||
/*
|
||||
Internal
|
||||
*/
|
||||
tokenizer: ^Tokenizer,
|
||||
allocator: mem.Allocator,
|
||||
|
||||
/*
|
||||
Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified.
|
||||
*/
|
||||
input: []u8,
|
||||
strings_to_free: [dynamic]string,
|
||||
}
|
||||
|
||||
Element :: struct {
|
||||
ident: string,
|
||||
value: string,
|
||||
attribs: Attributes,
|
||||
|
||||
kind: enum {
|
||||
Element = 0,
|
||||
Comment,
|
||||
},
|
||||
|
||||
parent: Element_ID,
|
||||
children: [dynamic]Element_ID,
|
||||
}
|
||||
|
||||
Attribute :: struct {
|
||||
key: string,
|
||||
val: string,
|
||||
}
|
||||
|
||||
Attributes :: [dynamic]Attribute
|
||||
|
||||
Options :: struct {
|
||||
flags: Option_Flags,
|
||||
expected_doctype: string,
|
||||
}
|
||||
|
||||
Encoding :: enum {
|
||||
Unknown,
|
||||
|
||||
UTF_8,
|
||||
ISO_8859_1,
|
||||
|
||||
/*
|
||||
Aliases
|
||||
*/
|
||||
LATIN_1 = ISO_8859_1,
|
||||
}
|
||||
|
||||
Error :: enum {
|
||||
/*
|
||||
General return values.
|
||||
*/
|
||||
None = 0,
|
||||
General_Error,
|
||||
Unexpected_Token,
|
||||
Invalid_Token,
|
||||
|
||||
/*
|
||||
Couldn't find, open or read file.
|
||||
*/
|
||||
File_Error,
|
||||
|
||||
/*
|
||||
File too short.
|
||||
*/
|
||||
Premature_EOF,
|
||||
|
||||
/*
|
||||
XML-specific errors.
|
||||
*/
|
||||
No_Prolog,
|
||||
Invalid_Prolog,
|
||||
Too_Many_Prologs,
|
||||
|
||||
No_DocType,
|
||||
Too_Many_DocTypes,
|
||||
DocType_Must_Preceed_Elements,
|
||||
|
||||
/*
|
||||
If a DOCTYPE is present _or_ the caller
|
||||
asked for a specific DOCTYPE and the DOCTYPE
|
||||
and root tag don't match, we return `.Invalid_DocType`.
|
||||
*/
|
||||
Invalid_DocType,
|
||||
|
||||
Invalid_Tag_Value,
|
||||
Mismatched_Closing_Tag,
|
||||
|
||||
Unclosed_Comment,
|
||||
Comment_Before_Root_Element,
|
||||
Invalid_Sequence_In_Comment,
|
||||
|
||||
Unsupported_Version,
|
||||
Unsupported_Encoding,
|
||||
|
||||
/*
|
||||
<!FOO are usually skipped.
|
||||
*/
|
||||
Unhandled_Bang,
|
||||
|
||||
Duplicate_Attribute,
|
||||
Conflicting_Options,
|
||||
}
|
||||
|
||||
/*
|
||||
Implementation starts here.
|
||||
*/
|
||||
parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) {
|
||||
data := data
|
||||
context.allocator = allocator
|
||||
|
||||
opts := validate_options(options) or_return
|
||||
|
||||
/*
|
||||
If `.Input_May_Be_Modified` is not specified, we duplicate the input so that we can modify it in-place.
|
||||
*/
|
||||
if .Input_May_Be_Modified not_in opts.flags {
|
||||
data = bytes.clone(data)
|
||||
}
|
||||
|
||||
t := &Tokenizer{}
|
||||
init(t, string(data), path, error_handler)
|
||||
|
||||
doc = new(Document)
|
||||
doc.allocator = allocator
|
||||
doc.tokenizer = t
|
||||
doc.input = data
|
||||
|
||||
doc.elements = make([dynamic]Element, 1024, 1024, allocator)
|
||||
|
||||
// strings.intern_init(&doc.intern, allocator, allocator)
|
||||
|
||||
err = .Unexpected_Token
|
||||
element, parent: Element_ID
|
||||
|
||||
tag_is_open := false
|
||||
first_element := true
|
||||
open: Token
|
||||
|
||||
/*
|
||||
If a DOCTYPE is present, the root tag has to match.
|
||||
If an expected DOCTYPE is given in options (i.e. it's non-empty), the DOCTYPE (if present) and root tag have to match.
|
||||
*/
|
||||
expected_doctype := options.expected_doctype
|
||||
|
||||
loop: for {
|
||||
skip_whitespace(t)
|
||||
// NOTE(Jeroen): This is faster as a switch.
|
||||
switch t.ch {
|
||||
case '<':
|
||||
/*
|
||||
Consume peeked `<`
|
||||
*/
|
||||
advance_rune(t)
|
||||
|
||||
open = scan(t)
|
||||
// NOTE(Jeroen): We're not using a switch because this if-else chain ordered by likelihood is 2.5% faster at -o:size and -o:speed.
|
||||
if likely(open.kind, Token_Kind.Ident) == .Ident {
|
||||
/*
|
||||
e.g. <odin - Start of new element.
|
||||
*/
|
||||
element = new_element(doc)
|
||||
tag_is_open = true
|
||||
|
||||
if first_element {
|
||||
/*
|
||||
First element.
|
||||
*/
|
||||
parent = element
|
||||
first_element = false
|
||||
} else {
|
||||
append(&doc.elements[parent].children, element)
|
||||
}
|
||||
|
||||
doc.elements[element].parent = parent
|
||||
doc.elements[element].ident = open.text
|
||||
|
||||
parse_attributes(doc, &doc.elements[element].attribs) or_return
|
||||
|
||||
/*
|
||||
If a DOCTYPE is present _or_ the caller
|
||||
asked for a specific DOCTYPE and the DOCTYPE
|
||||
and root tag don't match, we return .Invalid_Root_Tag.
|
||||
*/
|
||||
if element == 0 { // Root tag?
|
||||
if len(expected_doctype) > 0 && expected_doctype != open.text {
|
||||
error(t, t.offset, "Root Tag doesn't match DOCTYPE. Expected: %v, got: %v\n", expected_doctype, open.text)
|
||||
return doc, .Invalid_DocType
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
One of these should follow:
|
||||
- `>`, which means we've just opened this tag and expect a later element to close it.
|
||||
- `/>`, which means this is an 'empty' or self-closing tag.
|
||||
*/
|
||||
end_token := scan(t)
|
||||
#partial switch end_token.kind {
|
||||
case .Gt:
|
||||
/*
|
||||
We're now the new parent.
|
||||
*/
|
||||
parent = element
|
||||
|
||||
case .Slash:
|
||||
/*
|
||||
Empty tag. Close it.
|
||||
*/
|
||||
expect(t, .Gt) or_return
|
||||
parent = doc.elements[element].parent
|
||||
element = parent
|
||||
tag_is_open = false
|
||||
|
||||
case:
|
||||
error(t, t.offset, "Expected close tag, got: %#v\n", end_token)
|
||||
return
|
||||
}
|
||||
|
||||
} else if open.kind == .Slash {
|
||||
/*
|
||||
Close tag.
|
||||
*/
|
||||
ident := expect(t, .Ident) or_return
|
||||
_ = expect(t, .Gt) or_return
|
||||
|
||||
if 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
|
||||
}
|
||||
parent = doc.elements[element].parent
|
||||
element = parent
|
||||
tag_is_open = false
|
||||
|
||||
} else if open.kind == .Exclaim {
|
||||
/*
|
||||
<!
|
||||
*/
|
||||
next := scan(t)
|
||||
#partial switch next.kind {
|
||||
case .Ident:
|
||||
switch next.text {
|
||||
case "DOCTYPE":
|
||||
if len(doc.doctype.ident) > 0 {
|
||||
return doc, .Too_Many_DocTypes
|
||||
}
|
||||
if doc.element_count > 0 {
|
||||
return doc, .DocType_Must_Preceed_Elements
|
||||
}
|
||||
parse_doctype(doc) or_return
|
||||
|
||||
if len(expected_doctype) > 0 && expected_doctype != doc.doctype.ident {
|
||||
error(t, t.offset, "Invalid DOCTYPE. Expected: %v, got: %v\n", expected_doctype, doc.doctype.ident)
|
||||
return doc, .Invalid_DocType
|
||||
}
|
||||
expected_doctype = doc.doctype.ident
|
||||
|
||||
case:
|
||||
if .Error_on_Unsupported in opts.flags {
|
||||
error(t, t.offset, "Unhandled: <!%v\n", next.text)
|
||||
return doc, .Unhandled_Bang
|
||||
}
|
||||
skip_element(t) or_return
|
||||
}
|
||||
|
||||
case .Dash:
|
||||
/*
|
||||
Comment: <!-- -->.
|
||||
The grammar does not allow a comment to end in --->
|
||||
*/
|
||||
expect(t, .Dash)
|
||||
comment := scan_comment(t) or_return
|
||||
|
||||
if .Intern_Comments in opts.flags {
|
||||
if len(doc.elements) == 0 {
|
||||
append(&doc.comments, comment)
|
||||
} else {
|
||||
el := new_element(doc)
|
||||
doc.elements[el].parent = element
|
||||
doc.elements[el].kind = .Comment
|
||||
doc.elements[el].value = comment
|
||||
append(&doc.elements[element].children, el)
|
||||
}
|
||||
}
|
||||
|
||||
case:
|
||||
error(t, t.offset, "Invalid Token after <!. Expected .Ident, got %#v\n", next)
|
||||
return
|
||||
}
|
||||
|
||||
} else if open.kind == .Question {
|
||||
/*
|
||||
<?xml
|
||||
*/
|
||||
next := scan(t)
|
||||
#partial switch next.kind {
|
||||
case .Ident:
|
||||
if len(next.text) == 3 && strings.to_lower(next.text, context.temp_allocator) == "xml" {
|
||||
parse_prologue(doc) or_return
|
||||
} else if len(doc.prologue) > 0 {
|
||||
/*
|
||||
We've already seen a prologue.
|
||||
*/
|
||||
return doc, .Too_Many_Prologs
|
||||
} else {
|
||||
/*
|
||||
Could be `<?xml-stylesheet`, etc. Ignore it.
|
||||
*/
|
||||
skip_element(t) or_return
|
||||
}
|
||||
case:
|
||||
error(t, t.offset, "Expected \"<?xml\", got \"<?%v\".", next.text)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
error(t, t.offset, "Invalid Token after <: %#v\n", open)
|
||||
return
|
||||
}
|
||||
|
||||
case -1:
|
||||
/*
|
||||
End of file.
|
||||
*/
|
||||
if tag_is_open {
|
||||
return doc, .Premature_EOF
|
||||
}
|
||||
break loop
|
||||
|
||||
case:
|
||||
/*
|
||||
This should be a tag's body text.
|
||||
*/
|
||||
body_text := scan_string(t, t.offset) or_return
|
||||
needs_processing := .Unbox_CDATA in opts.flags
|
||||
needs_processing |= .Decode_SGML_Entities in opts.flags
|
||||
|
||||
if !needs_processing {
|
||||
doc.elements[element].value = body_text
|
||||
continue
|
||||
}
|
||||
|
||||
decode_opts := entity.XML_Decode_Options{}
|
||||
if .Keep_Tag_Body_Comments not_in opts.flags {
|
||||
decode_opts += { .Comment_Strip }
|
||||
}
|
||||
|
||||
if .Decode_SGML_Entities not_in opts.flags {
|
||||
decode_opts += { .No_Entity_Decode }
|
||||
}
|
||||
|
||||
if .Unbox_CDATA in opts.flags {
|
||||
decode_opts += { .Unbox_CDATA }
|
||||
if .Decode_SGML_Entities in opts.flags {
|
||||
decode_opts += { .Decode_CDATA }
|
||||
}
|
||||
}
|
||||
|
||||
decoded, decode_err := entity.decode_xml(body_text, decode_opts)
|
||||
if decode_err == .None {
|
||||
doc.elements[element].value = decoded
|
||||
append(&doc.strings_to_free, decoded)
|
||||
} else {
|
||||
doc.elements[element].value = body_text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if .Must_Have_Prolog in opts.flags && len(doc.prologue) == 0 {
|
||||
return doc, .No_Prolog
|
||||
}
|
||||
|
||||
if .Must_Have_DocType in opts.flags && len(doc.doctype.ident) == 0 {
|
||||
return doc, .No_DocType
|
||||
}
|
||||
|
||||
resize(&doc.elements, int(doc.element_count))
|
||||
return doc, .None
|
||||
}
|
||||
|
||||
parse_string :: proc(data: string, options := DEFAULT_OPTIONS, path := "", error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) {
|
||||
_data := transmute([]u8)data
|
||||
|
||||
return parse_bytes(_data, options, path, error_handler, allocator)
|
||||
}
|
||||
|
||||
parse :: proc { parse_string, parse_bytes }
|
||||
|
||||
// Load an XML file
|
||||
load_from_file :: proc(filename: string, options := DEFAULT_OPTIONS, error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) {
|
||||
context.allocator = allocator
|
||||
options := options
|
||||
|
||||
data, data_ok := os.read_entire_file(filename)
|
||||
if !data_ok { return {}, .File_Error }
|
||||
|
||||
options.flags += { .Input_May_Be_Modified }
|
||||
|
||||
return parse_bytes(data, options, filename, error_handler, allocator)
|
||||
}
|
||||
|
||||
destroy :: proc(doc: ^Document) {
|
||||
if doc == nil { return }
|
||||
|
||||
for el in doc.elements {
|
||||
delete(el.attribs)
|
||||
delete(el.children)
|
||||
}
|
||||
delete(doc.elements)
|
||||
|
||||
delete(doc.prologue)
|
||||
delete(doc.comments)
|
||||
delete(doc.input)
|
||||
|
||||
for s in doc.strings_to_free {
|
||||
delete(s)
|
||||
}
|
||||
delete(doc.strings_to_free)
|
||||
|
||||
free(doc)
|
||||
}
|
||||
|
||||
/*
|
||||
Helpers.
|
||||
*/
|
||||
|
||||
validate_options :: proc(options: Options) -> (validated: Options, err: Error) {
|
||||
validated = options
|
||||
|
||||
if .Error_on_Unsupported in validated.flags && .Ignore_Unsupported in validated.flags {
|
||||
return options, .Conflicting_Options
|
||||
}
|
||||
return validated, .None
|
||||
}
|
||||
|
||||
expect :: proc(t: ^Tokenizer, kind: Token_Kind) -> (tok: Token, err: Error) {
|
||||
tok = scan(t)
|
||||
if tok.kind == kind { return tok, .None }
|
||||
|
||||
error(t, t.offset, "Expected \"%v\", got \"%v\".", kind, tok.kind)
|
||||
return tok, .Unexpected_Token
|
||||
}
|
||||
|
||||
parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: Error) {
|
||||
assert(doc != nil)
|
||||
context.allocator = doc.allocator
|
||||
t := doc.tokenizer
|
||||
|
||||
key := expect(t, .Ident) or_return
|
||||
offset = t.offset - len(key.text)
|
||||
|
||||
_ = expect(t, .Eq) or_return
|
||||
value := expect(t, .String) or_return
|
||||
|
||||
attr.key = key.text
|
||||
attr.val = value.text
|
||||
|
||||
err = .None
|
||||
return
|
||||
}
|
||||
|
||||
check_duplicate_attributes :: proc(t: ^Tokenizer, attribs: Attributes, attr: Attribute, offset: int) -> (err: Error) {
|
||||
for a in attribs {
|
||||
if attr.key == a.key {
|
||||
error(t, offset, "Duplicate attribute: %v\n", attr.key)
|
||||
return .Duplicate_Attribute
|
||||
}
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
parse_attributes :: proc(doc: ^Document, attribs: ^Attributes) -> (err: Error) {
|
||||
assert(doc != nil)
|
||||
context.allocator = doc.allocator
|
||||
t := doc.tokenizer
|
||||
|
||||
for peek(t).kind == .Ident {
|
||||
attr, offset := parse_attribute(doc) or_return
|
||||
check_duplicate_attributes(t, attribs^, attr, offset) or_return
|
||||
append(attribs, attr)
|
||||
}
|
||||
skip_whitespace(t)
|
||||
return .None
|
||||
}
|
||||
|
||||
parse_prologue :: proc(doc: ^Document) -> (err: Error) {
|
||||
assert(doc != nil)
|
||||
context.allocator = doc.allocator
|
||||
t := doc.tokenizer
|
||||
|
||||
offset := t.offset
|
||||
parse_attributes(doc, &doc.prologue) or_return
|
||||
|
||||
for attr in doc.prologue {
|
||||
switch attr.key {
|
||||
case "version":
|
||||
switch attr.val {
|
||||
case "1.0", "1.1":
|
||||
case:
|
||||
error(t, offset, "[parse_prologue] Warning: Unhandled XML version: %v\n", attr.val)
|
||||
}
|
||||
|
||||
case "encoding":
|
||||
switch strings.to_lower(attr.val, context.temp_allocator) {
|
||||
case "utf-8", "utf8":
|
||||
doc.encoding = .UTF_8
|
||||
|
||||
case "latin-1", "latin1", "iso-8859-1":
|
||||
doc.encoding = .LATIN_1
|
||||
|
||||
case:
|
||||
/*
|
||||
Unrecognized encoding, assume UTF-8.
|
||||
*/
|
||||
error(t, offset, "[parse_prologue] Warning: Unrecognized encoding: %v\n", attr.val)
|
||||
}
|
||||
|
||||
case:
|
||||
// Ignored.
|
||||
}
|
||||
}
|
||||
|
||||
_ = expect(t, .Question) or_return
|
||||
_ = expect(t, .Gt) or_return
|
||||
|
||||
return .None
|
||||
}
|
||||
|
||||
skip_element :: proc(t: ^Tokenizer) -> (err: Error) {
|
||||
close := 1
|
||||
|
||||
loop: for {
|
||||
tok := scan(t)
|
||||
#partial switch tok.kind {
|
||||
case .EOF:
|
||||
error(t, t.offset, "[skip_element] Premature EOF\n")
|
||||
return .Premature_EOF
|
||||
|
||||
case .Lt:
|
||||
close += 1
|
||||
|
||||
case .Gt:
|
||||
close -= 1
|
||||
if close == 0 {
|
||||
break loop
|
||||
}
|
||||
|
||||
case:
|
||||
|
||||
}
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
parse_doctype :: proc(doc: ^Document) -> (err: Error) {
|
||||
/*
|
||||
<!DOCTYPE greeting SYSTEM "hello.dtd">
|
||||
|
||||
<!DOCTYPE greeting [
|
||||
<!ELEMENT greeting (#PCDATA)>
|
||||
]>
|
||||
*/
|
||||
assert(doc != nil)
|
||||
context.allocator = doc.allocator
|
||||
t := doc.tokenizer
|
||||
|
||||
tok := expect(t, .Ident) or_return
|
||||
doc.doctype.ident = tok.text
|
||||
|
||||
skip_whitespace(t)
|
||||
offset := t.offset
|
||||
skip_element(t) or_return
|
||||
|
||||
/*
|
||||
-1 because the current offset is that of the closing tag, so the rest of the DOCTYPE tag ends just before it.
|
||||
*/
|
||||
doc.doctype.rest = string(t.src[offset : t.offset - 1])
|
||||
return .None
|
||||
}
|
||||
|
||||
Element_ID :: u32
|
||||
|
||||
new_element :: proc(doc: ^Document) -> (id: Element_ID) {
|
||||
element_space := len(doc.elements)
|
||||
|
||||
// Need to resize
|
||||
if int(doc.element_count) + 1 > element_space {
|
||||
if element_space < 65536 {
|
||||
element_space *= 2
|
||||
} else {
|
||||
element_space += 65536
|
||||
}
|
||||
resize(&doc.elements, element_space)
|
||||
}
|
||||
|
||||
cur := doc.element_count
|
||||
doc.element_count += 1
|
||||
|
||||
return cur
|
||||
}
|
||||
+673
-498
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,8 +1,8 @@
|
||||
package hash
|
||||
|
||||
@(optimization_mode="speed")
|
||||
crc64_ecma_182 :: proc(data: []byte, seed := u32(0)) -> u64 #no_bounds_check {
|
||||
result := u64(seed)
|
||||
crc64_ecma_182 :: proc(data: []byte, seed := u64(0)) -> (result: u64) #no_bounds_check {
|
||||
result = seed
|
||||
#no_bounds_check for b in data {
|
||||
result = result<<8 ~ _crc64_table_ecma_182[((result>>56) ~ u64(b)) & 0xff]
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ crc32 :: proc(data: []byte, seed := u32(0)) -> u32 #no_bounds_check {
|
||||
length := len(data)
|
||||
|
||||
for length != 0 && uintptr(buffer) & 7 != 0 {
|
||||
crc = crc32_table[0][byte(crc) ~ buffer^] ~ (crc >> 8)
|
||||
buffer = intrinsics.ptr_offset(buffer, 1)
|
||||
crc = crc32_table[0][byte(crc) ~ buffer[0]] ~ (crc >> 8)
|
||||
buffer = buffer[1:]
|
||||
length -= 1
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ crc32 :: proc(data: []byte, seed := u32(0)) -> u32 #no_bounds_check {
|
||||
crc32_table[1][buf[6]] ~
|
||||
crc32_table[0][buf[7]]
|
||||
|
||||
buffer = intrinsics.ptr_offset(buffer, 8)
|
||||
buffer = buffer[8:]
|
||||
length -= 8
|
||||
}
|
||||
|
||||
|
||||
for length != 0 {
|
||||
crc = crc32_table[0][byte(crc) ~ buffer^] ~ (crc >> 8)
|
||||
buffer = intrinsics.ptr_offset(buffer, 1)
|
||||
crc = crc32_table[0][byte(crc) ~ buffer[0]] ~ (crc >> 8)
|
||||
buffer = buffer[1:]
|
||||
length -= 1
|
||||
}
|
||||
|
||||
|
||||
+105
-99
@@ -15,7 +15,7 @@ adler32 :: proc(data: []byte, seed := u32(1)) -> u32 #no_bounds_check {
|
||||
for len(buf) != 0 && uintptr(buffer) & 7 != 0 {
|
||||
a = (a + u64(buf[0]))
|
||||
b = (b + a)
|
||||
buffer = intrinsics.ptr_offset(buffer, 1)
|
||||
buffer = buffer[1:]
|
||||
buf = buf[1:]
|
||||
}
|
||||
|
||||
@@ -130,9 +130,9 @@ murmur32 :: proc(data: []byte, seed := u32(0)) -> u32 {
|
||||
h1: u32 = seed
|
||||
nblocks := len(data)/4
|
||||
p := raw_data(data)
|
||||
p1 := mem.ptr_offset(p, 4*nblocks)
|
||||
p1 := p[4*nblocks:]
|
||||
|
||||
for ; p < p1; p = mem.ptr_offset(p, 4) {
|
||||
for ; p < p1; p = p[4:] {
|
||||
k1 := (cast(^u32)p)^
|
||||
|
||||
k1 *= c1_32
|
||||
@@ -151,7 +151,7 @@ murmur32 :: proc(data: []byte, seed := u32(0)) -> u32 {
|
||||
k1 ~= u32(tail[2]) << 16
|
||||
fallthrough
|
||||
case 2:
|
||||
k1 ~= u32(tail[2]) << 8
|
||||
k1 ~= u32(tail[1]) << 8
|
||||
fallthrough
|
||||
case 1:
|
||||
k1 ~= u32(tail[0])
|
||||
@@ -172,108 +172,114 @@ murmur32 :: proc(data: []byte, seed := u32(0)) -> u32 {
|
||||
return h1
|
||||
}
|
||||
|
||||
// See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L96
|
||||
@(optimization_mode="speed")
|
||||
murmur64 :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
||||
when size_of(int) == 8 {
|
||||
m :: 0xc6a4a7935bd1e995
|
||||
r :: 47
|
||||
murmur64a :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
||||
m :: 0xc6a4a7935bd1e995
|
||||
r :: 47
|
||||
|
||||
h: u64 = seed ~ (u64(len(data)) * m)
|
||||
data64 := mem.slice_ptr(cast(^u64)raw_data(data), len(data)/size_of(u64))
|
||||
h: u64 = seed ~ (u64(len(data)) * m)
|
||||
data64 := mem.slice_data_cast([]u64, data)
|
||||
|
||||
for _, i in data64 {
|
||||
k := data64[i]
|
||||
for _, i in data64 {
|
||||
k := data64[i]
|
||||
|
||||
k *= m
|
||||
k ~= k>>r
|
||||
k *= m
|
||||
k *= m
|
||||
k ~= k>>r
|
||||
k *= m
|
||||
|
||||
h ~= k
|
||||
h *= m
|
||||
}
|
||||
|
||||
switch len(data)&7 {
|
||||
case 7: h ~= u64(data[6]) << 48; fallthrough
|
||||
case 6: h ~= u64(data[5]) << 40; fallthrough
|
||||
case 5: h ~= u64(data[4]) << 32; fallthrough
|
||||
case 4: h ~= u64(data[3]) << 24; fallthrough
|
||||
case 3: h ~= u64(data[2]) << 16; fallthrough
|
||||
case 2: h ~= u64(data[1]) << 8; fallthrough
|
||||
case 1:
|
||||
h ~= u64(data[0])
|
||||
h *= m
|
||||
}
|
||||
|
||||
h ~= h>>r
|
||||
h ~= k
|
||||
h *= m
|
||||
h ~= h>>r
|
||||
|
||||
return h
|
||||
} else {
|
||||
m :: 0x5bd1e995
|
||||
r :: 24
|
||||
|
||||
h1 := u32(seed) ~ u32(len(data))
|
||||
h2 := u32(seed) >> 32
|
||||
data32 := mem.slice_ptr(cast(^u32)raw_data(data), len(data)/size_of(u32))
|
||||
len := len(data)
|
||||
i := 0
|
||||
|
||||
for len >= 8 {
|
||||
k1, k2: u32
|
||||
k1 = data32[i]; i += 1
|
||||
k1 *= m
|
||||
k1 ~= k1>>r
|
||||
k1 *= m
|
||||
h1 *= m
|
||||
h1 ~= k1
|
||||
len -= 4
|
||||
|
||||
k2 = data32[i]; i += 1
|
||||
k2 *= m
|
||||
k2 ~= k2>>r
|
||||
k2 *= m
|
||||
h2 *= m
|
||||
h2 ~= k2
|
||||
len -= 4
|
||||
}
|
||||
|
||||
if len >= 4 {
|
||||
k1: u32
|
||||
k1 = data32[i]; i += 1
|
||||
k1 *= m
|
||||
k1 ~= k1>>r
|
||||
k1 *= m
|
||||
h1 *= m
|
||||
h1 ~= k1
|
||||
len -= 4
|
||||
}
|
||||
|
||||
// TODO(bill): Fix this
|
||||
#no_bounds_check data8 := mem.slice_to_bytes(data32[i:])[:3]
|
||||
switch len {
|
||||
case 3:
|
||||
h2 ~= u32(data8[2]) << 16
|
||||
fallthrough
|
||||
case 2:
|
||||
h2 ~= u32(data8[1]) << 8
|
||||
fallthrough
|
||||
case 1:
|
||||
h2 ~= u32(data8[0])
|
||||
h2 *= m
|
||||
}
|
||||
|
||||
h1 ~= h2>>18
|
||||
h1 *= m
|
||||
h2 ~= h1>>22
|
||||
h2 *= m
|
||||
h1 ~= h2>>17
|
||||
h1 *= m
|
||||
h2 ~= h1>>19
|
||||
h2 *= m
|
||||
|
||||
return u64(h1)<<32 | u64(h2)
|
||||
}
|
||||
|
||||
offset := len(data64) * size_of(u64)
|
||||
|
||||
switch len(data)&7 {
|
||||
case 7: h ~= u64(data[offset + 6]) << 48; fallthrough
|
||||
case 6: h ~= u64(data[offset + 5]) << 40; fallthrough
|
||||
case 5: h ~= u64(data[offset + 4]) << 32; fallthrough
|
||||
case 4: h ~= u64(data[offset + 3]) << 24; fallthrough
|
||||
case 3: h ~= u64(data[offset + 2]) << 16; fallthrough
|
||||
case 2: h ~= u64(data[offset + 1]) << 8; fallthrough
|
||||
case 1:
|
||||
h ~= u64(data[offset + 0])
|
||||
h *= m
|
||||
}
|
||||
|
||||
h ~= h>>r
|
||||
h *= m
|
||||
h ~= h>>r
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L140
|
||||
@(optimization_mode="speed")
|
||||
murmur64b :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
||||
m :: 0x5bd1e995
|
||||
r :: 24
|
||||
|
||||
h1 := u32(seed) ~ u32(len(data))
|
||||
h2 := u32(seed) >> 32
|
||||
|
||||
data32 := mem.slice_ptr(cast(^u32)raw_data(data), len(data)/size_of(u32))
|
||||
len := len(data)
|
||||
i := 0
|
||||
|
||||
for len >= 8 {
|
||||
k1, k2: u32
|
||||
k1 = data32[i]; i += 1
|
||||
k1 *= m
|
||||
k1 ~= k1>>r
|
||||
k1 *= m
|
||||
h1 *= m
|
||||
h1 ~= k1
|
||||
len -= 4
|
||||
|
||||
k2 = data32[i]; i += 1
|
||||
k2 *= m
|
||||
k2 ~= k2>>r
|
||||
k2 *= m
|
||||
h2 *= m
|
||||
h2 ~= k2
|
||||
len -= 4
|
||||
}
|
||||
|
||||
if len >= 4 {
|
||||
k1: u32
|
||||
k1 = data32[i]; i += 1
|
||||
k1 *= m
|
||||
k1 ~= k1>>r
|
||||
k1 *= m
|
||||
h1 *= m
|
||||
h1 ~= k1
|
||||
len -= 4
|
||||
}
|
||||
|
||||
// TODO(bill): Fix this
|
||||
#no_bounds_check data8 := mem.slice_to_bytes(data32[i:])[:3]
|
||||
switch len {
|
||||
case 3:
|
||||
h2 ~= u32(data8[2]) << 16
|
||||
fallthrough
|
||||
case 2:
|
||||
h2 ~= u32(data8[1]) << 8
|
||||
fallthrough
|
||||
case 1:
|
||||
h2 ~= u32(data8[0])
|
||||
h2 *= m
|
||||
}
|
||||
|
||||
h1 ~= h2>>18
|
||||
h1 *= m
|
||||
h2 ~= h1>>22
|
||||
h2 *= m
|
||||
h1 ~= h2>>17
|
||||
h1 *= m
|
||||
h2 ~= h1>>19
|
||||
h2 *= m
|
||||
|
||||
return u64(h1)<<32 | u64(h2)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
|
||||
@@ -52,9 +52,6 @@ XXH3_128_reset_with_seed :: proc(state: ^XXH3_state, seed: XXH64_hash) -> (err:
|
||||
XXH3_64_reset_with_seed :: XXH3_128_reset_with_seed
|
||||
|
||||
XXH3_128_update :: proc(state: ^XXH3_state, input: []u8) -> (err: Error) {
|
||||
if len(input) < XXH3_MIDSIZE_MAX {
|
||||
return .Error
|
||||
}
|
||||
return XXH3_update(state, input, XXH3_accumulate_512, XXH3_scramble_accumulator)
|
||||
}
|
||||
XXH3_64_update :: XXH3_128_update
|
||||
@@ -127,6 +124,7 @@ XXH3_create_state :: proc(allocator := context.allocator) -> (res: ^XXH3_state,
|
||||
err = nil if mem_error == nil else .Error
|
||||
|
||||
XXH3_init_state(state)
|
||||
XXH3_128_reset(state)
|
||||
return state, nil
|
||||
}
|
||||
|
||||
@@ -213,7 +211,9 @@ XXH3_update :: #force_inline proc(
|
||||
length := len(input)
|
||||
secret := state.custom_secret[:] if len(state.external_secret) == 0 else state.external_secret[:]
|
||||
|
||||
assert(len(input) > 0)
|
||||
if len(input) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
state.total_length += u64(length)
|
||||
assert(state.buffered_size <= XXH3_INTERNAL_BUFFER_SIZE)
|
||||
@@ -234,7 +234,9 @@ XXH3_update :: #force_inline proc(
|
||||
*/
|
||||
if state.buffered_size > 0 {
|
||||
load_size := int(XXH3_INTERNAL_BUFFER_SIZE - state.buffered_size)
|
||||
mem_copy(&state.buffer[state.buffered_size], &input[0], load_size)
|
||||
|
||||
state_ptr := rawptr(uintptr(raw_data(state.buffer[:])) + uintptr(state.buffered_size))
|
||||
mem_copy(state_ptr, raw_data(input), load_size)
|
||||
input = input[load_size:]
|
||||
|
||||
XXH3_consume_stripes(
|
||||
|
||||
@@ -197,6 +197,7 @@ XXH32 :: proc(input: []u8, seed := XXH32_DEFAULT_SEED) -> (digest: XXH32_hash) {
|
||||
*/
|
||||
XXH32_create_state :: proc(allocator := context.allocator) -> (res: ^XXH32_state, err: Error) {
|
||||
state := new(XXH32_state, allocator)
|
||||
XXH32_reset_state(state)
|
||||
return state, .None if state != nil else .Error
|
||||
}
|
||||
|
||||
@@ -258,7 +259,7 @@ XXH32_update :: proc(state: ^XXH32_state, input: []u8) -> (err: Error) {
|
||||
v3 := state.v3
|
||||
v4 := state.v4
|
||||
|
||||
for len(buf) >= 15 {
|
||||
for len(buf) >= 16 {
|
||||
#no_bounds_check v1 = XXH32_round(v1, XXH32_read32(buf, .Unaligned)); buf = buf[4:]
|
||||
#no_bounds_check v2 = XXH32_round(v2, XXH32_read32(buf, .Unaligned)); buf = buf[4:]
|
||||
#no_bounds_check v3 = XXH32_round(v3, XXH32_read32(buf, .Unaligned)); buf = buf[4:]
|
||||
|
||||
@@ -163,6 +163,7 @@ XXH64 :: proc(input: []u8, seed := XXH64_DEFAULT_SEED) -> (digest: XXH64_hash) {
|
||||
*/
|
||||
XXH64_create_state :: proc(allocator := context.allocator) -> (res: ^XXH64_state, err: Error) {
|
||||
state := new(XXH64_state, allocator)
|
||||
XXH64_reset_state(state)
|
||||
return state, .None if state != nil else .Error
|
||||
}
|
||||
|
||||
|
||||
+946
-25
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
package image
|
||||
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:bytes"
|
||||
|
||||
Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error)
|
||||
Destroy_Proc :: #type proc(img: ^Image)
|
||||
|
||||
@(private)
|
||||
_internal_loaders: [Which_File_Type]Loader_Proc
|
||||
_internal_destroyers: [Which_File_Type]Destroy_Proc
|
||||
|
||||
register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) {
|
||||
assert(loader != nil)
|
||||
assert(destroyer != nil)
|
||||
assert(_internal_loaders[kind] == nil)
|
||||
_internal_loaders[kind] = loader
|
||||
|
||||
assert(_internal_destroyers[kind] == nil)
|
||||
_internal_destroyers[kind] = destroyer
|
||||
}
|
||||
|
||||
load :: proc{
|
||||
load_from_bytes,
|
||||
load_from_file,
|
||||
}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
loader := _internal_loaders[which(data)]
|
||||
if loader == nil {
|
||||
return nil, .Unsupported_Format
|
||||
}
|
||||
return loader(data, options, allocator)
|
||||
}
|
||||
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
data, ok := os.read_entire_file(filename, allocator)
|
||||
defer delete(data, allocator)
|
||||
if ok {
|
||||
return load_from_bytes(data, options, allocator)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
destroy :: proc(img: ^Image, allocator := context.allocator) {
|
||||
if img == nil {
|
||||
return
|
||||
}
|
||||
context.allocator = allocator
|
||||
destroyer := _internal_destroyers[img.which]
|
||||
if destroyer != nil {
|
||||
destroyer(img)
|
||||
} else {
|
||||
assert(img.metadata == nil)
|
||||
bytes.buffer_destroy(&img.pixels)
|
||||
free(img)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Formats:
|
||||
PBM (P1, P4): Portable Bit Map, stores black and white images (1 channel)
|
||||
PGM (P2, P5): Portable Gray Map, stores greyscale images (1 channel, 1 or 2 bytes per value)
|
||||
PPM (P3, P6): Portable Pixel Map, stores colour images (3 channel, 1 or 2 bytes per value)
|
||||
PAM (P7 ): Portable Arbitrary Map, stores arbitrary channel images (1 or 2 bytes per value)
|
||||
PFM (Pf, PF): Portable Float Map, stores floating-point images (Pf: 1 channel, PF: 3 channel)
|
||||
|
||||
Reading:
|
||||
All formats fill out header fields `format`, `width`, `height`, `channels`, `depth`
|
||||
Specific formats use more fields
|
||||
PGM, PPM, and PAM set `maxval` (maximum of 65535)
|
||||
PAM sets `tupltype` if there is one, and can set `channels` to any value (not just 1 or 3)
|
||||
PFM sets `scale` (float equivalent of `maxval`) and `little_endian` (endianness of stored floats)
|
||||
Currently doesn't support reading multiple images from one binary-format file
|
||||
|
||||
Writing:
|
||||
You can use your own `Netpbm_Info` struct to control how images are written
|
||||
All formats require the header field `format` to be specified
|
||||
Additional header fields are required for specific formats
|
||||
PGM, PPM, and PAM require `maxval` (maximum of 65535)
|
||||
PAM also uses `tupltype`, though it may be left as default (empty or nil string)
|
||||
PFM requires `scale`, and optionally `little_endian`
|
||||
|
||||
Some syntax differences from the specifications:
|
||||
`channels` stores the number of values per pixel, what the PAM specification calls `depth`
|
||||
`depth` instead is the number of bits for a single value (32 for PFM, 16 or 8 otherwise)
|
||||
`scale` and `little_endian` are separated, so the `header` will always store a positive `scale`
|
||||
`little_endian` will only be true for a negative `scale` PFM, every other format will be false
|
||||
`little_endian` only describes the netpbm data being read/written, the image buffer will be native
|
||||
*/
|
||||
|
||||
package netpbm
|
||||
@@ -0,0 +1,27 @@
|
||||
package netpbm
|
||||
|
||||
import "core:bytes"
|
||||
import "core:image"
|
||||
|
||||
destroy :: proc(img: ^image.Image) -> bool {
|
||||
if img == nil do return false
|
||||
|
||||
defer free(img)
|
||||
bytes.buffer_destroy(&img.pixels)
|
||||
|
||||
info, ok := img.metadata.(^image.Netpbm_Info)
|
||||
if !ok do return false
|
||||
|
||||
header_destroy(&info.header)
|
||||
free(info)
|
||||
img.metadata = nil
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
header_destroy :: proc(using header: ^Header) {
|
||||
if format == .P7 && tupltype != "" {
|
||||
delete(tupltype)
|
||||
tupltype = ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,763 @@
|
||||
package netpbm
|
||||
|
||||
import "core:bytes"
|
||||
import "core:fmt"
|
||||
import "core:image"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
import "core:unicode"
|
||||
|
||||
Image :: image.Image
|
||||
Format :: image.Netpbm_Format
|
||||
Header :: image.Netpbm_Header
|
||||
Info :: image.Netpbm_Info
|
||||
Error :: image.Error
|
||||
Format_Error :: image.Netpbm_Error
|
||||
|
||||
Formats :: bit_set[Format]
|
||||
PBM :: Formats{.P1, .P4}
|
||||
PGM :: Formats{.P2, .P5}
|
||||
PPM :: Formats{.P3, .P6}
|
||||
PNM :: PBM + PGM + PPM
|
||||
PAM :: Formats{.P7}
|
||||
PFM :: Formats{.Pf, .PF}
|
||||
ASCII :: Formats{.P1, .P2, .P3}
|
||||
BINARY :: Formats{.P4, .P5, .P6} + PAM + PFM
|
||||
|
||||
load :: proc {
|
||||
load_from_file,
|
||||
load_from_bytes,
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename); defer delete(data)
|
||||
if !ok {
|
||||
err = .Unable_To_Read_File
|
||||
return
|
||||
}
|
||||
|
||||
return load_from_bytes(data)
|
||||
}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
img = new(Image)
|
||||
img.which = .NetPBM
|
||||
|
||||
header: Header; defer header_destroy(&header)
|
||||
header_size: int
|
||||
header, header_size = parse_header(data) or_return
|
||||
|
||||
img_data := data[header_size:]
|
||||
decode_image(img, header, img_data) or_return
|
||||
|
||||
info := new(Info)
|
||||
info.header = header
|
||||
if header.format == .P7 && header.tupltype != "" {
|
||||
info.header.tupltype = strings.clone(header.tupltype)
|
||||
}
|
||||
img.metadata = info
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
save :: proc {
|
||||
save_to_file,
|
||||
save_to_buffer,
|
||||
}
|
||||
|
||||
save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data: []byte; defer delete(data)
|
||||
data = save_to_buffer(img, custom_info) or_return
|
||||
|
||||
if ok := os.write_entire_file(filename, data); !ok {
|
||||
return .Unable_To_Write_File
|
||||
}
|
||||
|
||||
return Format_Error.None
|
||||
}
|
||||
|
||||
save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (buffer: []byte, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
info: Info = {}
|
||||
if custom_info.header.width > 0 {
|
||||
// Custom info has been set, use it.
|
||||
info = custom_info
|
||||
} else {
|
||||
img_info, ok := img.metadata.(^image.Netpbm_Info)
|
||||
if !ok {
|
||||
// image doesn't have .Netpbm info, guess it
|
||||
auto_info, auto_info_found := autoselect_pbm_format_from_image(img)
|
||||
if auto_info_found {
|
||||
info = auto_info
|
||||
} else {
|
||||
return {}, .Invalid_Input_Image
|
||||
}
|
||||
} else {
|
||||
// use info as stored on image
|
||||
info = img_info^
|
||||
}
|
||||
}
|
||||
|
||||
// using info so we can just talk about the header
|
||||
using info
|
||||
|
||||
// validation
|
||||
if header.format in (PBM + PGM + Formats{.Pf}) && img.channels != 1 \
|
||||
|| header.format in (PPM + Formats{.PF}) && img.channels != 3 {
|
||||
err = .Invalid_Number_Of_Channels
|
||||
return
|
||||
}
|
||||
|
||||
if header.format in (PNM + PAM) {
|
||||
if header.maxval <= int(max(u8)) && img.depth != 8 \
|
||||
|| header.maxval > int(max(u8)) && header.maxval <= int(max(u16)) && img.depth != 16 {
|
||||
err = .Invalid_Image_Depth
|
||||
return
|
||||
}
|
||||
} else if header.format in PFM && img.depth != 32 {
|
||||
err = .Invalid_Image_Depth
|
||||
return
|
||||
}
|
||||
|
||||
// we will write to a string builder
|
||||
data: strings.Builder
|
||||
strings.builder_init(&data)
|
||||
|
||||
// all PNM headers start with the format
|
||||
fmt.sbprintf(&data, "%s\n", header.format)
|
||||
if header.format in PNM {
|
||||
fmt.sbprintf(&data, "%i %i\n", img.width, img.height)
|
||||
if header.format not_in PBM {
|
||||
fmt.sbprintf(&data, "%i\n", header.maxval)
|
||||
}
|
||||
} else if header.format in PAM {
|
||||
if len(header.tupltype) > 0 {
|
||||
fmt.sbprintf(&data, "WIDTH %i\nHEIGHT %i\nMAXVAL %i\nDEPTH %i\nTUPLTYPE %s\nENDHDR\n",
|
||||
img.width, img.height, header.maxval, img.channels, header.tupltype)
|
||||
} else {
|
||||
fmt.sbprintf(&data, "WIDTH %i\nHEIGHT %i\nMAXVAL %i\nDEPTH %i\nENDHDR\n",
|
||||
img.width, img.height, header.maxval, img.channels)
|
||||
}
|
||||
|
||||
} else if header.format in PFM {
|
||||
scale := -header.scale if header.little_endian else header.scale
|
||||
fmt.sbprintf(&data, "%i %i\n%f\n", img.width, img.height, scale)
|
||||
}
|
||||
|
||||
switch header.format {
|
||||
// Compressed binary
|
||||
case .P4:
|
||||
header_buf := data.buf[:]
|
||||
pixels := img.pixels.buf[:]
|
||||
|
||||
p4_buffer_size := (img.width / 8 + 1) * img.height
|
||||
reserve(&data.buf, len(header_buf) + p4_buffer_size)
|
||||
|
||||
// we build up a byte value until it is completely filled
|
||||
// or we reach the end the row
|
||||
for y in 0 ..< img.height {
|
||||
b: byte
|
||||
|
||||
for x in 0 ..< img.width {
|
||||
i := y * img.width + x
|
||||
bit := byte(7 - (x % 8))
|
||||
v : byte = 0 if pixels[i] == 0 else 1
|
||||
b |= (v << bit)
|
||||
|
||||
if bit == 0 {
|
||||
append(&data.buf, b)
|
||||
b = 0
|
||||
}
|
||||
}
|
||||
|
||||
if b != 0 {
|
||||
append(&data.buf, b)
|
||||
b = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Simple binary
|
||||
case .P5, .P6, .P7, .Pf, .PF:
|
||||
header_buf := data.buf[:]
|
||||
pixels := img.pixels.buf[:]
|
||||
|
||||
resize(&data.buf, len(header_buf) + len(pixels))
|
||||
mem.copy(raw_data(data.buf[len(header_buf):]), raw_data(pixels), len(pixels))
|
||||
|
||||
// convert from native endianness
|
||||
if img.depth == 16 {
|
||||
pixels := mem.slice_data_cast([]u16be, data.buf[len(header_buf):])
|
||||
for p in &pixels {
|
||||
p = u16be(transmute(u16) p)
|
||||
}
|
||||
} else if header.format in PFM {
|
||||
if header.little_endian {
|
||||
pixels := mem.slice_data_cast([]f32le, data.buf[len(header_buf):])
|
||||
for p in &pixels {
|
||||
p = f32le(transmute(f32) p)
|
||||
}
|
||||
} else {
|
||||
pixels := mem.slice_data_cast([]f32be, data.buf[len(header_buf):])
|
||||
for p in &pixels {
|
||||
p = f32be(transmute(f32) p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If-it-looks-like-a-bitmap ASCII
|
||||
case .P1:
|
||||
pixels := img.pixels.buf[:]
|
||||
for y in 0 ..< img.height {
|
||||
for x in 0 ..< img.width {
|
||||
i := y * img.width + x
|
||||
append(&data.buf, '0' if pixels[i] == 0 else '1')
|
||||
}
|
||||
append(&data.buf, '\n')
|
||||
}
|
||||
|
||||
// Token ASCII
|
||||
case .P2, .P3:
|
||||
switch img.depth {
|
||||
case 8:
|
||||
pixels := img.pixels.buf[:]
|
||||
for y in 0 ..< img.height {
|
||||
for x in 0 ..< img.width {
|
||||
i := y * img.width + x
|
||||
for c in 0 ..< img.channels {
|
||||
i := i * img.channels + c
|
||||
fmt.sbprintf(&data, "%i ", pixels[i])
|
||||
}
|
||||
fmt.sbprint(&data, "\n")
|
||||
}
|
||||
fmt.sbprint(&data, "\n")
|
||||
}
|
||||
|
||||
case 16:
|
||||
pixels := mem.slice_data_cast([]u16, img.pixels.buf[:])
|
||||
for y in 0 ..< img.height {
|
||||
for x in 0 ..< img.width {
|
||||
i := y * img.width + x
|
||||
for c in 0 ..< img.channels {
|
||||
i := i * img.channels + c
|
||||
fmt.sbprintf(&data, "%i ", pixels[i])
|
||||
}
|
||||
fmt.sbprint(&data, "\n")
|
||||
}
|
||||
fmt.sbprint(&data, "\n")
|
||||
}
|
||||
|
||||
case:
|
||||
return data.buf[:], .Invalid_Image_Depth
|
||||
}
|
||||
|
||||
case:
|
||||
return data.buf[:], .Invalid_Format
|
||||
}
|
||||
|
||||
return data.buf[:], Format_Error.None
|
||||
}
|
||||
|
||||
parse_header :: proc(data: []byte, allocator := context.allocator) -> (header: Header, length: int, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
// we need the signature and a space
|
||||
if len(data) < 3 {
|
||||
err = Format_Error.Incomplete_Header
|
||||
return
|
||||
}
|
||||
|
||||
if data[0] == 'P' {
|
||||
switch data[1] {
|
||||
case '1' ..= '6':
|
||||
return _parse_header_pnm(data)
|
||||
case '7':
|
||||
return _parse_header_pam(data, allocator)
|
||||
case 'F', 'f':
|
||||
return _parse_header_pfm(data)
|
||||
}
|
||||
}
|
||||
|
||||
err = .Invalid_Signature
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_parse_header_pnm :: proc(data: []byte) -> (header: Header, length: int, err: Error) {
|
||||
SIG_LENGTH :: 2
|
||||
|
||||
{
|
||||
header_formats := []Format{.P1, .P2, .P3, .P4, .P5, .P6}
|
||||
header.format = header_formats[data[1] - '0' - 1]
|
||||
}
|
||||
|
||||
// have a list of fielda for easy iteration
|
||||
header_fields: []^int
|
||||
if header.format in PBM {
|
||||
header_fields = {&header.width, &header.height}
|
||||
header.maxval = 1 // we know maxval for a bitmap
|
||||
} else {
|
||||
header_fields = {&header.width, &header.height, &header.maxval}
|
||||
}
|
||||
|
||||
// we're keeping track of the header byte length
|
||||
length = SIG_LENGTH
|
||||
|
||||
// loop state
|
||||
in_comment := false
|
||||
already_in_space := true
|
||||
current_field := 0
|
||||
current_value := header_fields[0]
|
||||
|
||||
parse_loop: for d, i in data[SIG_LENGTH:] {
|
||||
length += 1
|
||||
|
||||
// handle comments
|
||||
if in_comment {
|
||||
switch d {
|
||||
// comments only go up to next carriage return or line feed
|
||||
case '\r', '\n':
|
||||
in_comment = false
|
||||
}
|
||||
continue
|
||||
} else if d == '#' {
|
||||
in_comment = true
|
||||
continue
|
||||
}
|
||||
|
||||
// handle whitespace
|
||||
in_space := unicode.is_white_space(rune(d))
|
||||
if in_space {
|
||||
if already_in_space {
|
||||
continue
|
||||
}
|
||||
already_in_space = true
|
||||
|
||||
// switch to next value
|
||||
current_field += 1
|
||||
if current_field == len(header_fields) {
|
||||
// header byte length is 1-index so we'll increment again
|
||||
length += 1
|
||||
break parse_loop
|
||||
}
|
||||
current_value = header_fields[current_field]
|
||||
} else {
|
||||
already_in_space = false
|
||||
|
||||
if !unicode.is_digit(rune(d)) {
|
||||
err = Format_Error.Invalid_Header_Token_Character
|
||||
return
|
||||
}
|
||||
|
||||
val := int(d - '0')
|
||||
current_value^ = current_value^ * 10 + val
|
||||
}
|
||||
}
|
||||
|
||||
// set extra info
|
||||
header.channels = 3 if header.format in PPM else 1
|
||||
header.depth = 16 if header.maxval > int(max(u8)) else 8
|
||||
|
||||
// limit checking
|
||||
if current_field < len(header_fields) {
|
||||
err = Format_Error.Incomplete_Header
|
||||
return
|
||||
}
|
||||
|
||||
if header.width < 1 \
|
||||
|| header.height < 1 \
|
||||
|| header.maxval < 1 || header.maxval > int(max(u16)) {
|
||||
fmt.printf("[pnm] Header: {{width = %v, height = %v, maxval: %v}}\n", header.width, header.height, header.maxval)
|
||||
err = .Invalid_Header_Value
|
||||
return
|
||||
}
|
||||
|
||||
length -= 1
|
||||
err = Format_Error.None
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_parse_header_pam :: proc(data: []byte, allocator := context.allocator) -> (header: Header, length: int, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
// the spec needs the newline apparently
|
||||
if string(data[0:3]) != "P7\n" {
|
||||
err = .Invalid_Signature
|
||||
return
|
||||
}
|
||||
header.format = .P7
|
||||
|
||||
SIGNATURE_LENGTH :: 3
|
||||
HEADER_END :: "ENDHDR\n"
|
||||
|
||||
// we can already work out the size of the header
|
||||
header_end_index := strings.index(string(data), HEADER_END)
|
||||
if header_end_index == -1 {
|
||||
err = Format_Error.Incomplete_Header
|
||||
return
|
||||
}
|
||||
length = header_end_index + len(HEADER_END)
|
||||
|
||||
// string buffer for the tupltype
|
||||
tupltype: strings.Builder
|
||||
strings.builder_init(&tupltype, context.temp_allocator); defer strings.builder_destroy(&tupltype)
|
||||
fmt.sbprint(&tupltype, "")
|
||||
|
||||
// PAM uses actual lines, so we can iterate easily
|
||||
line_iterator := string(data[SIGNATURE_LENGTH : header_end_index])
|
||||
parse_loop: for line in strings.split_lines_iterator(&line_iterator) {
|
||||
line := line
|
||||
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
field, ok := strings.fields_iterator(&line)
|
||||
value := strings.trim_space(line)
|
||||
|
||||
// the field will change, but the logic stays the same
|
||||
current_field: ^int
|
||||
|
||||
switch field {
|
||||
case "WIDTH": current_field = &header.width
|
||||
case "HEIGHT": current_field = &header.height
|
||||
case "DEPTH": current_field = &header.channels
|
||||
case "MAXVAL": current_field = &header.maxval
|
||||
|
||||
case "TUPLTYPE":
|
||||
if len(value) == 0 {
|
||||
err = .Invalid_Header_Value
|
||||
return
|
||||
}
|
||||
|
||||
if len(tupltype.buf) == 0 {
|
||||
fmt.sbprint(&tupltype, value)
|
||||
} else {
|
||||
fmt.sbprint(&tupltype, "", value)
|
||||
}
|
||||
|
||||
continue
|
||||
|
||||
case:
|
||||
continue
|
||||
}
|
||||
|
||||
if current_field^ != 0 {
|
||||
err = Format_Error.Duplicate_Header_Field
|
||||
return
|
||||
}
|
||||
current_field^, ok = strconv.parse_int(value)
|
||||
if !ok {
|
||||
err = Format_Error.Invalid_Header_Value
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// extra info
|
||||
header.depth = 16 if header.maxval > int(max(u8)) else 8
|
||||
|
||||
// limit checking
|
||||
if header.width < 1 \
|
||||
|| header.height < 1 \
|
||||
|| header.maxval < 1 \
|
||||
|| header.maxval > int(max(u16)) {
|
||||
fmt.printf("[pam] Header: {{width = %v, height = %v, maxval: %v}}\n", header.width, header.height, header.maxval)
|
||||
err = Format_Error.Invalid_Header_Value
|
||||
return
|
||||
}
|
||||
|
||||
header.tupltype = strings.clone(strings.to_string(tupltype))
|
||||
err = Format_Error.None
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_parse_header_pfm :: proc(data: []byte) -> (header: Header, length: int, err: Error) {
|
||||
// we can just cycle through tokens for PFM
|
||||
field_iterator := string(data)
|
||||
field, ok := strings.fields_iterator(&field_iterator)
|
||||
|
||||
switch field {
|
||||
case "Pf":
|
||||
header.format = .Pf
|
||||
header.channels = 1
|
||||
case "PF":
|
||||
header.format = .PF
|
||||
header.channels = 3
|
||||
case:
|
||||
err = .Invalid_Signature
|
||||
return
|
||||
}
|
||||
|
||||
// floating point
|
||||
header.depth = 32
|
||||
|
||||
// width
|
||||
field, ok = strings.fields_iterator(&field_iterator)
|
||||
if !ok {
|
||||
err = Format_Error.Incomplete_Header
|
||||
return
|
||||
}
|
||||
header.width, ok = strconv.parse_int(field)
|
||||
if !ok {
|
||||
err = Format_Error.Invalid_Header_Value
|
||||
return
|
||||
}
|
||||
|
||||
// height
|
||||
field, ok = strings.fields_iterator(&field_iterator)
|
||||
if !ok {
|
||||
err = Format_Error.Incomplete_Header
|
||||
return
|
||||
}
|
||||
header.height, ok = strconv.parse_int(field)
|
||||
if !ok {
|
||||
err = Format_Error.Invalid_Header_Value
|
||||
return
|
||||
}
|
||||
|
||||
// scale (sign is endianness)
|
||||
field, ok = strings.fields_iterator(&field_iterator)
|
||||
if !ok {
|
||||
err = Format_Error.Incomplete_Header
|
||||
return
|
||||
}
|
||||
header.scale, ok = strconv.parse_f32(field)
|
||||
if !ok {
|
||||
err = Format_Error.Invalid_Header_Value
|
||||
return
|
||||
}
|
||||
|
||||
if header.scale < 0.0 {
|
||||
header.little_endian = true
|
||||
header.scale = -header.scale
|
||||
}
|
||||
|
||||
// pointer math to get header size
|
||||
length = int((uintptr(raw_data(field_iterator)) + 1) - uintptr(raw_data(data)))
|
||||
|
||||
// limit checking
|
||||
if header.width < 1 \
|
||||
|| header.height < 1 \
|
||||
|| header.scale == 0.0 {
|
||||
fmt.printf("[pfm] Header: {{width = %v, height = %v, scale: %v}}\n", header.width, header.height, header.scale)
|
||||
err = .Invalid_Header_Value
|
||||
return
|
||||
}
|
||||
|
||||
err = Format_Error.None
|
||||
return
|
||||
}
|
||||
|
||||
decode_image :: proc(img: ^Image, header: Header, data: []byte, allocator := context.allocator) -> (err: Error) {
|
||||
assert(img != nil)
|
||||
context.allocator = allocator
|
||||
|
||||
img.width = header.width
|
||||
img.height = header.height
|
||||
img.channels = header.channels
|
||||
img.depth = header.depth
|
||||
|
||||
buffer_size := image.compute_buffer_size(img.width, img.height, img.channels, img.depth)
|
||||
|
||||
// we can check data size for binary formats
|
||||
if header.format in BINARY {
|
||||
if len(data) < buffer_size {
|
||||
fmt.printf("len(data): %v, buffer size: %v\n", len(data), buffer_size)
|
||||
return .Buffer_Too_Small
|
||||
}
|
||||
}
|
||||
|
||||
// for ASCII and P4, we use length for the termination condition, so start at 0
|
||||
// BINARY will be a simple memcopy so the buffer length should also be initialised
|
||||
if header.format in ASCII || header.format == .P4 {
|
||||
bytes.buffer_init_allocator(&img.pixels, 0, buffer_size)
|
||||
} else {
|
||||
bytes.buffer_init_allocator(&img.pixels, buffer_size, buffer_size)
|
||||
}
|
||||
|
||||
switch header.format {
|
||||
// Compressed binary
|
||||
case .P4:
|
||||
for d in data {
|
||||
for b in 1 ..= 8 {
|
||||
bit := byte(8 - b)
|
||||
pix := (d >> bit) & 1
|
||||
bytes.buffer_write_byte(&img.pixels, pix)
|
||||
if len(img.pixels.buf) % img.width == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(img.pixels.buf) == cap(img.pixels.buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Simple binary
|
||||
case .P5, .P6, .P7, .Pf, .PF:
|
||||
copy(img.pixels.buf[:], data[:])
|
||||
|
||||
// convert to native endianness
|
||||
if header.format in PFM {
|
||||
pixels := mem.slice_data_cast([]f32, img.pixels.buf[:])
|
||||
if header.little_endian {
|
||||
for p in &pixels {
|
||||
p = f32(transmute(f32le) p)
|
||||
}
|
||||
} else {
|
||||
for p in &pixels {
|
||||
p = f32(transmute(f32be) p)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if img.depth == 16 {
|
||||
pixels := mem.slice_data_cast([]u16, img.pixels.buf[:])
|
||||
for p in &pixels {
|
||||
p = u16(transmute(u16be) p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If-it-looks-like-a-bitmap ASCII
|
||||
case .P1:
|
||||
for c in data {
|
||||
switch c {
|
||||
case '0', '1':
|
||||
bytes.buffer_write_byte(&img.pixels, c - '0')
|
||||
}
|
||||
|
||||
if len(img.pixels.buf) == cap(img.pixels.buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(img.pixels.buf) < cap(img.pixels.buf) {
|
||||
err = Format_Error.Buffer_Too_Small
|
||||
return
|
||||
}
|
||||
|
||||
// Token ASCII
|
||||
case .P2, .P3:
|
||||
field_iterator := string(data)
|
||||
for field in strings.fields_iterator(&field_iterator) {
|
||||
value, ok := strconv.parse_int(field)
|
||||
if !ok {
|
||||
err = Format_Error.Invalid_Buffer_ASCII_Token
|
||||
return
|
||||
}
|
||||
|
||||
//? do we want to enforce the maxval, the limit, or neither
|
||||
if value > int(max(u16)) /*header.maxval*/ {
|
||||
err = Format_Error.Invalid_Buffer_Value
|
||||
return
|
||||
}
|
||||
|
||||
switch img.depth {
|
||||
case 8:
|
||||
bytes.buffer_write_byte(&img.pixels, u8(value))
|
||||
case 16:
|
||||
vb := transmute([2]u8) u16(value)
|
||||
bytes.buffer_write(&img.pixels, vb[:])
|
||||
}
|
||||
|
||||
if len(img.pixels.buf) == cap(img.pixels.buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(img.pixels.buf) < cap(img.pixels.buf) {
|
||||
err = Format_Error.Buffer_Too_Small
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = Format_Error.None
|
||||
return
|
||||
}
|
||||
|
||||
// Automatically try to select an appropriate format to save to based on `img.channel` and `img.depth`
|
||||
autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, force_black_and_white := false, pfm_scale := f32(1.0)) -> (res: Info, ok: bool) {
|
||||
/*
|
||||
PBM (P1, P4): Portable Bit Map, stores black and white images (1 channel)
|
||||
PGM (P2, P5): Portable Gray Map, stores greyscale images (1 channel, 1 or 2 bytes per value)
|
||||
PPM (P3, P6): Portable Pixel Map, stores colour images (3 channel, 1 or 2 bytes per value)
|
||||
PAM (P7 ): Portable Arbitrary Map, stores arbitrary channel images (1 or 2 bytes per value)
|
||||
PFM (Pf, PF): Portable Float Map, stores floating-point images (Pf: 1 channel, PF: 3 channel)
|
||||
|
||||
ASCII :: Formats{.P1, .P2, .P3}
|
||||
*/
|
||||
using res.header
|
||||
|
||||
width = img.width
|
||||
height = img.height
|
||||
channels = img.channels
|
||||
depth = img.depth
|
||||
maxval = 255 if img.depth == 8 else 65535
|
||||
little_endian = true if ODIN_ENDIAN == .Little else false
|
||||
|
||||
// Assume we'll find a suitable format
|
||||
ok = true
|
||||
|
||||
switch img.channels {
|
||||
case 1:
|
||||
// Must be Portable Float Map
|
||||
if img.depth == 32 {
|
||||
format = .Pf
|
||||
return
|
||||
}
|
||||
|
||||
if force_black_and_white {
|
||||
// Portable Bit Map
|
||||
format = .P4 if prefer_binary else .P1
|
||||
maxval = 1
|
||||
return
|
||||
} else {
|
||||
// Portable Gray Map
|
||||
format = .P5 if prefer_binary else .P2
|
||||
return
|
||||
}
|
||||
|
||||
case 3:
|
||||
// Must be Portable Float Map
|
||||
if img.depth == 32 {
|
||||
format = .PF
|
||||
return
|
||||
}
|
||||
|
||||
// Portable Pixel Map
|
||||
format = .P6 if prefer_binary else .P3
|
||||
return
|
||||
|
||||
case:
|
||||
// Portable Arbitrary Map
|
||||
if img.depth == 8 || img.depth == 16 {
|
||||
format = .P7
|
||||
scale = pfm_scale
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't find a suitable format
|
||||
return {}, false
|
||||
}
|
||||
|
||||
@(init, private)
|
||||
_register :: proc() {
|
||||
loader :: proc(data: []byte, options: image.Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) {
|
||||
return load_from_bytes(data, allocator)
|
||||
}
|
||||
destroyer :: proc(img: ^Image) {
|
||||
_ = destroy(img)
|
||||
}
|
||||
image.register(.NetPBM, loader, destroyer)
|
||||
}
|
||||
@@ -219,7 +219,7 @@ write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: b
|
||||
defer close(fd)
|
||||
|
||||
write_string(fd,
|
||||
fmt.tprintf("P6\n%v %v\n%v\n", width, height, (1 << uint(depth) - 1)),
|
||||
fmt.tprintf("P6\n%v %v\n%v\n", width, height, uint(1 << uint(depth) - 1)),
|
||||
)
|
||||
|
||||
if channels == 3 {
|
||||
|
||||
@@ -242,17 +242,16 @@ srgb :: proc(c: image.PNG_Chunk) -> (res: sRGB, ok: bool) {
|
||||
}
|
||||
|
||||
plte :: proc(c: image.PNG_Chunk) -> (res: PLTE, ok: bool) {
|
||||
if c.header.type != .PLTE {
|
||||
if c.header.type != .PLTE || c.header.length % 3 != 0 || c.header.length > 768 {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
i := 0; j := 0; ok = true
|
||||
for j < int(c.header.length) {
|
||||
res.entries[i] = {c.data[j], c.data[j+1], c.data[j+2]}
|
||||
i += 1; j += 3
|
||||
plte := mem.slice_data_cast([]image.RGB_Pixel, c.data[:])
|
||||
for color, i in plte {
|
||||
res.entries[i] = color
|
||||
}
|
||||
res.used = u16(i)
|
||||
return
|
||||
res.used = u16(len(plte))
|
||||
return res, true
|
||||
}
|
||||
|
||||
splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
|
||||
@@ -439,7 +438,7 @@ when false {
|
||||
flags: int = O_WRONLY|O_CREATE|O_TRUNC
|
||||
|
||||
if len(image.pixels) == 0 || len(image.pixels) < image.width * image.height * int(image.channels) {
|
||||
return E_PNG.Invalid_Image_Dimensions
|
||||
return .Invalid_Image_Dimensions
|
||||
}
|
||||
|
||||
mode: int = 0
|
||||
@@ -450,7 +449,7 @@ when false {
|
||||
|
||||
fd, fderr := open(filename, flags, mode)
|
||||
if fderr != 0 {
|
||||
return E_General.Cannot_Open_File
|
||||
return .Cannot_Open_File
|
||||
}
|
||||
defer close(fd)
|
||||
|
||||
@@ -473,7 +472,7 @@ when false {
|
||||
case 3: ihdr.color_type = Color_Type{.Color}
|
||||
case 4: ihdr.color_type = Color_Type{.Color, .Alpha}
|
||||
case:// Unhandled
|
||||
return E_PNG.Unknown_Color_Type
|
||||
return .Unknown_Color_Type
|
||||
}
|
||||
h := make_chunk(ihdr, .IHDR)
|
||||
write_chunk(fd, h)
|
||||
|
||||
+62
-49
@@ -18,23 +18,16 @@ import "core:compress/zlib"
|
||||
import "core:image"
|
||||
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
import "core:hash"
|
||||
import "core:bytes"
|
||||
import "core:io"
|
||||
import "core:mem"
|
||||
import "core:intrinsics"
|
||||
|
||||
/*
|
||||
67_108_864 pixels max by default.
|
||||
Maximum allowed dimensions are capped at 65535 * 65535.
|
||||
*/
|
||||
MAX_DIMENSIONS :: min(#config(PNG_MAX_DIMENSIONS, 8192 * 8192), 65535 * 65535)
|
||||
// Limit chunk sizes.
|
||||
// By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes.
|
||||
// The total number of pixels defaults to 64 Megapixel and can be tuned in image/common.odin.
|
||||
|
||||
/*
|
||||
Limit chunk sizes.
|
||||
By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes.
|
||||
*/
|
||||
_MAX_IDAT_DEFAULT :: ( 8192 /* Width */ * 8192 /* Height */ * 2 /* 16-bit */) + 8192 /* Filter bytes */
|
||||
_MAX_IDAT :: (65535 /* Width */ * 65535 /* Height */ * 2 /* 16-bit */) + 65535 /* Filter bytes */
|
||||
|
||||
@@ -64,7 +57,7 @@ Row_Filter :: enum u8 {
|
||||
Paeth = 4,
|
||||
}
|
||||
|
||||
PLTE_Entry :: [3]u8
|
||||
PLTE_Entry :: image.RGB_Pixel
|
||||
|
||||
PLTE :: struct #packed {
|
||||
entries: [256]PLTE_Entry,
|
||||
@@ -244,7 +237,7 @@ append_chunk :: proc(list: ^[dynamic]image.PNG_Chunk, src: image.PNG_Chunk, allo
|
||||
append(list, c)
|
||||
if len(list) != length + 1 {
|
||||
// Resize during append failed.
|
||||
return mem.Allocator_Error.Out_Of_Memory
|
||||
return .Unable_To_Allocate_Or_Resize
|
||||
}
|
||||
|
||||
return
|
||||
@@ -259,7 +252,7 @@ read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) {
|
||||
header := (^image.PNG_IHDR)(raw_data(c.data))^
|
||||
// Validate IHDR
|
||||
using header
|
||||
if width == 0 || height == 0 || u128(width) * u128(height) > MAX_DIMENSIONS {
|
||||
if width == 0 || height == 0 || u128(width) * u128(height) > image.MAX_DIMENSIONS {
|
||||
return {}, .Invalid_Image_Dimensions
|
||||
}
|
||||
|
||||
@@ -324,13 +317,12 @@ read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) {
|
||||
}
|
||||
|
||||
chunk_type_to_name :: proc(type: ^image.PNG_Chunk_Type) -> string {
|
||||
t := transmute(^u8)type
|
||||
return strings.string_from_ptr(t, 4)
|
||||
return string(([^]u8)(type)[:4])
|
||||
}
|
||||
|
||||
load_from_slice :: proc(slice: []u8, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
ctx := &compress.Context_Memory_Input{
|
||||
input_data = slice,
|
||||
input_data = data,
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -350,10 +342,9 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_slice(data, options)
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
img = new(Image)
|
||||
return img, compress.General_Error.File_Not_Found
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +357,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
options -= {.info}
|
||||
}
|
||||
|
||||
if .return_header in options && .return_metadata in options {
|
||||
options -= {.return_header}
|
||||
}
|
||||
|
||||
if .alpha_drop_if_present in options && .alpha_add_if_missing in options {
|
||||
return {}, compress.General_Error.Incompatible_Options
|
||||
}
|
||||
@@ -377,13 +372,14 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
if img == nil {
|
||||
img = new(Image)
|
||||
}
|
||||
img.which = .PNG
|
||||
|
||||
info := new(image.PNG_Info)
|
||||
img.metadata = info
|
||||
|
||||
signature, io_error := compress.read_data(ctx, Signature)
|
||||
if io_error != .None || signature != .PNG {
|
||||
return img, .Invalid_PNG_Signature
|
||||
return img, .Invalid_Signature
|
||||
}
|
||||
|
||||
idat: []u8
|
||||
@@ -392,7 +388,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
|
||||
idat_length := u64(0)
|
||||
|
||||
c: image.PNG_Chunk
|
||||
c: image.PNG_Chunk
|
||||
ch: image.PNG_Chunk_Header
|
||||
e: io.Error
|
||||
|
||||
@@ -473,6 +469,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
}
|
||||
info.header = h
|
||||
|
||||
if .return_header in options && .return_metadata not_in options && .do_not_decompress_image not_in options {
|
||||
return img, nil
|
||||
}
|
||||
|
||||
case .PLTE:
|
||||
seen_plte = true
|
||||
// PLTE must appear before IDAT and can't appear for color types 0, 4.
|
||||
@@ -540,9 +540,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
seen_iend = true
|
||||
|
||||
case .bKGD:
|
||||
|
||||
// TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be
|
||||
|
||||
c = read_chunk(ctx) or_return
|
||||
seen_bkgd = true
|
||||
if .return_metadata in options {
|
||||
@@ -594,23 +591,36 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
*/
|
||||
|
||||
final_image_channels += 1
|
||||
|
||||
seen_trns = true
|
||||
|
||||
if .Paletted in header.color_type {
|
||||
if len(c.data) > 256 {
|
||||
return img, .TNRS_Invalid_Length
|
||||
}
|
||||
} else if .Color in header.color_type {
|
||||
if len(c.data) != 6 {
|
||||
return img, .TNRS_Invalid_Length
|
||||
}
|
||||
} else if len(c.data) != 2 {
|
||||
return img, .TNRS_Invalid_Length
|
||||
}
|
||||
|
||||
if info.header.bit_depth < 8 && .Paletted not_in info.header.color_type {
|
||||
// Rescale tRNS data so key matches intensity
|
||||
dsc := depth_scale_table
|
||||
dsc := depth_scale_table
|
||||
scale := dsc[info.header.bit_depth]
|
||||
if scale != 1 {
|
||||
key := mem.slice_data_cast([]u16be, c.data)[0] * u16be(scale)
|
||||
c.data = []u8{0, u8(key & 255)}
|
||||
}
|
||||
}
|
||||
|
||||
trns = c
|
||||
|
||||
case .iDOT, .CbGI:
|
||||
case .iDOT, .CgBI:
|
||||
/*
|
||||
iPhone PNG bastardization that doesn't adhere to spec with broken IDAT chunk.
|
||||
We're not going to add support for it. If you have the misfortunte of coming
|
||||
We're not going to add support for it. If you have the misfortune of coming
|
||||
across one of these files, use a utility to defry it.
|
||||
*/
|
||||
return img, .Image_Does_Not_Adhere_to_Spec
|
||||
@@ -635,6 +645,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
return img, .IDAT_Missing
|
||||
}
|
||||
|
||||
if .Paletted in header.color_type && !seen_plte {
|
||||
return img, .PLTE_Missing
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate the expected output size, to help `inflate` make better decisions about the output buffer.
|
||||
We'll also use it to check the returned buffer size is what we expected it to be.
|
||||
@@ -683,15 +697,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
return {}, defilter_error
|
||||
}
|
||||
|
||||
/*
|
||||
Now we'll handle the relocoring of paletted images, handling of tRNS chunks,
|
||||
and we'll expand grayscale images to RGB(A).
|
||||
|
||||
For the sake of convenience we return only RGB(A) images. In the future we
|
||||
may supply an option to return Gray/Gray+Alpha as-is, in which case RGB(A)
|
||||
will become the default.
|
||||
*/
|
||||
|
||||
if .Paletted in header.color_type && .do_not_expand_indexed in options {
|
||||
return img, nil
|
||||
}
|
||||
@@ -699,7 +704,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
return img, nil
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Now we're going to optionally apply various post-processing stages,
|
||||
to for example expand grayscale, apply a palette, premultiply alpha, etc.
|
||||
*/
|
||||
raw_image_channels := img.channels
|
||||
out_image_channels := 3
|
||||
|
||||
@@ -737,7 +745,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
|
||||
t := bytes.Buffer{}
|
||||
if !resize(&t.buf, dest_raw_size) {
|
||||
return {}, mem.Allocator_Error.Out_Of_Memory
|
||||
return {}, .Unable_To_Allocate_Or_Resize
|
||||
}
|
||||
|
||||
i := 0; j := 0
|
||||
@@ -818,7 +826,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 16)
|
||||
t := bytes.Buffer{}
|
||||
if !resize(&t.buf, dest_raw_size) {
|
||||
return {}, mem.Allocator_Error.Out_Of_Memory
|
||||
return {}, .Unable_To_Allocate_Or_Resize
|
||||
}
|
||||
|
||||
p16 := mem.slice_data_cast([]u16, temp.buf[:])
|
||||
@@ -994,7 +1002,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
o16 = o16[out_image_channels:]
|
||||
}
|
||||
case:
|
||||
unreachable("We should never seen # channels other than 1-4 inclusive.")
|
||||
panic("We should never seen # channels other than 1-4 inclusive.")
|
||||
}
|
||||
|
||||
img.pixels = t
|
||||
@@ -1017,7 +1025,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
|
||||
t := bytes.Buffer{}
|
||||
if !resize(&t.buf, dest_raw_size) {
|
||||
return {}, mem.Allocator_Error.Out_Of_Memory
|
||||
return {}, .Unable_To_Allocate_Or_Resize
|
||||
}
|
||||
|
||||
p := mem.slice_data_cast([]u8, temp.buf[:])
|
||||
@@ -1187,7 +1195,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
o = o[out_image_channels:]
|
||||
}
|
||||
case:
|
||||
unreachable("We should never seen # channels other than 1-4 inclusive.")
|
||||
panic("We should never seen # channels other than 1-4 inclusive.")
|
||||
}
|
||||
|
||||
img.pixels = t
|
||||
@@ -1198,13 +1206,12 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
This may change if we ever don't expand 1, 2 and 4 bit images. But, those raw
|
||||
returns will likely bypass this processing pipeline.
|
||||
*/
|
||||
unreachable("We should never see bit depths other than 8, 16 and 'Paletted' here.")
|
||||
panic("We should never see bit depths other than 8, 16 and 'Paletted' here.")
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
|
||||
filter_paeth :: #force_inline proc(left, up, up_left: u8) -> u8 {
|
||||
aa, bb, cc := i16(left), i16(up), i16(up_left)
|
||||
p := aa + bb - cc
|
||||
@@ -1526,7 +1533,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
|
||||
|
||||
num_bytes := compute_buffer_size(width, height, channels, depth == 16 ? 16 : 8)
|
||||
if !resize(&img.pixels.buf, num_bytes) {
|
||||
return mem.Allocator_Error.Out_Of_Memory
|
||||
return .Unable_To_Allocate_Or_Resize
|
||||
}
|
||||
|
||||
filter_ok: bool
|
||||
@@ -1568,7 +1575,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
|
||||
temp: bytes.Buffer
|
||||
temp_len := compute_buffer_size(x, y, channels, depth == 16 ? 16 : 8)
|
||||
if !resize(&temp.buf, temp_len) {
|
||||
return mem.Allocator_Error.Out_Of_Memory
|
||||
return .Unable_To_Allocate_Or_Resize
|
||||
}
|
||||
|
||||
params := Filter_Params{
|
||||
@@ -1630,4 +1637,10 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
|
||||
return nil
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_slice, load_from_context}
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
|
||||
@(init, private)
|
||||
_register :: proc() {
|
||||
image.register(.PNG, load_from_bytes, destroy)
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
*/
|
||||
|
||||
|
||||
// package qoi implements a QOI image reader
|
||||
//
|
||||
// The QOI specification is at https://qoiformat.org.
|
||||
package qoi
|
||||
|
||||
import "core:image"
|
||||
import "core:compress"
|
||||
import "core:bytes"
|
||||
import "core:os"
|
||||
|
||||
Error :: image.Error
|
||||
Image :: image.Image
|
||||
Options :: image.Options
|
||||
|
||||
RGB_Pixel :: image.RGB_Pixel
|
||||
RGBA_Pixel :: image.RGBA_Pixel
|
||||
|
||||
save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if img == nil {
|
||||
return .Invalid_Input_Image
|
||||
}
|
||||
|
||||
if output == nil {
|
||||
return .Invalid_Output
|
||||
}
|
||||
|
||||
pixels := img.width * img.height
|
||||
if pixels == 0 || pixels > image.MAX_DIMENSIONS {
|
||||
return .Invalid_Input_Image
|
||||
}
|
||||
|
||||
// QOI supports only 8-bit images with 3 or 4 channels.
|
||||
if img.depth != 8 || img.channels < 3 || img.channels > 4 {
|
||||
return .Invalid_Input_Image
|
||||
}
|
||||
|
||||
if img.channels * pixels != len(img.pixels.buf) {
|
||||
return .Invalid_Input_Image
|
||||
}
|
||||
|
||||
written := 0
|
||||
|
||||
// Calculate and allocate maximum size. We'll reclaim space to actually written output at the end.
|
||||
max_size := pixels * (img.channels + 1) + size_of(image.QOI_Header) + size_of(u64be)
|
||||
|
||||
if !resize(&output.buf, max_size) {
|
||||
return .Unable_To_Allocate_Or_Resize
|
||||
}
|
||||
|
||||
header := image.QOI_Header{
|
||||
magic = image.QOI_Magic,
|
||||
width = u32be(img.width),
|
||||
height = u32be(img.height),
|
||||
channels = u8(img.channels),
|
||||
color_space = .Linear if .qoi_all_channels_linear in options else .sRGB,
|
||||
}
|
||||
header_bytes := transmute([size_of(image.QOI_Header)]u8)header
|
||||
|
||||
copy(output.buf[written:], header_bytes[:])
|
||||
written += size_of(image.QOI_Header)
|
||||
|
||||
/*
|
||||
Encode loop starts here.
|
||||
*/
|
||||
seen: [64]RGBA_Pixel
|
||||
pix := RGBA_Pixel{0, 0, 0, 255}
|
||||
prev := pix
|
||||
|
||||
seen[qoi_hash(pix)] = pix
|
||||
|
||||
input := img.pixels.buf[:]
|
||||
run := u8(0)
|
||||
|
||||
for len(input) > 0 {
|
||||
if img.channels == 4 {
|
||||
pix = (^RGBA_Pixel)(raw_data(input))^
|
||||
} else {
|
||||
pix.rgb = (^RGB_Pixel)(raw_data(input))^
|
||||
}
|
||||
input = input[img.channels:]
|
||||
|
||||
if pix == prev {
|
||||
run += 1
|
||||
// As long as the pixel matches the last one, accumulate the run total.
|
||||
// If we reach the max run length or the end of the image, write the run.
|
||||
if run == 62 || len(input) == 0 {
|
||||
// Encode and write run
|
||||
output.buf[written] = u8(QOI_Opcode_Tag.RUN) | (run - 1)
|
||||
written += 1
|
||||
run = 0
|
||||
}
|
||||
} else {
|
||||
if run > 0 {
|
||||
// The pixel differs from the previous one, but we still need to write the pending run.
|
||||
// Encode and write run
|
||||
output.buf[written] = u8(QOI_Opcode_Tag.RUN) | (run - 1)
|
||||
written += 1
|
||||
run = 0
|
||||
}
|
||||
|
||||
index := qoi_hash(pix)
|
||||
|
||||
if seen[index] == pix {
|
||||
// Write indexed pixel
|
||||
output.buf[written] = u8(QOI_Opcode_Tag.INDEX) | index
|
||||
written += 1
|
||||
} else {
|
||||
// Add pixel to index
|
||||
seen[index] = pix
|
||||
|
||||
// If the alpha matches the previous pixel's alpha, we don't need to write a full RGBA literal.
|
||||
if pix.a == prev.a {
|
||||
// Delta
|
||||
d := pix.rgb - prev.rgb
|
||||
|
||||
// DIFF, biased and modulo 256
|
||||
_d := d + 2
|
||||
|
||||
// LUMA, biased and modulo 256
|
||||
_l := RGB_Pixel{ d.r - d.g + 8, d.g + 32, d.b - d.g + 8 }
|
||||
|
||||
if _d.r < 4 && _d.g < 4 && _d.b < 4 {
|
||||
// Delta is between -2 and 1 inclusive
|
||||
output.buf[written] = u8(QOI_Opcode_Tag.DIFF) | _d.r << 4 | _d.g << 2 | _d.b
|
||||
written += 1
|
||||
} else if _l.r < 16 && _l.g < 64 && _l.b < 16 {
|
||||
// Biased luma is between {-8..7, -32..31, -8..7}
|
||||
output.buf[written ] = u8(QOI_Opcode_Tag.LUMA) | _l.g
|
||||
output.buf[written + 1] = _l.r << 4 | _l.b
|
||||
written += 2
|
||||
} else {
|
||||
// Write RGB literal
|
||||
output.buf[written] = u8(QOI_Opcode_Tag.RGB)
|
||||
pix_bytes := transmute([4]u8)pix
|
||||
copy(output.buf[written + 1:], pix_bytes[:3])
|
||||
written += 4
|
||||
}
|
||||
} else {
|
||||
// Write RGBA literal
|
||||
output.buf[written] = u8(QOI_Opcode_Tag.RGBA)
|
||||
pix_bytes := transmute([4]u8)pix
|
||||
copy(output.buf[written + 1:], pix_bytes[:])
|
||||
written += 5
|
||||
}
|
||||
}
|
||||
}
|
||||
prev = pix
|
||||
}
|
||||
|
||||
trailer := []u8{0, 0, 0, 0, 0, 0, 0, 1}
|
||||
copy(output.buf[written:], trailer[:])
|
||||
written += len(trailer)
|
||||
|
||||
resize(&output.buf, written)
|
||||
return nil
|
||||
}
|
||||
|
||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
defer bytes.buffer_destroy(out)
|
||||
|
||||
save_to_memory(out, img, options) or_return
|
||||
write_ok := os.write_entire_file(output, out.buf[:])
|
||||
|
||||
return nil if write_ok else .Unable_To_Write_File
|
||||
}
|
||||
|
||||
save :: proc{save_to_memory, save_to_file}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
ctx := &compress.Context_Memory_Input{
|
||||
input_data = data,
|
||||
}
|
||||
|
||||
img, err = load_from_context(ctx, options, allocator)
|
||||
return img, err
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
options := options
|
||||
|
||||
if .info in options {
|
||||
options |= {.return_metadata, .do_not_decompress_image}
|
||||
options -= {.info}
|
||||
}
|
||||
|
||||
if .return_header in options && .return_metadata in options {
|
||||
options -= {.return_header}
|
||||
}
|
||||
|
||||
header := image.read_data(ctx, image.QOI_Header) or_return
|
||||
if header.magic != image.QOI_Magic {
|
||||
return img, .Invalid_Signature
|
||||
}
|
||||
|
||||
if img == nil {
|
||||
img = new(Image)
|
||||
}
|
||||
img.which = .QOI
|
||||
|
||||
if .return_metadata in options {
|
||||
info := new(image.QOI_Info)
|
||||
info.header = header
|
||||
img.metadata = info
|
||||
}
|
||||
|
||||
if header.channels != 3 && header.channels != 4 {
|
||||
return img, .Invalid_Number_Of_Channels
|
||||
}
|
||||
|
||||
if header.color_space != .sRGB && header.color_space != .Linear {
|
||||
return img, .Invalid_Color_Space
|
||||
}
|
||||
|
||||
if header.width == 0 || header.height == 0 {
|
||||
return img, .Invalid_Image_Dimensions
|
||||
}
|
||||
|
||||
total_pixels := header.width * header.height
|
||||
if total_pixels > image.MAX_DIMENSIONS {
|
||||
return img, .Image_Dimensions_Too_Large
|
||||
}
|
||||
|
||||
img.width = int(header.width)
|
||||
img.height = int(header.height)
|
||||
img.channels = 4 if .alpha_add_if_missing in options else int(header.channels)
|
||||
img.depth = 8
|
||||
|
||||
if .do_not_decompress_image in options {
|
||||
img.channels = int(header.channels)
|
||||
return
|
||||
}
|
||||
|
||||
bytes_needed := image.compute_buffer_size(int(header.width), int(header.height), img.channels, 8)
|
||||
|
||||
if !resize(&img.pixels.buf, bytes_needed) {
|
||||
return img, .Unable_To_Allocate_Or_Resize
|
||||
}
|
||||
|
||||
/*
|
||||
Decode loop starts here.
|
||||
*/
|
||||
seen: [64]RGBA_Pixel
|
||||
pix := RGBA_Pixel{0, 0, 0, 255}
|
||||
seen[qoi_hash(pix)] = pix
|
||||
pixels := img.pixels.buf[:]
|
||||
|
||||
decode: for len(pixels) > 0 {
|
||||
data := image.read_u8(ctx) or_return
|
||||
|
||||
tag := QOI_Opcode_Tag(data)
|
||||
#partial switch tag {
|
||||
case .RGB:
|
||||
pix.rgb = image.read_data(ctx, RGB_Pixel) or_return
|
||||
|
||||
#no_bounds_check {
|
||||
seen[qoi_hash(pix)] = pix
|
||||
}
|
||||
|
||||
case .RGBA:
|
||||
pix = image.read_data(ctx, RGBA_Pixel) or_return
|
||||
|
||||
#no_bounds_check {
|
||||
seen[qoi_hash(pix)] = pix
|
||||
}
|
||||
|
||||
case:
|
||||
// 2-bit tag
|
||||
tag = QOI_Opcode_Tag(data & QOI_Opcode_Mask)
|
||||
#partial switch tag {
|
||||
case .INDEX:
|
||||
pix = seen[data & 63]
|
||||
|
||||
case .DIFF:
|
||||
diff_r := ((data >> 4) & 3) - 2
|
||||
diff_g := ((data >> 2) & 3) - 2
|
||||
diff_b := ((data >> 0) & 3) - 2
|
||||
|
||||
pix += {diff_r, diff_g, diff_b, 0}
|
||||
|
||||
#no_bounds_check {
|
||||
seen[qoi_hash(pix)] = pix
|
||||
}
|
||||
|
||||
case .LUMA:
|
||||
data2 := image.read_u8(ctx) or_return
|
||||
|
||||
diff_g := (data & 63) - 32
|
||||
diff_r := diff_g - 8 + ((data2 >> 4) & 15)
|
||||
diff_b := diff_g - 8 + (data2 & 15)
|
||||
|
||||
pix += {diff_r, diff_g, diff_b, 0}
|
||||
|
||||
#no_bounds_check {
|
||||
seen[qoi_hash(pix)] = pix
|
||||
}
|
||||
|
||||
case .RUN:
|
||||
if length := int(data & 63) + 1; (length * img.channels) > len(pixels) {
|
||||
return img, .Corrupt
|
||||
} else {
|
||||
#no_bounds_check for in 0..<length {
|
||||
copy(pixels, pix[:img.channels])
|
||||
pixels = pixels[img.channels:]
|
||||
}
|
||||
}
|
||||
|
||||
continue decode
|
||||
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
#no_bounds_check {
|
||||
copy(pixels, pix[:img.channels])
|
||||
pixels = pixels[img.channels:]
|
||||
}
|
||||
}
|
||||
|
||||
// The byte stream's end is marked with 7 0x00 bytes followed by a single 0x01 byte.
|
||||
trailer, trailer_err := compress.read_data(ctx, u64be)
|
||||
if trailer_err != nil || trailer != 0x1 {
|
||||
return img, .Missing_Or_Corrupt_Trailer
|
||||
}
|
||||
|
||||
if .alpha_premultiply in options && !image.alpha_drop_if_present(img, options) {
|
||||
return img, .Post_Processing_Error
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
/*
|
||||
Cleanup of image-specific data.
|
||||
*/
|
||||
destroy :: proc(img: ^Image) {
|
||||
if img == nil {
|
||||
/*
|
||||
Nothing to do.
|
||||
Load must've returned with an error.
|
||||
*/
|
||||
return
|
||||
}
|
||||
|
||||
bytes.buffer_destroy(&img.pixels)
|
||||
|
||||
if v, ok := img.metadata.(^image.QOI_Info); ok {
|
||||
free(v)
|
||||
}
|
||||
free(img)
|
||||
}
|
||||
|
||||
QOI_Opcode_Tag :: enum u8 {
|
||||
// 2-bit tags
|
||||
INDEX = 0b0000_0000, // 6-bit index into color array follows
|
||||
DIFF = 0b0100_0000, // 3x (RGB) 2-bit difference follows (-2..1), bias of 2.
|
||||
LUMA = 0b1000_0000, // Luma difference
|
||||
RUN = 0b1100_0000, // Run length encoding, bias -1
|
||||
|
||||
// 8-bit tags
|
||||
RGB = 0b1111_1110, // Raw RGB pixel follows
|
||||
RGBA = 0b1111_1111, // Raw RGBA pixel follows
|
||||
}
|
||||
|
||||
QOI_Opcode_Mask :: 0b1100_0000
|
||||
QOI_Data_Mask :: 0b0011_1111
|
||||
|
||||
qoi_hash :: #force_inline proc(pixel: RGBA_Pixel) -> (index: u8) {
|
||||
i1 := u16(pixel.r) * 3
|
||||
i2 := u16(pixel.g) * 5
|
||||
i3 := u16(pixel.b) * 7
|
||||
i4 := u16(pixel.a) * 11
|
||||
|
||||
return u8((i1 + i2 + i3 + i4) & 63)
|
||||
}
|
||||
|
||||
@(init, private)
|
||||
_register :: proc() {
|
||||
image.register(.QOI, load_from_bytes, destroy)
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
/*
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
Benoit Jacquier: tga loader
|
||||
*/
|
||||
|
||||
|
||||
// package tga implements a TGA image writer for 8-bit RGB and RGBA images.
|
||||
package tga
|
||||
|
||||
import "core:mem"
|
||||
import "core:image"
|
||||
import "core:bytes"
|
||||
import "core:os"
|
||||
import "core:compress"
|
||||
import "core:strings"
|
||||
|
||||
// TODO: alpha_premultiply support
|
||||
|
||||
Error :: image.Error
|
||||
Image :: image.Image
|
||||
Options :: image.Options
|
||||
|
||||
GA_Pixel :: image.GA_Pixel
|
||||
RGB_Pixel :: image.RGB_Pixel
|
||||
RGBA_Pixel :: image.RGBA_Pixel
|
||||
|
||||
save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if img == nil {
|
||||
return .Invalid_Input_Image
|
||||
}
|
||||
|
||||
if output == nil {
|
||||
return .Invalid_Output
|
||||
}
|
||||
|
||||
pixels := img.width * img.height
|
||||
if pixels == 0 || pixels > image.MAX_DIMENSIONS || img.width > 65535 || img.height > 65535 {
|
||||
return .Invalid_Input_Image
|
||||
}
|
||||
|
||||
// Our TGA writer supports only 8-bit images with 3 or 4 channels.
|
||||
if img.depth != 8 || img.channels < 3 || img.channels > 4 {
|
||||
return .Invalid_Input_Image
|
||||
}
|
||||
|
||||
if img.channels * pixels != len(img.pixels.buf) {
|
||||
return .Invalid_Input_Image
|
||||
}
|
||||
|
||||
written := 0
|
||||
|
||||
// Calculate and allocate necessary space.
|
||||
necessary := pixels * img.channels + size_of(image.TGA_Header)
|
||||
|
||||
if !resize(&output.buf, necessary) {
|
||||
return .Unable_To_Allocate_Or_Resize
|
||||
}
|
||||
|
||||
header := image.TGA_Header{
|
||||
data_type_code = .Uncompressed_RGB,
|
||||
dimensions = {u16le(img.width), u16le(img.height)},
|
||||
bits_per_pixel = u8(img.depth * img.channels),
|
||||
image_descriptor = 1 << 5, // Origin is top left.
|
||||
}
|
||||
header_bytes := transmute([size_of(image.TGA_Header)]u8)header
|
||||
|
||||
copy(output.buf[written:], header_bytes[:])
|
||||
written += size_of(image.TGA_Header)
|
||||
|
||||
/*
|
||||
Encode loop starts here.
|
||||
*/
|
||||
if img.channels == 3 {
|
||||
pix := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
|
||||
out := mem.slice_data_cast([]RGB_Pixel, output.buf[written:])
|
||||
for p, i in pix {
|
||||
out[i] = p.bgr
|
||||
}
|
||||
} else if img.channels == 4 {
|
||||
pix := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:])
|
||||
out := mem.slice_data_cast([]RGBA_Pixel, output.buf[written:])
|
||||
for p, i in pix {
|
||||
out[i] = p.bgra
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
defer bytes.buffer_destroy(out)
|
||||
|
||||
save_to_memory(out, img, options) or_return
|
||||
write_ok := os.write_entire_file(output, out.buf[:])
|
||||
|
||||
return nil if write_ok else .Unable_To_Write_File
|
||||
}
|
||||
|
||||
save :: proc{save_to_memory, save_to_file}
|
||||
|
||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
options := options
|
||||
|
||||
if .alpha_premultiply in options {
|
||||
return nil, .Unsupported_Option
|
||||
}
|
||||
|
||||
if .info in options {
|
||||
options |= {.return_metadata, .do_not_decompress_image}
|
||||
options -= {.info}
|
||||
}
|
||||
|
||||
if .return_header in options && .return_metadata in options {
|
||||
options -= {.return_header}
|
||||
}
|
||||
|
||||
// First check for a footer.
|
||||
filesize := compress.input_size(ctx) or_return
|
||||
|
||||
footer: image.TGA_Footer
|
||||
have_valid_footer := false
|
||||
|
||||
extension: image.TGA_Extension
|
||||
have_valid_extension := false
|
||||
|
||||
if filesize >= size_of(image.TGA_Header) + size_of(image.TGA_Footer) {
|
||||
if f, f_err := compress.peek_data(ctx, image.TGA_Footer, filesize - i64(size_of(image.TGA_Footer))); f_err == .None {
|
||||
if string(f.signature[:]) == image.New_TGA_Signature {
|
||||
have_valid_footer = true
|
||||
footer = f
|
||||
|
||||
if i64(footer.extension_area_offset) + i64(size_of(image.TGA_Extension)) < filesize {
|
||||
if e, e_err := compress.peek_data(ctx, image.TGA_Extension, footer.extension_area_offset); e_err == .None {
|
||||
if e.extension_size == size_of(image.TGA_Extension) {
|
||||
have_valid_extension = true
|
||||
extension = e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header := image.read_data(ctx, image.TGA_Header) or_return
|
||||
|
||||
// Header checks
|
||||
rle_encoding := false
|
||||
color_mapped := false
|
||||
black_white := false
|
||||
src_channels := 0
|
||||
dest_depth := header.bits_per_pixel
|
||||
dest_channels := 0
|
||||
|
||||
#partial switch header.data_type_code {
|
||||
// Supported formats: RGB(A), RGB(A) RLE
|
||||
case .Compressed_RGB:
|
||||
rle_encoding = true
|
||||
case .Uncompressed_RGB:
|
||||
// Intentionally blank
|
||||
case .Uncompressed_Black_White:
|
||||
black_white = true
|
||||
dest_depth = 24
|
||||
case .Uncompressed_Color_Mapped:
|
||||
color_mapped = true
|
||||
case .Compressed_Color_Mapped:
|
||||
color_mapped = true
|
||||
rle_encoding = true
|
||||
case .Compressed_Black_White:
|
||||
black_white = true
|
||||
rle_encoding = true
|
||||
dest_depth = 24
|
||||
|
||||
case:
|
||||
return nil, .Unsupported_Format
|
||||
}
|
||||
|
||||
if color_mapped {
|
||||
if header.color_map_type != 1 {
|
||||
return nil, .Unsupported_Format
|
||||
}
|
||||
dest_depth = header.color_map_depth
|
||||
|
||||
// Expect LUT entry index to be 8 bits
|
||||
if header.bits_per_pixel != 8 || header.color_map_origin != 0 || header.color_map_length > 256 {
|
||||
return nil, .Unsupported_Format
|
||||
}
|
||||
}
|
||||
|
||||
switch dest_depth {
|
||||
case 15: // B5G5R5
|
||||
src_channels = 2
|
||||
dest_channels = 3
|
||||
if color_mapped {
|
||||
src_channels = 1
|
||||
}
|
||||
case 16: // B5G5R5A1
|
||||
src_channels = 2
|
||||
dest_channels = 3 // Alpha bit is dodgy in TGA, so we ignore it.
|
||||
if color_mapped {
|
||||
src_channels = 1
|
||||
}
|
||||
case 24: // RGB8
|
||||
src_channels = 1 if (color_mapped || black_white) else 3
|
||||
dest_channels = 3
|
||||
case 32: // RGBA8
|
||||
src_channels = 4 if !color_mapped else 1
|
||||
dest_channels = 4
|
||||
|
||||
case:
|
||||
return nil, .Unsupported_Format
|
||||
}
|
||||
|
||||
if header.image_descriptor & IMAGE_DESCRIPTOR_INTERLEAVING_MASK != 0 {
|
||||
return nil, .Unsupported_Format
|
||||
}
|
||||
|
||||
if int(header.dimensions[0]) * int(header.dimensions[1]) > image.MAX_DIMENSIONS {
|
||||
return nil, .Image_Dimensions_Too_Large
|
||||
}
|
||||
|
||||
if img == nil {
|
||||
img = new(Image)
|
||||
}
|
||||
|
||||
defer if err != nil {
|
||||
destroy(img)
|
||||
}
|
||||
|
||||
img.which = .TGA
|
||||
img.channels = 4 if .alpha_add_if_missing in options else dest_channels
|
||||
img.channels = 3 if .alpha_drop_if_present in options else img.channels
|
||||
|
||||
img.depth = 8
|
||||
img.width = int(header.dimensions[0])
|
||||
img.height = int(header.dimensions[1])
|
||||
|
||||
// Read Image ID if present
|
||||
image_id := ""
|
||||
if _id, e := compress.read_slice(ctx, int(header.id_length)); e != .None {
|
||||
return img, .Corrupt
|
||||
} else {
|
||||
if .return_metadata in options {
|
||||
id := strings.trim_right_null(string(_id))
|
||||
image_id = strings.clone(id)
|
||||
}
|
||||
}
|
||||
|
||||
color_map := make([]RGBA_Pixel, header.color_map_length)
|
||||
defer delete(color_map)
|
||||
|
||||
if color_mapped {
|
||||
switch header.color_map_depth {
|
||||
case 16:
|
||||
for i in 0..<header.color_map_length {
|
||||
if lut, lut_err := compress.read_data(ctx, GA_Pixel); lut_err != .None {
|
||||
return img, .Corrupt
|
||||
} else {
|
||||
color_map[i].rg = lut
|
||||
color_map[i].ba = 255
|
||||
}
|
||||
}
|
||||
|
||||
case 24:
|
||||
for i in 0..<header.color_map_length {
|
||||
if lut, lut_err := compress.read_data(ctx, RGB_Pixel); lut_err != .None {
|
||||
return img, .Corrupt
|
||||
} else {
|
||||
color_map[i].rgb = lut
|
||||
color_map[i].a = 255
|
||||
}
|
||||
}
|
||||
|
||||
case 32:
|
||||
for i in 0..<header.color_map_length {
|
||||
if lut, lut_err := compress.read_data(ctx, RGBA_Pixel); lut_err != .None {
|
||||
return img, .Corrupt
|
||||
} else {
|
||||
color_map[i] = lut
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if .return_metadata in options {
|
||||
info := new(image.TGA_Info)
|
||||
info.header = header
|
||||
info.image_id = image_id
|
||||
if have_valid_footer {
|
||||
info.footer = footer
|
||||
}
|
||||
if have_valid_extension {
|
||||
info.extension = extension
|
||||
}
|
||||
img.metadata = info
|
||||
}
|
||||
|
||||
if .do_not_decompress_image in options {
|
||||
return img, nil
|
||||
}
|
||||
|
||||
if !resize(&img.pixels.buf, dest_channels * img.width * img.height) {
|
||||
return img, .Unable_To_Allocate_Or_Resize
|
||||
}
|
||||
|
||||
origin_is_top := header.image_descriptor & IMAGE_DESCRIPTOR_TOP_MASK != 0
|
||||
origin_is_left := header.image_descriptor & IMAGE_DESCRIPTOR_RIGHT_MASK == 0
|
||||
rle_repetition_count := 0
|
||||
read_pixel := true
|
||||
is_packet_rle := false
|
||||
|
||||
pixel: RGBA_Pixel
|
||||
|
||||
stride := img.width * dest_channels
|
||||
line := 0 if origin_is_top else img.height - 1
|
||||
|
||||
for _ in 0..<img.height {
|
||||
offset := line * stride + (0 if origin_is_left else (stride - dest_channels))
|
||||
for _ in 0..<img.width {
|
||||
// handle RLE decoding
|
||||
if rle_encoding {
|
||||
if rle_repetition_count == 0 {
|
||||
rle_cmd, err := compress.read_u8(ctx)
|
||||
if err != .None {
|
||||
return img, .Corrupt
|
||||
}
|
||||
is_packet_rle = (rle_cmd >> 7) != 0
|
||||
rle_repetition_count = 1 + int(rle_cmd & 0x7F)
|
||||
read_pixel = true
|
||||
} else if !is_packet_rle {
|
||||
read_pixel = rle_repetition_count > 0
|
||||
} else {
|
||||
read_pixel = false
|
||||
}
|
||||
}
|
||||
// Read pixel
|
||||
if read_pixel {
|
||||
src, src_err := compress.read_slice(ctx, src_channels)
|
||||
if src_err != .None {
|
||||
return img, .Corrupt
|
||||
}
|
||||
switch src_channels {
|
||||
case 1:
|
||||
// Color-mapped or Black & White
|
||||
if black_white {
|
||||
pixel = {src[0], src[0], src[0], 255}
|
||||
} else if header.color_map_depth == 24 {
|
||||
pixel = color_map[src[0]].bgra
|
||||
} else if header.color_map_depth == 16 {
|
||||
lut := color_map[src[0]]
|
||||
v := u16(lut.r) | u16(lut.g) << 8
|
||||
b := u8( v & 31) << 3
|
||||
g := u8((v >> 5) & 31) << 3
|
||||
r := u8((v >> 10) & 31) << 3
|
||||
pixel = {r, g, b, 255}
|
||||
}
|
||||
|
||||
case 2:
|
||||
v := u16(src[0]) | u16(src[1]) << 8
|
||||
b := u8( v & 31) << 3
|
||||
g := u8((v >> 5) & 31) << 3
|
||||
r := u8((v >> 10) & 31) << 3
|
||||
pixel = {r, g, b, 255}
|
||||
|
||||
case 3:
|
||||
pixel = {src[2], src[1], src[0], 255}
|
||||
case 4:
|
||||
pixel = {src[2], src[1], src[0], src[3]}
|
||||
case:
|
||||
return img, .Corrupt
|
||||
}
|
||||
}
|
||||
|
||||
// Write pixel
|
||||
copy(img.pixels.buf[offset:], pixel[:dest_channels])
|
||||
offset += dest_channels if origin_is_left else -dest_channels
|
||||
rle_repetition_count -= 1
|
||||
}
|
||||
line += 1 if origin_is_top else -1
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
ctx := &compress.Context_Memory_Input{
|
||||
input_data = data,
|
||||
}
|
||||
|
||||
img, err = load_from_context(ctx, options, allocator)
|
||||
return img, err
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
destroy :: proc(img: ^Image) {
|
||||
if img == nil || img.width == 0 || img.height == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
bytes.buffer_destroy(&img.pixels)
|
||||
if v, ok := img.metadata.(^image.TGA_Info); ok {
|
||||
delete(v.image_id)
|
||||
free(v)
|
||||
}
|
||||
|
||||
// Make destroy idempotent
|
||||
img.width = 0
|
||||
img.height = 0
|
||||
free(img)
|
||||
}
|
||||
|
||||
IMAGE_DESCRIPTOR_INTERLEAVING_MASK :: (1<<6) | (1<<7)
|
||||
IMAGE_DESCRIPTOR_RIGHT_MASK :: 1<<4
|
||||
IMAGE_DESCRIPTOR_TOP_MASK :: 1<<5
|
||||
|
||||
@(init, private)
|
||||
_register :: proc() {
|
||||
image.register(.TGA, load_from_bytes, destroy)
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package image
|
||||
|
||||
import "core:os"
|
||||
|
||||
Which_File_Type :: enum {
|
||||
Unknown,
|
||||
|
||||
BMP,
|
||||
DjVu, // AT&T DjVu file format
|
||||
EXR,
|
||||
FLIF,
|
||||
GIF,
|
||||
HDR, // Radiance RGBE HDR
|
||||
ICNS, // Apple Icon Image
|
||||
JPEG,
|
||||
JPEG_2000,
|
||||
JPEG_XL,
|
||||
NetPBM, // NetPBM family
|
||||
PIC, // Softimage PIC
|
||||
PNG, // Portable Network Graphics
|
||||
PSD, // Photoshop PSD
|
||||
QOI, // Quite Okay Image
|
||||
SGI_RGB, // Silicon Graphics Image RGB file format
|
||||
Sun_Rast, // Sun Raster Graphic
|
||||
TGA, // Targa Truevision
|
||||
TIFF, // Tagged Image File Format
|
||||
WebP,
|
||||
XBM, // X BitMap
|
||||
}
|
||||
|
||||
which :: proc{
|
||||
which_bytes,
|
||||
which_file,
|
||||
}
|
||||
|
||||
which_bytes :: proc(data: []byte) -> Which_File_Type {
|
||||
test_tga :: proc(s: string) -> bool {
|
||||
get8 :: #force_inline proc(s: ^string) -> u8 {
|
||||
v := s[0]
|
||||
s^ = s[1:]
|
||||
return v
|
||||
}
|
||||
get16le :: #force_inline proc(s: ^string) -> u16 {
|
||||
v := u16(s[0]) | u16(s[1])<<16
|
||||
s^ = s[2:]
|
||||
return v
|
||||
}
|
||||
s := s
|
||||
s = s[1:] // skip offset
|
||||
|
||||
color_type := get8(&s)
|
||||
if color_type > 1 {
|
||||
return false
|
||||
}
|
||||
image_type := get8(&s) // image type
|
||||
if color_type == 1 { // Colormap (Paletted) Image
|
||||
if image_type != 1 && image_type != 9 { // color type requires 1 or 9
|
||||
return false
|
||||
}
|
||||
s = s[4:] // skip index of first colormap
|
||||
bpcme := get8(&s) // check bits per colormap entry
|
||||
if bpcme != 8 && bpcme != 15 && bpcme != 16 && bpcme != 24 && bpcme != 32 {
|
||||
return false
|
||||
}
|
||||
s = s[4:] // skip image origin (x, y)
|
||||
} else { // Normal image without colormap
|
||||
if image_type != 2 && image_type != 3 && image_type != 10 && image_type != 11 {
|
||||
return false
|
||||
}
|
||||
s = s[9:] // skip colormap specification
|
||||
}
|
||||
if get16le(&s) < 1 || get16le(&s) < 1 { // test width and height
|
||||
return false
|
||||
}
|
||||
bpp := get8(&s) // bits per pixel
|
||||
if color_type == 1 && bpp != 8 && bpp != 16 {
|
||||
return false
|
||||
}
|
||||
if bpp != 8 && bpp != 15 && bpp != 16 && bpp != 24 && bpp != 32 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
header: [128]byte
|
||||
copy(header[:], data)
|
||||
s := string(header[:])
|
||||
|
||||
switch {
|
||||
case s[:2] == "BM":
|
||||
return .BMP
|
||||
case s[:8] == "AT&TFORM":
|
||||
switch s[12:16] {
|
||||
case "DJVU", "DJVM":
|
||||
return .DjVu
|
||||
}
|
||||
case s[:4] == "\x76\x2f\x31\x01":
|
||||
return .EXR
|
||||
case s[:6] == "GIF87a", s[:6] == "GIF89a":
|
||||
return .GIF
|
||||
case s[6:10] == "JFIF", s[6:10] == "Exif":
|
||||
return .JPEG
|
||||
case s[:3] == "\xff\xd8\xff":
|
||||
switch s[4] {
|
||||
case 0xdb, 0xee, 0xe1, 0xe0:
|
||||
return .JPEG
|
||||
}
|
||||
switch {
|
||||
case s[:12] == "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01":
|
||||
return .JPEG
|
||||
}
|
||||
case s[:4] == "\xff\x4f\xff\x51", s[:12] == "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a":
|
||||
return .JPEG_2000
|
||||
case s[:12] == "\x00\x00\x00\x0c\x4a\x58\x4c\x20\x0d\x0a\x87\x0a":
|
||||
return .JPEG_XL
|
||||
case s[0] == 'P':
|
||||
switch s[2] {
|
||||
case '\t', '\n', '\r':
|
||||
switch s[1] {
|
||||
case '1', '4': // PBM
|
||||
return .NetPBM
|
||||
case '2', '5': // PGM
|
||||
return .NetPBM
|
||||
case '3', '6': // PPM
|
||||
return .NetPBM
|
||||
case '7': // PAM
|
||||
return .NetPBM
|
||||
case 'F', 'f': // PFM
|
||||
return .NetPBM
|
||||
}
|
||||
}
|
||||
case s[:8] == "\x89PNG\r\n\x1a\n":
|
||||
return .PNG
|
||||
case s[:4] == "qoif":
|
||||
return .QOI
|
||||
case s[:2] == "\x01\xda":
|
||||
return .SGI_RGB
|
||||
case s[:4] == "\x59\xA6\x6A\x95":
|
||||
return .Sun_Rast
|
||||
case s[:4] == "MM\x2a\x00", s[:4] == "II\x00\x2A":
|
||||
return .TIFF
|
||||
case s[:4] == "RIFF" && s[8:12] == "WEBP":
|
||||
return .WebP
|
||||
case s[:8] == "#define ":
|
||||
return .XBM
|
||||
|
||||
case s[:11] == "#?RADIANCE\n", s[:7] == "#?RGBE\n":
|
||||
return .HDR
|
||||
case s[:4] == "\x38\x42\x50\x53":
|
||||
return .PSD
|
||||
case s[:4] != "\x53\x80\xF6\x34" && s[88:92] == "PICT":
|
||||
return .PIC
|
||||
case s[:4] == "\x69\x63\x6e\x73":
|
||||
return .ICNS
|
||||
case s[:4] == "\x46\x4c\x49\x46":
|
||||
return .FLIF
|
||||
case:
|
||||
// More complex formats
|
||||
if test_tga(s) {
|
||||
return .TGA
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return .Unknown
|
||||
}
|
||||
|
||||
|
||||
which_file :: proc(path: string) -> Which_File_Type {
|
||||
f, err := os.open(path)
|
||||
if err != 0 {
|
||||
return .Unknown
|
||||
}
|
||||
header: [128]byte
|
||||
os.read(f, header[:])
|
||||
file_type := which_bytes(header[:])
|
||||
os.close(f)
|
||||
return file_type
|
||||
}
|
||||
+185
-86
@@ -6,12 +6,14 @@ package intrinsics
|
||||
is_package_imported :: proc(package_name: string) -> bool ---
|
||||
|
||||
// Types
|
||||
simd_vector :: proc($N: int, $T: typeid) -> type/#simd[N]T
|
||||
soa_struct :: proc($N: int, $T: typeid) -> type/#soa[N]T
|
||||
|
||||
// Volatile
|
||||
volatile_load :: proc(dst: ^$T) -> T ---
|
||||
volatile_store :: proc(dst: ^$T, val: T) -> T ---
|
||||
volatile_store :: proc(dst: ^$T, val: T) ---
|
||||
|
||||
non_temporal_load :: proc(dst: ^$T) -> T ---
|
||||
non_temporal_store :: proc(dst: ^$T, val: T) ---
|
||||
|
||||
// Trapping
|
||||
debug_trap :: proc() ---
|
||||
@@ -23,24 +25,32 @@ alloca :: proc(size, align: int) -> [^]u8 ---
|
||||
cpu_relax :: proc() ---
|
||||
read_cycle_counter :: proc() -> i64 ---
|
||||
|
||||
count_ones :: proc(x: $T) -> T where type_is_integer(T) ---
|
||||
count_zeros :: proc(x: $T) -> T where type_is_integer(T) ---
|
||||
count_trailing_zeros :: proc(x: $T) -> T where type_is_integer(T) ---
|
||||
count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) ---
|
||||
reverse_bits :: proc(x: $T) -> T where type_is_integer(T) ---
|
||||
count_ones :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
|
||||
count_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
|
||||
count_trailing_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
|
||||
count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
|
||||
reverse_bits :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
|
||||
byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) ---
|
||||
|
||||
overflow_add :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
|
||||
overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
|
||||
overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
|
||||
|
||||
sqrt :: proc(x: $T) -> T where type_is_float(T) ---
|
||||
sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) ---
|
||||
|
||||
fused_mul_add :: proc(a, b, c: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) ---
|
||||
|
||||
mem_copy :: proc(dst, src: rawptr, len: int) ---
|
||||
mem_copy_non_overlapping :: proc(dst, src: rawptr, len: int) ---
|
||||
mem_zero :: proc(ptr: rawptr, len: int) ---
|
||||
mem_zero_volatile :: proc(ptr: rawptr, len: int) ---
|
||||
|
||||
// prefer [^]T operations if possible
|
||||
ptr_offset :: proc(ptr: ^$T, offset: int) -> ^T ---
|
||||
ptr_sub :: proc(a, b: ^$T) -> int ---
|
||||
|
||||
unaligned_load :: proc(src: ^$T) -> T ---
|
||||
unaligned_store :: proc(dst: ^$T, val: T) -> T ---
|
||||
|
||||
fixed_point_mul :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) ---
|
||||
fixed_point_div :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) ---
|
||||
@@ -60,77 +70,47 @@ syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr ---
|
||||
|
||||
|
||||
// Atomics
|
||||
atomic_fence :: proc() ---
|
||||
atomic_fence_acq :: proc() ---
|
||||
atomic_fence_rel :: proc() ---
|
||||
atomic_fence_acqrel :: proc() ---
|
||||
Atomic_Memory_Order :: enum {
|
||||
Relaxed = 0, // Unordered
|
||||
Consume = 1, // Monotonic
|
||||
Acquire = 2,
|
||||
Release = 3,
|
||||
Acq_Rel = 4,
|
||||
Seq_Cst = 5,
|
||||
}
|
||||
|
||||
atomic_store :: proc(dst: ^$T, val: T) ---
|
||||
atomic_store_rel :: proc(dst: ^$T, val: T) ---
|
||||
atomic_store_relaxed :: proc(dst: ^$T, val: T) ---
|
||||
atomic_store_unordered :: proc(dst: ^$T, val: T) ---
|
||||
atomic_type_is_lock_free :: proc($T: typeid) -> bool ---
|
||||
|
||||
atomic_thread_fence :: proc(order: Atomic_Memory_Order) ---
|
||||
atomic_signal_fence :: proc(order: Atomic_Memory_Order) ---
|
||||
|
||||
atomic_store :: proc(dst: ^$T, val: T) ---
|
||||
atomic_store_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) ---
|
||||
|
||||
atomic_load :: proc(dst: ^$T) -> T ---
|
||||
atomic_load_acq :: proc(dst: ^$T) -> T ---
|
||||
atomic_load_relaxed :: proc(dst: ^$T) -> T ---
|
||||
atomic_load_unordered :: proc(dst: ^$T) -> T ---
|
||||
atomic_load_explicit :: proc(dst: ^$T, order: Atomic_Memory_Order) -> T ---
|
||||
|
||||
atomic_add :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_add_acq :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_add_rel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_add_acqrel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_add_relaxed :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_sub :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_sub_acq :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_sub_rel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_sub_acqrel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_sub_relaxed :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_and :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_and_acq :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_and_rel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_and_acqrel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_and_relaxed :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_nand :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_nand_acq :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_nand_rel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_nand_acqrel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_nand_relaxed :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_or :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_or_acq :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_or_rel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_or_acqrel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_or_relaxed :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_xor :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_xor_acq :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_xor_rel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_xor_acqrel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_xor_relaxed :: proc(dst; ^$T, val: T) -> T ---
|
||||
// fetch then operator
|
||||
atomic_add :: proc(dst: ^$T, val: T) -> T ---
|
||||
atomic_add_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
|
||||
atomic_sub :: proc(dst: ^$T, val: T) -> T ---
|
||||
atomic_sub_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
|
||||
atomic_and :: proc(dst: ^$T, val: T) -> T ---
|
||||
atomic_and_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
|
||||
atomic_nand :: proc(dst: ^$T, val: T) -> T ---
|
||||
atomic_nand_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
|
||||
atomic_or :: proc(dst: ^$T, val: T) -> T ---
|
||||
atomic_or_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
|
||||
atomic_xor :: proc(dst: ^$T, val: T) -> T ---
|
||||
atomic_xor_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
|
||||
atomic_exchange :: proc(dst: ^$T, val: T) -> T ---
|
||||
atomic_exchange_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
|
||||
|
||||
atomic_xchg :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_xchg_acq :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_xchg_rel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_xchg_acqrel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_xchg_relaxed :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_compare_exchange_strong :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_compare_exchange_strong_explicit :: proc(dst: ^$T, old, new: T, success, failure: Atomic_Memory_Order) -> (T, bool) #optional_ok ---
|
||||
atomic_compare_exchange_weak :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_compare_exchange_weak_explicit :: proc(dst: ^$T, old, new: T, success, failure: Atomic_Memory_Order) -> (T, bool) #optional_ok ---
|
||||
|
||||
atomic_cxchg :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_acq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_rel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_acqrel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_relaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_failacq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_acq_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_acqrel_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
|
||||
atomic_cxchgweak :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_acq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_rel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_acqrel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_relaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_failacq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_acq_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_acqrel_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
|
||||
// Constant type tests
|
||||
|
||||
@@ -148,22 +128,24 @@ type_is_string :: proc($T: typeid) -> bool ---
|
||||
type_is_typeid :: proc($T: typeid) -> bool ---
|
||||
type_is_any :: proc($T: typeid) -> bool ---
|
||||
|
||||
type_is_endian_platform :: proc($T: typeid) -> bool ---
|
||||
type_is_endian_little :: proc($T: typeid) -> bool ---
|
||||
type_is_endian_big :: proc($T: typeid) -> bool ---
|
||||
type_is_unsigned :: proc($T: typeid) -> bool ---
|
||||
type_is_numeric :: proc($T: typeid) -> bool ---
|
||||
type_is_ordered :: proc($T: typeid) -> bool ---
|
||||
type_is_ordered_numeric :: proc($T: typeid) -> bool ---
|
||||
type_is_indexable :: proc($T: typeid) -> bool ---
|
||||
type_is_sliceable :: proc($T: typeid) -> bool ---
|
||||
type_is_comparable :: proc($T: typeid) -> bool ---
|
||||
type_is_simple_compare :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=)
|
||||
type_is_dereferenceable :: proc($T: typeid) -> bool ---
|
||||
type_is_valid_map_key :: proc($T: typeid) -> bool ---
|
||||
type_is_endian_platform :: proc($T: typeid) -> bool ---
|
||||
type_is_endian_little :: proc($T: typeid) -> bool ---
|
||||
type_is_endian_big :: proc($T: typeid) -> bool ---
|
||||
type_is_unsigned :: proc($T: typeid) -> bool ---
|
||||
type_is_numeric :: proc($T: typeid) -> bool ---
|
||||
type_is_ordered :: proc($T: typeid) -> bool ---
|
||||
type_is_ordered_numeric :: proc($T: typeid) -> bool ---
|
||||
type_is_indexable :: proc($T: typeid) -> bool ---
|
||||
type_is_sliceable :: proc($T: typeid) -> bool ---
|
||||
type_is_comparable :: proc($T: typeid) -> bool ---
|
||||
type_is_simple_compare :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=)
|
||||
type_is_dereferenceable :: proc($T: typeid) -> bool ---
|
||||
type_is_valid_map_key :: proc($T: typeid) -> bool ---
|
||||
type_is_valid_matrix_elements :: proc($T: typeid) -> bool ---
|
||||
|
||||
type_is_named :: proc($T: typeid) -> bool ---
|
||||
type_is_pointer :: proc($T: typeid) -> bool ---
|
||||
type_is_multi_pointer :: proc($T: typeid) -> bool ---
|
||||
type_is_array :: proc($T: typeid) -> bool ---
|
||||
type_is_enumerated_array :: proc($T: typeid) -> bool ---
|
||||
type_is_slice :: proc($T: typeid) -> bool ---
|
||||
@@ -175,6 +157,7 @@ type_is_enum :: proc($T: typeid) -> bool ---
|
||||
type_is_proc :: proc($T: typeid) -> bool ---
|
||||
type_is_bit_set :: proc($T: typeid) -> bool ---
|
||||
type_is_simd_vector :: proc($T: typeid) -> bool ---
|
||||
type_is_matrix :: proc($T: typeid) -> bool ---
|
||||
|
||||
type_has_nil :: proc($T: typeid) -> bool ---
|
||||
|
||||
@@ -182,6 +165,7 @@ type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
|
||||
type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
|
||||
|
||||
type_has_field :: proc($T: typeid, $name: string) -> bool ---
|
||||
type_field_type :: proc($T: typeid, $name: string) -> typeid ---
|
||||
|
||||
type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
|
||||
type_proc_return_count :: proc($T: typeid) -> int where type_is_proc(T) ---
|
||||
@@ -189,15 +173,130 @@ 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_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_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid ---
|
||||
type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V ---
|
||||
|
||||
type_is_specialized_polymorphic_record :: proc($T: typeid) -> bool ---
|
||||
type_is_unspecialized_polymorphic_record :: proc($T: typeid) -> bool ---
|
||||
|
||||
type_is_subtype_of :: proc($T, $U: typeid) -> bool ---
|
||||
|
||||
type_field_index_of :: proc($T: typeid, $name: string) -> uintptr ---
|
||||
|
||||
type_equal_proc :: proc($T: typeid) -> (equal: proc "contextless" (rawptr, rawptr) -> bool) where type_is_comparable(T) ---
|
||||
type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) ---
|
||||
|
||||
constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
|
||||
|
||||
// SIMD related
|
||||
simd_add :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
simd_sub :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
simd_mul :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
simd_div :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_float(T) ---
|
||||
|
||||
// Keeps Odin's Behaviour
|
||||
// (x << y) if y <= mask else 0
|
||||
simd_shl :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
|
||||
simd_shr :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
|
||||
|
||||
// Similar to C's Behaviour
|
||||
// x << (y & mask)
|
||||
simd_shl_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
|
||||
simd_shr_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
|
||||
|
||||
simd_add_sat :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
simd_sub_sat :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
|
||||
simd_and :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
simd_or :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
simd_xor :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
simd_and_not :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
|
||||
simd_neg :: proc(a: #simd[N]T) -> #simd[N]T ---
|
||||
|
||||
simd_abs :: proc(a: #simd[N]T) -> #simd[N]T ---
|
||||
|
||||
simd_min :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
simd_max :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
simd_clamp :: proc(v, min, max: #simd[N]T) -> #simd[N]T ---
|
||||
|
||||
// Return an unsigned integer of the same size as the input type
|
||||
// NOT A BOOLEAN
|
||||
// element-wise:
|
||||
// false => 0x00...00
|
||||
// true => 0xff...ff
|
||||
simd_lanes_eq :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
|
||||
simd_lanes_ne :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
|
||||
simd_lanes_lt :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
|
||||
simd_lanes_le :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
|
||||
simd_lanes_gt :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
|
||||
simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
|
||||
|
||||
simd_extract :: proc(a: #simd[N]T, idx: uint) -> T ---
|
||||
simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T ---
|
||||
|
||||
simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T ---
|
||||
simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T ---
|
||||
simd_reduce_min :: proc(a: #simd[N]T) -> T ---
|
||||
simd_reduce_max :: proc(a: #simd[N]T) -> T ---
|
||||
simd_reduce_and :: proc(a: #simd[N]T) -> T ---
|
||||
simd_reduce_or :: proc(a: #simd[N]T) -> T ---
|
||||
simd_reduce_xor :: proc(a: #simd[N]T) -> T ---
|
||||
|
||||
simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T ---
|
||||
simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T ---
|
||||
|
||||
// Lane-wise operations
|
||||
simd_ceil :: proc(a: #simd[N]any_float) -> #simd[N]any_float ---
|
||||
simd_floor :: proc(a: #simd[N]any_float) -> #simd[N]any_float ---
|
||||
simd_trunc :: proc(a: #simd[N]any_float) -> #simd[N]any_float ---
|
||||
// rounding to the nearest integral value; if two values are equally near, rounds to the even one
|
||||
simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float ---
|
||||
|
||||
simd_to_bits :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), type_is_unsigned(Integer) ---
|
||||
|
||||
// equivalent a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0)
|
||||
simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
|
||||
|
||||
simd_rotate_left :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
|
||||
simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
|
||||
|
||||
|
||||
// WASM targets only
|
||||
wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
|
||||
wasm_memory_size :: proc(index: uintptr) -> int ---
|
||||
|
||||
// `timeout_ns` is maximum number of nanoseconds the calling thread will be blocked for
|
||||
// A negative value will be blocked forever
|
||||
// Return value:
|
||||
// 0 - indicates that the thread blocked and then was woken up
|
||||
// 1 - the loaded value from `ptr` did not match `expected`, the thread did not block
|
||||
// 2 - the thread blocked, but the timeout
|
||||
wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -> u32 ---
|
||||
wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) ---
|
||||
|
||||
// x86 Targets (i386, amd64)
|
||||
x86_cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) ---
|
||||
x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
|
||||
|
||||
|
||||
// Darwin targets only
|
||||
objc_object :: struct{}
|
||||
objc_selector :: struct{}
|
||||
objc_class :: struct{}
|
||||
objc_id :: ^objc_object
|
||||
objc_SEL :: ^objc_selector
|
||||
objc_Class :: ^objc_class
|
||||
|
||||
objc_find_selector :: proc($name: string) -> objc_SEL ---
|
||||
objc_register_selector :: proc($name: string) -> objc_SEL ---
|
||||
objc_find_class :: proc($name: string) -> objc_Class ---
|
||||
objc_register_class :: proc($name: string) -> objc_Class ---
|
||||
|
||||
|
||||
valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
|
||||
|
||||
// Internal compiler use only
|
||||
|
||||
|
||||
+2
-7
@@ -4,7 +4,6 @@
|
||||
package io
|
||||
|
||||
import "core:intrinsics"
|
||||
import "core:runtime"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
// Seek whence values
|
||||
@@ -254,11 +253,7 @@ read_at :: proc(r: Reader_At, p: []byte, offset: i64, n_read: ^int = nil) -> (n:
|
||||
return 0, .Empty
|
||||
}
|
||||
|
||||
curr_offset: i64
|
||||
curr_offset, err = r->impl_seek(offset, .Current)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
curr_offset := r->impl_seek(offset, .Current) or_return
|
||||
|
||||
n, err = r->impl_read(p)
|
||||
_, err1 := r->impl_seek(curr_offset, .Start)
|
||||
@@ -552,7 +547,7 @@ _copy_buffer :: proc(dst: Writer, src: Reader, buf: []byte) -> (written: i64, er
|
||||
}
|
||||
}
|
||||
// NOTE(bill): alloca is fine here
|
||||
buf = transmute([]byte)runtime.Raw_Slice{intrinsics.alloca(size, 2*align_of(rawptr)), size}
|
||||
buf = intrinsics.alloca(size, 2*align_of(rawptr))[:size]
|
||||
}
|
||||
for {
|
||||
nr, er := read(src, buf)
|
||||
|
||||
@@ -247,6 +247,30 @@ write_quoted_string :: proc(w: Writer, str: string, quote: byte = '"', n_written
|
||||
return
|
||||
}
|
||||
|
||||
// writer append a quoted rune into the byte buffer, return the written size
|
||||
write_quoted_rune :: proc(w: Writer, r: rune) -> (n: int) {
|
||||
_write_byte :: #force_inline proc(w: Writer, c: byte) -> int {
|
||||
err := write_byte(w, c)
|
||||
return 1 if err == nil else 0
|
||||
}
|
||||
|
||||
quote := byte('\'')
|
||||
n += _write_byte(w, quote)
|
||||
buf, width := utf8.encode_rune(r)
|
||||
if width == 1 && r == utf8.RUNE_ERROR {
|
||||
n += _write_byte(w, '\\')
|
||||
n += _write_byte(w, 'x')
|
||||
n += _write_byte(w, DIGITS_LOWER[buf[0]>>4])
|
||||
n += _write_byte(w, DIGITS_LOWER[buf[0]&0xf])
|
||||
} else {
|
||||
i, _ := write_escaped_rune(w, r, quote)
|
||||
n += i
|
||||
}
|
||||
n += _write_byte(w, quote)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Tee_Reader :: struct {
|
||||
|
||||
@@ -56,7 +56,7 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
|
||||
return Logger{file_console_logger_proc, data, lowest, opt}
|
||||
}
|
||||
|
||||
destroy_console_logger :: proc(log: ^Logger) {
|
||||
destroy_console_logger :: proc(log: Logger) {
|
||||
free(log.data)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
|
||||
h = data.file_handle
|
||||
}
|
||||
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
|
||||
buf := strings.builder_from_slice(backing[:])
|
||||
buf := strings.builder_from_bytes(backing[:])
|
||||
|
||||
do_level_header(options, level, &buf)
|
||||
|
||||
@@ -95,7 +95,7 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
|
||||
fmt.sbprintf(&buf, "[%s] ", data.ident)
|
||||
}
|
||||
//TODO(Hoej): When we have better atomics and such, make this thread-safe
|
||||
fmt.fprintf(h, "%s %s\n", strings.to_string(buf), text)
|
||||
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
|
||||
}
|
||||
|
||||
do_level_header :: proc(opts: Options, level: Level, str: ^strings.Builder) {
|
||||
|
||||
+5
-6
@@ -6,7 +6,6 @@ import "core:fmt"
|
||||
|
||||
// NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle.
|
||||
|
||||
Level :: runtime.Logger_Level
|
||||
/*
|
||||
Logger_Level :: enum {
|
||||
Debug = 0,
|
||||
@@ -16,8 +15,8 @@ Logger_Level :: enum {
|
||||
Fatal = 40,
|
||||
}
|
||||
*/
|
||||
Level :: runtime.Logger_Level
|
||||
|
||||
Option :: runtime.Logger_Option
|
||||
/*
|
||||
Option :: enum {
|
||||
Level,
|
||||
@@ -30,11 +29,12 @@ Option :: enum {
|
||||
Terminal_Color
|
||||
}
|
||||
*/
|
||||
Option :: runtime.Logger_Option
|
||||
|
||||
Options :: runtime.Logger_Options
|
||||
/*
|
||||
Options :: bit_set[Option];
|
||||
*/
|
||||
Options :: runtime.Logger_Options
|
||||
|
||||
Full_Timestamp_Opts :: Options{
|
||||
.Date,
|
||||
@@ -52,12 +52,11 @@ Location_File_Opts :: Options{
|
||||
}
|
||||
|
||||
|
||||
Logger_Proc :: runtime.Logger_Proc
|
||||
/*
|
||||
Logger_Proc :: #type proc(data: rawptr, level: Level, text: string, options: Options, location := #caller_location);
|
||||
*/
|
||||
Logger_Proc :: runtime.Logger_Proc
|
||||
|
||||
Logger :: runtime.Logger
|
||||
/*
|
||||
Logger :: struct {
|
||||
procedure: Logger_Proc,
|
||||
@@ -66,6 +65,7 @@ Logger :: struct {
|
||||
options: Logger_Options,
|
||||
}
|
||||
*/
|
||||
Logger :: runtime.Logger
|
||||
|
||||
nil_logger_proc :: proc(data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
|
||||
// Do nothing
|
||||
@@ -75,7 +75,6 @@ nil_logger :: proc() -> Logger {
|
||||
return Logger{nil_logger_proc, nil, Level.Debug, nil}
|
||||
}
|
||||
|
||||
// TODO(bill): Should these be redesigned so that they are do not rely upon `package fmt`?
|
||||
debugf :: proc(fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logf(level=.Debug, fmt_str=fmt_str, args=args, location=location)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package log
|
||||
|
||||
import "core:runtime"
|
||||
|
||||
Log_Allocator :: struct {
|
||||
allocator: runtime.Allocator,
|
||||
level: Level,
|
||||
prefix: string,
|
||||
locked: bool,
|
||||
}
|
||||
|
||||
log_allocator_init :: proc(la: ^Log_Allocator, level: Level, allocator := context.allocator, prefix := "") {
|
||||
la.allocator = allocator
|
||||
la.level = level
|
||||
la.prefix = prefix
|
||||
la.locked = false
|
||||
}
|
||||
|
||||
|
||||
log_allocator :: proc(la: ^Log_Allocator) -> runtime.Allocator {
|
||||
return runtime.Allocator{
|
||||
procedure = log_allocator_proc,
|
||||
data = la,
|
||||
}
|
||||
}
|
||||
|
||||
log_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, runtime.Allocator_Error) {
|
||||
la := (^Log_Allocator)(allocator_data)
|
||||
|
||||
padding := " " if la.prefix != "" else ""
|
||||
|
||||
if !la.locked {
|
||||
la.locked = true
|
||||
defer la.locked = false
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
logf(
|
||||
level=la.level,
|
||||
fmt_str = "%s%s>>> ALLOCATOR(mode=.Alloc, size=%d, alignment=%d)",
|
||||
args = {la.prefix, padding, size, alignment},
|
||||
location = location,
|
||||
)
|
||||
case .Free:
|
||||
if old_size != 0 {
|
||||
logf(
|
||||
level=la.level,
|
||||
fmt_str = "%s%s<<< ALLOCATOR(mode=.Free, ptr=%p, size=%d)",
|
||||
args = {la.prefix, padding, old_memory, old_size},
|
||||
location = location,
|
||||
)
|
||||
} else {
|
||||
logf(
|
||||
level=la.level,
|
||||
fmt_str = "%s%s<<< ALLOCATOR(mode=.Free, ptr=%p)",
|
||||
args = {la.prefix, padding, old_memory},
|
||||
location = location,
|
||||
)
|
||||
}
|
||||
case .Free_All:
|
||||
logf(
|
||||
level=la.level,
|
||||
fmt_str = "%s%s<<< ALLOCATOR(mode=.Free_All)",
|
||||
args = {la.prefix, padding},
|
||||
location = location,
|
||||
)
|
||||
case .Resize:
|
||||
logf(
|
||||
level=la.level,
|
||||
fmt_str = "%s%s>>> ALLOCATOR(mode=.Resize, ptr=%p, old_size=%d, size=%d, alignment=%d)",
|
||||
args = {la.prefix, padding, old_memory, old_size, size, alignment},
|
||||
location = location,
|
||||
)
|
||||
case .Query_Features:
|
||||
logf(
|
||||
level=la.level,
|
||||
fmt_str = "%s%ALLOCATOR(mode=.Query_Features)",
|
||||
args = {la.prefix, padding},
|
||||
location = location,
|
||||
)
|
||||
case .Query_Info:
|
||||
logf(
|
||||
level=la.level,
|
||||
fmt_str = "%s%ALLOCATOR(mode=.Query_Info)",
|
||||
args = {la.prefix, padding},
|
||||
location = location,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data, err := la.allocator.procedure(la.allocator.data, mode, size, alignment, old_memory, old_size, location)
|
||||
if !la.locked {
|
||||
la.locked = true
|
||||
defer la.locked = false
|
||||
if err != nil {
|
||||
logf(
|
||||
level=la.level,
|
||||
fmt_str = "%s%ALLOCATOR ERROR=%v",
|
||||
args = {la.prefix, padding, error},
|
||||
location = location,
|
||||
)
|
||||
}
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
@@ -449,7 +449,7 @@ internal_int_is_prime :: proc(a: ^Int, miller_rabin_trials := int(-1), miller_ra
|
||||
in the loop is non-zero, although very low.
|
||||
-- NOTE(Jeroen): This is not yet true in Odin, but I have some ideas.
|
||||
|
||||
If the BPSW test and/or the addtional Frobenious test have been
|
||||
If the BPSW test and/or the additional Frobenious test have been
|
||||
performed instead of just the Miller-Rabin test with the bases 2 and 3,
|
||||
a single extra test should suffice, so such a very unlikely event will not do much harm.
|
||||
|
||||
|
||||
@@ -0,0 +1,495 @@
|
||||
// easing procedures and flux easing used for animations
|
||||
package ease
|
||||
|
||||
import "core:math"
|
||||
import "core:intrinsics"
|
||||
import "core:time"
|
||||
|
||||
@(private) PI_2 :: math.PI / 2
|
||||
|
||||
// converted to odin from https://github.com/warrenm/AHEasing
|
||||
// with additional enum based call
|
||||
|
||||
// Modeled after the parabola y = x^2
|
||||
quadratic_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return p * p
|
||||
}
|
||||
|
||||
// Modeled after the parabola y = -x^2 + 2x
|
||||
quadratic_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return -(p * (p - 2))
|
||||
}
|
||||
|
||||
// Modeled after the piecewise quadratic
|
||||
// y = (1/2)((2x)^2) ; [0, 0.5)
|
||||
// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
|
||||
quadratic_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
return 2 * p * p
|
||||
} else {
|
||||
return (-2 * p * p) + (4 * p) - 1
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the cubic y = x^3
|
||||
cubic_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return p * p * p
|
||||
}
|
||||
|
||||
// Modeled after the cubic y = (x - 1)^3 + 1
|
||||
cubic_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
f := p - 1
|
||||
return f * f * f + 1
|
||||
}
|
||||
|
||||
// Modeled after the piecewise cubic
|
||||
// y = (1/2)((2x)^3) ; [0, 0.5)
|
||||
// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
|
||||
cubic_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
return 4 * p * p * p
|
||||
} else {
|
||||
f := (2 * p) - 2
|
||||
return 0.5 * f * f * f + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the quartic x^4
|
||||
quartic_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return p * p * p * p
|
||||
}
|
||||
|
||||
// Modeled after the quartic y = 1 - (x - 1)^4
|
||||
quartic_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
f := p - 1
|
||||
return f * f * f * (1 - p) + 1
|
||||
}
|
||||
|
||||
// Modeled after the piecewise quartic
|
||||
// y = (1/2)((2x)^4) ; [0, 0.5)
|
||||
// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
|
||||
quartic_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
return 8 * p * p * p * p
|
||||
} else {
|
||||
f := p - 1
|
||||
return -8 * f * f * f * f + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the quintic y = x^5
|
||||
quintic_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return p * p * p * p * p
|
||||
}
|
||||
|
||||
// Modeled after the quintic y = (x - 1)^5 + 1
|
||||
quintic_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
f := p - 1
|
||||
return f * f * f * f * f + 1
|
||||
}
|
||||
|
||||
// Modeled after the piecewise quintic
|
||||
// y = (1/2)((2x)^5) ; [0, 0.5)
|
||||
// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
|
||||
quintic_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
return 16 * p * p * p * p * p
|
||||
} else {
|
||||
f := (2 * p) - 2
|
||||
return 0.5 * f * f * f * f * f + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after quarter-cycle of sine wave
|
||||
sine_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.sin((p - 1) * PI_2) + 1
|
||||
}
|
||||
|
||||
// Modeled after quarter-cycle of sine wave (different phase)
|
||||
sine_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.sin(p * PI_2)
|
||||
}
|
||||
|
||||
// Modeled after half sine wave
|
||||
sine_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return 0.5 * (1 - math.cos(p * math.PI))
|
||||
}
|
||||
|
||||
// Modeled after shifted quadrant IV of unit circle
|
||||
circular_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return 1 - math.sqrt(1 - (p * p))
|
||||
}
|
||||
|
||||
// Modeled after shifted quadrant II of unit circle
|
||||
circular_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.sqrt((2 - p) * p)
|
||||
}
|
||||
|
||||
// Modeled after the piecewise circular function
|
||||
// y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5)
|
||||
// y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
|
||||
circular_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
return 0.5 * (1 - math.sqrt(1 - 4 * (p * p)))
|
||||
} else {
|
||||
return 0.5 * (math.sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the exponential function y = 2^(10(x - 1))
|
||||
exponential_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return p == 0.0 ? p : math.pow(2, 10 * (p - 1))
|
||||
}
|
||||
|
||||
// Modeled after the exponential function y = -2^(-10x) + 1
|
||||
exponential_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return p == 1.0 ? p : 1 - math.pow(2, -10 * p)
|
||||
}
|
||||
|
||||
// Modeled after the piecewise exponential
|
||||
// y = (1/2)2^(10(2x - 1)) ; [0,0.5)
|
||||
// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
|
||||
exponential_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p == 0.0 || p == 1.0 {
|
||||
return p
|
||||
}
|
||||
|
||||
if p < 0.5 {
|
||||
return 0.5 * math.pow(2, (20 * p) - 10)
|
||||
} else {
|
||||
return -0.5 * math.pow(2, (-20 * p) + 10) + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1))
|
||||
elastic_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.sin(13 * PI_2 * p) * math.pow(2, 10 * (p - 1))
|
||||
}
|
||||
|
||||
// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1
|
||||
elastic_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.sin(-13 * PI_2 * (p + 1)) * math.pow(2, -10 * p) + 1
|
||||
}
|
||||
|
||||
// Modeled after the piecewise exponentially-damped sine wave:
|
||||
// y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
|
||||
// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
|
||||
elastic_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
return 0.5 * math.sin(13 * PI_2 * (2 * p)) * math.pow(2, 10 * ((2 * p) - 1))
|
||||
} else {
|
||||
return 0.5 * (math.sin(-13 * PI_2 * ((2 * p - 1) + 1)) * math.pow(2, -10 * (2 * p - 1)) + 2)
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the overshooting cubic y = x^3-x*sin(x*pi)
|
||||
back_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return p * p * p - p * math.sin(p * math.PI)
|
||||
}
|
||||
|
||||
// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
|
||||
back_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
f := 1 - p
|
||||
return 1 - (f * f * f - f * math.sin(f * math.PI))
|
||||
}
|
||||
|
||||
// Modeled after the piecewise overshooting cubic function:
|
||||
// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
|
||||
// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
|
||||
back_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
f := 2 * p
|
||||
return 0.5 * (f * f * f - f * math.sin(f * math.PI))
|
||||
} else {
|
||||
f := (1 - (2*p - 1))
|
||||
return 0.5 * (1 - (f * f * f - f * math.sin(f * math.PI))) + 0.5
|
||||
}
|
||||
}
|
||||
|
||||
bounce_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return 1 - bounce_out(1 - p)
|
||||
}
|
||||
|
||||
bounce_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 4/11.0 {
|
||||
return (121 * p * p)/16.0
|
||||
} else if p < 8/11.0 {
|
||||
return (363/40.0 * p * p) - (99/10.0 * p) + 17/5.0
|
||||
} else if p < 9/10.0 {
|
||||
return (4356/361.0 * p * p) - (35442/1805.0 * p) + 16061/1805.0
|
||||
} else {
|
||||
return (54/5.0 * p * p) - (513/25.0 * p) + 268/25.0
|
||||
}
|
||||
}
|
||||
|
||||
bounce_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
return 0.5 * bounce_in(p*2)
|
||||
} else {
|
||||
return 0.5 * bounce_out(p * 2 - 1) + 0.5
|
||||
}
|
||||
}
|
||||
|
||||
// additional enum variant
|
||||
|
||||
Ease :: enum {
|
||||
Linear,
|
||||
|
||||
Quadratic_In,
|
||||
Quadratic_Out,
|
||||
Quadratic_In_Out,
|
||||
|
||||
Cubic_In,
|
||||
Cubic_Out,
|
||||
Cubic_In_Out,
|
||||
|
||||
Quartic_In,
|
||||
Quartic_Out,
|
||||
Quartic_In_Out,
|
||||
|
||||
Quintic_In,
|
||||
Quintic_Out,
|
||||
Quintic_In_Out,
|
||||
|
||||
Sine_In,
|
||||
Sine_Out,
|
||||
Sine_In_Out,
|
||||
|
||||
Circular_In,
|
||||
Circular_Out,
|
||||
Circular_In_Out,
|
||||
|
||||
Exponential_In,
|
||||
Exponential_Out,
|
||||
Exponential_In_Out,
|
||||
|
||||
Elastic_In,
|
||||
Elastic_Out,
|
||||
Elastic_In_Out,
|
||||
|
||||
Back_In,
|
||||
Back_Out,
|
||||
Back_In_Out,
|
||||
|
||||
Bounce_In,
|
||||
Bounce_Out,
|
||||
Bounce_In_Out,
|
||||
}
|
||||
|
||||
ease :: proc "contextless" (type: Ease, p: $T) -> T
|
||||
where intrinsics.type_is_float(T) {
|
||||
switch type {
|
||||
case .Linear: return p
|
||||
|
||||
case .Quadratic_In: return quadratic_in(p)
|
||||
case .Quadratic_Out: return quadratic_out(p)
|
||||
case .Quadratic_In_Out: return quadratic_in_out(p)
|
||||
|
||||
case .Cubic_In: return cubic_in(p)
|
||||
case .Cubic_Out: return cubic_out(p)
|
||||
case .Cubic_In_Out: return cubic_in_out(p)
|
||||
|
||||
case .Quartic_In: return quartic_in(p)
|
||||
case .Quartic_Out: return quartic_out(p)
|
||||
case .Quartic_In_Out: return quartic_in_out(p)
|
||||
|
||||
case .Quintic_In: return quintic_in(p)
|
||||
case .Quintic_Out: return quintic_out(p)
|
||||
case .Quintic_In_Out: return quintic_in_out(p)
|
||||
|
||||
case .Sine_In: return sine_in(p)
|
||||
case .Sine_Out: return sine_out(p)
|
||||
case .Sine_In_Out: return sine_in_out(p)
|
||||
|
||||
case .Circular_In: return circular_in(p)
|
||||
case .Circular_Out: return circular_out(p)
|
||||
case .Circular_In_Out: return circular_in_out(p)
|
||||
|
||||
case .Exponential_In: return exponential_in(p)
|
||||
case .Exponential_Out: return exponential_out(p)
|
||||
case .Exponential_In_Out: return exponential_in_out(p)
|
||||
|
||||
case .Elastic_In: return elastic_in(p)
|
||||
case .Elastic_Out: return elastic_out(p)
|
||||
case .Elastic_In_Out: return elastic_in_out(p)
|
||||
|
||||
case .Back_In: return back_in(p)
|
||||
case .Back_Out: return back_out(p)
|
||||
case .Back_In_Out: return back_in_out(p)
|
||||
|
||||
case .Bounce_In: return bounce_in(p)
|
||||
case .Bounce_Out: return bounce_out(p)
|
||||
case .Bounce_In_Out: return bounce_in_out(p)
|
||||
}
|
||||
|
||||
// in case type was invalid
|
||||
return 0
|
||||
}
|
||||
Flux_Map :: struct($T: typeid) {
|
||||
values: map[^T]Flux_Tween(T),
|
||||
keys_to_be_deleted: [dynamic]^T,
|
||||
}
|
||||
|
||||
Flux_Tween :: struct($T: typeid) {
|
||||
value: ^T,
|
||||
start: T,
|
||||
diff: T,
|
||||
goal: T,
|
||||
|
||||
delay: f64, // in seconds
|
||||
duration: time.Duration,
|
||||
|
||||
progress: f64,
|
||||
rate: f64,
|
||||
type: Ease,
|
||||
|
||||
inited: bool,
|
||||
|
||||
// callbacks, data can be set, will be pushed to callback
|
||||
data: rawptr, // by default gets set to value input
|
||||
on_start: proc(flux: ^Flux_Map(T), data: rawptr),
|
||||
on_update: proc(flux: ^Flux_Map(T), data: rawptr),
|
||||
on_complete: proc(flux: ^Flux_Map(T), data: rawptr),
|
||||
}
|
||||
|
||||
// init flux map to a float type and a wanted cap
|
||||
flux_init :: proc($T: typeid, value_capacity := 8) -> Flux_Map(T) where intrinsics.type_is_float(T) {
|
||||
return {
|
||||
values = make(map[^T]Flux_Tween(T), value_capacity),
|
||||
keys_to_be_deleted = make([dynamic]^T, 0, value_capacity),
|
||||
}
|
||||
}
|
||||
|
||||
// delete map content
|
||||
flux_destroy :: proc(flux: Flux_Map($T)) where intrinsics.type_is_float(T) {
|
||||
delete(flux.values)
|
||||
delete(flux.keys_to_be_deleted)
|
||||
}
|
||||
|
||||
// clear map content, stops all animations
|
||||
flux_clear :: proc(flux: ^Flux_Map($T)) where intrinsics.type_is_float(T) {
|
||||
clear(&flux.values)
|
||||
}
|
||||
|
||||
// append / overwrite existing tween value to parameters
|
||||
// rest is initialized in flux_tween_init, inside update
|
||||
// return value can be used to set callbacks
|
||||
flux_to :: proc(
|
||||
flux: ^Flux_Map($T),
|
||||
value: ^T,
|
||||
goal: T,
|
||||
type: Ease = .Quadratic_Out,
|
||||
duration: time.Duration = time.Second,
|
||||
delay: f64 = 0,
|
||||
) -> (tween: ^Flux_Tween(T)) where intrinsics.type_is_float(T) {
|
||||
if res, ok := &flux.values[value]; ok {
|
||||
tween = res
|
||||
} else {
|
||||
flux.values[value] = {}
|
||||
tween = &flux.values[value]
|
||||
}
|
||||
|
||||
tween^ = {
|
||||
value = value,
|
||||
goal = goal,
|
||||
duration = duration,
|
||||
delay = delay,
|
||||
type = type,
|
||||
data = value,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// init internal properties
|
||||
flux_tween_init :: proc(tween: ^Flux_Tween($T), duration: time.Duration) where intrinsics.type_is_float(T) {
|
||||
tween.inited = true
|
||||
tween.start = tween.value^
|
||||
tween.diff = tween.goal - tween.value^
|
||||
s := time.duration_seconds(duration)
|
||||
tween.rate = duration > 0 ? 1.0 / s : 0
|
||||
tween.progress = duration > 0 ? 0 : 1
|
||||
}
|
||||
|
||||
// update all tweens, wait for their delay if one exists
|
||||
// calls callbacks in all stages, when they're filled
|
||||
// deletes tween from the map after completion
|
||||
flux_update :: proc(flux: ^Flux_Map($T), dt: f64) where intrinsics.type_is_float(T) {
|
||||
clear(&flux.keys_to_be_deleted)
|
||||
|
||||
for key, tween in &flux.values {
|
||||
delay_remainder := f64(0)
|
||||
|
||||
// Update delay if necessary.
|
||||
if tween.delay > 0 {
|
||||
tween.delay -= dt
|
||||
|
||||
if tween.delay < 0 {
|
||||
// We finished the delay, but in doing so consumed part of this frame's `dt` budget.
|
||||
// Keep track of it so we can apply it to this tween without affecting others.
|
||||
delay_remainder = tween.delay
|
||||
// We're done with this delay.
|
||||
tween.delay = 0
|
||||
}
|
||||
}
|
||||
|
||||
// We either had no delay, or the delay has been consumed.
|
||||
if tween.delay <= 0 {
|
||||
if !tween.inited {
|
||||
flux_tween_init(&tween, tween.duration)
|
||||
|
||||
if tween.on_start != nil {
|
||||
tween.on_start(flux, tween.data)
|
||||
}
|
||||
}
|
||||
|
||||
// If part of the `dt` budget was consumed this frame, then `delay_remainder` will be
|
||||
// that remainder, a negative value. Adding it to `dt` applies what's left of the `dt`
|
||||
// to the tween so it advances properly, instead of too much or little.
|
||||
tween.progress += tween.rate * (dt + delay_remainder)
|
||||
x := tween.progress >= 1 ? 1 : ease(tween.type, tween.progress)
|
||||
tween.value^ = tween.start + tween.diff * T(x)
|
||||
|
||||
if tween.on_update != nil {
|
||||
tween.on_update(flux, tween.data)
|
||||
}
|
||||
|
||||
if tween.progress >= 1 {
|
||||
// append keys to array that will be deleted after the loop
|
||||
append(&flux.keys_to_be_deleted, key)
|
||||
|
||||
if tween.on_complete != nil {
|
||||
tween.on_complete(flux, tween.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loop through keys that should be deleted from the map
|
||||
if len(flux.keys_to_be_deleted) != 0 {
|
||||
for key in flux.keys_to_be_deleted {
|
||||
delete_key(&flux.values, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stop a specific key inside the map
|
||||
// returns true when it successfully removed the key
|
||||
flux_stop :: proc(flux: ^Flux_Map($T), key: ^T) -> bool where intrinsics.type_is_float(T) {
|
||||
if key in flux.values {
|
||||
delete_key(&flux.values, key)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// returns the amount of time left for the tween animation, if the key exists in the map
|
||||
// returns 0 if the tween doesnt exist on the map
|
||||
flux_tween_time_left :: proc(flux: Flux_Map($T), key: ^T) -> f64 {
|
||||
if tween, ok := flux.values[key]; ok {
|
||||
return ((1 - tween.progress) * tween.rate) + tween.delay
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -426,14 +426,14 @@ distance :: proc(p0, p1: $V/[$N]$E) -> E where IS_NUMERIC(E) {
|
||||
}
|
||||
|
||||
reflect :: proc(I, N: $T) -> (out: T) where IS_ARRAY(T), IS_FLOAT(ELEM_TYPE(T)) {
|
||||
b := n * (2 * dot(n, i))
|
||||
return i - b
|
||||
b := N * (2 * dot(N, I))
|
||||
return I - b
|
||||
}
|
||||
refract :: proc(I, N: $T) -> (out: T) where IS_ARRAY(T), IS_FLOAT(ELEM_TYPE(T)) {
|
||||
dv := dot(n, i)
|
||||
dv := dot(N, I)
|
||||
k := 1 - eta*eta - (1 - dv*dv)
|
||||
a := i * eta
|
||||
b := n * eta*dv*math.sqrt(k)
|
||||
a := I * eta
|
||||
b := N * eta*dv*math.sqrt(k)
|
||||
return (a - b) * E(int(k >= 0))
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ quaternion_cross :: proc(q1, q2: $Q) -> (q3: Q) where IS_QUATERNION(Q) {
|
||||
vector_cross :: proc{scalar_cross, vector_cross2, vector_cross3}
|
||||
cross :: proc{scalar_cross, vector_cross2, vector_cross3, quaternion_cross}
|
||||
|
||||
vector_normalize :: proc(v: $T/[$N]$E) -> T where IS_NUMERIC(E) {
|
||||
vector_normalize :: proc(v: $T/[$N]$E) -> T where IS_FLOAT(E) {
|
||||
return v / length(v)
|
||||
}
|
||||
quaternion_normalize :: proc(q: $Q) -> Q where IS_QUATERNION(Q) {
|
||||
@@ -102,7 +102,7 @@ quaternion_normalize :: proc(q: $Q) -> Q where IS_QUATERNION(Q) {
|
||||
}
|
||||
normalize :: proc{vector_normalize, quaternion_normalize}
|
||||
|
||||
vector_normalize0 :: proc(v: $T/[$N]$E) -> T where IS_NUMERIC(E) {
|
||||
vector_normalize0 :: proc(v: $T/[$N]$E) -> T where IS_FLOAT(E) {
|
||||
m := length(v)
|
||||
return 0 if m == 0 else v/m
|
||||
}
|
||||
@@ -113,7 +113,7 @@ quaternion_normalize0 :: proc(q: $Q) -> Q where IS_QUATERNION(Q) {
|
||||
normalize0 :: proc{vector_normalize0, quaternion_normalize0}
|
||||
|
||||
|
||||
vector_length :: proc(v: $T/[$N]$E) -> E where IS_NUMERIC(E) {
|
||||
vector_length :: proc(v: $T/[$N]$E) -> E where IS_FLOAT(E) {
|
||||
return math.sqrt(dot(v, v))
|
||||
}
|
||||
|
||||
|
||||
@@ -476,24 +476,24 @@ quaternion_angle_axis :: proc{
|
||||
|
||||
angle_from_quaternion_f16 :: proc(q: Quaternionf16) -> f16 {
|
||||
if abs(q.w) > math.SQRT_THREE*0.5 {
|
||||
return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2
|
||||
return math.asin(math.sqrt(q.x*q.x + q.y*q.y + q.z*q.z)) * 2
|
||||
}
|
||||
|
||||
return math.cos(q.x) * 2
|
||||
return math.acos(q.w) * 2
|
||||
}
|
||||
angle_from_quaternion_f32 :: proc(q: Quaternionf32) -> f32 {
|
||||
if abs(q.w) > math.SQRT_THREE*0.5 {
|
||||
return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2
|
||||
return math.asin(math.sqrt(q.x*q.x + q.y*q.y + q.z*q.z)) * 2
|
||||
}
|
||||
|
||||
return math.cos(q.x) * 2
|
||||
return math.acos(q.w) * 2
|
||||
}
|
||||
angle_from_quaternion_f64 :: proc(q: Quaternionf64) -> f64 {
|
||||
if abs(q.w) > math.SQRT_THREE*0.5 {
|
||||
return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2
|
||||
return math.asin(math.sqrt(q.x*q.x + q.y*q.y + q.z*q.z)) * 2
|
||||
}
|
||||
|
||||
return math.cos(q.x) * 2
|
||||
return math.acos(q.w) * 2
|
||||
}
|
||||
angle_from_quaternion :: proc{
|
||||
angle_from_quaternion_f16,
|
||||
|
||||
+64
-17
@@ -185,16 +185,23 @@ log :: proc{
|
||||
log_f64, log_f64le, log_f64be,
|
||||
}
|
||||
|
||||
log2_f16 :: logb_f16
|
||||
log2_f16le :: logb_f16le
|
||||
log2_f16be :: logb_f16be
|
||||
log2_f32 :: logb_f32
|
||||
log2_f32le :: logb_f32le
|
||||
log2_f32be :: logb_f32be
|
||||
log2_f64 :: logb_f64
|
||||
log2_f64le :: logb_f64le
|
||||
log2_f64be :: logb_f64be
|
||||
log2 :: logb
|
||||
log2_f16 :: proc "contextless" (x: f16) -> f16 { return log(f16(x), f16(2.0)) }
|
||||
log2_f16le :: proc "contextless" (x: f16le) -> f16le { return f16le(log_f16(f16(x), f16(2.0))) }
|
||||
log2_f16be :: proc "contextless" (x: f16be) -> f16be { return f16be(log_f16(f16(x), f16(2.0))) }
|
||||
|
||||
log2_f32 :: proc "contextless" (x: f32) -> f32 { return log(f32(x), f32(2.0)) }
|
||||
log2_f32le :: proc "contextless" (x: f32le) -> f32le { return f32le(log_f32(f32(x), f32(2.0))) }
|
||||
log2_f32be :: proc "contextless" (x: f32be) -> f32be { return f32be(log_f32(f32(x), f32(2.0))) }
|
||||
|
||||
log2_f64 :: proc "contextless" (x: f64) -> f64 { return log(f64(x), f64(2.0)) }
|
||||
log2_f64le :: proc "contextless" (x: f64le) -> f64le { return f64le(log_f64(f64(x), f64(2.0))) }
|
||||
log2_f64be :: proc "contextless" (x: f64be) -> f64be { return f64be(log_f64(f64(x), f64(2.0))) }
|
||||
|
||||
log2 :: proc{
|
||||
log2_f16, log2_f16le, log2_f16be,
|
||||
log2_f32, log2_f32le, log2_f32be,
|
||||
log2_f64, log2_f64le, log2_f64be,
|
||||
}
|
||||
|
||||
log10_f16 :: proc "contextless" (x: f16) -> f16 { return ln(x)/LN10 }
|
||||
log10_f16le :: proc "contextless" (x: f16le) -> f16le { return f16le(log10_f16(f16(x))) }
|
||||
@@ -396,7 +403,7 @@ trunc_f16 :: proc "contextless" (x: f16) -> f16 {
|
||||
e := (x >> shift) & mask - bias
|
||||
|
||||
if e < shift {
|
||||
x &= ~(1 << (shift-e)) - 1
|
||||
x &~= 1 << (shift-e) - 1
|
||||
}
|
||||
return transmute(f16)x
|
||||
}
|
||||
@@ -428,7 +435,7 @@ trunc_f32 :: proc "contextless" (x: f32) -> f32 {
|
||||
e := (x >> shift) & mask - bias
|
||||
|
||||
if e < shift {
|
||||
x &= ~(1 << (shift-e)) - 1
|
||||
x &~= 1 << (shift-e) - 1
|
||||
}
|
||||
return transmute(f32)x
|
||||
}
|
||||
@@ -460,7 +467,7 @@ trunc_f64 :: proc "contextless" (x: f64) -> f64 {
|
||||
e := (x >> shift) & mask - bias
|
||||
|
||||
if e < shift {
|
||||
x &= ~(1 << (shift-e)) - 1
|
||||
x &~= 1 << (shift-e) - 1
|
||||
}
|
||||
return transmute(f64)x
|
||||
}
|
||||
@@ -473,6 +480,7 @@ trunc_f64 :: proc "contextless" (x: f64) -> f64 {
|
||||
}
|
||||
trunc_f64le :: proc "contextless" (x: f64le) -> f64le { return #force_inline f64le(trunc_f64(f64(x))) }
|
||||
trunc_f64be :: proc "contextless" (x: f64be) -> f64be { return #force_inline f64be(trunc_f64(f64(x))) }
|
||||
// Removes the fractional part of the value, i.e. rounds towards zero.
|
||||
trunc :: proc{
|
||||
trunc_f16, trunc_f16le, trunc_f16be,
|
||||
trunc_f32, trunc_f32le, trunc_f32be,
|
||||
@@ -606,6 +614,25 @@ floor_mod :: proc "contextless" (x, y: $T) -> T
|
||||
return r
|
||||
}
|
||||
|
||||
divmod :: #force_inline proc "contextless" (x, y: $T) -> (div, mod: T)
|
||||
where intrinsics.type_is_integer(T) {
|
||||
div = x / y
|
||||
mod = x % y
|
||||
return
|
||||
}
|
||||
|
||||
floor_divmod :: #force_inline proc "contextless" (x, y: $T) -> (div, mod: T)
|
||||
where intrinsics.type_is_integer(T) {
|
||||
div = x / y
|
||||
mod = x % y
|
||||
if (div > 0 && y < 0) || (mod < 0 && y > 0) {
|
||||
div -= 1
|
||||
mod += y
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
modf_f16 :: proc "contextless" (x: f16) -> (int: f16, frac: f16) {
|
||||
shift :: F16_SHIFT
|
||||
mask :: F16_MASK
|
||||
@@ -958,7 +985,7 @@ classify_f16 :: proc "contextless" (x: f16) -> Float_Class {
|
||||
return .Neg_Zero
|
||||
}
|
||||
return .Zero
|
||||
case x*0.5 == x:
|
||||
case x*0.25 == x:
|
||||
if x < 0 {
|
||||
return .Neg_Inf
|
||||
}
|
||||
@@ -1027,6 +1054,8 @@ classify_f64 :: proc "contextless" (x: f64) -> Float_Class {
|
||||
}
|
||||
classify_f64le :: proc "contextless" (x: f64le) -> Float_Class { return #force_inline classify_f64(f64(x)) }
|
||||
classify_f64be :: proc "contextless" (x: f64be) -> Float_Class { return #force_inline classify_f64(f64(x)) }
|
||||
// Returns the `Float_Class` of the value, i.e. whether normal, subnormal, zero, negative zero, NaN, infinity or
|
||||
// negative infinity.
|
||||
classify :: proc{
|
||||
classify_f16, classify_f16le, classify_f16be,
|
||||
classify_f32, classify_f32le, classify_f32be,
|
||||
@@ -1203,7 +1232,7 @@ prod :: proc "contextless" (x: $T/[]$E) -> (res: E)
|
||||
return
|
||||
}
|
||||
|
||||
cumsum_inplace :: proc "contextless" (x: $T/[]$E) -> T
|
||||
cumsum_inplace :: proc "contextless" (x: $T/[]$E)
|
||||
where intrinsics.type_is_numeric(E) {
|
||||
for i in 1..<len(x) {
|
||||
x[i] = x[i-1] + x[i]
|
||||
@@ -1354,7 +1383,7 @@ atan :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) {
|
||||
}
|
||||
|
||||
asin :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return atan2(x, 1 + sqrt(1 - x*x))
|
||||
return atan2(x, sqrt(1 - x*x))
|
||||
}
|
||||
|
||||
acos :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) {
|
||||
@@ -1715,4 +1744,22 @@ F32_BIAS :: 0x7f
|
||||
|
||||
F64_MASK :: 0x7ff
|
||||
F64_SHIFT :: 64 - 12
|
||||
F64_BIAS :: 0x3ff
|
||||
F64_BIAS :: 0x3ff
|
||||
|
||||
INF_F16 :f16: 0h7C00
|
||||
NEG_INF_F16 :f16: 0hFC00
|
||||
|
||||
SNAN_F16 :f16: 0h7C01
|
||||
QNAN_F16 :f16: 0h7E01
|
||||
|
||||
INF_F32 :f32: 0h7F80_0000
|
||||
NEG_INF_F32 :f32: 0hFF80_0000
|
||||
|
||||
SNAN_F32 :f32: 0hFF80_0001
|
||||
QNAN_F32 :f32: 0hFFC0_0001
|
||||
|
||||
INF_F64 :f64: 0h7FF0_0000_0000_0000
|
||||
NEG_INF_F64 :f64: 0hFFF0_0000_0000_0000
|
||||
|
||||
SNAN_F64 :f64: 0h7FF0_0000_0000_0001
|
||||
QNAN_F64 :f64: 0h7FF8_0000_0000_0001
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
package rand
|
||||
|
||||
import "core:math"
|
||||
|
||||
float64_uniform :: float64_range
|
||||
float32_uniform :: float32_range
|
||||
|
||||
// Triangular Distribution
|
||||
// See: http://wikipedia.org/wiki/Triangular_distribution
|
||||
float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64 {
|
||||
if hi-lo == 0 {
|
||||
return lo
|
||||
}
|
||||
lo, hi := lo, hi
|
||||
u := float64(r)
|
||||
c := f64(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1)
|
||||
if u > c {
|
||||
u = 1-u
|
||||
c = 1-c
|
||||
lo, hi = hi, lo
|
||||
}
|
||||
return lo + (hi - lo) * math.sqrt(u * c)
|
||||
|
||||
}
|
||||
// Triangular Distribution
|
||||
// See: http://wikipedia.org/wiki/Triangular_distribution
|
||||
float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32 {
|
||||
if hi-lo == 0 {
|
||||
return lo
|
||||
}
|
||||
lo, hi := lo, hi
|
||||
u := float32(r)
|
||||
c := f32(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1)
|
||||
if u > c {
|
||||
u = 1-u
|
||||
c = 1-c
|
||||
lo, hi = hi, lo
|
||||
}
|
||||
return lo + (hi - lo) * math.sqrt(u * c)
|
||||
}
|
||||
|
||||
|
||||
// Normal/Gaussian Distribution
|
||||
float64_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 {
|
||||
return norm_float64(r) * stddev + mean
|
||||
}
|
||||
// Normal/Gaussian Distribution
|
||||
float32_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_normal(f64(mean), f64(stddev), r))
|
||||
}
|
||||
|
||||
|
||||
// Log Normal Distribution
|
||||
float64_log_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 {
|
||||
return math.exp(float64_normal(mean, stddev, r))
|
||||
}
|
||||
// Log Normal Distribution
|
||||
float32_log_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_log_normal(f64(mean), f64(stddev), r))
|
||||
}
|
||||
|
||||
|
||||
// Exponential Distribution
|
||||
// `lambda` is 1.0/(desired mean). It should be non-zero.
|
||||
// Return values range from
|
||||
// 0 to positive infinity if lambda > 0
|
||||
// negative infinity to 0 if lambda <= 0
|
||||
float64_exponential :: proc(lambda: f64, r: ^Rand = nil) -> f64 {
|
||||
return - math.ln(1 - float64(r)) / lambda
|
||||
}
|
||||
// Exponential Distribution
|
||||
// `lambda` is 1.0/(desired mean). It should be non-zero.
|
||||
// Return values range from
|
||||
// 0 to positive infinity if lambda > 0
|
||||
// negative infinity to 0 if lambda <= 0
|
||||
float32_exponential :: proc(lambda: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_exponential(f64(lambda), r))
|
||||
}
|
||||
|
||||
|
||||
// Gamma Distribution (NOT THE GAMMA FUNCTION)
|
||||
//
|
||||
// Required: alpha > 0 and beta > 0
|
||||
//
|
||||
// math.pow(x, alpha-1) * math.exp(-x / beta)
|
||||
// pdf(x) = --------------------------------------------
|
||||
// math.gamma(alpha) * math.pow(beta, alpha)
|
||||
//
|
||||
// mean is alpha*beta, variance is math.pow(alpha*beta, 2)
|
||||
float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 {
|
||||
if alpha <= 0 || beta <= 0 {
|
||||
panic(#procedure + ": alpha and beta must be > 0.0")
|
||||
}
|
||||
|
||||
LOG4 :: 1.3862943611198906188344642429163531361510002687205105082413600189
|
||||
SG_MAGIC_CONST :: 2.5040773967762740733732583523868748412194809812852436493487
|
||||
|
||||
switch {
|
||||
case alpha > 1:
|
||||
// R.C.H. Cheng, "The generation of Gamma variables with non-integral shape parameters", Applied Statistics, (1977), 26, No. 1, p71-74
|
||||
|
||||
ainv := math.sqrt(2 * alpha - 1)
|
||||
bbb := alpha - LOG4
|
||||
ccc := alpha + ainv
|
||||
for {
|
||||
u1 := float64(r)
|
||||
if !(1e-7 < u1 && u1 < 0.9999999) {
|
||||
continue
|
||||
}
|
||||
u2 := 1 - float64(r)
|
||||
v := math.ln(u1 / (1 - u1)) / ainv
|
||||
x := alpha * math.exp(v)
|
||||
z := u1 * u1 * u2
|
||||
t := bbb + ccc*v - x
|
||||
if t + SG_MAGIC_CONST - 4.5 * z >= 0 || t >= math.ln(z) {
|
||||
return x * beta
|
||||
}
|
||||
}
|
||||
case alpha == 1:
|
||||
// float64_exponential(1/beta)
|
||||
return -math.ln(1 - float64(r)) * beta
|
||||
case:
|
||||
// ALGORITHM GS of Statistical Computing - Kennedy & Gentle
|
||||
x: f64
|
||||
for {
|
||||
u := float64(r)
|
||||
b := (math.e + alpha) / math.e
|
||||
p := b * u
|
||||
if p <= 1 {
|
||||
x = math.pow(p, 1/alpha)
|
||||
} else {
|
||||
x = -math.ln((b - p) / alpha)
|
||||
}
|
||||
u1 := float64(r)
|
||||
if p > 1 {
|
||||
if u1 <= math.pow(x, alpha-1) {
|
||||
break
|
||||
}
|
||||
} else if u1 <= math.exp(-x) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return x * beta
|
||||
}
|
||||
}
|
||||
// Gamma Distribution (NOT THE GAMMA FUNCTION)
|
||||
//
|
||||
// Required: alpha > 0 and beta > 0
|
||||
//
|
||||
// math.pow(x, alpha-1) * math.exp(-x / beta)
|
||||
// pdf(x) = --------------------------------------------
|
||||
// math.gamma(alpha) * math.pow(beta, alpha)
|
||||
//
|
||||
// mean is alpha*beta, variance is math.pow(alpha*beta, 2)
|
||||
float32_gamma :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_gamma(f64(alpha), f64(beta), r))
|
||||
}
|
||||
|
||||
|
||||
// Beta Distribution
|
||||
//
|
||||
// Required: alpha > 0 and beta > 0
|
||||
//
|
||||
// Return values range between 0 and 1
|
||||
float64_beta :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 {
|
||||
if alpha <= 0 || beta <= 0 {
|
||||
panic(#procedure + ": alpha and beta must be > 0.0")
|
||||
}
|
||||
// Knuth Vol 2 Ed 3 pg 134 "the beta distribution"
|
||||
y := float64_gamma(alpha, 1.0, r)
|
||||
if y != 0 {
|
||||
return y / (y + float64_gamma(beta, 1.0, r))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
// Beta Distribution
|
||||
//
|
||||
// Required: alpha > 0 and beta > 0
|
||||
//
|
||||
// Return values range between 0 and 1
|
||||
float32_beta :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_beta(f64(alpha), f64(beta), r))
|
||||
}
|
||||
|
||||
|
||||
// Pareto distribution, `alpha` is the shape parameter.
|
||||
// https://wikipedia.org/wiki/Pareto_distribution
|
||||
float64_pareto :: proc(alpha: f64, r: ^Rand = nil) -> f64 {
|
||||
return math.pow(1 - float64(r), -1.0 / alpha)
|
||||
}
|
||||
// Pareto distribution, `alpha` is the shape parameter.
|
||||
// https://wikipedia.org/wiki/Pareto_distribution
|
||||
float32_pareto :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_pareto(f64(alpha), r))
|
||||
}
|
||||
|
||||
|
||||
// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter.
|
||||
float64_weibull :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 {
|
||||
u := 1 - float64(r)
|
||||
return alpha * math.pow(-math.ln(u), 1.0/beta)
|
||||
}
|
||||
// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter.
|
||||
float32_weibull :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_weibull(f64(alpha), f64(beta), r))
|
||||
}
|
||||
|
||||
|
||||
// Circular Data (von Mises) Distribution
|
||||
// `mean_angle` is the in mean angle between 0 and 2pi radians
|
||||
// `kappa` is the concentration parameter which must be >= 0
|
||||
// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi
|
||||
float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 {
|
||||
// Fisher, N.I., "Statistical Analysis of Circular Data", Cambridge University Press, 1993.
|
||||
|
||||
mu := mean_angle
|
||||
if kappa <= 1e-6 {
|
||||
return math.TAU * float64(r)
|
||||
}
|
||||
|
||||
s := 0.5 / kappa
|
||||
t := s + math.sqrt(1 + s*s)
|
||||
z: f64
|
||||
for {
|
||||
u1 := float64(r)
|
||||
z = math.cos(math.TAU * 0.5 * u1)
|
||||
|
||||
d := z / (t + z)
|
||||
u2 := float64(r)
|
||||
if u2 < 1 - d*d || u2 <= (1-d)*math.exp(d) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
q := 1.0 / t
|
||||
f := (q + z) / (1 + q*z)
|
||||
u3 := float64(r)
|
||||
if u3 > 0.5 {
|
||||
return math.mod(mu + math.acos(f), math.TAU)
|
||||
} else {
|
||||
return math.mod(mu - math.acos(f), math.TAU)
|
||||
}
|
||||
}
|
||||
// Circular Data (von Mises) Distribution
|
||||
// `mean_angle` is the in mean angle between 0 and 2pi radians
|
||||
// `kappa` is the concentration parameter which must be >= 0
|
||||
// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi
|
||||
float32_von_mises :: proc(mean_angle, kappa: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_von_mises(f64(mean_angle), f64(kappa), r))
|
||||
}
|
||||
|
||||
|
||||
// Cauchy-Lorentz Distribution
|
||||
// `x_0` is the location, `gamma` is the scale where `gamma` > 0
|
||||
float64_cauchy_lorentz :: proc(x_0, gamma: f64, r: ^Rand = nil) -> f64 {
|
||||
assert(gamma > 0)
|
||||
|
||||
// Calculated from the inverse CDF
|
||||
|
||||
return math.tan(math.PI * (float64(r) - 0.5))*gamma + x_0
|
||||
}
|
||||
// Cauchy-Lorentz Distribution
|
||||
// `x_0` is the location, `gamma` is the scale where `gamma` > 0
|
||||
float32_cauchy_lorentz :: proc(x_0, gamma: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_cauchy_lorentz(f64(x_0), f64(gamma), r))
|
||||
}
|
||||
|
||||
|
||||
// Log Cauchy-Lorentz Distribution
|
||||
// `x_0` is the location, `gamma` is the scale where `gamma` > 0
|
||||
float64_log_cauchy_lorentz :: proc(x_0, gamma: f64, r: ^Rand = nil) -> f64 {
|
||||
assert(gamma > 0)
|
||||
return math.exp(math.tan(math.PI * (float64(r) - 0.5))*gamma + x_0)
|
||||
}
|
||||
// Log Cauchy-Lorentz Distribution
|
||||
// `x_0` is the location, `gamma` is the scale where `gamma` > 0
|
||||
float32_log_cauchy_lorentz :: proc(x_0, gamma: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_log_cauchy_lorentz(f64(x_0), f64(gamma), r))
|
||||
}
|
||||
|
||||
|
||||
// Laplace Distribution
|
||||
// `b` is the scale where `b` > 0
|
||||
float64_laplace :: proc(mean, b: f64, r: ^Rand = nil) -> f64 {
|
||||
assert(b > 0)
|
||||
p := float64(r)-0.5
|
||||
return -math.sign(p)*math.ln(1 - 2*abs(p))*b + mean
|
||||
}
|
||||
// Laplace Distribution
|
||||
// `b` is the scale where `b` > 0
|
||||
float32_laplace :: proc(mean, b: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_laplace(f64(mean), f64(b), r))
|
||||
}
|
||||
|
||||
|
||||
// Gompertz Distribution
|
||||
// `eta` is the shape, `b` is the scale
|
||||
// Both `eta` and `b` must be > 0
|
||||
float64_gompertz :: proc(eta, b: f64, r: ^Rand = nil) -> f64 {
|
||||
if eta <= 0 || b <= 0 {
|
||||
panic(#procedure + ": eta and b must be > 0.0")
|
||||
}
|
||||
|
||||
p := float64(r)
|
||||
return math.ln(1 - math.ln(1 - p)/eta)/b
|
||||
}
|
||||
// Gompertz Distribution
|
||||
// `eta` is the shape, `b` is the scale
|
||||
// Both `eta` and `b` must be > 0
|
||||
float32_gompertz :: proc(eta, b: f32, r: ^Rand = nil) -> f32 {
|
||||
return f32(float64_gompertz(f64(eta), f64(b), r))
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import "core:intrinsics"
|
||||
Rand :: struct {
|
||||
state: u64,
|
||||
inc: u64,
|
||||
is_system: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +30,16 @@ init :: proc(r: ^Rand, seed: u64) {
|
||||
_random(r)
|
||||
}
|
||||
|
||||
init_as_system :: proc(r: ^Rand) {
|
||||
if !#defined(_system_random) {
|
||||
panic(#procedure + " is not supported on this platform yet")
|
||||
}
|
||||
r.state = 0
|
||||
r.inc = 0
|
||||
r.is_system = true
|
||||
}
|
||||
|
||||
@(private)
|
||||
_random :: proc(r: ^Rand) -> u32 {
|
||||
r := r
|
||||
if r == nil {
|
||||
@@ -36,6 +47,12 @@ _random :: proc(r: ^Rand) -> u32 {
|
||||
// enforce the global random state if necessary with `nil`
|
||||
r = &global_rand
|
||||
}
|
||||
when #defined(_system_random) {
|
||||
if r.is_system {
|
||||
return _system_random()
|
||||
}
|
||||
}
|
||||
|
||||
old_state := r.state
|
||||
r.state = old_state * 6364136223846793005 + (r.inc|1)
|
||||
xor_shifted := u32(((old_state>>18) ~ old_state) >> 27)
|
||||
@@ -119,13 +136,14 @@ int_max :: proc(n: int, r: ^Rand = nil) -> int {
|
||||
}
|
||||
}
|
||||
|
||||
// Uniform random distribution [0, 1)
|
||||
float64 :: proc(r: ^Rand = nil) -> f64 { return f64(int63_max(1<<53, r)) / (1 << 53) }
|
||||
// Uniform random distribution [0, 1)
|
||||
float32 :: proc(r: ^Rand = nil) -> f32 { return f32(float64(r)) }
|
||||
|
||||
float64_range :: proc(lo, hi: f64, r: ^Rand = nil) -> f64 { return (hi-lo)*float64(r) + lo }
|
||||
float32_range :: proc(lo, hi: f32, r: ^Rand = nil) -> f32 { return (hi-lo)*float32(r) + lo }
|
||||
|
||||
|
||||
read :: proc(p: []byte, r: ^Rand = nil) -> (n: int) {
|
||||
pos := i8(0)
|
||||
val := i64(0)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package rand
|
||||
|
||||
import "core:sys/darwin"
|
||||
|
||||
_system_random :: proc() -> u32 {
|
||||
for {
|
||||
value: u32
|
||||
ret := darwin.syscall_getentropy(([^]u8)(&value), 4)
|
||||
if ret < 0 {
|
||||
switch ret {
|
||||
case -4: // EINTR
|
||||
continue
|
||||
case -78: // ENOSYS
|
||||
panic("getentropy not available in kernel")
|
||||
case:
|
||||
panic("getentropy failed")
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package rand
|
||||
|
||||
import "core:sys/unix"
|
||||
|
||||
_system_random :: proc() -> u32 {
|
||||
for {
|
||||
value: u32
|
||||
ret := unix.sys_getrandom(([^]u8)(&value), 4, 0)
|
||||
if ret < 0 {
|
||||
switch ret {
|
||||
case -4: // EINTR
|
||||
// Call interupted by a signal handler, just retry the request.
|
||||
continue
|
||||
case -38: // ENOSYS
|
||||
// The kernel is apparently prehistoric (< 3.17 circa 2014)
|
||||
// and does not support getrandom.
|
||||
panic("getrandom not available in kernel")
|
||||
case:
|
||||
// All other failures are things that should NEVER happen
|
||||
// unless the kernel interface changes (ie: the Linux
|
||||
// developers break userland).
|
||||
panic("getrandom failed")
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package rand
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
_system_random :: proc() -> u32 {
|
||||
value: u32
|
||||
status := win32.BCryptGenRandom(nil, ([^]u8)(&value), 4, win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG)
|
||||
if status < 0 {
|
||||
panic("BCryptGenRandom failed")
|
||||
}
|
||||
return value
|
||||
}
|
||||
+14
-85
@@ -55,115 +55,44 @@ Allocator :: struct {
|
||||
|
||||
DEFAULT_ALIGNMENT :: 2*align_of(rawptr)
|
||||
|
||||
DEFAULT_PAGE_SIZE ::
|
||||
64 * 1024 when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64 else
|
||||
16 * 1024 when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 else
|
||||
4 * 1024
|
||||
|
||||
alloc :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> rawptr {
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
if allocator.procedure == nil {
|
||||
return nil
|
||||
}
|
||||
data, err := allocator.procedure(allocator.data, Allocator_Mode.Alloc, size, alignment, nil, 0, loc)
|
||||
_ = err
|
||||
data, _ := runtime.mem_alloc(size, alignment, allocator, loc)
|
||||
return raw_data(data)
|
||||
}
|
||||
|
||||
alloc_bytes :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
if size == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if allocator.procedure == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return allocator.procedure(allocator.data, Allocator_Mode.Alloc, size, alignment, nil, 0, loc)
|
||||
return runtime.mem_alloc(size, alignment, allocator, loc)
|
||||
}
|
||||
|
||||
free :: proc(ptr: rawptr, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
if ptr == nil {
|
||||
return nil
|
||||
}
|
||||
if allocator.procedure == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := allocator.procedure(allocator.data, Allocator_Mode.Free, 0, 0, ptr, 0, loc)
|
||||
return err
|
||||
return runtime.mem_free(ptr, allocator, loc)
|
||||
}
|
||||
|
||||
free_bytes :: proc(bytes: []byte, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
if bytes == nil {
|
||||
return nil
|
||||
}
|
||||
if allocator.procedure == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := allocator.procedure(allocator.data, Allocator_Mode.Free, 0, 0, raw_data(bytes), len(bytes), loc)
|
||||
return err
|
||||
return runtime.mem_free_bytes(bytes, allocator, loc)
|
||||
}
|
||||
|
||||
free_all :: proc(allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
if allocator.procedure != nil {
|
||||
_, err := allocator.procedure(allocator.data, Allocator_Mode.Free_All, 0, 0, nil, 0, loc)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return runtime.mem_free_all(allocator, loc)
|
||||
}
|
||||
|
||||
resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> rawptr {
|
||||
if allocator.procedure == nil {
|
||||
return nil
|
||||
}
|
||||
if new_size == 0 {
|
||||
if ptr != nil {
|
||||
allocator.procedure(allocator.data, Allocator_Mode.Free, 0, 0, ptr, old_size, loc)
|
||||
}
|
||||
return nil
|
||||
} else if ptr == nil {
|
||||
_, err := allocator.procedure(allocator.data, Allocator_Mode.Alloc, new_size, alignment, nil, 0, loc)
|
||||
_ = err
|
||||
return nil
|
||||
}
|
||||
data, err := allocator.procedure(allocator.data, Allocator_Mode.Resize, new_size, alignment, ptr, old_size, loc)
|
||||
if err == .Mode_Not_Implemented {
|
||||
data, err = allocator.procedure(allocator.data, Allocator_Mode.Alloc, new_size, alignment, nil, 0, loc)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
runtime.copy(data, byte_slice(ptr, old_size))
|
||||
_, err = allocator.procedure(allocator.data, Allocator_Mode.Free, 0, 0, ptr, old_size, loc)
|
||||
return raw_data(data)
|
||||
}
|
||||
data, _ := runtime.mem_resize(ptr, old_size, new_size, alignment, allocator, loc)
|
||||
return raw_data(data)
|
||||
}
|
||||
|
||||
resize_bytes :: proc(old_data: []byte, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
if allocator.procedure == nil {
|
||||
return nil, nil
|
||||
}
|
||||
ptr := raw_data(old_data)
|
||||
old_size := len(old_data)
|
||||
if new_size == 0 {
|
||||
if ptr != nil {
|
||||
_, err := allocator.procedure(allocator.data, Allocator_Mode.Free, 0, 0, ptr, old_size, loc)
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
} else if ptr == nil {
|
||||
return allocator.procedure(allocator.data, Allocator_Mode.Alloc, new_size, alignment, nil, 0, loc)
|
||||
}
|
||||
data, err := allocator.procedure(allocator.data, Allocator_Mode.Resize, new_size, alignment, ptr, old_size, loc)
|
||||
if err == .Mode_Not_Implemented {
|
||||
data, err = allocator.procedure(allocator.data, Allocator_Mode.Alloc, new_size, alignment, nil, 0, loc)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
runtime.copy(data, old_data)
|
||||
_, err = allocator.procedure(allocator.data, Allocator_Mode.Free, 0, 0, ptr, old_size, loc)
|
||||
}
|
||||
return data, err
|
||||
return runtime.mem_resize(raw_data(old_data), len(old_data), new_size, alignment, allocator, loc)
|
||||
}
|
||||
|
||||
query_features :: proc(allocator: Allocator, loc := #caller_location) -> (set: Allocator_Mode_Set) {
|
||||
if allocator.procedure != nil {
|
||||
allocator.procedure(allocator.data, Allocator_Mode.Query_Features, 0, 0, &set, 0, loc)
|
||||
allocator.procedure(allocator.data, .Query_Features, 0, 0, &set, 0, loc)
|
||||
return set
|
||||
}
|
||||
return nil
|
||||
@@ -172,7 +101,7 @@ query_features :: proc(allocator: Allocator, loc := #caller_location) -> (set: A
|
||||
query_info :: proc(pointer: rawptr, allocator: Allocator, loc := #caller_location) -> (props: Allocator_Query_Info) {
|
||||
props.pointer = pointer
|
||||
if allocator.procedure != nil {
|
||||
allocator.procedure(allocator.data, Allocator_Mode.Query_Info, 0, 0, &props, 0, loc)
|
||||
allocator.procedure(allocator.data, .Query_Info, 0, 0, &props, 0, loc)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user