mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-05 11:11:37 -07:00
Compare commits
637 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2cf0755f2 | |||
| d399d2256b | |||
| 27fd702692 | |||
| 5f29288254 | |||
| 7c108dbf48 | |||
| 5c7cb393dc | |||
| 54b37573c9 | |||
| c3a64c2a59 | |||
| a5c31bbee0 | |||
| d6bd56da2c | |||
| 17d31bfad6 | |||
| 89ffd40d70 | |||
| 737b4fde1c | |||
| 2cfd6b7024 | |||
| 713cd728ba | |||
| f9bea5b791 | |||
| 586641d77f | |||
| 8ca4286624 | |||
| 165118c641 | |||
| 852643e6ba | |||
| 102d080a31 | |||
| ec4cae4f04 | |||
| 4153898c55 | |||
| 33df335ec9 | |||
| 893cc013b5 | |||
| b88e945268 | |||
| bf56e3ea8d | |||
| 284acc37f9 | |||
| 582559f7ac | |||
| 53556d9bd2 | |||
| da79124e5d | |||
| 2f34f1283a | |||
| f973d271cf | |||
| 4625b25287 | |||
| 8d8b3fd071 | |||
| a852c17614 | |||
| b33bf3f704 | |||
| 726788a483 | |||
| cdd3560702 | |||
| 6d49df1d87 | |||
| ac191bd31f | |||
| ad3a3547d6 | |||
| aba14c43ac | |||
| 25c3fd48f0 | |||
| e45aa68c14 | |||
| 6dfab34aca | |||
| d3d805ffb3 | |||
| 05b9724c85 | |||
| 5053f0179c | |||
| 3e4d615983 | |||
| aa8777ee47 | |||
| 98dd59e412 | |||
| 7b2f6aaa1c | |||
| ad943f0189 | |||
| 224496dca7 | |||
| 1ef59417ef | |||
| 0d5a160409 | |||
| 82facb387c | |||
| da7a0df7a1 | |||
| fcbd94b924 | |||
| 5bb3912001 | |||
| f4248b159d | |||
| 2e4edcc7e9 | |||
| 0d3272d914 | |||
| 50f3e77b43 | |||
| 964d91b855 | |||
| bdac3ee120 | |||
| b81c670597 | |||
| 4489df2871 | |||
| 766c17a6a3 | |||
| 018f8a82d6 | |||
| d5a0c004b6 | |||
| 0b6e45c9a2 | |||
| 5c41f64829 | |||
| ad56cf0038 | |||
| 58b5e92c2f | |||
| 0a0752db7c | |||
| 49fbdd6188 | |||
| 1048553e78 | |||
| 63282290db | |||
| 7a1498e7dc | |||
| 0d3cbb8883 | |||
| ce7698c20e | |||
| 382ca20916 | |||
| fe2ad54f60 | |||
| b014879159 | |||
| 81623861c0 | |||
| bd86993035 | |||
| 4ccf135892 | |||
| cba0bd30f5 | |||
| 276d4b8f0d | |||
| 7bdbaca938 | |||
| 7f34080b69 | |||
| 9397555c91 | |||
| 7a00ef1879 | |||
| 35204e3cc5 | |||
| daced956e3 | |||
| 5a2d582a09 | |||
| b878be6f79 | |||
| 1f25f60a68 | |||
| 2e921c88fb | |||
| abaf8c127d | |||
| 0c5fa2cdd5 | |||
| 36cb1f868b | |||
| 037cc679c4 | |||
| db6fad7396 | |||
| 07bfb55658 | |||
| 56078ee099 | |||
| ae4a378294 | |||
| 791d7f764b | |||
| b39a4f3e3b | |||
| 389b50f735 | |||
| 2f6e566a32 | |||
| d5bad374d9 | |||
| 445ed9be2b | |||
| 8694a0f68a | |||
| d3fee9d761 | |||
| 36a6805b7c | |||
| 19bf12aa09 | |||
| 0decdaed1a | |||
| 93b5befe45 | |||
| d72f4a8a79 | |||
| 91247a8fe1 | |||
| 18a0fa02c5 | |||
| 932f330a51 | |||
| 6a77fc4cdd | |||
| 01a888fced | |||
| f0437a4242 | |||
| 32bdad322a | |||
| bf130087e7 | |||
| 38e038a1ab | |||
| 6504607adf | |||
| c8378fce95 | |||
| 1aeaec8d5c | |||
| 2aaf927beb | |||
| bb86b0f526 | |||
| 2f5edebefa | |||
| b5cdb331b0 | |||
| fa4f3aa7ad | |||
| a90fe7211c | |||
| ac6cc5191a | |||
| 38841dd46e | |||
| e722af7f61 | |||
| df372dbd5b | |||
| 5c4d95d539 | |||
| 9ae4de2ab8 | |||
| 7845769d4b | |||
| 33239324b8 | |||
| 82a74ebfa9 | |||
| 35026000bb | |||
| 55cf3d26bf | |||
| 23d29be4d8 | |||
| 1ad0743a52 | |||
| 54af47a138 | |||
| b84ee3ab8f | |||
| e023b96737 | |||
| 3fde4616e0 | |||
| 4812006eb8 | |||
| 740995df3d | |||
| aa5c3da414 | |||
| d419d81841 | |||
| 08942714a2 | |||
| 3c443babb2 | |||
| 326e5cd046 | |||
| 79e98b71d3 | |||
| 96605f700b | |||
| 38b5e01343 | |||
| 06cde91ba3 | |||
| 3891d6a483 | |||
| 5fd7a5f32a | |||
| a01c946c20 | |||
| 84b0da44db | |||
| cdb3a5205c | |||
| eae98feb7a | |||
| 3af078e941 | |||
| 48c1f0ab59 | |||
| 19386814b3 | |||
| 706e58c1c7 | |||
| 9ab94650c8 | |||
| fce86ff3d5 | |||
| 0051cd12e2 | |||
| df159dbae7 | |||
| 94d298755a | |||
| 8b49bbb0fc | |||
| 5f072591ba | |||
| e3fef2dade | |||
| 4c306a6f99 | |||
| 0996cc82a7 | |||
| f293d7c997 | |||
| b2097604d5 | |||
| 4e1c9b71f4 | |||
| 3e2788afdc | |||
| 1a7f508dd9 | |||
| dc8cfcf92a | |||
| ac08d37ca0 | |||
| 4035fec784 | |||
| b071a07c86 | |||
| c27b8a71fd | |||
| b8661e0ae0 | |||
| 21cbac755e | |||
| a3a20f09e2 | |||
| d62f189d72 | |||
| 84713b58e0 | |||
| 294c8426e6 | |||
| 7bbc9a4634 | |||
| 1cd3b693ae | |||
| d1b9b06614 | |||
| 19aefa6a40 | |||
| effecf8595 | |||
| d5e3f72a0b | |||
| 9fb486b2ad | |||
| d70fa4329c | |||
| 3f29a0d6dd | |||
| 0db86a0638 | |||
| dc02566a84 | |||
| 0e84e06756 | |||
| e6b2df4b2b | |||
| 37be8d4091 | |||
| a3930cb470 | |||
| ad402726f1 | |||
| 8ff9f2e44f | |||
| 799a56bbcb | |||
| fbbd43a6d8 | |||
| 367bf0c7ae | |||
| f72a0de074 | |||
| 07baae04c9 | |||
| eb22a49b02 | |||
| ee24f2dd37 | |||
| 12f9b6db63 | |||
| 851780b8f4 | |||
| 5f34ff9f9f | |||
| 2b274fefbb | |||
| 6c681b258c | |||
| 1f91a2fe65 | |||
| 19ff27788c | |||
| 1ebb0bd9d6 | |||
| d505a05d36 | |||
| 07dca737f0 | |||
| 6d34a8344a | |||
| 40b7b9ecdf | |||
| 53bf66ce1e | |||
| fd95f50c56 | |||
| 777e17d80f | |||
| d4a03acbc3 | |||
| c3db24f834 | |||
| 62dcccd7ef | |||
| e288a563e1 | |||
| 6298226238 | |||
| 9321616c80 | |||
| 9890e7cfeb | |||
| 4be48973ad | |||
| f8442e0524 | |||
| 9858989b1c | |||
| 511057ca36 | |||
| 35d8976de4 | |||
| 463003e86a | |||
| 85a2a8815e | |||
| 47397a6a48 | |||
| 2323ca1622 | |||
| fc0a92f8ac | |||
| 97d80d03f9 | |||
| 627872db97 | |||
| a27612ec6a | |||
| 491e4ecc74 | |||
| cd0ce7b76e | |||
| 320387c4ee | |||
| 6424a5a8dd | |||
| 06f5a6c785 | |||
| 8b1d8c8453 | |||
| 0028cb0258 | |||
| b15ee059ad | |||
| 50feeaa285 | |||
| e80ac18324 | |||
| db0196abc7 | |||
| 149c7b88df | |||
| 7afd1b15a8 | |||
| f12672727d | |||
| 2179cc2bc7 | |||
| 961adfedd9 | |||
| 385b9c9922 | |||
| 922df6a438 | |||
| c1a001c331 | |||
| 13fab36639 | |||
| 708389a7ee | |||
| fb6c9af1ae | |||
| 85aa4dd670 | |||
| 74258a170a | |||
| 2fbff25a18 | |||
| 9c150381bf | |||
| 531c4936dd | |||
| 2aae1016ab | |||
| 9c2468ecf7 | |||
| 0a431eef19 | |||
| 9646d1f2b8 | |||
| 5f7aeb3045 | |||
| 1ebaa9fb3b | |||
| c2255c6c19 | |||
| 2884fa5506 | |||
| 31c94bd7f8 | |||
| 5f63e3952e | |||
| d953e40fb3 | |||
| c3a4d7dda2 | |||
| b4a29844e9 | |||
| 0254057f1b | |||
| f34ba44bf8 | |||
| d4d863c4db | |||
| 78c0877994 | |||
| 1d0b37c1d8 | |||
| 7648f2e655 | |||
| d9efa6c8b5 | |||
| 2e372b33a3 | |||
| 687c211a58 | |||
| 4eadd0867d | |||
| 9dba17cf87 | |||
| baef0c291d | |||
| cccd290834 | |||
| 5af85aed3d | |||
| e600e5947b | |||
| d7ae611f76 | |||
| 04a83eb9f7 | |||
| 341e8a3c99 | |||
| e3d8ac559d | |||
| 767948ab46 | |||
| dbcd8da733 | |||
| 905d5459a9 | |||
| dfd5a993a2 | |||
| daceaa65f5 | |||
| c2c07f07db | |||
| c5cbd3260a | |||
| d57e1be89f | |||
| 18dda6ff9d | |||
| 7afc367275 | |||
| c465171b45 | |||
| 02f22a0b3f | |||
| 193fd0eecb | |||
| 01f431b01f | |||
| aebfa4b28e | |||
| a3abe991a4 | |||
| e793f92e67 | |||
| 042f376626 | |||
| d99ed692ba | |||
| 4d00c2b800 | |||
| a5605e94b1 | |||
| 9cfe20cfb4 | |||
| db3501f61b | |||
| 48538aa792 | |||
| 5fd64f48ee | |||
| a3b7126875 | |||
| 5756c8a7c6 | |||
| cdd0061869 | |||
| e6adfd8054 | |||
| 6d59223efd | |||
| ebd034fff9 | |||
| 000bda8419 | |||
| 423b842347 | |||
| 16eeae36d7 | |||
| 5453e92bcb | |||
| c16c9535d9 | |||
| 662c7b1e71 | |||
| 571170fd30 | |||
| 911c428dac | |||
| 40822be595 | |||
| f5e51a29b5 | |||
| beaad719ad | |||
| 0d257c61cd | |||
| dd8fa1d690 | |||
| af6df7d7c9 | |||
| 57a17a708b | |||
| 1f79082921 | |||
| afff9478c8 | |||
| b352b42afc | |||
| c3e4509d17 | |||
| 664be28941 | |||
| d7e970ac32 | |||
| 810dcfc602 | |||
| 14645b147f | |||
| b036cc9013 | |||
| 3a8ac92995 | |||
| 67bedcba4b | |||
| 4987ef89f1 | |||
| 545b345eea | |||
| 3e961af5f1 | |||
| 9e6e769141 | |||
| c0f746a251 | |||
| ccbdf086ff | |||
| d260ca6738 | |||
| b0e64ca7e8 | |||
| 7f3d4cb504 | |||
| 97be36d18a | |||
| be76da2c90 | |||
| b1a8357f50 | |||
| 0dc900ba34 | |||
| 700624119b | |||
| af32aba7fc | |||
| 541c79c01a | |||
| 358226468d | |||
| d1e5f34f76 | |||
| 416dd93bf7 | |||
| 7c80577160 | |||
| a5d6fda433 | |||
| 4bc3796f9b | |||
| 5e12f5a746 | |||
| 4080ba4026 | |||
| e17593be94 | |||
| 44aa69748c | |||
| 9cd5ea59dd | |||
| 116e98b378 | |||
| ae25787f48 | |||
| 08dc829b70 | |||
| 3ac674cf02 | |||
| 6cd06ab95f | |||
| 99080d41f3 | |||
| 92f3567ee6 | |||
| 481fc8a5b6 | |||
| 5dfff51a40 | |||
| 2938ec028f | |||
| 981b9fb7a8 | |||
| 10f4d8df32 | |||
| e15858e2be | |||
| 6d8302825c | |||
| fae8bf96dd | |||
| bc59dc6389 | |||
| bd8e2f82be | |||
| 69027b6840 | |||
| 7a9b7af078 | |||
| 31c7afce1b | |||
| bab1873416 | |||
| 5e2950e9fb | |||
| 74c019f271 | |||
| a745bb8f42 | |||
| da9870c77d | |||
| 698eeaf7c3 | |||
| bd954d9990 | |||
| 4ded42a33b | |||
| 1877965ac3 | |||
| cec2309504 | |||
| ed5a4afc8c | |||
| f29b51efdd | |||
| 3930a32b0c | |||
| 76707e1d2f | |||
| ff2e5c3efe | |||
| 3600b2e209 | |||
| eb36a0f3b1 | |||
| b397254696 | |||
| 9a37d3b6e5 | |||
| 51c4a19234 | |||
| a1a1668dcf | |||
| e308098f18 | |||
| 63b572a0ab | |||
| e90e7d4af9 | |||
| 460e14e586 | |||
| ebcabb8a27 | |||
| 257b749e9d | |||
| d9e6ade030 | |||
| 690374d4de | |||
| adb25d9d19 | |||
| 2949e4b0c7 | |||
| 6de0181c75 | |||
| 8a6b743d2a | |||
| ed8a6f872d | |||
| 0a61d4bf2b | |||
| 332461c0d2 | |||
| d8abe7fc4d | |||
| ec9667ef5a | |||
| 9f7154a039 | |||
| 4a932616fc | |||
| 73fe36f19c | |||
| 4167ae95ae | |||
| 13c3c5be95 | |||
| 3afec0bcbe | |||
| 8b1bfc80fb | |||
| 3662275119 | |||
| 141573c18c | |||
| e692efbe09 | |||
| f6c1a5bf6e | |||
| 6afc28f827 | |||
| df6681ad4e | |||
| 114efbc57c | |||
| 2c71494ad1 | |||
| 35230b1a11 | |||
| 7acbf8b7b9 | |||
| f7413ca974 | |||
| 9b3a0251ca | |||
| 3b9ca8535f | |||
| a98eee145d | |||
| c6b9b3b9a4 | |||
| a4be1a5e4c | |||
| ee908c00de | |||
| 46264032aa | |||
| 4b831dbddd | |||
| a01d6dcea7 | |||
| 01a15f78e6 | |||
| 5f71c41582 | |||
| e8f2c5a48a | |||
| 1c76577918 | |||
| 4285b58aaa | |||
| 9cc366de97 | |||
| 212d294b84 | |||
| d6125f05d4 | |||
| ad22eda87c | |||
| 8d31ba492d | |||
| 8f611b3399 | |||
| 618f858930 | |||
| 185277a2b6 | |||
| 8e5c3141f6 | |||
| 87aaa9c3f0 | |||
| 095605b7db | |||
| 6836b501af | |||
| d949d5a046 | |||
| 064516bf0b | |||
| eaf88bcc4d | |||
| 76d3bab955 | |||
| 02f9668185 | |||
| abda75feee | |||
| a779cb2798 | |||
| 5e42675b42 | |||
| 11c565e199 | |||
| 4689a6b341 | |||
| 30a5808460 | |||
| 8ba1c9a6cd | |||
| 65b78b1aa9 | |||
| c369719362 | |||
| 40a12cca53 | |||
| d8940f5fd7 | |||
| 74dee82dbf | |||
| f1cf724bd4 | |||
| fc809d3fad | |||
| 42d135aade | |||
| ab12ca69af | |||
| 17748f18b9 | |||
| 3803bdff5f | |||
| 0a94a67190 | |||
| 8dcb14fbc2 | |||
| f62f40e508 | |||
| 980aa37bee | |||
| 1e9cc058a0 | |||
| 824efc82b9 | |||
| 1cfe226686 | |||
| ea0ce7bd2c | |||
| 342adb627d | |||
| a70635d2f6 | |||
| 5cb16c4cd1 | |||
| 538004ba5f | |||
| 8663c64e47 | |||
| bb3ffdbdfb | |||
| fcf7cf973b | |||
| fb2e1c32bd | |||
| ae0b8fce44 | |||
| d2e55f9ffa | |||
| d5e2b387fa | |||
| 922b511a24 | |||
| 9de9111082 | |||
| 5a7fe2e3d9 | |||
| 1e8c12c2a3 | |||
| b92c70e55c | |||
| 352494cbb4 | |||
| 797c41950a | |||
| e036a321a0 | |||
| 18471f358e | |||
| 55d09251d8 | |||
| d66fd71d21 | |||
| f10fc2a494 | |||
| 055d8c5370 | |||
| 955472bd21 | |||
| a2d5f660ed | |||
| 8a4b9ddaa3 | |||
| 54a2b6f00e | |||
| abe728dbbb | |||
| 574ceb37a9 | |||
| dbdc4471c2 | |||
| af95381bf8 | |||
| 41f2539484 | |||
| 8f57bb0799 | |||
| 84a4188c72 | |||
| 31f1e0aeae | |||
| 4b8cbb5a3b | |||
| 3e7aabe6d8 | |||
| d4df325e0a | |||
| 9f8a63cb43 | |||
| 6f745677b4 | |||
| 86649e6b44 | |||
| 3ca887a60a | |||
| 312a1e8a94 | |||
| 9a311ab9e7 | |||
| 7d92eaaeb2 | |||
| 582f423b67 | |||
| c2524464f9 | |||
| 55e472cdb6 | |||
| e6ad773a88 | |||
| 82eae32bca | |||
| b0e21bd616 | |||
| 7b88bed098 | |||
| 28abf5d33b | |||
| fb8ad338d0 | |||
| ee60be0137 | |||
| 9efd4c5097 | |||
| f30e6f50bd | |||
| 8ec2ca9dcd | |||
| f19bb0f4d4 | |||
| 76bb82a726 | |||
| 8e62f9c83c | |||
| 696f758435 | |||
| 6421152104 | |||
| 1e989f5c10 | |||
| 3eb42ecb55 | |||
| 286cb60c45 | |||
| 28e9a4f79c | |||
| e79fb68291 | |||
| 16eaa17ed9 | |||
| 9491c13a5c | |||
| 8d8adac1b4 | |||
| ba6c63e366 | |||
| 963b1a12d7 | |||
| 89890d7900 | |||
| 661fcad895 | |||
| cef16feb0b | |||
| 84b851f578 | |||
| 785c27daa7 | |||
| 795a5910cf | |||
| 4c21f9495d | |||
| f119fd1ee1 | |||
| 46ab822316 | |||
| 104aea9f42 | |||
| a2f2041aa6 | |||
| 599d18f26f | |||
| 61084d832d | |||
| b957996577 | |||
| f41150f8e9 | |||
| 21adad4e09 | |||
| 47f9e8f850 | |||
| ba3f2a6a0c | |||
| b9888f8f68 | |||
| 32cda5d56a | |||
| a4d9847f45 | |||
| 8aa6d70dec | |||
| ea6b222430 | |||
| 91b4bf3daa |
+24
-15
@@ -10,12 +10,15 @@ jobs:
|
||||
run: sudo apt-get install llvm-11 clang-11 llvm
|
||||
- name: build odin
|
||||
run: make release
|
||||
- name: Odin run
|
||||
run: ./odin run examples/demo/demo.odin
|
||||
- name: Odin check
|
||||
run: ./odin check examples/demo/demo.odin -vet
|
||||
- name: Odin version
|
||||
run: ./odin version
|
||||
timeout-minutes: 1
|
||||
- name: Odin check
|
||||
run: ./odin check examples/demo/demo.odin -vet
|
||||
timeout-minutes: 10
|
||||
- name: Odin run
|
||||
run: ./odin run examples/demo/demo.odin
|
||||
timeout-minutes: 10
|
||||
build_macOS:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
@@ -28,12 +31,15 @@ jobs:
|
||||
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
|
||||
- name: build odin
|
||||
run: make release
|
||||
- name: Odin run
|
||||
run: ./odin run examples/demo/demo.odin
|
||||
- name: Odin check
|
||||
run: ./odin check examples/demo/demo.odin -vet
|
||||
- name: Odin version
|
||||
run: ./odin version
|
||||
timeout-minutes: 1
|
||||
- name: Odin check
|
||||
run: ./odin check examples/demo/demo.odin -vet
|
||||
timeout-minutes: 10
|
||||
- name: Odin run
|
||||
run: ./odin run examples/demo/demo.odin
|
||||
timeout-minutes: 10
|
||||
build_windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
@@ -43,17 +49,20 @@ jobs:
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||
./build.bat 1
|
||||
- name: Odin run
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||
odin run examples/demo/demo.odin
|
||||
- name: Odin version
|
||||
run: ./odin version
|
||||
timeout-minutes: 1
|
||||
- name: Odin check
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||
odin check examples/demo/demo.odin -vet
|
||||
- name: Odin version
|
||||
run: ./odin version
|
||||
timeout-minutes: 10
|
||||
- name: Odin run
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||
odin run examples/demo/demo.odin
|
||||
timeout-minutes: 10
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ jobs:
|
||||
cp LLVM-C.dll dist
|
||||
cp -r shared dist
|
||||
cp -r core dist
|
||||
cp -r vendor dist
|
||||
cp -r bin dist
|
||||
cp -r examples dist
|
||||
- name: Upload artifact
|
||||
@@ -51,6 +52,7 @@ jobs:
|
||||
cp odin dist
|
||||
cp -r shared dist
|
||||
cp -r core dist
|
||||
cp -r vendor dist
|
||||
cp -r examples dist
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
@@ -77,6 +79,7 @@ jobs:
|
||||
cp odin dist
|
||||
cp -r shared dist
|
||||
cp -r core dist
|
||||
cp -r vendor dist
|
||||
cp -r examples dist
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
|
||||
@@ -3,12 +3,16 @@ Copyright (c) 2016-2021 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:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GIT_SHA=$(shell git rev-parse --short HEAD)
|
||||
DISABLED_WARNINGS=-Wno-switch -Wno-pointer-sign -Wno-tautological-constant-out-of-range-compare -Wno-tautological-compare -Wno-macro-redefined
|
||||
DISABLED_WARNINGS=-Wno-switch -Wno-macro-redefined -Wno-unused-value
|
||||
LDFLAGS=-pthread -ldl -lm -lstdc++
|
||||
CFLAGS=-std=c++14 -DGIT_SHA=\"$(GIT_SHA)\"
|
||||
CFLAGS:=$(CFLAGS) -DODIN_VERSION_RAW=\"dev-$(shell date +"%Y-%m")\"
|
||||
@@ -41,13 +41,13 @@ demo:
|
||||
./odin run examples/demo/demo.odin
|
||||
|
||||
debug:
|
||||
$(CC) src/main.cpp $(DISABLED_WARNINGS) $(CFLAGS) -g $(LDFLAGS) -o odin
|
||||
$(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -g $(LDFLAGS) -o odin
|
||||
|
||||
release:
|
||||
$(CC) src/main.cpp $(DISABLED_WARNINGS) $(CFLAGS) -O3 -march=native $(LDFLAGS) -o odin
|
||||
$(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -O3 $(LDFLAGS) -o odin
|
||||
|
||||
release_native:
|
||||
$(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -O3 -march=native $(LDFLAGS) -o odin
|
||||
|
||||
nightly:
|
||||
$(CC) src/main.cpp $(DISABLED_WARNINGS) $(CFLAGS) -DNIGHTLY -O3 $(LDFLAGS) -o odin
|
||||
|
||||
|
||||
|
||||
$(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -DNIGHTLY -O3 $(LDFLAGS) -o odin
|
||||
|
||||
@@ -46,10 +46,8 @@ if %release_mode% EQU 0 ( rem Debug
|
||||
|
||||
set compiler_warnings= ^
|
||||
-W4 -WX ^
|
||||
-wd4100 -wd4101 -wd4127 -wd4189 ^
|
||||
-wd4201 -wd4204 ^
|
||||
-wd4456 -wd4457 -wd4480 ^
|
||||
-wd4512
|
||||
-wd4100 -wd4101 -wd4127 -wd4146 ^
|
||||
-wd4456 -wd4457
|
||||
|
||||
set compiler_includes=
|
||||
set libs= ^
|
||||
@@ -70,10 +68,10 @@ set linker_settings=%libs% %linker_flags%
|
||||
del *.pdb > NUL 2> NUL
|
||||
del *.ilk > NUL 2> NUL
|
||||
|
||||
cl %compiler_settings% "src\main.cpp" /link %linker_settings% -OUT:%exe_name%
|
||||
cl %compiler_settings% "src\main.cpp" "src\libtommath.cpp" /link %linker_settings% -OUT:%exe_name%
|
||||
|
||||
if %errorlevel% neq 0 goto end_of_build
|
||||
if %release_mode% EQU 0 odin run examples/demo/demo.odin
|
||||
if %release_mode% EQU 0 odin run examples/demo
|
||||
|
||||
del *.obj > NUL 2> NUL
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ def main():
|
||||
name = remove_prefix(data['fileName'], "nightly/")
|
||||
url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}"
|
||||
sha1 = data['contentSha1']
|
||||
size = int(data['contentLength'])
|
||||
size = int(data['size'])
|
||||
ts = int(data['fileInfo']['src_last_modified_millis'])
|
||||
date = datetime.datetime.fromtimestamp(ts/1000).strftime('%Y-%m-%d')
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package bufio
|
||||
|
||||
import "core:io"
|
||||
|
||||
// Loadahead_Reader provides io lookahead.
|
||||
// This is useful for tokenizers/parsers.
|
||||
// Loadahead_Reader is similar to bufio.Reader, but unlike bufio.Reader, Loadahead_Reader's buffer size
|
||||
// will EXACTLY match the specified size, whereas bufio.Reader's buffer size may differ from the specified size.
|
||||
// This makes sure that the buffer will not be accidentally read beyond the expected size.
|
||||
Loadahead_Reader :: struct {
|
||||
r: io.Reader,
|
||||
buf: []byte,
|
||||
n: int,
|
||||
}
|
||||
|
||||
lookahead_reader_init :: proc(lr: ^Loadahead_Reader, r: io.Reader, buf: []byte) -> ^Loadahead_Reader {
|
||||
lr.r = r;
|
||||
lr.buf = buf;
|
||||
lr.n = 0;
|
||||
return lr;
|
||||
}
|
||||
|
||||
lookahead_reader_buffer :: proc(lr: ^Loadahead_Reader) -> []byte {
|
||||
return lr.buf[:lr.n];
|
||||
}
|
||||
|
||||
|
||||
// lookahead_reader_peek returns a slice of the Lookahead_Reader which holds n bytes
|
||||
// If the Lookahead_Reader cannot hold enough bytes, it will read from the underlying reader to populate the rest.
|
||||
// NOTE: The returned buffer is not a copy of the underlying buffer
|
||||
lookahead_reader_peek :: proc(lr: ^Loadahead_Reader, n: int) -> ([]byte, io.Error) {
|
||||
switch {
|
||||
case n < 0:
|
||||
return nil, .Negative_Read;
|
||||
case n > len(lr.buf):
|
||||
return nil, .Buffer_Full;
|
||||
}
|
||||
|
||||
n := n;
|
||||
err: io.Error;
|
||||
read_count: int;
|
||||
|
||||
if lr.n < n {
|
||||
read_count, err = io.read_at_least(lr.r, lr.buf[lr.n:], n-lr.n);
|
||||
if err == .Unexpected_EOF {
|
||||
err = .EOF;
|
||||
}
|
||||
}
|
||||
|
||||
lr.n += read_count;
|
||||
|
||||
if n > lr.n {
|
||||
n = lr.n;
|
||||
}
|
||||
return lr.buf[:n], err;
|
||||
}
|
||||
|
||||
// lookahead_reader_peek_all returns a slice of the Lookahead_Reader populating the full buffer
|
||||
// If the Lookahead_Reader cannot hold enough bytes, it will read from the underlying reader to populate the rest.
|
||||
// NOTE: The returned buffer is not a copy of the underlying buffer
|
||||
lookahead_reader_peek_all :: proc(lr: ^Loadahead_Reader) -> ([]byte, io.Error) {
|
||||
return lookahead_reader_peek(lr, len(lr.buf));
|
||||
}
|
||||
|
||||
|
||||
// lookahead_reader_consume drops the first n populated bytes from the Lookahead_Reader.
|
||||
lookahead_reader_consume :: proc(lr: ^Loadahead_Reader, n: int) -> io.Error {
|
||||
switch {
|
||||
case n == 0:
|
||||
return nil;
|
||||
case n < 0:
|
||||
return .Negative_Read;
|
||||
case lr.n < n:
|
||||
return .Short_Buffer;
|
||||
}
|
||||
copy(lr.buf, lr.buf[n:lr.n]);
|
||||
lr.n -= n;
|
||||
return nil;
|
||||
}
|
||||
|
||||
lookahead_reader_consume_all :: proc(lr: ^Loadahead_Reader) -> io.Error {
|
||||
return lookahead_reader_consume(lr, lr.n);
|
||||
}
|
||||
@@ -17,6 +17,8 @@ Reader :: struct {
|
||||
|
||||
last_byte: int, // last byte read, invalid is -1
|
||||
last_rune_size: int, // size of last rune read, invalid is -1
|
||||
|
||||
max_consecutive_empty_reads: int,
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +27,7 @@ DEFAULT_BUF_SIZE :: 4096;
|
||||
@(private)
|
||||
MIN_READ_BUFFER_SIZE :: 16;
|
||||
@(private)
|
||||
MAX_CONSECUTIVE_EMPTY_READS :: 128;
|
||||
DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128;
|
||||
|
||||
reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) {
|
||||
size := size;
|
||||
@@ -71,8 +73,12 @@ _reader_read_new_chunk :: proc(b: ^Reader) -> io.Error {
|
||||
return .Buffer_Full;
|
||||
}
|
||||
|
||||
if b.max_consecutive_empty_reads <= 0 {
|
||||
b.max_consecutive_empty_reads = DEFAULT_MAX_CONSECUTIVE_EMPTY_READS;
|
||||
}
|
||||
|
||||
// read new data, and try a limited number of times
|
||||
for i := MAX_CONSECUTIVE_EMPTY_READS; i > 0; i -= 1 {
|
||||
for i := b.max_consecutive_empty_reads; i > 0; i -= 1 {
|
||||
n, err := io.read(b.rd, b.buf[b.w:]);
|
||||
if n < 0 {
|
||||
return .Negative_Read;
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
package bufio
|
||||
|
||||
import "core:bytes"
|
||||
import "core:io"
|
||||
import "core:mem"
|
||||
import "core:unicode/utf8"
|
||||
import "core:intrinsics"
|
||||
|
||||
// Extra errors returns by scanning procedures
|
||||
Scanner_Extra_Error :: enum i32 {
|
||||
Negative_Advance,
|
||||
Advanced_Too_Far,
|
||||
Bad_Read_Count,
|
||||
Too_Long,
|
||||
Too_Short,
|
||||
}
|
||||
|
||||
Scanner_Error :: union {
|
||||
io.Error,
|
||||
Scanner_Extra_Error,
|
||||
}
|
||||
|
||||
// Split_Proc is the signature of the split procedure used to tokenize the input.
|
||||
Split_Proc :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool);
|
||||
|
||||
Scanner :: struct {
|
||||
r: io.Reader,
|
||||
split: Split_Proc,
|
||||
|
||||
buf: [dynamic]byte,
|
||||
max_token_size: int,
|
||||
start: int,
|
||||
end: int,
|
||||
token: []byte,
|
||||
|
||||
_err: Scanner_Error,
|
||||
max_consecutive_empty_reads: int,
|
||||
successive_empty_token_count: int,
|
||||
scan_called: bool,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
DEFAULT_MAX_SCAN_TOKEN_SIZE :: 1<<16;
|
||||
|
||||
@(private)
|
||||
_INIT_BUF_SIZE :: 4096;
|
||||
|
||||
scanner_init :: proc(s: ^Scanner, r: io.Reader, buf_allocator := context.allocator) -> ^Scanner {
|
||||
s.r = r;
|
||||
s.split = scan_lines;
|
||||
s.max_token_size = DEFAULT_MAX_SCAN_TOKEN_SIZE;
|
||||
s.buf.allocator = buf_allocator;
|
||||
return s;
|
||||
}
|
||||
scanner_init_with_buffer :: proc(s: ^Scanner, r: io.Reader, buf: []byte) -> ^Scanner {
|
||||
s.r = r;
|
||||
s.split = scan_lines;
|
||||
s.max_token_size = DEFAULT_MAX_SCAN_TOKEN_SIZE;
|
||||
s.buf = mem.buffer_from_slice(buf);
|
||||
resize(&s.buf, cap(s.buf));
|
||||
return s;
|
||||
}
|
||||
scanner_destroy :: proc(s: ^Scanner) {
|
||||
delete(s.buf);
|
||||
}
|
||||
|
||||
|
||||
// Returns the first non-EOF error that was encounted by the scanner
|
||||
scanner_error :: proc(s: ^Scanner) -> Scanner_Error {
|
||||
switch s._err {
|
||||
case .EOF, .None:
|
||||
return nil;
|
||||
}
|
||||
return s._err;
|
||||
}
|
||||
|
||||
// Returns the most recent token created by scanner_scan.
|
||||
// The underlying array may point to data that may be overwritten
|
||||
// by another call to scanner_scan.
|
||||
// Treat the returned value as if it is immutable.
|
||||
scanner_bytes :: proc(s: ^Scanner) -> []byte {
|
||||
return s.token;
|
||||
}
|
||||
|
||||
// Returns the most recent token created by scanner_scan.
|
||||
// The underlying array may point to data that may be overwritten
|
||||
// by another call to scanner_scan.
|
||||
// Treat the returned value as if it is immutable.
|
||||
scanner_text :: proc(s: ^Scanner) -> string {
|
||||
return string(s.token);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
if s.done {
|
||||
return false;
|
||||
}
|
||||
s.scan_called = true;
|
||||
|
||||
for {
|
||||
// Check if a token is possible with what is available
|
||||
// Allow the split procedure to recover if it fails
|
||||
if s.start < s.end || s._err != nil {
|
||||
advance, token, err, final_token := s.split(s.buf[s.start:s.end], s._err != nil);
|
||||
if final_token {
|
||||
s.token = token;
|
||||
s.done = true;
|
||||
return true;
|
||||
}
|
||||
if err != nil {
|
||||
set_err(s, err);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do advance
|
||||
if advance < 0 {
|
||||
set_err(s, .Negative_Advance);
|
||||
return false;
|
||||
}
|
||||
if advance > s.end-s.start {
|
||||
set_err(s, .Advanced_Too_Far);
|
||||
return false;
|
||||
}
|
||||
s.start += advance;
|
||||
|
||||
s.token = token;
|
||||
if s.token != nil {
|
||||
if s._err == nil || advance > 0 {
|
||||
s.successive_empty_token_count = 0;
|
||||
} else {
|
||||
s.successive_empty_token_count += 1;
|
||||
|
||||
if s.max_consecutive_empty_reads <= 0 {
|
||||
s.max_consecutive_empty_reads = DEFAULT_MAX_CONSECUTIVE_EMPTY_READS;
|
||||
}
|
||||
if s.successive_empty_token_count > s.max_consecutive_empty_reads {
|
||||
set_err(s, .No_Progress);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If an error is hit, no token can be created
|
||||
if s._err != nil {
|
||||
s.start = 0;
|
||||
s.end = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// More data must be required to be read
|
||||
if s.start > 0 && (s.end == len(s.buf) || s.start > len(s.buf)/2) {
|
||||
copy(s.buf[:], s.buf[s.start:s.end]);
|
||||
s.end -= s.start;
|
||||
s.start = 0;
|
||||
}
|
||||
|
||||
could_be_too_short := false;
|
||||
|
||||
// Resize the buffer if full
|
||||
if s.end == len(s.buf) {
|
||||
if s.max_token_size <= 0 {
|
||||
s.max_token_size = DEFAULT_MAX_SCAN_TOKEN_SIZE;
|
||||
}
|
||||
if len(s.buf) >= s.max_token_size {
|
||||
set_err(s, .Too_Long);
|
||||
return false;
|
||||
}
|
||||
// overflow check
|
||||
new_size := _INIT_BUF_SIZE;
|
||||
if len(s.buf) > 0 {
|
||||
overflowed: bool;
|
||||
if new_size, overflowed = intrinsics.overflow_mul(len(s.buf), 2); overflowed {
|
||||
set_err(s, .Too_Long);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
old_size := len(s.buf);
|
||||
new_size = min(new_size, s.max_token_size);
|
||||
resize(&s.buf, new_size);
|
||||
s.end -= s.start;
|
||||
s.start = 0;
|
||||
|
||||
could_be_too_short = old_size >= len(s.buf);
|
||||
|
||||
}
|
||||
|
||||
// Read data into the buffer
|
||||
loop := 0;
|
||||
for {
|
||||
n, err := io.read(s.r, s.buf[s.end:len(s.buf)]);
|
||||
if n < 0 || len(s.buf)-s.end < n {
|
||||
set_err(s, .Bad_Read_Count);
|
||||
break;
|
||||
}
|
||||
s.end += n;
|
||||
if err != nil {
|
||||
set_err(s, err);
|
||||
break;
|
||||
}
|
||||
if n > 0 {
|
||||
s.successive_empty_token_count = 0;
|
||||
break;
|
||||
}
|
||||
loop += 1;
|
||||
|
||||
if s.max_consecutive_empty_reads <= 0 {
|
||||
s.max_consecutive_empty_reads = DEFAULT_MAX_CONSECUTIVE_EMPTY_READS;
|
||||
}
|
||||
if loop > s.max_consecutive_empty_reads {
|
||||
if could_be_too_short {
|
||||
set_err(s, .Too_Short);
|
||||
} else {
|
||||
set_err(s, .No_Progress);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scan_bytes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
|
||||
if at_eof && len(data) == 0 {
|
||||
return;
|
||||
}
|
||||
return 1, data[0:1], nil, false;
|
||||
}
|
||||
|
||||
scan_runes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
|
||||
if at_eof && len(data) == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if data[0] < utf8.RUNE_SELF {
|
||||
advance = 1;
|
||||
token = data[0:1];
|
||||
return;
|
||||
}
|
||||
|
||||
_, width := utf8.decode_rune(data);
|
||||
if width > 1 {
|
||||
advance = width;
|
||||
token = data[0:width];
|
||||
return;
|
||||
}
|
||||
|
||||
if !at_eof && !utf8.full_rune(data) {
|
||||
return;
|
||||
}
|
||||
|
||||
@thread_local ERROR_RUNE := []byte{0xef, 0xbf, 0xbd};
|
||||
|
||||
advance = 1;
|
||||
token = ERROR_RUNE;
|
||||
return;
|
||||
}
|
||||
|
||||
scan_words :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
|
||||
is_space :: proc "contextless" (r: rune) -> bool {
|
||||
switch r {
|
||||
// lower ones
|
||||
case ' ', '\t', '\n', '\v', '\f', '\r':
|
||||
return true;
|
||||
case '\u0085', '\u00a0':
|
||||
return true;
|
||||
// higher ones
|
||||
case '\u2000' ..= '\u200a':
|
||||
return true;
|
||||
case '\u1680', '\u2028', '\u2029', '\u202f', '\u205f', '\u3000':
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// skip spaces at the beginning
|
||||
start := 0;
|
||||
for width := 0; start < len(data); start += width {
|
||||
r: rune;
|
||||
r, width = utf8.decode_rune(data[start:]);
|
||||
if !is_space(r) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for width, i := 0, start; i < len(data); i += width {
|
||||
r: rune;
|
||||
r, width = utf8.decode_rune(data[i:]);
|
||||
if is_space(r) {
|
||||
advance = i+width;
|
||||
token = data[start:i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if at_eof && len(data) > start {
|
||||
advance = len(data);
|
||||
token = data[start:];
|
||||
return;
|
||||
}
|
||||
|
||||
advance = start;
|
||||
return;
|
||||
}
|
||||
|
||||
scan_lines :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
|
||||
trim_carriage_return :: proc "contextless" (data: []byte) -> []byte {
|
||||
if len(data) > 0 && data[len(data)-1] == '\r' {
|
||||
return data[0:len(data)-1];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
if at_eof && len(data) == 0 {
|
||||
return;
|
||||
}
|
||||
if i := bytes.index_byte(data, '\n'); i >= 0 {
|
||||
advance = i+1;
|
||||
token = trim_carriage_return(data[0:i]);
|
||||
return;
|
||||
}
|
||||
|
||||
if at_eof {
|
||||
advance = len(data);
|
||||
token = trim_carriage_return(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -15,6 +15,8 @@ Writer :: struct {
|
||||
|
||||
err: io.Error,
|
||||
|
||||
max_consecutive_empty_writes: int,
|
||||
|
||||
}
|
||||
|
||||
writer_init :: proc(b: ^Writer, wr: io.Writer, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) {
|
||||
@@ -181,20 +183,22 @@ writer_read_from :: proc(b: ^Writer, r: io.Reader) -> (n: i64, err: io.Error) {
|
||||
|
||||
for {
|
||||
if writer_available(b) == 0 {
|
||||
if ferr := writer_flush(b); ferr != nil {
|
||||
return n, ferr;
|
||||
}
|
||||
writer_flush(b) or_return;
|
||||
}
|
||||
if b.max_consecutive_empty_writes <= 0 {
|
||||
b.max_consecutive_empty_writes = DEFAULT_MAX_CONSECUTIVE_EMPTY_READS;
|
||||
}
|
||||
|
||||
m: int;
|
||||
nr := 0;
|
||||
for nr < MAX_CONSECUTIVE_EMPTY_READS {
|
||||
for nr < b.max_consecutive_empty_writes {
|
||||
m, err = io.read(r, b.buf[b.n:]);
|
||||
if m != 0 || err != nil {
|
||||
break;
|
||||
}
|
||||
nr += 1;
|
||||
}
|
||||
if nr == MAX_CONSECUTIVE_EMPTY_READS {
|
||||
if nr == b.max_consecutive_empty_writes {
|
||||
return n, .No_Progress;
|
||||
}
|
||||
b.n += m;
|
||||
|
||||
@@ -91,7 +91,7 @@ equal_fold :: proc(u, v: []byte) -> bool {
|
||||
|
||||
if tr < utf8.RUNE_SELF {
|
||||
switch sr {
|
||||
case 'A'..'Z':
|
||||
case 'A'..='Z':
|
||||
if tr == (sr+'a')-'A' {
|
||||
continue loop;
|
||||
}
|
||||
|
||||
@@ -139,18 +139,18 @@ append_token :: proc(a, b: ^Token) -> ^Token {
|
||||
|
||||
is_hex_digit :: proc(x: byte) -> bool {
|
||||
switch x {
|
||||
case '0'..'9', 'a'..'f', 'A'..'F':
|
||||
case '0'..='9', 'a'..='f', 'A'..='F':
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
from_hex :: proc(x: byte) -> i32 {
|
||||
switch x {
|
||||
case '0'..'9':
|
||||
case '0'..='9':
|
||||
return i32(x) - '0';
|
||||
case 'a'..'f':
|
||||
case 'a'..='f':
|
||||
return i32(x) - 'a' + 10;
|
||||
case 'A'..'F':
|
||||
case 'A'..='F':
|
||||
return i32(x) - 'A' + 10;
|
||||
}
|
||||
return 16;
|
||||
|
||||
@@ -5,9 +5,9 @@ import "core:unicode/utf8"
|
||||
unquote_char :: proc(str: string, quote: byte) -> (r: rune, multiple_bytes: bool, tail_string: string, success: bool) {
|
||||
hex_to_int :: proc(c: byte) -> int {
|
||||
switch c {
|
||||
case '0'..'9': return int(c-'0');
|
||||
case 'a'..'f': return int(c-'a')+10;
|
||||
case 'A'..'F': return int(c-'A')+10;
|
||||
case '0'..='9': return int(c-'0');
|
||||
case 'a'..='f': return int(c-'a')+10;
|
||||
case 'A'..='F': return int(c-'A')+10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ unquote_char :: proc(str: string, quote: byte) -> (r: rune, multiple_bytes: bool
|
||||
case '"': r = '"';
|
||||
case '\'': r = '\'';
|
||||
|
||||
case '0'..'7':
|
||||
case '0'..='7':
|
||||
v := int(c-'0');
|
||||
if len(s) < 2 {
|
||||
return;
|
||||
|
||||
@@ -224,11 +224,11 @@ scan_string :: proc(t: ^Tokenizer) -> string {
|
||||
|
||||
digit_val :: proc(r: rune) -> int {
|
||||
switch r {
|
||||
case '0'..'9':
|
||||
case '0'..='9':
|
||||
return int(r-'0');
|
||||
case 'A'..'F':
|
||||
case 'A'..='F':
|
||||
return int(r-'A' + 10);
|
||||
case 'a'..'f':
|
||||
case 'a'..='f':
|
||||
return int(r-'a' + 10);
|
||||
}
|
||||
return 16;
|
||||
@@ -245,7 +245,7 @@ scan_escape :: proc(t: ^Tokenizer) -> bool {
|
||||
advance_rune(t);
|
||||
return true;
|
||||
|
||||
case '0'..'7':
|
||||
case '0'..='7':
|
||||
for digit_val(t.ch) < 8 {
|
||||
advance_rune(t);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# C support
|
||||
|
||||
The following is a mostly-complete projection of the C11 standard library as defined by the C11 specification: N1570, or ISO/IEC 9899:2011. Only the macros, types, and functions as required by the standard are projected. Extensions to C, such as POSIX are not handled by these bindings, this is otherwise portable to any implementation which can support a hosted C runtime.
|
||||
|
||||
## Support matrix
|
||||
| Header | Status |
|
||||
|:------------------|:---------------------------------------------------|
|
||||
| `<assert.h>` | Not applicable, use Odin's `#assert` |
|
||||
| `<complex.h>` | Mostly projected, see [limitations](#Limitations) |
|
||||
| `<ctype.h>` | Fully projected |
|
||||
| `<errno.h>` | Fully projected |
|
||||
| `<fenv.h>` | Not projected |
|
||||
| `<float.h>` | Not projected |
|
||||
| `<inttypes.h>` | Fully projected |
|
||||
| `<iso646.h>` | Not applicable, use Odin's operators |
|
||||
| `<limits.h>` | Not projected |
|
||||
| `<locale.h>` | Not projected |
|
||||
| `<math.h>` | Mostly projected, see [limitations](#Limitations) |
|
||||
| `<setjmp.h>` | Fully projected |
|
||||
| `<signal.h>` | Fully projected |
|
||||
| `<stdalign.h>` | Not applicable, use Odin's `#align` |
|
||||
| `<stdarg.h>` | Mostly projected, see [limitations](#Limitations) |
|
||||
| `<stdatomic.h>` | Fully projected |
|
||||
| `<stdbool.h>` | Not applicable, use Odin's `b32` |
|
||||
| `<stddef.h>` | Mostly projected, see [limitations](#Limitations) |
|
||||
| `<stdint.h>` | Fully projected |
|
||||
| `<stdio.h>` | Fully projected |
|
||||
| `<stdlib.h>` | Fully projected |
|
||||
| `<stdnoreturn.h>` | Not applicable, use Odin's divergent return `!` |
|
||||
| `<string.h>` | Fully projected |
|
||||
| `<tgmath.h>` | Mostly projected, see [limitations](#Limitations) |
|
||||
| `<threads.h>` | Fully projected |
|
||||
| `<time.h>` | Fully projected |
|
||||
| `<uchar.h>` | Fully projected |
|
||||
| `<wchar.h>` | Fully projected |
|
||||
| `<wctype.h>` | Fully projected |
|
||||
|
||||
## Limitations
|
||||
Not all C standard library functionality can be fully projected due to language differences. These limitations are listed here.
|
||||
|
||||
### `long double`
|
||||
As Odin lacks a means to interact with `long double` in it's foreign interface, this projection effort does not bind or define anything requiring `long double` which is permitted by the C standard.
|
||||
|
||||
### `<complex.h>`
|
||||
The special values `_Complex_I`, `_Imaginary_I` and the appropriate definition of `I` cannot be realized with the same type in Odin as it would be in C. The literal `1i` is tempting to use for these definitions but the semantics differ from C and would be confusing to use.
|
||||
|
||||
### `<math.h>`
|
||||
The classification functions, e.g: `fpclassify` are required by C to be implemented as macros, meaning no implementation would expose functions in their library we could bind. Instead, we provide native Odin implementations with functionally equivalent semantics and behavior as the C ones. Unfortunately, since classification returns unspecified constant values this may be an ABI break where the value of those constants enter and exit native C code.
|
||||
|
||||
### `<stdarg.h>`
|
||||
While Odin can interact with variable argument C functions through the use of the `#c_vararg` attribute within a foreign block, it's not actually possible to create procedures in Odin with bodies that have the same ABI as that of variable argument C functions, as a result `va_arg` is not projected.
|
||||
|
||||
### `<stddef.h>`
|
||||
`offsetof` is not realizable in Odin, however you can use `offset_of` instead.
|
||||
|
||||
### `<tgmath.h>`
|
||||
C has some strange promotion and type-coercion behavior for `<tgmath.h>` which isn't correctly handled by this projection, specifically involving the use of complex arithmetic and kernels. We do mostly support type-generic math through the use of Odin's explicit procedure overloading, however the semantic behavior of that doesn't match C and so literal expressions of complex type in C may not call the same underlying math kernel functions as they do in Odin through this projection.
|
||||
|
||||
## Caveats
|
||||
|
||||
In addition to limitations, there are some minor caveats you should be aware when using this projection.
|
||||
|
||||
* `errno()` is a function which returns `^int` rather than a macro.
|
||||
* `MB_CUR_MAX()` is a function which return `size_t` rather than a macro.
|
||||
* Currently only works on Windows (MSVCRT) and Linux (GLIBC or MUSL)
|
||||
|
||||
## License
|
||||
Every file within this directory is made available under Odin's BSD-2 license
|
||||
with the following copyright.
|
||||
|
||||
```
|
||||
Copyright 2021 Dale Weiler <weilercdale@gmail.com>.
|
||||
```
|
||||
@@ -0,0 +1,82 @@
|
||||
package libc
|
||||
|
||||
// 7.3 Complex arithmetic
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.3.5 Trigonometric functions
|
||||
cacos :: proc(z: complex_double) -> complex_double ---;
|
||||
cacosf :: proc(z: complex_float) -> complex_float ---;
|
||||
casin :: proc(z: complex_double) -> complex_double ---;
|
||||
casinf :: proc(z: complex_float) -> complex_float ---;
|
||||
catan :: proc(z: complex_double) -> complex_double ---;
|
||||
catanf :: proc(z: complex_float) -> complex_float ---;
|
||||
ccos :: proc(z: complex_double) -> complex_double ---;
|
||||
ccosf :: proc(z: complex_float) -> complex_float ---;
|
||||
csin :: proc(z: complex_double) -> complex_double ---;
|
||||
csinf :: proc(z: complex_float) -> complex_float ---;
|
||||
ctan :: proc(z: complex_double) -> complex_double ---;
|
||||
ctanf :: proc(z: complex_float) -> complex_float ---;
|
||||
|
||||
// 7.3.6 Hyperbolic functions
|
||||
cacosh :: proc(z: complex_double) -> complex_double ---;
|
||||
cacoshf :: proc(z: complex_float) -> complex_float ---;
|
||||
casinh :: proc(z: complex_double) -> complex_double ---;
|
||||
casinhf :: proc(z: complex_float) -> complex_float ---;
|
||||
catanh :: proc(z: complex_double) -> complex_double ---;
|
||||
catanhf :: proc(z: complex_float) -> complex_float ---;
|
||||
ccosh :: proc(z: complex_double) -> complex_double ---;
|
||||
ccoshf :: proc(z: complex_float) -> complex_float ---;
|
||||
csinh :: proc(z: complex_double) -> complex_double ---;
|
||||
csinhf :: proc(z: complex_float) -> complex_float ---;
|
||||
ctanh :: proc(z: complex_double) -> complex_double ---;
|
||||
ctanhf :: proc(z: complex_float) -> complex_float ---;
|
||||
|
||||
// 7.3.7 Exponential and logarithmic functions
|
||||
cexp :: proc(z: complex_double) -> complex_double ---;
|
||||
cexpf :: proc(z: complex_float) -> complex_float ---;
|
||||
clog :: proc(z: complex_double) -> complex_double ---;
|
||||
clogf :: proc(z: complex_float) -> complex_float ---;
|
||||
|
||||
// 7.3.8 Power and absolute-value functions
|
||||
cabs :: proc(z: complex_double) -> complex_double ---;
|
||||
cabsf :: proc(z: complex_float) -> complex_float ---;
|
||||
cpow :: proc(z: complex_double) -> complex_double ---;
|
||||
cpowf :: proc(z: complex_float) -> complex_float ---;
|
||||
csqrt :: proc(z: complex_double) -> complex_double ---;
|
||||
csqrtf :: proc(z: complex_float) -> complex_float ---;
|
||||
|
||||
// 7.3.9 Manipulation functions
|
||||
carg :: proc(z: complex_double) -> double ---;
|
||||
cargf :: proc(z: complex_float) -> float ---;
|
||||
cimag :: proc(z: complex_double) -> double ---;
|
||||
cimagf :: proc(z: complex_float) -> float ---;
|
||||
conj :: proc(z: complex_double) -> complex_double ---;
|
||||
conjf :: proc(z: complex_float) -> complex_float ---;
|
||||
cproj :: proc(z: complex_double) -> complex_double ---;
|
||||
cprojf :: proc(z: complex_float) -> complex_float ---;
|
||||
creal :: proc(z: complex_double) -> double ---;
|
||||
crealf :: proc(z: complex_float) -> float ---;
|
||||
}
|
||||
|
||||
import builtin "core:builtin"
|
||||
|
||||
complex_float :: distinct builtin.complex64;
|
||||
complex_double :: distinct builtin.complex128;
|
||||
|
||||
// Cannot implement _Complex_I or _Imaginary_I in Odin, thus
|
||||
// complex and imaginary cannot be implement either.
|
||||
|
||||
CMPLX :: #force_inline proc(x, y: double) -> complex_double {
|
||||
return builtin.complex(x, y);
|
||||
}
|
||||
|
||||
CMPLXF :: #force_inline proc(x, y: float) -> complex_float {
|
||||
return builtin.complex(x, y);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package libc
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
// 7.4 Character handling
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.4.1 Character classification functions
|
||||
isalnum :: proc(c: int) -> int ---;
|
||||
isalpha :: proc(c: int) -> int ---;
|
||||
isblank :: proc(c: int) -> int ---;
|
||||
iscntrl :: proc(c: int) -> int ---;
|
||||
isdigit :: proc(c: int) -> int ---;
|
||||
isgraph :: proc(c: int) -> int ---;
|
||||
islower :: proc(c: int) -> int ---;
|
||||
isprint :: proc(c: int) -> int ---;
|
||||
ispunct :: proc(c: int) -> int ---;
|
||||
isspace :: proc(c: int) -> int ---;
|
||||
isupper :: proc(c: int) -> int ---;
|
||||
isxdigit :: proc(c: int) -> int ---;
|
||||
|
||||
// 7.4.2 Character case mapping functions
|
||||
tolower :: proc(c: int) -> int ---;
|
||||
toupper :: proc(c: int) -> int ---;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package libc
|
||||
|
||||
// 7.5 Errors
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
// C11 standard only requires the definition of:
|
||||
// EDOM,
|
||||
// EILSEQ
|
||||
// ERANGE
|
||||
when ODIN_OS == "linux" || ODIN_OS == "freebsd" {
|
||||
@(private="file")
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
@(link_name="__libc_errno_location")
|
||||
_get_errno :: proc() -> ^int ---;
|
||||
}
|
||||
|
||||
EDOM :: 33;
|
||||
EILSEQ :: 84;
|
||||
ERANGE :: 34;
|
||||
}
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
@(private="file")
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
@(link_name="_errno")
|
||||
_get_errno :: proc() -> ^int ---;
|
||||
}
|
||||
|
||||
EDOM :: 33;
|
||||
EILSEQ :: 42;
|
||||
ERANGE :: 34;
|
||||
}
|
||||
|
||||
// Odin has no way to make an identifier "errno" behave as a function call to
|
||||
// read the value, or to produce an lvalue such that you can assign a different
|
||||
// error value to errno. To work around this, just expose it as a function like
|
||||
// it actually is.
|
||||
errno :: #force_inline proc() -> ^int {
|
||||
return _get_errno();
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
package libc
|
||||
|
||||
// 7.12 Mathematics
|
||||
|
||||
import "core:intrinsics"
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
// To support C's tgmath behavior we use Odin's explicit procedure overloading,
|
||||
// but we cannot use the same names as exported by libc so use @(link_name)
|
||||
// and keep them as private symbols of name "libc_"
|
||||
@(private="file")
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.12.4 Trigonometric functions
|
||||
@(link_name="acos") libc_acos :: proc(x: double) -> double ---;
|
||||
@(link_name="acosf") libc_acosf :: proc(x: float) -> float ---;
|
||||
@(link_name="asin") libc_asin :: proc(x: double) -> double ---;
|
||||
@(link_name="asinf") libc_asinf :: proc(x: float) -> float ---;
|
||||
@(link_name="atan") libc_atan :: proc(x: double) -> double ---;
|
||||
@(link_name="atanf") libc_atanf :: proc(x: float) -> float ---;
|
||||
@(link_name="atan2") libc_atan2 :: proc(y: double, x: double) -> double ---;
|
||||
@(link_name="atan2f") libc_atan2f :: proc(y: float, x: float) -> float ---;
|
||||
@(link_name="cos") libc_cos :: proc(x: double) -> double ---;
|
||||
@(link_name="cosf") libc_cosf :: proc(x: float) -> float ---;
|
||||
@(link_name="sin") libc_sin :: proc(x: double) -> double ---;
|
||||
@(link_name="sinf") libc_sinf :: proc(x: float) -> float ---;
|
||||
@(link_name="tan") libc_tan :: proc(x: double) -> double ---;
|
||||
@(link_name="tanf") libc_tanf :: proc(x: float) -> float ---;
|
||||
|
||||
// 7.12.5 Hyperbolic functions
|
||||
@(link_name="acosh") libc_acosh :: proc(x: double) -> double ---;
|
||||
@(link_name="acoshf") libc_acoshf :: proc(x: float) -> float ---;
|
||||
@(link_name="asinh") libc_asinh :: proc(x: double) -> double ---;
|
||||
@(link_name="asinhf") libc_asinhf :: proc(x: float) -> float ---;
|
||||
@(link_name="atanh") libc_atanh :: proc(x: double) -> double ---;
|
||||
@(link_name="atanhf") libc_atanhf :: proc(x: float) -> float ---;
|
||||
@(link_name="cosh") libc_cosh :: proc(x: double) -> double ---;
|
||||
@(link_name="coshf") libc_coshf :: proc(x: float) -> float ---;
|
||||
@(link_name="sinh") libc_sinh :: proc(x: double) -> double ---;
|
||||
@(link_name="sinhf") libc_sinhf :: proc(x: float) -> float ---;
|
||||
@(link_name="tanh") libc_tanh :: proc(x: double) -> double ---;
|
||||
@(link_name="tanhf") libc_tanhf :: proc(x: float) -> float ---;
|
||||
|
||||
// 7.12.6 Exponential and logarithmic functions
|
||||
@(link_name="exp") libc_exp :: proc(x: double) -> double ---;
|
||||
@(link_name="expf") libc_expf :: proc(x: float) -> float ---;
|
||||
@(link_name="exp2") libc_exp2 :: proc(x: double) -> double ---;
|
||||
@(link_name="exp2f") libc_exp2f :: proc(x: float) -> float ---;
|
||||
@(link_name="expm1") libc_expm1 :: proc(x: double) -> double ---;
|
||||
@(link_name="expm1f") libc_expm1f :: proc(x: float) -> float ---;
|
||||
@(link_name="frexp") libc_frexp :: proc(value: double, exp: ^int) -> double ---;
|
||||
@(link_name="frexpf") libc_frexpf :: proc(value: float, exp: ^int) -> float ---;
|
||||
@(link_name="ilogb") libc_ilogb :: proc(x: double) -> int ---;
|
||||
@(link_name="ilogbf") libc_ilogbf :: proc(x: float) -> int ---;
|
||||
@(link_name="ldexp") libc_ldexp :: proc(x: double, exp: int) -> double ---;
|
||||
@(link_name="ldexpf") libc_ldexpf :: proc(x: float, exp: int) -> float ---;
|
||||
@(link_name="log") libc_log :: proc(x: double) -> double ---;
|
||||
@(link_name="logf") libc_logf :: proc(x: float) -> float ---;
|
||||
@(link_name="log10") libc_log10 :: proc(x: double) -> double ---;
|
||||
@(link_name="log10f") libc_log10f :: proc(x: float) -> float ---;
|
||||
@(link_name="log1p") libc_log1p :: proc(x: double) -> double ---;
|
||||
@(link_name="log1pf") libc_log1pf :: proc(x: float) -> float ---;
|
||||
@(link_name="log2") libc_log2 :: proc(x: double) -> double ---;
|
||||
@(link_name="log2f") libc_log2f :: proc(x: float) -> float ---;
|
||||
@(link_name="logb") libc_logb :: proc(x: double) -> double ---;
|
||||
@(link_name="logbf") libc_logbf :: proc(x: float) -> float ---;
|
||||
@(link_name="modf") libc_modf :: proc(value: double, iptr: ^double) -> double ---;
|
||||
@(link_name="modff") libc_modff :: proc(value: float, iptr: ^float) -> float ---;
|
||||
@(link_name="scalbn") libc_scalbn :: proc(x: double, n: int) -> double ---;
|
||||
@(link_name="scalbnf") libc_scalbnf :: proc(x: float, n: int) -> float ---;
|
||||
@(link_name="scalbln") libc_scalbln :: proc(x: double, n: long) -> double ---;
|
||||
@(link_name="scalblnf") libc_scalblnf :: proc(x: float, n: long) -> float ---;
|
||||
|
||||
// 7.12.7 Power and absolute-value functions
|
||||
@(link_name="cbrt") libc_cbrt :: proc(x: double) -> double ---;
|
||||
@(link_name="cbrtf") libc_cbrtf :: proc(x: float) -> float ---;
|
||||
@(link_name="fabs") libc_fabs :: proc(x: double) -> double ---;
|
||||
@(link_name="fabsf") libc_fabsf :: proc(x: float) -> float ---;
|
||||
@(link_name="hypot") libc_hypot :: proc(x: double, y: double) -> double ---;
|
||||
@(link_name="hypotf") libc_hypotf :: proc(x: float, y: float) -> float ---;
|
||||
@(link_name="pow") libc_pow :: proc(x: double, y: double) -> double ---;
|
||||
@(link_name="powf") libc_powf :: proc(x: float, y: float) -> float ---;
|
||||
@(link_name="sqrt") libc_sqrt :: proc(x: double) -> double ---;
|
||||
@(link_name="sqrtf") libc_sqrtf :: proc(x: float) -> float ---;
|
||||
|
||||
// 7.12.8 Error and gamma functions
|
||||
@(link_name="erf") libc_erf :: proc(x: double) -> double ---;
|
||||
@(link_name="erff") libc_erff :: proc(x: float) -> float ---;
|
||||
@(link_name="erfc") libc_erfc :: proc(x: double) -> double ---;
|
||||
@(link_name="erfcf") libc_erfcf :: proc(x: float) -> float ---;
|
||||
@(link_name="lgamma") libc_lgamma :: proc(x: double) -> double ---;
|
||||
@(link_name="lgammaf") libc_lgammaf :: proc(x: float) -> float ---;
|
||||
@(link_name="tgamma") libc_tgamma :: proc(x: double) -> double ---;
|
||||
@(link_name="tgammaf") libc_tgammaf :: proc(x: float) -> float ---;
|
||||
|
||||
// 7.12.9 Nearest integer functions
|
||||
@(link_name="ceil") libc_ceil :: proc(x: double) -> double ---;
|
||||
@(link_name="ceilf") libc_ceilf :: proc(x: float) -> float ---;
|
||||
@(link_name="floor") libc_floor :: proc(x: double) -> double ---;
|
||||
@(link_name="floorf") libc_floorf :: proc(x: float) -> float ---;
|
||||
@(link_name="nearbyint") libc_nearbyint :: proc(x: double) -> double ---;
|
||||
@(link_name="nearbyintf") libc_nearbyintf :: proc(x: float) -> float ---;
|
||||
@(link_name="rint") libc_rint :: proc(x: double) -> double ---;
|
||||
@(link_name="rintf") libc_rintf :: proc(x: float) -> float ---;
|
||||
@(link_name="lrint") libc_lrint :: proc(x: double) -> long ---;
|
||||
@(link_name="lrintf") libc_lrintf :: proc(x: float) -> long ---;
|
||||
@(link_name="llrint") libc_llrint :: proc(x: double) -> longlong ---;
|
||||
@(link_name="llrintf") libc_llrintf :: proc(x: float) -> longlong ---;
|
||||
@(link_name="round") libc_round :: proc(x: double) -> double ---;
|
||||
@(link_name="roundf") libc_roundf :: proc(x: float) -> float ---;
|
||||
@(link_name="lround") libc_lround :: proc(x: double) -> long ---;
|
||||
@(link_name="lroundf") libc_lroundf :: proc(x: float) -> long ---;
|
||||
@(link_name="llround") libc_llround :: proc(x: double) -> longlong ---;
|
||||
@(link_name="llroundf") libc_llroundf :: proc(x: float) -> longlong ---;
|
||||
@(link_name="trunc") libc_trunc :: proc(x: double) -> double ---;
|
||||
@(link_name="truncf") libc_truncf :: proc(x: float) -> float ---;
|
||||
|
||||
// 7.12.10 Remainder functions
|
||||
@(link_name="fmod") libc_fmod :: proc(x: double, y: double) -> double ---;
|
||||
@(link_name="fmodf") libc_fmodf :: proc(x: float, y: float) -> float ---;
|
||||
@(link_name="remainder") libc_remainder :: proc(x: double, y: double) -> double ---;
|
||||
@(link_name="remainderf") libc_remainderf :: proc(x: float, y: float) -> float ---;
|
||||
@(link_name="remquo") libc_remquo :: proc(x: double, y: double, quo: ^int) -> double ---;
|
||||
@(link_name="remquof") libc_remquof :: proc(x: float, y: float, quo: ^int) -> float ---;
|
||||
|
||||
// 7.12.11 Manipulation functions
|
||||
@(link_name="copysign") libc_copysign :: proc(x: double, y: double) -> double ---;
|
||||
@(link_name="copysignf") libc_copysignf :: proc(x: float, y: float) -> float ---;
|
||||
@(link_name="nan") libc_nan :: proc(tagp: cstring) -> double ---;
|
||||
@(link_name="nanf") libc_nanf :: proc(tagp: cstring) -> float ---;
|
||||
@(link_name="nextafter") libc_nextafter :: proc(x: double, y: double) -> double ---;
|
||||
@(link_name="nextafterf") libc_nextafterf :: proc(x: float, y: float) -> float ---;
|
||||
|
||||
// 7.12.12 Maximum, minimum, and positive difference functions
|
||||
@(link_name="fdim") libc_fdim :: proc(x: double, y: double) -> double ---;
|
||||
@(link_name="fdimf") libc_fdimf :: proc(x: float, y: float) -> float ---;
|
||||
@(link_name="fmax") libc_fmax :: proc(x: double, y: double) -> double ---;
|
||||
@(link_name="fmaxf") libc_fmaxf :: proc(x: float, y: float) -> float ---;
|
||||
@(link_name="fmin") libc_fmin :: proc(x: double, y: double) -> double ---;
|
||||
@(link_name="fminf") libc_fminf :: proc(x: float, y: float) -> float ---;
|
||||
@(link_name="fma") libc_fma :: proc(x, y, z: double) -> double ---;
|
||||
@(link_name="fmaf") libc_fmaf :: proc(x, y, z: float) -> float ---;
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
_nan_bit_pattern := ~u64(0);
|
||||
|
||||
// On amd64 Windows and Linux, float_t and double_t are respectively both
|
||||
// their usual types. On x86 it's not possible to define these types correctly
|
||||
// since they would be long double which Odin does have support for.
|
||||
float_t :: float;
|
||||
double_t :: double;
|
||||
|
||||
NAN := transmute(double)(_nan_bit_pattern);
|
||||
INFINITY :: 1e5000;
|
||||
|
||||
HUGE_VALF :: INFINITY;
|
||||
HUGE_VAL :: double(INFINITY);
|
||||
|
||||
MATH_ERRNO :: 1;
|
||||
MATH_ERREXCEPT :: 2;
|
||||
|
||||
math_errhandling :: 2; // Windows, Linux, macOS all use this mode.
|
||||
|
||||
FP_ILOGBNAN :: -1 - int((~uint(0)) >> 1);
|
||||
FP_ILOGB0 :: FP_ILOGBNAN;
|
||||
|
||||
// Number classification constants. These do not have to match libc since we
|
||||
// implement our own classification functions as libc requires they be macros,
|
||||
// which means libc does not export standard functions for them.
|
||||
FP_NAN :: 0;
|
||||
FP_INFINITE :: 1;
|
||||
FP_ZERO :: 2;
|
||||
FP_NORMAL :: 3;
|
||||
FP_SUBNORMAL :: 4;
|
||||
|
||||
@(private)
|
||||
_fpclassify :: #force_inline proc(x: double) -> int {
|
||||
u := transmute(uint64_t)x;
|
||||
e := u >> 52 & 0x7ff;
|
||||
if e == 0 do return FP_SUBNORMAL if (u << 1) != 0 else FP_ZERO;
|
||||
if e == 0x7ff do return FP_NAN if (u << 12) != 0 else FP_INFINITE;
|
||||
return FP_NORMAL;
|
||||
}
|
||||
|
||||
@(private)
|
||||
_fpclassifyf :: #force_inline proc(x: float) -> int {
|
||||
u := transmute(uint32_t)x;
|
||||
e := u >> 23 & 0xff;
|
||||
if e == 0 do return FP_SUBNORMAL if (u << 1) != 0 else FP_ZERO;
|
||||
if e == 0xff do return FP_NAN if (u << 9) != 0 else FP_INFINITE;
|
||||
return FP_NORMAL;
|
||||
}
|
||||
|
||||
@(private)
|
||||
_signbit :: #force_inline proc(x: double) -> int {
|
||||
return int(transmute(uint64_t)x >> 63);
|
||||
}
|
||||
|
||||
@(private)
|
||||
_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) {
|
||||
return fpclassify(x) == FP_INFINITE;
|
||||
}
|
||||
|
||||
isinf :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) {
|
||||
return fpclassify(x) > FP_INFINITE;
|
||||
}
|
||||
|
||||
isnan :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) {
|
||||
return fpclassify(x) == FP_NAN;
|
||||
}
|
||||
|
||||
isnormal :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) {
|
||||
return fpclassify(x) == FP_NORMAL;
|
||||
}
|
||||
|
||||
// These are special in that they avoid float exceptions. They cannot just be
|
||||
// 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) {
|
||||
return !isunordered(x, y) && x > y;
|
||||
}
|
||||
|
||||
isgreaterequal :: #force_inline proc(x, y: $T) 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) {
|
||||
return !isunordered(x, y) && x < y;
|
||||
}
|
||||
|
||||
islessequal :: #force_inline proc(x, y: $T) 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) {
|
||||
return !isunordered(x, y) && x <= y;
|
||||
}
|
||||
|
||||
isunordered :: #force_inline proc(x, y: $T) 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.
|
||||
sink: T;
|
||||
intrinsics.volatile_store(&sink, intrinsics.volatile_load(&y));
|
||||
return true;
|
||||
}
|
||||
return isnan(y);
|
||||
}
|
||||
|
||||
fpclassify :: proc{_fpclassify, _fpclassifyf};
|
||||
signbit :: proc{_signbit, _signbitf};
|
||||
|
||||
// Emulate tgmath.h behavior with explicit procedure overloading here.
|
||||
acos :: proc{libc_acos, libc_acosf, cacos, cacosf};
|
||||
asin :: proc{libc_asin, libc_asinf, casin, casinf};
|
||||
atan :: proc{libc_atan, libc_atanf, catan, catanf};
|
||||
atan2 :: proc{libc_atan2, libc_atan2f};
|
||||
cos :: proc{libc_cos, libc_cosf, ccos, ccosf};
|
||||
sin :: proc{libc_sin, libc_sinf, csin, csinf};
|
||||
tan :: proc{libc_tan, libc_tanf, ctan, ctanf};
|
||||
|
||||
acosh :: proc{libc_acosh, libc_acoshf, cacosh, cacoshf};
|
||||
asinh :: proc{libc_asinh, libc_asinhf, casinh, casinhf};
|
||||
atanh :: proc{libc_atanh, libc_atanhf, catanh, catanhf};
|
||||
cosh :: proc{libc_cosh, libc_coshf, ccosh, ccoshf};
|
||||
sinh :: proc{libc_sinh, libc_sinhf, csinh, csinhf};
|
||||
tanh :: proc{libc_tanh, libc_tanhf, ctanh, ctanhf};
|
||||
|
||||
exp :: proc{libc_exp, libc_expf, cexp, cexpf};
|
||||
exp2 :: proc{libc_exp2, libc_exp2f};
|
||||
expm1 :: proc{libc_expm1, libc_expm1f};
|
||||
frexp :: proc{libc_frexp, libc_frexpf};
|
||||
ilogb :: proc{libc_ilogb, libc_ilogbf};
|
||||
ldexp :: proc{libc_ldexp, libc_ldexpf};
|
||||
log :: proc{libc_log, libc_logf, clog, clogf};
|
||||
log10 :: proc{libc_log10, libc_log10f};
|
||||
log1p :: proc{libc_log1p, libc_log1pf};
|
||||
log2 :: proc{libc_log2, libc_log2f};
|
||||
logb :: proc{libc_logb, libc_logbf};
|
||||
modf :: proc{libc_modf, libc_modff};
|
||||
scalbn :: proc{libc_scalbn, libc_scalbnf};
|
||||
scalbln :: proc{libc_scalbln, libc_scalblnf};
|
||||
|
||||
cbrt :: proc{libc_cbrt, libc_cbrtf};
|
||||
fabs :: proc{libc_fabs, libc_fabsf, cabs, cabsf};
|
||||
hypot :: proc{libc_hypot, libc_hypotf};
|
||||
pow :: proc{libc_pow, libc_powf, cpow, cpowf};
|
||||
sqrt :: proc{libc_sqrt, libc_sqrtf, csqrt, csqrtf};
|
||||
|
||||
erf :: proc{libc_erf, libc_erff};
|
||||
erfc :: proc{libc_erfc, libc_erfcf};
|
||||
lgamma :: proc{libc_lgamma, libc_lgammaf};
|
||||
tgamma :: proc{libc_tgamma, libc_tgammaf};
|
||||
|
||||
ceil :: proc{libc_ceil, libc_ceilf};
|
||||
floor :: proc{libc_floor, libc_floorf};
|
||||
nearbyint :: proc{libc_nearbyint, libc_nearbyintf};
|
||||
rint :: proc{libc_rint, libc_rintf};
|
||||
lrint :: proc{libc_lrint, libc_lrintf};
|
||||
llrint :: proc{libc_llrint, libc_llrintf};
|
||||
round :: proc{libc_round, libc_roundf};
|
||||
lround :: proc{libc_lround, libc_lroundf};
|
||||
llround :: proc{libc_llround, libc_llroundf};
|
||||
trunc :: proc{libc_trunc, libc_truncf};
|
||||
|
||||
fmod :: proc{libc_fmod, libc_fmodf};
|
||||
remainder :: proc{libc_remainder, libc_remainderf};
|
||||
remquo :: proc{libc_remquo, libc_remquof};
|
||||
|
||||
copysign :: proc{libc_copysign, libc_copysignf};
|
||||
nextafter :: proc{libc_nextafter, libc_nextafterf};
|
||||
|
||||
fdim :: proc{libc_fdim, libc_fdimf};
|
||||
fmax :: proc{libc_fmax, libc_fmaxf};
|
||||
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
|
||||
// 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};
|
||||
asinf :: proc{libc_asinf};
|
||||
atanf :: proc{libc_atanf};
|
||||
atan2f :: proc{libc_atan2f};
|
||||
cosf :: proc{libc_cosf};
|
||||
sinf :: proc{libc_sinf};
|
||||
tanf :: proc{libc_tanf};
|
||||
|
||||
acoshf :: proc{libc_acoshf};
|
||||
asinhf :: proc{libc_asinhf};
|
||||
atanhf :: proc{libc_atanhf};
|
||||
coshf :: proc{libc_coshf};
|
||||
sinhf :: proc{libc_sinhf};
|
||||
tanhf :: proc{libc_tanhf};
|
||||
|
||||
expf :: proc{libc_expf};
|
||||
exp2f :: proc{libc_exp2f};
|
||||
expm1f :: proc{libc_expm1f};
|
||||
frexpf :: proc{libc_frexpf};
|
||||
ilogbf :: proc{libc_ilogbf};
|
||||
ldexpf :: proc{libc_ldexpf};
|
||||
logf :: proc{libc_logf};
|
||||
log10f :: proc{libc_log10f};
|
||||
log1pf :: proc{libc_log1pf};
|
||||
log2f :: proc{libc_log2f};
|
||||
logbf :: proc{libc_logbf};
|
||||
modff :: proc{libc_modff};
|
||||
scalbnf :: proc{libc_scalbnf};
|
||||
scalblnf :: proc{libc_scalblnf};
|
||||
|
||||
cbrtf :: proc{libc_cbrtf};
|
||||
fabsf :: proc{libc_fabsf};
|
||||
hypotf :: proc{libc_hypotf};
|
||||
powf :: proc{libc_powf};
|
||||
sqrtf :: proc{libc_sqrtf};
|
||||
|
||||
erff :: proc{libc_erff};
|
||||
erfcf :: proc{libc_erfcf};
|
||||
lgammaf :: proc{libc_lgammaf};
|
||||
tgammaf :: proc{libc_tgammaf};
|
||||
|
||||
ceilf :: proc{libc_ceilf};
|
||||
floorf :: proc{libc_floorf};
|
||||
nearbyintf :: proc{libc_nearbyintf};
|
||||
rintf :: proc{libc_rintf};
|
||||
lrintf :: proc{libc_lrintf};
|
||||
llrintf :: proc{libc_llrintf};
|
||||
roundf :: proc{libc_roundf};
|
||||
lroundf :: proc{libc_lroundf};
|
||||
llroundf :: proc{libc_llroundf};
|
||||
truncf :: proc{libc_truncf};
|
||||
|
||||
fmodf :: proc{libc_fmodf};
|
||||
remainderf :: proc{libc_remainderf};
|
||||
remquof :: proc{libc_remquof};
|
||||
|
||||
copysignf :: proc{libc_copysignf};
|
||||
nextafterf :: proc{libc_nextafterf};
|
||||
|
||||
fdimf :: proc{libc_fdimf};
|
||||
fmaxf :: proc{libc_fmaxf};
|
||||
fminf :: proc{libc_fminf};
|
||||
fmaf :: proc{libc_fmaf};
|
||||
|
||||
// These two functions are special and not made type generic in tgmath.h since
|
||||
// they only differ by their return type.
|
||||
nan :: proc{libc_nan};
|
||||
nanf :: proc{libc_nanf};
|
||||
@@ -0,0 +1,65 @@
|
||||
package libc
|
||||
|
||||
// 7.13 Nonlocal jumps
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.13.1 Save calling environment
|
||||
//
|
||||
// NOTE(dweiler): C11 requires setjmp be a macro, which means it won't
|
||||
// necessarily export a symbol named setjmp but rather _setjmp in the case
|
||||
// of musl, glibc, BSD libc, and msvcrt.
|
||||
//
|
||||
/// NOTE(dweiler): UCRT has two implementations of longjmp. One that performs
|
||||
// stack unwinding and one that doesn't. The choice of which to use depends on a
|
||||
// flag which is set inside the jmp_buf structure given to setjmp. The default
|
||||
// behavior is to unwind the stack. Within Odin, we cannot use the stack
|
||||
// unwinding version as the unwinding information isn't present. To opt-in to
|
||||
// the regular non-unwinding version we need a way to set this flag. Since the
|
||||
// location of the flag within the struct is not defined or part of the ABI and
|
||||
// can change between versions of UCRT, we must rely on setjmp to set it. It
|
||||
// turns out that setjmp receives this flag in the RDX register on Win64, this
|
||||
// just so happens to coincide with the second argument of a function in the
|
||||
// Win64 ABI. By giving our setjmp a second argument with the value of zero,
|
||||
// the RDX register will contain zero and correctly set the flag to disable
|
||||
// stack unwinding.
|
||||
@(link_name="_setjmp")
|
||||
setjmp :: proc(env: ^jmp_buf, hack: rawptr = nil) -> int ---;
|
||||
}
|
||||
} else {
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.13.1 Save calling environment
|
||||
//
|
||||
// NOTE(dweiler): C11 requires setjmp be a macro, which means it won't
|
||||
// necessarily export a symbol named setjmp but rather _setjmp in the case
|
||||
// of musl, glibc, BSD libc, and msvcrt.
|
||||
@(link_name="_setjmp")
|
||||
setjmp :: proc(env: ^jmp_buf) -> int ---;
|
||||
}
|
||||
}
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.13.2 Restore calling environment
|
||||
longjmp :: proc(env: ^jmp_buf, val: int) -> ! ---;
|
||||
}
|
||||
|
||||
// The C99 Rationale describes jmp_buf as being an array type for backward
|
||||
// compatibility. Odin does not need to honor this and couldn't as arrays in
|
||||
// Odin don't decay to pointers. It is somewhat easy for us to bind this, we
|
||||
// just need to ensure the structure contains enough storage with appropriate
|
||||
// alignment. Since there are no types in C with an alignment larger than
|
||||
// that of max_align_t, which cannot be larger than sizeof(long double) as any
|
||||
// other exposed type wouldn't be valid C, the maximum alignment possible in a
|
||||
// strictly conformant C implementation is 16 on the platforms we care about.
|
||||
// The choice of 4096 bytes for storage of this type is more than enough on all
|
||||
// relevant platforms.
|
||||
jmp_buf :: struct #align 16 { _: [4096]char, };
|
||||
@@ -0,0 +1,43 @@
|
||||
package libc
|
||||
|
||||
// 7.14 Signal handling
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
sig_atomic_t :: distinct atomic_int;
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
signal :: proc(sig: int, func: proc "c" (int)) -> proc "c" (int) ---;
|
||||
raise :: proc(sig: int) -> int ---;
|
||||
}
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
SIG_ERR :: rawptr(~uintptr(0));
|
||||
SIG_DFL :: rawptr(uintptr(0));
|
||||
SIG_IGN :: rawptr(uintptr(1));
|
||||
|
||||
SIGABRT :: 22;
|
||||
SIGFPE :: 8;
|
||||
SIGILL :: 4;
|
||||
SIGINT :: 2;
|
||||
SIGSEGV :: 11;
|
||||
SIGTERM :: 15;
|
||||
}
|
||||
|
||||
when ODIN_OS == "linux" || ODIN_OS == "freebsd" || ODIN_OS == "darwin" {
|
||||
SIG_ERR :: rawptr(~uintptr(0));
|
||||
SIG_DFL :: rawptr(uintptr(0));
|
||||
SIG_IGN :: rawptr(uintptr(1));
|
||||
|
||||
SIGABRT :: 6;
|
||||
SIGFPE :: 8;
|
||||
SIGILL :: 4;
|
||||
SIGINT :: 2;
|
||||
SIGSEGV :: 11;
|
||||
SIGTERM :: 15;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package libc
|
||||
|
||||
// 7.16 Variable arguments
|
||||
|
||||
import "core:intrinsics"
|
||||
import "core:runtime"
|
||||
import "core:mem"
|
||||
|
||||
@(private="file")
|
||||
@(default_calling_convention="none")
|
||||
foreign _ {
|
||||
@(link_name="llvm.va_start") _va_start :: proc(arglist: ^i8) ---;
|
||||
@(link_name="llvm.va_end") _va_end :: proc(arglist: ^i8) ---;
|
||||
@(link_name="llvm.va_copy") _va_copy :: proc(dst, src: ^i8) ---;
|
||||
}
|
||||
|
||||
// Since there are no types in C with an alignment larger than that of
|
||||
// max_align_t, which cannot be larger than sizeof(long double) as any other
|
||||
// exposed type wouldn't be valid C, the maximum alignment possible in a
|
||||
// strictly conformant C implementation is 16 on the platforms we care about.
|
||||
// The choice of 4096 bytes for storage of this type is more than enough on all
|
||||
// relevant platforms.
|
||||
va_list :: struct #align 16 {
|
||||
_: [4096]u8,
|
||||
};
|
||||
|
||||
va_start :: #force_inline proc(ap: ^va_list, _: any) {
|
||||
_va_start(cast(^i8)ap);
|
||||
}
|
||||
|
||||
va_end :: #force_inline proc(ap: ^va_list) {
|
||||
_va_end(cast(^i8)ap);
|
||||
}
|
||||
|
||||
va_copy :: #force_inline proc(dst, src: ^va_list) {
|
||||
_va_copy(cast(^i8)dst, cast(^i8)src);
|
||||
}
|
||||
|
||||
// We cannot provide va_arg as there is no way to create "C" style procedures
|
||||
// in Odin which take variable arguments the C way. The #c_vararg attribute only
|
||||
// exists for foreign imports. That being said, being able to copy a va_list,
|
||||
// as well as start and end one is necessary in some functions, the va_list
|
||||
// taking functions in libc as an example.
|
||||
@@ -0,0 +1,416 @@
|
||||
package libc
|
||||
|
||||
// 7.17 Atomics
|
||||
|
||||
import "core:intrinsics"
|
||||
|
||||
ATOMIC_BOOL_LOCK_FREE :: true;
|
||||
ATOMIC_CHAR_LOCK_FREE :: true;
|
||||
ATOMIC_CHAR16_T_LOCK_FREE :: true;
|
||||
ATOMIC_CHAR32_T_LOCK_FREE :: true;
|
||||
ATOMIC_WCHAR_T_LOCK_FREE :: true;
|
||||
ATOMIC_SHORT_LOCK_FREE :: true;
|
||||
ATOMIC_INT_LOCK_FREE :: true;
|
||||
ATOMIC_LONG_LOCK_FREE :: true;
|
||||
ATOMIC_LLONG_LOCK_FREE :: true;
|
||||
ATOMIC_POINTER_LOCK_FREE :: true;
|
||||
|
||||
// 7.17.3 Order and consistency
|
||||
memory_order :: enum int {
|
||||
relaxed,
|
||||
consume,
|
||||
acquire,
|
||||
release,
|
||||
acq_rel,
|
||||
seq_cst,
|
||||
}
|
||||
|
||||
memory_order_relaxed :: memory_order.relaxed;
|
||||
memory_order_consume :: memory_order.consume;
|
||||
memory_order_acquire :: memory_order.acquire;
|
||||
memory_order_release :: memory_order.release;
|
||||
memory_order_acq_rel :: memory_order.acq_rel;
|
||||
memory_order_seq_cst :: memory_order.seq_cst;
|
||||
|
||||
// 7.17.2 Initialization
|
||||
ATOMIC_VAR_INIT :: #force_inline proc(value: $T) -> T {
|
||||
return value;
|
||||
}
|
||||
|
||||
atomic_init :: #force_inline proc(obj: ^$T, value: T) {
|
||||
intrinsics.atomic_store(obj, value);
|
||||
}
|
||||
|
||||
kill_dependency :: #force_inline proc(value: $T) -> T {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
atomic_signal_fence :: #force_inline proc(order: memory_order) {
|
||||
atomic_thread_fence(order);
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
// 7.17.6 Atomic integer types
|
||||
atomic_bool :: distinct bool;
|
||||
atomic_char :: distinct char;
|
||||
atomic_schar :: distinct char;
|
||||
atomic_uchar :: distinct uchar;
|
||||
atomic_short :: distinct short;
|
||||
atomic_ushort :: distinct ushort;
|
||||
atomic_int :: distinct int;
|
||||
atomic_uint :: distinct uint;
|
||||
atomic_long :: distinct long;
|
||||
atomic_ulong :: distinct ulong;
|
||||
atomic_llong :: distinct longlong;
|
||||
atomic_ullong :: distinct ulonglong;
|
||||
atomic_char16_t :: distinct char16_t;
|
||||
atomic_char32_t :: distinct char32_t;
|
||||
atomic_wchar_t :: distinct wchar_t;
|
||||
atomic_int_least8_t :: distinct int_least8_t;
|
||||
atomic_uint_least8_t :: distinct uint_least8_t;
|
||||
atomic_int_least16_t :: distinct int_least16_t;
|
||||
atomic_uint_least16_t :: distinct uint_least16_t;
|
||||
atomic_int_least32_t :: distinct int_least32_t;
|
||||
atomic_uint_least32_t :: distinct uint_least32_t;
|
||||
atomic_int_least64_t :: distinct int_least64_t;
|
||||
atomic_uint_least64_t :: distinct uint_least64_t;
|
||||
atomic_int_fast8_t :: distinct int_fast8_t;
|
||||
atomic_uint_fast8_t :: distinct uint_fast8_t;
|
||||
atomic_int_fast16_t :: distinct int_fast16_t;
|
||||
atomic_uint_fast16_t :: distinct uint_fast16_t;
|
||||
atomic_int_fast32_t :: distinct int_fast32_t;
|
||||
atomic_uint_fast32_t :: distinct uint_fast32_t;
|
||||
atomic_int_fast64_t :: distinct int_fast64_t;
|
||||
atomic_uint_fast64_t :: distinct uint_fast64_t;
|
||||
atomic_intptr_t :: distinct intptr_t;
|
||||
atomic_uintptr_t :: distinct uintptr_t;
|
||||
atomic_size_t :: distinct size_t;
|
||||
atomic_ptrdiff_t :: distinct ptrdiff_t;
|
||||
atomic_intmax_t :: distinct intmax_t;
|
||||
atomic_uintmax_t :: distinct uintmax_t;
|
||||
|
||||
// 7.17.7 Operations on atomic types
|
||||
atomic_store :: #force_inline proc(object: ^$T, desired: T) {
|
||||
intrinsics.atomic_store(object, desired);
|
||||
}
|
||||
|
||||
atomic_store_explicit :: #force_inline proc(object: ^$T, desired: T, order: memory_order) {
|
||||
assert(order != .consume);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
atomic_load :: #force_inline proc(object: ^$T) -> T {
|
||||
return intrinsics.atomic_load(object);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
atomic_exchange :: #force_inline proc(object: ^$T, desired: T) -> T {
|
||||
return intrinsics.atomic_xchg(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);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// C does not allow failure memory order to be order_release or acq_rel.
|
||||
// Similarly, it does not allow the failure order to be stronger than success
|
||||
// order. Since consume and acquire are both monotonic, we can count them as
|
||||
// one, for a total of three memory orders that are relevant in compare exchange.
|
||||
// relaxed, acquire (consume), seq_cst.
|
||||
// The requirement that the failure order cannot be stronger than success limits
|
||||
// the valid combinations for the failure order to this table:
|
||||
// [success = seq_cst, failure = seq_cst] => _
|
||||
// [success = acquire, failure = seq_cst] => acq
|
||||
// [success = release, failure = seq_cst] => rel
|
||||
// [success = acq_rel, failure = seq_cst] => acqrel
|
||||
// [success = relaxed, failure = relaxed] => relaxed
|
||||
// [success = seq_cst, failure = relaxed] => failrelaxed
|
||||
// [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);
|
||||
if !ok do expected^ = value;
|
||||
return ok;
|
||||
}
|
||||
|
||||
atomic_compare_exchange_strong_explicit :: #force_inline proc(object, expected: ^$T, desired: T, success, failure: memory_order) {
|
||||
assert(failure != .release);
|
||||
assert(failure != .acq_rel);
|
||||
|
||||
value: T; ok: bool;
|
||||
#partial switch (failure) {
|
||||
case .seq_cst:
|
||||
assert(success != .relaxed);
|
||||
#partial switch (success) {
|
||||
case .seq_cst:
|
||||
value, ok := intrinsics.atomic_cxchg(object, expected^, desired);
|
||||
case .acquire:
|
||||
value, ok := intrinsics.atomic_cxchg_acq(object, expected^, desired);
|
||||
case .consume:
|
||||
value, ok := intrinsics.atomic_cxchg_acq(object, expected^, desired);
|
||||
case .release:
|
||||
value, ok := intrinsics.atomic_cxchg_rel(object, expected^, desired);
|
||||
case .acq_rel:
|
||||
value, ok := intrinsics.atomic_cxchg_acqrel(object, expected^, desired);
|
||||
}
|
||||
case .relaxed:
|
||||
assert(success != .release);
|
||||
#partial switch (success) {
|
||||
case .relaxed:
|
||||
value, ok := intrinsics.atomic_cxchg_relaxed(object, expected^, desired);
|
||||
case .seq_cst:
|
||||
value, ok := intrinsics.atomic_cxchg_failrelaxed(object, expected^, desired);
|
||||
case .acquire:
|
||||
value, ok := intrinsics.atomic_cxchg_acq_failrelaxed(object, expected^, desired);
|
||||
case .consume:
|
||||
value, ok := intrinsics.atomic_cxchg_acq_failrelaxed(object, expected^, desired);
|
||||
case .acq_rel:
|
||||
value, ok := intrinsics.atomic_cxchg_acqrel_failrelaxed(object, expected^, desired);
|
||||
}
|
||||
case .consume:
|
||||
fallthrough;
|
||||
case .acquire:
|
||||
assert(success == .seq_cst);
|
||||
value, ok := intrinsics.atomic_cxchg_failacq(object, expected^, desired);
|
||||
|
||||
}
|
||||
if !ok do expected^ = value;
|
||||
return ok;
|
||||
}
|
||||
|
||||
atomic_compare_exchange_weak :: #force_inline proc(object, expected: ^$T, desired: T) {
|
||||
value, ok := intrinsics.atomic_cxchgweak(object, expected^, desired);
|
||||
if !ok do expected^ = value;
|
||||
return ok;
|
||||
}
|
||||
|
||||
atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desited: T, success, failure: memory_order) {
|
||||
assert(failure != .release);
|
||||
assert(failure != .acq_rel);
|
||||
|
||||
value: T; ok: bool;
|
||||
#partial switch (failure) {
|
||||
case .seq_cst:
|
||||
assert(success != .relaxed);
|
||||
#partial switch (success) {
|
||||
case .seq_cst:
|
||||
value, ok := intrinsics.atomic_cxchgweak(object, expected^, desired);
|
||||
case .acquire:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acq(object, expected^, desired);
|
||||
case .consume:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acq(object, expected^, desired);
|
||||
case .release:
|
||||
value, ok := intrinsics.atomic_cxchgweak_rel(object, expected^, desired);
|
||||
case .acq_rel:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acqrel(object, expected^, desired);
|
||||
}
|
||||
case .relaxed:
|
||||
assert(success != .release);
|
||||
#partial switch (success) {
|
||||
case .relaxed:
|
||||
value, ok := intrinsics.atomic_cxchgweak_relaxed(object, expected^, desired);
|
||||
case .seq_cst:
|
||||
value, ok := intrinsics.atomic_cxchgweak_failrelaxed(object, expected^, desired);
|
||||
case .acquire:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acq_failrelaxed(object, expected^, desired);
|
||||
case .consume:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acq_failrelaxed(object, expected^, desired);
|
||||
case .acq_rel:
|
||||
value, ok := intrinsics.atomic_cxchgweak_acqrel_failrelaxed(object, expected^, desired);
|
||||
}
|
||||
case .consume:
|
||||
fallthrough;
|
||||
case .acquire:
|
||||
assert(success == .seq_cst);
|
||||
value, ok := intrinsics.atomic_cxchgweak_failacq(object, expected^, desired);
|
||||
|
||||
}
|
||||
if !ok do expected^ = value;
|
||||
return ok;
|
||||
}
|
||||
|
||||
// 7.17.7.5 The atomic_fetch and modify generic functions
|
||||
atomic_fetch_add :: #force_inline proc(object: ^$T, operand: T) -> T {
|
||||
return intrinsics.atomic_add(object, operand);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
atomic_fetch_sub :: #force_inline proc(object: ^$T, operand: T) -> T {
|
||||
return intrinsics.atomic_sub(object, operand);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
atomic_fetch_or :: #force_inline proc(object: ^$T, operand: T) -> T {
|
||||
return intrinsics.atomic_or(object, operand);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
atomic_fetch_xor :: #force_inline proc(object: ^$T, operand: T) -> T {
|
||||
return intrinsics.atomic_xor(object, operand);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 7.17.8 Atomic flag type and operations
|
||||
atomic_flag :: distinct atomic_bool;
|
||||
|
||||
atomic_flag_test_and_set :: #force_inline proc(flag: ^atomic_flag) -> bool {
|
||||
return bool(atomic_exchange(flag, atomic_flag(true)));
|
||||
}
|
||||
|
||||
atomic_flag_test_and_set_explicit :: #force_inline proc(flag: ^atomic_flag, order: memory_order) -> bool {
|
||||
return bool(atomic_exchange_explicit(flag, atomic_flag(true), order));
|
||||
}
|
||||
|
||||
atomic_flag_clear :: #force_inline proc(flag: ^atomic_flag) {
|
||||
atomic_store(flag, atomic_flag(false));
|
||||
}
|
||||
|
||||
atomic_flag_clear_explicit :: #force_inline proc(flag: ^atomic_flag, order: memory_order) {
|
||||
atomic_store_explicit(flag, atomic_flag(false), order);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package libc
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
// 7.21 Input/output
|
||||
|
||||
FILE :: struct {};
|
||||
|
||||
// MSVCRT compatible.
|
||||
when ODIN_OS == "windows" {
|
||||
_IOFBF :: 0x0000;
|
||||
_IONBF :: 0x0004;
|
||||
_IOLBF :: 0x0040;
|
||||
|
||||
BUFSIZ :: 512;
|
||||
|
||||
EOF :: int(-1);
|
||||
|
||||
FOPEN_MAX :: 20;
|
||||
|
||||
FILENAME_MAX :: 260;
|
||||
|
||||
L_tmpnam :: 15; // "\\" + 12 + NUL
|
||||
|
||||
SEEK_SET :: 0;
|
||||
SEEK_CUR :: 1;
|
||||
SEEK_END :: 2;
|
||||
|
||||
TMP_MAX :: 32767; // SHRT_MAX
|
||||
|
||||
fpos_t :: distinct i64;
|
||||
|
||||
@(private="file")
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
__acrt_iob_func :: proc (index: uint) -> ^FILE ---;
|
||||
}
|
||||
|
||||
stdin := __acrt_iob_func(0);
|
||||
stdout := __acrt_iob_func(1);
|
||||
stderr := __acrt_iob_func(2);
|
||||
}
|
||||
|
||||
// GLIBC and MUSL compatible.
|
||||
when ODIN_OS == "linux" {
|
||||
fpos_t :: struct #raw_union { _: [16]char, _: longlong, _: double, };
|
||||
|
||||
_IOFBF :: 0;
|
||||
_IOLBF :: 1;
|
||||
_IONBF :: 2;
|
||||
|
||||
BUFSIZ :: 1024;
|
||||
|
||||
EOF :: int(-1);
|
||||
|
||||
FOPEN_MAX :: 1000;
|
||||
|
||||
FILENAME_MAX :: 4096;
|
||||
|
||||
L_tmpnam :: 20;
|
||||
|
||||
SEEK_SET :: 0;
|
||||
SEEK_CUR :: 1;
|
||||
SEEK_END :: 2;
|
||||
|
||||
TMP_MAX :: 10000;
|
||||
|
||||
foreign libc {
|
||||
stderr: ^FILE;
|
||||
stdin: ^FILE;
|
||||
stdout: ^FILE;
|
||||
}
|
||||
}
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.21.4 Operations on files
|
||||
remove :: proc(filename: cstring) -> int ---;
|
||||
rename :: proc(old, new: cstring) -> int ---;
|
||||
tmpfile :: proc() -> ^FILE ---;
|
||||
tmpnam :: proc(s: ^char) -> ^char ---;
|
||||
|
||||
// 7.21.5 File access functions
|
||||
fclose :: proc(stream: ^FILE) -> int ---;
|
||||
fflush :: proc(stream: ^FILE) -> int ---;
|
||||
fopen :: proc(filename, mode: cstring) -> ^FILE ---;
|
||||
freopen :: proc(filename, mode: cstring, stream: ^FILE) -> ^FILE ---;
|
||||
setbuf :: proc(stream: ^FILE, buf: ^char) ---;
|
||||
setvbuf :: proc(stream: ^FILE, buf: ^char, mode: int, size: size_t) -> int ---;
|
||||
|
||||
// 7.21.6 Formatted input/output functions
|
||||
fprintf :: proc(stream: ^FILE, format: cstring, #c_vararg args: ..any) -> int ---;
|
||||
fscanf :: proc(stream: ^FILE, format: cstring, #c_vararg args: ..any) -> int ---;
|
||||
printf :: proc(format: cstring, #c_vararg args: ..any) -> int ---;
|
||||
scanf :: proc(format: cstring, #c_vararg args: ..any) -> int ---;
|
||||
snprintf :: proc(s: ^char, format: cstring, #c_vararg args: ..any) -> int ---;
|
||||
sscanf :: proc(s, format: cstring, #c_vararg args: ..any) -> int ---;
|
||||
vfprintf :: proc(stream: ^FILE, format: cstring, arg: ^va_list) -> int ---;
|
||||
vfscanf :: proc(stream: ^FILE, format: cstring, arg: ^va_list) -> int ---;
|
||||
vprintf :: proc(format: cstring, arg: ^va_list) -> int ---;
|
||||
vscanf :: proc(format: cstring, arg: ^va_list) -> int ---;
|
||||
vsnprintf :: proc(s: ^char, n: size_t, format: cstring, arg: ^va_list) -> int ---;
|
||||
vsprintf :: proc(s: ^char, format: cstring, arg: ^va_list) -> int ---;
|
||||
vsscanf :: proc(s, format: cstring, arg: ^va_list) -> int ---;
|
||||
|
||||
// 7.21.7 Character input/output functions
|
||||
fgetc :: proc(stream: ^FILE) -> int ---;
|
||||
fgets :: proc(s: ^char, n: int, stream: ^FILE) -> ^char ---;
|
||||
fputc :: proc(s: cstring, stream: ^FILE) -> int ---;
|
||||
getc :: proc(stream: ^FILE) -> int ---;
|
||||
getchar :: proc() -> int ---;
|
||||
putc :: proc(c: int, stream: ^FILE) -> int ---;
|
||||
putchar :: proc() -> int ---;
|
||||
puts :: proc(s: cstring) -> int ---;
|
||||
ungetc :: proc(c: int, stream: ^FILE) -> int ---;
|
||||
fread :: proc(ptr: rawptr, size: size_t, stream: ^FILE) -> size_t ---;
|
||||
fwrite :: proc(ptr: rawptr, size: size_t, nmemb: size_t, stream: ^FILE) -> size_t ---;
|
||||
|
||||
// 7.21.9 File positioning functions
|
||||
fgetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int ---;
|
||||
fseek :: proc(stream: ^FILE, offset: long, whence: int) -> int ---;
|
||||
fsetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int ---;
|
||||
ftell :: proc(stream: ^FILE) -> long ---;
|
||||
rewind :: proc(stream: ^FILE) ---;
|
||||
|
||||
// 7.21.10 Error-handling functions
|
||||
clearerr :: proc(stream: ^FILE) ---;
|
||||
feof :: proc(stream: ^FILE) -> int ---;
|
||||
ferror :: proc(stream: ^FILE) -> int ---;
|
||||
perror :: proc(s: cstring) ---;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package libc
|
||||
|
||||
// 7.22 General utilities
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
RAND_MAX :: 0x7fff;
|
||||
|
||||
@(private="file")
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
___mb_cur_max_func :: proc() -> int ---;
|
||||
}
|
||||
|
||||
MB_CUR_MAX :: #force_inline proc() -> size_t {
|
||||
return size_t(___mb_cur_max_func());
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == "linux" {
|
||||
RAND_MAX :: 0x7fffffff;
|
||||
|
||||
// GLIBC and MUSL only
|
||||
@(private="file")
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
__ctype_get_mb_cur_max :: proc() -> size_t ---;
|
||||
}
|
||||
|
||||
MB_CUR_MAX :: #force_inline proc() -> size_t {
|
||||
return __ctype_get_mb_cur_max();
|
||||
}
|
||||
}
|
||||
|
||||
// C does not declare what these values should be, as an implementation is free
|
||||
// to use any two distinct values it wants to indicate success or failure.
|
||||
// However, nobody actually does and everyone appears to have agreed upon these
|
||||
// values.
|
||||
EXIT_SUCCESS :: 0;
|
||||
EXIT_FAILURE :: 1;
|
||||
|
||||
// C does not declare which order 'quot' and 'rem' should be for the divide
|
||||
// structures. An implementation could put 'rem' first. However, nobody actually
|
||||
// does and everyone appears to have agreed upon this layout.
|
||||
div_t :: struct { quot, rem: int, }
|
||||
ldiv_t :: struct { quot, rem: long, }
|
||||
lldiv_t :: struct { quot, rem: longlong, }
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.22.1 Numeric conversion functions
|
||||
atof :: proc(nptr: cstring) -> double ---;
|
||||
atoi :: proc(nptr: cstring) -> int ---;
|
||||
atol :: proc(nptr: cstring) -> long ---;
|
||||
atoll :: proc(nptr: cstring) -> longlong ---;
|
||||
strtod :: proc(nptr: cstring, endptr: ^^char) -> double ---;
|
||||
strtof :: proc(nptr: cstring, endptr: ^^char) -> float ---;
|
||||
strtol :: proc(nptr: cstring, endptr: ^^char, base: int) -> long ---;
|
||||
strtoll :: proc(nptr: cstring, endptr: ^^char, base: int) -> longlong ---;
|
||||
strtoul :: proc(nptr: cstring, endptr: ^^char, base: int) -> ulong ---;
|
||||
strtoull :: proc(nptr: cstring, endptr: ^^char, base: int) -> ulonglong ---;
|
||||
|
||||
// 7.22.2 Pseudo-random sequence generation functions
|
||||
rand :: proc() -> int ---;
|
||||
srand :: proc(seed: uint) ---;
|
||||
|
||||
// 7.22.3 Memory management functions
|
||||
aligned_alloc :: proc(aligment, size: size_t) -> rawptr ---;
|
||||
calloc :: proc(nmemb, size: size_t) -> rawptr ---;
|
||||
free :: proc(ptr: rawptr) ---;
|
||||
malloc :: proc(size: size_t) -> rawptr ---;
|
||||
realloc :: proc(ptr: rawptr, size: size_t) -> rawptr ---;
|
||||
|
||||
// 7.22.4 Communication with the environment
|
||||
abort :: proc() -> ! ---;
|
||||
atexit :: proc(func: proc "c" ()) -> int ---;
|
||||
at_quick_exit :: proc(func: proc "c" ()) -> int ---;
|
||||
exit :: proc(status: int) -> ! ---;
|
||||
_Exit :: proc(status: int) -> ! ---;
|
||||
getenv :: proc(name: cstring) -> ^char ---;
|
||||
quick_exit :: proc(status: int) -> ! ---;
|
||||
system :: proc(cmd: cstring) -> int ---;
|
||||
|
||||
// 7.22.5 Searching and sorting utilities
|
||||
bsearch :: proc(key, base: rawptr, nmemb, size: size_t, compar: proc "c" (lhs, rhs: rawptr) -> int) -> rawptr ---;
|
||||
qsort :: proc(base: rawptr, nmemb, size: size_t, compar: proc "c" (lhs, rhs: rawptr) -> int) ---;
|
||||
|
||||
// 7.22.6 Integer arithmetic functions
|
||||
abs :: proc(j: int) -> int ---;
|
||||
labs :: proc(j: long) -> long ---;
|
||||
llabs :: proc(j: longlong) -> longlong ---;
|
||||
div :: proc(numer, denom: int) -> div_t ---;
|
||||
ldiv :: proc(numer, denom: long) -> ldiv_t ---;
|
||||
lldiv :: proc(numer, denom: longlong) -> lldiv_t ---;
|
||||
|
||||
// 7.22.7 Multibyte/wide character conversion functions
|
||||
mblen :: proc(s: cstring, n: size_t) -> int ---;
|
||||
mbtowc :: proc(pwc: ^wchar_t, s: cstring, n: size_t) -> int ---;
|
||||
wctomb :: proc(s: ^char, wc: wchar_t) -> int ---;
|
||||
|
||||
// 7.22.8 Multibyte/wide string conversion functions
|
||||
mbstowcs :: proc(pwcs: ^wchar_t, s: cstring, n: size_t) -> size_t ---;
|
||||
wcstombs :: proc(s: ^char, pwcs: ^wchar_t, n: size_t) -> size_t ---;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
package libc
|
||||
|
||||
// 7.24 String handling
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
foreign libc {
|
||||
// 7.24.2 Copying functions
|
||||
memcpy :: proc(s1, s2: rawptr, n: size_t) -> rawptr ---;
|
||||
memmove :: proc(s1, s2: rawptr, n: size_t) -> rawptr ---;
|
||||
strcpy :: proc(s1: ^char, s2: cstring) -> ^char ---;
|
||||
strncpy :: proc(s1: ^char, s2: cstring, n: size_t) -> ^char ---;
|
||||
|
||||
// 7.24.3 Concatenation functions
|
||||
strcat :: proc(s1: ^char, s2: cstring) -> ^char ---;
|
||||
strncat :: proc(s1: ^char, s2: cstring, n: size_t) -> ^char ---;
|
||||
|
||||
// 7.24.4 Comparison functions
|
||||
memcmp :: proc(s1, s2: rawptr, n: size_t) -> int ---;
|
||||
strcmp :: proc(s1, s2: cstring) -> int ---;
|
||||
strcoll :: proc(s1, s2: cstring) -> int ---;
|
||||
strncmp :: proc(s1, s2: cstring, n: size_t) -> int ---;
|
||||
strxfrm :: proc(s1: ^char, s2: cstring, n: size_t) -> size_t ---;
|
||||
|
||||
// 7.24.5 Search functions
|
||||
memchr :: proc(s: rawptr, c: int, n: size_t) -> rawptr ---;
|
||||
strchr :: proc(s: cstring, c: int) -> ^char ---;
|
||||
strcspn :: proc(s1, s2: cstring) -> size_t ---;
|
||||
strpbrk :: proc(s1, s2: cstring) -> ^char ---;
|
||||
strrchr :: proc(s: ^char, c: int) -> ^char ---;
|
||||
strcpn :: proc(s1, s2: cstring) -> ^char ---;
|
||||
strtok :: proc(s1: ^char, s2: cstring) -> ^char ---;
|
||||
|
||||
// 7.24.6 Miscellaneous functions
|
||||
memset :: proc(s: rawptr, c: int, n: size_t) -> rawptr ---;
|
||||
strerror :: proc(errnum: int) -> ^char ---;
|
||||
strlen :: proc(s: cstring) -> size_t ---;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package libc_tests
|
||||
|
||||
import "core:c/libc"
|
||||
|
||||
test_stdio :: proc() {
|
||||
c: libc.char = 'C';
|
||||
libc.puts("Hello from puts");
|
||||
libc.printf("Hello from printf in %c\n", c);
|
||||
}
|
||||
test_thread :: proc() {
|
||||
thread_proc :: proc "c" (rawptr) -> libc.int {
|
||||
libc.printf("Hello from thread");
|
||||
return 42;
|
||||
}
|
||||
thread: libc.thrd_t;
|
||||
libc.thrd_create(&thread, thread_proc, nil);
|
||||
result: libc.int;
|
||||
libc.thrd_join(thread, &result);
|
||||
libc.printf(" %d\n", result);
|
||||
}
|
||||
|
||||
jmp: libc.jmp_buf;
|
||||
test_sjlj :: proc() {
|
||||
if libc.setjmp(&jmp) != 0 {
|
||||
libc.printf("Hello from longjmp\n");
|
||||
return;
|
||||
}
|
||||
libc.printf("Hello from setjmp\n");
|
||||
libc.longjmp(&jmp, 1);
|
||||
}
|
||||
test_signal :: proc() {
|
||||
handler :: proc "c" (sig: libc.int) {
|
||||
libc.printf("Hello from signal handler\n");
|
||||
}
|
||||
libc.signal(libc.SIGABRT, handler);
|
||||
libc.raise(libc.SIGABRT);
|
||||
}
|
||||
test_atexit :: proc() {
|
||||
handler :: proc "c" () {
|
||||
libc.printf("Hello from atexit\n");
|
||||
}
|
||||
libc.atexit(handler);
|
||||
}
|
||||
main :: proc() {
|
||||
test_stdio();
|
||||
test_thread();
|
||||
test_sjlj();
|
||||
test_signal();
|
||||
test_atexit();
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package libc
|
||||
|
||||
// 7.26 Threads
|
||||
|
||||
thrd_start_t :: proc "c" (rawptr) -> int;
|
||||
tss_dtor_t :: proc "c" (rawptr);
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc {
|
||||
"system:libucrt.lib",
|
||||
"system:msvcprt.lib"
|
||||
}
|
||||
|
||||
thrd_success :: 0; // _Thrd_success
|
||||
thrd_nomem :: 1; // _Thrd_nomem
|
||||
thrd_timedout :: 2; // _Thrd_timedout
|
||||
thrd_busy :: 3; // _Thrd_busy
|
||||
thrd_error :: 4; // _Thrd_error
|
||||
|
||||
mtx_plain :: 1; // _Mtx_plain
|
||||
mtx_recursive :: 0x100; // _Mtx_recursive
|
||||
mtx_timed :: 4; // _Mtx_timed
|
||||
|
||||
TSS_DTOR_ITERATIONS :: 4; // _TSS_DTOR_ITERATIONS_IMP
|
||||
|
||||
once_flag :: distinct i8; // _Once_flag_imp_t
|
||||
thrd_t :: struct { _: rawptr, _: uint, } // _Thrd_t
|
||||
tss_t :: distinct int; // _Tss_imp_t
|
||||
cnd_t :: distinct rawptr; // _Cnd_imp_t
|
||||
mtx_t :: distinct rawptr; // _Mtx_imp_t
|
||||
|
||||
// MSVCRT does not expose the C11 symbol names as what they are in C11
|
||||
// because they held off implementing <threads.h> and C11 support for so
|
||||
// long that people started implementing their own. To prevent symbol
|
||||
// conflict with existing customers code they had to namespace them
|
||||
// differently. Thus we need to alias the correct symbol names with Odin's
|
||||
// link_name attribute.
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.26.2 Initialization functions
|
||||
@(link_name="_Call_once") call_once :: proc(flag: ^once_flag, func: proc "c" ()) ---;
|
||||
// 7.26.3 Condition variable functions
|
||||
@(link_name="_Cnd_broadcast") cnd_broadcast :: proc(cond: ^cnd_t) -> int ---;
|
||||
@(link_name="_Cnd_destroy") cnd_destroy :: proc(cond: ^cnd_t) ---;
|
||||
@(link_name="_Cnd_init") cnd_init :: proc(cond: ^cnd_t) -> int ---;
|
||||
@(link_name="_Cnd_signal") cnd_signal :: proc(cond: ^cnd_t) -> int ---;
|
||||
@(link_name="_Cnd_timedwait") cnd_timedwait :: proc(cond: ^cnd_t, ts: ^timespec) -> int ---;
|
||||
@(link_name="_Cnd_wait") cnd_wait :: proc(cond: ^cnd_t, mtx: ^mtx_t) -> int ---;
|
||||
|
||||
// 7.26.4 Mutex functions
|
||||
@(link_name="_Mtx_destroy") mtx_destroy :: proc(mtx: ^mtx_t) ---;
|
||||
@(link_name="_Mtx_init") mtx_init :: proc(mtx: ^mtx_t, type: int) -> int ---;
|
||||
@(link_name="_Mtx_lock") mtx_lock :: proc(mtx: ^mtx_t) -> int ---;
|
||||
@(link_name="_Mtx_timedlock") mtx_timedlock :: proc(mtx: ^mtx_t, ts: ^timespec) -> int ---;
|
||||
@(link_name="_Mtx_trylock") mtx_trylock :: proc(mtx: ^mtx_t) -> int ---;
|
||||
@(link_name="_Mtx_unlock") mtx_unlock :: proc(mtx: ^mtx_t) -> int ---;
|
||||
|
||||
// 7.26.5 Thread functions
|
||||
@(link_name="_Thrd_create") thrd_create :: proc(thr: ^thrd_t, func: thrd_start_t, arg: rawptr) -> int ---;
|
||||
@(link_name="_Thrd_current") thrd_current :: proc() -> thrd_t ---;
|
||||
@(link_name="_Thrd_detach") thrd_detach :: proc(thr: thrd_t) -> int ---;
|
||||
@(link_name="_Thrd_equal") thrd_equal :: proc(lhs, rhs: thrd_t) -> int ---;
|
||||
@(link_name="_Thrd_exit") thrd_exit :: proc(res: int) -> ! ---;
|
||||
@(link_name="_Thrd_join") thrd_join :: proc(thr: thrd_t, res: ^int) -> int ---;
|
||||
@(link_name="_Thrd_sleep") thrd_sleep :: proc(duration, remaining: ^timespec) -> int ---;
|
||||
@(link_name="_Thrd_yield") thrd_yield :: proc() ---;
|
||||
|
||||
// 7.26.6 Thread-specific storage functions
|
||||
@(link_name="_Tss_create") tss_create :: proc(key: ^tss_t, dtor: tss_dtor_t) -> int ---;
|
||||
@(link_name="_Tss_delete") tss_delete :: proc(key: tss_t) ---;
|
||||
@(link_name="_Tss_get") tss_get :: proc(key: tss_t) -> rawptr ---;
|
||||
@(link_name="_Tss_set") tss_set :: proc(key: tss_t, val: rawptr) -> int ---;
|
||||
}
|
||||
}
|
||||
|
||||
// GLIBC and MUSL compatible constants and types.
|
||||
when ODIN_OS == "linux" {
|
||||
foreign import libc {
|
||||
"system:c",
|
||||
"system:pthread"
|
||||
}
|
||||
|
||||
thrd_success :: 0;
|
||||
thrd_busy :: 1;
|
||||
thrd_error :: 2;
|
||||
thrd_nomem :: 3;
|
||||
thrd_timedout :: 4;
|
||||
|
||||
mtx_plain :: 0;
|
||||
mtx_recursive :: 1;
|
||||
mtx_timed :: 2;
|
||||
|
||||
TSS_DTOR_ITERATIONS :: 4;
|
||||
|
||||
once_flag :: distinct int;
|
||||
thrd_t :: distinct ulong;
|
||||
tss_t :: distinct uint;
|
||||
cnd_t :: struct #raw_union { _: [12]int, _: [12 * size_of(int) / size_of(rawptr)]rawptr, };
|
||||
mtx_t :: struct #raw_union { _: [10 when size_of(long) == 8 else 6]int, _: [5 when size_of(long) == 8 else 6]rawptr, };
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.26.2 Initialization functions
|
||||
call_once :: proc(flag: ^once_flag, func: proc "c" ()) ---;
|
||||
|
||||
// 7.26.3 Condition variable functions
|
||||
cnd_broadcast :: proc(cond: ^cnd_t) -> int ---;
|
||||
cnd_destroy :: proc(cond: ^cnd_t) ---;
|
||||
cnd_init :: proc(cond: ^cnd_t) -> int ---;
|
||||
cnd_signal :: proc(cond: ^cnd_t) -> int ---;
|
||||
cnd_timedwait :: proc(cond: ^cnd_t, ts: ^timespec) -> int ---;
|
||||
cnd_wait :: proc(cond: ^cnd_t, mtx: ^mtx_t) -> int ---;
|
||||
|
||||
// 7.26.4 Mutex functions
|
||||
mtx_destroy :: proc(mtx: ^mtx_t) ---;
|
||||
mtx_init :: proc(mtx: ^mtx_t, type: int) -> int ---;
|
||||
mtx_lock :: proc(mtx: ^mtx_t) -> int ---;
|
||||
mtx_timedlock :: proc(mtx: ^mtx_t, ts: ^timespec) -> int ---;
|
||||
mtx_trylock :: proc(mtx: ^mtx_t) -> int ---;
|
||||
mtx_unlock :: proc(mtx: ^mtx_t) -> int ---;
|
||||
|
||||
// 7.26.5 Thread functions
|
||||
thrd_create :: proc(thr: ^thrd_t, func: thrd_start_t, arg: rawptr) -> int ---;
|
||||
thrd_current :: proc() -> thrd_t ---;
|
||||
thrd_detach :: proc(thr: thrd_t) -> int ---;
|
||||
thrd_equal :: proc(lhs, rhs: thrd_t) -> int ---;
|
||||
thrd_exit :: proc(res: int) -> ! ---;
|
||||
thrd_join :: proc(thr: thrd_t, res: ^int) -> int ---;
|
||||
thrd_sleep :: proc(duration, remaining: ^timespec) -> int ---;
|
||||
thrd_yield :: proc() ---;
|
||||
|
||||
// 7.26.6 Thread-specific storage functions
|
||||
tss_create :: proc(key: ^tss_t, dtor: tss_dtor_t) -> int ---;
|
||||
tss_delete :: proc(key: tss_t) ---;
|
||||
tss_get :: proc(key: tss_t) -> rawptr ---;
|
||||
tss_set :: proc(key: tss_t, val: rawptr) -> int ---;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package libc
|
||||
|
||||
// 7.27 Date and time
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
// We enforce 64-bit time_t and timespec as there is no reason to use 32-bit as
|
||||
// we approach the 2038 problem. Windows has defaulted to this since VC8 (2005).
|
||||
when ODIN_OS == "windows" {
|
||||
foreign libc {
|
||||
// 7.27.2 Time manipulation functions
|
||||
clock :: proc() -> clock_t ---;
|
||||
@(link_name="_difftime64") difftime :: proc(time1, time2: time_t) -> double ---;
|
||||
mktime :: proc(timeptr: ^tm) -> time_t ---;
|
||||
@(link_name="_time64") time :: proc(timer: ^time_t) -> time_t ---;
|
||||
@(link_name="_timespec64_get") timespec_get :: proc(ts: ^timespec, base: int) -> int ---;
|
||||
|
||||
// 7.27.3 Time conversion functions
|
||||
asctime :: proc(timeptr: ^tm) -> ^char ---;
|
||||
@(link_name="_ctime64") ctime :: proc(timer: ^time_t) -> ^char ---;
|
||||
@(link_name="_gmtime64") gmtime :: proc(timer: ^time_t) -> ^tm ---;
|
||||
@(link_name="_localtime64") localtime :: proc(timer: ^time_t) -> ^tm ---;
|
||||
strftime :: proc(s: ^char, maxsize: size_t, format: cstring, timeptr: ^tm) -> size_t ---;
|
||||
}
|
||||
|
||||
CLOCKS_PER_SEC :: 1000;
|
||||
TIME_UTC :: 1;
|
||||
|
||||
clock_t :: distinct long;
|
||||
time_t :: distinct i64;
|
||||
|
||||
timespec :: struct #align 8 {
|
||||
tv_sec: time_t,
|
||||
tv_nsec: long,
|
||||
}
|
||||
|
||||
tm :: struct #align 8 {
|
||||
tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday, tm_yday, tm_isdst: int,
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == "linux" || ODIN_OS == "freebsd" {
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.27.2 Time manipulation functions
|
||||
clock :: proc() -> clock_t ---;
|
||||
difftime :: proc(time1, time2: time_t) -> double ---;
|
||||
mktime :: proc(timeptr: ^tm) -> time_t ---;
|
||||
time :: proc(timer: ^time_t) -> time_t ---;
|
||||
timespec_get :: proc(ts: ^timespec, base: int) -> int ---;
|
||||
|
||||
// 7.27.3 Time conversion functions
|
||||
asctime :: proc(timeptr: ^tm) -> ^char ---;
|
||||
ctime :: proc(timer: ^time_t) -> ^char ---;
|
||||
gmtime :: proc(timer: ^time_t) -> ^tm ---;
|
||||
localtime :: proc(timer: ^time_t) -> ^tm ---;
|
||||
strftime :: proc(s: ^char, maxsize: size_t, format: cstring, timeptr: ^tm) -> size_t ---;
|
||||
}
|
||||
|
||||
CLOCKS_PER_SEC :: 1000000;
|
||||
TIME_UTC :: 1;
|
||||
|
||||
time_t :: distinct i64;
|
||||
|
||||
clock_t :: long;
|
||||
|
||||
timespec :: struct {
|
||||
tv_sec: time_t,
|
||||
tv_nsec: long,
|
||||
}
|
||||
|
||||
tm :: struct {
|
||||
tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday, tm_yday, tm_isdst: int,
|
||||
_: long,
|
||||
_: rawptr,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package libc
|
||||
|
||||
import builtin "core:builtin"
|
||||
|
||||
char :: builtin.u8; // assuming -funsigned-char
|
||||
short :: builtin.i16;
|
||||
int :: builtin.i32;
|
||||
long :: builtin.i32 when (ODIN_OS == "windows" || size_of(builtin.rawptr) == 4) else builtin.i64;
|
||||
longlong :: builtin.i64;
|
||||
|
||||
uchar :: builtin.u8;
|
||||
ushort :: builtin.u16;
|
||||
uint :: builtin.u32;
|
||||
ulong :: builtin.u32 when (ODIN_OS == "windows" || size_of(builtin.rawptr) == 4) else builtin.u64;
|
||||
ulonglong :: builtin.u64;
|
||||
|
||||
bool :: distinct builtin.b8;
|
||||
|
||||
size_t :: builtin.uint;
|
||||
wchar_t :: builtin.u16 when (ODIN_OS == "windows") else builtin.u32;
|
||||
|
||||
float :: builtin.f32;
|
||||
double :: builtin.f64;
|
||||
|
||||
// 7.20.1 Integer types
|
||||
int8_t :: builtin.i8;
|
||||
uint8_t :: builtin.u8;
|
||||
int16_t :: builtin.i16;
|
||||
uint16_t :: builtin.u16;
|
||||
int32_t :: builtin.i32;
|
||||
uint32_t :: builtin.u32;
|
||||
int64_t :: builtin.i64;
|
||||
uint64_t :: builtin.u64;
|
||||
|
||||
// These are all the same in multiple libc's for multiple architectures.
|
||||
int_least8_t :: builtin.i8;
|
||||
uint_least8_t :: builtin.u8;
|
||||
int_least16_t :: builtin.i16;
|
||||
uint_least16_t :: builtin.u16;
|
||||
int_least32_t :: builtin.i32;
|
||||
uint_least32_t :: builtin.u32;
|
||||
int_least64_t :: builtin.i64;
|
||||
uint_least64_t :: builtin.u64;
|
||||
|
||||
// Same on Windows, Linux, and FreeBSD
|
||||
when ODIN_ARCH == "386" || ODIN_ARCH == "amd64" {
|
||||
int_fast8_t :: builtin.i8;
|
||||
uint_fast8_t :: builtin.u8;
|
||||
int_fast16_t :: builtin.i32;
|
||||
uint_fast16_t :: builtin.u32;
|
||||
int_fast32_t :: builtin.i32;
|
||||
uint_fast32_t :: builtin.u32;
|
||||
int_fast64_t :: builtin.i64;
|
||||
uint_fast64_t :: builtin.u64;
|
||||
}
|
||||
|
||||
intptr_t :: builtin.int;
|
||||
uintptr_t :: builtin.uintptr;
|
||||
ptrdiff_t :: distinct intptr_t;
|
||||
|
||||
intmax_t :: builtin.i64;
|
||||
uintmax_t :: builtin.u64;
|
||||
|
||||
// Copy C's rules for type promotion here by forcing the type on the literals.
|
||||
INT8_MAX :: int(0x7f);
|
||||
INT16_MAX :: int(0x7fff);
|
||||
INT32_MAX :: int(0x7fffffff);
|
||||
INT64_MAX :: longlong(0x7fffffffffffffff);
|
||||
|
||||
UINT8_MAX :: int(0xff);
|
||||
UINT16_MAX :: int(0xffff);
|
||||
UINT32_MAX :: uint(0xffffffff);
|
||||
UINT64_MAX :: ulonglong(0xffffffffffffffff);
|
||||
|
||||
INT8_MIN :: ~INT8_MAX;
|
||||
INT16_MIN :: ~INT16_MAX;
|
||||
INT32_MIN :: ~INT32_MAX;
|
||||
INT64_MIN :: ~INT64_MAX;
|
||||
|
||||
NULL :: rawptr(uintptr(0));
|
||||
|
||||
NDEBUG :: !ODIN_DEBUG;
|
||||
@@ -0,0 +1,21 @@
|
||||
package libc
|
||||
|
||||
// 7.28 Unicode utilities
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.28.1 Restartable multibyte/wide character conversion functions
|
||||
mbrtoc16 :: proc(pc16: ^char16_t, s: cstring, n: size_t, ps: ^mbstate_t) -> size_t ---;
|
||||
c16rtomb :: proc(s: ^char, c16: char16_t, ps: ^mbstate_t) -> size_t ---;
|
||||
mbrtoc32 :: proc(pc32: ^char32_t, s: cstring, n: size_t, ps: ^mbstate_t) -> size_t ---;
|
||||
c32rtomb :: proc(s: ^char, c32: char32_t, ps: ^mbstate_t) -> size_t ---;
|
||||
}
|
||||
|
||||
char16_t :: uint_least16_t;
|
||||
char32_t :: uint_least32_t;
|
||||
@@ -0,0 +1,108 @@
|
||||
package libc
|
||||
|
||||
// 7.29 Extended multibyte and wide character utilities
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.29.2 Formatted wide character input/output functions
|
||||
fwprintf :: proc(stream: ^FILE, format: ^wchar_t, #c_vararg arg: ..any) -> int ---;
|
||||
fwscanf :: proc(stream: ^FILE, format: ^wchar_t, #c_vararg arg: ..any) -> int ---;
|
||||
swprintf :: proc(stream: ^FILE, n: size_t, format: ^wchar_t, #c_vararg arg: ..any) -> int ---;
|
||||
swscanf :: proc(s, format: ^wchar_t, #c_vararg arg: ..any) -> int ---;
|
||||
vfwprintf :: proc(stream: ^FILE, format: ^wchar_t, arg: va_list) -> int ---;
|
||||
vfwscanf :: proc(stream: ^FILE, format: ^wchar_t, arg: va_list) -> int ---;
|
||||
vswprintf :: proc(s: ^wchar_t, n: size_t, format: ^wchar_t, arg: va_list) -> int ---;
|
||||
vswscanf :: proc(s, format: ^wchar_t, arg: va_list) -> int ---;
|
||||
vwprintf :: proc(format: ^wchar_t, arg: va_list) -> int ---;
|
||||
vwscanf :: proc(format: ^wchar_t, arg: va_list) -> int ---;
|
||||
wprintf :: proc(format: ^wchar_t, #c_vararg arg: ..any) -> int ---;
|
||||
wscanf :: proc(format: ^wchar_t, #c_vararg arg: ..any) -> int ---;
|
||||
|
||||
// 7.29.3 Wide character input/output functions
|
||||
fwgetc :: proc(stream: ^FILE) -> wint_t ---;
|
||||
fgetws :: proc(s: ^wchar_t, n: int, stream: ^FILE) -> wchar_t ---;
|
||||
fputwc :: proc(c: wchar_t, stream: ^FILE) -> wint_t ---;
|
||||
fputws :: proc(s: ^wchar_t, stream: ^FILE) -> int ---;
|
||||
fwide :: proc(stream: ^FILE, mode: int) -> int ---;
|
||||
getwc :: proc(stream: ^FILE) -> wint_t ---;
|
||||
getwchar :: proc() -> wint_t ---;
|
||||
putwc :: proc(c: wchar_t, stream: ^FILE) -> wint_t ---;
|
||||
putwchar :: proc(c: wchar_t) -> wint_t ---;
|
||||
ungetwc :: proc(c: wchar_t, stream: ^FILE) -> wint_t ---;
|
||||
|
||||
// 7.29.4 General wide string utilities
|
||||
wcstod :: proc(nptr: ^wchar_t, endptr: ^^wchar_t) -> double ---;
|
||||
wcstof :: proc(nptr: ^wchar_t, endptr: ^^wchar_t) -> float ---;
|
||||
wcstol :: proc(nptr: ^wchar_t, endptr: ^^wchar_t, base: int) -> long ---;
|
||||
wcstoll :: proc(nptr: ^wchar_t, endptr: ^^wchar_t, base: int) -> longlong ---;
|
||||
wcstoul :: proc(nptr: ^wchar_t, endptr: ^^wchar_t, base: int) -> ulong ---;
|
||||
wcstoull :: proc(nptr: ^wchar_t, endptr: ^^wchar_t, base: int) -> ulonglong ---;
|
||||
|
||||
// 7.29.4.2 Wide string copying functions
|
||||
wcscpy :: proc(s1, s2: ^wchar_t) -> ^wchar_t ---;
|
||||
wcsncpy :: proc(s1, s2: ^wchar_t, n: size_t) -> ^wchar_t ---;
|
||||
wmemcpy :: proc(s1, s2: ^wchar_t, n: size_t) -> ^wchar_t ---;
|
||||
wmemmove :: proc(s1, s2: ^wchar_t, n: size_t) -> ^wchar_t ---;
|
||||
|
||||
// 7.29.4.3 Wide string concatenation functions
|
||||
wcscat :: proc(s1, s2: ^wchar_t) -> ^wchar_t ---;
|
||||
wcsncat :: proc(s1, s2: ^wchar_t, n: size_t) -> ^wchar_t ---;
|
||||
|
||||
// 7.29.4.4 Wide string comparison functions
|
||||
wcscmp :: proc(s1, s2: ^wchar_t) -> int ---;
|
||||
wcscoll :: proc(s1, s2: ^wchar_t) -> int ---;
|
||||
wcsncmp :: proc(s1, s2: ^wchar_t, n: size_t) -> int ---;
|
||||
wcsxfrm :: proc(s1, s2: ^wchar_t, n: size_t) -> size_t ---;
|
||||
wmemcmp :: proc(s1, s2: ^wchar_t, n: size_t) -> int ---;
|
||||
|
||||
// 7.29.4.5 Wide string search functions
|
||||
wcschr :: proc(s: ^wchar_t, c: wchar_t) -> ^wchar_t ---;
|
||||
wcscspn :: proc(s1, s2: ^wchar_t) -> size_t ---;
|
||||
wcspbrk :: proc(s1, s2: ^wchar_t) -> ^wchar_t ---;
|
||||
wcsrchr :: proc(s: ^wchar_t, c: wchar_t) -> ^wchar_t ---;
|
||||
wcsspn :: proc(s1, s2: ^wchar_t) -> size_t ---;
|
||||
wcsstr :: proc(s1, s2: ^wchar_t) -> ^wchar_t ---;
|
||||
wcstok :: proc(s1, s2: ^wchar_t, ptr: ^^wchar_t) -> ^wchar_t ---;
|
||||
wmemchr :: proc(s: ^wchar_t, c: wchar_t, n: size_t) -> ^wchar_t ---;
|
||||
|
||||
// 7.29.4.6 Miscellaneous functions
|
||||
wcslen :: proc(s: ^wchar_t) -> size_t ---;
|
||||
wmemset :: proc(s: ^wchar_t, c: wchar_t, n: size_t) -> ^wchar_t ---;
|
||||
|
||||
// 7.29.5 Wide character time conversion functions
|
||||
wcsftime :: proc(s: ^wchar_t, maxsize: size_t, format: ^wchar_t, timeptr: ^tm) -> size_t ---;
|
||||
|
||||
// 7.29.6.1 Single-byte/wide character conversion functions
|
||||
btowc :: proc(c: int) -> wint_t ---;
|
||||
wctob :: proc(c: wint_t) -> int ---;
|
||||
|
||||
// 7.29.6.2 Conversion state functions
|
||||
mbsinit :: proc(ps: ^mbstate_t) -> int ---;
|
||||
|
||||
// 7.29.6.3 Restartable multibyte/wide character conversion functions
|
||||
mbrlen :: proc(s: cstring, n: size_t, ps: ^mbstate_t) -> size_t ---;
|
||||
mbrtowc :: proc(pwc: ^wchar_t, s: cstring, n: size_t, ps: ^mbstate_t) -> size_t ---;
|
||||
wcrtomb :: proc(s: ^char, wc: wchar_t, ps: ^mbstate_t) -> size_t ---;
|
||||
|
||||
// 7.29.6.4 Restartable multibyte/wide string conversion functions
|
||||
mbsrtowcs :: proc(dst: ^wchar_t, src: ^cstring, len: size_t, ps: ^mbstate_t) -> size_t ---;
|
||||
wcsrtombs :: proc(dst: ^char, src: ^^wchar_t, len: size_t, ps: ^mbstate_t) -> size_t ---;
|
||||
}
|
||||
|
||||
// Large enough and aligned enough for any wide-spread in-use libc.
|
||||
mbstate_t :: struct #align 16 { _: [32]char, }
|
||||
|
||||
// Odin does not have default argument promotion so the need for a separate type
|
||||
// here isn't necessary, though make it distinct just to be safe.
|
||||
wint_t :: distinct wchar_t;
|
||||
|
||||
// Calculate these values correctly regardless of what type wchar_t actually is.
|
||||
WINT_MIN :: 0;
|
||||
WINT_MAX :: 1 << (size_of(wint_t) * 8);
|
||||
WEOF :: ~wint_t(0);
|
||||
@@ -0,0 +1,48 @@
|
||||
package libc
|
||||
|
||||
// 7.30 Wide character classification and mapping utilities
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign import libc "system:libucrt.lib"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
wctrans_t :: distinct wchar_t;
|
||||
wctype_t :: distinct ushort;
|
||||
}
|
||||
|
||||
when ODIN_OS == "linux" {
|
||||
wctrans_t :: distinct rawptr;
|
||||
wctype_t :: distinct ulong;
|
||||
}
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.30.2.1 Wide character classification functions
|
||||
iswalnum :: proc(wc: wint_t) -> int ---;
|
||||
iswalpha :: proc(wc: wint_t) -> int ---;
|
||||
iswblank :: proc(wc: wint_t) -> int ---;
|
||||
iswcntrl :: proc(wc: wint_t) -> int ---;
|
||||
iswdigit :: proc(wc: wint_t) -> int ---;
|
||||
iswgraph :: proc(wc: wint_t) -> int ---;
|
||||
iswlower :: proc(wc: wint_t) -> int ---;
|
||||
iswprint :: proc(wc: wint_t) -> int ---;
|
||||
iswpunct :: proc(wc: wint_t) -> int ---;
|
||||
iswspace :: proc(wc: wint_t) -> int ---;
|
||||
iswupper :: proc(wc: wint_t) -> int ---;
|
||||
iswxdigit :: proc(wc: wint_t) -> int ---;
|
||||
|
||||
// 7.30.2.2 Extensible wide character classification functions
|
||||
iswctype :: proc(wc: wint_t, desc: wctype_t) -> int ---;
|
||||
wctype :: proc(property: cstring) -> wctype_t ---;
|
||||
|
||||
// 7.30.3 Wide character case mapping utilities
|
||||
towlower :: proc(wc: wint_t) -> wint_t ---;
|
||||
towupper :: proc(wc: wint_t) -> wint_t ---;
|
||||
|
||||
// 7.30.3.2 Extensible wide character case mapping functions
|
||||
towctrans :: proc(wc: wint_t, desc: wctrans_t) -> wint_t ---;
|
||||
wctrans :: proc(property: cstring) -> wctrans_t ---;
|
||||
}
|
||||
+342
-60
@@ -1,7 +1,49 @@
|
||||
package compress
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation, optimization.
|
||||
*/
|
||||
|
||||
import "core:io"
|
||||
import "core:image"
|
||||
import "core:bytes"
|
||||
|
||||
/*
|
||||
These settings bound how much compression algorithms will allocate for their output buffer.
|
||||
If streaming their output, these are unnecessary and will be ignored.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
When a decompression routine doesn't stream its output, but writes to a buffer,
|
||||
we pre-allocate an output buffer to speed up decompression. The default is 1 MiB.
|
||||
*/
|
||||
COMPRESS_OUTPUT_ALLOCATE_MIN :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MIN, 1 << 20));
|
||||
|
||||
/*
|
||||
This bounds the maximum a buffer will resize to as needed, or the maximum we'll
|
||||
pre-allocate if you inform the decompression routine you know the payload size.
|
||||
|
||||
For reference, the largest payload size of a GZIP file is 4 GiB.
|
||||
|
||||
*/
|
||||
when size_of(uintptr) == 8 {
|
||||
/*
|
||||
For 64-bit platforms, we set the default max buffer size to 4 GiB,
|
||||
which is GZIP and PKZIP's max payload size.
|
||||
*/
|
||||
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 32));
|
||||
} else {
|
||||
/*
|
||||
For 32-bit platforms, we set the default max buffer size to 512 MiB.
|
||||
*/
|
||||
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 29));
|
||||
}
|
||||
|
||||
|
||||
Error :: union {
|
||||
General_Error,
|
||||
@@ -26,6 +68,13 @@ General_Error :: enum {
|
||||
Checksum_Failed,
|
||||
Incompatible_Options,
|
||||
Unimplemented,
|
||||
|
||||
|
||||
/*
|
||||
Memory errors
|
||||
*/
|
||||
Allocation_Failed,
|
||||
Resize_Failed,
|
||||
}
|
||||
|
||||
GZIP_Error :: enum {
|
||||
@@ -36,6 +85,20 @@ GZIP_Error :: enum {
|
||||
Comment_Too_Long,
|
||||
Payload_Length_Invalid,
|
||||
Payload_CRC_Invalid,
|
||||
|
||||
/*
|
||||
GZIP's payload can be a maximum of max(u32le), or 4 GiB.
|
||||
If you tell it you expect it to contain more, that's obviously an error.
|
||||
*/
|
||||
Payload_Size_Exceeds_Max_Payload,
|
||||
/*
|
||||
For buffered instead of streamed output, the payload size can't exceed
|
||||
the max set by the `COMPRESS_OUTPUT_ALLOCATE_MAX` switch in compress/common.odin.
|
||||
|
||||
You can tweak this setting using `-define:COMPRESS_OUTPUT_ALLOCATE_MAX=size_in_bytes`
|
||||
*/
|
||||
Output_Exceeds_COMPRESS_OUTPUT_ALLOCATE_MAX,
|
||||
|
||||
}
|
||||
|
||||
ZIP_Error :: enum {
|
||||
@@ -62,135 +125,354 @@ Deflate_Error :: enum {
|
||||
BType_3,
|
||||
}
|
||||
|
||||
// General context for ZLIB, LZW, etc.
|
||||
Context :: struct {
|
||||
code_buffer: u32,
|
||||
num_bits: i8,
|
||||
|
||||
// General I/O context for ZLIB, LZW, etc.
|
||||
Context_Memory_Input :: struct #packed {
|
||||
input_data: []u8,
|
||||
output: ^bytes.Buffer,
|
||||
bytes_written: i64,
|
||||
|
||||
code_buffer: u64,
|
||||
num_bits: u64,
|
||||
|
||||
/*
|
||||
num_bits will be set to -100 if the buffer is malformed
|
||||
If we know the data size, we can optimize the reads and writes.
|
||||
*/
|
||||
eof: b8,
|
||||
size_packed: i64,
|
||||
size_unpacked: i64,
|
||||
}
|
||||
#assert(size_of(Context_Memory_Input) == 64);
|
||||
|
||||
input: io.Stream,
|
||||
output: io.Stream,
|
||||
bytes_written: i64,
|
||||
// Used to update hash as we write instead of all at once
|
||||
rolling_hash: u32,
|
||||
Context_Stream_Input :: struct #packed {
|
||||
input_data: []u8,
|
||||
input: io.Stream,
|
||||
output: ^bytes.Buffer,
|
||||
bytes_written: i64,
|
||||
|
||||
// Sliding window buffer. Size must be a power of two.
|
||||
window_size: i64,
|
||||
last: ^[dynamic]byte,
|
||||
code_buffer: u64,
|
||||
num_bits: u64,
|
||||
|
||||
/*
|
||||
If we know the data size, we can optimize the reads and writes.
|
||||
*/
|
||||
size_packed: i64,
|
||||
size_unpacked: i64,
|
||||
|
||||
/*
|
||||
Flags:
|
||||
`input_fully_in_memory`
|
||||
true = This tells us we read input from `input_data` exclusively. [] = EOF.
|
||||
false = Try to refill `input_data` from the `input` stream.
|
||||
*/
|
||||
input_fully_in_memory: b8,
|
||||
|
||||
padding: [1]u8,
|
||||
}
|
||||
|
||||
// Stream helpers
|
||||
/*
|
||||
TODO: These need to be optimized.
|
||||
|
||||
Streams should really only check if a certain method is available once, perhaps even during setup.
|
||||
TODO: The stream versions should really only check if a certain method is available once, perhaps even during setup.
|
||||
|
||||
Bit and byte readers may be merged so that reading bytes will grab them from the bit buffer first.
|
||||
This simplifies end-of-stream handling where bits may be left in the bit buffer.
|
||||
*/
|
||||
|
||||
read_data :: #force_inline proc(c: ^Context, $T: typeid) -> (res: T, err: io.Error) {
|
||||
b := make([]u8, size_of(T), context.temp_allocator);
|
||||
r, e1 := io.to_reader(c.input);
|
||||
_, e2 := io.read(r, b);
|
||||
if !e1 || e2 != .None {
|
||||
return T{}, e2;
|
||||
// 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;
|
||||
}
|
||||
|
||||
input_size_from_stream :: proc(z: ^Context_Stream_Input) -> (res: i64, err: Error) {
|
||||
return io.size(z.input), nil;
|
||||
}
|
||||
|
||||
input_size :: proc{input_size_from_memory, input_size_from_stream};
|
||||
|
||||
@(optimization_mode="speed")
|
||||
read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int) -> (res: []u8, err: io.Error) {
|
||||
#no_bounds_check {
|
||||
if len(z.input_data) >= size {
|
||||
res = z.input_data[:size];
|
||||
z.input_data = z.input_data[size:];
|
||||
return res, .None;
|
||||
}
|
||||
}
|
||||
|
||||
res = (^T)(raw_data(b))^;
|
||||
return res, .None;
|
||||
if len(z.input_data) == 0 {
|
||||
return []u8{}, .EOF;
|
||||
} else {
|
||||
return []u8{}, .Short_Buffer;
|
||||
}
|
||||
}
|
||||
|
||||
read_u8 :: #force_inline proc(z: ^Context) -> (res: u8, err: io.Error) {
|
||||
return read_data(z, u8);
|
||||
@(optimization_mode="speed")
|
||||
read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) {
|
||||
b := make([]u8, size, context.temp_allocator);
|
||||
_, e := z.input->impl_read(b[:]);
|
||||
if e == .None {
|
||||
return b, .None;
|
||||
}
|
||||
|
||||
return []u8{}, e;
|
||||
}
|
||||
|
||||
peek_data :: #force_inline proc(c: ^Context, $T: typeid) -> (res: T, err: io.Error) {
|
||||
read_slice :: proc{read_slice_from_memory, read_slice_from_stream};
|
||||
|
||||
@(optimization_mode="speed")
|
||||
read_data :: #force_inline proc(z: ^$C, $T: typeid) -> (res: T, err: io.Error) {
|
||||
b, e := read_slice(z, size_of(T));
|
||||
if e == .None {
|
||||
return (^T)(&b[0])^, .None;
|
||||
}
|
||||
|
||||
return T{}, e;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8, err: io.Error) {
|
||||
#no_bounds_check {
|
||||
if len(z.input_data) >= 1 {
|
||||
res = z.input_data[0];
|
||||
z.input_data = z.input_data[1:];
|
||||
return res, .None;
|
||||
}
|
||||
}
|
||||
return 0, .EOF;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
read_u8_from_stream :: #force_inline proc(z: ^Context_Stream_Input) -> (res: u8, err: io.Error) {
|
||||
b, e := read_slice_from_stream(z, 1);
|
||||
if e == .None {
|
||||
return b[0], .None;
|
||||
}
|
||||
|
||||
return 0, e;
|
||||
}
|
||||
|
||||
read_u8 :: proc{read_u8_from_memory, read_u8_from_stream};
|
||||
|
||||
/*
|
||||
You would typically only use this at the end of Inflate, to drain bits from the code buffer
|
||||
preferentially.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: io.Error) {
|
||||
if z.num_bits >= 8 {
|
||||
res = u8(read_bits_no_refill_lsb(z, 8));
|
||||
} else {
|
||||
size, _ := input_size(z);
|
||||
if size > 0 {
|
||||
res, err = read_u8(z);
|
||||
} else {
|
||||
err = .EOF;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T);
|
||||
|
||||
#no_bounds_check {
|
||||
if len(z.input_data) >= size {
|
||||
buf := z.input_data[: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);
|
||||
|
||||
// Get current position to read from.
|
||||
curr, e1 := c.input->impl_seek(0, .Current);
|
||||
curr, e1 := z.input->impl_seek(0, .Current);
|
||||
if e1 != .None {
|
||||
return T{}, e1;
|
||||
}
|
||||
r, e2 := io.to_reader_at(c.input);
|
||||
r, e2 := io.to_reader_at(z.input);
|
||||
if !e2 {
|
||||
return T{}, .Empty;
|
||||
}
|
||||
b := make([]u8, size_of(T), context.temp_allocator);
|
||||
_, e3 := io.read_at(r, b, curr);
|
||||
when size <= 128 {
|
||||
b: [size]u8;
|
||||
} else {
|
||||
b := make([]u8, size, context.temp_allocator);
|
||||
}
|
||||
_, e3 := io.read_at(r, b[:], curr);
|
||||
if e3 != .None {
|
||||
return T{}, .Empty;
|
||||
}
|
||||
|
||||
res = (^T)(raw_data(b))^;
|
||||
res = (^T)(&b[0])^;
|
||||
return res, .None;
|
||||
}
|
||||
|
||||
peek_data :: proc{peek_data_from_memory, peek_data_from_stream};
|
||||
|
||||
|
||||
|
||||
// Sliding window read back
|
||||
peek_back_byte :: proc(c: ^Context, offset: i64) -> (res: u8, err: io.Error) {
|
||||
@(optimization_mode="speed")
|
||||
peek_back_byte :: #force_inline proc(z: ^$C, offset: i64) -> (res: u8, err: io.Error) {
|
||||
// Look back into the sliding window.
|
||||
return c.last[offset % c.window_size], .None;
|
||||
return z.output.buf[z.bytes_written - offset], .None;
|
||||
}
|
||||
|
||||
// Generalized bit reader LSB
|
||||
refill_lsb :: proc(z: ^Context, width := i8(24)) {
|
||||
@(optimization_mode="speed")
|
||||
refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width := i8(48)) {
|
||||
refill := u64(width);
|
||||
b := u64(0);
|
||||
|
||||
if z.num_bits > refill {
|
||||
return;
|
||||
}
|
||||
|
||||
for {
|
||||
if z.num_bits > width {
|
||||
if len(z.input_data) != 0 {
|
||||
b = u64(z.input_data[0]);
|
||||
z.input_data = z.input_data[1:];
|
||||
} else {
|
||||
b = 0;
|
||||
}
|
||||
|
||||
z.code_buffer |= b << u8(z.num_bits);
|
||||
z.num_bits += 8;
|
||||
if z.num_bits > refill {
|
||||
break;
|
||||
}
|
||||
if z.code_buffer == 0 && z.num_bits == -1 {
|
||||
}
|
||||
}
|
||||
|
||||
// Generalized bit reader LSB
|
||||
@(optimization_mode="speed")
|
||||
refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) {
|
||||
refill := u64(width);
|
||||
|
||||
for {
|
||||
if z.num_bits > refill {
|
||||
break;
|
||||
}
|
||||
if z.code_buffer == 0 && z.num_bits > 63 {
|
||||
z.num_bits = 0;
|
||||
}
|
||||
if z.code_buffer >= 1 << uint(z.num_bits) {
|
||||
// Code buffer is malformed.
|
||||
z.num_bits = -100;
|
||||
return;
|
||||
}
|
||||
c, err := read_u8(z);
|
||||
if err != .None {
|
||||
// This is fine at the end of the file.
|
||||
z.num_bits = -42;
|
||||
z.eof = true;
|
||||
z.num_bits = max(u64);
|
||||
return;
|
||||
}
|
||||
z.code_buffer |= (u32(c) << u8(z.num_bits));
|
||||
b, err := read_u8(z);
|
||||
if err != .None {
|
||||
// This is fine at the end of the file.
|
||||
return;
|
||||
}
|
||||
z.code_buffer |= (u64(b) << u8(z.num_bits));
|
||||
z.num_bits += 8;
|
||||
}
|
||||
}
|
||||
|
||||
consume_bits_lsb :: #force_inline proc(z: ^Context, width: u8) {
|
||||
refill_lsb :: proc{refill_lsb_from_memory, refill_lsb_from_stream};
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
consume_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) {
|
||||
z.code_buffer >>= width;
|
||||
z.num_bits -= i8(width);
|
||||
z.num_bits -= u64(width);
|
||||
}
|
||||
|
||||
peek_bits_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
|
||||
if z.num_bits < i8(width) {
|
||||
@(optimization_mode="speed")
|
||||
consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) {
|
||||
z.code_buffer >>= width;
|
||||
z.num_bits -= u64(width);
|
||||
}
|
||||
|
||||
consume_bits_lsb :: proc{consume_bits_lsb_from_memory, consume_bits_lsb_from_stream};
|
||||
|
||||
@(optimization_mode="speed")
|
||||
peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
if z.num_bits < u64(width) {
|
||||
refill_lsb(z);
|
||||
}
|
||||
// assert(z.num_bits >= i8(width));
|
||||
return z.code_buffer & ~(~u32(0) << width);
|
||||
return u32(z.code_buffer & ~(~u64(0) << width));
|
||||
}
|
||||
|
||||
peek_bits_no_refill_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
|
||||
assert(z.num_bits >= i8(width));
|
||||
return z.code_buffer & ~(~u32(0) << width);
|
||||
@(optimization_mode="speed")
|
||||
peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
if z.num_bits < u64(width) {
|
||||
refill_lsb(z);
|
||||
}
|
||||
return u32(z.code_buffer & ~(~u64(0) << width));
|
||||
}
|
||||
|
||||
read_bits_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
|
||||
peek_bits_lsb :: proc{peek_bits_lsb_from_memory, peek_bits_lsb_from_stream};
|
||||
|
||||
@(optimization_mode="speed")
|
||||
peek_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
assert(z.num_bits >= u64(width));
|
||||
return u32(z.code_buffer & ~(~u64(0) << width));
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
assert(z.num_bits >= u64(width));
|
||||
return u32(z.code_buffer & ~(~u64(0) << width));
|
||||
}
|
||||
|
||||
peek_bits_no_refill_lsb :: proc{peek_bits_no_refill_lsb_from_memory, peek_bits_no_refill_lsb_from_stream};
|
||||
|
||||
@(optimization_mode="speed")
|
||||
read_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
k := #force_inline peek_bits_lsb(z, width);
|
||||
#force_inline consume_bits_lsb(z, width);
|
||||
return k;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
k := peek_bits_lsb(z, width);
|
||||
consume_bits_lsb(z, width);
|
||||
return k;
|
||||
}
|
||||
|
||||
read_bits_no_refill_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
|
||||
read_bits_lsb :: proc{read_bits_lsb_from_memory, read_bits_lsb_from_stream};
|
||||
|
||||
@(optimization_mode="speed")
|
||||
read_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
k := #force_inline peek_bits_no_refill_lsb(z, width);
|
||||
#force_inline consume_bits_lsb(z, width);
|
||||
return k;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
k := peek_bits_no_refill_lsb(z, width);
|
||||
consume_bits_lsb(z, width);
|
||||
return k;
|
||||
}
|
||||
|
||||
discard_to_next_byte_lsb :: proc(z: ^Context) {
|
||||
read_bits_no_refill_lsb :: proc{read_bits_no_refill_lsb_from_memory, read_bits_no_refill_lsb_from_stream};
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
discard_to_next_byte_lsb_from_memory :: proc(z: ^Context_Memory_Input) {
|
||||
discard := u8(z.num_bits & 7);
|
||||
#force_inline consume_bits_lsb(z, discard);
|
||||
}
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
discard_to_next_byte_lsb_from_stream :: proc(z: ^Context_Stream_Input) {
|
||||
discard := u8(z.num_bits & 7);
|
||||
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};
|
||||
@@ -1,9 +1,21 @@
|
||||
//+ignore
|
||||
package gzip
|
||||
|
||||
import "core:compress/gzip"
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
Ginger Bill: Cosmetic changes.
|
||||
|
||||
A small GZIP implementation as an example.
|
||||
*/
|
||||
|
||||
import "core:bytes"
|
||||
import "core:os"
|
||||
import "core:compress"
|
||||
import "core:fmt"
|
||||
|
||||
// Small GZIP file with fextra, fname and fcomment present.
|
||||
@private
|
||||
@@ -20,8 +32,7 @@ TEST: []u8 = {
|
||||
|
||||
main :: proc() {
|
||||
// Set up output buffer.
|
||||
buf: bytes.Buffer;
|
||||
defer bytes.buffer_destroy(&buf);
|
||||
buf := bytes.Buffer{};
|
||||
|
||||
stdout :: proc(s: string) {
|
||||
os.write_string(os.stdout, s);
|
||||
@@ -34,25 +45,32 @@ main :: proc() {
|
||||
|
||||
if len(args) < 2 {
|
||||
stderr("No input file specified.\n");
|
||||
err := gzip.load(TEST, &buf);
|
||||
if err != nil {
|
||||
err := load(slice=TEST, buf=&buf, known_gzip_size=len(TEST));
|
||||
if err == nil {
|
||||
stdout("Displaying test vector: ");
|
||||
stdout(bytes.buffer_to_string(&buf));
|
||||
stdout("\n");
|
||||
} else {
|
||||
fmt.printf("gzip.load returned %v\n", err);
|
||||
}
|
||||
bytes.buffer_destroy(&buf);
|
||||
os.exit(0);
|
||||
}
|
||||
|
||||
// The rest are all files.
|
||||
args = args[1:];
|
||||
err: gzip.Error;
|
||||
err: Error;
|
||||
|
||||
for file in args {
|
||||
if file == "-" {
|
||||
// Read from stdin
|
||||
s := os.stream_from_handle(os.stdin);
|
||||
err = gzip.load(s, &buf);
|
||||
ctx := &compress.Context_Stream_Input{
|
||||
input = s,
|
||||
};
|
||||
err = load(ctx, &buf);
|
||||
} else {
|
||||
err = gzip.load(file, &buf);
|
||||
err = load(file, &buf);
|
||||
}
|
||||
if err != nil {
|
||||
if err != E_General.File_Not_Found {
|
||||
@@ -62,9 +80,10 @@ main :: proc() {
|
||||
os.exit(1);
|
||||
}
|
||||
stderr("GZIP returned an error.\n");
|
||||
bytes.buffer_destroy(&buf);
|
||||
os.exit(2);
|
||||
}
|
||||
stdout(bytes.buffer_to_string(&buf));
|
||||
}
|
||||
os.exit(0);
|
||||
bytes.buffer_destroy(&buf);
|
||||
}
|
||||
|
||||
+112
-58
@@ -1,5 +1,19 @@
|
||||
package gzip
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
This package implements support for the GZIP file format v4.3,
|
||||
as specified in RFC 1952.
|
||||
|
||||
It is implemented in such a way that it lends itself naturally
|
||||
to be the input to a complementary TAR implementation.
|
||||
*/
|
||||
|
||||
import "core:compress/zlib"
|
||||
import "core:compress"
|
||||
import "core:os"
|
||||
@@ -7,16 +21,6 @@ import "core:io"
|
||||
import "core:bytes"
|
||||
import "core:hash"
|
||||
|
||||
/*
|
||||
|
||||
This package implements support for the GZIP file format v4.3,
|
||||
as specified in RFC 1952.
|
||||
|
||||
It is implemented in such a way that it lends itself naturally
|
||||
to be the input to a complementary TAR implementation.
|
||||
|
||||
*/
|
||||
|
||||
Magic :: enum u16le {
|
||||
GZIP = 0x8b << 8 | 0x1f,
|
||||
}
|
||||
@@ -95,40 +99,54 @@ E_GZIP :: compress.GZIP_Error;
|
||||
E_ZLIB :: compress.ZLIB_Error;
|
||||
E_Deflate :: compress.Deflate_Error;
|
||||
|
||||
load_from_slice :: proc(slice: []u8, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) {
|
||||
GZIP_MAX_PAYLOAD_SIZE :: int(max(u32le));
|
||||
|
||||
r := bytes.Reader{};
|
||||
bytes.reader_init(&r, slice);
|
||||
stream := bytes.reader_to_stream(&r);
|
||||
load :: proc{load_from_slice, load_from_file, load_from_context};
|
||||
|
||||
err = load_from_stream(stream, buf, allocator);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) {
|
||||
load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
|
||||
data, ok := os.read_entire_file(filename, allocator);
|
||||
defer delete(data);
|
||||
|
||||
err = E_General.File_Not_Found;
|
||||
if ok {
|
||||
err = load_from_slice(data, buf, allocator);
|
||||
err = load_from_slice(data, buf, len(data), expected_output_size, allocator);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) {
|
||||
ctx := compress.Context{
|
||||
input = stream,
|
||||
};
|
||||
load_from_slice :: proc(slice: []u8, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
|
||||
buf := buf;
|
||||
ws := bytes.buffer_to_stream(buf);
|
||||
ctx.output = ws;
|
||||
|
||||
header, e := compress.read_data(&ctx, Header);
|
||||
z := &compress.Context_Memory_Input{
|
||||
input_data = slice,
|
||||
output = buf,
|
||||
};
|
||||
return load_from_context(z, buf, known_gzip_size, expected_output_size, allocator);
|
||||
}
|
||||
|
||||
load_from_context :: proc(z: ^$C, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
|
||||
buf := buf;
|
||||
expected_output_size := expected_output_size;
|
||||
|
||||
input_data_consumed := 0;
|
||||
|
||||
z.output = buf;
|
||||
|
||||
if expected_output_size > GZIP_MAX_PAYLOAD_SIZE {
|
||||
return E_GZIP.Payload_Size_Exceeds_Max_Payload;
|
||||
}
|
||||
|
||||
if expected_output_size > compress.COMPRESS_OUTPUT_ALLOCATE_MAX {
|
||||
return E_GZIP.Output_Exceeds_COMPRESS_OUTPUT_ALLOCATE_MAX;
|
||||
}
|
||||
|
||||
b: []u8;
|
||||
|
||||
header, e := compress.read_data(z, Header);
|
||||
if e != .None {
|
||||
return E_General.File_Too_Short;
|
||||
}
|
||||
input_data_consumed += size_of(Header);
|
||||
|
||||
if header.magic != .GZIP {
|
||||
return E_GZIP.Invalid_GZIP_Signature;
|
||||
@@ -153,7 +171,9 @@ load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := con
|
||||
// printf("os: %v\n", OS_Name[header.os]);
|
||||
|
||||
if .extra in header.flags {
|
||||
xlen, e_extra := compress.read_data(&ctx, u16le);
|
||||
xlen, e_extra := compress.read_data(z, u16le);
|
||||
input_data_consumed += 2;
|
||||
|
||||
if e_extra != .None {
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
@@ -169,19 +189,21 @@ load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := con
|
||||
|
||||
for xlen >= 4 {
|
||||
// println("Parsing Extra field(s).");
|
||||
field_id, field_error = compress.read_data(&ctx, [2]u8);
|
||||
field_id, field_error = compress.read_data(z, [2]u8);
|
||||
if field_error != .None {
|
||||
// printf("Parsing Extra returned: %v\n", field_error);
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
xlen -= 2;
|
||||
input_data_consumed += 2;
|
||||
|
||||
field_length, field_error = compress.read_data(&ctx, u16le);
|
||||
field_length, field_error = compress.read_data(z, u16le);
|
||||
if field_error != .None {
|
||||
// printf("Parsing Extra returned: %v\n", field_error);
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
xlen -= 2;
|
||||
input_data_consumed += 2;
|
||||
|
||||
if xlen <= 0 {
|
||||
// We're not going to try and recover by scanning for a ZLIB header.
|
||||
@@ -191,16 +213,16 @@ load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := con
|
||||
|
||||
// printf(" Field \"%v\" of length %v found: ", string(field_id[:]), field_length);
|
||||
if field_length > 0 {
|
||||
field_data := make([]u8, field_length, context.temp_allocator);
|
||||
_, field_error = ctx.input->impl_read(field_data);
|
||||
b, field_error = compress.read_slice(z, int(field_length));
|
||||
if field_error != .None {
|
||||
// printf("Parsing Extra returned: %v\n", field_error);
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
xlen -= field_length;
|
||||
input_data_consumed += int(field_length);
|
||||
|
||||
// printf("%v\n", string(field_data));
|
||||
}
|
||||
}
|
||||
|
||||
if xlen != 0 {
|
||||
return E_GZIP.Invalid_Extra_Data;
|
||||
@@ -211,16 +233,16 @@ load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := con
|
||||
if .name in header.flags {
|
||||
// Should be enough.
|
||||
name: [1024]u8;
|
||||
b: [1]u8;
|
||||
i := 0;
|
||||
name_error: io.Error;
|
||||
|
||||
for i < len(name) {
|
||||
_, name_error = ctx.input->impl_read(b[:]);
|
||||
b, name_error = compress.read_slice(z, 1);
|
||||
if name_error != .None {
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
if b == 0 {
|
||||
input_data_consumed += 1;
|
||||
if b[0] == 0 {
|
||||
break;
|
||||
}
|
||||
name[i] = b[0];
|
||||
@@ -235,16 +257,16 @@ load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := con
|
||||
if .comment in header.flags {
|
||||
// Should be enough.
|
||||
comment: [1024]u8;
|
||||
b: [1]u8;
|
||||
i := 0;
|
||||
comment_error: io.Error;
|
||||
|
||||
for i < len(comment) {
|
||||
_, comment_error = ctx.input->impl_read(b[:]);
|
||||
b, comment_error = compress.read_slice(z, 1);
|
||||
if comment_error != .None {
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
if b == 0 {
|
||||
input_data_consumed += 1;
|
||||
if b[0] == 0 {
|
||||
break;
|
||||
}
|
||||
comment[i] = b[0];
|
||||
@@ -257,9 +279,9 @@ load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := con
|
||||
}
|
||||
|
||||
if .header_crc in header.flags {
|
||||
crc16: [2]u8;
|
||||
crc_error: io.Error;
|
||||
_, crc_error = ctx.input->impl_read(crc16[:]);
|
||||
_, crc_error = compress.read_slice(z, 2);
|
||||
input_data_consumed += 2;
|
||||
if crc_error != .None {
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
@@ -272,42 +294,74 @@ load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := con
|
||||
/*
|
||||
We should have arrived at the ZLIB payload.
|
||||
*/
|
||||
payload_u32le: u32le;
|
||||
|
||||
zlib_error := zlib.inflate_raw(&ctx);
|
||||
// fmt.printf("known_gzip_size: %v | expected_output_size: %v\n", known_gzip_size, expected_output_size);
|
||||
|
||||
// fmt.printf("ZLIB returned: %v\n", zlib_error);
|
||||
if expected_output_size > -1 {
|
||||
/*
|
||||
We already checked that it's not larger than the output buffer max,
|
||||
or GZIP length field's max.
|
||||
|
||||
We'll just pass it on to `zlib.inflate_raw`;
|
||||
*/
|
||||
} else {
|
||||
/*
|
||||
If we know the size of the GZIP file *and* it is fully in memory,
|
||||
then we can peek at the unpacked size at the end.
|
||||
|
||||
We'll still want to ensure there's capacity left in the output buffer when we write, of course.
|
||||
|
||||
*/
|
||||
if known_gzip_size > -1 {
|
||||
offset := i64(known_gzip_size - input_data_consumed - 4);
|
||||
size, _ := compress.input_size(z);
|
||||
if size >= offset + 4 {
|
||||
length_bytes := z.input_data[offset:][:4];
|
||||
payload_u32le = (^u32le)(&length_bytes[0])^;
|
||||
expected_output_size = int(payload_u32le);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
TODO(Jeroen): When reading a GZIP from a stream, check if impl_seek is present.
|
||||
If so, we can seek to the end, grab the size from the footer, and seek back to payload start.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// fmt.printf("GZIP: Expected Payload Size: %v\n", expected_output_size);
|
||||
|
||||
zlib_error := zlib.inflate_raw(z=z, expected_output_size=expected_output_size);
|
||||
if zlib_error != nil {
|
||||
return zlib_error;
|
||||
}
|
||||
|
||||
/*
|
||||
Read CRC32 using the ctx bit reader because zlib may leave bytes in there.
|
||||
*/
|
||||
compress.discard_to_next_byte_lsb(&ctx);
|
||||
compress.discard_to_next_byte_lsb(z);
|
||||
|
||||
footer_error: io.Error;
|
||||
|
||||
payload_crc_b: [4]u8;
|
||||
payload_len_b: [4]u8;
|
||||
for i in 0..3 {
|
||||
payload_crc_b[i] = u8(compress.read_bits_lsb(&ctx, 8));
|
||||
for _, i in payload_crc_b {
|
||||
payload_crc_b[i], footer_error = compress.read_u8_prefer_code_buffer_lsb(z);
|
||||
}
|
||||
payload_crc := transmute(u32le)payload_crc_b;
|
||||
for i in 0..3 {
|
||||
payload_len_b[i] = u8(compress.read_bits_lsb(&ctx, 8));
|
||||
}
|
||||
payload_len := int(transmute(u32le)payload_len_b);
|
||||
|
||||
payload := bytes.buffer_to_bytes(buf);
|
||||
crc32 := u32le(hash.crc32(payload));
|
||||
|
||||
crc32 := u32le(hash.crc32(payload));
|
||||
if crc32 != payload_crc {
|
||||
return E_GZIP.Payload_CRC_Invalid;
|
||||
}
|
||||
|
||||
if len(payload) != payload_len {
|
||||
payload_len_b: [4]u8;
|
||||
for _, i in payload_len_b {
|
||||
payload_len_b[i], footer_error = compress.read_u8_prefer_code_buffer_lsb(z);
|
||||
}
|
||||
payload_len := transmute(u32le)payload_len_b;
|
||||
|
||||
if len(payload) != int(payload_len) {
|
||||
return E_GZIP.Payload_Length_Invalid;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_slice, load_from_stream};
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
//+ignore
|
||||
package zlib
|
||||
|
||||
import "core:compress/zlib"
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
An example of how to use `zlib.inflate`.
|
||||
*/
|
||||
|
||||
import "core:bytes"
|
||||
import "core:fmt"
|
||||
|
||||
@@ -26,11 +35,12 @@ main :: proc() {
|
||||
171, 15, 18, 59, 138, 112, 63, 23, 205, 110, 254, 136, 109, 78, 231,
|
||||
63, 234, 138, 133, 204,
|
||||
};
|
||||
OUTPUT_SIZE :: 438;
|
||||
|
||||
buf: bytes.Buffer;
|
||||
|
||||
// We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one.
|
||||
err := zlib.inflate(ODIN_DEMO, &buf);
|
||||
err := inflate(input=ODIN_DEMO, buf=&buf, expected_output_size=OUTPUT_SIZE);
|
||||
defer bytes.buffer_destroy(&buf);
|
||||
|
||||
if err != nil {
|
||||
@@ -38,5 +48,5 @@ main :: proc() {
|
||||
}
|
||||
s := bytes.buffer_to_string(&buf);
|
||||
fmt.printf("Input: %v bytes, output (%v bytes):\n%v\n", len(ODIN_DEMO), len(s), s);
|
||||
assert(len(s) == 438);
|
||||
assert(len(s) == OUTPUT_SIZE);
|
||||
}
|
||||
|
||||
+220
-150
@@ -1,17 +1,34 @@
|
||||
package zlib
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation, optimization.
|
||||
Ginger Bill: Cosmetic changes.
|
||||
*/
|
||||
|
||||
import "core:compress"
|
||||
|
||||
import "core:mem"
|
||||
import "core:io"
|
||||
import "core:bytes"
|
||||
import "core:hash"
|
||||
import "core:bytes"
|
||||
|
||||
/*
|
||||
zlib.inflate decompresses a ZLIB stream passed in as a []u8 or io.Stream.
|
||||
Returns: Error.
|
||||
*/
|
||||
|
||||
Context :: compress.Context;
|
||||
/*
|
||||
Do we do Adler32 as we write bytes to output?
|
||||
It used to be faster to do it inline, now it's faster to do it at the end of `inflate`.
|
||||
|
||||
We'll see what's faster after more optimization, and might end up removing
|
||||
`Context.rolling_hash` if not inlining it is still faster.
|
||||
|
||||
*/
|
||||
|
||||
Compression_Method :: enum u8 {
|
||||
DEFLATE = 8,
|
||||
@@ -102,7 +119,7 @@ Huffman_Table :: struct {
|
||||
};
|
||||
|
||||
// Implementation starts here
|
||||
|
||||
@(optimization_mode="speed")
|
||||
z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) {
|
||||
assert(bits <= 16);
|
||||
// NOTE: Can optimize with llvm.bitreverse.i64 or some bit twiddling
|
||||
@@ -117,27 +134,114 @@ z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) {
|
||||
return;
|
||||
}
|
||||
|
||||
write_byte :: #force_inline proc(z: ^Context, c: u8) -> (err: io.Error) #no_bounds_check {
|
||||
c := c;
|
||||
buf := transmute([]u8)mem.Raw_Slice{data=&c, len=1};
|
||||
z.rolling_hash = hash.adler32(buf, z.rolling_hash);
|
||||
|
||||
_, e := z.output->impl_write(buf);
|
||||
if e != .None {
|
||||
return e;
|
||||
@(optimization_mode="speed")
|
||||
grow_buffer :: proc(buf: ^[dynamic]u8) -> (err: compress.Error) {
|
||||
/*
|
||||
That we get here at all means that we didn't pass an expected output size,
|
||||
or that it was too little.
|
||||
*/
|
||||
|
||||
/*
|
||||
Double until we reach the maximum allowed.
|
||||
*/
|
||||
new_size := min(len(buf) << 1, compress.COMPRESS_OUTPUT_ALLOCATE_MAX);
|
||||
resize(buf, new_size);
|
||||
if len(buf) != new_size {
|
||||
/*
|
||||
Resize failed.
|
||||
*/
|
||||
return .Resize_Failed;
|
||||
}
|
||||
z.last[z.bytes_written % z.window_size] = c;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: Make these return compress.Error.
|
||||
*/
|
||||
|
||||
@(optimization_mode="speed")
|
||||
write_byte :: #force_inline proc(z: ^$C, c: u8) -> (err: io.Error) #no_bounds_check {
|
||||
/*
|
||||
Resize if needed.
|
||||
*/
|
||||
if int(z.bytes_written) + 1 >= len(z.output.buf) {
|
||||
e := grow_buffer(&z.output.buf);
|
||||
if e != nil {
|
||||
return .Short_Write;
|
||||
}
|
||||
}
|
||||
|
||||
#no_bounds_check {
|
||||
z.output.buf[z.bytes_written] = c;
|
||||
}
|
||||
z.bytes_written += 1;
|
||||
return .None;
|
||||
}
|
||||
|
||||
allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_Table, err: Error) {
|
||||
@(optimization_mode="speed")
|
||||
repl_byte :: proc(z: ^$C, count: u16, c: u8) -> (err: io.Error) #no_bounds_check {
|
||||
/*
|
||||
TODO(Jeroen): Once we have a magic ring buffer, we can just peek/write into it
|
||||
without having to worry about wrapping, so no need for a temp allocation to give to
|
||||
the output stream, just give it _that_ slice.
|
||||
*/
|
||||
|
||||
z = new(Huffman_Table, allocator);
|
||||
return z, nil;
|
||||
/*
|
||||
Resize if needed.
|
||||
*/
|
||||
if int(z.bytes_written) + int(count) >= len(z.output.buf) {
|
||||
e := grow_buffer(&z.output.buf);
|
||||
if e != nil {
|
||||
return .Short_Write;
|
||||
}
|
||||
}
|
||||
|
||||
#no_bounds_check {
|
||||
for _ in 0..<count {
|
||||
z.output.buf[z.bytes_written] = c;
|
||||
z.bytes_written += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return .None;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
repl_bytes :: proc(z: ^$C, count: u16, distance: u16) -> (err: io.Error) {
|
||||
/*
|
||||
TODO(Jeroen): Once we have a magic ring buffer, we can just peek/write into it
|
||||
without having to worry about wrapping, so no need for a temp allocation to give to
|
||||
the output stream, just give it _that_ slice.
|
||||
*/
|
||||
|
||||
offset := i64(distance);
|
||||
|
||||
if int(z.bytes_written) + int(count) >= len(z.output.buf) {
|
||||
e := grow_buffer(&z.output.buf);
|
||||
if e != nil {
|
||||
return .Short_Write;
|
||||
}
|
||||
}
|
||||
|
||||
#no_bounds_check {
|
||||
for _ in 0..<count {
|
||||
c := z.output.buf[z.bytes_written - offset];
|
||||
z.output.buf[z.bytes_written] = c;
|
||||
z.bytes_written += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return .None;
|
||||
}
|
||||
|
||||
|
||||
allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_Table, err: Error) {
|
||||
return new(Huffman_Table, allocator), nil;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
sizes: [HUFFMAN_MAX_BITS+1]int;
|
||||
next_code: [HUFFMAN_MAX_BITS]int;
|
||||
@@ -147,34 +251,34 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
mem.zero_slice(sizes[:]);
|
||||
mem.zero_slice(z.fast[:]);
|
||||
|
||||
for v, _ in code_lengths {
|
||||
for v in code_lengths {
|
||||
sizes[v] += 1;
|
||||
}
|
||||
sizes[0] = 0;
|
||||
|
||||
for i in 1..16 {
|
||||
for i in 1..<(HUFFMAN_MAX_BITS+1) {
|
||||
if sizes[i] > (1 << uint(i)) {
|
||||
return E_Deflate.Huffman_Bad_Sizes;
|
||||
}
|
||||
}
|
||||
code := int(0);
|
||||
|
||||
for i in 1..<16 {
|
||||
for i in 1..<HUFFMAN_MAX_BITS {
|
||||
next_code[i] = code;
|
||||
z.firstcode[i] = u16(code);
|
||||
z.firstsymbol[i] = u16(k);
|
||||
code = code + sizes[i];
|
||||
if sizes[i] != 0 {
|
||||
if (code - 1 >= (1 << u16(i))) {
|
||||
if code - 1 >= (1 << u16(i)) {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths;
|
||||
}
|
||||
}
|
||||
z.maxcode[i] = code << (16 - uint(i));
|
||||
z.maxcode[i] = code << (HUFFMAN_MAX_BITS - uint(i));
|
||||
code <<= 1;
|
||||
k += int(sizes[i]);
|
||||
}
|
||||
|
||||
z.maxcode[16] = 0x10000; // Sentinel
|
||||
z.maxcode[HUFFMAN_MAX_BITS] = 0x10000; // Sentinel
|
||||
c: int;
|
||||
|
||||
for v, ci in code_lengths {
|
||||
@@ -183,7 +287,7 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
fastv := u16((u16(v) << 9) | u16(ci));
|
||||
z.size[c] = u8(v);
|
||||
z.value[c] = u16(ci);
|
||||
if (v <= ZFAST_BITS) {
|
||||
if v <= ZFAST_BITS {
|
||||
j := z_bit_reverse(u16(next_code[v]), v);
|
||||
for j < (1 << ZFAST_BITS) {
|
||||
z.fast[j] = fastv;
|
||||
@@ -196,25 +300,20 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
decode_huffman_slowpath :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
|
||||
@(optimization_mode="speed")
|
||||
decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
|
||||
code := u16(compress.peek_bits_lsb(z,16));
|
||||
|
||||
r = 0;
|
||||
err = nil;
|
||||
|
||||
k: int;
|
||||
k := int(z_bit_reverse(code, 16));
|
||||
s: u8;
|
||||
|
||||
code := u16(compress.peek_bits_lsb(z, 16));
|
||||
|
||||
k = int(z_bit_reverse(code, 16));
|
||||
|
||||
#no_bounds_check for s = HUFFMAN_FAST_BITS+1; ; {
|
||||
if k < t.maxcode[s] {
|
||||
break;
|
||||
}
|
||||
s += 1;
|
||||
}
|
||||
if (s >= 16) {
|
||||
if s >= 16 {
|
||||
return 0, E_Deflate.Bad_Huffman_Code;
|
||||
}
|
||||
// code size is s, so:
|
||||
@@ -232,14 +331,14 @@ decode_huffman_slowpath :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err:
|
||||
return r, nil;
|
||||
}
|
||||
|
||||
decode_huffman :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
|
||||
|
||||
@(optimization_mode="speed")
|
||||
decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
|
||||
if z.num_bits < 16 {
|
||||
if z.num_bits == -100 {
|
||||
if z.num_bits > 63 {
|
||||
return 0, E_ZLIB.Code_Buffer_Malformed;
|
||||
}
|
||||
compress.refill_lsb(z);
|
||||
if z.eof {
|
||||
if z.num_bits > 63 {
|
||||
return 0, E_General.Stream_Too_Short;
|
||||
}
|
||||
}
|
||||
@@ -252,7 +351,8 @@ decode_huffman :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) #
|
||||
return decode_huffman_slowpath(z, t);
|
||||
}
|
||||
|
||||
parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) -> (err: Error) #no_bounds_check {
|
||||
@(optimization_mode="speed")
|
||||
parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err: Error) #no_bounds_check {
|
||||
#no_bounds_check for {
|
||||
value, e := decode_huffman(z, z_repeat);
|
||||
if e != nil {
|
||||
@@ -265,8 +365,8 @@ parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) ->
|
||||
}
|
||||
} else {
|
||||
if value == 256 {
|
||||
// End of block
|
||||
return nil;
|
||||
// End of block
|
||||
return nil;
|
||||
}
|
||||
|
||||
value -= 257;
|
||||
@@ -290,7 +390,6 @@ parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) ->
|
||||
return E_Deflate.Bad_Distance;
|
||||
}
|
||||
|
||||
offset := i64(z.bytes_written - i64(distance));
|
||||
/*
|
||||
These might be sped up with a repl_byte call that copies
|
||||
from the already written output more directly, and that
|
||||
@@ -303,23 +402,17 @@ parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) ->
|
||||
Replicate the last outputted byte, length times.
|
||||
*/
|
||||
if length > 0 {
|
||||
b, e := compress.peek_back_byte(z, offset);
|
||||
c := z.output.buf[z.bytes_written - i64(distance)];
|
||||
e := repl_byte(z, length, c);
|
||||
if e != .None {
|
||||
return E_General.Output_Too_Short;
|
||||
}
|
||||
#no_bounds_check for _ in 0..<length {
|
||||
write_byte(z, b);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if length > 0 {
|
||||
#no_bounds_check for _ in 0..<length {
|
||||
b, e := compress.peek_back_byte(z, offset);
|
||||
if e != .None {
|
||||
return E_General.Output_Too_Short;
|
||||
}
|
||||
write_byte(z, b);
|
||||
offset += 1;
|
||||
e := repl_bytes(z, length, distance);
|
||||
if e != .None {
|
||||
return E_General.Output_Too_Short;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,22 +420,18 @@ parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) ->
|
||||
}
|
||||
}
|
||||
|
||||
inflate_from_stream :: proc(using ctx: ^Context, raw := false, allocator := context.allocator) -> (err: Error) #no_bounds_check {
|
||||
@(optimization_mode="speed")
|
||||
inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := false, expected_output_size := -1, allocator := context.allocator) -> (err: Error) #no_bounds_check {
|
||||
/*
|
||||
ctx.input must be an io.Stream backed by an implementation that supports:
|
||||
- read
|
||||
- size
|
||||
|
||||
ctx.output must be an io.Stream backed by an implementation that supports:
|
||||
- write
|
||||
ctx.output must be a bytes.Buffer for now. We'll add a separate implementation that writes to a stream.
|
||||
|
||||
raw determines whether the ZLIB header is processed, or we're inflating a raw
|
||||
DEFLATE stream.
|
||||
*/
|
||||
|
||||
if !raw {
|
||||
data_size := io.size(ctx.input);
|
||||
if data_size < 6 {
|
||||
size, size_err := compress.input_size(ctx);
|
||||
if size < 6 || size_err != nil {
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
|
||||
@@ -353,26 +442,22 @@ inflate_from_stream :: proc(using ctx: ^Context, raw := false, allocator := cont
|
||||
return E_General.Unknown_Compression_Method;
|
||||
}
|
||||
|
||||
cinfo := (cmf >> 4) & 0xf;
|
||||
if cinfo > 7 {
|
||||
if cinfo := (cmf >> 4) & 0xf; cinfo > 7 {
|
||||
return E_ZLIB.Unsupported_Window_Size;
|
||||
}
|
||||
ctx.window_size = 1 << (cinfo + 8);
|
||||
|
||||
flg, _ := compress.read_u8(ctx);
|
||||
|
||||
fcheck := flg & 0x1f;
|
||||
fcheck := flg & 0x1f;
|
||||
fcheck_computed := (cmf << 8 | flg) & 0x1f;
|
||||
if fcheck != fcheck_computed {
|
||||
return E_General.Checksum_Failed;
|
||||
}
|
||||
|
||||
fdict := (flg >> 5) & 1;
|
||||
/*
|
||||
We don't handle built-in dictionaries for now.
|
||||
They're application specific and PNG doesn't use them.
|
||||
*/
|
||||
if fdict != 0 {
|
||||
if fdict := (flg >> 5) & 1; fdict != 0 {
|
||||
return E_ZLIB.FDICT_Unsupported;
|
||||
}
|
||||
|
||||
@@ -383,63 +468,70 @@ inflate_from_stream :: proc(using ctx: ^Context, raw := false, allocator := cont
|
||||
at the end to compare checksums.
|
||||
*/
|
||||
|
||||
// Seed the Adler32 rolling checksum.
|
||||
ctx.rolling_hash = 1;
|
||||
}
|
||||
|
||||
// Parse ZLIB stream without header.
|
||||
err = inflate_raw(ctx);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
// Parse ZLIB stream without header.
|
||||
inflate_raw(z=ctx, expected_output_size=expected_output_size) or_return;
|
||||
|
||||
if !raw {
|
||||
compress.discard_to_next_byte_lsb(ctx);
|
||||
|
||||
adler32 := compress.read_bits_lsb(ctx, 8) << 24 | compress.read_bits_lsb(ctx, 8) << 16 | compress.read_bits_lsb(ctx, 8) << 8 | compress.read_bits_lsb(ctx, 8);
|
||||
if ctx.rolling_hash != u32(adler32) {
|
||||
adler_b: [4]u8;
|
||||
for _, i in adler_b {
|
||||
adler_b[i], _ = compress.read_u8_prefer_code_buffer_lsb(ctx);
|
||||
}
|
||||
adler := transmute(u32be)adler_b;
|
||||
|
||||
output_hash := hash.adler32(ctx.output.buf[:]);
|
||||
|
||||
if output_hash != u32(adler) {
|
||||
return E_General.Checksum_Failed;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
// @(optimization_mode="speed")
|
||||
inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> (err: Error) #no_bounds_check {
|
||||
final := u32(0);
|
||||
type := u32(0);
|
||||
// TODO: Check alignment of reserve/resize.
|
||||
|
||||
z.num_bits = 0;
|
||||
@(optimization_mode="speed")
|
||||
inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.allocator) -> (err: Error) #no_bounds_check {
|
||||
expected_output_size := expected_output_size;
|
||||
|
||||
/*
|
||||
Always set up a minimum allocation size.
|
||||
*/
|
||||
expected_output_size = max(max(expected_output_size, compress.COMPRESS_OUTPUT_ALLOCATE_MIN), 512);
|
||||
|
||||
// fmt.printf("\nZLIB: Expected Payload Size: %v\n\n", expected_output_size);
|
||||
|
||||
if expected_output_size > 0 && expected_output_size <= compress.COMPRESS_OUTPUT_ALLOCATE_MAX {
|
||||
/*
|
||||
Try to pre-allocate the output buffer.
|
||||
*/
|
||||
reserve(&z.output.buf, expected_output_size);
|
||||
resize (&z.output.buf, expected_output_size);
|
||||
};
|
||||
|
||||
if len(z.output.buf) != expected_output_size {
|
||||
return .Resize_Failed;
|
||||
}
|
||||
|
||||
z.num_bits = 0;
|
||||
z.code_buffer = 0;
|
||||
|
||||
z_repeat: ^Huffman_Table;
|
||||
z_offset: ^Huffman_Table;
|
||||
codelength_ht: ^Huffman_Table;
|
||||
|
||||
z_repeat, err = allocate_huffman_table(allocator=context.allocator);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
z_offset, err = allocate_huffman_table(allocator=context.allocator);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
codelength_ht, err = allocate_huffman_table(allocator=context.allocator);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
defer free(z_repeat);
|
||||
defer free(z_offset);
|
||||
defer free(codelength_ht);
|
||||
|
||||
if z.window_size == 0 {
|
||||
z.window_size = DEFLATE_MAX_DISTANCE;
|
||||
}
|
||||
z_repeat = allocate_huffman_table(allocator=context.allocator) or_return;
|
||||
z_offset = allocate_huffman_table(allocator=context.allocator) or_return;
|
||||
codelength_ht = allocate_huffman_table(allocator=context.allocator) or_return;
|
||||
|
||||
// Allocate rolling window buffer.
|
||||
last_b := mem.make_dynamic_array_len_cap([dynamic]u8, z.window_size, z.window_size, allocator);
|
||||
z.last = &last_b;
|
||||
defer delete(last_b);
|
||||
final := u32(0);
|
||||
type := u32(0);
|
||||
|
||||
for {
|
||||
final = compress.read_bits_lsb(z, 1);
|
||||
@@ -454,8 +546,8 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) ->
|
||||
// Discard bits until next byte boundary
|
||||
compress.discard_to_next_byte_lsb(z);
|
||||
|
||||
uncompressed_len := i16(compress.read_bits_lsb(z, 16));
|
||||
length_check := i16(compress.read_bits_lsb(z, 16));
|
||||
uncompressed_len := i16(compress.read_bits_lsb(z, 16));
|
||||
length_check := i16(compress.read_bits_lsb(z, 16));
|
||||
|
||||
// fmt.printf("LEN: %v, ~LEN: %v, NLEN: %v, ~NLEN: %v\n", uncompressed_len, ~uncompressed_len, length_check, ~length_check);
|
||||
|
||||
@@ -480,14 +572,8 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) ->
|
||||
// log.debugf("Err: %v | Final: %v | Type: %v\n", err, final, type);
|
||||
if type == 1 {
|
||||
// Use fixed code lengths.
|
||||
err = build_huffman(z_repeat, Z_FIXED_LENGTH[:]);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
err = build_huffman(z_offset, Z_FIXED_DIST[:]);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
build_huffman(z_repeat, Z_FIXED_LENGTH[:]) or_return;
|
||||
build_huffman(z_offset, Z_FIXED_DIST[:]) or_return;
|
||||
} else {
|
||||
lencodes: [286+32+137]u8;
|
||||
codelength_sizes: [19]u8;
|
||||
@@ -505,19 +591,13 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) ->
|
||||
s := compress.read_bits_lsb(z, 3);
|
||||
codelength_sizes[Z_LENGTH_DEZIGZAG[i]] = u8(s);
|
||||
}
|
||||
err = build_huffman(codelength_ht, codelength_sizes[:]);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
build_huffman(codelength_ht, codelength_sizes[:]) or_return;
|
||||
|
||||
n = 0;
|
||||
c: u16;
|
||||
|
||||
for n < ntot {
|
||||
c, err = decode_huffman(z, codelength_ht);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
c = decode_huffman(z, codelength_ht) or_return;
|
||||
|
||||
if c < 0 || c >= 19 {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths;
|
||||
@@ -540,7 +620,7 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) ->
|
||||
case 18:
|
||||
c = u16(compress.read_bits_no_refill_lsb(z, 7) + 11);
|
||||
case:
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths;
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths;
|
||||
}
|
||||
|
||||
if ntot - n < u32(c) {
|
||||
@@ -558,49 +638,39 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) ->
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths;
|
||||
}
|
||||
|
||||
err = build_huffman(z_repeat, lencodes[:hlit]);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = build_huffman(z_offset, lencodes[hlit:ntot]);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
err = parse_huffman_block(z, z_repeat, z_offset);
|
||||
// log.debugf("Err: %v | Final: %v | Type: %v\n", err, final, type);
|
||||
if err != nil {
|
||||
return err;
|
||||
build_huffman(z_repeat, lencodes[:hlit]) or_return;
|
||||
build_huffman(z_offset, lencodes[hlit:ntot]) or_return;
|
||||
}
|
||||
parse_huffman_block(z, z_repeat, z_offset) or_return;
|
||||
}
|
||||
if final == 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if int(z.bytes_written) != len(z.output.buf) {
|
||||
resize(&z.output.buf, int(z.bytes_written));
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
inflate_from_byte_array :: proc(input: []u8, buf: ^bytes.Buffer, raw := false) -> (err: Error) {
|
||||
ctx := Context{};
|
||||
inflate_from_byte_array :: proc(input: []u8, buf: ^bytes.Buffer, raw := false, expected_output_size := -1) -> (err: Error) {
|
||||
ctx := compress.Context_Memory_Input{};
|
||||
|
||||
r := bytes.Reader{};
|
||||
bytes.reader_init(&r, input);
|
||||
rs := bytes.reader_to_stream(&r);
|
||||
ctx.input = rs;
|
||||
ctx.input_data = input;
|
||||
ctx.output = buf;
|
||||
|
||||
buf := buf;
|
||||
ws := bytes.buffer_to_stream(buf);
|
||||
ctx.output = ws;
|
||||
|
||||
err = inflate_from_stream(&ctx, raw);
|
||||
|
||||
return err;
|
||||
return inflate_from_context(ctx=&ctx, raw=raw, expected_output_size=expected_output_size);
|
||||
}
|
||||
|
||||
inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := false) -> (err: Error) {
|
||||
return inflate_from_byte_array(input, buf, true);
|
||||
inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := false, expected_output_size := -1) -> (err: Error) {
|
||||
ctx := compress.Context_Memory_Input{};
|
||||
|
||||
ctx.input_data = input;
|
||||
ctx.output = buf;
|
||||
|
||||
return inflate_raw(z=&ctx, expected_output_size=expected_output_size);
|
||||
}
|
||||
|
||||
inflate :: proc{inflate_from_stream, inflate_from_byte_array};
|
||||
inflate_raw :: proc{inflate_from_stream_raw, inflate_from_byte_array_raw};
|
||||
inflate :: proc{inflate_from_context, inflate_from_byte_array};
|
||||
@@ -3,7 +3,7 @@ package container
|
||||
import "core:mem"
|
||||
import "core:runtime"
|
||||
|
||||
Array :: struct(T: typeid) {
|
||||
Array :: struct($T: typeid) {
|
||||
data: ^T,
|
||||
len: int,
|
||||
cap: int,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package container
|
||||
|
||||
import "intrinsics"
|
||||
import "core:intrinsics"
|
||||
_ :: intrinsics;
|
||||
|
||||
|
||||
Map :: struct(Key, Value: typeid) where intrinsics.type_is_valid_map_key(Key) {
|
||||
Map :: struct($Key, $Value: typeid) where intrinsics.type_is_valid_map_key(Key) {
|
||||
hash: Array(int),
|
||||
entries: Array(Map_Entry(Key, Value)),
|
||||
}
|
||||
|
||||
Map_Entry :: struct(Key, Value: typeid) where intrinsics.type_is_valid_map_key(Key) {
|
||||
Map_Entry :: struct($Key, $Value: typeid) where intrinsics.type_is_valid_map_key(Key) {
|
||||
hash: uintptr,
|
||||
next: int,
|
||||
key: Key,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package container
|
||||
|
||||
Priority_Queue :: struct(T: typeid) {
|
||||
Priority_Queue :: struct($T: typeid) {
|
||||
data: Array(T),
|
||||
len: int,
|
||||
priority: proc(item: T) -> int,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package container
|
||||
|
||||
Queue :: struct(T: typeid) {
|
||||
Queue :: struct($T: typeid) {
|
||||
data: Array(T),
|
||||
len: int,
|
||||
offset: int,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package container
|
||||
|
||||
|
||||
Ring :: struct(T: typeid) {
|
||||
Ring :: struct($T: typeid) {
|
||||
next, prev: ^Ring(T),
|
||||
value: T,
|
||||
}
|
||||
@@ -26,6 +26,7 @@ ring_prev :: proc(r: ^$R/Ring) -> ^R {
|
||||
|
||||
|
||||
ring_move :: proc(r: ^$R/Ring, n: int) -> ^R {
|
||||
r := r;
|
||||
if r.next == nil {
|
||||
return ring_init(r);
|
||||
}
|
||||
@@ -64,7 +65,7 @@ ring_len :: proc(r: ^$R/Ring) -> int {
|
||||
n := 0;
|
||||
if r != nil {
|
||||
n = 1;
|
||||
for p := ring_next(&p); p != r; p = p.next {
|
||||
for p := ring_next(r); p != r; p = p.next {
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package container
|
||||
|
||||
Small_Array :: struct(N: int, T: typeid) where N >= 0 {
|
||||
Small_Array :: struct($N: int, $T: typeid) where N >= 0 {
|
||||
data: [N]T,
|
||||
len: int,
|
||||
}
|
||||
|
||||
@@ -1,852 +0,0 @@
|
||||
package cel;
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strconv"
|
||||
import "core:unicode/utf8"
|
||||
import "core:strings"
|
||||
|
||||
Array :: []Value;
|
||||
Dict :: map[string]Value;
|
||||
Nil_Value :: struct{};
|
||||
|
||||
Value :: union {
|
||||
Nil_Value,
|
||||
bool, i64, f64, string,
|
||||
Array, Dict,
|
||||
}
|
||||
|
||||
Parser :: struct {
|
||||
tokens: [dynamic]Token,
|
||||
prev_token: Token,
|
||||
curr_token: Token,
|
||||
curr_token_index: int,
|
||||
|
||||
allocated_strings: [dynamic]string,
|
||||
|
||||
error_count: int,
|
||||
|
||||
root: Dict,
|
||||
dict_stack: [dynamic]^Dict, // NOTE: Pointers may be stored on the stack
|
||||
}
|
||||
|
||||
|
||||
print_value :: proc(value: Value, pretty := true, indent := 0) {
|
||||
print_indent :: proc(indent: int) {
|
||||
for _ in 0..<indent {
|
||||
fmt.print("\t");
|
||||
}
|
||||
}
|
||||
|
||||
switch v in value {
|
||||
case bool: fmt.print(v);
|
||||
case i64: fmt.print(v);
|
||||
case f64: fmt.print(v);
|
||||
case string: fmt.print(v);
|
||||
case Array:
|
||||
fmt.print("[");
|
||||
if pretty { fmt.println(); }
|
||||
for e, i in v {
|
||||
if pretty {
|
||||
print_indent(indent+1);
|
||||
print_value(e, pretty, indent+1);
|
||||
fmt.println(",");
|
||||
} else {
|
||||
if i > 0 { fmt.print(", "); }
|
||||
print_value(e);
|
||||
}
|
||||
}
|
||||
if pretty { print_indent(indent); }
|
||||
fmt.print("]");
|
||||
case Dict:
|
||||
fmt.print("{");
|
||||
if pretty { fmt.println(); }
|
||||
|
||||
i := 0;
|
||||
for name, val in v {
|
||||
if pretty {
|
||||
print_indent(indent+1);
|
||||
fmt.printf("%s = ", name);
|
||||
print_value(val, pretty, indent+1);
|
||||
fmt.println(",");
|
||||
} else {
|
||||
if i > 0 { fmt.print(", "); }
|
||||
fmt.printf("%s = ", name);
|
||||
print_value(val, pretty, indent+1);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if pretty { print_indent(indent); }
|
||||
fmt.print("}");
|
||||
case:
|
||||
fmt.print("nil");
|
||||
case Nil_Value:
|
||||
fmt.print("nil");
|
||||
}
|
||||
}
|
||||
print :: proc(p: ^Parser, pretty := false) {
|
||||
for name, val in p.root {
|
||||
fmt.printf("%s = ", name);
|
||||
print_value(val, pretty);
|
||||
fmt.println(";");
|
||||
}
|
||||
}
|
||||
|
||||
create_from_string :: proc(src: string) -> (^Parser, bool) {
|
||||
return init(transmute([]byte)src);
|
||||
}
|
||||
|
||||
|
||||
init :: proc(src: []byte) -> (^Parser, bool) {
|
||||
t: Tokenizer;
|
||||
tokenizer_init(&t, src);
|
||||
return create_from_tokenizer(&t);
|
||||
}
|
||||
|
||||
|
||||
create_from_tokenizer :: proc(t: ^Tokenizer) -> (^Parser, bool) {
|
||||
p := new(Parser);
|
||||
for {
|
||||
tok := scan(t);
|
||||
if tok.kind == .Illegal {
|
||||
return p, false;
|
||||
}
|
||||
append(&p.tokens, tok);
|
||||
if tok.kind == .EOF {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if t.error_count > 0 {
|
||||
return p, false;
|
||||
}
|
||||
|
||||
if len(p.tokens) == 0 {
|
||||
tok := Token{kind = .EOF};
|
||||
tok.line, tok.column = 1, 1;
|
||||
append(&p.tokens, tok);
|
||||
return p, true;
|
||||
}
|
||||
|
||||
p.curr_token_index = 0;
|
||||
p.prev_token = p.tokens[p.curr_token_index];
|
||||
p.curr_token = p.tokens[p.curr_token_index];
|
||||
|
||||
p.root = Dict{};
|
||||
p.dict_stack = make([dynamic]^Dict, 0, 4);
|
||||
append(&p.dict_stack, &p.root);
|
||||
|
||||
for p.curr_token.kind != .EOF &&
|
||||
p.curr_token.kind != .Illegal &&
|
||||
p.curr_token_index < len(p.tokens) {
|
||||
if !parse_assignment(p) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return p, true;
|
||||
}
|
||||
|
||||
destroy :: proc(p: ^Parser) {
|
||||
destroy_value :: proc(value: Value) {
|
||||
#partial switch v in value {
|
||||
case Array:
|
||||
for elem in v {
|
||||
destroy_value(elem);
|
||||
}
|
||||
delete(v);
|
||||
|
||||
case Dict:
|
||||
for _, dv in v {
|
||||
destroy_value(dv);
|
||||
}
|
||||
delete(v);
|
||||
}
|
||||
}
|
||||
|
||||
delete(p.tokens);
|
||||
for s in p.allocated_strings {
|
||||
delete(s);
|
||||
}
|
||||
delete(p.allocated_strings);
|
||||
delete(p.dict_stack);
|
||||
|
||||
destroy_value(p.root);
|
||||
free(p);
|
||||
}
|
||||
|
||||
error :: proc(p: ^Parser, pos: Pos, msg: string, args: ..any) {
|
||||
fmt.eprintf("%s(%d:%d) Error: ", pos.file, pos.line, pos.column);
|
||||
fmt.eprintf(msg, ..args);
|
||||
fmt.eprintln();
|
||||
|
||||
p.error_count += 1;
|
||||
}
|
||||
|
||||
next_token :: proc(p: ^Parser) -> Token {
|
||||
p.prev_token = p.curr_token;
|
||||
prev := p.prev_token;
|
||||
|
||||
if p.curr_token_index+1 < len(p.tokens) {
|
||||
p.curr_token_index += 1;
|
||||
p.curr_token = p.tokens[p.curr_token_index];
|
||||
return prev;
|
||||
}
|
||||
p.curr_token_index = len(p.tokens);
|
||||
p.curr_token = p.tokens[p.curr_token_index-1];
|
||||
error(p, prev.pos, "Token is EOF");
|
||||
return prev;
|
||||
}
|
||||
|
||||
unquote_char :: proc(str: string, quote: byte) -> (r: rune, multiple_bytes: bool, tail_string: string, success: bool) {
|
||||
hex_to_int :: proc(c: byte) -> int {
|
||||
switch c {
|
||||
case '0'..'9': return int(c-'0');
|
||||
case 'a'..'f': return int(c-'a')+10;
|
||||
case 'A'..'F': return int(c-'A')+10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
w: int;
|
||||
|
||||
if str[0] == quote && quote == '"' {
|
||||
return;
|
||||
} else if str[0] >= 0x80 {
|
||||
r, w = utf8.decode_rune_in_string(str);
|
||||
return r, true, str[w:], true;
|
||||
} else if str[0] != '\\' {
|
||||
return rune(str[0]), false, str[1:], true;
|
||||
}
|
||||
|
||||
if len(str) <= 1 {
|
||||
return;
|
||||
}
|
||||
s := str;
|
||||
c := s[1];
|
||||
s = s[2:];
|
||||
|
||||
switch c {
|
||||
case:
|
||||
return;
|
||||
|
||||
case 'a': r = '\a';
|
||||
case 'b': r = '\b';
|
||||
case 'f': r = '\f';
|
||||
case 'n': r = '\n';
|
||||
case 'r': r = '\r';
|
||||
case 't': r = '\t';
|
||||
case 'v': r = '\v';
|
||||
case '\\': r = '\\';
|
||||
|
||||
case '"': r = '"';
|
||||
case '\'': r = '\'';
|
||||
|
||||
case '0'..'7':
|
||||
v := int(c-'0');
|
||||
if len(s) < 2 {
|
||||
return;
|
||||
}
|
||||
for i in 0..<len(s) {
|
||||
d := int(s[i]-'0');
|
||||
if d < 0 || d > 7 {
|
||||
return;
|
||||
}
|
||||
v = (v<<3) | d;
|
||||
}
|
||||
s = s[2:];
|
||||
if v > 0xff {
|
||||
return;
|
||||
}
|
||||
r = rune(v);
|
||||
|
||||
case 'x', 'u', 'U':
|
||||
count: int;
|
||||
switch c {
|
||||
case 'x': count = 2;
|
||||
case 'u': count = 4;
|
||||
case 'U': count = 8;
|
||||
}
|
||||
|
||||
if len(s) < count {
|
||||
return;
|
||||
}
|
||||
|
||||
for i in 0..<count {
|
||||
d := hex_to_int(s[i]);
|
||||
if d < 0 {
|
||||
return;
|
||||
}
|
||||
r = (r<<4) | rune(d);
|
||||
}
|
||||
s = s[count:];
|
||||
if c == 'x' {
|
||||
break;
|
||||
}
|
||||
if r > utf8.MAX_RUNE {
|
||||
return;
|
||||
}
|
||||
multiple_bytes = true;
|
||||
}
|
||||
|
||||
success = true;
|
||||
tail_string = s;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
unquote_string :: proc(p: ^Parser, t: Token) -> (string, bool) {
|
||||
if t.kind != .String {
|
||||
return t.lit, true;
|
||||
}
|
||||
s := t.lit;
|
||||
quote := '"';
|
||||
|
||||
if s == `""` {
|
||||
return "", true;
|
||||
}
|
||||
|
||||
if strings.contains_rune(s, '\n') >= 0 {
|
||||
return s, false;
|
||||
}
|
||||
|
||||
if strings.contains_rune(s, '\\') < 0 && strings.contains_rune(s, quote) < 0 {
|
||||
if quote == '"' {
|
||||
return s, true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
buf_len := 3*len(s) / 2;
|
||||
buf := make([]byte, buf_len);
|
||||
offset := 0;
|
||||
for len(s) > 0 {
|
||||
r, multiple_bytes, tail_string, ok := unquote_char(s, byte(quote));
|
||||
if !ok {
|
||||
delete(buf);
|
||||
return s, false;
|
||||
}
|
||||
s = tail_string;
|
||||
if r < 0x80 || !multiple_bytes {
|
||||
buf[offset] = byte(r);
|
||||
offset += 1;
|
||||
} else {
|
||||
b, w := utf8.encode_rune(r);
|
||||
copy(buf[offset:], b[:w]);
|
||||
offset += w;
|
||||
}
|
||||
}
|
||||
|
||||
new_string := string(buf[:offset]);
|
||||
|
||||
append(&p.allocated_strings, new_string);
|
||||
|
||||
return new_string, true;
|
||||
}
|
||||
|
||||
|
||||
allow_token :: proc(p: ^Parser, kind: Kind) -> bool {
|
||||
if p.curr_token.kind == kind {
|
||||
next_token(p);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
expect_token :: proc(p: ^Parser, kind: Kind) -> Token {
|
||||
prev := p.curr_token;
|
||||
if prev.kind != kind {
|
||||
got := prev.lit;
|
||||
if got == "\n" {
|
||||
got = ";";
|
||||
}
|
||||
error(p, prev.pos, "Expected %s, got %s", kind_to_string[kind], got);
|
||||
}
|
||||
next_token(p);
|
||||
return prev;
|
||||
}
|
||||
|
||||
expect_operator :: proc(p: ^Parser) -> Token {
|
||||
prev := p.curr_token;
|
||||
if !is_operator(prev.kind) {
|
||||
error(p, prev.pos, "Expected an operator, got %s", prev.lit);
|
||||
}
|
||||
|
||||
|
||||
next_token(p);
|
||||
return prev;
|
||||
}
|
||||
|
||||
fix_advance :: proc(p: ^Parser) {
|
||||
for {
|
||||
#partial switch t := p.curr_token; t.kind {
|
||||
case .EOF, .Semicolon:
|
||||
return;
|
||||
}
|
||||
next_token(p);
|
||||
}
|
||||
}
|
||||
|
||||
copy_value :: proc(value: Value) -> Value {
|
||||
#partial switch v in value {
|
||||
case Array:
|
||||
a := make(Array, len(v));
|
||||
for elem, idx in v {
|
||||
a[idx] = copy_value(elem);
|
||||
}
|
||||
return a;
|
||||
case Dict:
|
||||
d := make(Dict, cap(v));
|
||||
for key, val in v {
|
||||
d[key] = copy_value(val);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
lookup_value :: proc(p: ^Parser, name: string) -> (Value, bool) {
|
||||
for i := len(p.dict_stack)-1; i >= 0; i -= 1 {
|
||||
d := p.dict_stack[i];
|
||||
if val, ok := d[name]; ok {
|
||||
return copy_value(val), true;
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false;
|
||||
}
|
||||
|
||||
parse_operand :: proc(p: ^Parser) -> (Value, Pos) {
|
||||
tok := p.curr_token;
|
||||
#partial switch p.curr_token.kind {
|
||||
case .Ident:
|
||||
next_token(p);
|
||||
v, ok := lookup_value(p, tok.lit);
|
||||
if !ok { error(p, tok.pos, "Undeclared identifier %s", tok.lit); }
|
||||
return v, tok.pos;
|
||||
|
||||
case .True:
|
||||
next_token(p);
|
||||
return true, tok.pos;
|
||||
case .False:
|
||||
next_token(p);
|
||||
return false, tok.pos;
|
||||
|
||||
case .Nil:
|
||||
next_token(p);
|
||||
return Nil_Value{}, tok.pos;
|
||||
|
||||
case .Integer:
|
||||
next_token(p);
|
||||
i, _ := strconv.parse_i64(tok.lit);
|
||||
return i, tok.pos;
|
||||
|
||||
case .Float:
|
||||
next_token(p);
|
||||
f, _ := strconv.parse_f64(tok.lit);
|
||||
return f, tok.pos;
|
||||
|
||||
case .String:
|
||||
next_token(p);
|
||||
str, ok := unquote_string(p, tok);
|
||||
if !ok { error(p, tok.pos, "Unable to unquote string"); }
|
||||
return string(str), tok.pos;
|
||||
|
||||
case .Open_Paren:
|
||||
expect_token(p, .Open_Paren);
|
||||
expr, _ := parse_expr(p);
|
||||
expect_token(p, .Close_Paren);
|
||||
return expr, tok.pos;
|
||||
|
||||
case .Open_Bracket:
|
||||
expect_token(p, .Open_Bracket);
|
||||
elems := make([dynamic]Value, 0, 4);
|
||||
for p.curr_token.kind != .Close_Bracket &&
|
||||
p.curr_token.kind != .EOF {
|
||||
elem, _ := parse_expr(p);
|
||||
append(&elems, elem);
|
||||
|
||||
if p.curr_token.kind == .Semicolon && p.curr_token.lit == "\n" {
|
||||
next_token(p);
|
||||
} else if !allow_token(p, .Comma) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
expect_token(p, .Close_Bracket);
|
||||
return Array(elems[:]), tok.pos;
|
||||
|
||||
case .Open_Brace:
|
||||
expect_token(p, .Open_Brace);
|
||||
|
||||
dict := Dict{};
|
||||
append(&p.dict_stack, &dict);
|
||||
defer pop(&p.dict_stack);
|
||||
|
||||
for p.curr_token.kind != .Close_Brace &&
|
||||
p.curr_token.kind != .EOF {
|
||||
name_tok := p.curr_token;
|
||||
if !allow_token(p, .Ident) && !allow_token(p, .String) {
|
||||
name_tok = expect_token(p, .Ident);
|
||||
}
|
||||
|
||||
name, ok := unquote_string(p, name_tok);
|
||||
if !ok { error(p, tok.pos, "Unable to unquote string"); }
|
||||
expect_token(p, .Assign);
|
||||
elem, _ := parse_expr(p);
|
||||
|
||||
if _, ok2 := dict[name]; ok2 {
|
||||
error(p, name_tok.pos, "Previous declaration of %s in this scope", name);
|
||||
} else {
|
||||
dict[name] = elem;
|
||||
}
|
||||
|
||||
if p.curr_token.kind == .Semicolon && p.curr_token.lit == "\n" {
|
||||
next_token(p);
|
||||
} else if !allow_token(p, .Comma) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect_token(p, .Close_Brace);
|
||||
return dict, tok.pos;
|
||||
|
||||
}
|
||||
return nil, tok.pos;
|
||||
}
|
||||
|
||||
parse_atom_expr :: proc(p: ^Parser, operand: Value, pos: Pos) -> (Value, Pos) {
|
||||
loop := true;
|
||||
for operand := operand; loop; {
|
||||
#partial switch p.curr_token.kind {
|
||||
case .Period:
|
||||
next_token(p);
|
||||
tok := next_token(p);
|
||||
|
||||
#partial switch tok.kind {
|
||||
case .Ident:
|
||||
d, ok := operand.(Dict);
|
||||
if !ok || d == nil {
|
||||
error(p, tok.pos, "Expected a dictionary");
|
||||
operand = nil;
|
||||
continue;
|
||||
}
|
||||
name, usok := unquote_string(p, tok);
|
||||
if !usok { error(p, tok.pos, "Unable to unquote string"); }
|
||||
val, found := d[name];
|
||||
if !found {
|
||||
error(p, tok.pos, "Field %s not found in dictionary", name);
|
||||
operand = nil;
|
||||
continue;
|
||||
}
|
||||
operand = val;
|
||||
case:
|
||||
error(p, tok.pos, "Expected a selector, got %s", tok.kind);
|
||||
operand = nil;
|
||||
}
|
||||
|
||||
case .Open_Bracket:
|
||||
expect_token(p, .Open_Bracket);
|
||||
index, index_pos := parse_expr(p);
|
||||
expect_token(p, .Close_Bracket);
|
||||
|
||||
|
||||
#partial switch a in operand {
|
||||
case Array:
|
||||
i, ok := index.(i64);
|
||||
if !ok {
|
||||
error(p, index_pos, "Index must be an integer for an array");
|
||||
operand = nil;
|
||||
continue;
|
||||
}
|
||||
|
||||
if 0 <= i && i < i64(len(a)) {
|
||||
operand = a[i];
|
||||
} else {
|
||||
error(p, index_pos, "Index %d out of bounds range 0..%d", i, len(a));
|
||||
operand = nil;
|
||||
continue;
|
||||
}
|
||||
|
||||
case Dict:
|
||||
key, ok := index.(string);
|
||||
if !ok {
|
||||
error(p, index_pos, "Index must be a string for a dictionary");
|
||||
operand = nil;
|
||||
continue;
|
||||
}
|
||||
|
||||
val, found := a[key];
|
||||
if found {
|
||||
operand = val;
|
||||
} else {
|
||||
error(p, index_pos, "`%s` was not found in the dictionary", key);
|
||||
operand = nil;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
case:
|
||||
error(p, index_pos, "Indexing is only allowed on an array or dictionary");
|
||||
}
|
||||
|
||||
case:
|
||||
loop = false;
|
||||
}
|
||||
}
|
||||
|
||||
return operand, pos;
|
||||
}
|
||||
|
||||
parse_unary_expr :: proc(p: ^Parser) -> (Value, Pos) {
|
||||
op := p.curr_token;
|
||||
#partial switch p.curr_token.kind {
|
||||
case .At:
|
||||
next_token(p);
|
||||
tok := expect_token(p, .String);
|
||||
v, ok := lookup_value(p, tok.lit);
|
||||
if !ok { error(p, tok.pos, "Undeclared identifier %s", tok.lit); }
|
||||
return parse_atom_expr(p, v, tok.pos);
|
||||
|
||||
case .Add, .Sub:
|
||||
next_token(p);
|
||||
// TODO(bill): Calcuate values as you go!
|
||||
expr, pos := parse_unary_expr(p);
|
||||
|
||||
#partial switch e in expr {
|
||||
case i64: if op.kind == .Sub { return -e, pos; }
|
||||
case f64: if op.kind == .Sub { return -e, pos; }
|
||||
case:
|
||||
error(p, op.pos, "Unary operator %s can only be used on integers or floats", op.lit);
|
||||
return nil, op.pos;
|
||||
}
|
||||
|
||||
return expr, op.pos;
|
||||
|
||||
case .Not:
|
||||
next_token(p);
|
||||
expr, _ := parse_unary_expr(p);
|
||||
if v, ok := expr.(bool); ok {
|
||||
return !v, op.pos;
|
||||
}
|
||||
error(p, op.pos, "Unary operator %s can only be used on booleans", op.lit);
|
||||
return nil, op.pos;
|
||||
}
|
||||
|
||||
return parse_atom_expr(p, parse_operand(p));
|
||||
}
|
||||
|
||||
|
||||
value_order :: proc(v: Value) -> int {
|
||||
#partial switch _ in v {
|
||||
case bool, string:
|
||||
return 1;
|
||||
case i64:
|
||||
return 2;
|
||||
case f64:
|
||||
return 3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
match_values :: proc(left, right: ^Value) -> bool {
|
||||
if value_order(right^) < value_order(left^) {
|
||||
return match_values(right, left);
|
||||
}
|
||||
|
||||
#partial switch x in left^ {
|
||||
case:
|
||||
right^ = left^;
|
||||
case bool, string:
|
||||
return true;
|
||||
case i64:
|
||||
#partial switch y in right^ {
|
||||
case i64:
|
||||
return true;
|
||||
case f64:
|
||||
left^ = f64(x);
|
||||
return true;
|
||||
}
|
||||
|
||||
case f64:
|
||||
#partial switch y in right {
|
||||
case f64:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
calculate_binary_value :: proc(p: ^Parser, op: Kind, a_, b_: Value) -> (Value, bool) {
|
||||
// TODO(bill): Calculate value as you go!
|
||||
x, y := a_, b_;
|
||||
match_values(&x, &y);
|
||||
|
||||
|
||||
#partial switch a in x {
|
||||
case: return x, true;
|
||||
|
||||
case bool:
|
||||
b, ok := y.(bool);
|
||||
if !ok { return nil, false; }
|
||||
#partial switch op {
|
||||
case .Eq: return a == b, true;
|
||||
case .NotEq: return a != b, true;
|
||||
case .And: return a && b, true;
|
||||
case .Or: return a || b, true;
|
||||
}
|
||||
|
||||
case i64:
|
||||
b, ok := y.(i64);
|
||||
if !ok { return nil, false; }
|
||||
#partial switch op {
|
||||
case .Add: return a + b, true;
|
||||
case .Sub: return a - b, true;
|
||||
case .Mul: return a * b, true;
|
||||
case .Quo: return a / b, true;
|
||||
case .Rem: return a % b, true;
|
||||
case .Eq: return a == b, true;
|
||||
case .NotEq: return a != b, true;
|
||||
case .Lt: return a < b, true;
|
||||
case .Gt: return a > b, true;
|
||||
case .LtEq: return a <= b, true;
|
||||
case .GtEq: return a >= b, true;
|
||||
}
|
||||
|
||||
case f64:
|
||||
b, ok := y.(f64);
|
||||
if !ok { return nil, false; }
|
||||
|
||||
#partial switch op {
|
||||
case .Add: return a + b, true;
|
||||
case .Sub: return a - b, true;
|
||||
case .Mul: return a * b, true;
|
||||
case .Quo: return a / b, true;
|
||||
case .Eq: return a == b, true;
|
||||
case .NotEq: return a != b, true;
|
||||
case .Lt: return a < b, true;
|
||||
case .Gt: return a > b, true;
|
||||
case .LtEq: return a <= b, true;
|
||||
case .GtEq: return a >= b, true;
|
||||
}
|
||||
|
||||
case string:
|
||||
b, ok := y.(string);
|
||||
if !ok { return nil, false; }
|
||||
|
||||
#partial switch op {
|
||||
case .Add:
|
||||
n := len(a) + len(b);
|
||||
data := make([]byte, n);
|
||||
copy(data[:], a);
|
||||
copy(data[len(a):], b);
|
||||
s := string(data);
|
||||
append(&p.allocated_strings, s);
|
||||
return s, true;
|
||||
|
||||
case .Eq: return a == b, true;
|
||||
case .NotEq: return a != b, true;
|
||||
case .Lt: return a < b, true;
|
||||
case .Gt: return a > b, true;
|
||||
case .LtEq: return a <= b, true;
|
||||
case .GtEq: return a >= b, true;
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false;
|
||||
}
|
||||
|
||||
parse_binary_expr :: proc(p: ^Parser, prec_in: int) -> (Value, Pos) {
|
||||
expr, pos := parse_unary_expr(p);
|
||||
for prec := precedence(p.curr_token.kind); prec >= prec_in; prec -= 1 {
|
||||
for {
|
||||
op := p.curr_token;
|
||||
op_prec := precedence(op.kind);
|
||||
if op_prec != prec {
|
||||
break;
|
||||
}
|
||||
expect_operator(p);
|
||||
|
||||
if op.kind == .Question {
|
||||
cond := expr;
|
||||
x, _ := parse_expr(p);
|
||||
expect_token(p, .Colon);
|
||||
y, _ := parse_expr(p);
|
||||
|
||||
if t, ok := cond.(bool); ok {
|
||||
expr = t ? x : y;
|
||||
} else {
|
||||
error(p, pos, "Condition must be a boolean");
|
||||
}
|
||||
|
||||
} else {
|
||||
right, right_pos := parse_binary_expr(p, prec+1);
|
||||
if right == nil {
|
||||
error(p, right_pos, "Expected expression on the right-hand side of the binary operator %s", op.lit);
|
||||
}
|
||||
left := expr;
|
||||
ok: bool;
|
||||
expr, ok = calculate_binary_value(p, op.kind, left, right);
|
||||
if !ok {
|
||||
error(p, pos, "Invalid binary operation");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return expr, pos;
|
||||
}
|
||||
|
||||
parse_expr :: proc(p: ^Parser) -> (Value, Pos) {
|
||||
return parse_binary_expr(p, 1);
|
||||
}
|
||||
|
||||
expect_semicolon :: proc(p: ^Parser) {
|
||||
kind := p.curr_token.kind;
|
||||
|
||||
#partial switch kind {
|
||||
case .Comma:
|
||||
error(p, p.curr_token.pos, "Expected ';', got ','");
|
||||
next_token(p);
|
||||
case .Semicolon:
|
||||
next_token(p);
|
||||
case .EOF:
|
||||
// okay
|
||||
case:
|
||||
error(p, p.curr_token.pos, "Expected ';', got %s", p.curr_token.lit);
|
||||
fix_advance(p);
|
||||
}
|
||||
}
|
||||
|
||||
parse_assignment :: proc(p: ^Parser) -> bool {
|
||||
top_dict :: proc(p: ^Parser) -> ^Dict {
|
||||
assert(len(p.dict_stack) > 0);
|
||||
return p.dict_stack[len(p.dict_stack)-1];
|
||||
}
|
||||
|
||||
if p.curr_token.kind == .Semicolon {
|
||||
next_token(p);
|
||||
return true;
|
||||
}
|
||||
if p.curr_token.kind == .EOF {
|
||||
return false;
|
||||
}
|
||||
|
||||
tok := p.curr_token;
|
||||
if allow_token(p, .Ident) || allow_token(p, .String) {
|
||||
expect_token(p, .Assign);
|
||||
name, ok := unquote_string(p, tok);
|
||||
if !ok { error(p, tok.pos, "Unable to unquote string"); }
|
||||
expr, _ := parse_expr(p);
|
||||
d := top_dict(p);
|
||||
if _, ok2 := d[name]; ok2 {
|
||||
error(p, tok.pos, "Previous declaration of %s", name);
|
||||
} else {
|
||||
d[name] = expr;
|
||||
}
|
||||
expect_semicolon(p);
|
||||
return true;
|
||||
}
|
||||
error(p, tok.pos, "Expected an assignment, got %s", kind_to_string[tok.kind]);
|
||||
fix_advance(p);
|
||||
return false;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
package cel
|
||||
|
||||
sample := `
|
||||
x = 123;
|
||||
y = 321.456;
|
||||
z = x * (y - 1) / 2;
|
||||
w = "foo" + "bar";
|
||||
|
||||
# This is a comment
|
||||
|
||||
asd = "Semicolons are optional"
|
||||
|
||||
a = {id = {b = 123}} # Dict
|
||||
b = a.id.b
|
||||
|
||||
f = [1, 4, 9] # Array
|
||||
g = f[2]
|
||||
|
||||
h = x < y and w == "foobar"
|
||||
i = h ? 123 : "google"
|
||||
|
||||
j = nil
|
||||
|
||||
"127.0.0.1" = "value" # Keys can be strings
|
||||
|
||||
"foo" = {
|
||||
"bar" = {
|
||||
"baz" = 123, # optional commas if newline is present
|
||||
"zab" = 456,
|
||||
"abz" = 789,
|
||||
},
|
||||
};
|
||||
|
||||
bar = @"foo"["bar"].baz
|
||||
`;
|
||||
|
||||
|
||||
main :: proc() {
|
||||
p, ok := create_from_string(sample);
|
||||
if !ok {
|
||||
return;
|
||||
}
|
||||
defer destroy(p);
|
||||
|
||||
if p.error_count == 0 {
|
||||
print(p);
|
||||
}
|
||||
}
|
||||
*/
|
||||
package cel
|
||||
@@ -1,523 +0,0 @@
|
||||
package cel
|
||||
|
||||
import "core:fmt"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
using Kind :: enum {
|
||||
Illegal,
|
||||
EOF,
|
||||
Comment,
|
||||
|
||||
_literal_start,
|
||||
Ident,
|
||||
Integer,
|
||||
Float,
|
||||
Char,
|
||||
String,
|
||||
_literal_end,
|
||||
|
||||
_keyword_start,
|
||||
True, // true
|
||||
False, // false
|
||||
Nil, // nil
|
||||
_keyword_end,
|
||||
|
||||
|
||||
_operator_start,
|
||||
Question, // ?
|
||||
|
||||
And, // and
|
||||
Or, // or
|
||||
|
||||
Add, // +
|
||||
Sub, // -
|
||||
Mul, // *
|
||||
Quo, // /
|
||||
Rem, // %
|
||||
|
||||
Not, // !
|
||||
|
||||
Eq, // ==
|
||||
NotEq, // !=
|
||||
Lt, // <
|
||||
Gt, // >
|
||||
LtEq, // <=
|
||||
GtEq, // >=
|
||||
|
||||
At, // @
|
||||
_operator_end,
|
||||
|
||||
_punc_start,
|
||||
Assign, // =
|
||||
|
||||
Open_Paren, // (
|
||||
Close_Paren, // )
|
||||
Open_Bracket, // [
|
||||
Close_Bracket, // ]
|
||||
Open_Brace, // {
|
||||
Close_Brace, // }
|
||||
|
||||
Colon, // :
|
||||
Semicolon, // ;
|
||||
Comma, // ,
|
||||
Period, // .
|
||||
_punc_end,
|
||||
}
|
||||
|
||||
|
||||
Pos :: struct {
|
||||
file: string,
|
||||
line: int,
|
||||
column: int,
|
||||
}
|
||||
|
||||
Token :: struct {
|
||||
kind: Kind,
|
||||
using pos: Pos,
|
||||
lit: string,
|
||||
}
|
||||
|
||||
Tokenizer :: struct {
|
||||
src: []byte,
|
||||
|
||||
file: string, // May not be used
|
||||
|
||||
curr_rune: rune,
|
||||
offset: int,
|
||||
read_offset: int,
|
||||
line_offset: int,
|
||||
line_count: int,
|
||||
|
||||
insert_semi: bool,
|
||||
|
||||
error_count: int,
|
||||
}
|
||||
|
||||
|
||||
keywords := map[string]Kind{
|
||||
"true" = True,
|
||||
"false" = False,
|
||||
"nil" = Nil,
|
||||
"and" = And,
|
||||
"or" = Or,
|
||||
};
|
||||
|
||||
kind_to_string := [len(Kind)]string{
|
||||
"illegal",
|
||||
"EOF",
|
||||
"comment",
|
||||
|
||||
"",
|
||||
"identifier",
|
||||
"integer",
|
||||
"float",
|
||||
"character",
|
||||
"string",
|
||||
"",
|
||||
|
||||
"",
|
||||
"true", "false", "nil",
|
||||
"",
|
||||
|
||||
"",
|
||||
"?", "and", "or",
|
||||
"+", "-", "*", "/", "%",
|
||||
"!",
|
||||
"==", "!=", "<", ">", "<=", ">=",
|
||||
"@",
|
||||
"",
|
||||
|
||||
"",
|
||||
"=",
|
||||
"(", ")",
|
||||
"[", "]",
|
||||
"{", "}",
|
||||
":", ";", ",", ".",
|
||||
"",
|
||||
};
|
||||
|
||||
precedence :: proc(op: Kind) -> int {
|
||||
#partial switch op {
|
||||
case Question:
|
||||
return 1;
|
||||
case Or:
|
||||
return 2;
|
||||
case And:
|
||||
return 3;
|
||||
case Eq, NotEq, Lt, Gt, LtEq, GtEq:
|
||||
return 4;
|
||||
case Add, Sub:
|
||||
return 5;
|
||||
case Mul, Quo, Rem:
|
||||
return 6;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
token_lookup :: proc(ident: string) -> Kind {
|
||||
if tok, is_keyword := keywords[ident]; is_keyword {
|
||||
return tok;
|
||||
}
|
||||
return Ident;
|
||||
}
|
||||
|
||||
is_literal :: proc(tok: Kind) -> bool { return _literal_start < tok && tok < _literal_end; }
|
||||
is_operator :: proc(tok: Kind) -> bool { return _operator_start < tok && tok < _operator_end; }
|
||||
is_keyword :: proc(tok: Kind) -> bool { return _keyword_start < tok && tok < _keyword_end; }
|
||||
|
||||
|
||||
tokenizer_init :: proc(t: ^Tokenizer, src: []byte, file := "") {
|
||||
t.src = src;
|
||||
t.file = file;
|
||||
t.curr_rune = ' ';
|
||||
t.offset = 0;
|
||||
t.read_offset = 0;
|
||||
t.line_offset = 0;
|
||||
t.line_count = 1;
|
||||
|
||||
advance_to_next_rune(t);
|
||||
if t.curr_rune == utf8.RUNE_BOM {
|
||||
advance_to_next_rune(t);
|
||||
}
|
||||
}
|
||||
|
||||
token_error :: proc(t: ^Tokenizer, msg: string, args: ..any) {
|
||||
fmt.eprintf("%s(%d:%d) Error: ", t.file, t.line_count, t.read_offset-t.line_offset+1);
|
||||
fmt.eprintf(msg, ..args);
|
||||
fmt.eprintln();
|
||||
t.error_count += 1;
|
||||
}
|
||||
|
||||
advance_to_next_rune :: proc(t: ^Tokenizer) {
|
||||
if t.read_offset < len(t.src) {
|
||||
t.offset = t.read_offset;
|
||||
if t.curr_rune == '\n' {
|
||||
t.line_offset = t.offset;
|
||||
t.line_count += 1;
|
||||
}
|
||||
r, w := rune(t.src[t.read_offset]), 1;
|
||||
switch {
|
||||
case r == 0:
|
||||
token_error(t, "Illegal character NUL");
|
||||
case r >= utf8.RUNE_SELF:
|
||||
r, w = utf8.decode_rune(t.src[t.read_offset:]);
|
||||
if r == utf8.RUNE_ERROR && w == 1 {
|
||||
token_error(t, "Illegal utf-8 encoding");
|
||||
} else if r == utf8.RUNE_BOM && t.offset > 0 {
|
||||
token_error(t, "Illegal byte order mark");
|
||||
}
|
||||
}
|
||||
|
||||
t.read_offset += w;
|
||||
t.curr_rune = r;
|
||||
} else {
|
||||
t.offset = len(t.src);
|
||||
if t.curr_rune == '\n' {
|
||||
t.line_offset = t.offset;
|
||||
t.line_count += 1;
|
||||
}
|
||||
t.curr_rune = utf8.RUNE_EOF;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get_pos :: proc(t: ^Tokenizer) -> Pos {
|
||||
return Pos {
|
||||
file = t.file,
|
||||
line = t.line_count,
|
||||
column = t.offset - t.line_offset + 1,
|
||||
};
|
||||
}
|
||||
|
||||
is_letter :: proc(r: rune) -> bool {
|
||||
switch r {
|
||||
case 'a'..'z', 'A'..'Z', '_':
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
is_digit :: proc(r: rune) -> bool {
|
||||
switch r {
|
||||
case '0'..'9':
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
skip_whitespace :: proc(t: ^Tokenizer) {
|
||||
loop: for {
|
||||
switch t.curr_rune {
|
||||
case '\n':
|
||||
if t.insert_semi {
|
||||
break loop;
|
||||
}
|
||||
fallthrough;
|
||||
case ' ', '\t', '\r', '\v', '\f':
|
||||
advance_to_next_rune(t);
|
||||
|
||||
case:
|
||||
break loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scan_identifier :: proc(t: ^Tokenizer) -> string {
|
||||
offset := t.offset;
|
||||
for is_letter(t.curr_rune) || is_digit(t.curr_rune) {
|
||||
advance_to_next_rune(t);
|
||||
}
|
||||
return string(t.src[offset : t.offset]);
|
||||
}
|
||||
|
||||
digit_value :: proc(r: rune) -> int {
|
||||
switch r {
|
||||
case '0'..'9': return int(r - '0');
|
||||
case 'a'..'f': return int(r - 'a' + 10);
|
||||
case 'A'..'F': return int(r - 'A' + 10);
|
||||
}
|
||||
return 16;
|
||||
}
|
||||
|
||||
scan_number :: proc(t: ^Tokenizer, seen_decimal_point: bool) -> (Kind, string) {
|
||||
scan_mantissa :: proc(t: ^Tokenizer, base: int) {
|
||||
for digit_value(t.curr_rune) < base || t.curr_rune == '_' {
|
||||
advance_to_next_rune(t);
|
||||
}
|
||||
}
|
||||
scan_exponent :: proc(t: ^Tokenizer, tok: Kind, offset: int) -> (kind: Kind, text: string) {
|
||||
kind = tok;
|
||||
if t.curr_rune == 'e' || t.curr_rune == 'E' {
|
||||
kind = Float;
|
||||
advance_to_next_rune(t);
|
||||
if t.curr_rune == '-' || t.curr_rune == '+' {
|
||||
advance_to_next_rune(t);
|
||||
}
|
||||
if digit_value(t.curr_rune) < 10 {
|
||||
scan_mantissa(t, 10);
|
||||
} else {
|
||||
token_error(t, "Illegal floating point exponent");
|
||||
}
|
||||
}
|
||||
text = string(t.src[offset : t.offset]);
|
||||
return;
|
||||
}
|
||||
scan_fraction :: proc(t: ^Tokenizer, tok: Kind, offset: int) -> (kind: Kind, text: string) {
|
||||
kind = tok;
|
||||
if t.curr_rune == '.' {
|
||||
kind = Float;
|
||||
advance_to_next_rune(t);
|
||||
scan_mantissa(t, 10);
|
||||
}
|
||||
|
||||
return scan_exponent(t, kind, offset);
|
||||
}
|
||||
|
||||
offset := t.offset;
|
||||
tok := Integer;
|
||||
|
||||
if seen_decimal_point {
|
||||
offset -= 1;
|
||||
tok = Float;
|
||||
scan_mantissa(t, 10);
|
||||
return scan_exponent(t, tok, offset);
|
||||
}
|
||||
|
||||
if t.curr_rune == '0' {
|
||||
offset = t.offset;
|
||||
advance_to_next_rune(t);
|
||||
switch t.curr_rune {
|
||||
case 'b', 'B':
|
||||
advance_to_next_rune(t);
|
||||
scan_mantissa(t, 2);
|
||||
if t.offset - offset <= 2 {
|
||||
token_error(t, "Illegal binary number");
|
||||
}
|
||||
case 'o', 'O':
|
||||
advance_to_next_rune(t);
|
||||
scan_mantissa(t, 8);
|
||||
if t.offset - offset <= 2 {
|
||||
token_error(t, "Illegal octal number");
|
||||
}
|
||||
case 'x', 'X':
|
||||
advance_to_next_rune(t);
|
||||
scan_mantissa(t, 16);
|
||||
if t.offset - offset <= 2 {
|
||||
token_error(t, "Illegal hexadecimal number");
|
||||
}
|
||||
case:
|
||||
scan_mantissa(t, 10);
|
||||
switch t.curr_rune {
|
||||
case '.', 'e', 'E':
|
||||
return scan_fraction(t, tok, offset);
|
||||
}
|
||||
}
|
||||
|
||||
return tok, string(t.src[offset:t.offset]);
|
||||
}
|
||||
|
||||
scan_mantissa(t, 10);
|
||||
|
||||
return scan_fraction(t, tok, offset);
|
||||
}
|
||||
|
||||
scan :: proc(t: ^Tokenizer) -> Token {
|
||||
skip_whitespace(t);
|
||||
|
||||
offset := t.offset;
|
||||
|
||||
tok: Kind;
|
||||
pos := get_pos(t);
|
||||
lit: string;
|
||||
|
||||
insert_semi := false;
|
||||
|
||||
|
||||
switch r := t.curr_rune; {
|
||||
case is_letter(r):
|
||||
insert_semi = true;
|
||||
lit = scan_identifier(t);
|
||||
tok = Ident;
|
||||
if len(lit) > 1 {
|
||||
tok = token_lookup(lit);
|
||||
}
|
||||
|
||||
case '0' <= r && r <= '9':
|
||||
insert_semi = true;
|
||||
tok, lit = scan_number(t, false);
|
||||
|
||||
case:
|
||||
advance_to_next_rune(t);
|
||||
switch r {
|
||||
case -1:
|
||||
if t.insert_semi {
|
||||
t.insert_semi = false;
|
||||
return Token{Semicolon, pos, "\n"};
|
||||
}
|
||||
return Token{EOF, pos, "\n"};
|
||||
|
||||
case '\n':
|
||||
t.insert_semi = false;
|
||||
return Token{Semicolon, pos, "\n"};
|
||||
|
||||
case '"':
|
||||
insert_semi = true;
|
||||
quote := r;
|
||||
tok = String;
|
||||
for {
|
||||
this_r := t.curr_rune;
|
||||
if this_r == '\n' || r < 0 {
|
||||
token_error(t, "String literal not terminated");
|
||||
break;
|
||||
}
|
||||
advance_to_next_rune(t);
|
||||
if this_r == quote {
|
||||
break;
|
||||
}
|
||||
// TODO(bill); Handle properly
|
||||
if this_r == '\\' && t.curr_rune == quote {
|
||||
advance_to_next_rune(t);
|
||||
}
|
||||
}
|
||||
|
||||
lit = string(t.src[offset+1:t.offset-1]);
|
||||
|
||||
|
||||
case '#':
|
||||
for t.curr_rune != '\n' && t.curr_rune >= 0 {
|
||||
advance_to_next_rune(t);
|
||||
}
|
||||
if t.insert_semi {
|
||||
t.insert_semi = false;
|
||||
return Token{Semicolon, pos, "\n"};
|
||||
}
|
||||
// Recursive!
|
||||
return scan(t);
|
||||
|
||||
case '?': tok = Question;
|
||||
case ':': tok = Colon;
|
||||
case '@': tok = At;
|
||||
|
||||
case ';':
|
||||
tok = Semicolon;
|
||||
lit = ";";
|
||||
case ',': tok = Comma;
|
||||
|
||||
case '(':
|
||||
tok = Open_Paren;
|
||||
case ')':
|
||||
insert_semi = true;
|
||||
tok = Close_Paren;
|
||||
|
||||
case '[':
|
||||
tok = Open_Bracket;
|
||||
case ']':
|
||||
insert_semi = true;
|
||||
tok = Close_Bracket;
|
||||
|
||||
case '{':
|
||||
tok = Open_Brace;
|
||||
case '}':
|
||||
insert_semi = true;
|
||||
tok = Close_Brace;
|
||||
|
||||
case '+': tok = Add;
|
||||
case '-': tok = Sub;
|
||||
case '*': tok = Mul;
|
||||
case '/': tok = Quo;
|
||||
case '%': tok = Rem;
|
||||
|
||||
case '!':
|
||||
tok = Not;
|
||||
if t.curr_rune == '=' {
|
||||
advance_to_next_rune(t);
|
||||
tok = NotEq;
|
||||
}
|
||||
|
||||
case '=':
|
||||
tok = Assign;
|
||||
if t.curr_rune == '=' {
|
||||
advance_to_next_rune(t);
|
||||
tok = Eq;
|
||||
}
|
||||
|
||||
case '<':
|
||||
tok = Lt;
|
||||
if t.curr_rune == '=' {
|
||||
advance_to_next_rune(t);
|
||||
tok = LtEq;
|
||||
}
|
||||
|
||||
case '>':
|
||||
tok = Gt;
|
||||
if t.curr_rune == '=' {
|
||||
advance_to_next_rune(t);
|
||||
tok = GtEq;
|
||||
}
|
||||
|
||||
case '.':
|
||||
if '0' <= t.curr_rune && t.curr_rune <= '9' {
|
||||
insert_semi = true;
|
||||
tok, lit = scan_number(t, true);
|
||||
} else {
|
||||
tok = Period;
|
||||
}
|
||||
|
||||
case:
|
||||
if r != utf8.RUNE_BOM {
|
||||
token_error(t, "Illegal character '%r'", r);
|
||||
}
|
||||
insert_semi = t.insert_semi;
|
||||
tok = Illegal;
|
||||
}
|
||||
}
|
||||
|
||||
t.insert_semi = insert_semi;
|
||||
|
||||
if lit == "" {
|
||||
lit = string(t.src[offset:t.offset]);
|
||||
}
|
||||
|
||||
return Token{tok, pos, lit};
|
||||
}
|
||||
@@ -64,21 +64,15 @@ write :: proc(w: ^Writer, record: []string) -> io.Error {
|
||||
field := record[field_idx];
|
||||
|
||||
if field_idx > 0 {
|
||||
if _, err := io.write_rune(w.w, w.comma); err != nil {
|
||||
return err;
|
||||
}
|
||||
io.write_rune(w.w, w.comma) or_return;
|
||||
}
|
||||
|
||||
if !field_needs_quoting(w, field) {
|
||||
if _, err := io.write_string(w.w, field); err != nil {
|
||||
return err;
|
||||
}
|
||||
io.write_string(w.w, field) or_return;
|
||||
continue;
|
||||
}
|
||||
|
||||
if err := io.write_byte(w.w, '"'); err != nil {
|
||||
return err;
|
||||
}
|
||||
io.write_byte(w.w, '"') or_return;
|
||||
|
||||
for len(field) > 0 {
|
||||
i := strings.index_any(field, CHAR_SET);
|
||||
@@ -86,40 +80,28 @@ write :: proc(w: ^Writer, record: []string) -> io.Error {
|
||||
i = len(field);
|
||||
}
|
||||
|
||||
if _, err := io.write_string(w.w, field[:i]); err != nil {
|
||||
return err;
|
||||
}
|
||||
io.write_string(w.w, field[:i]) or_return;
|
||||
field = field[i:];
|
||||
|
||||
if len(field) > 0 {
|
||||
switch field[0] {
|
||||
case '\r':
|
||||
if !w.use_crlf {
|
||||
if err := io.write_byte(w.w, '\r'); err != nil {
|
||||
return err;
|
||||
}
|
||||
io.write_byte(w.w, '\r') or_return;
|
||||
}
|
||||
case '\n':
|
||||
if w.use_crlf {
|
||||
if _, err := io.write_string(w.w, "\r\n"); err != nil {
|
||||
return err;
|
||||
}
|
||||
io.write_string(w.w, "\r\n") or_return;
|
||||
} else {
|
||||
if err := io.write_byte(w.w, '\n'); err != nil {
|
||||
return err;
|
||||
}
|
||||
io.write_byte(w.w, '\n') or_return;
|
||||
}
|
||||
case '"':
|
||||
if _, err := io.write_string(w.w, `""`); err != nil {
|
||||
return err;
|
||||
}
|
||||
io.write_string(w.w, `""`) or_return;
|
||||
}
|
||||
field = field[1:];
|
||||
}
|
||||
}
|
||||
if err := io.write_byte(w.w, '"'); err != nil {
|
||||
return err;
|
||||
}
|
||||
io.write_byte(w.w, '"') or_return;
|
||||
}
|
||||
|
||||
if w.use_crlf {
|
||||
@@ -132,10 +114,7 @@ write :: proc(w: ^Writer, record: []string) -> io.Error {
|
||||
// write_all writes multiple CSV records to w using write, and then flushes (if necessary).
|
||||
write_all :: proc(w: ^Writer, records: [][]string) -> io.Error {
|
||||
for record in records {
|
||||
err := write(w, record);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
write(w, record) or_return;
|
||||
}
|
||||
return writer_flush(w);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Implementation of the HxA 3D asset format
|
||||
// HxA is a interchangeable graphics asset format.
|
||||
// Designed by Eskil Steenberg. @quelsolaar / eskil 'at' obsession 'dot' se / www.quelsolaar.com
|
||||
//
|
||||
// Author of this Odin package: Ginger Bill
|
||||
//
|
||||
// Following comment is copied from the original C-implementation
|
||||
// ---------
|
||||
// -Does the world need another Graphics file format?
|
||||
// Unfortunately, Yes. All existing formats are either too large and complicated to be implemented from
|
||||
// scratch, or don't have some basic features needed in modern computer graphics.
|
||||
// -Who is this format for?
|
||||
// For people who want a capable open Graphics format that can be implemented from scratch in
|
||||
// a few hours. It is ideal for graphics researchers, game developers or other people who
|
||||
// wants to build custom graphics pipelines. Given how easy it is to parse and write, it
|
||||
// should be easy to write utilities that process assets to preform tasks like: generating
|
||||
// normals, light-maps, tangent spaces, Error detection, GPU optimization, LOD generation,
|
||||
// and UV mapping.
|
||||
// -Why store images in the format when there are so many good image formats already?
|
||||
// Yes there are, but only for 2D RGB/RGBA images. A lot of computer graphics rendering rely
|
||||
// on 1D, 3D, cube, multilayer, multi channel, floating point bitmap buffers. There almost no
|
||||
// formats for this kind of data. Also 3D files that reference separate image files rely on
|
||||
// file paths, and this often creates issues when the assets are moved. By including the
|
||||
// texture data in the files directly the assets become self contained.
|
||||
// -Why doesn't the format support <insert whatever>?
|
||||
// Because the entire point is to make a format that can be implemented. Features like NURBSs,
|
||||
// Construction history, or BSP trees would make the format too large to serve its purpose.
|
||||
// The facilities of the formats to store meta data should make the format flexible enough
|
||||
// for most uses. Adding HxA support should be something anyone can do in a days work.
|
||||
|
||||
// Structure:
|
||||
// ----------
|
||||
// HxA is designed to be extremely simple to parse, and is therefore based around conventions. It has
|
||||
// a few basic structures, and depending on how they are used they mean different things. This means
|
||||
// that you can implement a tool that loads the entire file, modifies the parts it cares about and
|
||||
// leaves the rest intact. It is also possible to write a tool that makes all data in the file
|
||||
// editable without the need to understand its use. It is also possible for anyone to use the format
|
||||
// to store data axillary data. Anyone who wants to store data not covered by a convention can submit
|
||||
// a convention to extend the format. There should never be a convention for storing the same data in
|
||||
// two differed ways.
|
||||
// The data is story in a number of nodes that are stored in an array. Each node stores an array of
|
||||
// meta data. Meta data can describe anything you want, and a lot of conventions will use meta data
|
||||
// to store additional information, for things like transforms, lights, shaders and animation.
|
||||
// Data for Vertices, Corners, Faces, and Pixels are stored in named layer stacks. Each stack consists
|
||||
// of a number of named layers. All layers in the stack have the same number of elements. Each layer
|
||||
// describes one property of the primitive. Each layer can have multiple channels and each layer can
|
||||
// store data of a different type.
|
||||
|
||||
// HaX stores 3 kinds of nodes
|
||||
// - Pixel data.
|
||||
// - Polygon geometry data.
|
||||
// - Meta data only.
|
||||
|
||||
// Pixel Nodes stores pixels in a layer stack. A layer may store things like Albedo, Roughness,
|
||||
// Reflectance, Light maps, Masks, Normal maps, and Displacement. Layers use the channels of the
|
||||
// layers to store things like color. The length of the layer stack is determined by the type and
|
||||
// dimensions stored in the
|
||||
|
||||
// Geometry data is stored in 3 separate layer stacks for: vertex data, corner data and face data. The
|
||||
// vertex data stores things like verities, blend shapes, weight maps, and vertex colors. The first
|
||||
// layer in a vertex stack has to be a 3 channel layer named "position" describing the base position
|
||||
// of the vertices. The corner stack describes data per corner or edge of the polygons. It can be used
|
||||
// for things like UV, normals, and adjacency. The first layer in a corner stack has to be a 1 channel
|
||||
// integer layer named "index" describing the vertices used to form polygons. The last value in each
|
||||
// polygon has a negative - 1 index to indicate the end of the polygon.
|
||||
|
||||
// Example:
|
||||
// A quad and a tri with the vertex index:
|
||||
// [0, 1, 2, 3] [1, 4, 2]
|
||||
// is stored:
|
||||
// [0, 1, 2, -4, 1, 4, -3]
|
||||
// The face stack stores values per face. the length of the face stack has to match the number of
|
||||
// negative values in the index layer in the corner stack. The face stack can be used to store things
|
||||
// like material index.
|
||||
|
||||
// Storage
|
||||
// -------
|
||||
// All data is stored in little endian byte order with no padding. The layout mirrors the structs
|
||||
// defined below with a few exceptions. All names are stored as a 8-bit unsigned integer indicating
|
||||
// the length of the name followed by that many characters. Termination is not stored in the file.
|
||||
// Text strings stored in meta data are stored the same way as names, but instead of a 8-bit unsigned
|
||||
// integer a 32-bit unsigned integer is used.
|
||||
package encoding_hxa
|
||||
@@ -0,0 +1,193 @@
|
||||
package encoding_hxa
|
||||
|
||||
import "core:mem"
|
||||
|
||||
LATEST_VERSION :: 3;
|
||||
VERSION_API :: "0.3";
|
||||
|
||||
MAGIC_NUMBER :: 'H'<<0 | 'x'<<8 | 'A'<<16 | '\x00'<<24;
|
||||
|
||||
Header :: struct #packed {
|
||||
magic_number: u32le,
|
||||
version: u32le,
|
||||
internal_node_count: u32le,
|
||||
}
|
||||
|
||||
File :: struct {
|
||||
using header: Header,
|
||||
backing: []byte,
|
||||
allocator: mem.Allocator,
|
||||
nodes: []Node,
|
||||
}
|
||||
|
||||
Node_Type :: enum u8 {
|
||||
Meta_Only = 0, // node only containing meta data.
|
||||
Geometry = 1, // node containing a geometry mesh, and meta data.
|
||||
Image = 2, // node containing a 1D, 2D, 3D, or Cube image, and meta data.
|
||||
}
|
||||
|
||||
Layer_Data_Type :: enum u8 {
|
||||
Uint8 = 0, // 8-bit unsigned integer,
|
||||
Int32 = 1, // 32-bit little-endian signed integer
|
||||
Float = 2, // 32-bit little-endian IEEE 754 floating point value
|
||||
Double = 3, // 64-bit little-endian IEEE 754 floating point value
|
||||
}
|
||||
|
||||
// Pixel data is arranged in the following configurations
|
||||
Image_Type :: enum u8 {
|
||||
Image_Cube = 0, // 6 sided qube, in the order of: +x, -x, +y, -y, +z, -z.
|
||||
Image_1D = 1, // One dimensional pixel data.
|
||||
Image_2D = 2, // Two dimensional pixel data.
|
||||
Image_3D = 3, // Three dimensional pixel data.
|
||||
}
|
||||
|
||||
Meta_Value_Type :: enum u8 {
|
||||
Int64 = 0,
|
||||
Double = 1,
|
||||
Node = 2,
|
||||
Text = 3,
|
||||
Binary = 4,
|
||||
Meta = 5,
|
||||
};
|
||||
|
||||
Meta :: struct {
|
||||
name: string, // name of the meta data value (maximum length is 255)
|
||||
value: union {
|
||||
[]i64le,
|
||||
[]f64le,
|
||||
[]Node_Index, // a reference to another node
|
||||
string, // text
|
||||
[]byte, // binary data
|
||||
[]Meta,
|
||||
},
|
||||
}
|
||||
|
||||
Layer :: struct {
|
||||
name: string, // name of the layer (maximum length is 255)
|
||||
components: u8, // 2 for uv, 3 for xyz/rgb, 4 for rgba
|
||||
data: union {
|
||||
[]u8,
|
||||
[]i32le,
|
||||
[]f32le,
|
||||
[]f64le,
|
||||
},
|
||||
}
|
||||
|
||||
// Layers stacks are arrays of layers where all the layers have the same number of entries (polygons, edges, vertices or pixels)
|
||||
Layer_Stack :: distinct []Layer;
|
||||
|
||||
Node_Geometry :: struct {
|
||||
vertex_count: u32le, // number of vertices
|
||||
vertex_stack: Layer_Stack, // stack of vertex arrays. the first layer is always the vertex positions
|
||||
edge_corner_count: u32le, // number of corners
|
||||
corner_stack: Layer_Stack, // stack of corner arrays, the first layer is always a reference array (see below)
|
||||
edge_stack: Layer_Stack, // stack of edge arrays
|
||||
face_count: u32le, // number of polygons
|
||||
face_stack: Layer_Stack, // stack of per polygon data.
|
||||
}
|
||||
|
||||
Node_Image :: struct {
|
||||
type: Image_Type,
|
||||
resolution: [3]u32le,
|
||||
image_stack: Layer_Stack,
|
||||
}
|
||||
|
||||
Node_Index :: distinct u32le;
|
||||
|
||||
// A file consists of an array of nodes, All nodes have meta data. Geometry nodes have geometry, image nodes have pixels
|
||||
Node :: struct {
|
||||
meta_data: []Meta,
|
||||
content: union {
|
||||
Node_Geometry,
|
||||
Node_Image,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
/* 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.
|
||||
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!
|
||||
*/
|
||||
|
||||
/* Hard conventions */
|
||||
/* ---------------- */
|
||||
|
||||
CONVENTION_HARD_BASE_VERTEX_LAYER_NAME :: "vertex";
|
||||
CONVENTION_HARD_BASE_VERTEX_LAYER_ID :: 0;
|
||||
CONVENTION_HARD_BASE_VERTEX_LAYER_COMPONENTS :: 3;
|
||||
CONVENTION_HARD_BASE_CORNER_LAYER_NAME :: "reference";
|
||||
CONVENTION_HARD_BASE_CORNER_LAYER_ID :: 0;
|
||||
CONVENTION_HARD_BASE_CORNER_LAYER_COMPONENTS :: 1;
|
||||
CONVENTION_HARD_BASE_CORNER_LAYER_TYPE :: Layer_Data_Type.Int32;
|
||||
CONVENTION_HARD_EDGE_NEIGHBOUR_LAYER_NAME :: "neighbour";
|
||||
CONVENTION_HARD_EDGE_NEIGHBOUR_LAYER_TYPE :: Layer_Data_Type.Int32;
|
||||
|
||||
|
||||
|
||||
/* Soft Conventions */
|
||||
/* ---------------- */
|
||||
|
||||
/* geometry layers */
|
||||
|
||||
CONVENTION_SOFT_LAYER_SEQUENCE0 :: "sequence";
|
||||
CONVENTION_SOFT_LAYER_NAME_UV0 :: "uv";
|
||||
CONVENTION_SOFT_LAYER_NORMALS :: "normal";
|
||||
CONVENTION_SOFT_LAYER_BINORMAL :: "binormal";
|
||||
CONVENTION_SOFT_LAYER_TANGENT :: "tangent";
|
||||
CONVENTION_SOFT_LAYER_COLOR :: "color";
|
||||
CONVENTION_SOFT_LAYER_CREASES :: "creases";
|
||||
CONVENTION_SOFT_LAYER_SELECTION :: "select";
|
||||
CONVENTION_SOFT_LAYER_SKIN_WEIGHT :: "skining_weight";
|
||||
CONVENTION_SOFT_LAYER_SKIN_REFERENCE :: "skining_reference";
|
||||
CONVENTION_SOFT_LAYER_BLENDSHAPE :: "blendshape";
|
||||
CONVENTION_SOFT_LAYER_ADD_BLENDSHAPE :: "addblendshape";
|
||||
CONVENTION_SOFT_LAYER_MATERIAL_ID :: "material";
|
||||
|
||||
/* Image layers */
|
||||
|
||||
CONVENTION_SOFT_ALBEDO :: "albedo";
|
||||
CONVENTION_SOFT_LIGHT :: "light";
|
||||
CONVENTION_SOFT_DISPLACEMENT :: "displacement";
|
||||
CONVENTION_SOFT_DISTORTION :: "distortion";
|
||||
CONVENTION_SOFT_AMBIENT_OCCLUSION :: "ambient_occlusion";
|
||||
|
||||
/* tags layers */
|
||||
|
||||
CONVENTION_SOFT_NAME :: "name";
|
||||
CONVENTION_SOFT_TRANSFORM :: "transform";
|
||||
|
||||
/* destroy procedures */
|
||||
|
||||
meta_destroy :: proc(meta: Meta, allocator := context.allocator) {
|
||||
if nested, ok := meta.value.([]Meta); ok {
|
||||
for m in nested {
|
||||
meta_destroy(m);
|
||||
}
|
||||
delete(nested, allocator);
|
||||
}
|
||||
}
|
||||
nodes_destroy :: proc(nodes: []Node, allocator := context.allocator) {
|
||||
for node in nodes {
|
||||
for meta in node.meta_data {
|
||||
meta_destroy(meta);
|
||||
}
|
||||
delete(node.meta_data, allocator);
|
||||
|
||||
switch n in node.content {
|
||||
case Node_Geometry:
|
||||
delete(n.corner_stack, allocator);
|
||||
delete(n.edge_stack, allocator);
|
||||
delete(n.face_stack, allocator);
|
||||
case Node_Image:
|
||||
delete(n.image_stack, allocator);
|
||||
}
|
||||
}
|
||||
delete(nodes, allocator);
|
||||
}
|
||||
|
||||
file_destroy :: proc(file: File) {
|
||||
nodes_destroy(file.nodes, file.allocator);
|
||||
delete(file.backing, file.allocator);
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package encoding_hxa
|
||||
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import "core:mem"
|
||||
|
||||
Read_Error :: enum {
|
||||
None,
|
||||
Short_Read,
|
||||
Invalid_Data,
|
||||
Unable_To_Read_File,
|
||||
}
|
||||
|
||||
read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) {
|
||||
context.allocator = allocator;
|
||||
|
||||
data, ok := os.read_entire_file(filename);
|
||||
if !ok {
|
||||
err = .Unable_To_Read_File;
|
||||
return;
|
||||
}
|
||||
defer if !ok {
|
||||
delete(data);
|
||||
} else {
|
||||
file.backing = data;
|
||||
}
|
||||
file, err = read(data, filename, print_error, allocator);
|
||||
return;
|
||||
}
|
||||
|
||||
read :: proc(data: []byte, filename := "<input>", print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) {
|
||||
Reader :: struct {
|
||||
filename: string,
|
||||
data: []byte,
|
||||
offset: int,
|
||||
print_error: bool,
|
||||
};
|
||||
|
||||
read_value :: proc(r: ^Reader, $T: typeid) -> (value: T, err: Read_Error) {
|
||||
remaining := len(r.data) - r.offset;
|
||||
if remaining < size_of(T) {
|
||||
err = .Short_Read;
|
||||
return;
|
||||
}
|
||||
ptr := raw_data(r.data[r.offset:]);
|
||||
value = (^T)(ptr)^;
|
||||
r.offset += size_of(T);
|
||||
return;
|
||||
}
|
||||
|
||||
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 {
|
||||
err = .Short_Read;
|
||||
return;
|
||||
}
|
||||
ptr := raw_data(r.data[r.offset:]);
|
||||
|
||||
value = mem.slice_ptr((^T)(ptr), count);
|
||||
r.offset += size_of(T)*count;
|
||||
return;
|
||||
}
|
||||
|
||||
read_string :: proc(r: ^Reader, count: int) -> (string, Read_Error) {
|
||||
buf, err := read_array(r, byte, count);
|
||||
return string(buf), err;
|
||||
}
|
||||
|
||||
read_name :: proc(r: ^Reader) -> (value: string, err: Read_Error) {
|
||||
len := read_value(r, u8) or_return;
|
||||
data := read_array(r, byte, int(len)) or_return;
|
||||
return string(data[:len]), nil;
|
||||
}
|
||||
|
||||
read_meta :: proc(r: ^Reader, capacity: u32le) -> (meta_data: []Meta, err: Read_Error) {
|
||||
meta_data = make([]Meta, int(capacity));
|
||||
count := 0;
|
||||
defer meta_data = meta_data[:count];
|
||||
for m in &meta_data {
|
||||
m.name = read_name(r) or_return;
|
||||
|
||||
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)));
|
||||
}
|
||||
err = .Invalid_Data;
|
||||
return;
|
||||
}
|
||||
array_length := read_value(r, u32le) or_return;
|
||||
|
||||
switch type {
|
||||
case .Int64: m.value = read_array(r, i64le, int(array_length)) or_return;
|
||||
case .Double: m.value = read_array(r, f64le, int(array_length)) or_return;
|
||||
case .Node: m.value = read_array(r, Node_Index, int(array_length)) or_return;
|
||||
case .Text: m.value = read_string(r, int(array_length)) or_return;
|
||||
case .Binary: m.value = read_array(r, byte, int(array_length)) or_return;
|
||||
case .Meta: m.value = read_meta(r, array_length) or_return;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
read_layer_stack :: proc(r: ^Reader, capacity: u32le) -> (layers: Layer_Stack, err: Read_Error) {
|
||||
stack_count := read_value(r, u32le) or_return;
|
||||
layer_count := 0;
|
||||
layers = make(Layer_Stack, stack_count);
|
||||
defer layers = layers[:layer_count];
|
||||
for layer in &layers {
|
||||
layer.name = read_name(r) or_return;
|
||||
layer.components = read_value(r, u8) or_return;
|
||||
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)));
|
||||
}
|
||||
err = .Invalid_Data;
|
||||
return;
|
||||
}
|
||||
data_len := int(layer.components) * int(capacity);
|
||||
|
||||
switch type {
|
||||
case .Uint8: layer.data = read_array(r, u8, data_len) or_return;
|
||||
case .Int32: layer.data = read_array(r, i32le, data_len) or_return;
|
||||
case .Float: layer.data = read_array(r, f32le, data_len) or_return;
|
||||
case .Double: layer.data = read_array(r, f64le, data_len) or_return;
|
||||
}
|
||||
layer_count += 1;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if len(data) < size_of(Header) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.allocator = allocator;
|
||||
|
||||
header := cast(^Header)raw_data(data);
|
||||
assert(header.magic_number == MAGIC_NUMBER);
|
||||
|
||||
r := &Reader{
|
||||
filename = filename,
|
||||
data = data[:],
|
||||
offset = size_of(Header),
|
||||
print_error = print_error,
|
||||
};
|
||||
|
||||
node_count := 0;
|
||||
file.nodes = make([]Node, header.internal_node_count);
|
||||
defer if err != nil {
|
||||
nodes_destroy(file.nodes);
|
||||
file.nodes = nil;
|
||||
}
|
||||
defer file.nodes = file.nodes[:node_count];
|
||||
|
||||
for node_idx in 0..<header.internal_node_count {
|
||||
node := &file.nodes[node_count];
|
||||
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)));
|
||||
}
|
||||
err = .Invalid_Data;
|
||||
return;
|
||||
}
|
||||
node_count += 1;
|
||||
|
||||
node.meta_data = read_meta(r, read_value(r, u32le) or_return) or_return;
|
||||
|
||||
switch type {
|
||||
case .Meta_Only:
|
||||
// Okay
|
||||
case .Geometry:
|
||||
g: Node_Geometry;
|
||||
|
||||
g.vertex_count = read_value(r, u32le) or_return;
|
||||
g.vertex_stack = read_layer_stack(r, g.vertex_count) or_return;
|
||||
g.edge_corner_count = read_value(r, u32le) or_return;
|
||||
g.corner_stack = read_layer_stack(r, g.edge_corner_count) or_return;
|
||||
if header.version > 2 {
|
||||
g.edge_stack = read_layer_stack(r, g.edge_corner_count) or_return;
|
||||
}
|
||||
g.face_count = read_value(r, u32le) or_return;
|
||||
g.face_stack = read_layer_stack(r, g.face_count) or_return;
|
||||
|
||||
node.content = g;
|
||||
|
||||
case .Image:
|
||||
img: Node_Image;
|
||||
|
||||
img.type = read_value(r, Image_Type) or_return;
|
||||
dimensions := int(img.type);
|
||||
if img.type == .Image_Cube {
|
||||
dimensions = 2;
|
||||
}
|
||||
img.resolution = {1, 1, 1};
|
||||
for d in 0..<dimensions {
|
||||
img.resolution[d] = read_value(r, u32le) or_return;
|
||||
}
|
||||
size := img.resolution[0]*img.resolution[1]*img.resolution[2];
|
||||
if img.type == .Image_Cube {
|
||||
size *= 6;
|
||||
}
|
||||
img.image_stack = read_layer_stack(r, size) or_return;
|
||||
|
||||
node.content = img;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package encoding_hxa
|
||||
|
||||
import "core:os"
|
||||
import "core:mem"
|
||||
|
||||
Write_Error :: enum {
|
||||
None,
|
||||
Buffer_Too_Small,
|
||||
Failed_File_Write,
|
||||
}
|
||||
|
||||
write_to_file :: proc(filepath: string, file: File) -> (err: Write_Error) {
|
||||
required := required_write_size(file);
|
||||
buf, alloc_err := make([]byte, required);
|
||||
if alloc_err == .Out_Of_Memory {
|
||||
return .Failed_File_Write;
|
||||
}
|
||||
defer delete(buf);
|
||||
|
||||
write_internal(&Writer{data = buf}, file);
|
||||
if !os.write_entire_file(filepath, buf) {
|
||||
err =.Failed_File_Write;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
write :: proc(buf: []byte, file: File) -> (n: int, err: Write_Error) {
|
||||
required := required_write_size(file);
|
||||
if len(buf) < required {
|
||||
err = .Buffer_Too_Small;
|
||||
return;
|
||||
}
|
||||
n = required;
|
||||
write_internal(&Writer{data = buf}, file);
|
||||
return;
|
||||
}
|
||||
|
||||
required_write_size :: proc(file: File) -> (n: int) {
|
||||
writer := &Writer{dummy_pass = true};
|
||||
write_internal(writer, file);
|
||||
n = writer.offset;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@(private)
|
||||
Writer :: struct {
|
||||
data: []byte,
|
||||
offset: int,
|
||||
dummy_pass: bool,
|
||||
};
|
||||
|
||||
@(private)
|
||||
write_internal :: proc(w: ^Writer, file: File) {
|
||||
write_value :: proc(w: ^Writer, value: $T) {
|
||||
if !w.dummy_pass {
|
||||
remaining := len(w.data) - w.offset;
|
||||
assert(size_of(T) <= remaining);
|
||||
ptr := raw_data(w.data[w.offset:]);
|
||||
(^T)(ptr)^ = value;
|
||||
}
|
||||
w.offset += size_of(T);
|
||||
}
|
||||
write_array :: proc(w: ^Writer, array: []$T) {
|
||||
if !w.dummy_pass {
|
||||
remaining := len(w.data) - w.offset;
|
||||
assert(size_of(T)*len(array) <= remaining);
|
||||
ptr := raw_data(w.data[w.offset:]);
|
||||
dst := mem.slice_ptr((^T)(ptr), len(array));
|
||||
copy(dst, array);
|
||||
}
|
||||
w.offset += size_of(T)*len(array);
|
||||
}
|
||||
write_string :: proc(w: ^Writer, str: string) {
|
||||
if !w.dummy_pass {
|
||||
remaining := len(w.data) - w.offset;
|
||||
assert(size_of(byte)*len(str) <= remaining);
|
||||
ptr := raw_data(w.data[w.offset:]);
|
||||
dst := mem.slice_ptr((^byte)(ptr), len(str));
|
||||
copy(dst, str);
|
||||
}
|
||||
w.offset += size_of(byte)*len(str);
|
||||
}
|
||||
|
||||
write_metadata :: proc(w: ^Writer, meta_data: []Meta) {
|
||||
for m in meta_data {
|
||||
name_len := max(len(m.name), 255);
|
||||
write_value(w, u8(name_len));
|
||||
write_string(w, m.name[:name_len]);
|
||||
|
||||
meta_data_type: Meta_Value_Type;
|
||||
length: u32le = 0;
|
||||
switch v in m.value {
|
||||
case []i64le:
|
||||
meta_data_type = .Int64;
|
||||
length = u32le(len(v));
|
||||
case []f64le:
|
||||
meta_data_type = .Double;
|
||||
length = u32le(len(v));
|
||||
case []Node_Index:
|
||||
meta_data_type = .Node;
|
||||
length = u32le(len(v));
|
||||
case string:
|
||||
meta_data_type = .Text;
|
||||
length = u32le(len(v));
|
||||
case []byte:
|
||||
meta_data_type = .Binary;
|
||||
length = u32le(len(v));
|
||||
case []Meta:
|
||||
meta_data_type = .Meta;
|
||||
length = u32le(len(v));
|
||||
}
|
||||
write_value(w, meta_data_type);
|
||||
write_value(w, length);
|
||||
|
||||
switch v in m.value {
|
||||
case []i64le: write_array(w, v);
|
||||
case []f64le: write_array(w, v);
|
||||
case []Node_Index: write_array(w, v);
|
||||
case string: write_string(w, v);
|
||||
case []byte: write_array(w, v);
|
||||
case []Meta: write_metadata(w, v);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
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);
|
||||
write_value(w, u8(name_len));
|
||||
write_string(w, layer .name[:name_len]);
|
||||
|
||||
write_value(w, layer.components);
|
||||
|
||||
layer_data_type: Layer_Data_Type;
|
||||
switch v in layer.data {
|
||||
case []u8: layer_data_type = .Uint8;
|
||||
case []i32le: layer_data_type = .Int32;
|
||||
case []f32le: layer_data_type = .Float;
|
||||
case []f64le: layer_data_type = .Double;
|
||||
}
|
||||
write_value(w, layer_data_type);
|
||||
|
||||
switch v in layer.data {
|
||||
case []u8: write_array(w, v);
|
||||
case []i32le: write_array(w, v);
|
||||
case []f32le: write_array(w, v);
|
||||
case []f64le: write_array(w, v);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
write_value(w, &Header{
|
||||
magic_number = MAGIC_NUMBER,
|
||||
version = LATEST_VERSION,
|
||||
internal_node_count = u32le(len(file.nodes)),
|
||||
});
|
||||
|
||||
for node in file.nodes {
|
||||
node_type: Node_Type;
|
||||
switch content in node.content {
|
||||
case Node_Geometry: node_type = .Geometry;
|
||||
case Node_Image: node_type = .Image;
|
||||
}
|
||||
write_value(w, node_type);
|
||||
|
||||
write_value(w, u32(len(node.meta_data)));
|
||||
write_metadata(w, node.meta_data);
|
||||
|
||||
switch content in node.content {
|
||||
case Node_Geometry:
|
||||
write_value(w, content.vertex_count);
|
||||
write_layer_stack(w, content.vertex_stack);
|
||||
write_value(w, content.edge_corner_count);
|
||||
write_layer_stack(w, content.corner_stack);
|
||||
write_layer_stack(w, content.edge_stack);
|
||||
write_value(w, content.face_count);
|
||||
write_layer_stack(w, content.face_stack);
|
||||
case Node_Image:
|
||||
write_value(w, content.type);
|
||||
dimensions := int(content.type);
|
||||
if content.type == .Image_Cube {
|
||||
dimensions = 2;
|
||||
}
|
||||
for d in 0..<dimensions {
|
||||
write_value(w, content.resolution[d]);
|
||||
}
|
||||
write_layer_stack(w, content.image_stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,21 +31,19 @@ marshal :: proc(v: any, allocator := context.allocator) -> ([]byte, Marshal_Erro
|
||||
|
||||
|
||||
marshal_arg :: proc(b: ^strings.Builder, v: any) -> Marshal_Error {
|
||||
using strings;
|
||||
using runtime;
|
||||
if v == nil {
|
||||
write_string(b, "null");
|
||||
strings.write_string(b, "null");
|
||||
return .None;
|
||||
}
|
||||
|
||||
ti := type_info_base(type_info_of(v.id));
|
||||
ti := runtime.type_info_base(type_info_of(v.id));
|
||||
a := any{v.data, ti.id};
|
||||
|
||||
switch info in ti.variant {
|
||||
case Type_Info_Named:
|
||||
case runtime.Type_Info_Named:
|
||||
unreachable();
|
||||
|
||||
case Type_Info_Integer:
|
||||
case runtime.Type_Info_Integer:
|
||||
buf: [21]byte;
|
||||
u: u64;
|
||||
switch i in a {
|
||||
@@ -77,16 +75,16 @@ marshal_arg :: proc(b: ^strings.Builder, v: any) -> Marshal_Error {
|
||||
}
|
||||
|
||||
s := strconv.append_bits(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil);
|
||||
write_string(b, s);
|
||||
strings.write_string(b, s);
|
||||
|
||||
|
||||
case Type_Info_Rune:
|
||||
case runtime.Type_Info_Rune:
|
||||
r := a.(rune);
|
||||
write_byte(b, '"');
|
||||
write_escaped_rune(b, r, '"', true);
|
||||
write_byte(b, '"');
|
||||
strings.write_byte(b, '"');
|
||||
strings.write_escaped_rune(b, r, '"', true);
|
||||
strings.write_byte(b, '"');
|
||||
|
||||
case Type_Info_Float:
|
||||
case runtime.Type_Info_Float:
|
||||
val: f64;
|
||||
switch f in a {
|
||||
case f16: val = f64(f);
|
||||
@@ -107,21 +105,21 @@ marshal_arg :: proc(b: ^strings.Builder, v: any) -> Marshal_Error {
|
||||
s = s[1:];
|
||||
}
|
||||
|
||||
write_string(b, string(s));
|
||||
strings.write_string(b, string(s));
|
||||
|
||||
case Type_Info_Complex:
|
||||
case runtime.Type_Info_Complex:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case Type_Info_Quaternion:
|
||||
case runtime.Type_Info_Quaternion:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case Type_Info_String:
|
||||
case runtime.Type_Info_String:
|
||||
switch s in a {
|
||||
case string: write_quoted_string(b, s);
|
||||
case cstring: write_quoted_string(b, string(s));
|
||||
case string: strings.write_quoted_string(b, s);
|
||||
case cstring: strings.write_quoted_string(b, string(s));
|
||||
}
|
||||
|
||||
case Type_Info_Boolean:
|
||||
case runtime.Type_Info_Boolean:
|
||||
val: bool;
|
||||
switch b in a {
|
||||
case bool: val = bool(b);
|
||||
@@ -130,109 +128,112 @@ marshal_arg :: proc(b: ^strings.Builder, v: any) -> Marshal_Error {
|
||||
case b32: val = bool(b);
|
||||
case b64: val = bool(b);
|
||||
}
|
||||
write_string_builder(b, val ? "true" : "false");
|
||||
strings.write_string(b, val ? "true" : "false");
|
||||
|
||||
case Type_Info_Any:
|
||||
case runtime.Type_Info_Any:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case Type_Info_Type_Id:
|
||||
case runtime.Type_Info_Type_Id:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case Type_Info_Pointer:
|
||||
case runtime.Type_Info_Pointer:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case Type_Info_Procedure:
|
||||
case runtime.Type_Info_Multi_Pointer:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case Type_Info_Tuple:
|
||||
case runtime.Type_Info_Procedure:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case Type_Info_Enumerated_Array:
|
||||
case runtime.Type_Info_Tuple:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case Type_Info_Simd_Vector:
|
||||
case runtime.Type_Info_Enumerated_Array:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case Type_Info_Relative_Pointer:
|
||||
case runtime.Type_Info_Simd_Vector:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case Type_Info_Relative_Slice:
|
||||
case runtime.Type_Info_Relative_Pointer:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case Type_Info_Array:
|
||||
write_byte(b, '[');
|
||||
case runtime.Type_Info_Relative_Slice:
|
||||
return .Unsupported_Type;
|
||||
|
||||
case runtime.Type_Info_Array:
|
||||
strings.write_byte(b, '[');
|
||||
for i in 0..<info.count {
|
||||
if i > 0 { write_string(b, ", "); }
|
||||
if i > 0 { strings.write_string(b, ", "); }
|
||||
|
||||
data := uintptr(v.data) + uintptr(i*info.elem_size);
|
||||
marshal_arg(b, any{rawptr(data), info.elem.id});
|
||||
}
|
||||
write_byte(b, ']');
|
||||
strings.write_byte(b, ']');
|
||||
|
||||
case Type_Info_Dynamic_Array:
|
||||
write_byte(b, '[');
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
strings.write_byte(b, '[');
|
||||
array := cast(^mem.Raw_Dynamic_Array)v.data;
|
||||
for i in 0..<array.len {
|
||||
if i > 0 { write_string(b, ", "); }
|
||||
if i > 0 { strings.write_string(b, ", "); }
|
||||
|
||||
data := uintptr(array.data) + uintptr(i*info.elem_size);
|
||||
marshal_arg(b, any{rawptr(data), info.elem.id});
|
||||
}
|
||||
write_byte(b, ']');
|
||||
strings.write_byte(b, ']');
|
||||
|
||||
case Type_Info_Slice:
|
||||
write_byte(b, '[');
|
||||
case runtime.Type_Info_Slice:
|
||||
strings.write_byte(b, '[');
|
||||
slice := cast(^mem.Raw_Slice)v.data;
|
||||
for i in 0..<slice.len {
|
||||
if i > 0 { write_string(b, ", "); }
|
||||
if i > 0 { strings.write_string(b, ", "); }
|
||||
|
||||
data := uintptr(slice.data) + uintptr(i*info.elem_size);
|
||||
marshal_arg(b, any{rawptr(data), info.elem.id});
|
||||
}
|
||||
write_byte(b, ']');
|
||||
strings.write_byte(b, ']');
|
||||
|
||||
case Type_Info_Map:
|
||||
case runtime.Type_Info_Map:
|
||||
m := (^mem.Raw_Map)(v.data);
|
||||
|
||||
write_byte(b, '{');
|
||||
strings.write_byte(b, '{');
|
||||
if m != nil {
|
||||
if info.generated_struct == nil {
|
||||
return .Unsupported_Type;
|
||||
}
|
||||
entries := &m.entries;
|
||||
gs := type_info_base(info.generated_struct).variant.(Type_Info_Struct);
|
||||
ed := type_info_base(gs.types[1]).variant.(Type_Info_Dynamic_Array);
|
||||
entry_type := ed.elem.variant.(Type_Info_Struct);
|
||||
gs := runtime.type_info_base(info.generated_struct).variant.(runtime.Type_Info_Struct);
|
||||
ed := runtime.type_info_base(gs.types[1]).variant.(runtime.Type_Info_Dynamic_Array);
|
||||
entry_type := ed.elem.variant.(runtime.Type_Info_Struct);
|
||||
entry_size := ed.elem_size;
|
||||
|
||||
for i in 0..<entries.len {
|
||||
if i > 0 { write_string(b, ", "); }
|
||||
if i > 0 { strings.write_string(b, ", "); }
|
||||
|
||||
data := uintptr(entries.data) + uintptr(i*entry_size);
|
||||
key := rawptr(data + entry_type.offsets[2]);
|
||||
value := rawptr(data + entry_type.offsets[3]);
|
||||
|
||||
marshal_arg(b, any{key, info.key.id});
|
||||
write_string(b, ": ");
|
||||
strings.write_string(b, ": ");
|
||||
marshal_arg(b, any{value, info.value.id});
|
||||
}
|
||||
}
|
||||
write_byte(b, '}');
|
||||
strings.write_byte(b, '}');
|
||||
|
||||
case Type_Info_Struct:
|
||||
write_byte(b, '{');
|
||||
case runtime.Type_Info_Struct:
|
||||
strings.write_byte(b, '{');
|
||||
for name, i in info.names {
|
||||
if i > 0 { write_string(b, ", "); }
|
||||
write_quoted_string(b, name);
|
||||
write_string(b, ": ");
|
||||
if i > 0 { strings.write_string(b, ", "); }
|
||||
strings.write_quoted_string(b, name);
|
||||
strings.write_string(b, ": ");
|
||||
|
||||
id := info.types[i].id;
|
||||
data := rawptr(uintptr(v.data) + info.offsets[i]);
|
||||
marshal_arg(b, any{data, id});
|
||||
}
|
||||
write_byte(b, '}');
|
||||
strings.write_byte(b, '}');
|
||||
|
||||
case Type_Info_Union:
|
||||
case runtime.Type_Info_Union:
|
||||
tag_ptr := uintptr(v.data) + info.tag_offset;
|
||||
tag_any := any{rawptr(tag_ptr), info.tag_type.id};
|
||||
|
||||
@@ -250,16 +251,16 @@ marshal_arg :: proc(b: ^strings.Builder, v: any) -> Marshal_Error {
|
||||
}
|
||||
|
||||
if v.data == nil || tag == 0 {
|
||||
write_string(b, "null");
|
||||
strings.write_string(b, "null");
|
||||
} else {
|
||||
id := info.variants[tag-1].id;
|
||||
marshal_arg(b, any{v.data, id});
|
||||
}
|
||||
|
||||
case Type_Info_Enum:
|
||||
case runtime.Type_Info_Enum:
|
||||
return marshal_arg(b, any{v.data, info.base.id});
|
||||
|
||||
case Type_Info_Bit_Set:
|
||||
case runtime.Type_Info_Bit_Set:
|
||||
is_bit_set_different_endian_to_platform :: proc(ti: ^runtime.Type_Info) -> bool {
|
||||
if ti == nil {
|
||||
return false;
|
||||
@@ -306,7 +307,7 @@ marshal_arg :: proc(b: ^strings.Builder, v: any) -> Marshal_Error {
|
||||
bit_data = u64(x);
|
||||
case: panic("unknown bit_size size");
|
||||
}
|
||||
write_u64(b, bit_data);
|
||||
strings.write_u64(b, bit_data);
|
||||
|
||||
|
||||
return .Unsupported_Type;
|
||||
|
||||
@@ -68,36 +68,33 @@ expect_token :: proc(p: ^Parser, kind: Token_Kind) -> Error {
|
||||
|
||||
|
||||
parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
value.pos = p.curr_token.pos;
|
||||
defer value.end = token_end_pos(p.prev_token);
|
||||
|
||||
token := p.curr_token;
|
||||
#partial switch token.kind {
|
||||
case .Null:
|
||||
value.value = Null{};
|
||||
value = Null{};
|
||||
advance_token(p);
|
||||
return;
|
||||
case .False:
|
||||
value.value = Boolean(false);
|
||||
value = Boolean(false);
|
||||
advance_token(p);
|
||||
return;
|
||||
case .True:
|
||||
value.value = Boolean(true);
|
||||
value = Boolean(true);
|
||||
advance_token(p);
|
||||
return;
|
||||
|
||||
case .Integer:
|
||||
i, _ := strconv.parse_i64(token.text);
|
||||
value.value = Integer(i);
|
||||
value = Integer(i);
|
||||
advance_token(p);
|
||||
return;
|
||||
case .Float:
|
||||
f, _ := strconv.parse_f64(token.text);
|
||||
value.value = Float(f);
|
||||
value = Float(f);
|
||||
advance_token(p);
|
||||
return;
|
||||
case .String:
|
||||
value.value = String(unquote_string(token, p.spec, p.allocator));
|
||||
value = String(unquote_string(token, p.spec, p.allocator));
|
||||
advance_token(p);
|
||||
return;
|
||||
|
||||
@@ -115,7 +112,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
if token.text[0] == '-' {
|
||||
inf = 0xfff0000000000000;
|
||||
}
|
||||
value.value = transmute(f64)inf;
|
||||
value = transmute(f64)inf;
|
||||
advance_token(p);
|
||||
return;
|
||||
case .NaN:
|
||||
@@ -123,7 +120,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
if token.text[0] == '-' {
|
||||
nan = 0xfff7ffffffffffff;
|
||||
}
|
||||
value.value = transmute(f64)nan;
|
||||
value = transmute(f64)nan;
|
||||
advance_token(p);
|
||||
return;
|
||||
}
|
||||
@@ -136,11 +133,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
}
|
||||
|
||||
parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
value.pos = p.curr_token.pos;
|
||||
defer value.end = token_end_pos(p.prev_token);
|
||||
if err = expect_token(p, .Open_Bracket); err != .None {
|
||||
return;
|
||||
}
|
||||
expect_token(p, .Open_Bracket) or_return;
|
||||
|
||||
array: Array;
|
||||
array.allocator = p.allocator;
|
||||
@@ -152,11 +145,7 @@ parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
}
|
||||
|
||||
for p.curr_token.kind != .Close_Bracket {
|
||||
elem, elem_err := parse_value(p);
|
||||
if elem_err != .None {
|
||||
err = elem_err;
|
||||
return;
|
||||
}
|
||||
elem := parse_value(p) or_return;
|
||||
append(&array, elem);
|
||||
|
||||
// Disallow trailing commas for the time being
|
||||
@@ -167,11 +156,8 @@ parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err = expect_token(p, .Close_Bracket); err != .None {
|
||||
return;
|
||||
}
|
||||
|
||||
value.value = array;
|
||||
expect_token(p, .Close_Bracket) or_return;
|
||||
value = array;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -205,13 +191,7 @@ parse_object_key :: proc(p: ^Parser) -> (key: string, err: Error) {
|
||||
}
|
||||
|
||||
parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
value.pos = p.curr_token.pos;
|
||||
defer value.end = token_end_pos(p.prev_token);
|
||||
|
||||
if err = expect_token(p, .Open_Brace); err != .None {
|
||||
value.pos = p.curr_token.pos;
|
||||
return;
|
||||
}
|
||||
expect_token(p, .Open_Brace) or_return;
|
||||
|
||||
obj: Object;
|
||||
obj.allocator = p.allocator;
|
||||
@@ -228,26 +208,18 @@ parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
key, err = parse_object_key(p);
|
||||
if err != .None {
|
||||
delete(key, p.allocator);
|
||||
value.pos = p.curr_token.pos;
|
||||
return;
|
||||
}
|
||||
|
||||
if colon_err := expect_token(p, .Colon); colon_err != .None {
|
||||
err = .Expected_Colon_After_Key;
|
||||
value.pos = p.curr_token.pos;
|
||||
return;
|
||||
}
|
||||
|
||||
elem, elem_err := parse_value(p);
|
||||
if elem_err != .None {
|
||||
err = elem_err;
|
||||
value.pos = p.curr_token.pos;
|
||||
return;
|
||||
}
|
||||
elem := parse_value(p) or_return;
|
||||
|
||||
if key in obj {
|
||||
err = .Duplicate_Object_Key;
|
||||
value.pos = p.curr_token.pos;
|
||||
delete(key, p.allocator);
|
||||
return;
|
||||
}
|
||||
@@ -269,12 +241,8 @@ parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err = expect_token(p, .Close_Brace); err != .None {
|
||||
value.pos = p.curr_token.pos;
|
||||
return;
|
||||
}
|
||||
|
||||
value.value = obj;
|
||||
expect_token(p, .Close_Brace) or_return;
|
||||
value = obj;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -290,9 +258,9 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a
|
||||
for c in s[2:4] {
|
||||
x: rune;
|
||||
switch c {
|
||||
case '0'..'9': x = c - '0';
|
||||
case 'a'..'f': x = c - 'a' + 10;
|
||||
case 'A'..'F': x = c - 'A' + 10;
|
||||
case '0'..='9': x = c - '0';
|
||||
case 'a'..='f': x = c - 'a' + 10;
|
||||
case 'A'..='F': x = c - 'A' + 10;
|
||||
case: return -1;
|
||||
}
|
||||
r = r*16 + x;
|
||||
@@ -308,9 +276,9 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a
|
||||
for c in s[2:6] {
|
||||
x: rune;
|
||||
switch c {
|
||||
case '0'..'9': x = c - '0';
|
||||
case 'a'..'f': x = c - 'a' + 10;
|
||||
case 'A'..'F': x = c - 'A' + 10;
|
||||
case '0'..='9': x = c - '0';
|
||||
case 'a'..='f': x = c - 'a' + 10;
|
||||
case 'A'..='F': x = c - 'A' + 10;
|
||||
case: return -1;
|
||||
}
|
||||
r = r*16 + x;
|
||||
|
||||
@@ -2,6 +2,12 @@ package json
|
||||
|
||||
import "core:unicode/utf8"
|
||||
|
||||
Pos :: struct {
|
||||
offset: int,
|
||||
line: int,
|
||||
column: int,
|
||||
}
|
||||
|
||||
Token :: struct {
|
||||
using pos: Pos,
|
||||
kind: Token_Kind,
|
||||
@@ -82,7 +88,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
for t.offset < len(t.data) {
|
||||
next_rune(t);
|
||||
switch t.r {
|
||||
case '0'..'9', 'a'..'f', 'A'..'F':
|
||||
case '0'..='9', 'a'..='f', 'A'..='F':
|
||||
// Okay
|
||||
case:
|
||||
return;
|
||||
@@ -100,12 +106,13 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
for i := 0; i < 4; i += 1 {
|
||||
r := next_rune(t);
|
||||
switch r {
|
||||
case '0'..'9', 'a'..'f', 'A'..'F':
|
||||
case '0'..='9', 'a'..='f', 'A'..='F':
|
||||
// Okay
|
||||
case:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case:
|
||||
// Ignore the next rune regardless
|
||||
next_rune(t);
|
||||
@@ -149,7 +156,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
skip_alphanum :: proc(t: ^Tokenizer) {
|
||||
for t.offset < len(t.data) {
|
||||
switch next_rune(t) {
|
||||
case 'A'..'Z', 'a'..'z', '0'..'9', '_':
|
||||
case 'A'..='Z', 'a'..='z', '0'..='9', '_':
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -173,7 +180,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
token.kind = .EOF;
|
||||
err = .EOF;
|
||||
|
||||
case 'A'..'Z', 'a'..'z', '_':
|
||||
case 'A'..='Z', 'a'..='z', '_':
|
||||
token.kind = .Ident;
|
||||
|
||||
skip_alphanum(t);
|
||||
@@ -200,7 +207,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
|
||||
case '-':
|
||||
switch t.r {
|
||||
case '0'..'9':
|
||||
case '0'..='9':
|
||||
// Okay
|
||||
case:
|
||||
// Illegal use of +/-
|
||||
@@ -219,7 +226,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
}
|
||||
fallthrough;
|
||||
|
||||
case '0'..'9':
|
||||
case '0'..='9':
|
||||
token.kind = t.parse_integers ? .Integer : .Float;
|
||||
if t.spec == .JSON5 { // Hexadecimal Numbers
|
||||
if curr_rune == '0' && (t.r == 'x' || t.r == 'X') {
|
||||
@@ -361,7 +368,7 @@ is_valid_number :: proc(str: string, spec: Specification) -> bool {
|
||||
switch s[0] {
|
||||
case '0':
|
||||
s = s[1:];
|
||||
case '1'..'9':
|
||||
case '1'..='9':
|
||||
s = s[1:];
|
||||
for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
|
||||
s = s[1:];
|
||||
@@ -453,7 +460,7 @@ is_valid_string_literal :: proc(str: string, spec: Specification) -> bool {
|
||||
for j := 0; j < 4; j += 1 {
|
||||
c2 := hex[j];
|
||||
switch c2 {
|
||||
case '0'..'9', 'a'..'z', 'A'..'Z':
|
||||
case '0'..='9', 'a'..='z', 'A'..='Z':
|
||||
// Okay
|
||||
case:
|
||||
return false;
|
||||
|
||||
@@ -14,26 +14,16 @@ String :: string;
|
||||
Array :: distinct [dynamic]Value;
|
||||
Object :: distinct map[string]Value;
|
||||
|
||||
Value :: struct {
|
||||
pos, end: Pos,
|
||||
value: union {
|
||||
Null,
|
||||
Integer,
|
||||
Float,
|
||||
Boolean,
|
||||
String,
|
||||
Array,
|
||||
Object,
|
||||
},
|
||||
Value :: union {
|
||||
Null,
|
||||
Integer,
|
||||
Float,
|
||||
Boolean,
|
||||
String,
|
||||
Array,
|
||||
Object,
|
||||
}
|
||||
|
||||
Pos :: struct {
|
||||
offset: int,
|
||||
line: int,
|
||||
column: int,
|
||||
}
|
||||
|
||||
|
||||
Error :: enum {
|
||||
None,
|
||||
|
||||
@@ -57,7 +47,7 @@ Error :: enum {
|
||||
|
||||
|
||||
destroy_value :: proc(value: Value) {
|
||||
#partial switch v in value.value {
|
||||
#partial switch v in value {
|
||||
case Object:
|
||||
for key, elem in v {
|
||||
delete(key);
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
package fmt implemented formatted I/O with procedures similar to C's printf and Python's format.
|
||||
The format 'verbs' are derived from C's but simpler.
|
||||
|
||||
Printing
|
||||
|
||||
The verbs:
|
||||
|
||||
General:
|
||||
%v the value in a default format
|
||||
%#v an expanded format of %v with newlines and indentation
|
||||
%T an Odin-syntax representation of the type of the value
|
||||
%% a literal percent sign; consumes no value
|
||||
{{ a literal open brace; consumes no value
|
||||
}} a literal close brace; consumes no value
|
||||
{:v} equivalent to %v (Python-like formatting syntax)
|
||||
|
||||
Boolean:
|
||||
%t the word "true" or "false"
|
||||
Integer:
|
||||
%b base 2
|
||||
%c the character represented by the corresponding Unicode code point
|
||||
%r synonym for %c
|
||||
%o base 8
|
||||
%d base 10
|
||||
%i base 10
|
||||
%z base 12
|
||||
%x base 16, with lower-case letters for a-f
|
||||
%X base 16, with upper-case letters for A-F
|
||||
%U Unicode format: U+1234; same as "U+%04X"
|
||||
Floating-point, complex numbers, and quaternions:
|
||||
%e scientific notation, e.g. -1.23456e+78
|
||||
%E scientific notation, e.g. -1.23456E+78
|
||||
%f decimal point but no exponent, e.g. 123.456
|
||||
%F synonym for %f
|
||||
%h hexadecimal (lower-case) representation with 0h prefix (0h01234abcd)
|
||||
%H hexadecimal (upper-case) representation with 0H prefix (0h01234ABCD)
|
||||
String and slice of bytes
|
||||
%s the uninterpreted bytes of the string or slice
|
||||
%q a double-quoted string safely escaped with Odin syntax
|
||||
%x base 16, lower-case, two characters per byte
|
||||
%X base 16, upper-case, two characters per byte
|
||||
Slice and dynamic array:
|
||||
%p address of the 0th element in base 16 notation (upper-case), with leading 0x
|
||||
Pointer:
|
||||
%p base 16 notation (upper-case), with leading 0x
|
||||
The %b, %d, %o, %z, %x, %X verbs also work with pointers,
|
||||
treating it as if it was an integer
|
||||
Enums:
|
||||
%s prints the name of the enum field
|
||||
The %i, %d, %f verbs also work with enums,
|
||||
treating it as if it was a number
|
||||
|
||||
For compound values, the elements are printed using these rules recursively; laid out like the following:
|
||||
struct: {name0 = field0, name1 = field1, ...}
|
||||
array [elem0, elem1, elem2, ...]
|
||||
enumerated array [key0 = elem0, key1 = elem1, key2 = elem2, ...]
|
||||
maps: map[key0 = value0, key1 = value1, ...]
|
||||
bit sets {key0 = elem0, key1 = elem1, ...}
|
||||
pointer to above: &{}, &[], &map[]
|
||||
|
||||
Width is specified by an optional decimal number immediately preceding the verb.
|
||||
If not present, the width is whatever is necessary to represent the value.
|
||||
Precision is specified after the (optional) width followed by a period followed by a decimal number.
|
||||
If no period is present, a default precision is used.
|
||||
A period with no following number specifies a precision of 0.
|
||||
Examples:
|
||||
%f default width, default precision
|
||||
%8f width 8, default precision
|
||||
%.3f default width, precision 2
|
||||
%8.3f width 8, precision 3
|
||||
%8.f width 8, precision 0
|
||||
|
||||
Width and precision are measured in units of Unicode code points (runes).
|
||||
n.b. C's printf uses units of bytes
|
||||
|
||||
|
||||
Other flags:
|
||||
+ always print a sign for numeric values
|
||||
- pad with spaces on the right rather the left (left-justify the field)
|
||||
# alternate format:
|
||||
add leading 0b for binary (%#b)
|
||||
add leading 0o for octal (%#o)
|
||||
add leading 0z for dozenal (%#z)
|
||||
add leading 0x or 0X for hexadecimal (%#x or %#X)
|
||||
remove leading 0x for %p (%#p)
|
||||
|
||||
' ' (space) leave a space for elided sign in numbers (% d)
|
||||
0 pad with leading zeros rather than spaces
|
||||
|
||||
|
||||
Flags are ignored by verbs that don't expect them
|
||||
|
||||
|
||||
For each printf-like procedure, there is a print function that takes no
|
||||
format, and is equivalent to doing %v for every value and inserts a separator
|
||||
between each value (default is a single space).
|
||||
Another procedure println which has the same functionality as print but appends a newline.
|
||||
|
||||
Explicit argument indices:
|
||||
|
||||
In printf-like procedures, the default behaviour is for each formatting verb to format successive
|
||||
arguments passed in the call. However, the notation [n] immediately before the verb indicates that
|
||||
the nth zero-index argument is to be formatted instead.
|
||||
The same notation before an '*' for a width or precision selecting the argument index holding the value.
|
||||
Python-like syntax with argument indices differs for the selecting the argument index: {N:v}
|
||||
|
||||
Examples:
|
||||
fmt.printf("%[1]d %[0]d\n", 13, 37); // C-like syntax
|
||||
fmt.printf("{1:d} {0:d}\n", 13, 37); // Python-like syntax
|
||||
prints "37 13", whilst:
|
||||
fmt.printf("%[2]*.[1]*[0]f\n", 17.0, 2, 6); // C-like syntax
|
||||
fmt.printf("%{0:[2]*.[1]*f}\n", 17.0, 2, 6); // Python-like syntax
|
||||
equivalent to:
|
||||
fmt.printf("%6.2f\n", 17.0, 2, 6); // C-like syntax
|
||||
fmt.printf("{:6.2f}\n", 17.0, 2, 6); // Python-like syntax
|
||||
prints "17.00"
|
||||
|
||||
Format errors:
|
||||
|
||||
If an invalid argument is given for a verb, such as providing a string to %d, the generated string
|
||||
will contain a description of the problem. For example:
|
||||
|
||||
Bad enum value:
|
||||
%!(BAD ENUM VALUE)
|
||||
Too many arguments:
|
||||
%!(EXTRA <value>, <value>, ...)
|
||||
Too few arguments:
|
||||
%!(MISSING ARGUMENT)
|
||||
Invalid width or precision
|
||||
%!(BAD WIDTH)
|
||||
%!(BAD PRECISION)
|
||||
Missing verb:
|
||||
%!(NO VERB)
|
||||
Invalid or invalid use of argument index:
|
||||
%!(BAD ARGUMENT NUMBER)
|
||||
Missing close brace when using Python-like formatting syntax:
|
||||
%!(MISSING CLOSE BRACE)
|
||||
|
||||
*/
|
||||
package fmt
|
||||
+50
-8
@@ -10,7 +10,7 @@ import "core:strconv"
|
||||
import "core:strings"
|
||||
import "core:time"
|
||||
import "core:unicode/utf8"
|
||||
import "intrinsics"
|
||||
import "core:intrinsics"
|
||||
|
||||
Info :: struct {
|
||||
minus: bool,
|
||||
@@ -27,6 +27,7 @@ Info :: struct {
|
||||
|
||||
reordered: bool,
|
||||
good_arg_index: bool,
|
||||
ignore_user_formatters: bool,
|
||||
|
||||
writer: io.Writer,
|
||||
arg: any, // Temporary
|
||||
@@ -93,11 +94,6 @@ eprintln :: proc(args: ..any, sep := " ") -> int { return fprintln(fd=os.stderr,
|
||||
eprintf :: proc(fmt: string, args: ..any) -> int { return fprintf(os.stderr, fmt, ..args); }
|
||||
|
||||
|
||||
@(deprecated="prefer eprint") print_err :: proc(args: ..any) -> int { return eprint(..args); }
|
||||
@(deprecated="prefer eprintf") printf_err :: proc(fmt: string, args: ..any) -> int { return eprintf(fmt, ..args); }
|
||||
@(deprecated="prefer eprintln") println_err :: proc(args: ..any) -> int { return eprintln(..args); }
|
||||
|
||||
|
||||
// aprint* procedures return a string that was allocated with the current context
|
||||
// They must be freed accordingly
|
||||
aprint :: proc(args: ..any, sep := " ") -> string {
|
||||
@@ -1013,6 +1009,7 @@ fmt_pointer :: proc(fi: ^Info, p: rawptr, verb: rune) {
|
||||
case 'b': _fmt_int(fi, u, 2, false, 8*size_of(rawptr), __DIGITS_UPPER);
|
||||
case 'o': _fmt_int(fi, u, 8, false, 8*size_of(rawptr), __DIGITS_UPPER);
|
||||
case 'i', 'd': _fmt_int(fi, u, 10, false, 8*size_of(rawptr), __DIGITS_UPPER);
|
||||
case 'z': _fmt_int(fi, u, 12, false, 8*size_of(rawptr), __DIGITS_UPPER);
|
||||
case 'x': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER);
|
||||
case 'X': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER);
|
||||
|
||||
@@ -1082,7 +1079,7 @@ fmt_enum :: proc(fi: ^Info, v: any, verb: rune) {
|
||||
case 's', 'v':
|
||||
str, ok := enum_value_to_string(v);
|
||||
if !ok {
|
||||
str = "!%(BAD ENUM VALUE)";
|
||||
str = "%!(BAD ENUM VALUE)";
|
||||
}
|
||||
io.write_string(fi.writer, str);
|
||||
}
|
||||
@@ -1266,15 +1263,18 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
|
||||
return;
|
||||
}
|
||||
|
||||
if _user_formatters != nil {
|
||||
if _user_formatters != nil && !fi.ignore_user_formatters {
|
||||
formatter := _user_formatters[v.id];
|
||||
if formatter != nil {
|
||||
fi.ignore_user_formatters = false;
|
||||
if ok := formatter(fi, v, verb); !ok {
|
||||
fi.ignore_user_formatters = true;
|
||||
fmt_bad_verb(fi, verb);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
fi.ignore_user_formatters = false;
|
||||
|
||||
type_info := type_info_of(v.id);
|
||||
switch info in type_info.variant {
|
||||
@@ -1569,6 +1569,48 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
|
||||
fmt_pointer(fi, ptr, verb);
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Multi_Pointer:
|
||||
ptr := (^rawptr)(v.data)^;
|
||||
if verb != 'p' && info.elem != nil {
|
||||
a := any{ptr, info.elem.id};
|
||||
|
||||
elem := runtime.type_info_base(info.elem);
|
||||
if elem != nil {
|
||||
#partial switch e in elem.variant {
|
||||
case runtime.Type_Info_Array,
|
||||
runtime.Type_Info_Slice,
|
||||
runtime.Type_Info_Dynamic_Array,
|
||||
runtime.Type_Info_Map:
|
||||
if ptr == nil {
|
||||
io.write_string(fi.writer, "<nil>");
|
||||
return;
|
||||
}
|
||||
if fi.record_level < 1 {
|
||||
fi.record_level += 1;
|
||||
defer fi.record_level -= 1;
|
||||
io.write_byte(fi.writer, '&');
|
||||
fmt_value(fi, a, verb);
|
||||
return;
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Struct,
|
||||
runtime.Type_Info_Union:
|
||||
if ptr == nil {
|
||||
io.write_string(fi.writer, "<nil>");
|
||||
return;
|
||||
}
|
||||
if fi.record_level < 1 {
|
||||
fi.record_level += 1;
|
||||
defer fi.record_level -= 1;
|
||||
io.write_byte(fi.writer, '&');
|
||||
fmt_value(fi, a, verb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt_pointer(fi, ptr, verb);
|
||||
|
||||
case runtime.Type_Info_Array:
|
||||
if (verb == 's' || verb == 'q') && reflect.is_byte(info.elem) {
|
||||
s := strings.string_from_ptr((^byte)(v.data), info.count);
|
||||
|
||||
+3
-74
@@ -1,86 +1,15 @@
|
||||
package hash
|
||||
|
||||
crc32 :: proc(data: []byte, seed := u32(0)) -> u32 #no_bounds_check {
|
||||
result := ~u32(seed);
|
||||
for b in data {
|
||||
result = result>>8 ~ _crc32_table[(result ~ u32(b)) & 0xff];
|
||||
}
|
||||
return ~result;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
crc64 :: proc(data: []byte, seed := u32(0)) -> u64 #no_bounds_check {
|
||||
result := ~u64(seed);
|
||||
for b in data {
|
||||
#no_bounds_check for b in data {
|
||||
result = result>>8 ~ _crc64_table[(result ~ u64(b)) & 0xff];
|
||||
}
|
||||
return ~result;
|
||||
}
|
||||
|
||||
@private _crc32_table := [256]u32{
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
|
||||
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
|
||||
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
|
||||
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
|
||||
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
||||
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
|
||||
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
|
||||
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
|
||||
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
|
||||
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
|
||||
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
|
||||
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
|
||||
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
|
||||
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
|
||||
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
|
||||
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
|
||||
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
|
||||
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
|
||||
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
|
||||
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
|
||||
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
|
||||
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
|
||||
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
|
||||
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
|
||||
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
|
||||
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
|
||||
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
||||
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
|
||||
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
|
||||
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
|
||||
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
|
||||
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
|
||||
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
|
||||
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
|
||||
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
|
||||
};
|
||||
@private _crc64_table := [256]u64{
|
||||
0x0000000000000000, 0x42f0e1eba9ea3693, 0x85e1c3d753d46d26, 0xc711223cfa3e5bb5,
|
||||
0x493366450e42ecdf, 0x0bc387aea7a8da4c, 0xccd2a5925d9681f9, 0x8e224479f47cb76a,
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
package hash
|
||||
|
||||
import "core:intrinsics"
|
||||
|
||||
@(optimization_mode="speed")
|
||||
crc32 :: proc(data: []byte, seed := u32(0)) -> u32 #no_bounds_check {
|
||||
crc := ~seed;
|
||||
buffer := raw_data(data);
|
||||
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);
|
||||
length -= 1;
|
||||
}
|
||||
|
||||
for length >= 8 {
|
||||
buf := (^[8]byte)(buffer);
|
||||
word := u32((^u32le)(buffer)^);
|
||||
crc ~= word;
|
||||
|
||||
crc = crc32_table[7][crc & 0xff] ~
|
||||
crc32_table[6][(crc >> 8) & 0xff] ~
|
||||
crc32_table[5][(crc >> 16) & 0xff] ~
|
||||
crc32_table[4][(crc >> 24) & 0xff] ~
|
||||
crc32_table[3][buf[4]] ~
|
||||
crc32_table[2][buf[5]] ~
|
||||
crc32_table[1][buf[6]] ~
|
||||
crc32_table[0][buf[7]];
|
||||
|
||||
buffer = intrinsics.ptr_offset(buffer, 8);
|
||||
length -= 8;
|
||||
}
|
||||
|
||||
|
||||
for length != 0 {
|
||||
crc = crc32_table[0][byte(crc) ~ buffer^] ~ (crc >> 8);
|
||||
buffer = intrinsics.ptr_offset(buffer, 1);
|
||||
length -= 1;
|
||||
}
|
||||
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
@(private)
|
||||
crc32_table := [8][256]u32{
|
||||
{
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
|
||||
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
|
||||
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
|
||||
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
|
||||
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
|
||||
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
|
||||
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
|
||||
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
|
||||
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
|
||||
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
|
||||
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
|
||||
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
|
||||
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
|
||||
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
|
||||
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
|
||||
},
|
||||
{
|
||||
0x00000000, 0x191b3141, 0x32366282, 0x2b2d53c3, 0x646cc504, 0x7d77f445, 0x565aa786, 0x4f4196c7,
|
||||
0xc8d98a08, 0xd1c2bb49, 0xfaefe88a, 0xe3f4d9cb, 0xacb54f0c, 0xb5ae7e4d, 0x9e832d8e, 0x87981ccf,
|
||||
0x4ac21251, 0x53d92310, 0x78f470d3, 0x61ef4192, 0x2eaed755, 0x37b5e614, 0x1c98b5d7, 0x05838496,
|
||||
0x821b9859, 0x9b00a918, 0xb02dfadb, 0xa936cb9a, 0xe6775d5d, 0xff6c6c1c, 0xd4413fdf, 0xcd5a0e9e,
|
||||
0x958424a2, 0x8c9f15e3, 0xa7b24620, 0xbea97761, 0xf1e8e1a6, 0xe8f3d0e7, 0xc3de8324, 0xdac5b265,
|
||||
0x5d5daeaa, 0x44469feb, 0x6f6bcc28, 0x7670fd69, 0x39316bae, 0x202a5aef, 0x0b07092c, 0x121c386d,
|
||||
0xdf4636f3, 0xc65d07b2, 0xed705471, 0xf46b6530, 0xbb2af3f7, 0xa231c2b6, 0x891c9175, 0x9007a034,
|
||||
0x179fbcfb, 0x0e848dba, 0x25a9de79, 0x3cb2ef38, 0x73f379ff, 0x6ae848be, 0x41c51b7d, 0x58de2a3c,
|
||||
0xf0794f05, 0xe9627e44, 0xc24f2d87, 0xdb541cc6, 0x94158a01, 0x8d0ebb40, 0xa623e883, 0xbf38d9c2,
|
||||
0x38a0c50d, 0x21bbf44c, 0x0a96a78f, 0x138d96ce, 0x5ccc0009, 0x45d73148, 0x6efa628b, 0x77e153ca,
|
||||
0xbabb5d54, 0xa3a06c15, 0x888d3fd6, 0x91960e97, 0xded79850, 0xc7cca911, 0xece1fad2, 0xf5facb93,
|
||||
0x7262d75c, 0x6b79e61d, 0x4054b5de, 0x594f849f, 0x160e1258, 0x0f152319, 0x243870da, 0x3d23419b,
|
||||
0x65fd6ba7, 0x7ce65ae6, 0x57cb0925, 0x4ed03864, 0x0191aea3, 0x188a9fe2, 0x33a7cc21, 0x2abcfd60,
|
||||
0xad24e1af, 0xb43fd0ee, 0x9f12832d, 0x8609b26c, 0xc94824ab, 0xd05315ea, 0xfb7e4629, 0xe2657768,
|
||||
0x2f3f79f6, 0x362448b7, 0x1d091b74, 0x04122a35, 0x4b53bcf2, 0x52488db3, 0x7965de70, 0x607eef31,
|
||||
0xe7e6f3fe, 0xfefdc2bf, 0xd5d0917c, 0xcccba03d, 0x838a36fa, 0x9a9107bb, 0xb1bc5478, 0xa8a76539,
|
||||
0x3b83984b, 0x2298a90a, 0x09b5fac9, 0x10aecb88, 0x5fef5d4f, 0x46f46c0e, 0x6dd93fcd, 0x74c20e8c,
|
||||
0xf35a1243, 0xea412302, 0xc16c70c1, 0xd8774180, 0x9736d747, 0x8e2de606, 0xa500b5c5, 0xbc1b8484,
|
||||
0x71418a1a, 0x685abb5b, 0x4377e898, 0x5a6cd9d9, 0x152d4f1e, 0x0c367e5f, 0x271b2d9c, 0x3e001cdd,
|
||||
0xb9980012, 0xa0833153, 0x8bae6290, 0x92b553d1, 0xddf4c516, 0xc4eff457, 0xefc2a794, 0xf6d996d5,
|
||||
0xae07bce9, 0xb71c8da8, 0x9c31de6b, 0x852aef2a, 0xca6b79ed, 0xd37048ac, 0xf85d1b6f, 0xe1462a2e,
|
||||
0x66de36e1, 0x7fc507a0, 0x54e85463, 0x4df36522, 0x02b2f3e5, 0x1ba9c2a4, 0x30849167, 0x299fa026,
|
||||
0xe4c5aeb8, 0xfdde9ff9, 0xd6f3cc3a, 0xcfe8fd7b, 0x80a96bbc, 0x99b25afd, 0xb29f093e, 0xab84387f,
|
||||
0x2c1c24b0, 0x350715f1, 0x1e2a4632, 0x07317773, 0x4870e1b4, 0x516bd0f5, 0x7a468336, 0x635db277,
|
||||
0xcbfad74e, 0xd2e1e60f, 0xf9ccb5cc, 0xe0d7848d, 0xaf96124a, 0xb68d230b, 0x9da070c8, 0x84bb4189,
|
||||
0x03235d46, 0x1a386c07, 0x31153fc4, 0x280e0e85, 0x674f9842, 0x7e54a903, 0x5579fac0, 0x4c62cb81,
|
||||
0x8138c51f, 0x9823f45e, 0xb30ea79d, 0xaa1596dc, 0xe554001b, 0xfc4f315a, 0xd7626299, 0xce7953d8,
|
||||
0x49e14f17, 0x50fa7e56, 0x7bd72d95, 0x62cc1cd4, 0x2d8d8a13, 0x3496bb52, 0x1fbbe891, 0x06a0d9d0,
|
||||
0x5e7ef3ec, 0x4765c2ad, 0x6c48916e, 0x7553a02f, 0x3a1236e8, 0x230907a9, 0x0824546a, 0x113f652b,
|
||||
0x96a779e4, 0x8fbc48a5, 0xa4911b66, 0xbd8a2a27, 0xf2cbbce0, 0xebd08da1, 0xc0fdde62, 0xd9e6ef23,
|
||||
0x14bce1bd, 0x0da7d0fc, 0x268a833f, 0x3f91b27e, 0x70d024b9, 0x69cb15f8, 0x42e6463b, 0x5bfd777a,
|
||||
0xdc656bb5, 0xc57e5af4, 0xee530937, 0xf7483876, 0xb809aeb1, 0xa1129ff0, 0x8a3fcc33, 0x9324fd72,
|
||||
},
|
||||
{
|
||||
0x00000000, 0x01c26a37, 0x0384d46e, 0x0246be59, 0x0709a8dc, 0x06cbc2eb, 0x048d7cb2, 0x054f1685,
|
||||
0x0e1351b8, 0x0fd13b8f, 0x0d9785d6, 0x0c55efe1, 0x091af964, 0x08d89353, 0x0a9e2d0a, 0x0b5c473d,
|
||||
0x1c26a370, 0x1de4c947, 0x1fa2771e, 0x1e601d29, 0x1b2f0bac, 0x1aed619b, 0x18abdfc2, 0x1969b5f5,
|
||||
0x1235f2c8, 0x13f798ff, 0x11b126a6, 0x10734c91, 0x153c5a14, 0x14fe3023, 0x16b88e7a, 0x177ae44d,
|
||||
0x384d46e0, 0x398f2cd7, 0x3bc9928e, 0x3a0bf8b9, 0x3f44ee3c, 0x3e86840b, 0x3cc03a52, 0x3d025065,
|
||||
0x365e1758, 0x379c7d6f, 0x35dac336, 0x3418a901, 0x3157bf84, 0x3095d5b3, 0x32d36bea, 0x331101dd,
|
||||
0x246be590, 0x25a98fa7, 0x27ef31fe, 0x262d5bc9, 0x23624d4c, 0x22a0277b, 0x20e69922, 0x2124f315,
|
||||
0x2a78b428, 0x2bbade1f, 0x29fc6046, 0x283e0a71, 0x2d711cf4, 0x2cb376c3, 0x2ef5c89a, 0x2f37a2ad,
|
||||
0x709a8dc0, 0x7158e7f7, 0x731e59ae, 0x72dc3399, 0x7793251c, 0x76514f2b, 0x7417f172, 0x75d59b45,
|
||||
0x7e89dc78, 0x7f4bb64f, 0x7d0d0816, 0x7ccf6221, 0x798074a4, 0x78421e93, 0x7a04a0ca, 0x7bc6cafd,
|
||||
0x6cbc2eb0, 0x6d7e4487, 0x6f38fade, 0x6efa90e9, 0x6bb5866c, 0x6a77ec5b, 0x68315202, 0x69f33835,
|
||||
0x62af7f08, 0x636d153f, 0x612bab66, 0x60e9c151, 0x65a6d7d4, 0x6464bde3, 0x662203ba, 0x67e0698d,
|
||||
0x48d7cb20, 0x4915a117, 0x4b531f4e, 0x4a917579, 0x4fde63fc, 0x4e1c09cb, 0x4c5ab792, 0x4d98dda5,
|
||||
0x46c49a98, 0x4706f0af, 0x45404ef6, 0x448224c1, 0x41cd3244, 0x400f5873, 0x4249e62a, 0x438b8c1d,
|
||||
0x54f16850, 0x55330267, 0x5775bc3e, 0x56b7d609, 0x53f8c08c, 0x523aaabb, 0x507c14e2, 0x51be7ed5,
|
||||
0x5ae239e8, 0x5b2053df, 0x5966ed86, 0x58a487b1, 0x5deb9134, 0x5c29fb03, 0x5e6f455a, 0x5fad2f6d,
|
||||
0xe1351b80, 0xe0f771b7, 0xe2b1cfee, 0xe373a5d9, 0xe63cb35c, 0xe7fed96b, 0xe5b86732, 0xe47a0d05,
|
||||
0xef264a38, 0xeee4200f, 0xeca29e56, 0xed60f461, 0xe82fe2e4, 0xe9ed88d3, 0xebab368a, 0xea695cbd,
|
||||
0xfd13b8f0, 0xfcd1d2c7, 0xfe976c9e, 0xff5506a9, 0xfa1a102c, 0xfbd87a1b, 0xf99ec442, 0xf85cae75,
|
||||
0xf300e948, 0xf2c2837f, 0xf0843d26, 0xf1465711, 0xf4094194, 0xf5cb2ba3, 0xf78d95fa, 0xf64fffcd,
|
||||
0xd9785d60, 0xd8ba3757, 0xdafc890e, 0xdb3ee339, 0xde71f5bc, 0xdfb39f8b, 0xddf521d2, 0xdc374be5,
|
||||
0xd76b0cd8, 0xd6a966ef, 0xd4efd8b6, 0xd52db281, 0xd062a404, 0xd1a0ce33, 0xd3e6706a, 0xd2241a5d,
|
||||
0xc55efe10, 0xc49c9427, 0xc6da2a7e, 0xc7184049, 0xc25756cc, 0xc3953cfb, 0xc1d382a2, 0xc011e895,
|
||||
0xcb4dafa8, 0xca8fc59f, 0xc8c97bc6, 0xc90b11f1, 0xcc440774, 0xcd866d43, 0xcfc0d31a, 0xce02b92d,
|
||||
0x91af9640, 0x906dfc77, 0x922b422e, 0x93e92819, 0x96a63e9c, 0x976454ab, 0x9522eaf2, 0x94e080c5,
|
||||
0x9fbcc7f8, 0x9e7eadcf, 0x9c381396, 0x9dfa79a1, 0x98b56f24, 0x99770513, 0x9b31bb4a, 0x9af3d17d,
|
||||
0x8d893530, 0x8c4b5f07, 0x8e0de15e, 0x8fcf8b69, 0x8a809dec, 0x8b42f7db, 0x89044982, 0x88c623b5,
|
||||
0x839a6488, 0x82580ebf, 0x801eb0e6, 0x81dcdad1, 0x8493cc54, 0x8551a663, 0x8717183a, 0x86d5720d,
|
||||
0xa9e2d0a0, 0xa820ba97, 0xaa6604ce, 0xaba46ef9, 0xaeeb787c, 0xaf29124b, 0xad6fac12, 0xacadc625,
|
||||
0xa7f18118, 0xa633eb2f, 0xa4755576, 0xa5b73f41, 0xa0f829c4, 0xa13a43f3, 0xa37cfdaa, 0xa2be979d,
|
||||
0xb5c473d0, 0xb40619e7, 0xb640a7be, 0xb782cd89, 0xb2cddb0c, 0xb30fb13b, 0xb1490f62, 0xb08b6555,
|
||||
0xbbd72268, 0xba15485f, 0xb853f606, 0xb9919c31, 0xbcde8ab4, 0xbd1ce083, 0xbf5a5eda, 0xbe9834ed,
|
||||
},
|
||||
{
|
||||
0x00000000, 0xb8bc6765, 0xaa09c88b, 0x12b5afee, 0x8f629757, 0x37def032, 0x256b5fdc, 0x9dd738b9,
|
||||
0xc5b428ef, 0x7d084f8a, 0x6fbde064, 0xd7018701, 0x4ad6bfb8, 0xf26ad8dd, 0xe0df7733, 0x58631056,
|
||||
0x5019579f, 0xe8a530fa, 0xfa109f14, 0x42acf871, 0xdf7bc0c8, 0x67c7a7ad, 0x75720843, 0xcdce6f26,
|
||||
0x95ad7f70, 0x2d111815, 0x3fa4b7fb, 0x8718d09e, 0x1acfe827, 0xa2738f42, 0xb0c620ac, 0x087a47c9,
|
||||
0xa032af3e, 0x188ec85b, 0x0a3b67b5, 0xb28700d0, 0x2f503869, 0x97ec5f0c, 0x8559f0e2, 0x3de59787,
|
||||
0x658687d1, 0xdd3ae0b4, 0xcf8f4f5a, 0x7733283f, 0xeae41086, 0x525877e3, 0x40edd80d, 0xf851bf68,
|
||||
0xf02bf8a1, 0x48979fc4, 0x5a22302a, 0xe29e574f, 0x7f496ff6, 0xc7f50893, 0xd540a77d, 0x6dfcc018,
|
||||
0x359fd04e, 0x8d23b72b, 0x9f9618c5, 0x272a7fa0, 0xbafd4719, 0x0241207c, 0x10f48f92, 0xa848e8f7,
|
||||
0x9b14583d, 0x23a83f58, 0x311d90b6, 0x89a1f7d3, 0x1476cf6a, 0xaccaa80f, 0xbe7f07e1, 0x06c36084,
|
||||
0x5ea070d2, 0xe61c17b7, 0xf4a9b859, 0x4c15df3c, 0xd1c2e785, 0x697e80e0, 0x7bcb2f0e, 0xc377486b,
|
||||
0xcb0d0fa2, 0x73b168c7, 0x6104c729, 0xd9b8a04c, 0x446f98f5, 0xfcd3ff90, 0xee66507e, 0x56da371b,
|
||||
0x0eb9274d, 0xb6054028, 0xa4b0efc6, 0x1c0c88a3, 0x81dbb01a, 0x3967d77f, 0x2bd27891, 0x936e1ff4,
|
||||
0x3b26f703, 0x839a9066, 0x912f3f88, 0x299358ed, 0xb4446054, 0x0cf80731, 0x1e4da8df, 0xa6f1cfba,
|
||||
0xfe92dfec, 0x462eb889, 0x549b1767, 0xec277002, 0x71f048bb, 0xc94c2fde, 0xdbf98030, 0x6345e755,
|
||||
0x6b3fa09c, 0xd383c7f9, 0xc1366817, 0x798a0f72, 0xe45d37cb, 0x5ce150ae, 0x4e54ff40, 0xf6e89825,
|
||||
0xae8b8873, 0x1637ef16, 0x048240f8, 0xbc3e279d, 0x21e91f24, 0x99557841, 0x8be0d7af, 0x335cb0ca,
|
||||
0xed59b63b, 0x55e5d15e, 0x47507eb0, 0xffec19d5, 0x623b216c, 0xda874609, 0xc832e9e7, 0x708e8e82,
|
||||
0x28ed9ed4, 0x9051f9b1, 0x82e4565f, 0x3a58313a, 0xa78f0983, 0x1f336ee6, 0x0d86c108, 0xb53aa66d,
|
||||
0xbd40e1a4, 0x05fc86c1, 0x1749292f, 0xaff54e4a, 0x322276f3, 0x8a9e1196, 0x982bbe78, 0x2097d91d,
|
||||
0x78f4c94b, 0xc048ae2e, 0xd2fd01c0, 0x6a4166a5, 0xf7965e1c, 0x4f2a3979, 0x5d9f9697, 0xe523f1f2,
|
||||
0x4d6b1905, 0xf5d77e60, 0xe762d18e, 0x5fdeb6eb, 0xc2098e52, 0x7ab5e937, 0x680046d9, 0xd0bc21bc,
|
||||
0x88df31ea, 0x3063568f, 0x22d6f961, 0x9a6a9e04, 0x07bda6bd, 0xbf01c1d8, 0xadb46e36, 0x15080953,
|
||||
0x1d724e9a, 0xa5ce29ff, 0xb77b8611, 0x0fc7e174, 0x9210d9cd, 0x2aacbea8, 0x38191146, 0x80a57623,
|
||||
0xd8c66675, 0x607a0110, 0x72cfaefe, 0xca73c99b, 0x57a4f122, 0xef189647, 0xfdad39a9, 0x45115ecc,
|
||||
0x764dee06, 0xcef18963, 0xdc44268d, 0x64f841e8, 0xf92f7951, 0x41931e34, 0x5326b1da, 0xeb9ad6bf,
|
||||
0xb3f9c6e9, 0x0b45a18c, 0x19f00e62, 0xa14c6907, 0x3c9b51be, 0x842736db, 0x96929935, 0x2e2efe50,
|
||||
0x2654b999, 0x9ee8defc, 0x8c5d7112, 0x34e11677, 0xa9362ece, 0x118a49ab, 0x033fe645, 0xbb838120,
|
||||
0xe3e09176, 0x5b5cf613, 0x49e959fd, 0xf1553e98, 0x6c820621, 0xd43e6144, 0xc68bceaa, 0x7e37a9cf,
|
||||
0xd67f4138, 0x6ec3265d, 0x7c7689b3, 0xc4caeed6, 0x591dd66f, 0xe1a1b10a, 0xf3141ee4, 0x4ba87981,
|
||||
0x13cb69d7, 0xab770eb2, 0xb9c2a15c, 0x017ec639, 0x9ca9fe80, 0x241599e5, 0x36a0360b, 0x8e1c516e,
|
||||
0x866616a7, 0x3eda71c2, 0x2c6fde2c, 0x94d3b949, 0x090481f0, 0xb1b8e695, 0xa30d497b, 0x1bb12e1e,
|
||||
0x43d23e48, 0xfb6e592d, 0xe9dbf6c3, 0x516791a6, 0xccb0a91f, 0x740cce7a, 0x66b96194, 0xde0506f1,
|
||||
},
|
||||
{
|
||||
0x00000000, 0x3d6029b0, 0x7ac05360, 0x47a07ad0, 0xf580a6c0, 0xc8e08f70, 0x8f40f5a0, 0xb220dc10,
|
||||
0x30704bc1, 0x0d106271, 0x4ab018a1, 0x77d03111, 0xc5f0ed01, 0xf890c4b1, 0xbf30be61, 0x825097d1,
|
||||
0x60e09782, 0x5d80be32, 0x1a20c4e2, 0x2740ed52, 0x95603142, 0xa80018f2, 0xefa06222, 0xd2c04b92,
|
||||
0x5090dc43, 0x6df0f5f3, 0x2a508f23, 0x1730a693, 0xa5107a83, 0x98705333, 0xdfd029e3, 0xe2b00053,
|
||||
0xc1c12f04, 0xfca106b4, 0xbb017c64, 0x866155d4, 0x344189c4, 0x0921a074, 0x4e81daa4, 0x73e1f314,
|
||||
0xf1b164c5, 0xccd14d75, 0x8b7137a5, 0xb6111e15, 0x0431c205, 0x3951ebb5, 0x7ef19165, 0x4391b8d5,
|
||||
0xa121b886, 0x9c419136, 0xdbe1ebe6, 0xe681c256, 0x54a11e46, 0x69c137f6, 0x2e614d26, 0x13016496,
|
||||
0x9151f347, 0xac31daf7, 0xeb91a027, 0xd6f18997, 0x64d15587, 0x59b17c37, 0x1e1106e7, 0x23712f57,
|
||||
0x58f35849, 0x659371f9, 0x22330b29, 0x1f532299, 0xad73fe89, 0x9013d739, 0xd7b3ade9, 0xead38459,
|
||||
0x68831388, 0x55e33a38, 0x124340e8, 0x2f236958, 0x9d03b548, 0xa0639cf8, 0xe7c3e628, 0xdaa3cf98,
|
||||
0x3813cfcb, 0x0573e67b, 0x42d39cab, 0x7fb3b51b, 0xcd93690b, 0xf0f340bb, 0xb7533a6b, 0x8a3313db,
|
||||
0x0863840a, 0x3503adba, 0x72a3d76a, 0x4fc3feda, 0xfde322ca, 0xc0830b7a, 0x872371aa, 0xba43581a,
|
||||
0x9932774d, 0xa4525efd, 0xe3f2242d, 0xde920d9d, 0x6cb2d18d, 0x51d2f83d, 0x167282ed, 0x2b12ab5d,
|
||||
0xa9423c8c, 0x9422153c, 0xd3826fec, 0xeee2465c, 0x5cc29a4c, 0x61a2b3fc, 0x2602c92c, 0x1b62e09c,
|
||||
0xf9d2e0cf, 0xc4b2c97f, 0x8312b3af, 0xbe729a1f, 0x0c52460f, 0x31326fbf, 0x7692156f, 0x4bf23cdf,
|
||||
0xc9a2ab0e, 0xf4c282be, 0xb362f86e, 0x8e02d1de, 0x3c220dce, 0x0142247e, 0x46e25eae, 0x7b82771e,
|
||||
0xb1e6b092, 0x8c869922, 0xcb26e3f2, 0xf646ca42, 0x44661652, 0x79063fe2, 0x3ea64532, 0x03c66c82,
|
||||
0x8196fb53, 0xbcf6d2e3, 0xfb56a833, 0xc6368183, 0x74165d93, 0x49767423, 0x0ed60ef3, 0x33b62743,
|
||||
0xd1062710, 0xec660ea0, 0xabc67470, 0x96a65dc0, 0x248681d0, 0x19e6a860, 0x5e46d2b0, 0x6326fb00,
|
||||
0xe1766cd1, 0xdc164561, 0x9bb63fb1, 0xa6d61601, 0x14f6ca11, 0x2996e3a1, 0x6e369971, 0x5356b0c1,
|
||||
0x70279f96, 0x4d47b626, 0x0ae7ccf6, 0x3787e546, 0x85a73956, 0xb8c710e6, 0xff676a36, 0xc2074386,
|
||||
0x4057d457, 0x7d37fde7, 0x3a978737, 0x07f7ae87, 0xb5d77297, 0x88b75b27, 0xcf1721f7, 0xf2770847,
|
||||
0x10c70814, 0x2da721a4, 0x6a075b74, 0x576772c4, 0xe547aed4, 0xd8278764, 0x9f87fdb4, 0xa2e7d404,
|
||||
0x20b743d5, 0x1dd76a65, 0x5a7710b5, 0x67173905, 0xd537e515, 0xe857cca5, 0xaff7b675, 0x92979fc5,
|
||||
0xe915e8db, 0xd475c16b, 0x93d5bbbb, 0xaeb5920b, 0x1c954e1b, 0x21f567ab, 0x66551d7b, 0x5b3534cb,
|
||||
0xd965a31a, 0xe4058aaa, 0xa3a5f07a, 0x9ec5d9ca, 0x2ce505da, 0x11852c6a, 0x562556ba, 0x6b457f0a,
|
||||
0x89f57f59, 0xb49556e9, 0xf3352c39, 0xce550589, 0x7c75d999, 0x4115f029, 0x06b58af9, 0x3bd5a349,
|
||||
0xb9853498, 0x84e51d28, 0xc34567f8, 0xfe254e48, 0x4c059258, 0x7165bbe8, 0x36c5c138, 0x0ba5e888,
|
||||
0x28d4c7df, 0x15b4ee6f, 0x521494bf, 0x6f74bd0f, 0xdd54611f, 0xe03448af, 0xa794327f, 0x9af41bcf,
|
||||
0x18a48c1e, 0x25c4a5ae, 0x6264df7e, 0x5f04f6ce, 0xed242ade, 0xd044036e, 0x97e479be, 0xaa84500e,
|
||||
0x4834505d, 0x755479ed, 0x32f4033d, 0x0f942a8d, 0xbdb4f69d, 0x80d4df2d, 0xc774a5fd, 0xfa148c4d,
|
||||
0x78441b9c, 0x4524322c, 0x028448fc, 0x3fe4614c, 0x8dc4bd5c, 0xb0a494ec, 0xf704ee3c, 0xca64c78c,
|
||||
},
|
||||
{
|
||||
0x00000000, 0xcb5cd3a5, 0x4dc8a10b, 0x869472ae, 0x9b914216, 0x50cd91b3, 0xd659e31d, 0x1d0530b8,
|
||||
0xec53826d, 0x270f51c8, 0xa19b2366, 0x6ac7f0c3, 0x77c2c07b, 0xbc9e13de, 0x3a0a6170, 0xf156b2d5,
|
||||
0x03d6029b, 0xc88ad13e, 0x4e1ea390, 0x85427035, 0x9847408d, 0x531b9328, 0xd58fe186, 0x1ed33223,
|
||||
0xef8580f6, 0x24d95353, 0xa24d21fd, 0x6911f258, 0x7414c2e0, 0xbf481145, 0x39dc63eb, 0xf280b04e,
|
||||
0x07ac0536, 0xccf0d693, 0x4a64a43d, 0x81387798, 0x9c3d4720, 0x57619485, 0xd1f5e62b, 0x1aa9358e,
|
||||
0xebff875b, 0x20a354fe, 0xa6372650, 0x6d6bf5f5, 0x706ec54d, 0xbb3216e8, 0x3da66446, 0xf6fab7e3,
|
||||
0x047a07ad, 0xcf26d408, 0x49b2a6a6, 0x82ee7503, 0x9feb45bb, 0x54b7961e, 0xd223e4b0, 0x197f3715,
|
||||
0xe82985c0, 0x23755665, 0xa5e124cb, 0x6ebdf76e, 0x73b8c7d6, 0xb8e41473, 0x3e7066dd, 0xf52cb578,
|
||||
0x0f580a6c, 0xc404d9c9, 0x4290ab67, 0x89cc78c2, 0x94c9487a, 0x5f959bdf, 0xd901e971, 0x125d3ad4,
|
||||
0xe30b8801, 0x28575ba4, 0xaec3290a, 0x659ffaaf, 0x789aca17, 0xb3c619b2, 0x35526b1c, 0xfe0eb8b9,
|
||||
0x0c8e08f7, 0xc7d2db52, 0x4146a9fc, 0x8a1a7a59, 0x971f4ae1, 0x5c439944, 0xdad7ebea, 0x118b384f,
|
||||
0xe0dd8a9a, 0x2b81593f, 0xad152b91, 0x6649f834, 0x7b4cc88c, 0xb0101b29, 0x36846987, 0xfdd8ba22,
|
||||
0x08f40f5a, 0xc3a8dcff, 0x453cae51, 0x8e607df4, 0x93654d4c, 0x58399ee9, 0xdeadec47, 0x15f13fe2,
|
||||
0xe4a78d37, 0x2ffb5e92, 0xa96f2c3c, 0x6233ff99, 0x7f36cf21, 0xb46a1c84, 0x32fe6e2a, 0xf9a2bd8f,
|
||||
0x0b220dc1, 0xc07ede64, 0x46eaacca, 0x8db67f6f, 0x90b34fd7, 0x5bef9c72, 0xdd7beedc, 0x16273d79,
|
||||
0xe7718fac, 0x2c2d5c09, 0xaab92ea7, 0x61e5fd02, 0x7ce0cdba, 0xb7bc1e1f, 0x31286cb1, 0xfa74bf14,
|
||||
0x1eb014d8, 0xd5ecc77d, 0x5378b5d3, 0x98246676, 0x852156ce, 0x4e7d856b, 0xc8e9f7c5, 0x03b52460,
|
||||
0xf2e396b5, 0x39bf4510, 0xbf2b37be, 0x7477e41b, 0x6972d4a3, 0xa22e0706, 0x24ba75a8, 0xefe6a60d,
|
||||
0x1d661643, 0xd63ac5e6, 0x50aeb748, 0x9bf264ed, 0x86f75455, 0x4dab87f0, 0xcb3ff55e, 0x006326fb,
|
||||
0xf135942e, 0x3a69478b, 0xbcfd3525, 0x77a1e680, 0x6aa4d638, 0xa1f8059d, 0x276c7733, 0xec30a496,
|
||||
0x191c11ee, 0xd240c24b, 0x54d4b0e5, 0x9f886340, 0x828d53f8, 0x49d1805d, 0xcf45f2f3, 0x04192156,
|
||||
0xf54f9383, 0x3e134026, 0xb8873288, 0x73dbe12d, 0x6eded195, 0xa5820230, 0x2316709e, 0xe84aa33b,
|
||||
0x1aca1375, 0xd196c0d0, 0x5702b27e, 0x9c5e61db, 0x815b5163, 0x4a0782c6, 0xcc93f068, 0x07cf23cd,
|
||||
0xf6999118, 0x3dc542bd, 0xbb513013, 0x700de3b6, 0x6d08d30e, 0xa65400ab, 0x20c07205, 0xeb9ca1a0,
|
||||
0x11e81eb4, 0xdab4cd11, 0x5c20bfbf, 0x977c6c1a, 0x8a795ca2, 0x41258f07, 0xc7b1fda9, 0x0ced2e0c,
|
||||
0xfdbb9cd9, 0x36e74f7c, 0xb0733dd2, 0x7b2fee77, 0x662adecf, 0xad760d6a, 0x2be27fc4, 0xe0beac61,
|
||||
0x123e1c2f, 0xd962cf8a, 0x5ff6bd24, 0x94aa6e81, 0x89af5e39, 0x42f38d9c, 0xc467ff32, 0x0f3b2c97,
|
||||
0xfe6d9e42, 0x35314de7, 0xb3a53f49, 0x78f9ecec, 0x65fcdc54, 0xaea00ff1, 0x28347d5f, 0xe368aefa,
|
||||
0x16441b82, 0xdd18c827, 0x5b8cba89, 0x90d0692c, 0x8dd55994, 0x46898a31, 0xc01df89f, 0x0b412b3a,
|
||||
0xfa1799ef, 0x314b4a4a, 0xb7df38e4, 0x7c83eb41, 0x6186dbf9, 0xaada085c, 0x2c4e7af2, 0xe712a957,
|
||||
0x15921919, 0xdececabc, 0x585ab812, 0x93066bb7, 0x8e035b0f, 0x455f88aa, 0xc3cbfa04, 0x089729a1,
|
||||
0xf9c19b74, 0x329d48d1, 0xb4093a7f, 0x7f55e9da, 0x6250d962, 0xa90c0ac7, 0x2f987869, 0xe4c4abcc,
|
||||
},
|
||||
{
|
||||
0x00000000, 0xa6770bb4, 0x979f1129, 0x31e81a9d, 0xf44f2413, 0x52382fa7, 0x63d0353a, 0xc5a73e8e,
|
||||
0x33ef4e67, 0x959845d3, 0xa4705f4e, 0x020754fa, 0xc7a06a74, 0x61d761c0, 0x503f7b5d, 0xf64870e9,
|
||||
0x67de9cce, 0xc1a9977a, 0xf0418de7, 0x56368653, 0x9391b8dd, 0x35e6b369, 0x040ea9f4, 0xa279a240,
|
||||
0x5431d2a9, 0xf246d91d, 0xc3aec380, 0x65d9c834, 0xa07ef6ba, 0x0609fd0e, 0x37e1e793, 0x9196ec27,
|
||||
0xcfbd399c, 0x69ca3228, 0x582228b5, 0xfe552301, 0x3bf21d8f, 0x9d85163b, 0xac6d0ca6, 0x0a1a0712,
|
||||
0xfc5277fb, 0x5a257c4f, 0x6bcd66d2, 0xcdba6d66, 0x081d53e8, 0xae6a585c, 0x9f8242c1, 0x39f54975,
|
||||
0xa863a552, 0x0e14aee6, 0x3ffcb47b, 0x998bbfcf, 0x5c2c8141, 0xfa5b8af5, 0xcbb39068, 0x6dc49bdc,
|
||||
0x9b8ceb35, 0x3dfbe081, 0x0c13fa1c, 0xaa64f1a8, 0x6fc3cf26, 0xc9b4c492, 0xf85cde0f, 0x5e2bd5bb,
|
||||
0x440b7579, 0xe27c7ecd, 0xd3946450, 0x75e36fe4, 0xb044516a, 0x16335ade, 0x27db4043, 0x81ac4bf7,
|
||||
0x77e43b1e, 0xd19330aa, 0xe07b2a37, 0x460c2183, 0x83ab1f0d, 0x25dc14b9, 0x14340e24, 0xb2430590,
|
||||
0x23d5e9b7, 0x85a2e203, 0xb44af89e, 0x123df32a, 0xd79acda4, 0x71edc610, 0x4005dc8d, 0xe672d739,
|
||||
0x103aa7d0, 0xb64dac64, 0x87a5b6f9, 0x21d2bd4d, 0xe47583c3, 0x42028877, 0x73ea92ea, 0xd59d995e,
|
||||
0x8bb64ce5, 0x2dc14751, 0x1c295dcc, 0xba5e5678, 0x7ff968f6, 0xd98e6342, 0xe86679df, 0x4e11726b,
|
||||
0xb8590282, 0x1e2e0936, 0x2fc613ab, 0x89b1181f, 0x4c162691, 0xea612d25, 0xdb8937b8, 0x7dfe3c0c,
|
||||
0xec68d02b, 0x4a1fdb9f, 0x7bf7c102, 0xdd80cab6, 0x1827f438, 0xbe50ff8c, 0x8fb8e511, 0x29cfeea5,
|
||||
0xdf879e4c, 0x79f095f8, 0x48188f65, 0xee6f84d1, 0x2bc8ba5f, 0x8dbfb1eb, 0xbc57ab76, 0x1a20a0c2,
|
||||
0x8816eaf2, 0x2e61e146, 0x1f89fbdb, 0xb9fef06f, 0x7c59cee1, 0xda2ec555, 0xebc6dfc8, 0x4db1d47c,
|
||||
0xbbf9a495, 0x1d8eaf21, 0x2c66b5bc, 0x8a11be08, 0x4fb68086, 0xe9c18b32, 0xd82991af, 0x7e5e9a1b,
|
||||
0xefc8763c, 0x49bf7d88, 0x78576715, 0xde206ca1, 0x1b87522f, 0xbdf0599b, 0x8c184306, 0x2a6f48b2,
|
||||
0xdc27385b, 0x7a5033ef, 0x4bb82972, 0xedcf22c6, 0x28681c48, 0x8e1f17fc, 0xbff70d61, 0x198006d5,
|
||||
0x47abd36e, 0xe1dcd8da, 0xd034c247, 0x7643c9f3, 0xb3e4f77d, 0x1593fcc9, 0x247be654, 0x820cede0,
|
||||
0x74449d09, 0xd23396bd, 0xe3db8c20, 0x45ac8794, 0x800bb91a, 0x267cb2ae, 0x1794a833, 0xb1e3a387,
|
||||
0x20754fa0, 0x86024414, 0xb7ea5e89, 0x119d553d, 0xd43a6bb3, 0x724d6007, 0x43a57a9a, 0xe5d2712e,
|
||||
0x139a01c7, 0xb5ed0a73, 0x840510ee, 0x22721b5a, 0xe7d525d4, 0x41a22e60, 0x704a34fd, 0xd63d3f49,
|
||||
0xcc1d9f8b, 0x6a6a943f, 0x5b828ea2, 0xfdf58516, 0x3852bb98, 0x9e25b02c, 0xafcdaab1, 0x09baa105,
|
||||
0xfff2d1ec, 0x5985da58, 0x686dc0c5, 0xce1acb71, 0x0bbdf5ff, 0xadcafe4b, 0x9c22e4d6, 0x3a55ef62,
|
||||
0xabc30345, 0x0db408f1, 0x3c5c126c, 0x9a2b19d8, 0x5f8c2756, 0xf9fb2ce2, 0xc813367f, 0x6e643dcb,
|
||||
0x982c4d22, 0x3e5b4696, 0x0fb35c0b, 0xa9c457bf, 0x6c636931, 0xca146285, 0xfbfc7818, 0x5d8b73ac,
|
||||
0x03a0a617, 0xa5d7ada3, 0x943fb73e, 0x3248bc8a, 0xf7ef8204, 0x519889b0, 0x6070932d, 0xc6079899,
|
||||
0x304fe870, 0x9638e3c4, 0xa7d0f959, 0x01a7f2ed, 0xc400cc63, 0x6277c7d7, 0x539fdd4a, 0xf5e8d6fe,
|
||||
0x647e3ad9, 0xc209316d, 0xf3e12bf0, 0x55962044, 0x90311eca, 0x3646157e, 0x07ae0fe3, 0xa1d90457,
|
||||
0x579174be, 0xf1e67f0a, 0xc00e6597, 0x66796e23, 0xa3de50ad, 0x05a95b19, 0x34414184, 0x92364a30,
|
||||
},
|
||||
{
|
||||
0x00000000, 0xccaa009e, 0x4225077d, 0x8e8f07e3, 0x844a0efa, 0x48e00e64, 0xc66f0987, 0x0ac50919,
|
||||
0xd3e51bb5, 0x1f4f1b2b, 0x91c01cc8, 0x5d6a1c56, 0x57af154f, 0x9b0515d1, 0x158a1232, 0xd92012ac,
|
||||
0x7cbb312b, 0xb01131b5, 0x3e9e3656, 0xf23436c8, 0xf8f13fd1, 0x345b3f4f, 0xbad438ac, 0x767e3832,
|
||||
0xaf5e2a9e, 0x63f42a00, 0xed7b2de3, 0x21d12d7d, 0x2b142464, 0xe7be24fa, 0x69312319, 0xa59b2387,
|
||||
0xf9766256, 0x35dc62c8, 0xbb53652b, 0x77f965b5, 0x7d3c6cac, 0xb1966c32, 0x3f196bd1, 0xf3b36b4f,
|
||||
0x2a9379e3, 0xe639797d, 0x68b67e9e, 0xa41c7e00, 0xaed97719, 0x62737787, 0xecfc7064, 0x205670fa,
|
||||
0x85cd537d, 0x496753e3, 0xc7e85400, 0x0b42549e, 0x01875d87, 0xcd2d5d19, 0x43a25afa, 0x8f085a64,
|
||||
0x562848c8, 0x9a824856, 0x140d4fb5, 0xd8a74f2b, 0xd2624632, 0x1ec846ac, 0x9047414f, 0x5ced41d1,
|
||||
0x299dc2ed, 0xe537c273, 0x6bb8c590, 0xa712c50e, 0xadd7cc17, 0x617dcc89, 0xeff2cb6a, 0x2358cbf4,
|
||||
0xfa78d958, 0x36d2d9c6, 0xb85dde25, 0x74f7debb, 0x7e32d7a2, 0xb298d73c, 0x3c17d0df, 0xf0bdd041,
|
||||
0x5526f3c6, 0x998cf358, 0x1703f4bb, 0xdba9f425, 0xd16cfd3c, 0x1dc6fda2, 0x9349fa41, 0x5fe3fadf,
|
||||
0x86c3e873, 0x4a69e8ed, 0xc4e6ef0e, 0x084cef90, 0x0289e689, 0xce23e617, 0x40ace1f4, 0x8c06e16a,
|
||||
0xd0eba0bb, 0x1c41a025, 0x92cea7c6, 0x5e64a758, 0x54a1ae41, 0x980baedf, 0x1684a93c, 0xda2ea9a2,
|
||||
0x030ebb0e, 0xcfa4bb90, 0x412bbc73, 0x8d81bced, 0x8744b5f4, 0x4beeb56a, 0xc561b289, 0x09cbb217,
|
||||
0xac509190, 0x60fa910e, 0xee7596ed, 0x22df9673, 0x281a9f6a, 0xe4b09ff4, 0x6a3f9817, 0xa6959889,
|
||||
0x7fb58a25, 0xb31f8abb, 0x3d908d58, 0xf13a8dc6, 0xfbff84df, 0x37558441, 0xb9da83a2, 0x7570833c,
|
||||
0x533b85da, 0x9f918544, 0x111e82a7, 0xddb48239, 0xd7718b20, 0x1bdb8bbe, 0x95548c5d, 0x59fe8cc3,
|
||||
0x80de9e6f, 0x4c749ef1, 0xc2fb9912, 0x0e51998c, 0x04949095, 0xc83e900b, 0x46b197e8, 0x8a1b9776,
|
||||
0x2f80b4f1, 0xe32ab46f, 0x6da5b38c, 0xa10fb312, 0xabcaba0b, 0x6760ba95, 0xe9efbd76, 0x2545bde8,
|
||||
0xfc65af44, 0x30cfafda, 0xbe40a839, 0x72eaa8a7, 0x782fa1be, 0xb485a120, 0x3a0aa6c3, 0xf6a0a65d,
|
||||
0xaa4de78c, 0x66e7e712, 0xe868e0f1, 0x24c2e06f, 0x2e07e976, 0xe2ade9e8, 0x6c22ee0b, 0xa088ee95,
|
||||
0x79a8fc39, 0xb502fca7, 0x3b8dfb44, 0xf727fbda, 0xfde2f2c3, 0x3148f25d, 0xbfc7f5be, 0x736df520,
|
||||
0xd6f6d6a7, 0x1a5cd639, 0x94d3d1da, 0x5879d144, 0x52bcd85d, 0x9e16d8c3, 0x1099df20, 0xdc33dfbe,
|
||||
0x0513cd12, 0xc9b9cd8c, 0x4736ca6f, 0x8b9ccaf1, 0x8159c3e8, 0x4df3c376, 0xc37cc495, 0x0fd6c40b,
|
||||
0x7aa64737, 0xb60c47a9, 0x3883404a, 0xf42940d4, 0xfeec49cd, 0x32464953, 0xbcc94eb0, 0x70634e2e,
|
||||
0xa9435c82, 0x65e95c1c, 0xeb665bff, 0x27cc5b61, 0x2d095278, 0xe1a352e6, 0x6f2c5505, 0xa386559b,
|
||||
0x061d761c, 0xcab77682, 0x44387161, 0x889271ff, 0x825778e6, 0x4efd7878, 0xc0727f9b, 0x0cd87f05,
|
||||
0xd5f86da9, 0x19526d37, 0x97dd6ad4, 0x5b776a4a, 0x51b26353, 0x9d1863cd, 0x1397642e, 0xdf3d64b0,
|
||||
0x83d02561, 0x4f7a25ff, 0xc1f5221c, 0x0d5f2282, 0x079a2b9b, 0xcb302b05, 0x45bf2ce6, 0x89152c78,
|
||||
0x50353ed4, 0x9c9f3e4a, 0x121039a9, 0xdeba3937, 0xd47f302e, 0x18d530b0, 0x965a3753, 0x5af037cd,
|
||||
0xff6b144a, 0x33c114d4, 0xbd4e1337, 0x71e413a9, 0x7b211ab0, 0xb78b1a2e, 0x39041dcd, 0xf5ae1d53,
|
||||
0x2c8e0fff, 0xe0240f61, 0x6eab0882, 0xa201081c, 0xa8c40105, 0x646e019b, 0xeae10678, 0x264b06e6,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
@(optimization_mode="speed")
|
||||
crc32 :: proc(data: []byte, seed := u32(0)) -> u32 {
|
||||
result := ~u32(seed);
|
||||
#no_bounds_check for b in data {
|
||||
result = result>>8 ~ _crc32_table[(result ~ u32(b)) & 0xff];
|
||||
}
|
||||
return ~result;
|
||||
}
|
||||
|
||||
|
||||
@private _crc32_table := [256]u32{
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
|
||||
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
|
||||
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
|
||||
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
|
||||
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
||||
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
|
||||
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
|
||||
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
|
||||
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
|
||||
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
|
||||
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
|
||||
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
|
||||
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
|
||||
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
|
||||
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
|
||||
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
|
||||
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
|
||||
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
|
||||
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
|
||||
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
|
||||
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
|
||||
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
|
||||
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
|
||||
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
|
||||
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
|
||||
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
|
||||
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
||||
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
|
||||
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
|
||||
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
|
||||
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
|
||||
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
|
||||
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
|
||||
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
|
||||
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
|
||||
};
|
||||
*/
|
||||
+49
-7
@@ -1,17 +1,52 @@
|
||||
package hash
|
||||
|
||||
import "core:mem"
|
||||
import "core:intrinsics"
|
||||
|
||||
@(optimization_mode="speed")
|
||||
adler32 :: proc(data: []byte, seed := u32(1)) -> u32 #no_bounds_check {
|
||||
|
||||
adler32 :: proc(data: []byte, seed := u32(1)) -> u32 {
|
||||
ADLER_CONST :: 65521;
|
||||
a, b: u32 = seed & 0xFFFF, seed >> 16;
|
||||
for x in data {
|
||||
a = (a + u32(x)) % ADLER_CONST;
|
||||
b = (b + a) % ADLER_CONST;
|
||||
|
||||
buffer := raw_data(data);
|
||||
a, b: u64 = u64(seed) & 0xFFFF, u64(seed) >> 16;
|
||||
buf := data[:];
|
||||
|
||||
for len(buf) != 0 && uintptr(buffer) & 7 != 0 {
|
||||
a = (a + u64(buf[0]));
|
||||
b = (b + a);
|
||||
buffer = intrinsics.ptr_offset(buffer, 1);
|
||||
buf = buf[1:];
|
||||
}
|
||||
return (b << 16) | a;
|
||||
|
||||
for len(buf) > 7 {
|
||||
count := min(len(buf), 5552);
|
||||
for count > 7 {
|
||||
a += u64(buf[0]); b += a;
|
||||
a += u64(buf[1]); b += a;
|
||||
a += u64(buf[2]); b += a;
|
||||
a += u64(buf[3]); b += a;
|
||||
a += u64(buf[4]); b += a;
|
||||
a += u64(buf[5]); b += a;
|
||||
a += u64(buf[6]); b += a;
|
||||
a += u64(buf[7]); b += a;
|
||||
|
||||
buf = buf[8:];
|
||||
count -= 8;
|
||||
}
|
||||
a %= ADLER_CONST;
|
||||
b %= ADLER_CONST;
|
||||
}
|
||||
|
||||
for len(buf) != 0 {
|
||||
a = (a + u64(buf[0])) % ADLER_CONST;
|
||||
b = (b + a) % ADLER_CONST;
|
||||
buf = buf[1:];
|
||||
}
|
||||
return (u32(b) << 16) | u32(a);
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
djb2 :: proc(data: []byte) -> u32 {
|
||||
hash: u32 = 5381;
|
||||
for b in data {
|
||||
@@ -20,6 +55,7 @@ djb2 :: proc(data: []byte) -> u32 {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
fnv32 :: proc(data: []byte) -> u32 {
|
||||
h: u32 = 0x811c9dc5;
|
||||
for b in data {
|
||||
@@ -28,6 +64,7 @@ fnv32 :: proc(data: []byte) -> u32 {
|
||||
return h;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
fnv64 :: proc(data: []byte) -> u64 {
|
||||
h: u64 = 0xcbf29ce484222325;
|
||||
for b in data {
|
||||
@@ -36,6 +73,7 @@ fnv64 :: proc(data: []byte) -> u64 {
|
||||
return h;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
fnv32a :: proc(data: []byte) -> u32 {
|
||||
h: u32 = 0x811c9dc5;
|
||||
for b in data {
|
||||
@@ -44,6 +82,7 @@ fnv32a :: proc(data: []byte) -> u32 {
|
||||
return h;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
fnv64a :: proc(data: []byte) -> u64 {
|
||||
h: u64 = 0xcbf29ce484222325;
|
||||
for b in data {
|
||||
@@ -52,6 +91,7 @@ fnv64a :: proc(data: []byte) -> u64 {
|
||||
return h;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
jenkins :: proc(data: []byte) -> u32 {
|
||||
hash: u32 = 0;
|
||||
for b in data {
|
||||
@@ -65,6 +105,7 @@ jenkins :: proc(data: []byte) -> u32 {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
murmur32 :: proc(data: []byte) -> u32 {
|
||||
c1_32: u32 : 0xcc9e2d51;
|
||||
c2_32: u32 : 0x1b873593;
|
||||
@@ -114,6 +155,7 @@ murmur32 :: proc(data: []byte) -> u32 {
|
||||
return h1;
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
murmur64 :: proc(data: []byte) -> u64 {
|
||||
SEED :: 0x9747b28c;
|
||||
|
||||
@@ -219,7 +261,7 @@ murmur64 :: proc(data: []byte) -> u64 {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
sdbm :: proc(data: []byte) -> u32 {
|
||||
hash: u32 = 0;
|
||||
for b in data {
|
||||
|
||||
+56
-46
@@ -1,21 +1,32 @@
|
||||
package image
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation, optimization.
|
||||
Ginger Bill: Cosmetic changes.
|
||||
*/
|
||||
|
||||
import "core:bytes"
|
||||
import "core:mem"
|
||||
|
||||
Image :: struct {
|
||||
width: int,
|
||||
height: int,
|
||||
channels: int,
|
||||
depth: u8,
|
||||
pixels: bytes.Buffer,
|
||||
width: int,
|
||||
height: int,
|
||||
channels: int,
|
||||
depth: int,
|
||||
pixels: bytes.Buffer,
|
||||
/*
|
||||
Some image loaders/writers can return/take an optional background color.
|
||||
For convenience, we return them as u16 so we don't need to switch on the type
|
||||
in our viewer, and can just test against nil.
|
||||
*/
|
||||
background: Maybe([3]u16),
|
||||
sidecar: any,
|
||||
background: Maybe([3]u16),
|
||||
|
||||
metadata_ptr: rawptr,
|
||||
metadata_type: typeid,
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -64,10 +75,10 @@ Image_Option:
|
||||
If the image has an alpha channel, drop it.
|
||||
You may want to use `.alpha_premultiply` in this case.
|
||||
|
||||
NOTE: For PNG, this also skips handling of the tRNS chunk, if present,
|
||||
unless you select `alpha_premultiply`.
|
||||
In this case it'll premultiply the specified pixels in question only,
|
||||
as the others are implicitly fully opaque.
|
||||
NOTE: For PNG, this also skips handling of the tRNS chunk, if present,
|
||||
unless you select `alpha_premultiply`.
|
||||
In this case it'll premultiply the specified pixels in question only,
|
||||
as the others are implicitly fully opaque.
|
||||
|
||||
`.alpha_premultiply`
|
||||
If the image has an alpha channel, returns image data as follows:
|
||||
@@ -127,7 +138,6 @@ Error :: enum {
|
||||
*/
|
||||
|
||||
compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
|
||||
|
||||
size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height;
|
||||
return;
|
||||
}
|
||||
@@ -144,7 +154,6 @@ Channel :: enum u8 {
|
||||
}
|
||||
|
||||
return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok: bool) {
|
||||
|
||||
ok = false;
|
||||
t: bytes.Buffer;
|
||||
|
||||
@@ -159,46 +168,47 @@ return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
switch(img.depth) {
|
||||
case 8:
|
||||
buffer_size := compute_buffer_size(img.width, img.height, 1, 8);
|
||||
t = bytes.Buffer{};
|
||||
resize(&t.buf, buffer_size);
|
||||
switch img.depth {
|
||||
case 8:
|
||||
buffer_size := compute_buffer_size(img.width, img.height, 1, 8);
|
||||
t = bytes.Buffer{};
|
||||
resize(&t.buf, buffer_size);
|
||||
|
||||
i := bytes.buffer_to_bytes(&img.pixels);
|
||||
o := bytes.buffer_to_bytes(&t);
|
||||
i := bytes.buffer_to_bytes(&img.pixels);
|
||||
o := bytes.buffer_to_bytes(&t);
|
||||
|
||||
for len(i) > 0 {
|
||||
o[0] = i[idx];
|
||||
i = i[img.channels:];
|
||||
o = o[1:];
|
||||
}
|
||||
case 16:
|
||||
buffer_size := compute_buffer_size(img.width, img.height, 2, 8);
|
||||
t = bytes.Buffer{};
|
||||
resize(&t.buf, buffer_size);
|
||||
for len(i) > 0 {
|
||||
o[0] = i[idx];
|
||||
i = i[img.channels:];
|
||||
o = o[1:];
|
||||
}
|
||||
case 16:
|
||||
buffer_size := compute_buffer_size(img.width, img.height, 2, 8);
|
||||
t = bytes.Buffer{};
|
||||
resize(&t.buf, buffer_size);
|
||||
|
||||
i := mem.slice_data_cast([]u16, img.pixels.buf[:]);
|
||||
o := mem.slice_data_cast([]u16, t.buf[:]);
|
||||
i := mem.slice_data_cast([]u16, img.pixels.buf[:]);
|
||||
o := mem.slice_data_cast([]u16, t.buf[:]);
|
||||
|
||||
for len(i) > 0 {
|
||||
o[0] = i[idx];
|
||||
i = i[img.channels:];
|
||||
o = o[1:];
|
||||
}
|
||||
case 1, 2, 4:
|
||||
// We shouldn't see this case, as the loader already turns these into 8-bit.
|
||||
return {}, false;
|
||||
for len(i) > 0 {
|
||||
o[0] = i[idx];
|
||||
i = i[img.channels:];
|
||||
o = o[1:];
|
||||
}
|
||||
case 1, 2, 4:
|
||||
// We shouldn't see this case, as the loader already turns these into 8-bit.
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
res = new(Image);
|
||||
res.width = img.width;
|
||||
res.height = img.height;
|
||||
res.channels = 1;
|
||||
res.depth = img.depth;
|
||||
res.pixels = t;
|
||||
res.background = img.background;
|
||||
res.sidecar = img.sidecar;
|
||||
res.width = img.width;
|
||||
res.height = img.height;
|
||||
res.channels = 1;
|
||||
res.depth = img.depth;
|
||||
res.pixels = t;
|
||||
res.background = img.background;
|
||||
res.metadata_ptr = img.metadata_ptr;
|
||||
res.metadata_type = img.metadata_type;
|
||||
|
||||
return res, true;
|
||||
}
|
||||
|
||||
+113
-86
@@ -1,9 +1,20 @@
|
||||
//+ignore
|
||||
package png
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
Ginger Bill: Cosmetic changes.
|
||||
|
||||
An example of how to use `load`.
|
||||
*/
|
||||
|
||||
import "core:compress"
|
||||
import "core:image"
|
||||
import "core:image/png"
|
||||
// import "core:image/png"
|
||||
import "core:bytes"
|
||||
import "core:fmt"
|
||||
|
||||
@@ -12,109 +23,125 @@ import "core:mem"
|
||||
import "core:os"
|
||||
|
||||
main :: proc() {
|
||||
track := mem.Tracking_Allocator{};
|
||||
mem.tracking_allocator_init(&track, context.allocator);
|
||||
|
||||
context.allocator = mem.tracking_allocator(&track);
|
||||
|
||||
demo();
|
||||
|
||||
if len(track.allocation_map) > 0 {
|
||||
fmt.println("Leaks:");
|
||||
for _, v in track.allocation_map {
|
||||
fmt.printf("\t%v\n\n", v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
demo :: proc() {
|
||||
file: string;
|
||||
|
||||
options := image.Options{};
|
||||
options := image.Options{}; // {.return_metadata};
|
||||
err: compress.Error;
|
||||
img: ^image.Image;
|
||||
|
||||
file = "../../../misc/logo-slim.png";
|
||||
|
||||
img, err = png.load(file, options);
|
||||
defer png.destroy(img);
|
||||
img, err = load(file, options);
|
||||
defer destroy(img);
|
||||
|
||||
if err != nil {
|
||||
fmt.printf("Trying to read PNG file %v returned %v\n", file, err);
|
||||
} else {
|
||||
v: png.Info;
|
||||
ok: bool;
|
||||
v: ^Info;
|
||||
|
||||
fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth);
|
||||
if img.metadata_ptr != nil && img.metadata_type == Info {
|
||||
v = (^Info)(img.metadata_ptr);
|
||||
|
||||
if v, ok = img.sidecar.(png.Info); ok {
|
||||
// Handle ancillary chunks as you wish.
|
||||
// We provide helper functions for a few types.
|
||||
for c in v.chunks {
|
||||
#partial switch (c.header.type) {
|
||||
case .tIME:
|
||||
t, _ := png.core_time(c);
|
||||
fmt.printf("[tIME]: %v\n", t);
|
||||
case .gAMA:
|
||||
fmt.printf("[gAMA]: %v\n", png.gamma(c));
|
||||
case .pHYs:
|
||||
phys := png.phys(c);
|
||||
if phys.unit == .Meter {
|
||||
xm := f32(img.width) / f32(phys.ppu_x);
|
||||
ym := f32(img.height) / f32(phys.ppu_y);
|
||||
dpi_x, dpi_y := png.phys_to_dpi(phys);
|
||||
fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y);
|
||||
fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y);
|
||||
fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym);
|
||||
#partial switch c.header.type {
|
||||
case .tIME:
|
||||
t, _ := core_time(c);
|
||||
fmt.printf("[tIME]: %v\n", t);
|
||||
case .gAMA:
|
||||
fmt.printf("[gAMA]: %v\n", gamma(c));
|
||||
case .pHYs:
|
||||
phys := phys(c);
|
||||
if phys.unit == .Meter {
|
||||
xm := f32(img.width) / f32(phys.ppu_x);
|
||||
ym := f32(img.height) / f32(phys.ppu_y);
|
||||
dpi_x, dpi_y := phys_to_dpi(phys);
|
||||
fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y);
|
||||
fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y);
|
||||
fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym);
|
||||
} else {
|
||||
fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y);
|
||||
}
|
||||
case .iTXt, .zTXt, .tEXt:
|
||||
res, ok_text := text(c);
|
||||
if ok_text {
|
||||
if c.header.type == .iTXt {
|
||||
fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text);
|
||||
} else {
|
||||
fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y);
|
||||
fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text);
|
||||
}
|
||||
case .iTXt, .zTXt, .tEXt:
|
||||
res, ok_text := png.text(c);
|
||||
if ok_text {
|
||||
if c.header.type == .iTXt {
|
||||
fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text);
|
||||
} else {
|
||||
fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text);
|
||||
}
|
||||
}
|
||||
defer png.text_destroy(res);
|
||||
case .bKGD:
|
||||
fmt.printf("[bKGD] %v\n", img.background);
|
||||
case .eXIf:
|
||||
res, ok_exif := png.exif(c);
|
||||
if ok_exif {
|
||||
/*
|
||||
Other than checking the signature and byte order, we don't handle Exif data.
|
||||
If you wish to interpret it, pass it to an Exif parser.
|
||||
*/
|
||||
fmt.printf("[eXIf] %v\n", res);
|
||||
}
|
||||
case .PLTE:
|
||||
plte, plte_ok := png.plte(c);
|
||||
if plte_ok {
|
||||
fmt.printf("[PLTE] %v\n", plte);
|
||||
} else {
|
||||
fmt.printf("[PLTE] Error\n");
|
||||
}
|
||||
case .hIST:
|
||||
res, ok_hist := png.hist(c);
|
||||
if ok_hist {
|
||||
fmt.printf("[hIST] %v\n", res);
|
||||
}
|
||||
case .cHRM:
|
||||
res, ok_chrm := png.chrm(c);
|
||||
if ok_chrm {
|
||||
fmt.printf("[cHRM] %v\n", res);
|
||||
}
|
||||
case .sPLT:
|
||||
res, ok_splt := png.splt(c);
|
||||
if ok_splt {
|
||||
fmt.printf("[sPLT] %v\n", res);
|
||||
}
|
||||
png.splt_destroy(res);
|
||||
case .sBIT:
|
||||
if res, ok_sbit := png.sbit(c); ok_sbit {
|
||||
fmt.printf("[sBIT] %v\n", res);
|
||||
}
|
||||
case .iCCP:
|
||||
res, ok_iccp := png.iccp(c);
|
||||
if ok_iccp {
|
||||
fmt.printf("[iCCP] %v\n", res);
|
||||
}
|
||||
png.iccp_destroy(res);
|
||||
case .sRGB:
|
||||
if res, ok_srgb := png.srgb(c); ok_srgb {
|
||||
fmt.printf("[sRGB] Rendering intent: %v\n", res);
|
||||
}
|
||||
case:
|
||||
type := c.header.type;
|
||||
name := png.chunk_type_to_name(&type);
|
||||
fmt.printf("[%v]: %v\n", name, c.data);
|
||||
}
|
||||
defer text_destroy(res);
|
||||
case .bKGD:
|
||||
fmt.printf("[bKGD] %v\n", img.background);
|
||||
case .eXIf:
|
||||
res, ok_exif := exif(c);
|
||||
if ok_exif {
|
||||
/*
|
||||
Other than checking the signature and byte order, we don't handle Exif data.
|
||||
If you wish to interpret it, pass it to an Exif parser.
|
||||
*/
|
||||
fmt.printf("[eXIf] %v\n", res);
|
||||
}
|
||||
case .PLTE:
|
||||
plte, plte_ok := plte(c);
|
||||
if plte_ok {
|
||||
fmt.printf("[PLTE] %v\n", plte);
|
||||
} else {
|
||||
fmt.printf("[PLTE] Error\n");
|
||||
}
|
||||
case .hIST:
|
||||
res, ok_hist := hist(c);
|
||||
if ok_hist {
|
||||
fmt.printf("[hIST] %v\n", res);
|
||||
}
|
||||
case .cHRM:
|
||||
res, ok_chrm := chrm(c);
|
||||
if ok_chrm {
|
||||
fmt.printf("[cHRM] %v\n", res);
|
||||
}
|
||||
case .sPLT:
|
||||
res, ok_splt := splt(c);
|
||||
if ok_splt {
|
||||
fmt.printf("[sPLT] %v\n", res);
|
||||
}
|
||||
splt_destroy(res);
|
||||
case .sBIT:
|
||||
if res, ok_sbit := sbit(c); ok_sbit {
|
||||
fmt.printf("[sBIT] %v\n", res);
|
||||
}
|
||||
case .iCCP:
|
||||
res, ok_iccp := iccp(c);
|
||||
if ok_iccp {
|
||||
fmt.printf("[iCCP] %v\n", res);
|
||||
}
|
||||
iccp_destroy(res);
|
||||
case .sRGB:
|
||||
if res, ok_srgb := srgb(c); ok_srgb {
|
||||
fmt.printf("[sRGB] Rendering intent: %v\n", res);
|
||||
}
|
||||
case:
|
||||
type := c.header.type;
|
||||
name := chunk_type_to_name(&type);
|
||||
fmt.printf("[%v]: %v\n", name, c.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,7 +222,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 << depth -1)),
|
||||
fmt.tprintf("P6\n%v %v\n%v\n", width, height, (1 << uint(depth) - 1)),
|
||||
);
|
||||
|
||||
if channels == 3 {
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
package png
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
Ginger Bill: Cosmetic changes.
|
||||
|
||||
These are a few useful utility functions to work with PNG images.
|
||||
*/
|
||||
|
||||
import "core:image"
|
||||
import "core:compress/zlib"
|
||||
import coretime "core:time"
|
||||
@@ -7,10 +18,6 @@ import "core:strings"
|
||||
import "core:bytes"
|
||||
import "core:mem"
|
||||
|
||||
/*
|
||||
These are a few useful utility functions to work with PNG images.
|
||||
*/
|
||||
|
||||
/*
|
||||
Cleanup of image-specific data.
|
||||
There are other helpers for cleanup of PNG-specific data.
|
||||
@@ -27,6 +34,8 @@ destroy :: proc(img: ^Image) {
|
||||
}
|
||||
|
||||
bytes.buffer_destroy(&img.pixels);
|
||||
// Clean up Info.
|
||||
free(img.metadata_ptr);
|
||||
|
||||
/*
|
||||
We don't need to do anything for the individual chunks.
|
||||
@@ -81,7 +90,7 @@ core_time :: proc(c: Chunk) -> (t: coretime.Time, ok: bool) {
|
||||
}
|
||||
|
||||
text :: proc(c: Chunk) -> (res: Text, ok: bool) {
|
||||
#partial switch c.header.type {
|
||||
#partial switch c.header.type {
|
||||
case .tEXt:
|
||||
ok = true;
|
||||
|
||||
|
||||
+304
-326
@@ -1,5 +1,14 @@
|
||||
package png
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
Ginger Bill: Cosmetic changes.
|
||||
*/
|
||||
|
||||
import "core:compress"
|
||||
import "core:compress/zlib"
|
||||
import "core:image"
|
||||
@@ -115,11 +124,11 @@ Interlace_Method :: enum u8 {
|
||||
}
|
||||
|
||||
Row_Filter :: enum u8 {
|
||||
None = 0,
|
||||
Sub = 1,
|
||||
Up = 2,
|
||||
Average = 3,
|
||||
Paeth = 4,
|
||||
None = 0,
|
||||
Sub = 1,
|
||||
Up = 2,
|
||||
Average = 3,
|
||||
Paeth = 4,
|
||||
};
|
||||
|
||||
PLTE_Entry :: [3]u8;
|
||||
@@ -166,18 +175,18 @@ CIE_1931 :: struct #packed {
|
||||
}
|
||||
|
||||
cHRM_Raw :: struct #packed {
|
||||
w: CIE_1931_Raw,
|
||||
r: CIE_1931_Raw,
|
||||
g: CIE_1931_Raw,
|
||||
b: CIE_1931_Raw,
|
||||
w: CIE_1931_Raw,
|
||||
r: CIE_1931_Raw,
|
||||
g: CIE_1931_Raw,
|
||||
b: CIE_1931_Raw,
|
||||
}
|
||||
#assert(size_of(cHRM_Raw) == 32);
|
||||
|
||||
cHRM :: struct #packed {
|
||||
w: CIE_1931,
|
||||
r: CIE_1931,
|
||||
g: CIE_1931,
|
||||
b: CIE_1931,
|
||||
w: CIE_1931,
|
||||
r: CIE_1931,
|
||||
g: CIE_1931,
|
||||
b: CIE_1931,
|
||||
}
|
||||
#assert(size_of(cHRM) == 32);
|
||||
|
||||
@@ -236,27 +245,22 @@ ADAM7_Y_SPACING := []int{ 8,8,8,4,4,2,2 };
|
||||
|
||||
// Implementation starts here
|
||||
|
||||
read_chunk :: proc(ctx: ^compress.Context) -> (Chunk, Error) {
|
||||
|
||||
chunk := Chunk{};
|
||||
|
||||
read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) {
|
||||
ch, e := compress.read_data(ctx, Chunk_Header);
|
||||
if e != .None {
|
||||
return {}, E_General.Stream_Too_Short;
|
||||
}
|
||||
chunk.header = ch;
|
||||
|
||||
data := make([]u8, ch.length, context.temp_allocator);
|
||||
_, e2 := ctx.input->impl_read(data);
|
||||
if e2 != .None {
|
||||
chunk.data, e = compress.read_slice(ctx, int(ch.length));
|
||||
if e != .None {
|
||||
return {}, E_General.Stream_Too_Short;
|
||||
}
|
||||
chunk.data = data;
|
||||
|
||||
// Compute CRC over chunk type + data
|
||||
type := (^[4]byte)(&ch.type)^;
|
||||
computed_crc := hash.crc32(type[:]);
|
||||
computed_crc = hash.crc32(data, computed_crc);
|
||||
computed_crc = hash.crc32(chunk.data, computed_crc);
|
||||
|
||||
crc, e3 := compress.read_data(ctx, u32be);
|
||||
if e3 != .None {
|
||||
@@ -270,8 +274,7 @@ read_chunk :: proc(ctx: ^compress.Context) -> (Chunk, Error) {
|
||||
return chunk, nil;
|
||||
}
|
||||
|
||||
read_header :: proc(ctx: ^compress.Context) -> (IHDR, Error) {
|
||||
|
||||
read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
|
||||
c, e := read_chunk(ctx);
|
||||
if e != nil {
|
||||
return {}, e;
|
||||
@@ -297,48 +300,48 @@ read_header :: proc(ctx: ^compress.Context) -> (IHDR, Error) {
|
||||
|
||||
}
|
||||
|
||||
switch (transmute(u8)color_type) {
|
||||
case 0:
|
||||
/*
|
||||
Grayscale.
|
||||
Allowed bit depths: 1, 2, 4, 8 and 16.
|
||||
*/
|
||||
allowed := false;
|
||||
for i in ([]u8{1, 2, 4, 8, 16}) {
|
||||
if bit_depth == i {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
switch transmute(u8)color_type {
|
||||
case 0:
|
||||
/*
|
||||
Grayscale.
|
||||
Allowed bit depths: 1, 2, 4, 8 and 16.
|
||||
*/
|
||||
allowed := false;
|
||||
for i in ([]u8{1, 2, 4, 8, 16}) {
|
||||
if bit_depth == i {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
if !allowed {
|
||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo;
|
||||
}
|
||||
case 2, 4, 6:
|
||||
/*
|
||||
RGB, Grayscale+Alpha, RGBA.
|
||||
Allowed bit depths: 8 and 16
|
||||
*/
|
||||
if bit_depth != 8 && bit_depth != 16 {
|
||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo;
|
||||
}
|
||||
case 3:
|
||||
/*
|
||||
Paletted. PLTE chunk must appear.
|
||||
Allowed bit depths: 1, 2, 4 and 8.
|
||||
*/
|
||||
allowed := false;
|
||||
for i in ([]u8{1, 2, 4, 8}) {
|
||||
if bit_depth == i {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo;
|
||||
}
|
||||
if !allowed {
|
||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo;
|
||||
}
|
||||
case 2, 4, 6:
|
||||
/*
|
||||
RGB, Grayscale+Alpha, RGBA.
|
||||
Allowed bit depths: 8 and 16
|
||||
*/
|
||||
if bit_depth != 8 && bit_depth != 16 {
|
||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo;
|
||||
}
|
||||
case 3:
|
||||
/*
|
||||
Paletted. PLTE chunk must appear.
|
||||
Allowed bit depths: 1, 2, 4 and 8.
|
||||
*/
|
||||
allowed := false;
|
||||
for i in ([]u8{1, 2, 4, 8}) {
|
||||
if bit_depth == i {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo;
|
||||
}
|
||||
|
||||
case:
|
||||
return {}, E_PNG.Unknown_Color_Type;
|
||||
case:
|
||||
return {}, E_PNG.Unknown_Color_Type;
|
||||
}
|
||||
|
||||
return header, nil;
|
||||
@@ -350,16 +353,16 @@ chunk_type_to_name :: proc(type: ^Chunk_Type) -> string {
|
||||
}
|
||||
|
||||
load_from_slice :: proc(slice: []u8, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
r := bytes.Reader{};
|
||||
bytes.reader_init(&r, slice);
|
||||
stream := bytes.reader_to_stream(&r);
|
||||
ctx := &compress.Context_Memory_Input{
|
||||
input_data = slice,
|
||||
};
|
||||
|
||||
/*
|
||||
TODO: Add a flag to tell the PNG loader that the stream is backed by a slice.
|
||||
This way the stream reader could avoid the copy into the temp memory returned by it,
|
||||
and instead return a slice into the original memory that's already owned by the caller.
|
||||
*/
|
||||
img, err = load_from_stream(stream, options, allocator);
|
||||
img, err = load_from_context(ctx, options, allocator);
|
||||
|
||||
return img, err;
|
||||
}
|
||||
@@ -369,15 +372,14 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont
|
||||
defer delete(data);
|
||||
|
||||
if ok {
|
||||
img, err = load_from_slice(data, options, allocator);
|
||||
return;
|
||||
return load_from_slice(data, options, allocator);
|
||||
} else {
|
||||
img = new(Image);
|
||||
return img, E_General.File_Not_Found;
|
||||
}
|
||||
}
|
||||
|
||||
load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
options := options;
|
||||
if .info in options {
|
||||
options |= {.return_metadata, .do_not_decompress_image};
|
||||
@@ -396,13 +398,11 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c
|
||||
img = new(Image);
|
||||
}
|
||||
|
||||
img.sidecar = nil;
|
||||
info := new(Info, context.allocator);
|
||||
img.metadata_ptr = info;
|
||||
img.metadata_type = typeid_of(Info);
|
||||
|
||||
ctx := compress.Context{
|
||||
input = stream,
|
||||
};
|
||||
|
||||
signature, io_error := compress.read_data(&ctx, Signature);
|
||||
signature, io_error := compress.read_data(ctx, Signature);
|
||||
if io_error != .None || signature != .PNG {
|
||||
return img, E_PNG.Invalid_PNG_Signature;
|
||||
}
|
||||
@@ -417,7 +417,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c
|
||||
e: io.Error;
|
||||
|
||||
header: IHDR;
|
||||
info: Info;
|
||||
|
||||
info.chunks.allocator = context.temp_allocator;
|
||||
|
||||
// State to ensure correct chunk ordering.
|
||||
@@ -435,233 +435,209 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c
|
||||
|
||||
read_error: io.Error;
|
||||
// 12 bytes is the size of a chunk with a zero-length payload.
|
||||
for (read_error == .None && !seen_iend) {
|
||||
for read_error == .None && !seen_iend {
|
||||
// Peek at next chunk's length and type.
|
||||
// TODO: Some streams may not provide seek/read_at
|
||||
|
||||
ch, e = compress.peek_data(&ctx, Chunk_Header);
|
||||
ch, e = compress.peek_data(ctx, Chunk_Header);
|
||||
if e != .None {
|
||||
return img, E_General.Stream_Too_Short;
|
||||
}
|
||||
// name := chunk_type_to_name(&ch.type); // Only used for debug prints during development.
|
||||
|
||||
#partial switch(ch.type) {
|
||||
case .IHDR:
|
||||
if seen_ihdr || !first {
|
||||
return {}, E_PNG.IHDR_Not_First_Chunk;
|
||||
}
|
||||
seen_ihdr = true;
|
||||
#partial switch ch.type {
|
||||
case .IHDR:
|
||||
if seen_ihdr || !first {
|
||||
return {}, E_PNG.IHDR_Not_First_Chunk;
|
||||
}
|
||||
seen_ihdr = true;
|
||||
|
||||
header, err = read_header(&ctx);
|
||||
if err != nil {
|
||||
return img, err;
|
||||
}
|
||||
header = read_header(ctx) or_return;
|
||||
|
||||
if .Paletted in header.color_type {
|
||||
// Color type 3
|
||||
img.channels = 1;
|
||||
final_image_channels = 3;
|
||||
img.depth = 8;
|
||||
} else if .Color in header.color_type {
|
||||
// Color image without a palette
|
||||
img.channels = 3;
|
||||
final_image_channels = 3;
|
||||
img.depth = header.bit_depth;
|
||||
} else {
|
||||
// Grayscale
|
||||
img.channels = 1;
|
||||
final_image_channels = 1;
|
||||
img.depth = header.bit_depth;
|
||||
}
|
||||
|
||||
if .Alpha in header.color_type {
|
||||
img.channels += 1;
|
||||
final_image_channels += 1;
|
||||
}
|
||||
|
||||
if img.channels == 0 || img.depth == 0 {
|
||||
return {}, E_PNG.IHDR_Corrupt;
|
||||
}
|
||||
|
||||
img.width = int(header.width);
|
||||
img.height = int(header.height);
|
||||
|
||||
using header;
|
||||
h := IHDR{
|
||||
width = width,
|
||||
height = height,
|
||||
bit_depth = bit_depth,
|
||||
color_type = color_type,
|
||||
compression_method = compression_method,
|
||||
filter_method = filter_method,
|
||||
interlace_method = interlace_method,
|
||||
};
|
||||
info.header = h;
|
||||
case .PLTE:
|
||||
seen_plte = true;
|
||||
// PLTE must appear before IDAT and can't appear for color types 0, 4.
|
||||
ct := transmute(u8)info.header.color_type;
|
||||
if seen_idat || ct == 0 || ct == 4 {
|
||||
return img, E_PNG.PLTE_Encountered_Unexpectedly;
|
||||
}
|
||||
|
||||
c, err = read_chunk(&ctx);
|
||||
if err != nil {
|
||||
return img, err;
|
||||
}
|
||||
|
||||
if c.header.length % 3 != 0 || c.header.length > 768 {
|
||||
return img, E_PNG.PLTE_Invalid_Length;
|
||||
}
|
||||
plte_ok: bool;
|
||||
_plte, plte_ok = plte(c);
|
||||
if !plte_ok {
|
||||
return img, E_PNG.PLTE_Invalid_Length;
|
||||
}
|
||||
|
||||
if .return_metadata in options {
|
||||
append(&info.chunks, c);
|
||||
}
|
||||
case .IDAT:
|
||||
// If we only want image metadata and don't want the pixel data, we can early out.
|
||||
if .return_metadata not_in options && .do_not_decompress_image in options {
|
||||
img.channels = final_image_channels;
|
||||
img.sidecar = info;
|
||||
return img, nil;
|
||||
}
|
||||
// There must be at least 1 IDAT, contiguous if more.
|
||||
if seen_idat {
|
||||
return img, E_PNG.IDAT_Must_Be_Contiguous;
|
||||
}
|
||||
|
||||
if idat_length > 0 {
|
||||
return img, E_PNG.IDAT_Must_Be_Contiguous;
|
||||
}
|
||||
|
||||
next := ch.type;
|
||||
for next == .IDAT {
|
||||
c, err = read_chunk(&ctx);
|
||||
if err != nil {
|
||||
return img, err;
|
||||
}
|
||||
|
||||
bytes.buffer_write(&idat_b, c.data);
|
||||
idat_length += c.header.length;
|
||||
|
||||
ch, e = compress.peek_data(&ctx, Chunk_Header);
|
||||
if e != .None {
|
||||
return img, E_General.Stream_Too_Short;
|
||||
}
|
||||
next = ch.type;
|
||||
}
|
||||
idat = bytes.buffer_to_bytes(&idat_b);
|
||||
if int(idat_length) != len(idat) {
|
||||
return {}, E_PNG.IDAT_Corrupt;
|
||||
}
|
||||
seen_idat = true;
|
||||
case .IEND:
|
||||
c, err = read_chunk(&ctx);
|
||||
if err != nil {
|
||||
return img, err;
|
||||
}
|
||||
seen_iend = true;
|
||||
case .bKGD:
|
||||
|
||||
// TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be
|
||||
|
||||
c, err = read_chunk(&ctx);
|
||||
if err != nil {
|
||||
return img, err;
|
||||
}
|
||||
seen_bkgd = true;
|
||||
if .return_metadata in options {
|
||||
append(&info.chunks, c);
|
||||
}
|
||||
|
||||
ct := transmute(u8)info.header.color_type;
|
||||
switch(ct) {
|
||||
case 3: // Indexed color
|
||||
if c.header.length != 1 {
|
||||
return {}, E_PNG.BKGD_Invalid_Length;
|
||||
}
|
||||
col := _plte.entries[c.data[0]];
|
||||
img.background = [3]u16{
|
||||
u16(col[0]) << 8 | u16(col[0]),
|
||||
u16(col[1]) << 8 | u16(col[1]),
|
||||
u16(col[2]) << 8 | u16(col[2]),
|
||||
};
|
||||
case 0, 4: // Grayscale, with and without Alpha
|
||||
if c.header.length != 2 {
|
||||
return {}, E_PNG.BKGD_Invalid_Length;
|
||||
}
|
||||
col := u16(mem.slice_data_cast([]u16be, c.data[:])[0]);
|
||||
img.background = [3]u16{col, col, col};
|
||||
case 2, 6: // Color, with and without Alpha
|
||||
if c.header.length != 6 {
|
||||
return {}, E_PNG.BKGD_Invalid_Length;
|
||||
}
|
||||
col := mem.slice_data_cast([]u16be, c.data[:]);
|
||||
img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])};
|
||||
}
|
||||
case .tRNS:
|
||||
c, err = read_chunk(&ctx);
|
||||
if err != nil {
|
||||
return img, err;
|
||||
}
|
||||
|
||||
if .Alpha in info.header.color_type {
|
||||
return img, E_PNG.TRNS_Encountered_Unexpectedly;
|
||||
}
|
||||
|
||||
if .return_metadata in options {
|
||||
append(&info.chunks, c);
|
||||
}
|
||||
|
||||
/*
|
||||
This makes the image one with transparency, so set it to +1 here,
|
||||
even if we need we leave img.channels alone for the defilterer's
|
||||
sake. If we early because the user just cares about metadata,
|
||||
we'll set it to 'final_image_channels'.
|
||||
*/
|
||||
if .Paletted in header.color_type {
|
||||
// Color type 3
|
||||
img.channels = 1;
|
||||
final_image_channels = 3;
|
||||
img.depth = 8;
|
||||
} else if .Color in header.color_type {
|
||||
// Color image without a palette
|
||||
img.channels = 3;
|
||||
final_image_channels = 3;
|
||||
img.depth = int(header.bit_depth);
|
||||
} else {
|
||||
// Grayscale
|
||||
img.channels = 1;
|
||||
final_image_channels = 1;
|
||||
img.depth = int(header.bit_depth);
|
||||
}
|
||||
|
||||
if .Alpha in header.color_type {
|
||||
img.channels += 1;
|
||||
final_image_channels += 1;
|
||||
}
|
||||
|
||||
seen_trns = true;
|
||||
if info.header.bit_depth < 8 && .Paletted not_in info.header.color_type {
|
||||
// Rescale tRNS data so key matches intensity
|
||||
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)};
|
||||
if img.channels == 0 || img.depth == 0 {
|
||||
return {}, E_PNG.IHDR_Corrupt;
|
||||
}
|
||||
|
||||
img.width = int(header.width);
|
||||
img.height = int(header.height);
|
||||
|
||||
using header;
|
||||
h := IHDR{
|
||||
width = width,
|
||||
height = height,
|
||||
bit_depth = bit_depth,
|
||||
color_type = color_type,
|
||||
compression_method = compression_method,
|
||||
filter_method = filter_method,
|
||||
interlace_method = interlace_method,
|
||||
};
|
||||
info.header = h;
|
||||
case .PLTE:
|
||||
seen_plte = true;
|
||||
// PLTE must appear before IDAT and can't appear for color types 0, 4.
|
||||
ct := transmute(u8)info.header.color_type;
|
||||
if seen_idat || ct == 0 || ct == 4 {
|
||||
return img, E_PNG.PLTE_Encountered_Unexpectedly;
|
||||
}
|
||||
|
||||
c = read_chunk(ctx) or_return;
|
||||
|
||||
if c.header.length % 3 != 0 || c.header.length > 768 {
|
||||
return img, E_PNG.PLTE_Invalid_Length;
|
||||
}
|
||||
plte_ok: bool;
|
||||
_plte, plte_ok = plte(c);
|
||||
if !plte_ok {
|
||||
return img, E_PNG.PLTE_Invalid_Length;
|
||||
}
|
||||
|
||||
if .return_metadata in options {
|
||||
append(&info.chunks, c);
|
||||
}
|
||||
case .IDAT:
|
||||
// If we only want image metadata and don't want the pixel data, we can early out.
|
||||
if .return_metadata not_in options && .do_not_decompress_image in options {
|
||||
img.channels = final_image_channels;
|
||||
return img, nil;
|
||||
}
|
||||
// There must be at least 1 IDAT, contiguous if more.
|
||||
if seen_idat {
|
||||
return img, E_PNG.IDAT_Must_Be_Contiguous;
|
||||
}
|
||||
|
||||
if idat_length > 0 {
|
||||
return img, E_PNG.IDAT_Must_Be_Contiguous;
|
||||
}
|
||||
|
||||
next := ch.type;
|
||||
for next == .IDAT {
|
||||
c = read_chunk(ctx) or_return;
|
||||
|
||||
bytes.buffer_write(&idat_b, c.data);
|
||||
idat_length += c.header.length;
|
||||
|
||||
ch, e = compress.peek_data(ctx, Chunk_Header);
|
||||
if e != .None {
|
||||
return img, E_General.Stream_Too_Short;
|
||||
}
|
||||
next = ch.type;
|
||||
}
|
||||
idat = bytes.buffer_to_bytes(&idat_b);
|
||||
if int(idat_length) != len(idat) {
|
||||
return {}, E_PNG.IDAT_Corrupt;
|
||||
}
|
||||
seen_idat = true;
|
||||
case .IEND:
|
||||
c = read_chunk(ctx) or_return;
|
||||
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 {
|
||||
append(&info.chunks, c);
|
||||
}
|
||||
|
||||
ct := transmute(u8)info.header.color_type;
|
||||
switch ct {
|
||||
case 3: // Indexed color
|
||||
if c.header.length != 1 {
|
||||
return {}, E_PNG.BKGD_Invalid_Length;
|
||||
}
|
||||
col := _plte.entries[c.data[0]];
|
||||
img.background = [3]u16{
|
||||
u16(col[0]) << 8 | u16(col[0]),
|
||||
u16(col[1]) << 8 | u16(col[1]),
|
||||
u16(col[2]) << 8 | u16(col[2]),
|
||||
};
|
||||
case 0, 4: // Grayscale, with and without Alpha
|
||||
if c.header.length != 2 {
|
||||
return {}, E_PNG.BKGD_Invalid_Length;
|
||||
}
|
||||
col := u16(mem.slice_data_cast([]u16be, c.data[:])[0]);
|
||||
img.background = [3]u16{col, col, col};
|
||||
case 2, 6: // Color, with and without Alpha
|
||||
if c.header.length != 6 {
|
||||
return {}, E_PNG.BKGD_Invalid_Length;
|
||||
}
|
||||
col := mem.slice_data_cast([]u16be, c.data[:]);
|
||||
img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])};
|
||||
}
|
||||
case .tRNS:
|
||||
c = read_chunk(ctx) or_return;
|
||||
|
||||
if .Alpha in info.header.color_type {
|
||||
return img, E_PNG.TRNS_Encountered_Unexpectedly;
|
||||
}
|
||||
|
||||
if .return_metadata in options {
|
||||
append(&info.chunks, c);
|
||||
}
|
||||
|
||||
/*
|
||||
This makes the image one with transparency, so set it to +1 here,
|
||||
even if we need we leave img.channels alone for the defilterer's
|
||||
sake. If we early because the user just cares about metadata,
|
||||
we'll set it to 'final_image_channels'.
|
||||
*/
|
||||
|
||||
final_image_channels += 1;
|
||||
|
||||
seen_trns = true;
|
||||
if info.header.bit_depth < 8 && .Paletted not_in info.header.color_type {
|
||||
// Rescale tRNS data so key matches intensity
|
||||
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:
|
||||
/*
|
||||
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
|
||||
across one of these files, use a utility to defry it.s
|
||||
*/
|
||||
return img, E_PNG.PNG_Does_Not_Adhere_to_Spec;
|
||||
case:
|
||||
// Unhandled type
|
||||
c, err = read_chunk(&ctx);
|
||||
if err != nil {
|
||||
return img, err;
|
||||
}
|
||||
if .return_metadata in options {
|
||||
// NOTE: Chunk cata is currently allocated on the temp allocator.
|
||||
append(&info.chunks, c);
|
||||
}
|
||||
}
|
||||
trns = c;
|
||||
case .iDOT, .CbGI:
|
||||
/*
|
||||
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
|
||||
across one of these files, use a utility to defry it.s
|
||||
*/
|
||||
return img, E_PNG.PNG_Does_Not_Adhere_to_Spec;
|
||||
case:
|
||||
// Unhandled type
|
||||
c = read_chunk(ctx) or_return;
|
||||
|
||||
if .return_metadata in options {
|
||||
// NOTE: Chunk cata is currently allocated on the temp allocator.
|
||||
append(&info.chunks, c);
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
if .return_header in options || .return_metadata in options {
|
||||
img.sidecar = info;
|
||||
}
|
||||
if .do_not_decompress_image in options {
|
||||
img.channels = final_image_channels;
|
||||
return img, nil;
|
||||
@@ -671,39 +647,41 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c
|
||||
return img, E_PNG.IDAT_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.
|
||||
|
||||
Let's calcalate the expected size of the IDAT based on its dimensions, and whether or not it's interlaced.
|
||||
*/
|
||||
expected_size: int;
|
||||
|
||||
if header.interlace_method != .Adam7 {
|
||||
expected_size = compute_buffer_size(int(header.width), int(header.height), int(img.channels), int(header.bit_depth), 1);
|
||||
} else {
|
||||
/*
|
||||
Because Adam7 divides the image up into sub-images, and each scanline must start
|
||||
with a filter byte, Adam7 interlaced images can have a larger raw size.
|
||||
*/
|
||||
for p := 0; p < 7; p += 1 {
|
||||
x := (int(header.width) - ADAM7_X_ORIG[p] + ADAM7_X_SPACING[p] - 1) / ADAM7_X_SPACING[p];
|
||||
y := (int(header.height) - ADAM7_Y_ORIG[p] + ADAM7_Y_SPACING[p] - 1) / ADAM7_Y_SPACING[p];
|
||||
if x > 0 && y > 0 {
|
||||
expected_size += compute_buffer_size(int(x), int(y), int(img.channels), int(header.bit_depth), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf: bytes.Buffer;
|
||||
zlib_error := zlib.inflate(idat, &buf);
|
||||
zlib_error := zlib.inflate(idat, &buf, false, expected_size);
|
||||
defer bytes.buffer_destroy(&buf);
|
||||
|
||||
if zlib_error != nil {
|
||||
return {}, zlib_error;
|
||||
} else {
|
||||
/*
|
||||
Let's calcalate the expected size of the IDAT based on its dimensions,
|
||||
and whether or not it's interlaced
|
||||
*/
|
||||
expected_size: int;
|
||||
buf_len := len(buf.buf);
|
||||
}
|
||||
|
||||
if header.interlace_method != .Adam7 {
|
||||
expected_size = compute_buffer_size(int(header.width), int(header.height), int(img.channels), int(header.bit_depth), 1);
|
||||
} else {
|
||||
/*
|
||||
Because Adam7 divides the image up into sub-images, and each scanline must start
|
||||
with a filter byte, Adam7 interlaced images can have a larger raw size.
|
||||
*/
|
||||
for p := 0; p < 7; p += 1 {
|
||||
x := (int(header.width) - ADAM7_X_ORIG[p] + ADAM7_X_SPACING[p] - 1) / ADAM7_X_SPACING[p];
|
||||
y := (int(header.height) - ADAM7_Y_ORIG[p] + ADAM7_Y_SPACING[p] - 1) / ADAM7_Y_SPACING[p];
|
||||
if (x > 0 && y > 0) {
|
||||
expected_size += compute_buffer_size(int(x), int(y), int(img.channels), int(header.bit_depth), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if expected_size != buf_len {
|
||||
return {}, E_PNG.IDAT_Corrupt;
|
||||
}
|
||||
buf_len := len(buf.buf);
|
||||
if expected_size != buf_len {
|
||||
return {}, E_PNG.IDAT_Corrupt;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -854,7 +832,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c
|
||||
p16 := mem.slice_data_cast([]u16, temp.buf[:]);
|
||||
o16 := mem.slice_data_cast([]u16, t.buf[:]);
|
||||
|
||||
switch (raw_image_channels) {
|
||||
switch raw_image_channels {
|
||||
case 1:
|
||||
// Gray without Alpha. Might have tRNS alpha.
|
||||
key := u16(0);
|
||||
@@ -1051,7 +1029,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c
|
||||
p := mem.slice_data_cast([]u8, temp.buf[:]);
|
||||
o := mem.slice_data_cast([]u8, t.buf[:]);
|
||||
|
||||
switch (raw_image_channels) {
|
||||
switch raw_image_channels {
|
||||
case 1:
|
||||
// Gray without Alpha. Might have tRNS alpha.
|
||||
key := u8(0);
|
||||
@@ -1276,7 +1254,7 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
|
||||
nk := row_stride - channels;
|
||||
|
||||
filter := Row_Filter(src[0]); src = src[1:];
|
||||
switch(filter) {
|
||||
switch filter {
|
||||
case .None:
|
||||
copy(dest, src[:row_stride]);
|
||||
case .Sub:
|
||||
@@ -1348,31 +1326,31 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch
|
||||
case .None:
|
||||
copy(dest, src[:row_stride_in]);
|
||||
case .Sub:
|
||||
for i in 0..channels {
|
||||
for i in 0..=channels {
|
||||
dest[i] = src[i];
|
||||
}
|
||||
for k in 0..nk {
|
||||
for k in 0..=nk {
|
||||
dest[channels+k] = (src[channels+k] + dest[k]) & 255;
|
||||
}
|
||||
case .Up:
|
||||
for k in 0..row_stride_in {
|
||||
for k in 0..=row_stride_in {
|
||||
dest[k] = (src[k] + up[k]) & 255;
|
||||
}
|
||||
case .Average:
|
||||
for i in 0..channels {
|
||||
for i in 0..=channels {
|
||||
avg := up[i] >> 1;
|
||||
dest[i] = (src[i] + avg) & 255;
|
||||
}
|
||||
for k in 0..nk {
|
||||
for k in 0..=nk {
|
||||
avg := u8((u16(up[channels+k]) + u16(dest[k])) >> 1);
|
||||
dest[channels+k] = (src[channels+k] + avg) & 255;
|
||||
}
|
||||
case .Paeth:
|
||||
for i in 0..channels {
|
||||
for i in 0..=channels {
|
||||
paeth := filter_paeth(0, up[i], 0);
|
||||
dest[i] = (src[i] + paeth) & 255;
|
||||
}
|
||||
for k in 0..nk {
|
||||
for k in 0..=nk {
|
||||
paeth := filter_paeth(dest[k], up[channels+k], up[k]);
|
||||
dest[channels+k] = (src[channels+k] + paeth) & 255;
|
||||
}
|
||||
@@ -1380,9 +1358,9 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch
|
||||
return false;
|
||||
}
|
||||
|
||||
src = src [row_stride_in:];
|
||||
up = dest;
|
||||
dest = dest[row_stride_in:];
|
||||
src = src[row_stride_in:];
|
||||
up = dest;
|
||||
dest = dest[row_stride_in:];
|
||||
}
|
||||
|
||||
// Let's expand the bits
|
||||
@@ -1590,7 +1568,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option
|
||||
i,j,x,y: int;
|
||||
x = (width - ADAM7_X_ORIG[p] + ADAM7_X_SPACING[p] - 1) / ADAM7_X_SPACING[p];
|
||||
y = (height - ADAM7_Y_ORIG[p] + ADAM7_Y_SPACING[p] - 1) / ADAM7_Y_SPACING[p];
|
||||
if (x > 0 && y > 0) {
|
||||
if x > 0 && y > 0 {
|
||||
temp: bytes.Buffer;
|
||||
temp_len := compute_buffer_size(x, y, channels, depth == 16 ? 16 : 8);
|
||||
resize(&temp.buf, temp_len);
|
||||
@@ -1654,4 +1632,4 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option
|
||||
return nil;
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_slice, load_from_stream};
|
||||
load :: proc{load_from_file, load_from_slice, load_from_context};
|
||||
|
||||
@@ -136,6 +136,7 @@ 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 ---
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package io
|
||||
|
||||
import "intrinsics"
|
||||
import "core:intrinsics"
|
||||
import "core:runtime"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
package io
|
||||
|
||||
import "core:mem"
|
||||
import "core:strconv"
|
||||
|
||||
|
||||
read_ptr :: proc(r: Reader, p: rawptr, byte_size: int) -> (n: int, err: Error) {
|
||||
return read(r, mem.byte_slice(p, byte_size));
|
||||
}
|
||||
|
||||
write_ptr :: proc(w: Writer, p: rawptr, byte_size: int) -> (n: int, err: Error) {
|
||||
return write(w, mem.byte_slice(p, byte_size));
|
||||
}
|
||||
|
||||
read_ptr_at :: proc(r: Reader_At, p: rawptr, byte_size: int, offset: i64) -> (n: int, err: Error) {
|
||||
return read_at(r, mem.byte_slice(p, byte_size), offset);
|
||||
}
|
||||
|
||||
write_ptr_at :: proc(w: Writer_At, p: rawptr, byte_size: int, offset: i64) -> (n: int, err: Error) {
|
||||
return write_at(w, mem.byte_slice(p, byte_size), offset);
|
||||
}
|
||||
|
||||
write_u64 :: proc(w: Writer, i: u64, base: int = 10) -> (n: int, err: Error) {
|
||||
buf: [32]byte;
|
||||
s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil);
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
|
||||
|
||||
This file collects public proc maps and their aliases.
|
||||
|
||||
=== === === === === === === === === === === === === === === === === === === === === === === ===
|
||||
Basic arithmetic.
|
||||
See `public.odin`.
|
||||
=== === === === === === === === === === === === === === === === === === === === === === === ===
|
||||
*/
|
||||
|
||||
/*
|
||||
High-level addition. Handles sign.
|
||||
*/
|
||||
add :: proc {
|
||||
/*
|
||||
int_add :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error)
|
||||
*/
|
||||
int_add,
|
||||
/*
|
||||
Adds the unsigned `DIGIT` immediate to an `Int`, such that the
|
||||
`DIGIT` doesn't have to be turned into an `Int` first.
|
||||
|
||||
int_add_digit :: proc(dest, a: ^Int, digit: DIGIT, allocator := context.allocator) -> (err: Error)
|
||||
*/
|
||||
int_add_digit,
|
||||
};
|
||||
|
||||
/*
|
||||
err = sub(dest, a, b);
|
||||
*/
|
||||
sub :: proc {
|
||||
/*
|
||||
int_sub :: proc(dest, a, b: ^Int) -> (err: Error)
|
||||
*/
|
||||
int_sub,
|
||||
/*
|
||||
int_sub_digit :: proc(dest, a: ^Int, digit: DIGIT) -> (err: Error)
|
||||
*/
|
||||
int_sub_digit,
|
||||
};
|
||||
|
||||
/*
|
||||
=== === === === === === === === === === === === === === === === === === === === === === === ===
|
||||
Comparisons.
|
||||
See `compare.odin`.
|
||||
=== === === === === === === === === === === === === === === === === === === === === === === ===
|
||||
*/
|
||||
|
||||
is_initialized :: proc {
|
||||
/*
|
||||
int_is_initialized :: proc(a: ^Int) -> bool
|
||||
*/
|
||||
int_is_initialized,
|
||||
};
|
||||
|
||||
is_zero :: proc {
|
||||
/*
|
||||
int_is_zero :: proc(a: ^Int) -> bool
|
||||
*/
|
||||
int_is_zero,
|
||||
};
|
||||
|
||||
is_positive :: proc {
|
||||
/*
|
||||
int_is_positive :: proc(a: ^Int) -> bool
|
||||
*/
|
||||
int_is_positive,
|
||||
};
|
||||
is_pos :: is_positive;
|
||||
|
||||
is_negative :: proc {
|
||||
/*
|
||||
int_is_negative :: proc(a: ^Int) -> bool
|
||||
*/
|
||||
int_is_negative,
|
||||
};
|
||||
is_neg :: is_negative;
|
||||
|
||||
is_even :: proc {
|
||||
/*
|
||||
int_is_even :: proc(a: ^Int) -> bool
|
||||
*/
|
||||
int_is_even,
|
||||
};
|
||||
|
||||
is_odd :: proc {
|
||||
/*
|
||||
int_is_odd :: proc(a: ^Int) -> bool
|
||||
*/
|
||||
int_is_odd,
|
||||
};
|
||||
|
||||
is_power_of_two :: proc {
|
||||
/*
|
||||
platform_int_is_power_of_two :: proc(a: int) -> bool
|
||||
*/
|
||||
platform_int_is_power_of_two,
|
||||
/*
|
||||
int_is_power_of_two :: proc(a: ^Int) -> (res: bool)
|
||||
*/
|
||||
int_is_power_of_two,
|
||||
};
|
||||
|
||||
compare :: proc {
|
||||
/*
|
||||
Compare two `Int`s, signed.
|
||||
|
||||
int_compare :: proc(a, b: ^Int) -> Comparison_Flag
|
||||
*/
|
||||
int_compare,
|
||||
/*
|
||||
Compare an `Int` to an unsigned number upto the size of the backing type.
|
||||
|
||||
int_compare_digit :: proc(a: ^Int, u: DIGIT) -> Comparison_Flag
|
||||
*/
|
||||
int_compare_digit,
|
||||
};
|
||||
cmp :: compare;
|
||||
|
||||
compare_magnitude :: proc {
|
||||
/*
|
||||
Compare the magnitude of two `Int`s, unsigned.
|
||||
*/
|
||||
int_compare_magnitude,
|
||||
};
|
||||
cmp_mag :: compare_magnitude;
|
||||
|
||||
/*
|
||||
=== === === === === === === === === === === === === === === === === === === === === === === ===
|
||||
Initialization and other helpers.
|
||||
See `helpers.odin`.
|
||||
=== === === === === === === === === === === === === === === === === === === === === === === ===
|
||||
*/
|
||||
|
||||
destroy :: proc {
|
||||
/*
|
||||
Clears one or more `Int`s and dellocates their backing memory.
|
||||
|
||||
int_destroy :: proc(integers: ..^Int)
|
||||
*/
|
||||
int_destroy,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
@echo off
|
||||
:odin run . -vet
|
||||
|
||||
set TEST_ARGS=-fast-tests
|
||||
:odin build . -build-mode:shared -show-timings -o:minimal -no-bounds-check -define:MATH_BIG_EXE=false && python test.py %TEST_ARGS%
|
||||
odin build . -build-mode:shared -show-timings -o:size -no-bounds-check -define:MATH_BIG_EXE=false && python test.py %TEST_ARGS%
|
||||
:odin build . -build-mode:shared -show-timings -o:size -define:MATH_BIG_EXE=false && python test.py %TEST_ARGS%
|
||||
:odin build . -build-mode:shared -show-timings -o:speed -no-bounds-check -define:MATH_BIG_EXE=false && python test.py %TEST_ARGS%
|
||||
:odin build . -build-mode:shared -show-timings -o:speed -define:MATH_BIG_EXE=false && python test.py -fast-tests %TEST_ARGS%
|
||||
@@ -0,0 +1,218 @@
|
||||
package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
|
||||
*/
|
||||
|
||||
import "core:intrinsics"
|
||||
|
||||
/*
|
||||
TODO: Make the tunables runtime adjustable where practical.
|
||||
|
||||
This allows to benchmark and/or setting optimized values for a certain CPU without recompiling.
|
||||
*/
|
||||
|
||||
/*
|
||||
========================== TUNABLES ==========================
|
||||
|
||||
`initialize_constants` returns `#config(MUL_KARATSUBA_CUTOFF, _DEFAULT_MUL_KARATSUBA_CUTOFF)`
|
||||
and we initialize this cutoff that way so that the procedure is used and called,
|
||||
because it handles initializing the constants ONE, ZERO, MINUS_ONE, NAN and INF.
|
||||
|
||||
`initialize_constants` also replaces the other `_DEFAULT_*` cutoffs with custom compile-time values if so `#config`ured.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
There is a bug with DLL globals. They don't get set.
|
||||
To allow tests to run we add `-define:MATH_BIG_EXE=false` to hardcode the cutoffs for now.
|
||||
*/
|
||||
when #config(MATH_BIG_EXE, true) {
|
||||
MUL_KARATSUBA_CUTOFF := initialize_constants();
|
||||
SQR_KARATSUBA_CUTOFF := _DEFAULT_SQR_KARATSUBA_CUTOFF;
|
||||
MUL_TOOM_CUTOFF := _DEFAULT_MUL_TOOM_CUTOFF;
|
||||
SQR_TOOM_CUTOFF := _DEFAULT_SQR_TOOM_CUTOFF;
|
||||
} else {
|
||||
MUL_KARATSUBA_CUTOFF := _DEFAULT_MUL_KARATSUBA_CUTOFF;
|
||||
SQR_KARATSUBA_CUTOFF := _DEFAULT_SQR_KARATSUBA_CUTOFF;
|
||||
MUL_TOOM_CUTOFF := _DEFAULT_MUL_TOOM_CUTOFF;
|
||||
SQR_TOOM_CUTOFF := _DEFAULT_SQR_TOOM_CUTOFF;
|
||||
}
|
||||
|
||||
/*
|
||||
These defaults were tuned on an AMD A8-6600K (64-bit) using libTomMath's `make tune`.
|
||||
|
||||
TODO(Jeroen): Port this tuning algorithm and tune them for more modern processors.
|
||||
|
||||
It would also be cool if we collected some data across various processor families.
|
||||
This would let uss set reasonable defaults at runtime as this library initializes
|
||||
itself by using `cpuid` or the ARM equivalent.
|
||||
|
||||
IMPORTANT: The 32_BIT path has largely gone untested. It needs to be tested and
|
||||
debugged where necessary.
|
||||
*/
|
||||
|
||||
_DEFAULT_MUL_KARATSUBA_CUTOFF :: #config(MUL_KARATSUBA_CUTOFF, 80);
|
||||
_DEFAULT_SQR_KARATSUBA_CUTOFF :: #config(SQR_KARATSUBA_CUTOFF, 120);
|
||||
_DEFAULT_MUL_TOOM_CUTOFF :: #config(MUL_TOOM_CUTOFF, 350);
|
||||
_DEFAULT_SQR_TOOM_CUTOFF :: #config(SQR_TOOM_CUTOFF, 400);
|
||||
|
||||
|
||||
MAX_ITERATIONS_ROOT_N := 500;
|
||||
|
||||
/*
|
||||
Largest `N` for which we'll compute `N!`
|
||||
*/
|
||||
FACTORIAL_MAX_N := 1_000_000;
|
||||
|
||||
/*
|
||||
Cutoff to switch to int_factorial_binary_split, and its max recursion level.
|
||||
*/
|
||||
FACTORIAL_BINARY_SPLIT_CUTOFF := 6100;
|
||||
FACTORIAL_BINARY_SPLIT_MAX_RECURSIONS := 100;
|
||||
|
||||
|
||||
/*
|
||||
We don't allow these to be switched at runtime for two reasons:
|
||||
|
||||
1) 32-bit and 64-bit versions of procedures use different types for their storage,
|
||||
so we'd have to double the number of procedures, and they couldn't interact.
|
||||
|
||||
2) Optimizations thanks to precomputed masks wouldn't work.
|
||||
*/
|
||||
MATH_BIG_FORCE_64_BIT :: #config(MATH_BIG_FORCE_64_BIT, false);
|
||||
MATH_BIG_FORCE_32_BIT :: #config(MATH_BIG_FORCE_32_BIT, false);
|
||||
when (MATH_BIG_FORCE_32_BIT && MATH_BIG_FORCE_64_BIT) { #panic("Cannot force 32-bit and 64-bit big backend simultaneously."); };
|
||||
|
||||
_LOW_MEMORY :: #config(BIGINT_SMALL_MEMORY, false);
|
||||
when _LOW_MEMORY {
|
||||
_DEFAULT_DIGIT_COUNT :: 8;
|
||||
} else {
|
||||
_DEFAULT_DIGIT_COUNT :: 32;
|
||||
}
|
||||
|
||||
/*
|
||||
======================= END OF TUNABLES =======================
|
||||
*/
|
||||
|
||||
Sign :: enum u8 {
|
||||
Zero_or_Positive = 0,
|
||||
Negative = 1,
|
||||
};
|
||||
|
||||
Int :: struct {
|
||||
used: int,
|
||||
digit: [dynamic]DIGIT,
|
||||
sign: Sign,
|
||||
flags: Flags,
|
||||
};
|
||||
|
||||
Flag :: enum u8 {
|
||||
NaN,
|
||||
Inf,
|
||||
Immutable,
|
||||
};
|
||||
|
||||
Flags :: bit_set[Flag; u8];
|
||||
|
||||
/*
|
||||
Errors are a strict superset of runtime.Allocation_Error.
|
||||
*/
|
||||
Error :: enum int {
|
||||
Okay = 0,
|
||||
Out_Of_Memory = 1,
|
||||
Invalid_Pointer = 2,
|
||||
Invalid_Argument = 3,
|
||||
|
||||
Assignment_To_Immutable = 4,
|
||||
Max_Iterations_Reached = 5,
|
||||
Buffer_Overflow = 6,
|
||||
Integer_Overflow = 7,
|
||||
|
||||
Division_by_Zero = 8,
|
||||
Math_Domain_Error = 9,
|
||||
|
||||
Unimplemented = 127,
|
||||
};
|
||||
|
||||
Error_String :: #partial [Error]string{
|
||||
.Out_Of_Memory = "Out of memory",
|
||||
.Invalid_Pointer = "Invalid pointer",
|
||||
.Invalid_Argument = "Invalid argument",
|
||||
|
||||
.Assignment_To_Immutable = "Assignment to immutable",
|
||||
.Max_Iterations_Reached = "Max iterations reached",
|
||||
.Buffer_Overflow = "Buffer overflow",
|
||||
.Integer_Overflow = "Integer overflow",
|
||||
|
||||
.Division_by_Zero = "Division by zero",
|
||||
.Math_Domain_Error = "Math domain error",
|
||||
|
||||
.Unimplemented = "Unimplemented",
|
||||
};
|
||||
|
||||
Primality_Flag :: enum u8 {
|
||||
Blum_Blum_Shub = 0, /* BBS style prime */
|
||||
Safe = 1, /* Safe prime (p-1)/2 == prime */
|
||||
Second_MSB_On = 3, /* force 2nd MSB to 1 */
|
||||
};
|
||||
Primality_Flags :: bit_set[Primality_Flag; u8];
|
||||
|
||||
/*
|
||||
How do we store the Ints?
|
||||
|
||||
Minimum number of available digits in `Int`, `_DEFAULT_DIGIT_COUNT` >= `_MIN_DIGIT_COUNT`
|
||||
- Must be at least 3 for `_div_school`.
|
||||
- Must be large enough such that `init_integer` can store `u128` in the `Int` without growing.
|
||||
*/
|
||||
|
||||
_MIN_DIGIT_COUNT :: max(3, ((size_of(u128) + _DIGIT_BITS) - 1) / _DIGIT_BITS);
|
||||
#assert(_DEFAULT_DIGIT_COUNT >= _MIN_DIGIT_COUNT);
|
||||
|
||||
/*
|
||||
Maximum number of digits.
|
||||
- Must be small enough such that `_bit_count` does not overflow.
|
||||
- Must be small enough such that `_radix_size` for base 2 does not overflow.
|
||||
`_radix_size` needs two additional bytes for zero termination and sign.
|
||||
*/
|
||||
_MAX_BIT_COUNT :: (max(int) - 2);
|
||||
_MAX_DIGIT_COUNT :: _MAX_BIT_COUNT / _DIGIT_BITS;
|
||||
|
||||
when MATH_BIG_FORCE_64_BIT || (!MATH_BIG_FORCE_32_BIT && size_of(rawptr) == 8) {
|
||||
/*
|
||||
We can use u128 as an intermediary.
|
||||
*/
|
||||
DIGIT :: distinct u64;
|
||||
_WORD :: distinct u128;
|
||||
} else {
|
||||
DIGIT :: distinct u32;
|
||||
_WORD :: distinct u64;
|
||||
}
|
||||
#assert(size_of(_WORD) == 2 * size_of(DIGIT));
|
||||
|
||||
_DIGIT_TYPE_BITS :: 8 * size_of(DIGIT);
|
||||
_WORD_TYPE_BITS :: 8 * size_of(_WORD);
|
||||
|
||||
_DIGIT_BITS :: _DIGIT_TYPE_BITS - 4;
|
||||
_WORD_BITS :: 2 * _DIGIT_BITS;
|
||||
|
||||
_MASK :: (DIGIT(1) << DIGIT(_DIGIT_BITS)) - DIGIT(1);
|
||||
_DIGIT_MAX :: _MASK;
|
||||
_MAX_COMBA :: 1 << (_WORD_TYPE_BITS - (2 * _DIGIT_BITS)) ;
|
||||
_WARRAY :: 1 << ((_WORD_TYPE_BITS - (2 * _DIGIT_BITS)) + 1);
|
||||
|
||||
Order :: enum i8 {
|
||||
LSB_First = -1,
|
||||
MSB_First = 1,
|
||||
};
|
||||
|
||||
Endianness :: enum i8 {
|
||||
Little = -1,
|
||||
Platform = 0,
|
||||
Big = 1,
|
||||
};
|
||||
@@ -0,0 +1,232 @@
|
||||
//+ignore
|
||||
package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
A BigInt implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
|
||||
*/
|
||||
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
|
||||
print_configation :: proc() {
|
||||
fmt.printf(
|
||||
`
|
||||
Configuration:
|
||||
_DIGIT_BITS %v
|
||||
_MIN_DIGIT_COUNT %v
|
||||
_MAX_DIGIT_COUNT %v
|
||||
_DEFAULT_DIGIT_COUNT %v
|
||||
_MAX_COMBA %v
|
||||
_WARRAY %v
|
||||
Runtime tunable:
|
||||
MUL_KARATSUBA_CUTOFF %v
|
||||
SQR_KARATSUBA_CUTOFF %v
|
||||
MUL_TOOM_CUTOFF %v
|
||||
SQR_TOOM_CUTOFF %v
|
||||
MAX_ITERATIONS_ROOT_N %v
|
||||
FACTORIAL_MAX_N %v
|
||||
FACTORIAL_BINARY_SPLIT_CUTOFF %v
|
||||
FACTORIAL_BINARY_SPLIT_MAX_RECURSIONS %v
|
||||
|
||||
`, _DIGIT_BITS,
|
||||
_MIN_DIGIT_COUNT,
|
||||
_MAX_DIGIT_COUNT,
|
||||
_DEFAULT_DIGIT_COUNT,
|
||||
_MAX_COMBA,
|
||||
_WARRAY,
|
||||
MUL_KARATSUBA_CUTOFF,
|
||||
SQR_KARATSUBA_CUTOFF,
|
||||
MUL_TOOM_CUTOFF,
|
||||
SQR_TOOM_CUTOFF,
|
||||
MAX_ITERATIONS_ROOT_N,
|
||||
FACTORIAL_MAX_N,
|
||||
FACTORIAL_BINARY_SPLIT_CUTOFF,
|
||||
FACTORIAL_BINARY_SPLIT_MAX_RECURSIONS,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
print :: proc(name: string, a: ^Int, base := i8(10), print_name := true, newline := true, print_extra_info := false) {
|
||||
assert_if_nil(a);
|
||||
|
||||
as, err := itoa(a, base);
|
||||
defer delete(as);
|
||||
|
||||
cb := internal_count_bits(a);
|
||||
if print_name {
|
||||
fmt.printf("%v", name);
|
||||
}
|
||||
if err != nil {
|
||||
fmt.printf("%v (error: %v | %v)", name, err, a);
|
||||
}
|
||||
fmt.printf("%v", as);
|
||||
if print_extra_info {
|
||||
fmt.printf(" (base: %v, bits: %v (digits: %v), flags: %v)", base, cb, a.used, a.flags);
|
||||
}
|
||||
if newline {
|
||||
fmt.println();
|
||||
}
|
||||
}
|
||||
|
||||
int_to_byte :: proc(v: ^Int) {
|
||||
err: Error;
|
||||
size: int;
|
||||
print("v: ", v);
|
||||
fmt.println();
|
||||
|
||||
t := &Int{};
|
||||
defer destroy(t);
|
||||
|
||||
if size, err = int_to_bytes_size(v); err != nil {
|
||||
fmt.printf("int_to_bytes_size returned: %v\n", err);
|
||||
return;
|
||||
}
|
||||
b1 := make([]u8, size, context.temp_allocator);
|
||||
err = int_to_bytes_big(v, b1);
|
||||
int_from_bytes_big(t, b1);
|
||||
fmt.printf("big: %v | err: %v\n", b1, err);
|
||||
|
||||
int_from_bytes_big(t, b1);
|
||||
if internal_cmp_mag(t, v) != 0 {
|
||||
print("\tError parsing t: ", t);
|
||||
}
|
||||
|
||||
if size, err = int_to_bytes_size(v); err != nil {
|
||||
fmt.printf("int_to_bytes_size returned: %v\n", err);
|
||||
return;
|
||||
}
|
||||
b2 := make([]u8, size, context.temp_allocator);
|
||||
err = int_to_bytes_big_python(v, b2);
|
||||
fmt.printf("big python: %v | err: %v\n", b2, err);
|
||||
|
||||
if err == nil {
|
||||
int_from_bytes_big_python(t, b2);
|
||||
if internal_cmp_mag(t, v) != 0 {
|
||||
print("\tError parsing t: ", t);
|
||||
}
|
||||
}
|
||||
|
||||
if size, err = int_to_bytes_size(v, true); err != nil {
|
||||
fmt.printf("int_to_bytes_size returned: %v\n", err);
|
||||
return;
|
||||
}
|
||||
b3 := make([]u8, size, context.temp_allocator);
|
||||
err = int_to_bytes_big(v, b3, true);
|
||||
fmt.printf("big signed: %v | err: %v\n", b3, err);
|
||||
|
||||
int_from_bytes_big(t, b3, true);
|
||||
if internal_cmp(t, v) != 0 {
|
||||
print("\tError parsing t: ", t);
|
||||
}
|
||||
|
||||
if size, err = int_to_bytes_size(v, true); err != nil {
|
||||
fmt.printf("int_to_bytes_size returned: %v\n", err);
|
||||
return;
|
||||
}
|
||||
b4 := make([]u8, size, context.temp_allocator);
|
||||
err = int_to_bytes_big_python(v, b4, true);
|
||||
fmt.printf("big signed python: %v | err: %v\n", b4, err);
|
||||
|
||||
int_from_bytes_big_python(t, b4, true);
|
||||
if internal_cmp(t, v) != 0 {
|
||||
print("\tError parsing t: ", t);
|
||||
}
|
||||
}
|
||||
|
||||
int_to_byte_little :: proc(v: ^Int) {
|
||||
err: Error;
|
||||
size: int;
|
||||
print("v: ", v);
|
||||
fmt.println();
|
||||
|
||||
t := &Int{};
|
||||
defer destroy(t);
|
||||
|
||||
if size, err = int_to_bytes_size(v); err != nil {
|
||||
fmt.printf("int_to_bytes_size returned: %v\n", err);
|
||||
return;
|
||||
}
|
||||
b1 := make([]u8, size, context.temp_allocator);
|
||||
err = int_to_bytes_little(v, b1);
|
||||
fmt.printf("little: %v | err: %v\n", b1, err);
|
||||
|
||||
int_from_bytes_little(t, b1);
|
||||
if internal_cmp_mag(t, v) != 0 {
|
||||
print("\tError parsing t: ", t);
|
||||
}
|
||||
|
||||
if size, err = int_to_bytes_size(v); err != nil {
|
||||
fmt.printf("int_to_bytes_size returned: %v\n", err);
|
||||
return;
|
||||
}
|
||||
b2 := make([]u8, size, context.temp_allocator);
|
||||
err = int_to_bytes_little_python(v, b2);
|
||||
fmt.printf("little python: %v | err: %v\n", b2, err);
|
||||
|
||||
if err == nil {
|
||||
int_from_bytes_little_python(t, b2);
|
||||
if internal_cmp_mag(t, v) != 0 {
|
||||
print("\tError parsing t: ", t);
|
||||
}
|
||||
}
|
||||
|
||||
if size, err = int_to_bytes_size(v, true); err != nil {
|
||||
fmt.printf("int_to_bytes_size returned: %v\n", err);
|
||||
return;
|
||||
}
|
||||
b3 := make([]u8, size, context.temp_allocator);
|
||||
err = int_to_bytes_little(v, b3, true);
|
||||
fmt.printf("little signed: %v | err: %v\n", b3, err);
|
||||
|
||||
int_from_bytes_little(t, b3, true);
|
||||
if internal_cmp(t, v) != 0 {
|
||||
print("\tError parsing t: ", t);
|
||||
}
|
||||
|
||||
if size, err = int_to_bytes_size(v, true); err != nil {
|
||||
fmt.printf("int_to_bytes_size returned: %v\n", err);
|
||||
return;
|
||||
}
|
||||
b4 := make([]u8, size, context.temp_allocator);
|
||||
err = int_to_bytes_little_python(v, b4, true);
|
||||
fmt.printf("little signed python: %v | err: %v\n", b4, err);
|
||||
|
||||
int_from_bytes_little_python(t, b4, true);
|
||||
if internal_cmp(t, v) != 0 {
|
||||
print("\tError parsing t: ", t);
|
||||
}
|
||||
}
|
||||
|
||||
demo :: proc() {
|
||||
a, b, c, d, e, f := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{};
|
||||
defer destroy(a, b, c, d, e, f);
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
ta := mem.Tracking_Allocator{};
|
||||
mem.tracking_allocator_init(&ta, context.allocator);
|
||||
context.allocator = mem.tracking_allocator(&ta);
|
||||
|
||||
demo();
|
||||
|
||||
print_configation();
|
||||
|
||||
print_timings();
|
||||
|
||||
if len(ta.allocation_map) > 0 {
|
||||
for _, v in ta.allocation_map {
|
||||
fmt.printf("Leaked %v bytes @ %v\n", v.size, v.location);
|
||||
}
|
||||
}
|
||||
if len(ta.bad_free_array) > 0 {
|
||||
fmt.println("Bad frees:");
|
||||
for v in ta.bad_free_array {
|
||||
fmt.println(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,800 @@
|
||||
package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
|
||||
*/
|
||||
|
||||
import "core:intrinsics"
|
||||
import rnd "core:math/rand"
|
||||
|
||||
// import "core:fmt"
|
||||
|
||||
/*
|
||||
TODO: Int.flags and Constants like ONE, NAN, etc, are not yet properly handled everywhere.
|
||||
*/
|
||||
|
||||
/*
|
||||
Deallocates the backing memory of one or more `Int`s.
|
||||
*/
|
||||
int_destroy :: proc(integers: ..^Int) {
|
||||
integers := integers;
|
||||
|
||||
for a in &integers {
|
||||
assert_if_nil(a);
|
||||
}
|
||||
#force_inline internal_int_destroy(..integers);
|
||||
}
|
||||
|
||||
/*
|
||||
Helpers to set an `Int` to a specific value.
|
||||
*/
|
||||
int_set_from_integer :: proc(dest: ^Int, src: $T, minimize := false, allocator := context.allocator) -> (err: Error)
|
||||
where intrinsics.type_is_integer(T) {
|
||||
context.allocator = allocator;
|
||||
src := src;
|
||||
|
||||
/*
|
||||
Check that `src` is usable and `dest` isn't immutable.
|
||||
*/
|
||||
assert_if_nil(dest);
|
||||
#force_inline internal_error_if_immutable(dest) or_return;
|
||||
|
||||
return #force_inline internal_int_set_from_integer(dest, src, minimize);
|
||||
}
|
||||
|
||||
set :: proc { int_set_from_integer, int_copy, int_atoi, };
|
||||
|
||||
/*
|
||||
Copy one `Int` to another.
|
||||
*/
|
||||
int_copy :: proc(dest, src: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
If dest == src, do nothing
|
||||
*/
|
||||
if (dest == src) { return nil; }
|
||||
|
||||
/*
|
||||
Check that `src` is usable and `dest` isn't immutable.
|
||||
*/
|
||||
assert_if_nil(dest, src);
|
||||
context.allocator = allocator;
|
||||
|
||||
#force_inline internal_clear_if_uninitialized(src) or_return;
|
||||
#force_inline internal_error_if_immutable(dest) or_return;
|
||||
|
||||
return #force_inline internal_int_copy(dest, src, minimize);
|
||||
}
|
||||
copy :: proc { int_copy, };
|
||||
|
||||
/*
|
||||
In normal code, you can also write `a, b = b, a`.
|
||||
However, that only swaps within the current scope.
|
||||
This helper swaps completely.
|
||||
*/
|
||||
int_swap :: proc(a, b: ^Int) {
|
||||
assert_if_nil(a, b);
|
||||
#force_inline internal_swap(a, b);
|
||||
}
|
||||
swap :: proc { int_swap, };
|
||||
|
||||
/*
|
||||
Set `dest` to |`src`|.
|
||||
*/
|
||||
int_abs :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `src` is usable and `dest` isn't immutable.
|
||||
*/
|
||||
assert_if_nil(dest, src);
|
||||
context.allocator = allocator;
|
||||
|
||||
#force_inline internal_clear_if_uninitialized(src) or_return;
|
||||
#force_inline internal_error_if_immutable(dest) or_return;
|
||||
|
||||
return #force_inline internal_int_abs(dest, src);
|
||||
}
|
||||
|
||||
platform_abs :: proc(n: $T) -> T where intrinsics.type_is_integer(T) {
|
||||
return n if n >= 0 else -n;
|
||||
}
|
||||
abs :: proc{ int_abs, platform_abs, };
|
||||
|
||||
/*
|
||||
Set `dest` to `-src`.
|
||||
*/
|
||||
int_neg :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `src` is usable and `dest` isn't immutable.
|
||||
*/
|
||||
assert_if_nil(dest, src);
|
||||
context.allocator = allocator;
|
||||
|
||||
#force_inline internal_clear_if_uninitialized(src) or_return;
|
||||
#force_inline internal_error_if_immutable(dest) or_return;
|
||||
|
||||
return #force_inline internal_int_neg(dest, src);
|
||||
}
|
||||
neg :: proc { int_neg, };
|
||||
|
||||
/*
|
||||
Helpers to extract values from the `Int`.
|
||||
*/
|
||||
int_bitfield_extract_single :: proc(a: ^Int, offset: int, allocator := context.allocator) -> (bit: _WORD, err: Error) {
|
||||
return #force_inline int_bitfield_extract(a, offset, 1, allocator);
|
||||
}
|
||||
|
||||
int_bitfield_extract :: proc(a: ^Int, offset, count: int, allocator := context.allocator) -> (res: _WORD, err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
#force_inline internal_clear_if_uninitialized(a) or_return;
|
||||
return #force_inline internal_int_bitfield_extract(a, offset, count);
|
||||
}
|
||||
|
||||
/*
|
||||
Resize backing store.
|
||||
*/
|
||||
shrink :: proc(a: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
#force_inline internal_clear_if_uninitialized(a) or_return;
|
||||
return #force_inline internal_shrink(a);
|
||||
}
|
||||
|
||||
int_grow :: proc(a: ^Int, digits: int, allow_shrink := false, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return #force_inline internal_int_grow(a, digits, allow_shrink, allocator);
|
||||
}
|
||||
grow :: proc { int_grow, };
|
||||
|
||||
/*
|
||||
Clear `Int` and resize it to the default size.
|
||||
*/
|
||||
int_clear :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return #force_inline internal_int_clear(a, minimize, allocator);
|
||||
}
|
||||
clear :: proc { int_clear, };
|
||||
zero :: clear;
|
||||
|
||||
/*
|
||||
Set the `Int` to 1 and optionally shrink it to the minimum backing size.
|
||||
*/
|
||||
int_one :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return #force_inline internal_one(a, minimize, allocator);
|
||||
}
|
||||
one :: proc { int_one, };
|
||||
|
||||
/*
|
||||
Set the `Int` to -1 and optionally shrink it to the minimum backing size.
|
||||
*/
|
||||
int_minus_one :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return #force_inline internal_minus_one(a, minimize, allocator);
|
||||
}
|
||||
minus_one :: proc { int_minus_one, };
|
||||
|
||||
/*
|
||||
Set the `Int` to Inf and optionally shrink it to the minimum backing size.
|
||||
*/
|
||||
int_inf :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return #force_inline internal_inf(a, minimize, allocator);
|
||||
}
|
||||
inf :: proc { int_inf, };
|
||||
|
||||
/*
|
||||
Set the `Int` to -Inf and optionally shrink it to the minimum backing size.
|
||||
*/
|
||||
int_minus_inf :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return #force_inline internal_minus_inf(a, minimize, allocator);
|
||||
}
|
||||
minus_inf :: proc { int_inf, };
|
||||
|
||||
/*
|
||||
Set the `Int` to NaN and optionally shrink it to the minimum backing size.
|
||||
*/
|
||||
int_nan :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return #force_inline internal_nan(a, minimize, allocator);
|
||||
}
|
||||
nan :: proc { int_nan, };
|
||||
|
||||
power_of_two :: proc(a: ^Int, power: int, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return #force_inline internal_int_power_of_two(a, power, allocator);
|
||||
}
|
||||
|
||||
int_get_u128 :: proc(a: ^Int, allocator := context.allocator) -> (res: u128, err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return int_get(a, u128, allocator);
|
||||
}
|
||||
get_u128 :: proc { int_get_u128, };
|
||||
|
||||
int_get_i128 :: proc(a: ^Int, allocator := context.allocator) -> (res: i128, err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return int_get(a, i128, allocator);
|
||||
}
|
||||
get_i128 :: proc { int_get_i128, };
|
||||
|
||||
int_get_u64 :: proc(a: ^Int, allocator := context.allocator) -> (res: u64, err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return int_get(a, u64, allocator);
|
||||
}
|
||||
get_u64 :: proc { int_get_u64, };
|
||||
|
||||
int_get_i64 :: proc(a: ^Int, allocator := context.allocator) -> (res: i64, err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return int_get(a, i64, allocator);
|
||||
}
|
||||
get_i64 :: proc { int_get_i64, };
|
||||
|
||||
int_get_u32 :: proc(a: ^Int, allocator := context.allocator) -> (res: u32, err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return int_get(a, u32, allocator);
|
||||
}
|
||||
get_u32 :: proc { int_get_u32, };
|
||||
|
||||
int_get_i32 :: proc(a: ^Int, allocator := context.allocator) -> (res: i32, err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
return int_get(a, i32, allocator);
|
||||
}
|
||||
get_i32 :: proc { int_get_i32, };
|
||||
|
||||
/*
|
||||
TODO: Think about using `count_bits` to check if the value could be returned completely,
|
||||
and maybe return max(T), .Integer_Overflow if not?
|
||||
*/
|
||||
int_get :: proc(a: ^Int, $T: typeid, allocator := context.allocator) -> (res: T, err: Error) where intrinsics.type_is_integer(T) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
#force_inline internal_clear_if_uninitialized(a, allocator) or_return;
|
||||
return #force_inline internal_int_get(a, T);
|
||||
}
|
||||
get :: proc { int_get, };
|
||||
|
||||
int_get_float :: proc(a: ^Int, allocator := context.allocator) -> (res: f64, err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
#force_inline internal_clear_if_uninitialized(a, allocator) or_return;
|
||||
return #force_inline internal_int_get_float(a);
|
||||
}
|
||||
|
||||
/*
|
||||
Count bits in an `Int`.
|
||||
*/
|
||||
count_bits :: proc(a: ^Int, allocator := context.allocator) -> (count: int, err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
#force_inline internal_clear_if_uninitialized(a, allocator) or_return;
|
||||
return #force_inline internal_count_bits(a), nil;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the number of trailing zeroes before the first one.
|
||||
Differs from regular `ctz` in that 0 returns 0.
|
||||
*/
|
||||
int_count_lsb :: proc(a: ^Int, allocator := context.allocator) -> (count: int, err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(a);
|
||||
#force_inline internal_clear_if_uninitialized(a, allocator) or_return;
|
||||
return #force_inline internal_int_count_lsb(a);
|
||||
}
|
||||
|
||||
platform_count_lsb :: #force_inline proc(a: $T) -> (count: int)
|
||||
where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) {
|
||||
return int(intrinsics.count_trailing_zeros(a)) if a > 0 else 0;
|
||||
}
|
||||
|
||||
count_lsb :: proc { int_count_lsb, platform_count_lsb, };
|
||||
|
||||
int_random_digit :: proc(r: ^rnd.Rand = nil) -> (res: DIGIT) {
|
||||
when _DIGIT_BITS == 60 { // DIGIT = u64
|
||||
return DIGIT(rnd.uint64(r)) & _MASK;
|
||||
} else when _DIGIT_BITS == 28 { // DIGIT = u32
|
||||
return DIGIT(rnd.uint32(r)) & _MASK;
|
||||
} else {
|
||||
panic("Unsupported DIGIT size.");
|
||||
}
|
||||
|
||||
return 0; // We shouldn't get here.
|
||||
}
|
||||
|
||||
int_rand :: proc(dest: ^Int, bits: int, r: ^rnd.Rand = nil, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `a` is usable.
|
||||
*/
|
||||
assert_if_nil(dest);
|
||||
return #force_inline internal_int_rand(dest, bits, r, allocator);
|
||||
|
||||
}
|
||||
rand :: proc { int_rand, };
|
||||
|
||||
/*
|
||||
Internal helpers.
|
||||
*/
|
||||
assert_initialized :: proc(a: ^Int, loc := #caller_location) {
|
||||
assert_if_nil(a);
|
||||
assert(is_initialized(a), "`Int` was not properly initialized.", loc);
|
||||
}
|
||||
|
||||
zero_unused :: proc(dest: ^Int, old_used := -1) {
|
||||
assert_if_nil(dest);
|
||||
if ! #force_inline is_initialized(dest) { return; }
|
||||
|
||||
#force_inline internal_zero_unused(dest, old_used);
|
||||
}
|
||||
|
||||
clear_if_uninitialized_single :: proc(arg: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(arg);
|
||||
return #force_inline internal_clear_if_uninitialized_single(arg, allocator);
|
||||
}
|
||||
|
||||
clear_if_uninitialized_multi :: proc(args: ..^Int, allocator := context.allocator) -> (err: Error) {
|
||||
args := args;
|
||||
assert_if_nil(..args);
|
||||
|
||||
for i in &args {
|
||||
#force_inline internal_clear_if_uninitialized_single(i, allocator) or_return;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
clear_if_uninitialized :: proc {clear_if_uninitialized_single, clear_if_uninitialized_multi, };
|
||||
|
||||
error_if_immutable_single :: proc(arg: ^Int) -> (err: Error) {
|
||||
if arg != nil && .Immutable in arg.flags { return .Assignment_To_Immutable; }
|
||||
return nil;
|
||||
}
|
||||
|
||||
error_if_immutable_multi :: proc(args: ..^Int) -> (err: Error) {
|
||||
for i in args {
|
||||
if i != nil && .Immutable in i.flags { return .Assignment_To_Immutable; }
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
error_if_immutable :: proc {error_if_immutable_single, error_if_immutable_multi, };
|
||||
|
||||
/*
|
||||
Allocates several `Int`s at once.
|
||||
*/
|
||||
int_init_multi :: proc(integers: ..^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(..integers);
|
||||
|
||||
integers := integers;
|
||||
for a in &integers {
|
||||
#force_inline internal_clear(a, true, allocator) or_return;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
init_multi :: proc { int_init_multi, };
|
||||
|
||||
copy_digits :: proc(dest, src: ^Int, digits: int, offset := int(0), allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator;
|
||||
|
||||
/*
|
||||
Check that `src` is usable and `dest` isn't immutable.
|
||||
*/
|
||||
assert_if_nil(dest, src);
|
||||
#force_inline internal_clear_if_uninitialized(src) or_return;
|
||||
|
||||
return #force_inline internal_copy_digits(dest, src, digits, offset);
|
||||
}
|
||||
|
||||
/*
|
||||
Trim unused digits.
|
||||
|
||||
This is used to ensure that leading zero digits are trimmed and the leading "used" digit will be non-zero.
|
||||
Typically very fast. Also fixes the sign if there are no more leading digits.
|
||||
*/
|
||||
clamp :: proc(a: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(a);
|
||||
#force_inline internal_clear_if_uninitialized(a, allocator) or_return;
|
||||
|
||||
for a.used > 0 && a.digit[a.used - 1] == 0 {
|
||||
a.used -= 1;
|
||||
}
|
||||
|
||||
if z, _ := is_zero(a); z {
|
||||
a.sign = .Zero_or_Positive;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Size binary representation
|
||||
*/
|
||||
int_to_bytes_size :: proc(a: ^Int, signed := false, allocator := context.allocator) -> (size_in_bytes: int, err: Error) {
|
||||
assert_if_nil(a);
|
||||
#force_inline internal_clear_if_uninitialized(a, allocator) or_return;
|
||||
|
||||
size_in_bits := internal_count_bits(a);
|
||||
|
||||
size_in_bytes = (size_in_bits / 8);
|
||||
size_in_bytes += 0 if size_in_bits % 8 == 0 else 1;
|
||||
size_in_bytes += 1 if signed else 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Return Little Endian binary representation of `a`, either signed or unsigned.
|
||||
If `a` is negative and we ask for the default unsigned representation, we return abs(a).
|
||||
*/
|
||||
int_to_bytes_little :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(a);
|
||||
|
||||
size_in_bytes := int_to_bytes_size(a, signed, allocator) or_return;
|
||||
l := len(buf);
|
||||
if size_in_bytes > l { return .Buffer_Overflow; }
|
||||
|
||||
size_in_bits := internal_count_bits(a);
|
||||
i := 0;
|
||||
if signed {
|
||||
buf[l - 1] = 1 if a.sign == .Negative else 0;
|
||||
}
|
||||
for offset := 0; offset < size_in_bits; offset += 8 {
|
||||
bits, _ := internal_int_bitfield_extract(a, offset, 8);
|
||||
buf[i] = u8(bits & 255); i += 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Return Big Endian binary representation of `a`, either signed or unsigned.
|
||||
If `a` is negative and we ask for the default unsigned representation, we return abs(a).
|
||||
*/
|
||||
int_to_bytes_big :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(a);
|
||||
|
||||
size_in_bytes := int_to_bytes_size(a, signed, allocator) or_return;
|
||||
l := len(buf);
|
||||
if size_in_bytes > l { return .Buffer_Overflow; }
|
||||
|
||||
size_in_bits := internal_count_bits(a);
|
||||
i := l - 1;
|
||||
|
||||
if signed {
|
||||
buf[0] = 1 if a.sign == .Negative else 0;
|
||||
}
|
||||
for offset := 0; offset < size_in_bits; offset += 8 {
|
||||
bits, _ := internal_int_bitfield_extract(a, offset, 8);
|
||||
buf[i] = u8(bits & 255); i -= 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Return Python 3.x compatible Little Endian binary representation of `a`, either signed or unsigned.
|
||||
If `a` is negative when asking for an unsigned number, we return an error like Python does.
|
||||
*/
|
||||
int_to_bytes_little_python :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(a);
|
||||
|
||||
if !signed && a.sign == .Negative { return .Invalid_Argument; }
|
||||
|
||||
l := len(buf);
|
||||
size_in_bytes := int_to_bytes_size(a, signed, allocator) or_return;
|
||||
if size_in_bytes > l { return .Buffer_Overflow; }
|
||||
|
||||
if a.sign == .Negative {
|
||||
t := &Int{};
|
||||
defer destroy(t);
|
||||
internal_complement(t, a, allocator) or_return;
|
||||
|
||||
size_in_bits := internal_count_bits(t);
|
||||
i := 0;
|
||||
for offset := 0; offset < size_in_bits; offset += 8 {
|
||||
bits, _ := internal_int_bitfield_extract(t, offset, 8);
|
||||
buf[i] = 255 - u8(bits & 255); i += 1;
|
||||
}
|
||||
buf[l-1] = 255;
|
||||
} else {
|
||||
size_in_bits := internal_count_bits(a);
|
||||
i := 0;
|
||||
for offset := 0; offset < size_in_bits; offset += 8 {
|
||||
bits, _ := internal_int_bitfield_extract(a, offset, 8);
|
||||
buf[i] = u8(bits & 255); i += 1;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Return Python 3.x compatible Big Endian binary representation of `a`, either signed or unsigned.
|
||||
If `a` is negative when asking for an unsigned number, we return an error like Python does.
|
||||
*/
|
||||
int_to_bytes_big_python :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(a);
|
||||
|
||||
if !signed && a.sign == .Negative { return .Invalid_Argument; }
|
||||
if a.sign == .Zero_or_Positive { return int_to_bytes_big(a, buf, signed, allocator); }
|
||||
|
||||
l := len(buf);
|
||||
size_in_bytes := int_to_bytes_size(a, signed, allocator) or_return;
|
||||
if size_in_bytes > l { return .Buffer_Overflow; }
|
||||
|
||||
t := &Int{};
|
||||
defer destroy(t);
|
||||
|
||||
internal_complement(t, a, allocator) or_return;
|
||||
|
||||
size_in_bits := internal_count_bits(t);
|
||||
i := l - 1;
|
||||
for offset := 0; offset < size_in_bits; offset += 8 {
|
||||
bits, _ := internal_int_bitfield_extract(t, offset, 8);
|
||||
buf[i] = 255 - u8(bits & 255); i -= 1;
|
||||
}
|
||||
buf[0] = 255;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Read `Int` from a Big Endian binary representation.
|
||||
Sign is detected from the first byte if `signed` is true.
|
||||
*/
|
||||
int_from_bytes_big :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(a);
|
||||
buf := buf;
|
||||
l := len(buf);
|
||||
if l == 0 { return .Invalid_Argument; }
|
||||
|
||||
sign: Sign;
|
||||
size_in_bits := l * 8;
|
||||
if signed {
|
||||
/*
|
||||
First byte denotes the sign.
|
||||
*/
|
||||
size_in_bits -= 8;
|
||||
}
|
||||
size_in_digits := (size_in_bits + _DIGIT_BITS - 1) / _DIGIT_BITS;
|
||||
size_in_digits += 0 if size_in_bits % 8 == 0 else 1;
|
||||
internal_zero(a, false, allocator) or_return;
|
||||
internal_grow(a, size_in_digits, false, allocator) or_return;
|
||||
|
||||
if signed {
|
||||
sign = .Zero_or_Positive if buf[0] == 0 else .Negative;
|
||||
buf = buf[1:];
|
||||
}
|
||||
|
||||
for v in buf {
|
||||
internal_shl(a, a, 8) or_return;
|
||||
a.digit[0] |= DIGIT(v);
|
||||
}
|
||||
a.sign = sign;
|
||||
a.used = size_in_digits;
|
||||
return internal_clamp(a);
|
||||
}
|
||||
|
||||
/*
|
||||
Read `Int` from a Big Endian Python binary representation.
|
||||
Sign is detected from the first byte if `signed` is true.
|
||||
*/
|
||||
int_from_bytes_big_python :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(a);
|
||||
buf := buf;
|
||||
l := len(buf);
|
||||
if l == 0 { return .Invalid_Argument; }
|
||||
|
||||
sign: Sign;
|
||||
size_in_bits := l * 8;
|
||||
if signed {
|
||||
/*
|
||||
First byte denotes the sign.
|
||||
*/
|
||||
size_in_bits -= 8;
|
||||
}
|
||||
size_in_digits := (size_in_bits + _DIGIT_BITS - 1) / _DIGIT_BITS;
|
||||
size_in_digits += 0 if size_in_bits % 8 == 0 else 1;
|
||||
internal_zero(a, false, allocator) or_return;
|
||||
internal_grow(a, size_in_digits, false, allocator) or_return;
|
||||
|
||||
if signed {
|
||||
sign = .Zero_or_Positive if buf[0] == 0 else .Negative;
|
||||
buf = buf[1:];
|
||||
}
|
||||
|
||||
for v in buf {
|
||||
internal_shl(a, a, 8) or_return;
|
||||
if signed && sign == .Negative {
|
||||
a.digit[0] |= DIGIT(255 - v);
|
||||
} else {
|
||||
a.digit[0] |= DIGIT(v);
|
||||
}
|
||||
}
|
||||
a.sign = sign;
|
||||
a.used = size_in_digits;
|
||||
internal_clamp(a) or_return;
|
||||
|
||||
if signed && sign == .Negative {
|
||||
return internal_sub(a, a, 1);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*
|
||||
Read `Int` from a Little Endian binary representation.
|
||||
Sign is detected from the last byte if `signed` is true.
|
||||
*/
|
||||
int_from_bytes_little :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(a);
|
||||
buf := buf;
|
||||
l := len(buf);
|
||||
if l == 0 { return .Invalid_Argument; }
|
||||
|
||||
sign: Sign;
|
||||
size_in_bits := l * 8;
|
||||
if signed {
|
||||
/*
|
||||
First byte denotes the sign.
|
||||
*/
|
||||
size_in_bits -= 8;
|
||||
}
|
||||
size_in_digits := (size_in_bits + _DIGIT_BITS - 1) / _DIGIT_BITS;
|
||||
size_in_digits += 0 if size_in_bits % 8 == 0 else 1;
|
||||
internal_zero(a, false, allocator) or_return;
|
||||
internal_grow(a, size_in_digits, false, allocator) or_return;
|
||||
|
||||
if signed {
|
||||
sign = .Zero_or_Positive if buf[l-1] == 0 else .Negative;
|
||||
buf = buf[:l-1];
|
||||
l -= 1;
|
||||
}
|
||||
|
||||
for _, i in buf {
|
||||
internal_shl(a, a, 8) or_return;
|
||||
a.digit[0] |= DIGIT(buf[l-i-1]);
|
||||
}
|
||||
a.sign = sign;
|
||||
a.used = size_in_digits;
|
||||
return internal_clamp(a);
|
||||
}
|
||||
|
||||
/*
|
||||
Read `Int` from a Little Endian Python binary representation.
|
||||
Sign is detected from the first byte if `signed` is true.
|
||||
*/
|
||||
int_from_bytes_little_python :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(a);
|
||||
buf := buf;
|
||||
l := len(buf);
|
||||
if l == 0 { return .Invalid_Argument; }
|
||||
|
||||
sign: Sign;
|
||||
size_in_bits := l * 8;
|
||||
if signed {
|
||||
/*
|
||||
First byte denotes the sign.
|
||||
*/
|
||||
size_in_bits -= 8;
|
||||
}
|
||||
size_in_digits := (size_in_bits + _DIGIT_BITS - 1) / _DIGIT_BITS;
|
||||
size_in_digits += 0 if size_in_bits % 8 == 0 else 1;
|
||||
internal_zero(a, false, allocator) or_return;
|
||||
internal_grow(a, size_in_digits, false, allocator) or_return;
|
||||
|
||||
if signed {
|
||||
sign = .Zero_or_Positive if buf[l-1] == 0 else .Negative;
|
||||
buf = buf[:l-1];
|
||||
l -= 1;
|
||||
}
|
||||
|
||||
for _, i in buf {
|
||||
internal_shl(a, a, 8) or_return;
|
||||
if signed && sign == .Negative {
|
||||
a.digit[0] |= DIGIT(255 - buf[l-i-1]);
|
||||
} else {
|
||||
a.digit[0] |= DIGIT(buf[l-i-1]);
|
||||
}
|
||||
}
|
||||
a.sign = sign;
|
||||
a.used = size_in_digits;
|
||||
internal_clamp(a) or_return;
|
||||
|
||||
if signed && sign == .Negative {
|
||||
return internal_sub(a, a, 1);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize constants.
|
||||
*/
|
||||
INT_ONE, INT_ZERO, INT_MINUS_ONE, INT_INF, INT_MINUS_INF, INT_NAN := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{};
|
||||
|
||||
initialize_constants :: proc() -> (res: int) {
|
||||
internal_set( INT_ZERO, 0); INT_ZERO.flags = {.Immutable};
|
||||
internal_set( INT_ONE, 1); INT_ONE.flags = {.Immutable};
|
||||
internal_set(INT_MINUS_ONE, -1); INT_MINUS_ONE.flags = {.Immutable};
|
||||
|
||||
/*
|
||||
We set these special values to -1 or 1 so they don't get mistake for zero accidentally.
|
||||
This allows for shortcut tests of is_zero as .used == 0.
|
||||
*/
|
||||
internal_set( INT_NAN, 1); INT_NAN.flags = {.Immutable, .NaN};
|
||||
internal_set( INT_INF, 1); INT_INF.flags = {.Immutable, .Inf};
|
||||
internal_set( INT_INF, -1); INT_MINUS_INF.flags = {.Immutable, .Inf};
|
||||
|
||||
return _DEFAULT_MUL_KARATSUBA_CUTOFF;
|
||||
}
|
||||
|
||||
/*
|
||||
Destroy constants.
|
||||
Optional for an EXE, as this would be called at the very end of a process.
|
||||
*/
|
||||
destroy_constants :: proc() {
|
||||
internal_destroy(INT_ONE, INT_ZERO, INT_MINUS_ONE, INT_INF, INT_MINUS_INF, INT_NAN);
|
||||
}
|
||||
|
||||
|
||||
assert_if_nil :: #force_inline proc(integers: ..^Int, loc := #caller_location) {
|
||||
integers := integers;
|
||||
|
||||
for i in &integers {
|
||||
assert(i != nil, "(nil)", loc);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,144 @@
|
||||
package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
|
||||
|
||||
This file contains logical operations like `and`, `or` and `xor`.
|
||||
*/
|
||||
|
||||
/*
|
||||
The `and`, `or` and `xor` binops differ in two lines only.
|
||||
We could handle those with a switch, but that adds overhead.
|
||||
|
||||
TODO: Implement versions that take a DIGIT immediate.
|
||||
*/
|
||||
|
||||
/*
|
||||
2's complement `and`, returns `dest = a & b;`
|
||||
*/
|
||||
int_and :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, a, b);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a, b) or_return;
|
||||
return #force_inline internal_int_and(dest, a, b);
|
||||
}
|
||||
and :: proc { int_and, };
|
||||
|
||||
/*
|
||||
2's complement `or`, returns `dest = a | b;`
|
||||
*/
|
||||
int_or :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, a, b);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a, b) or_return;
|
||||
return #force_inline internal_int_or(dest, a, b);
|
||||
}
|
||||
or :: proc { int_or, };
|
||||
|
||||
/*
|
||||
2's complement `xor`, returns `dest = a ^ b;`
|
||||
*/
|
||||
int_xor :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, a, b);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a, b) or_return;
|
||||
return #force_inline internal_int_xor(dest, a, b);
|
||||
}
|
||||
xor :: proc { int_xor, };
|
||||
|
||||
/*
|
||||
dest = ~src
|
||||
*/
|
||||
int_complement :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `src` and `dest` are usable.
|
||||
*/
|
||||
assert_if_nil(dest, src);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(dest, src) or_return;
|
||||
return #force_inline internal_int_complement(dest, src);
|
||||
}
|
||||
complement :: proc { int_complement, };
|
||||
|
||||
/*
|
||||
quotient, remainder := numerator >> bits;
|
||||
`remainder` is allowed to be passed a `nil`, in which case `mod` won't be computed.
|
||||
*/
|
||||
int_shrmod :: proc(quotient, remainder, numerator: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(quotient, numerator);
|
||||
context.allocator = allocator;
|
||||
|
||||
if err = internal_clear_if_uninitialized(quotient, numerator); err != nil { return err; }
|
||||
return #force_inline internal_int_shrmod(quotient, remainder, numerator, bits);
|
||||
}
|
||||
shrmod :: proc { int_shrmod, };
|
||||
|
||||
int_shr :: proc(dest, source: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
|
||||
return #force_inline shrmod(dest, nil, source, bits, allocator);
|
||||
}
|
||||
shr :: proc { int_shr, };
|
||||
|
||||
/*
|
||||
Shift right by `digits` * _DIGIT_BITS bits.
|
||||
*/
|
||||
int_shr_digit :: proc(quotient: ^Int, digits: int, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `quotient` is usable.
|
||||
*/
|
||||
assert_if_nil(quotient);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(quotient) or_return;
|
||||
return #force_inline internal_int_shr_digit(quotient, digits);
|
||||
}
|
||||
shr_digit :: proc { int_shr_digit, };
|
||||
|
||||
/*
|
||||
Shift right by a certain bit count with sign extension.
|
||||
*/
|
||||
int_shr_signed :: proc(dest, src: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, src);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(dest, src) or_return;
|
||||
return #force_inline internal_int_shr_signed(dest, src, bits);
|
||||
}
|
||||
|
||||
shr_signed :: proc { int_shr_signed, };
|
||||
|
||||
/*
|
||||
Shift left by a certain bit count.
|
||||
*/
|
||||
int_shl :: proc(dest, src: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, src);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(dest, src) or_return;
|
||||
return #force_inline internal_int_shl(dest, src, bits);
|
||||
}
|
||||
shl :: proc { int_shl, };
|
||||
|
||||
|
||||
/*
|
||||
Shift left by `digits` * _DIGIT_BITS bits.
|
||||
*/
|
||||
int_shl_digit :: proc(quotient: ^Int, digits: int, allocator := context.allocator) -> (err: Error) {
|
||||
/*
|
||||
Check that `quotient` is usable.
|
||||
*/
|
||||
assert_if_nil(quotient);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(quotient) or_return;
|
||||
return #force_inline internal_int_shl_digit(quotient, digits);
|
||||
}
|
||||
shl_digit :: proc { int_shl_digit, };
|
||||
@@ -0,0 +1,256 @@
|
||||
package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
|
||||
|
||||
This file contains prime finding operations.
|
||||
*/
|
||||
|
||||
/*
|
||||
Determines if an Integer is divisible by one of the _PRIME_TABLE primes.
|
||||
Returns true if it is, false if not.
|
||||
*/
|
||||
int_prime_is_divisible :: proc(a: ^Int, allocator := context.allocator) -> (res: bool, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
|
||||
for prime in _private_prime_table {
|
||||
rem := #force_inline int_mod_digit(a, prime) or_return;
|
||||
if rem == 0 {
|
||||
return true, nil;
|
||||
}
|
||||
}
|
||||
/*
|
||||
Default to not divisible.
|
||||
*/
|
||||
return false, nil;
|
||||
}
|
||||
|
||||
/*
|
||||
Computes xR**-1 == x (mod N) via Montgomery Reduction.
|
||||
*/
|
||||
internal_int_montgomery_reduce :: proc(x, n: ^Int, rho: DIGIT, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator;
|
||||
/*
|
||||
Can the fast reduction [comba] method be used?
|
||||
Note that unlike in mul, you're safely allowed *less* than the available columns [255 per default],
|
||||
since carries are fixed up in the inner loop.
|
||||
*/
|
||||
digs := (n.used * 2) + 1;
|
||||
if digs < _WARRAY && x.used <= _WARRAY && n.used < _MAX_COMBA {
|
||||
return _private_montgomery_reduce_comba(x, n, rho);
|
||||
}
|
||||
|
||||
/*
|
||||
Grow the input as required
|
||||
*/
|
||||
internal_grow(x, digs) or_return;
|
||||
x.used = digs;
|
||||
|
||||
for ix := 0; ix < n.used; ix += 1 {
|
||||
/*
|
||||
`mu = ai * rho mod b`
|
||||
The value of rho must be precalculated via `int_montgomery_setup()`,
|
||||
such that it equals -1/n0 mod b this allows the following inner loop
|
||||
to reduce the input one digit at a time.
|
||||
*/
|
||||
|
||||
mu := DIGIT((_WORD(x.digit[ix]) * _WORD(rho)) & _WORD(_MASK));
|
||||
|
||||
/*
|
||||
a = a + mu * m * b**i
|
||||
Multiply and add in place.
|
||||
*/
|
||||
u := DIGIT(0);
|
||||
iy := int(0);
|
||||
for ; iy < n.used; iy += 1 {
|
||||
/*
|
||||
Compute product and sum.
|
||||
*/
|
||||
r := (_WORD(mu) * _WORD(n.digit[iy]) + _WORD(u) + _WORD(x.digit[ix + iy]));
|
||||
|
||||
/*
|
||||
Get carry.
|
||||
*/
|
||||
u = DIGIT(r >> _DIGIT_BITS);
|
||||
|
||||
/*
|
||||
Fix digit.
|
||||
*/
|
||||
x.digit[ix + iy] = DIGIT(r & _WORD(_MASK));
|
||||
}
|
||||
|
||||
/*
|
||||
At this point the ix'th digit of x should be zero.
|
||||
Propagate carries upwards as required.
|
||||
*/
|
||||
for u != 0 {
|
||||
x.digit[ix + iy] += u;
|
||||
u = x.digit[ix + iy] >> _DIGIT_BITS;
|
||||
x.digit[ix + iy] &= _MASK;
|
||||
iy += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
At this point the n.used'th least significant digits of x are all zero,
|
||||
which means we can shift x to the right by n.used digits and the
|
||||
residue is unchanged.
|
||||
|
||||
x = x/b**n.used.
|
||||
*/
|
||||
internal_clamp(x);
|
||||
internal_shr_digit(x, n.used);
|
||||
|
||||
/*
|
||||
if x >= n then x = x - n
|
||||
*/
|
||||
if internal_cmp_mag(x, n) != -1 {
|
||||
return internal_sub(x, x, n);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
int_montgomery_reduce :: proc(x, n: ^Int, rho: DIGIT, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(x, n);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(x, n) or_return;
|
||||
|
||||
return #force_inline internal_int_montgomery_reduce(x, n, rho);
|
||||
}
|
||||
|
||||
/*
|
||||
Shifts with subtractions when the result is greater than b.
|
||||
|
||||
The method is slightly modified to shift B unconditionally upto just under
|
||||
the leading bit of b. This saves alot of multiple precision shifting.
|
||||
*/
|
||||
internal_int_montgomery_calc_normalization :: proc(a, b: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator;
|
||||
/*
|
||||
How many bits of last digit does b use.
|
||||
*/
|
||||
bits := internal_count_bits(b) % _DIGIT_BITS;
|
||||
|
||||
if b.used > 1 {
|
||||
power := ((b.used - 1) * _DIGIT_BITS) + bits - 1;
|
||||
internal_int_power_of_two(a, power) or_return;
|
||||
} else {
|
||||
internal_one(a);
|
||||
bits = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Now compute C = A * B mod b.
|
||||
*/
|
||||
for x := bits - 1; x < _DIGIT_BITS; x += 1 {
|
||||
internal_int_shl1(a, a) or_return;
|
||||
if internal_cmp_mag(a, b) != -1 {
|
||||
internal_sub(a, a, b) or_return;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
int_montgomery_calc_normalization :: proc(a, b: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(a, b);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a, b) or_return;
|
||||
|
||||
return #force_inline internal_int_montgomery_calc_normalization(a, b);
|
||||
}
|
||||
|
||||
/*
|
||||
Sets up the Montgomery reduction stuff.
|
||||
*/
|
||||
internal_int_montgomery_setup :: proc(n: ^Int) -> (rho: DIGIT, err: Error) {
|
||||
/*
|
||||
Fast inversion mod 2**k
|
||||
Based on the fact that:
|
||||
|
||||
XA = 1 (mod 2**n) => (X(2-XA)) A = 1 (mod 2**2n)
|
||||
=> 2*X*A - X*X*A*A = 1
|
||||
=> 2*(1) - (1) = 1
|
||||
*/
|
||||
b := n.digit[0];
|
||||
if b & 1 == 0 { return 0, .Invalid_Argument; }
|
||||
|
||||
x := (((b + 2) & 4) << 1) + b; /* here x*a==1 mod 2**4 */
|
||||
x *= 2 - (b * x); /* here x*a==1 mod 2**8 */
|
||||
x *= 2 - (b * x); /* here x*a==1 mod 2**16 */
|
||||
when _WORD_TYPE_BITS == 64 {
|
||||
x *= 2 - (b * x); /* here x*a==1 mod 2**32 */
|
||||
x *= 2 - (b * x); /* here x*a==1 mod 2**64 */
|
||||
}
|
||||
|
||||
/*
|
||||
rho = -1/m mod b
|
||||
*/
|
||||
rho = DIGIT(((_WORD(1) << _WORD(_DIGIT_BITS)) - _WORD(x)) & _WORD(_MASK));
|
||||
return rho, nil;
|
||||
}
|
||||
|
||||
int_montgomery_setup :: proc(n: ^Int, allocator := context.allocator) -> (rho: DIGIT, err: Error) {
|
||||
assert_if_nil(n);
|
||||
internal_clear_if_uninitialized(n, allocator) or_return;
|
||||
|
||||
return #force_inline internal_int_montgomery_setup(n);
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the number of Rabin-Miller trials needed for a given bit size.
|
||||
*/
|
||||
number_of_rabin_miller_trials :: proc(bit_size: int) -> (number_of_trials: int) {
|
||||
switch {
|
||||
case bit_size <= 80:
|
||||
return - 1; /* Use deterministic algorithm for size <= 80 bits */
|
||||
case bit_size >= 81 && bit_size < 96:
|
||||
return 37; /* max. error = 2^(-96) */
|
||||
case bit_size >= 96 && bit_size < 128:
|
||||
return 32; /* max. error = 2^(-96) */
|
||||
case bit_size >= 128 && bit_size < 160:
|
||||
return 40; /* max. error = 2^(-112) */
|
||||
case bit_size >= 160 && bit_size < 256:
|
||||
return 35; /* max. error = 2^(-112) */
|
||||
case bit_size >= 256 && bit_size < 384:
|
||||
return 27; /* max. error = 2^(-128) */
|
||||
case bit_size >= 384 && bit_size < 512:
|
||||
return 16; /* max. error = 2^(-128) */
|
||||
case bit_size >= 512 && bit_size < 768:
|
||||
return 18; /* max. error = 2^(-160) */
|
||||
case bit_size >= 768 && bit_size < 896:
|
||||
return 11; /* max. error = 2^(-160) */
|
||||
case bit_size >= 896 && bit_size < 1_024:
|
||||
return 10; /* max. error = 2^(-160) */
|
||||
case bit_size >= 1_024 && bit_size < 1_536:
|
||||
return 12; /* max. error = 2^(-192) */
|
||||
case bit_size >= 1_536 && bit_size < 2_048:
|
||||
return 8; /* max. error = 2^(-192) */
|
||||
case bit_size >= 2_048 && bit_size < 3_072:
|
||||
return 6; /* max. error = 2^(-192) */
|
||||
case bit_size >= 3_072 && bit_size < 4_096:
|
||||
return 4; /* max. error = 2^(-192) */
|
||||
case bit_size >= 4_096 && bit_size < 5_120:
|
||||
return 5; /* max. error = 2^(-256) */
|
||||
case bit_size >= 5_120 && bit_size < 6_144:
|
||||
return 4; /* max. error = 2^(-256) */
|
||||
case bit_size >= 6_144 && bit_size < 8_192:
|
||||
return 4; /* max. error = 2^(-256) */
|
||||
case bit_size >= 8_192 && bit_size < 9_216:
|
||||
return 3; /* max. error = 2^(-256) */
|
||||
case bit_size >= 9_216 && bit_size < 10_240:
|
||||
return 3; /* max. error = 2^(-256) */
|
||||
case:
|
||||
return 2; /* For keysizes bigger than 10_240 use always at least 2 Rounds */
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,573 @@
|
||||
package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
|
||||
|
||||
This file contains basic arithmetic operations like `add`, `sub`, `mul`, `div`, ...
|
||||
*/
|
||||
|
||||
/*
|
||||
===========================
|
||||
User-level routines
|
||||
===========================
|
||||
*/
|
||||
|
||||
/*
|
||||
High-level addition. Handles sign.
|
||||
*/
|
||||
int_add :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, a, b);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(dest, a, b) or_return;
|
||||
/*
|
||||
All parameters have been initialized.
|
||||
*/
|
||||
return #force_inline internal_int_add_signed(dest, a, b);
|
||||
}
|
||||
|
||||
/*
|
||||
Adds the unsigned `DIGIT` immediate to an `Int`,
|
||||
such that the `DIGIT` doesn't have to be turned into an `Int` first.
|
||||
|
||||
dest = a + digit;
|
||||
*/
|
||||
int_add_digit :: proc(dest, a: ^Int, digit: DIGIT, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
/*
|
||||
Grow destination as required.
|
||||
*/
|
||||
grow(dest, a.used + 1) or_return;
|
||||
|
||||
/*
|
||||
All parameters have been initialized.
|
||||
*/
|
||||
return #force_inline internal_int_add_digit(dest, a, digit);
|
||||
}
|
||||
|
||||
/*
|
||||
High-level subtraction, dest = number - decrease. Handles signs.
|
||||
*/
|
||||
int_sub :: proc(dest, number, decrease: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, number, decrease);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(dest, number, decrease) or_return;
|
||||
/*
|
||||
All parameters have been initialized.
|
||||
*/
|
||||
return #force_inline internal_int_sub_signed(dest, number, decrease);
|
||||
}
|
||||
|
||||
/*
|
||||
Adds the unsigned `DIGIT` immediate to an `Int`,
|
||||
such that the `DIGIT` doesn't have to be turned into an `Int` first.
|
||||
|
||||
dest = a - digit;
|
||||
*/
|
||||
int_sub_digit :: proc(dest, a: ^Int, digit: DIGIT, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
/*
|
||||
Grow destination as required.
|
||||
*/
|
||||
grow(dest, a.used + 1) or_return;
|
||||
|
||||
/*
|
||||
All parameters have been initialized.
|
||||
*/
|
||||
return #force_inline internal_int_sub_digit(dest, a, digit);
|
||||
}
|
||||
|
||||
/*
|
||||
dest = src / 2
|
||||
dest = src >> 1
|
||||
*/
|
||||
int_halve :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, src);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(dest, src) or_return;
|
||||
/*
|
||||
Grow destination as required.
|
||||
*/
|
||||
if dest != src { grow(dest, src.used + 1) or_return }
|
||||
|
||||
return #force_inline internal_int_shr1(dest, src);
|
||||
}
|
||||
halve :: proc { int_halve, };
|
||||
shr1 :: halve;
|
||||
|
||||
/*
|
||||
dest = src * 2
|
||||
dest = src << 1
|
||||
*/
|
||||
int_double :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, src);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(dest, src) or_return;
|
||||
/*
|
||||
Grow destination as required.
|
||||
*/
|
||||
if dest != src { grow(dest, src.used + 1) or_return; }
|
||||
|
||||
return #force_inline internal_int_shl1(dest, src);
|
||||
}
|
||||
double :: proc { int_double, };
|
||||
shl1 :: double;
|
||||
|
||||
/*
|
||||
Multiply by a DIGIT.
|
||||
*/
|
||||
int_mul_digit :: proc(dest, src: ^Int, multiplier: DIGIT, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, src);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(src, dest) or_return;
|
||||
|
||||
return #force_inline internal_int_mul_digit(dest, src, multiplier);
|
||||
}
|
||||
|
||||
/*
|
||||
High level multiplication (handles sign).
|
||||
*/
|
||||
int_mul :: proc(dest, src, multiplier: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, src, multiplier);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(dest, src, multiplier) or_return;
|
||||
|
||||
return #force_inline internal_int_mul(dest, src, multiplier);
|
||||
}
|
||||
|
||||
mul :: proc { int_mul, int_mul_digit, };
|
||||
|
||||
sqr :: proc(dest, src: ^Int) -> (err: Error) { return mul(dest, src, src); }
|
||||
|
||||
/*
|
||||
divmod.
|
||||
Both the quotient and remainder are optional and may be passed a nil.
|
||||
*/
|
||||
int_divmod :: proc(quotient, remainder, numerator, denominator: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator;
|
||||
|
||||
/*
|
||||
Early out if neither of the results is wanted.
|
||||
*/
|
||||
if quotient == nil && remainder == nil { return nil; }
|
||||
internal_clear_if_uninitialized(numerator, denominator) or_return;
|
||||
|
||||
return #force_inline internal_divmod(quotient, remainder, numerator, denominator);
|
||||
}
|
||||
|
||||
int_divmod_digit :: proc(quotient, numerator: ^Int, denominator: DIGIT, allocator := context.allocator) -> (remainder: DIGIT, err: Error) {
|
||||
assert_if_nil(quotient, numerator);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(numerator) or_return;
|
||||
|
||||
return #force_inline internal_divmod(quotient, numerator, denominator);
|
||||
}
|
||||
divmod :: proc{ int_divmod, int_divmod_digit, };
|
||||
|
||||
int_div :: proc(quotient, numerator, denominator: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(quotient, numerator, denominator);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(numerator, denominator) or_return;
|
||||
|
||||
return #force_inline internal_divmod(quotient, nil, numerator, denominator);
|
||||
}
|
||||
|
||||
int_div_digit :: proc(quotient, numerator: ^Int, denominator: DIGIT, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(quotient, numerator);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(numerator) or_return;
|
||||
|
||||
_ = #force_inline internal_divmod(quotient, numerator, denominator) or_return;
|
||||
return;
|
||||
}
|
||||
div :: proc { int_div, int_div_digit, };
|
||||
|
||||
/*
|
||||
remainder = numerator % denominator.
|
||||
0 <= remainder < denominator if denominator > 0
|
||||
denominator < remainder <= 0 if denominator < 0
|
||||
*/
|
||||
int_mod :: proc(remainder, numerator, denominator: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(remainder, numerator, denominator);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(numerator, denominator) or_return;
|
||||
|
||||
return #force_inline internal_int_mod(remainder, numerator, denominator);
|
||||
}
|
||||
|
||||
int_mod_digit :: proc(numerator: ^Int, denominator: DIGIT, allocator := context.allocator) -> (remainder: DIGIT, err: Error) {
|
||||
return #force_inline internal_divmod(nil, numerator, denominator, allocator);
|
||||
}
|
||||
|
||||
mod :: proc { int_mod, int_mod_digit, };
|
||||
|
||||
/*
|
||||
remainder = (number + addend) % modulus.
|
||||
*/
|
||||
int_addmod :: proc(remainder, number, addend, modulus: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(remainder, number, addend);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(number, addend, modulus) or_return;
|
||||
|
||||
return #force_inline internal_addmod(remainder, number, addend, modulus);
|
||||
}
|
||||
addmod :: proc { int_addmod, };
|
||||
|
||||
/*
|
||||
remainder = (number - decrease) % modulus.
|
||||
*/
|
||||
int_submod :: proc(remainder, number, decrease, modulus: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(remainder, number, decrease);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(number, decrease, modulus) or_return;
|
||||
|
||||
return #force_inline internal_submod(remainder, number, decrease, modulus);
|
||||
}
|
||||
submod :: proc { int_submod, };
|
||||
|
||||
/*
|
||||
remainder = (number * multiplicand) % modulus.
|
||||
*/
|
||||
int_mulmod :: proc(remainder, number, multiplicand, modulus: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(remainder, number, multiplicand);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(number, multiplicand, modulus) or_return;
|
||||
|
||||
return #force_inline internal_mulmod(remainder, number, multiplicand, modulus);
|
||||
}
|
||||
mulmod :: proc { int_mulmod, };
|
||||
|
||||
/*
|
||||
remainder = (number * number) % modulus.
|
||||
*/
|
||||
int_sqrmod :: proc(remainder, number, modulus: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(remainder, number, modulus);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(number, modulus) or_return;
|
||||
|
||||
return #force_inline internal_sqrmod(remainder, number, modulus);
|
||||
}
|
||||
sqrmod :: proc { int_sqrmod, };
|
||||
|
||||
|
||||
int_factorial :: proc(res: ^Int, n: int, allocator := context.allocator) -> (err: Error) {
|
||||
if n < 0 || n > FACTORIAL_MAX_N { return .Invalid_Argument; }
|
||||
assert_if_nil(res);
|
||||
|
||||
return #force_inline internal_int_factorial(res, n, allocator);
|
||||
}
|
||||
factorial :: proc { int_factorial, };
|
||||
|
||||
|
||||
/*
|
||||
Number of ways to choose `k` items from `n` items.
|
||||
Also known as the binomial coefficient.
|
||||
|
||||
TODO: Speed up.
|
||||
|
||||
Could be done faster by reusing code from factorial and reusing the common "prefix" results for n!, k! and n-k!
|
||||
We know that n >= k, otherwise we early out with res = 0.
|
||||
|
||||
So:
|
||||
n-k, keep result
|
||||
n, start from previous result
|
||||
k, start from previous result
|
||||
|
||||
*/
|
||||
int_choose_digit :: proc(res: ^Int, n, k: int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(res);
|
||||
context.allocator = allocator;
|
||||
|
||||
if n < 0 || n > FACTORIAL_MAX_N { return .Invalid_Argument; }
|
||||
if k > n { return internal_zero(res); }
|
||||
|
||||
/*
|
||||
res = n! / (k! * (n - k)!)
|
||||
*/
|
||||
n_fac, k_fac, n_minus_k_fac := &Int{}, &Int{}, &Int{};
|
||||
defer internal_destroy(n_fac, k_fac, n_minus_k_fac);
|
||||
|
||||
#force_inline internal_int_factorial(n_minus_k_fac, n - k) or_return;
|
||||
#force_inline internal_int_factorial(k_fac, k) or_return;
|
||||
#force_inline internal_mul(k_fac, k_fac, n_minus_k_fac) or_return;
|
||||
|
||||
#force_inline internal_int_factorial(n_fac, n) or_return;
|
||||
#force_inline internal_div(res, n_fac, k_fac) or_return;
|
||||
|
||||
return;
|
||||
}
|
||||
choose :: proc { int_choose_digit, };
|
||||
|
||||
/*
|
||||
Function computing both GCD and (if target isn't `nil`) also LCM.
|
||||
*/
|
||||
int_gcd_lcm :: proc(res_gcd, res_lcm, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
if res_gcd == nil && res_lcm == nil { return nil; }
|
||||
assert_if_nil(a, b);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a, b) or_return;
|
||||
return #force_inline internal_int_gcd_lcm(res_gcd, res_lcm, a, b);
|
||||
}
|
||||
gcd_lcm :: proc { int_gcd_lcm, };
|
||||
|
||||
/*
|
||||
Greatest Common Divisor.
|
||||
*/
|
||||
int_gcd :: proc(res, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
return #force_inline int_gcd_lcm(res, nil, a, b, allocator);
|
||||
}
|
||||
gcd :: proc { int_gcd, };
|
||||
|
||||
/*
|
||||
Least Common Multiple.
|
||||
*/
|
||||
int_lcm :: proc(res, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
return #force_inline int_gcd_lcm(nil, res, a, b, allocator);
|
||||
}
|
||||
lcm :: proc { int_lcm, };
|
||||
|
||||
/*
|
||||
remainder = numerator % (1 << bits)
|
||||
*/
|
||||
int_mod_bits :: proc(remainder, numerator: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(remainder, numerator);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(remainder, numerator) or_return;
|
||||
if bits < 0 { return .Invalid_Argument; }
|
||||
|
||||
return #force_inline internal_int_mod_bits(remainder, numerator, bits);
|
||||
}
|
||||
|
||||
mod_bits :: proc { int_mod_bits, };
|
||||
|
||||
|
||||
/*
|
||||
Logs and roots and such.
|
||||
*/
|
||||
int_log :: proc(a: ^Int, base: DIGIT, allocator := context.allocator) -> (res: int, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
|
||||
return #force_inline internal_int_log(a, base);
|
||||
}
|
||||
|
||||
digit_log :: proc(a: DIGIT, base: DIGIT) -> (log: int, err: Error) {
|
||||
return #force_inline internal_digit_log(a, base);
|
||||
}
|
||||
log :: proc { int_log, digit_log, };
|
||||
|
||||
/*
|
||||
Calculate `dest = base^power` using a square-multiply algorithm.
|
||||
*/
|
||||
int_pow :: proc(dest, base: ^Int, power: int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, base);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(dest, base) or_return;
|
||||
|
||||
return #force_inline internal_int_pow(dest, base, power);
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate `dest = base^power` using a square-multiply algorithm.
|
||||
*/
|
||||
int_pow_int :: proc(dest: ^Int, base, power: int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest);
|
||||
|
||||
return #force_inline internal_pow(dest, base, power, allocator);
|
||||
}
|
||||
|
||||
pow :: proc { int_pow, int_pow_int, small_pow, };
|
||||
exp :: pow;
|
||||
|
||||
small_pow :: proc(base: _WORD, exponent: _WORD) -> (result: _WORD) {
|
||||
return #force_inline internal_small_pow(base, exponent);
|
||||
}
|
||||
|
||||
/*
|
||||
This function is less generic than `root_n`, simpler and faster.
|
||||
*/
|
||||
int_sqrt :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(dest, src);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(dest, src) or_return;
|
||||
|
||||
return #force_inline internal_int_sqrt(dest, src);
|
||||
}
|
||||
sqrt :: proc { int_sqrt, };
|
||||
|
||||
|
||||
/*
|
||||
Find the nth root of an Integer.
|
||||
Result found such that `(dest)**n <= src` and `(dest+1)**n > src`
|
||||
|
||||
This algorithm uses Newton's approximation `x[i+1] = x[i] - f(x[i])/f'(x[i])`,
|
||||
which will find the root in `log(n)` time where each step involves a fair bit.
|
||||
*/
|
||||
int_root_n :: proc(dest, src: ^Int, n: int, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator;
|
||||
|
||||
/*
|
||||
Fast path for n == 2.
|
||||
*/
|
||||
if n == 2 { return sqrt(dest, src); }
|
||||
|
||||
assert_if_nil(dest, src);
|
||||
/*
|
||||
Initialize dest + src if needed.
|
||||
*/
|
||||
internal_clear_if_uninitialized(dest, src) or_return;
|
||||
|
||||
return #force_inline internal_int_root_n(dest, src, n);
|
||||
}
|
||||
root_n :: proc { int_root_n, };
|
||||
|
||||
/*
|
||||
Comparison routines.
|
||||
*/
|
||||
|
||||
int_is_initialized :: proc(a: ^Int) -> bool {
|
||||
if a == nil { return false; }
|
||||
|
||||
return #force_inline internal_int_is_initialized(a);
|
||||
}
|
||||
|
||||
int_is_zero :: proc(a: ^Int, allocator := context.allocator) -> (zero: bool, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
|
||||
return #force_inline internal_is_zero(a), nil;
|
||||
}
|
||||
|
||||
int_is_positive :: proc(a: ^Int, allocator := context.allocator) -> (positive: bool, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
|
||||
return #force_inline internal_is_positive(a), nil;
|
||||
}
|
||||
|
||||
int_is_negative :: proc(a: ^Int, allocator := context.allocator) -> (negative: bool, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
|
||||
return #force_inline internal_is_negative(a), nil;
|
||||
}
|
||||
|
||||
int_is_even :: proc(a: ^Int, allocator := context.allocator) -> (even: bool, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
|
||||
return #force_inline internal_is_even(a), nil;
|
||||
}
|
||||
|
||||
int_is_odd :: proc(a: ^Int, allocator := context.allocator) -> (odd: bool, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
|
||||
return #force_inline internal_is_odd(a), nil;
|
||||
}
|
||||
|
||||
platform_int_is_power_of_two :: #force_inline proc(a: int) -> bool {
|
||||
return ((a) != 0) && (((a) & ((a) - 1)) == 0);
|
||||
}
|
||||
|
||||
int_is_power_of_two :: proc(a: ^Int, allocator := context.allocator) -> (res: bool, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
|
||||
return #force_inline internal_is_power_of_two(a), nil;
|
||||
}
|
||||
|
||||
/*
|
||||
Compare two `Int`s, signed.
|
||||
*/
|
||||
int_compare :: proc(a, b: ^Int, allocator := context.allocator) -> (comparison: int, err: Error) {
|
||||
assert_if_nil(a, b);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a, b) or_return;
|
||||
|
||||
return #force_inline internal_cmp(a, b), nil;
|
||||
}
|
||||
int_cmp :: int_compare;
|
||||
|
||||
/*
|
||||
Compare an `Int` to an unsigned number upto the size of the backing type.
|
||||
*/
|
||||
int_compare_digit :: proc(a: ^Int, b: DIGIT, allocator := context.allocator) -> (comparison: int, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
|
||||
return #force_inline internal_cmp_digit(a, b), nil;
|
||||
}
|
||||
int_cmp_digit :: int_compare_digit;
|
||||
|
||||
/*
|
||||
Compare the magnitude of two `Int`s, unsigned.
|
||||
*/
|
||||
int_compare_magnitude :: proc(a, b: ^Int, allocator := context.allocator) -> (res: int, err: Error) {
|
||||
assert_if_nil(a, b);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a, b) or_return;
|
||||
|
||||
return #force_inline internal_cmp_mag(a, b), nil;
|
||||
}
|
||||
|
||||
/*
|
||||
Check if remainders are possible squares - fast exclude non-squares.
|
||||
|
||||
Returns `true` if `a` is a square, `false` if not.
|
||||
Assumes `a` not to be `nil` and to have been initialized.
|
||||
*/
|
||||
int_is_square :: proc(a: ^Int, allocator := context.allocator) -> (square: bool, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
internal_clear_if_uninitialized(a) or_return;
|
||||
|
||||
return #force_inline internal_int_is_square(a);
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
|
||||
|
||||
This file contains radix conversions, `string_to_int` (atoi) and `int_to_string` (itoa).
|
||||
|
||||
TODO:
|
||||
- Use Barrett reduction for non-powers-of-two.
|
||||
- Also look at extracting and splatting several digits at once.
|
||||
*/
|
||||
|
||||
import "core:intrinsics"
|
||||
import "core:mem"
|
||||
|
||||
/*
|
||||
This version of `itoa` allocates one behalf of the caller. The caller must free the string.
|
||||
*/
|
||||
int_itoa_string :: proc(a: ^Int, radix := i8(-1), zero_terminate := false, allocator := context.allocator) -> (res: string, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
a := a; radix := radix;
|
||||
clear_if_uninitialized(a) or_return;
|
||||
/*
|
||||
Radix defaults to 10.
|
||||
*/
|
||||
radix = radix if radix > 0 else 10;
|
||||
|
||||
/*
|
||||
TODO: If we want to write a prefix for some of the radixes, we can oversize the buffer.
|
||||
Then after the digits are written and the string is reversed
|
||||
*/
|
||||
|
||||
/*
|
||||
Calculate the size of the buffer we need, and
|
||||
Exit if calculating the size returned an error.
|
||||
*/
|
||||
size := radix_size(a, radix, zero_terminate) or_return;
|
||||
|
||||
/*
|
||||
Allocate the buffer we need.
|
||||
*/
|
||||
buffer := make([]u8, size);
|
||||
|
||||
/*
|
||||
Write the digits out into the buffer.
|
||||
*/
|
||||
written: int;
|
||||
written, err = int_itoa_raw(a, radix, buffer, size, zero_terminate);
|
||||
|
||||
return string(buffer[:written]), err;
|
||||
}
|
||||
|
||||
/*
|
||||
This version of `itoa` allocates one behalf of the caller. The caller must free the string.
|
||||
*/
|
||||
int_itoa_cstring :: proc(a: ^Int, radix := i8(-1), allocator := context.allocator) -> (res: cstring, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
a := a; radix := radix;
|
||||
clear_if_uninitialized(a) or_return;
|
||||
/*
|
||||
Radix defaults to 10.
|
||||
*/
|
||||
radix = radix if radix > 0 else 10;
|
||||
|
||||
s: string;
|
||||
s, err = int_itoa_string(a, radix, true);
|
||||
return cstring(raw_data(s)), err;
|
||||
}
|
||||
|
||||
/*
|
||||
A low-level `itoa` using a caller-provided buffer. `itoa_string` and `itoa_cstring` use this.
|
||||
You can use also use it if you want to pre-allocate a buffer and optionally reuse it.
|
||||
|
||||
Use `radix_size` or `radix_size_estimate` to determine a buffer size big enough.
|
||||
|
||||
You can pass the output of `radix_size` to `size` if you've previously called it to size
|
||||
the output buffer. If you haven't, this routine will call it. This way it knows if the buffer
|
||||
is the appropriate size, and we can write directly in place without a reverse step at the end.
|
||||
|
||||
=== === === IMPORTANT === === ===
|
||||
|
||||
If you determined the buffer size using `radix_size_estimate`, or have a buffer
|
||||
that you reuse that you know is large enough, don't pass this size unless you know what you are doing,
|
||||
because we will always write backwards starting at last byte of the buffer.
|
||||
|
||||
Keep in mind that if you set `size` yourself and it's smaller than the buffer,
|
||||
it'll result in buffer overflows, as we use it to avoid reversing at the end
|
||||
and having to perform a buffer overflow check each character.
|
||||
*/
|
||||
int_itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, size := int(-1), zero_terminate := false) -> (written: int, err: Error) {
|
||||
assert_if_nil(a);
|
||||
a := a; radix := radix; size := size;
|
||||
clear_if_uninitialized(a) or_return;
|
||||
/*
|
||||
Radix defaults to 10.
|
||||
*/
|
||||
radix = radix if radix > 0 else 10;
|
||||
if radix < 2 || radix > 64 {
|
||||
return 0, .Invalid_Argument;
|
||||
}
|
||||
|
||||
/*
|
||||
We weren't given a size. Let's compute it.
|
||||
*/
|
||||
if size == -1 {
|
||||
size = radix_size(a, radix, zero_terminate) or_return;
|
||||
}
|
||||
|
||||
/*
|
||||
Early exit if the buffer we were given is too small.
|
||||
*/
|
||||
available := len(buffer);
|
||||
if available < size {
|
||||
return 0, .Buffer_Overflow;
|
||||
}
|
||||
/*
|
||||
Fast path for when `Int` == 0 or the entire `Int` fits in a single radix digit.
|
||||
*/
|
||||
z, _ := is_zero(a);
|
||||
if z || (a.used == 1 && a.digit[0] < DIGIT(radix)) {
|
||||
if zero_terminate {
|
||||
available -= 1;
|
||||
buffer[available] = 0;
|
||||
}
|
||||
available -= 1;
|
||||
buffer[available] = RADIX_TABLE[a.digit[0]];
|
||||
|
||||
if n, _ := is_neg(a); n {
|
||||
available -= 1;
|
||||
buffer[available] = '-';
|
||||
}
|
||||
|
||||
/*
|
||||
If we overestimated the size, we need to move the buffer left.
|
||||
*/
|
||||
written = len(buffer) - available;
|
||||
if written < size {
|
||||
diff := size - written;
|
||||
mem.copy(&buffer[0], &buffer[diff], written);
|
||||
}
|
||||
return written, nil;
|
||||
}
|
||||
|
||||
/*
|
||||
Fast path for when `Int` fits within a `_WORD`.
|
||||
*/
|
||||
if a.used == 1 || a.used == 2 {
|
||||
if zero_terminate {
|
||||
available -= 1;
|
||||
buffer[available] = 0;
|
||||
}
|
||||
|
||||
val := _WORD(a.digit[1]) << _DIGIT_BITS + _WORD(a.digit[0]);
|
||||
for val > 0 {
|
||||
q := val / _WORD(radix);
|
||||
available -= 1;
|
||||
buffer[available] = RADIX_TABLE[val - (q * _WORD(radix))];
|
||||
|
||||
val = q;
|
||||
}
|
||||
if n, _ := is_neg(a); n {
|
||||
available -= 1;
|
||||
buffer[available] = '-';
|
||||
}
|
||||
|
||||
/*
|
||||
If we overestimated the size, we need to move the buffer left.
|
||||
*/
|
||||
written = len(buffer) - available;
|
||||
if written < size {
|
||||
diff := size - written;
|
||||
mem.copy(&buffer[0], &buffer[diff], written);
|
||||
}
|
||||
return written, nil;
|
||||
}
|
||||
|
||||
/*
|
||||
Fast path for radixes that are a power of two.
|
||||
*/
|
||||
if is_power_of_two(int(radix)) {
|
||||
if zero_terminate {
|
||||
available -= 1;
|
||||
buffer[available] = 0;
|
||||
}
|
||||
|
||||
shift, count: int;
|
||||
// mask := _WORD(radix - 1);
|
||||
shift, err = log(DIGIT(radix), 2);
|
||||
count, err = count_bits(a);
|
||||
digit: _WORD;
|
||||
|
||||
for offset := 0; offset < count; offset += shift {
|
||||
bits_to_get := int(min(count - offset, shift));
|
||||
|
||||
digit, err = int_bitfield_extract(a, offset, bits_to_get);
|
||||
if err != nil {
|
||||
return len(buffer) - available, .Invalid_Argument;
|
||||
}
|
||||
available -= 1;
|
||||
buffer[available] = RADIX_TABLE[digit];
|
||||
}
|
||||
|
||||
if n, _ := is_neg(a); n {
|
||||
available -= 1;
|
||||
buffer[available] = '-';
|
||||
}
|
||||
|
||||
/*
|
||||
If we overestimated the size, we need to move the buffer left.
|
||||
*/
|
||||
written = len(buffer) - available;
|
||||
if written < size {
|
||||
diff := size - written;
|
||||
mem.copy(&buffer[0], &buffer[diff], written);
|
||||
}
|
||||
return written, nil;
|
||||
}
|
||||
|
||||
return _itoa_raw_full(a, radix, buffer, zero_terminate);
|
||||
}
|
||||
|
||||
itoa :: proc{int_itoa_string, int_itoa_raw};
|
||||
int_to_string :: int_itoa_string;
|
||||
int_to_cstring :: int_itoa_cstring;
|
||||
|
||||
/*
|
||||
Read a string [ASCII] in a given radix.
|
||||
*/
|
||||
int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context.allocator) -> (err: Error) {
|
||||
assert_if_nil(res);
|
||||
input := input;
|
||||
context.allocator = allocator;
|
||||
|
||||
/*
|
||||
Make sure the radix is ok.
|
||||
*/
|
||||
|
||||
if radix < 2 || radix > 64 { return .Invalid_Argument; }
|
||||
|
||||
/*
|
||||
Set the integer to the default of zero.
|
||||
*/
|
||||
internal_zero(res) or_return;
|
||||
|
||||
/*
|
||||
We'll interpret an empty string as zero.
|
||||
*/
|
||||
if len(input) == 0 {
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*
|
||||
If the leading digit is a minus set the sign to negative.
|
||||
Given the above early out, the length should be at least 1.
|
||||
*/
|
||||
sign := Sign.Zero_or_Positive;
|
||||
if input[0] == '-' {
|
||||
input = input[1:];
|
||||
sign = .Negative;
|
||||
}
|
||||
|
||||
/*
|
||||
Process each digit of the string.
|
||||
*/
|
||||
ch: rune;
|
||||
for len(input) > 0 {
|
||||
/* if the radix <= 36 the conversion is case insensitive
|
||||
* this allows numbers like 1AB and 1ab to represent the same value
|
||||
* [e.g. in hex]
|
||||
*/
|
||||
|
||||
ch = rune(input[0]);
|
||||
if radix <= 36 && ch >= 'a' && ch <= 'z' {
|
||||
ch -= 32; // 'a' - 'A'
|
||||
}
|
||||
|
||||
pos := ch - '+';
|
||||
if RADIX_TABLE_REVERSE_SIZE <= pos {
|
||||
break;
|
||||
}
|
||||
y := RADIX_TABLE_REVERSE[pos];
|
||||
/* if the char was found in the map
|
||||
* and is less than the given radix add it
|
||||
* to the number, otherwise exit the loop.
|
||||
*/
|
||||
if y >= u8(radix) {
|
||||
break;
|
||||
}
|
||||
|
||||
internal_mul(res, res, DIGIT(radix)) or_return;
|
||||
internal_add(res, res, DIGIT(y)) or_return;
|
||||
|
||||
input = input[1:];
|
||||
}
|
||||
/*
|
||||
If an illegal character was found, fail.
|
||||
*/
|
||||
if len(input) > 0 && ch != 0 && ch != '\r' && ch != '\n' {
|
||||
return .Invalid_Argument;
|
||||
}
|
||||
/*
|
||||
Set the sign only if res != 0.
|
||||
*/
|
||||
if res.used > 0 {
|
||||
res.sign = sign;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
atoi :: proc { int_atoi, };
|
||||
|
||||
/*
|
||||
We size for `string` by default.
|
||||
*/
|
||||
radix_size :: proc(a: ^Int, radix: i8, zero_terminate := false, allocator := context.allocator) -> (size: int, err: Error) {
|
||||
a := a;
|
||||
assert_if_nil(a);
|
||||
|
||||
if radix < 2 || radix > 64 { return -1, .Invalid_Argument; }
|
||||
clear_if_uninitialized(a) or_return;
|
||||
|
||||
if internal_is_zero(a) {
|
||||
if zero_terminate {
|
||||
return 2, nil;
|
||||
}
|
||||
return 1, nil;
|
||||
}
|
||||
|
||||
if internal_is_power_of_two(a) {
|
||||
/*
|
||||
Calculate `log` on a temporary "copy" with its sign set to positive.
|
||||
*/
|
||||
t := &Int{
|
||||
used = a.used,
|
||||
sign = .Zero_or_Positive,
|
||||
digit = a.digit,
|
||||
};
|
||||
|
||||
size = internal_log(t, DIGIT(radix)) or_return;
|
||||
} else {
|
||||
la, k := &Int{}, &Int{};
|
||||
defer internal_destroy(la, k);
|
||||
|
||||
/* la = floor(log_2(a)) + 1 */
|
||||
bit_count := internal_count_bits(a);
|
||||
internal_set(la, bit_count) or_return;
|
||||
|
||||
/* k = floor(2^29/log_2(radix)) + 1 */
|
||||
lb := _log_bases;
|
||||
internal_set(k, lb[radix]) or_return;
|
||||
|
||||
/* n = floor((la * k) / 2^29) + 1 */
|
||||
internal_mul(k, la, k) or_return;
|
||||
internal_shr(k, k, _RADIX_SIZE_SCALE) or_return;
|
||||
|
||||
/* The "+1" here is the "+1" in "floor((la * k) / 2^29) + 1" */
|
||||
/* n = n + 1 + EOS + sign */
|
||||
size_, _ := internal_get(k, u128);
|
||||
size = int(size_);
|
||||
}
|
||||
|
||||
/*
|
||||
log truncates to zero, so we need to add one more, and one for `-` if negative.
|
||||
*/
|
||||
size += 2 if a.sign == .Negative else 1;
|
||||
size += 1 if zero_terminate else 0;
|
||||
return size, nil;
|
||||
}
|
||||
|
||||
/*
|
||||
Overestimate the size needed for the bigint to string conversion by a very small amount.
|
||||
The error is about 10^-8; it will overestimate the result by at most 11 elements for
|
||||
a number of the size 2^(2^31)-1 which is currently the largest possible in this library.
|
||||
Some short tests gave no results larger than 5 (plus 2 for sign and EOS).
|
||||
*/
|
||||
|
||||
/*
|
||||
Table of {0, INT(log_2([1..64])*2^p)+1 } where p is the scale
|
||||
factor defined in MP_RADIX_SIZE_SCALE and INT() extracts the integer part (truncating).
|
||||
Good for 32 bit "int". Set MP_RADIX_SIZE_SCALE = 61 and recompute values
|
||||
for 64 bit "int".
|
||||
*/
|
||||
|
||||
_RADIX_SIZE_SCALE :: 29;
|
||||
_log_bases :: [65]u32{
|
||||
0, 0, 0x20000001, 0x14309399, 0x10000001,
|
||||
0xdc81a35, 0xc611924, 0xb660c9e, 0xaaaaaab, 0xa1849cd,
|
||||
0x9a209a9, 0x94004e1, 0x8ed19c2, 0x8a5ca7d, 0x867a000,
|
||||
0x830cee3, 0x8000001, 0x7d42d60, 0x7ac8b32, 0x7887847,
|
||||
0x7677349, 0x749131f, 0x72d0163, 0x712f657, 0x6fab5db,
|
||||
0x6e40d1b, 0x6ced0d0, 0x6badbde, 0x6a80e3b, 0x6964c19,
|
||||
0x6857d31, 0x6758c38, 0x6666667, 0x657fb21, 0x64a3b9f,
|
||||
0x63d1ab4, 0x6308c92, 0x624869e, 0x618ff47, 0x60dedea,
|
||||
0x6034ab0, 0x5f90e7b, 0x5ef32cb, 0x5e5b1b2, 0x5dc85c3,
|
||||
0x5d3aa02, 0x5cb19d9, 0x5c2d10f, 0x5bacbbf, 0x5b3064f,
|
||||
0x5ab7d68, 0x5a42df0, 0x59d1506, 0x5962ffe, 0x58f7c57,
|
||||
0x588f7bc, 0x582a000, 0x57c7319, 0x5766f1d, 0x5709243,
|
||||
0x56adad9, 0x565474d, 0x55fd61f, 0x55a85e8, 0x5555556,
|
||||
};
|
||||
|
||||
/*
|
||||
Characters used in radix conversions.
|
||||
*/
|
||||
RADIX_TABLE := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/";
|
||||
RADIX_TABLE_REVERSE := [RADIX_TABLE_REVERSE_SIZE]u8{
|
||||
0x3e, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x01, 0x02, 0x03, 0x04, /* +,-./01234 */
|
||||
0x05, 0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, /* 56789:;<=> */
|
||||
0xff, 0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, /* ?@ABCDEFGH */
|
||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, /* IJKLMNOPQR */
|
||||
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0xff, 0xff, /* STUVWXYZ[\ */
|
||||
0xff, 0xff, 0xff, 0xff, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, /* ]^_`abcdef */
|
||||
0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, /* ghijklmnop */
|
||||
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, /* qrstuvwxyz */
|
||||
};
|
||||
RADIX_TABLE_REVERSE_SIZE :: 80;
|
||||
|
||||
/*
|
||||
Stores a bignum as a ASCII string in a given radix (2..64)
|
||||
The buffer must be appropriately sized. This routine doesn't check.
|
||||
*/
|
||||
_itoa_raw_full :: proc(a: ^Int, radix: i8, buffer: []u8, zero_terminate := false, allocator := context.allocator) -> (written: int, err: Error) {
|
||||
assert_if_nil(a);
|
||||
context.allocator = allocator;
|
||||
|
||||
temp, denominator := &Int{}, &Int{};
|
||||
|
||||
internal_copy(temp, a) or_return;
|
||||
internal_set(denominator, radix) or_return;
|
||||
|
||||
available := len(buffer);
|
||||
if zero_terminate {
|
||||
available -= 1;
|
||||
buffer[available] = 0;
|
||||
}
|
||||
|
||||
if a.sign == .Negative {
|
||||
temp.sign = .Zero_or_Positive;
|
||||
}
|
||||
|
||||
remainder: DIGIT;
|
||||
for {
|
||||
if remainder, err = #force_inline internal_divmod(temp, temp, DIGIT(radix)); err != nil {
|
||||
internal_destroy(temp, denominator);
|
||||
return len(buffer) - available, err;
|
||||
}
|
||||
available -= 1;
|
||||
buffer[available] = RADIX_TABLE[remainder];
|
||||
if temp.used == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if a.sign == .Negative {
|
||||
available -= 1;
|
||||
buffer[available] = '-';
|
||||
}
|
||||
|
||||
internal_destroy(temp, denominator);
|
||||
|
||||
/*
|
||||
If we overestimated the size, we need to move the buffer left.
|
||||
*/
|
||||
written = len(buffer) - available;
|
||||
if written < len(buffer) {
|
||||
diff := len(buffer) - written;
|
||||
mem.copy(&buffer[0], &buffer[diff], written);
|
||||
}
|
||||
return written, nil;
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
//+ignore
|
||||
package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
|
||||
|
||||
This file exports procedures for use with the test.py test suite.
|
||||
*/
|
||||
|
||||
/*
|
||||
TODO: Write tests for `internal_*` and test reusing parameters with the public implementations.
|
||||
*/
|
||||
|
||||
import "core:runtime"
|
||||
import "core:strings"
|
||||
|
||||
PyRes :: struct {
|
||||
res: cstring,
|
||||
err: Error,
|
||||
}
|
||||
|
||||
@export test_initialize_constants :: proc "c" () -> (res: u64) {
|
||||
context = runtime.default_context();
|
||||
res = u64(initialize_constants());
|
||||
//assert(MUL_KARATSUBA_CUTOFF >= 40);
|
||||
return res;
|
||||
}
|
||||
|
||||
@export test_error_string :: proc "c" (err: Error) -> (res: cstring) {
|
||||
context = runtime.default_context();
|
||||
es := Error_String;
|
||||
return strings.clone_to_cstring(es[err], context.temp_allocator);
|
||||
}
|
||||
|
||||
@export test_add :: proc "c" (a, b: cstring) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
aa, bb, sum := &Int{}, &Int{}, &Int{};
|
||||
defer internal_destroy(aa, bb, sum);
|
||||
|
||||
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":add:atoi(a):", err=err}; }
|
||||
if err = atoi(bb, string(b), 16); err != nil { return PyRes{res=":add:atoi(b):", err=err}; }
|
||||
if bb.used == 1 {
|
||||
if err = #force_inline internal_add(sum, aa, bb.digit[0]); err != nil { return PyRes{res=":add:add(sum,a,b):", err=err}; }
|
||||
} else {
|
||||
if err = #force_inline internal_add(sum, aa, bb); err != nil { return PyRes{res=":add:add(sum,a,b):", err=err}; }
|
||||
}
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(sum, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":add:itoa(sum):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
@export test_sub :: proc "c" (a, b: cstring) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
aa, bb, sum := &Int{}, &Int{}, &Int{};
|
||||
defer internal_destroy(aa, bb, sum);
|
||||
|
||||
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":sub:atoi(a):", err=err}; }
|
||||
if err = atoi(bb, string(b), 16); err != nil { return PyRes{res=":sub:atoi(b):", err=err}; }
|
||||
if bb.used == 1 {
|
||||
if err = #force_inline internal_sub(sum, aa, bb.digit[0]); err != nil { return PyRes{res=":sub:sub(sum,a,b):", err=err}; }
|
||||
} else {
|
||||
if err = #force_inline internal_sub(sum, aa, bb); err != nil { return PyRes{res=":sub:sub(sum,a,b):", err=err}; }
|
||||
}
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(sum, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":sub:itoa(sum):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
@export test_mul :: proc "c" (a, b: cstring) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
aa, bb, product := &Int{}, &Int{}, &Int{};
|
||||
defer internal_destroy(aa, bb, product);
|
||||
|
||||
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":mul:atoi(a):", err=err}; }
|
||||
if err = atoi(bb, string(b), 16); err != nil { return PyRes{res=":mul:atoi(b):", err=err}; }
|
||||
if err = #force_inline internal_mul(product, aa, bb); err != nil { return PyRes{res=":mul:mul(product,a,b):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(product, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":mul:itoa(product):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
@export test_sqr :: proc "c" (a: cstring) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
aa, square := &Int{}, &Int{};
|
||||
defer internal_destroy(aa, square);
|
||||
|
||||
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":sqr:atoi(a):", err=err}; }
|
||||
if err = #force_inline internal_sqr(square, aa); err != nil { return PyRes{res=":sqr:sqr(square,a):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(square, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":sqr:itoa(square):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
NOTE(Jeroen): For simplicity, we don't return the quotient and the remainder, just the quotient.
|
||||
*/
|
||||
@export test_div :: proc "c" (a, b: cstring) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
aa, bb, quotient := &Int{}, &Int{}, &Int{};
|
||||
defer internal_destroy(aa, bb, quotient);
|
||||
|
||||
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":div:atoi(a):", err=err}; }
|
||||
if err = atoi(bb, string(b), 16); err != nil { return PyRes{res=":div:atoi(b):", err=err}; }
|
||||
if err = #force_inline internal_div(quotient, aa, bb); err != nil { return PyRes{res=":div:div(quotient,a,b):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(quotient, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":div:itoa(quotient):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
res = log(a, base)
|
||||
*/
|
||||
@export test_log :: proc "c" (a: cstring, base := DIGIT(2)) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
l: int;
|
||||
|
||||
aa := &Int{};
|
||||
defer internal_destroy(aa);
|
||||
|
||||
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":log:atoi(a):", err=err}; }
|
||||
if l, err = #force_inline internal_log(aa, base); err != nil { return PyRes{res=":log:log(a, base):", err=err}; }
|
||||
|
||||
#force_inline internal_zero(aa);
|
||||
aa.digit[0] = DIGIT(l) & _MASK;
|
||||
aa.digit[1] = DIGIT(l) >> _DIGIT_BITS;
|
||||
aa.used = 2;
|
||||
clamp(aa);
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(aa, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":log:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = base^power
|
||||
*/
|
||||
@export test_pow :: proc "c" (base: cstring, power := int(2)) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
dest, bb := &Int{}, &Int{};
|
||||
defer internal_destroy(dest, bb);
|
||||
|
||||
if err = atoi(bb, string(base), 16); err != nil { return PyRes{res=":pow:atoi(base):", err=err}; }
|
||||
if err = #force_inline internal_pow(dest, bb, power); err != nil { return PyRes{res=":pow:pow(dest, base, power):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(dest, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":log:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = sqrt(src)
|
||||
*/
|
||||
@export test_sqrt :: proc "c" (source: cstring) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
src := &Int{};
|
||||
defer internal_destroy(src);
|
||||
|
||||
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":sqrt:atoi(src):", err=err}; }
|
||||
if err = #force_inline internal_sqrt(src, src); err != nil { return PyRes{res=":sqrt:sqrt(src):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":log:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = root_n(src, power)
|
||||
*/
|
||||
@export test_root_n :: proc "c" (source: cstring, power: int) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
src := &Int{};
|
||||
defer internal_destroy(src);
|
||||
|
||||
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":root_n:atoi(src):", err=err}; }
|
||||
if err = #force_inline internal_root_n(src, src, power); err != nil { return PyRes{res=":root_n:root_n(src):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":root_n:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = shr_digit(src, digits)
|
||||
*/
|
||||
@export test_shr_digit :: proc "c" (source: cstring, digits: int) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
src := &Int{};
|
||||
defer internal_destroy(src);
|
||||
|
||||
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":shr_digit:atoi(src):", err=err}; }
|
||||
if err = #force_inline internal_shr_digit(src, digits); err != nil { return PyRes{res=":shr_digit:shr_digit(src):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":shr_digit:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = shl_digit(src, digits)
|
||||
*/
|
||||
@export test_shl_digit :: proc "c" (source: cstring, digits: int) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
src := &Int{};
|
||||
defer internal_destroy(src);
|
||||
|
||||
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":shl_digit:atoi(src):", err=err}; }
|
||||
if err = #force_inline internal_shl_digit(src, digits); err != nil { return PyRes{res=":shl_digit:shr_digit(src):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":shl_digit:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = shr(src, bits)
|
||||
*/
|
||||
@export test_shr :: proc "c" (source: cstring, bits: int) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
src := &Int{};
|
||||
defer internal_destroy(src);
|
||||
|
||||
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":shr:atoi(src):", err=err}; }
|
||||
if err = #force_inline internal_shr(src, src, bits); err != nil { return PyRes{res=":shr:shr(src, bits):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":shr:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = shr_signed(src, bits)
|
||||
*/
|
||||
@export test_shr_signed :: proc "c" (source: cstring, bits: int) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
src := &Int{};
|
||||
defer internal_destroy(src);
|
||||
|
||||
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":shr_signed:atoi(src):", err=err}; }
|
||||
if err = #force_inline internal_shr_signed(src, src, bits); err != nil { return PyRes{res=":shr_signed:shr_signed(src, bits):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":shr_signed:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = shl(src, bits)
|
||||
*/
|
||||
@export test_shl :: proc "c" (source: cstring, bits: int) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
src := &Int{};
|
||||
defer internal_destroy(src);
|
||||
|
||||
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":shl:atoi(src):", err=err}; }
|
||||
if err = #force_inline internal_shl(src, src, bits); err != nil { return PyRes{res=":shl:shl(src, bits):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":shl:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = factorial(n)
|
||||
*/
|
||||
@export test_factorial :: proc "c" (n: int) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
dest := &Int{};
|
||||
defer internal_destroy(dest);
|
||||
|
||||
if err = #force_inline internal_int_factorial(dest, n); err != nil { return PyRes{res=":factorial:factorial(n):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(dest, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":factorial:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = gcd(a, b)
|
||||
*/
|
||||
@export test_gcd :: proc "c" (a, b: cstring) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
ai, bi, dest := &Int{}, &Int{}, &Int{};
|
||||
defer internal_destroy(ai, bi, dest);
|
||||
|
||||
if err = atoi(ai, string(a), 16); err != nil { return PyRes{res=":gcd:atoi(a):", err=err}; }
|
||||
if err = atoi(bi, string(b), 16); err != nil { return PyRes{res=":gcd:atoi(b):", err=err}; }
|
||||
if err = #force_inline internal_int_gcd_lcm(dest, nil, ai, bi); err != nil { return PyRes{res=":gcd:gcd(a, b):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(dest, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":gcd:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = lcm(a, b)
|
||||
*/
|
||||
@export test_lcm :: proc "c" (a, b: cstring) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
|
||||
ai, bi, dest := &Int{}, &Int{}, &Int{};
|
||||
defer internal_destroy(ai, bi, dest);
|
||||
|
||||
if err = atoi(ai, string(a), 16); err != nil { return PyRes{res=":lcm:atoi(a):", err=err}; }
|
||||
if err = atoi(bi, string(b), 16); err != nil { return PyRes{res=":lcm:atoi(b):", err=err}; }
|
||||
if err = #force_inline internal_int_gcd_lcm(nil, dest, ai, bi); err != nil { return PyRes{res=":lcm:lcm(a, b):", err=err}; }
|
||||
|
||||
r: cstring;
|
||||
r, err = int_itoa_cstring(dest, 16, context.temp_allocator);
|
||||
if err != nil { return PyRes{res=":lcm:itoa(res):", err=err}; }
|
||||
return PyRes{res = r, err = nil};
|
||||
}
|
||||
|
||||
/*
|
||||
dest = lcm(a, b)
|
||||
*/
|
||||
@export test_is_square :: proc "c" (a: cstring) -> (res: PyRes) {
|
||||
context = runtime.default_context();
|
||||
err: Error;
|
||||
square: bool;
|
||||
|
||||
ai := &Int{};
|
||||
defer internal_destroy(ai);
|
||||
|
||||
if err = atoi(ai, string(a), 16); err != nil { return PyRes{res=":is_square:atoi(a):", err=err}; }
|
||||
if square, err = #force_inline internal_int_is_square(ai); err != nil { return PyRes{res=":is_square:is_square(a):", err=err}; }
|
||||
|
||||
if square {
|
||||
return PyRes{"True", nil};
|
||||
}
|
||||
return PyRes{"False", nil};
|
||||
}
|
||||
@@ -0,0 +1,707 @@
|
||||
from ctypes import *
|
||||
from random import *
|
||||
import math
|
||||
import os
|
||||
import platform
|
||||
import time
|
||||
import gc
|
||||
from enum import Enum
|
||||
import argparse
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description = "Odin core:math/big test suite",
|
||||
epilog = "By default we run regression and random tests with preset parameters.",
|
||||
formatter_class = argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
|
||||
#
|
||||
# Normally, we report the number of passes and fails. With this option set, we exit at first fail.
|
||||
#
|
||||
parser.add_argument(
|
||||
"-exit-on-fail",
|
||||
help = "Exit when a test fails",
|
||||
action = "store_true",
|
||||
)
|
||||
|
||||
#
|
||||
# We skip randomized tests altogether if this is set.
|
||||
#
|
||||
no_random = parser.add_mutually_exclusive_group()
|
||||
|
||||
no_random.add_argument(
|
||||
"-no-random",
|
||||
help = "No random tests",
|
||||
action = "store_true",
|
||||
)
|
||||
|
||||
#
|
||||
# Normally we run a given number of cycles on each test.
|
||||
# Timed tests budget 1 second per 20_000 bits instead.
|
||||
#
|
||||
# For timed tests we budget a second per `n` bits and iterate until we hit that time.
|
||||
#
|
||||
timed_or_fast = no_random.add_mutually_exclusive_group()
|
||||
|
||||
timed_or_fast.add_argument(
|
||||
"-timed",
|
||||
type = bool,
|
||||
default = False,
|
||||
help = "Timed tests instead of a preset number of iterations.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-timed-bits",
|
||||
type = int,
|
||||
metavar = "BITS",
|
||||
default = 20_000,
|
||||
help = "Timed tests. Every `BITS` worth of input is given a second of running time.",
|
||||
)
|
||||
|
||||
#
|
||||
# For normal tests (non-timed), `-fast-tests` cuts down on the number of iterations.
|
||||
#
|
||||
timed_or_fast.add_argument(
|
||||
"-fast-tests",
|
||||
help = "Cut down on the number of iterations of each test",
|
||||
action = "store_true",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
EXIT_ON_FAIL = args.exit_on_fail
|
||||
|
||||
#
|
||||
# How many iterations of each random test do we want to run?
|
||||
#
|
||||
BITS_AND_ITERATIONS = [
|
||||
( 120, 10_000),
|
||||
( 1_200, 1_000),
|
||||
( 4_096, 100),
|
||||
(12_000, 10),
|
||||
]
|
||||
|
||||
if args.fast_tests:
|
||||
for k in range(len(BITS_AND_ITERATIONS)):
|
||||
b, i = BITS_AND_ITERATIONS[k]
|
||||
BITS_AND_ITERATIONS[k] = (b, i // 10 if i >= 100 else 5)
|
||||
|
||||
if args.no_random:
|
||||
BITS_AND_ITERATIONS = []
|
||||
|
||||
#
|
||||
# Where is the DLL? If missing, build using: `odin build . -build-mode:shared`
|
||||
#
|
||||
if platform.system() == "Windows":
|
||||
LIB_PATH = os.getcwd() + os.sep + "big.dll"
|
||||
elif platform.system() == "Linux":
|
||||
LIB_PATH = os.getcwd() + os.sep + "big.so"
|
||||
elif platform.system() == "Darwin":
|
||||
LIB_PATH = os.getcwd() + os.sep + "big.dylib"
|
||||
else:
|
||||
print("Platform is unsupported.")
|
||||
exit(1)
|
||||
|
||||
|
||||
TOTAL_TIME = 0
|
||||
UNTIL_TIME = 0
|
||||
UNTIL_ITERS = 0
|
||||
|
||||
def we_iterate():
|
||||
if args.timed:
|
||||
return TOTAL_TIME < UNTIL_TIME
|
||||
else:
|
||||
global UNTIL_ITERS
|
||||
UNTIL_ITERS -= 1
|
||||
return UNTIL_ITERS != -1
|
||||
|
||||
#
|
||||
# Error enum values
|
||||
#
|
||||
class Error(Enum):
|
||||
Okay = 0
|
||||
Out_Of_Memory = 1
|
||||
Invalid_Pointer = 2
|
||||
Invalid_Argument = 3
|
||||
Unknown_Error = 4
|
||||
Max_Iterations_Reached = 5
|
||||
Buffer_Overflow = 6
|
||||
Integer_Overflow = 7
|
||||
Division_by_Zero = 8
|
||||
Math_Domain_Error = 9
|
||||
Unimplemented = 127
|
||||
|
||||
#
|
||||
# Disable garbage collection
|
||||
#
|
||||
gc.disable()
|
||||
|
||||
#
|
||||
# Set up exported procedures
|
||||
#
|
||||
try:
|
||||
l = cdll.LoadLibrary(LIB_PATH)
|
||||
except:
|
||||
print("Couldn't find or load " + LIB_PATH + ".")
|
||||
exit(1)
|
||||
|
||||
def load(export_name, args, res):
|
||||
export_name.argtypes = args
|
||||
export_name.restype = res
|
||||
return export_name
|
||||
|
||||
#
|
||||
# Result values will be passed in a struct { res: cstring, err: Error }
|
||||
#
|
||||
class Res(Structure):
|
||||
_fields_ = [("res", c_char_p), ("err", c_uint64)]
|
||||
|
||||
initialize_constants = load(l.test_initialize_constants, [], c_uint64)
|
||||
print("initialize_constants: ", initialize_constants())
|
||||
|
||||
error_string = load(l.test_error_string, [c_byte], c_char_p)
|
||||
|
||||
add = load(l.test_add, [c_char_p, c_char_p ], Res)
|
||||
sub = load(l.test_sub, [c_char_p, c_char_p ], Res)
|
||||
mul = load(l.test_mul, [c_char_p, c_char_p ], Res)
|
||||
sqr = load(l.test_sqr, [c_char_p ], Res)
|
||||
div = load(l.test_div, [c_char_p, c_char_p ], Res)
|
||||
|
||||
# Powers and such
|
||||
int_log = load(l.test_log, [c_char_p, c_longlong], Res)
|
||||
int_pow = load(l.test_pow, [c_char_p, c_longlong], Res)
|
||||
int_sqrt = load(l.test_sqrt, [c_char_p ], Res)
|
||||
int_root_n = load(l.test_root_n, [c_char_p, c_longlong], Res)
|
||||
|
||||
# Logical operations
|
||||
int_shl_digit = load(l.test_shl_digit, [c_char_p, c_longlong], Res)
|
||||
int_shr_digit = load(l.test_shr_digit, [c_char_p, c_longlong], Res)
|
||||
int_shl = load(l.test_shl, [c_char_p, c_longlong], Res)
|
||||
int_shr = load(l.test_shr, [c_char_p, c_longlong], Res)
|
||||
int_shr_signed = load(l.test_shr_signed, [c_char_p, c_longlong], Res)
|
||||
|
||||
int_factorial = load(l.test_factorial, [c_uint64 ], Res)
|
||||
int_gcd = load(l.test_gcd, [c_char_p, c_char_p ], Res)
|
||||
int_lcm = load(l.test_lcm, [c_char_p, c_char_p ], Res)
|
||||
|
||||
is_square = load(l.test_is_square, [c_char_p ], Res)
|
||||
|
||||
def test(test_name: "", res: Res, param=[], expected_error = Error.Okay, expected_result = "", radix=16):
|
||||
passed = True
|
||||
r = None
|
||||
err = Error(res.err)
|
||||
|
||||
if err != expected_error:
|
||||
error_loc = res.res.decode('utf-8')
|
||||
error = "{}: {} in '{}'".format(test_name, err, error_loc)
|
||||
|
||||
if len(param):
|
||||
error += " with params {}".format(param)
|
||||
|
||||
print(error, flush=True)
|
||||
passed = False
|
||||
elif err == Error.Okay:
|
||||
r = None
|
||||
try:
|
||||
r = res.res.decode('utf-8')
|
||||
r = int(res.res, radix)
|
||||
except:
|
||||
pass
|
||||
|
||||
if r != expected_result:
|
||||
error = "{}: Result was '{}', expected '{}'".format(test_name, r, expected_result)
|
||||
if len(param):
|
||||
error += " with params {}".format(param)
|
||||
|
||||
print(error, flush=True)
|
||||
passed = False
|
||||
|
||||
if EXIT_ON_FAIL and not passed: exit(res.err)
|
||||
|
||||
return passed
|
||||
|
||||
def arg_to_odin(a):
|
||||
if a >= 0:
|
||||
s = hex(a)[2:]
|
||||
else:
|
||||
s = '-' + hex(a)[3:]
|
||||
return s.encode('utf-8')
|
||||
|
||||
def test_add(a = 0, b = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), arg_to_odin(b)]
|
||||
res = add(*args)
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = a + b
|
||||
return test("test_add", res, [a, b], expected_error, expected_result)
|
||||
|
||||
def test_sub(a = 0, b = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), arg_to_odin(b)]
|
||||
res = sub(*args)
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = a - b
|
||||
return test("test_sub", res, [a, b], expected_error, expected_result)
|
||||
|
||||
def test_mul(a = 0, b = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), arg_to_odin(b)]
|
||||
try:
|
||||
res = mul(*args)
|
||||
except OSError as e:
|
||||
print("{} while trying to multiply {} x {}.".format(e, a, b))
|
||||
if EXIT_ON_FAIL: exit(3)
|
||||
return False
|
||||
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = a * b
|
||||
return test("test_mul", res, [a, b], expected_error, expected_result)
|
||||
|
||||
def test_sqr(a = 0, b = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a)]
|
||||
try:
|
||||
res = sqr(*args)
|
||||
except OSError as e:
|
||||
print("{} while trying to square {}.".format(e, a))
|
||||
if EXIT_ON_FAIL: exit(3)
|
||||
return False
|
||||
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = a * a
|
||||
return test("test_sqr", res, [a], expected_error, expected_result)
|
||||
|
||||
def test_div(a = 0, b = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), arg_to_odin(b)]
|
||||
try:
|
||||
res = div(*args)
|
||||
except OSError as e:
|
||||
print("{} while trying divide to {} / {}.".format(e, a, b))
|
||||
if EXIT_ON_FAIL: exit(3)
|
||||
return False
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
#
|
||||
# We don't round the division results, so if one component is negative, we're off by one.
|
||||
#
|
||||
if a < 0 and b > 0:
|
||||
expected_result = int(-(abs(a) // b))
|
||||
elif b < 0 and a > 0:
|
||||
expected_result = int(-(a // abs((b))))
|
||||
else:
|
||||
expected_result = a // b if b != 0 else None
|
||||
return test("test_div", res, [a, b], expected_error, expected_result)
|
||||
|
||||
|
||||
def test_log(a = 0, base = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), base]
|
||||
res = int_log(*args)
|
||||
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = int(math.log(a, base))
|
||||
return test("test_log", res, [a, base], expected_error, expected_result)
|
||||
|
||||
def test_pow(base = 0, power = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(base), power]
|
||||
res = int_pow(*args)
|
||||
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
if power < 0:
|
||||
expected_result = 0
|
||||
else:
|
||||
# NOTE(Jeroen): Don't use `math.pow`, it's a floating point approximation.
|
||||
# Use built-in `pow` or `a**b` instead.
|
||||
expected_result = pow(base, power)
|
||||
return test("test_pow", res, [base, power], expected_error, expected_result)
|
||||
|
||||
def test_sqrt(number = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(number)]
|
||||
try:
|
||||
res = int_sqrt(*args)
|
||||
except OSError as e:
|
||||
print("{} while trying to sqrt {}.".format(e, number))
|
||||
if EXIT_ON_FAIL: exit(3)
|
||||
return False
|
||||
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
if number < 0:
|
||||
expected_result = 0
|
||||
else:
|
||||
expected_result = int(math.isqrt(number))
|
||||
return test("test_sqrt", res, [number], expected_error, expected_result)
|
||||
|
||||
def root_n(number, root):
|
||||
u, s = number, number + 1
|
||||
while u < s:
|
||||
s = u
|
||||
t = (root-1) * s + number // pow(s, root - 1)
|
||||
u = t // root
|
||||
return s
|
||||
|
||||
def test_root_n(number = 0, root = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(number), root]
|
||||
res = int_root_n(*args)
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
if number < 0:
|
||||
expected_result = 0
|
||||
else:
|
||||
expected_result = root_n(number, root)
|
||||
|
||||
return test("test_root_n", res, [number, root], expected_error, expected_result)
|
||||
|
||||
def test_shl_digit(a = 0, digits = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), digits]
|
||||
res = int_shl_digit(*args)
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = a << (digits * 60)
|
||||
return test("test_shl_digit", res, [a, digits], expected_error, expected_result)
|
||||
|
||||
def test_shr_digit(a = 0, digits = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), digits]
|
||||
res = int_shr_digit(*args)
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
if a < 0:
|
||||
# Don't pass negative numbers. We have a shr_signed.
|
||||
return False
|
||||
else:
|
||||
expected_result = a >> (digits * 60)
|
||||
|
||||
return test("test_shr_digit", res, [a, digits], expected_error, expected_result)
|
||||
|
||||
def test_shl(a = 0, bits = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), bits]
|
||||
res = int_shl(*args)
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = a << bits
|
||||
return test("test_shl", res, [a, bits], expected_error, expected_result)
|
||||
|
||||
def test_shr(a = 0, bits = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), bits]
|
||||
res = int_shr(*args)
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
if a < 0:
|
||||
# Don't pass negative numbers. We have a shr_signed.
|
||||
return False
|
||||
else:
|
||||
expected_result = a >> bits
|
||||
|
||||
return test("test_shr", res, [a, bits], expected_error, expected_result)
|
||||
|
||||
def test_shr_signed(a = 0, bits = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), bits]
|
||||
res = int_shr_signed(*args)
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = a >> bits
|
||||
|
||||
return test("test_shr_signed", res, [a, bits], expected_error, expected_result)
|
||||
|
||||
def test_factorial(number = 0, expected_error = Error.Okay):
|
||||
print("Factorial:", number)
|
||||
args = [number]
|
||||
try:
|
||||
res = int_factorial(*args)
|
||||
except OSError as e:
|
||||
print("{} while trying to factorial {}.".format(e, number))
|
||||
if EXIT_ON_FAIL: exit(3)
|
||||
return False
|
||||
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = math.factorial(number)
|
||||
|
||||
return test("test_factorial", res, [number], expected_error, expected_result)
|
||||
|
||||
def test_gcd(a = 0, b = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), arg_to_odin(b)]
|
||||
res = int_gcd(*args)
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = math.gcd(a, b)
|
||||
|
||||
return test("test_gcd", res, [a, b], expected_error, expected_result)
|
||||
|
||||
def test_lcm(a = 0, b = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a), arg_to_odin(b)]
|
||||
res = int_lcm(*args)
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = math.lcm(a, b)
|
||||
|
||||
return test("test_lcm", res, [a, b], expected_error, expected_result)
|
||||
|
||||
def test_is_square(a = 0, b = 0, expected_error = Error.Okay):
|
||||
args = [arg_to_odin(a)]
|
||||
res = is_square(*args)
|
||||
expected_result = None
|
||||
if expected_error == Error.Okay:
|
||||
expected_result = str(math.isqrt(a) ** 2 == a) if a > 0 else "False"
|
||||
|
||||
return test("test_is_square", res, [a], expected_error, expected_result)
|
||||
|
||||
# TODO(Jeroen): Make sure tests cover edge cases, fast paths, and so on.
|
||||
#
|
||||
# The last two arguments in tests are the expected error and expected result.
|
||||
#
|
||||
# The expected error defaults to None.
|
||||
# By default the Odin implementation will be tested against the Python one.
|
||||
# You can override that by supplying an expected result as the last argument instead.
|
||||
|
||||
TESTS = {
|
||||
test_add: [
|
||||
[ 1234, 5432],
|
||||
],
|
||||
test_sub: [
|
||||
[ 1234, 5432],
|
||||
],
|
||||
test_mul: [
|
||||
[ 1234, 5432],
|
||||
[ 0xd3b4e926aaba3040e1c12b5ea553b5, 0x1a821e41257ed9281bee5bc7789ea7 ],
|
||||
[ 1 << 21_105, 1 << 21_501 ],
|
||||
],
|
||||
test_sqr: [
|
||||
[ 5432],
|
||||
[ 0xd3b4e926aaba3040e1c12b5ea553b5 ],
|
||||
],
|
||||
test_div: [
|
||||
[ 54321, 12345],
|
||||
[ 55431, 0, Error.Division_by_Zero],
|
||||
[ 12980742146337069150589594264770969721, 4611686018427387904 ],
|
||||
[ 831956404029821402159719858789932422, 243087903122332132 ],
|
||||
],
|
||||
test_log: [
|
||||
[ 3192, 1, Error.Invalid_Argument],
|
||||
[ -1234, 2, Error.Math_Domain_Error],
|
||||
[ 0, 2, Error.Math_Domain_Error],
|
||||
[ 1024, 2],
|
||||
],
|
||||
test_pow: [
|
||||
[ 0, -1, Error.Math_Domain_Error ], # Math
|
||||
[ 0, 0 ], # 1
|
||||
[ 0, 2 ], # 0
|
||||
[ 42, -1,], # 0
|
||||
[ 42, 1 ], # 1
|
||||
[ 42, 0 ], # 42
|
||||
[ 42, 2 ], # 42*42
|
||||
],
|
||||
test_sqrt: [
|
||||
[ -1, Error.Invalid_Argument, ],
|
||||
[ 42, Error.Okay, ],
|
||||
[ 12345678901234567890, Error.Okay, ],
|
||||
[ 1298074214633706907132624082305024, Error.Okay, ],
|
||||
[ 686885735734829009541949746871140768343076607029752932751182108475420900392874228486622313727012705619148037570309621219533087263900443932890792804879473795673302686046941536636874184361869252299636701671980034458333859202703255467709267777184095435235980845369829397344182319113372092844648570818726316581751114346501124871729572474923695509057166373026411194094493240101036672016770945150422252961487398124677567028263059046193391737576836378376192651849283925197438927999526058932679219572030021792914065825542626400207956134072247020690107136531852625253942429167557531123651471221455967386267137846791963149859804549891438562641323068751514370656287452006867713758971418043865298618635213551059471668293725548570452377976322899027050925842868079489675596835389444833567439058609775325447891875359487104691935576723532407937236505941186660707032433807075470656782452889754501872408562496805517394619388777930253411467941214807849472083814447498068636264021405175653742244368865090604940094889189800007448083930490871954101880815781177612910234741529950538835837693870921008635195545246771593130784786737543736434086434015200264933536294884482218945403958647118802574342840790536176272341586020230110889699633073513016344826709214, Error.Okay, ],
|
||||
],
|
||||
test_root_n: [
|
||||
[ 1298074214633706907132624082305024, 2, Error.Okay, ],
|
||||
],
|
||||
test_shl_digit: [
|
||||
[ 3192, 1 ],
|
||||
[ 1298074214633706907132624082305024, 2 ],
|
||||
[ 1024, 3 ],
|
||||
],
|
||||
test_shr_digit: [
|
||||
[ 3680125442705055547392, 1 ],
|
||||
[ 1725436586697640946858688965569256363112777243042596638790631055949824, 2 ],
|
||||
[ 219504133884436710204395031992179571, 2 ],
|
||||
],
|
||||
test_shl: [
|
||||
[ 3192, 1 ],
|
||||
[ 1298074214633706907132624082305024, 2 ],
|
||||
[ 1024, 3 ],
|
||||
],
|
||||
test_shr: [
|
||||
[ 3680125442705055547392, 1 ],
|
||||
[ 1725436586697640946858688965569256363112777243042596638790631055949824, 2 ],
|
||||
[ 219504133884436710204395031992179571, 2 ],
|
||||
],
|
||||
test_shr_signed: [
|
||||
[ -611105530635358368578155082258244262, 12 ],
|
||||
[ -149195686190273039203651143129455, 12 ],
|
||||
[ 611105530635358368578155082258244262, 12 ],
|
||||
[ 149195686190273039203651143129455, 12 ],
|
||||
],
|
||||
test_factorial: [
|
||||
[ 6_000 ], # Regular factorial, see cutoff in common.odin.
|
||||
[ 12_345 ], # Binary split factorial
|
||||
],
|
||||
test_gcd: [
|
||||
[ 23, 25, ],
|
||||
[ 125, 25, ],
|
||||
[ 125, 0, ],
|
||||
[ 0, 0, ],
|
||||
[ 0, 125,],
|
||||
],
|
||||
test_lcm: [
|
||||
[ 23, 25,],
|
||||
[ 125, 25, ],
|
||||
[ 125, 0, ],
|
||||
[ 0, 0, ],
|
||||
[ 0, 125,],
|
||||
],
|
||||
test_is_square: [
|
||||
[ 12, ],
|
||||
[ 92232459121502451677697058974826760244863271517919321608054113675118660929276431348516553336313179167211015633639725554914519355444316239500734169769447134357534241879421978647995614218985202290368055757891124109355450669008628757662409138767505519391883751112010824030579849970582074544353971308266211776494228299586414907715854328360867232691292422194412634523666770452490676515117702116926803826546868467146319938818238521874072436856528051486567230096290549225463582766830777324099589751817442141036031904145041055454639783559905920619197290800070679733841430619962318433709503256637256772215111521321630777950145713049902839937043785039344243357384899099910837463164007565230287809026956254332260375327814271845678201, ]
|
||||
],
|
||||
}
|
||||
|
||||
if not args.fast_tests:
|
||||
TESTS[test_factorial].append(
|
||||
# This one on its own takes around 800ms, so we exclude it for FAST_TESTS
|
||||
[ 10_000 ],
|
||||
)
|
||||
|
||||
total_passes = 0
|
||||
total_failures = 0
|
||||
|
||||
#
|
||||
# test_shr_signed also tests shr, so we're not going to test shr randomly.
|
||||
#
|
||||
RANDOM_TESTS = [
|
||||
test_add, test_sub, test_mul, test_sqr, test_div,
|
||||
test_log, test_pow, test_sqrt, test_root_n,
|
||||
test_shl_digit, test_shr_digit, test_shl, test_shr_signed,
|
||||
test_gcd, test_lcm, test_is_square,
|
||||
]
|
||||
SKIP_LARGE = [
|
||||
test_pow, test_root_n, # test_gcd,
|
||||
]
|
||||
SKIP_LARGEST = []
|
||||
|
||||
# Untimed warmup.
|
||||
for test_proc in TESTS:
|
||||
for t in TESTS[test_proc]:
|
||||
res = test_proc(*t)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("\n---- math/big tests ----")
|
||||
print()
|
||||
|
||||
max_name = 0
|
||||
for test_proc in TESTS:
|
||||
max_name = max(max_name, len(test_proc.__name__))
|
||||
|
||||
fmt_string = "{name:>{max_name}}: {count_pass:7,} passes and {count_fail:7,} failures in {timing:9.3f} ms."
|
||||
fmt_string = fmt_string.replace("{max_name}", str(max_name))
|
||||
|
||||
for test_proc in TESTS:
|
||||
count_pass = 0
|
||||
count_fail = 0
|
||||
TIMINGS = {}
|
||||
for t in TESTS[test_proc]:
|
||||
start = time.perf_counter()
|
||||
res = test_proc(*t)
|
||||
diff = time.perf_counter() - start
|
||||
TOTAL_TIME += diff
|
||||
|
||||
if test_proc not in TIMINGS:
|
||||
TIMINGS[test_proc] = diff
|
||||
else:
|
||||
TIMINGS[test_proc] += diff
|
||||
|
||||
if res:
|
||||
count_pass += 1
|
||||
total_passes += 1
|
||||
else:
|
||||
count_fail += 1
|
||||
total_failures += 1
|
||||
|
||||
print(fmt_string.format(name=test_proc.__name__, count_pass=count_pass, count_fail=count_fail, timing=TIMINGS[test_proc] * 1_000))
|
||||
|
||||
for BITS, ITERATIONS in BITS_AND_ITERATIONS:
|
||||
print()
|
||||
print("---- math/big with two random {bits:,} bit numbers ----".format(bits=BITS))
|
||||
print()
|
||||
|
||||
#
|
||||
# We've already tested up to the 10th root.
|
||||
#
|
||||
TEST_ROOT_N_PARAMS = [2, 3, 4, 5, 6]
|
||||
|
||||
for test_proc in RANDOM_TESTS:
|
||||
if BITS > 1_200 and test_proc in SKIP_LARGE: continue
|
||||
if BITS > 4_096 and test_proc in SKIP_LARGEST: continue
|
||||
|
||||
count_pass = 0
|
||||
count_fail = 0
|
||||
TIMINGS = {}
|
||||
|
||||
UNTIL_ITERS = ITERATIONS
|
||||
if test_proc == test_root_n and BITS == 1_200:
|
||||
UNTIL_ITERS /= 10
|
||||
|
||||
UNTIL_TIME = TOTAL_TIME + BITS / args.timed_bits
|
||||
# We run each test for a second per 20k bits
|
||||
|
||||
index = 0
|
||||
|
||||
while we_iterate():
|
||||
a = randint(-(1 << BITS), 1 << BITS)
|
||||
b = randint(-(1 << BITS), 1 << BITS)
|
||||
|
||||
if test_proc == test_div:
|
||||
# We've already tested division by zero above.
|
||||
bits = int(BITS * 0.6)
|
||||
b = randint(-(1 << bits), 1 << bits)
|
||||
if b == 0:
|
||||
b == 42
|
||||
elif test_proc == test_log:
|
||||
# We've already tested log's domain errors.
|
||||
a = randint(1, 1 << BITS)
|
||||
b = randint(2, 1 << 60)
|
||||
elif test_proc == test_pow:
|
||||
b = randint(1, 10)
|
||||
elif test_proc == test_sqrt:
|
||||
a = randint(1, 1 << BITS)
|
||||
b = Error.Okay
|
||||
elif test_proc == test_root_n:
|
||||
a = randint(1, 1 << BITS)
|
||||
b = TEST_ROOT_N_PARAMS[index]
|
||||
index = (index + 1) % len(TEST_ROOT_N_PARAMS)
|
||||
elif test_proc == test_shl_digit:
|
||||
b = randint(0, 10);
|
||||
elif test_proc == test_shr_digit:
|
||||
a = abs(a)
|
||||
b = randint(0, 10);
|
||||
elif test_proc == test_shl:
|
||||
b = randint(0, min(BITS, 120))
|
||||
elif test_proc == test_shr_signed:
|
||||
b = randint(0, min(BITS, 120))
|
||||
elif test_proc == test_is_square:
|
||||
a = randint(0, 1 << BITS)
|
||||
else:
|
||||
b = randint(0, 1 << BITS)
|
||||
|
||||
res = None
|
||||
|
||||
start = time.perf_counter()
|
||||
res = test_proc(a, b)
|
||||
diff = time.perf_counter() - start
|
||||
|
||||
TOTAL_TIME += diff
|
||||
|
||||
if test_proc not in TIMINGS:
|
||||
TIMINGS[test_proc] = diff
|
||||
else:
|
||||
TIMINGS[test_proc] += diff
|
||||
|
||||
if res:
|
||||
count_pass += 1; total_passes += 1
|
||||
else:
|
||||
count_fail += 1; total_failures += 1
|
||||
|
||||
print(fmt_string.format(name=test_proc.__name__, count_pass=count_pass, count_fail=count_fail, timing=TIMINGS[test_proc] * 1_000))
|
||||
|
||||
print()
|
||||
print("---- THE END ----")
|
||||
print()
|
||||
print(fmt_string.format(name="total", count_pass=total_passes, count_fail=total_failures, timing=TOTAL_TIME * 1_000))
|
||||
|
||||
if total_failures:
|
||||
exit(1)
|
||||
@@ -0,0 +1,81 @@
|
||||
//+ignore
|
||||
package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
A BigInt implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
|
||||
*/
|
||||
|
||||
import "core:fmt"
|
||||
import "core:time"
|
||||
|
||||
Category :: enum {
|
||||
itoa,
|
||||
atoi,
|
||||
factorial,
|
||||
factorial_bin,
|
||||
choose,
|
||||
lsb,
|
||||
ctz,
|
||||
sqr,
|
||||
bitfield_extract,
|
||||
rm_trials,
|
||||
};
|
||||
|
||||
Event :: struct {
|
||||
ticks: time.Duration,
|
||||
count: int,
|
||||
cycles: u64,
|
||||
}
|
||||
Timings := [Category]Event{};
|
||||
|
||||
print_timings :: proc() {
|
||||
duration :: proc(d: time.Duration) -> (res: string) {
|
||||
switch {
|
||||
case d < time.Microsecond:
|
||||
return fmt.tprintf("%v ns", time.duration_nanoseconds(d));
|
||||
case d < time.Millisecond:
|
||||
return fmt.tprintf("%v µs", time.duration_microseconds(d));
|
||||
case:
|
||||
return fmt.tprintf("%v ms", time.duration_milliseconds(d));
|
||||
}
|
||||
}
|
||||
|
||||
for v in Timings {
|
||||
if v.count > 0 {
|
||||
fmt.println("\nTimings:");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for v, i in Timings {
|
||||
if v.count > 0 {
|
||||
avg_ticks := time.Duration(f64(v.ticks) / f64(v.count));
|
||||
avg_cycles := f64(v.cycles) / f64(v.count);
|
||||
|
||||
fmt.printf("\t%v: %s / %v cycles (avg), %s / %v cycles (total, %v calls)\n", i, duration(avg_ticks), avg_cycles, duration(v.ticks), v.cycles, v.count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(deferred_in_out=_SCOPE_END)
|
||||
SCOPED_TIMING :: #force_inline proc(c: Category) -> (ticks: time.Tick, cycles: u64) {
|
||||
cycles = time.read_cycle_counter();
|
||||
ticks = time.tick_now();
|
||||
return;
|
||||
}
|
||||
_SCOPE_END :: #force_inline proc(c: Category, ticks: time.Tick, cycles: u64) {
|
||||
cycles_now := time.read_cycle_counter();
|
||||
ticks_now := time.tick_now();
|
||||
|
||||
Timings[c].ticks = time.tick_diff(ticks, ticks_now);
|
||||
Timings[c].cycles = cycles_now - cycles;
|
||||
Timings[c].count += 1;
|
||||
}
|
||||
SCOPED_COUNT_ADD :: #force_inline proc(c: Category, count: int) {
|
||||
Timings[c].count += count;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package math_bits
|
||||
|
||||
import "intrinsics"
|
||||
import "core:intrinsics"
|
||||
|
||||
U8_MIN :: 0;
|
||||
U16_MIN :: 0;
|
||||
|
||||
@@ -3,10 +3,10 @@ package math_fixed
|
||||
import "core:math"
|
||||
import "core:strconv"
|
||||
|
||||
import "intrinsics"
|
||||
import "core:intrinsics"
|
||||
_ :: intrinsics;
|
||||
|
||||
Fixed :: struct($Backing: typeid, Fraction_Width: uint)
|
||||
Fixed :: struct($Backing: typeid, $Fraction_Width: uint)
|
||||
where
|
||||
intrinsics.type_is_integer(Backing),
|
||||
0 <= Fraction_Width,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package linalg
|
||||
|
||||
import "builtin"
|
||||
import "core:builtin"
|
||||
import "core:math"
|
||||
|
||||
radians :: proc(degrees: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package linalg
|
||||
|
||||
import "core:math"
|
||||
import "intrinsics"
|
||||
import "core:intrinsics"
|
||||
|
||||
// Generic
|
||||
|
||||
@@ -41,7 +41,7 @@ scalar_dot :: proc(a, b: $T) -> T where IS_FLOAT(T), !IS_ARRAY(T) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
vector_dot :: proc(a, b: $T/[$N]$E) -> (c: E) where IS_NUMERIC(E) {
|
||||
vector_dot :: proc(a, b: $T/[$N]$E) -> (c: E) where IS_NUMERIC(E) #no_bounds_check {
|
||||
for i in 0..<N {
|
||||
c += a[i] * b[i];
|
||||
}
|
||||
@@ -60,7 +60,7 @@ quaternion256_dot :: proc(a, b: $T/quaternion256) -> (c: f64) {
|
||||
dot :: proc{scalar_dot, vector_dot, quaternion64_dot, quaternion128_dot, quaternion256_dot};
|
||||
|
||||
inner_product :: dot;
|
||||
outer_product :: proc(a: $A/[$M]$E, b: $B/[$N]E) -> (out: [M][N]E) where IS_NUMERIC(E) {
|
||||
outer_product :: proc(a: $A/[$M]$E, b: $B/[$N]E) -> (out: [M][N]E) where IS_NUMERIC(E) #no_bounds_check {
|
||||
for i in 0..<M {
|
||||
for j in 0..<N {
|
||||
out[i][j] = a[i]*b[j];
|
||||
@@ -156,7 +156,7 @@ projection :: proc(x, normal: $T/[$N]$E) -> T where IS_NUMERIC(E) {
|
||||
return dot(x, normal) / dot(normal, normal) * normal;
|
||||
}
|
||||
|
||||
identity :: proc($T: typeid/[$N][N]$E) -> (m: T) {
|
||||
identity :: proc($T: typeid/[$N][N]$E) -> (m: T) #no_bounds_check {
|
||||
for i in 0..<N {
|
||||
m[i][i] = E(1);
|
||||
}
|
||||
@@ -170,8 +170,7 @@ trace :: proc(m: $T/[$N][N]$E) -> (tr: E) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
transpose :: proc(a: $T/[$N][$M]$E) -> (m: T) {
|
||||
transpose :: proc(a: $T/[$N][$M]$E) -> (m: (T when N == M else [M][N]E)) #no_bounds_check {
|
||||
for j in 0..<M {
|
||||
for i in 0..<N {
|
||||
m[j][i] = a[i][j];
|
||||
@@ -181,8 +180,7 @@ transpose :: proc(a: $T/[$N][$M]$E) -> (m: T) {
|
||||
}
|
||||
|
||||
matrix_mul :: proc(a, b: $M/[$N][N]$E) -> (c: M)
|
||||
where !IS_ARRAY(E),
|
||||
IS_NUMERIC(E) {
|
||||
where !IS_ARRAY(E), IS_NUMERIC(E) #no_bounds_check {
|
||||
for i in 0..<N {
|
||||
for k in 0..<N {
|
||||
for j in 0..<N {
|
||||
@@ -194,8 +192,7 @@ matrix_mul :: proc(a, b: $M/[$N][N]$E) -> (c: M)
|
||||
}
|
||||
|
||||
matrix_comp_mul :: proc(a, b: $M/[$J][$I]$E) -> (c: M)
|
||||
where !IS_ARRAY(E),
|
||||
IS_NUMERIC(E) {
|
||||
where !IS_ARRAY(E), IS_NUMERIC(E) #no_bounds_check {
|
||||
for j in 0..<J {
|
||||
for i in 0..<I {
|
||||
c[j][i] = a[j][i] * b[j][i];
|
||||
@@ -205,9 +202,7 @@ matrix_comp_mul :: proc(a, b: $M/[$J][$I]$E) -> (c: M)
|
||||
}
|
||||
|
||||
matrix_mul_differ :: proc(a: $A/[$J][$I]$E, b: $B/[$K][J]E) -> (c: [K][I]E)
|
||||
where !IS_ARRAY(E),
|
||||
IS_NUMERIC(E),
|
||||
I != K {
|
||||
where !IS_ARRAY(E), IS_NUMERIC(E), I != K #no_bounds_check {
|
||||
for k in 0..<K {
|
||||
for j in 0..<J {
|
||||
for i in 0..<I {
|
||||
@@ -220,8 +215,7 @@ matrix_mul_differ :: proc(a: $A/[$J][$I]$E, b: $B/[$K][J]E) -> (c: [K][I]E)
|
||||
|
||||
|
||||
matrix_mul_vector :: proc(a: $A/[$I][$J]$E, b: $B/[I]E) -> (c: B)
|
||||
where !IS_ARRAY(E),
|
||||
IS_NUMERIC(E) {
|
||||
where !IS_ARRAY(E), IS_NUMERIC(E) #no_bounds_check {
|
||||
for i in 0..<I {
|
||||
for j in 0..<J {
|
||||
c[j] += a[i][j] * b[i];
|
||||
@@ -329,14 +323,14 @@ cubic :: proc(v1, v2, v3, v4: $T/[$N]$E, s: E) -> T {
|
||||
|
||||
|
||||
|
||||
array_cast :: proc(v: $A/[$N]$T, $Elem_Type: typeid) -> (w: [N]Elem_Type) {
|
||||
array_cast :: proc(v: $A/[$N]$T, $Elem_Type: typeid) -> (w: [N]Elem_Type) #no_bounds_check {
|
||||
for i in 0..<N {
|
||||
w[i] = Elem_Type(v[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
matrix_cast :: proc(v: $A/[$M][$N]$T, $Elem_Type: typeid) -> (w: [M][N]Elem_Type) {
|
||||
matrix_cast :: proc(v: $A/[$M][$N]$T, $Elem_Type: typeid) -> (w: [M][N]Elem_Type) #no_bounds_check {
|
||||
for i in 0..<M {
|
||||
for j in 0..<N {
|
||||
w[i][j] = Elem_Type(v[i][j]);
|
||||
|
||||
@@ -1284,13 +1284,13 @@ matrix3_from_quaternion :: proc{
|
||||
|
||||
|
||||
matrix3_inverse_f16 :: proc(m: Matrix3f16) -> Matrix3f16 {
|
||||
return transpose(matrix3_inverse_transpose(m));
|
||||
return auto_cast transpose(matrix3_inverse_transpose(m));
|
||||
}
|
||||
matrix3_inverse_f32 :: proc(m: Matrix3f32) -> Matrix3f32 {
|
||||
return transpose(matrix3_inverse_transpose(m));
|
||||
return auto_cast transpose(matrix3_inverse_transpose(m));
|
||||
}
|
||||
matrix3_inverse_f64 :: proc(m: Matrix3f64) -> Matrix3f64 {
|
||||
return transpose(matrix3_inverse_transpose(m));
|
||||
return auto_cast transpose(matrix3_inverse_transpose(m));
|
||||
}
|
||||
matrix3_inverse :: proc{
|
||||
matrix3_inverse_f16,
|
||||
@@ -1655,13 +1655,13 @@ matrix4_from_trs :: proc{
|
||||
|
||||
|
||||
matrix4_inverse_f16 :: proc(m: Matrix4f16) -> Matrix4f16 {
|
||||
return transpose(matrix4_inverse_transpose(m));
|
||||
return auto_cast transpose(matrix4_inverse_transpose(m));
|
||||
}
|
||||
matrix4_inverse_f32 :: proc(m: Matrix4f32) -> Matrix4f32 {
|
||||
return transpose(matrix4_inverse_transpose(m));
|
||||
return auto_cast transpose(matrix4_inverse_transpose(m));
|
||||
}
|
||||
matrix4_inverse_f64 :: proc(m: Matrix4f64) -> Matrix4f64 {
|
||||
return transpose(matrix4_inverse_transpose(m));
|
||||
return auto_cast transpose(matrix4_inverse_transpose(m));
|
||||
}
|
||||
matrix4_inverse :: proc{
|
||||
matrix4_inverse_f16,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package math
|
||||
|
||||
import "intrinsics"
|
||||
import "core:intrinsics"
|
||||
_ :: intrinsics;
|
||||
|
||||
Float_Class :: enum {
|
||||
|
||||
@@ -95,7 +95,7 @@ int63_max :: proc(n: i64, r: ^Rand = nil) -> i64 {
|
||||
|
||||
int127_max :: proc(n: i128, r: ^Rand = nil) -> i128 {
|
||||
if n <= 0 {
|
||||
panic("Invalid argument to int63_max");
|
||||
panic("Invalid argument to int127_max");
|
||||
}
|
||||
if n&(n-1) == 0 {
|
||||
return int127(r) & (n-1);
|
||||
|
||||
+65
-34
@@ -31,10 +31,11 @@ Allocator_Query_Info :: struct {
|
||||
Allocator_Error :: runtime.Allocator_Error;
|
||||
/*
|
||||
Allocator_Error :: enum byte {
|
||||
None = 0,
|
||||
Out_Of_Memory = 1,
|
||||
Invalid_Pointer = 2,
|
||||
Invalid_Argument = 3,
|
||||
None = 0,
|
||||
Out_Of_Memory = 1,
|
||||
Invalid_Pointer = 2,
|
||||
Invalid_Argument = 3,
|
||||
Mode_Not_Implemented = 4,
|
||||
}
|
||||
*/
|
||||
Allocator_Proc :: runtime.Allocator_Proc;
|
||||
@@ -121,7 +122,15 @@ resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_AL
|
||||
return nil;
|
||||
}
|
||||
data, err := allocator.procedure(allocator.data, Allocator_Mode.Resize, new_size, alignment, ptr, old_size, loc);
|
||||
_ = err;
|
||||
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);
|
||||
}
|
||||
return raw_data(data);
|
||||
}
|
||||
|
||||
@@ -140,7 +149,16 @@ resize_bytes :: proc(old_data: []byte, new_size: int, alignment: int = DEFAULT_A
|
||||
} else if ptr == nil {
|
||||
return allocator.procedure(allocator.data, Allocator_Mode.Alloc, new_size, alignment, nil, 0, loc);
|
||||
}
|
||||
return allocator.procedure(allocator.data, Allocator_Mode.Resize, new_size, alignment, ptr, old_size, 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;
|
||||
}
|
||||
|
||||
query_features :: proc(allocator: Allocator, loc := #caller_location) -> (set: Allocator_Mode_Set) {
|
||||
@@ -189,51 +207,54 @@ delete :: proc{
|
||||
};
|
||||
|
||||
|
||||
new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> ^T {
|
||||
new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> (^T, Allocator_Error) {
|
||||
return new_aligned(T, align_of(T), allocator, loc);
|
||||
}
|
||||
new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> ^T {
|
||||
ptr := (^T)(alloc(size_of(T), alignment, allocator, loc));
|
||||
if ptr != nil { ptr^ = T{}; }
|
||||
return ptr;
|
||||
new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) {
|
||||
data := alloc_bytes(size_of(T), alignment, allocator, loc) or_return;
|
||||
t = (^T)(raw_data(data));
|
||||
return;
|
||||
}
|
||||
new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_location) -> ^T {
|
||||
ptr := (^T)(alloc(size_of(T), align_of(T), allocator, loc));
|
||||
if ptr != nil { ptr^ = data; }
|
||||
return ptr;
|
||||
data := alloc_bytes(size_of(T), alignment, allocator, loc) or_return;
|
||||
t = (^T)(raw_data(data));
|
||||
if t != nil {
|
||||
t^ = data;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DEFAULT_RESERVE_CAPACITY :: 16;
|
||||
|
||||
make_slice :: proc($T: typeid/[]$E, auto_cast len: int, allocator := context.allocator, loc := #caller_location) -> T {
|
||||
make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (slice: T, err: Allocator_Error) {
|
||||
runtime.make_slice_error_loc(loc, len);
|
||||
data := alloc_bytes(size_of(E)*len, alignment, allocator, loc) or_return;
|
||||
if data == nil && size_of(E) != 0 {
|
||||
return;
|
||||
}
|
||||
slice = transmute(T)Raw_Slice{raw_data(data), len};
|
||||
return;
|
||||
}
|
||||
make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) {
|
||||
return make_aligned(T, len, align_of(E), allocator, loc);
|
||||
}
|
||||
make_aligned :: proc($T: typeid/[]$E, auto_cast len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> T {
|
||||
runtime.make_slice_error_loc(loc, len);
|
||||
data := alloc(size_of(E)*len, alignment, allocator, loc);
|
||||
if data == nil && size_of(E) != 0 {
|
||||
return nil;
|
||||
}
|
||||
zero(data, size_of(E)*len);
|
||||
s := Raw_Slice{data, len};
|
||||
return transmute(T)s;
|
||||
make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) {
|
||||
return make_dynamic_array_len_cap(T, 0, DEFAULT_RESERVE_CAPACITY, allocator, loc);
|
||||
}
|
||||
make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> T {
|
||||
return make_dynamic_array_len_cap(T, 0, 16, allocator, loc);
|
||||
}
|
||||
make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, auto_cast len: int, allocator := context.allocator, loc := #caller_location) -> T {
|
||||
make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) {
|
||||
return make_dynamic_array_len_cap(T, len, len, allocator, loc);
|
||||
}
|
||||
make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, auto_cast len: int, auto_cast cap: int, allocator := context.allocator, loc := #caller_location) -> T {
|
||||
make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) {
|
||||
runtime.make_dynamic_array_error_loc(loc, len, cap);
|
||||
data := alloc(size_of(E)*cap, align_of(E), allocator, loc);
|
||||
s := Raw_Dynamic_Array{data, len, cap, allocator};
|
||||
data := alloc_bytes(size_of(E)*cap, align_of(E), allocator, loc) or_return;
|
||||
s := Raw_Dynamic_Array{raw_data(data), len, cap, allocator};
|
||||
if data == nil && size_of(E) != 0 {
|
||||
s.len, s.cap = 0, 0;
|
||||
}
|
||||
zero(data, size_of(E)*len);
|
||||
return transmute(T)s;
|
||||
array = transmute(T)s;
|
||||
return;
|
||||
}
|
||||
make_map :: proc($T: typeid/map[$K]$E, auto_cast cap: int = 16, allocator := context.allocator, loc := #caller_location) -> T {
|
||||
make_map :: proc($T: typeid/map[$K]$E, #any_int cap: int = DEFAULT_RESERVE_CAPACITY, allocator := context.allocator, loc := #caller_location) -> T {
|
||||
runtime.make_map_expr_error_loc(loc, cap);
|
||||
context.allocator = allocator;
|
||||
|
||||
@@ -241,6 +262,15 @@ make_map :: proc($T: typeid/map[$K]$E, auto_cast cap: int = 16, allocator := con
|
||||
reserve_map(&m, cap);
|
||||
return m;
|
||||
}
|
||||
make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) {
|
||||
runtime.make_slice_error_loc(loc, len);
|
||||
data := alloc_bytes(size_of(E)*len, align_of(E), allocator, loc) or_return;
|
||||
if data == nil && size_of(E) != 0 {
|
||||
return;
|
||||
}
|
||||
mp = cast(T)raw_data(data);
|
||||
return;
|
||||
}
|
||||
|
||||
make :: proc{
|
||||
make_slice,
|
||||
@@ -248,6 +278,7 @@ make :: proc{
|
||||
make_dynamic_array_len,
|
||||
make_dynamic_array_len_cap,
|
||||
make_map,
|
||||
make_multi_pointer,
|
||||
};
|
||||
|
||||
|
||||
|
||||
+13
-110
@@ -1,6 +1,6 @@
|
||||
package mem
|
||||
|
||||
import "intrinsics"
|
||||
import "core:intrinsics"
|
||||
import "core:runtime"
|
||||
|
||||
nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
@@ -67,8 +67,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
return byte_slice(ptr, size), nil;
|
||||
|
||||
case .Free:
|
||||
// NOTE(bill): Free all at once
|
||||
// Use Arena_Temp_Memory if you want to free a block
|
||||
return nil, .Mode_Not_Implemented;
|
||||
|
||||
case .Free_All:
|
||||
arena.offset = 0;
|
||||
@@ -84,7 +83,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
return nil, nil;
|
||||
|
||||
case .Query_Info:
|
||||
return nil, nil;
|
||||
return nil, .Mode_Not_Implemented;
|
||||
}
|
||||
|
||||
return nil, nil;
|
||||
@@ -115,12 +114,13 @@ Scratch_Allocator :: struct {
|
||||
leaked_allocations: [dynamic][]byte,
|
||||
}
|
||||
|
||||
scratch_allocator_init :: proc(s: ^Scratch_Allocator, size: int, backup_allocator := context.allocator) {
|
||||
s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator);
|
||||
scratch_allocator_init :: proc(s: ^Scratch_Allocator, size: int, backup_allocator := context.allocator) -> Allocator_Error {
|
||||
s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator) or_return;
|
||||
s.curr_offset = 0;
|
||||
s.prev_allocation = nil;
|
||||
s.backup_allocator = backup_allocator;
|
||||
s.leaked_allocations.allocator = backup_allocator;
|
||||
return nil;
|
||||
}
|
||||
|
||||
scratch_allocator_destroy :: proc(s: ^Scratch_Allocator) {
|
||||
@@ -189,7 +189,7 @@ scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
return ptr, err;
|
||||
}
|
||||
if s.leaked_allocations == nil {
|
||||
s.leaked_allocations = make([dynamic][]byte, a);
|
||||
s.leaked_allocations, err = make([dynamic][]byte, a);
|
||||
}
|
||||
append(&s.leaked_allocations, ptr);
|
||||
|
||||
@@ -262,7 +262,7 @@ scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
return nil, nil;
|
||||
|
||||
case .Query_Info:
|
||||
return nil, nil;
|
||||
return nil, .Mode_Not_Implemented;
|
||||
}
|
||||
|
||||
return nil, nil;
|
||||
@@ -426,7 +426,7 @@ stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
}
|
||||
return nil, nil;
|
||||
case .Query_Info:
|
||||
return nil, nil;
|
||||
return nil, .Mode_Not_Implemented;
|
||||
}
|
||||
|
||||
return nil, nil;
|
||||
@@ -561,7 +561,7 @@ small_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
return nil, nil;
|
||||
|
||||
case .Query_Info:
|
||||
return nil, nil;
|
||||
return nil, .Mode_Not_Implemented;
|
||||
}
|
||||
|
||||
return nil, nil;
|
||||
@@ -602,7 +602,7 @@ dynamic_pool_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode
|
||||
case .Alloc:
|
||||
return dynamic_pool_alloc_bytes(pool, size);
|
||||
case .Free:
|
||||
return nil, nil;
|
||||
return nil, .Mode_Not_Implemented;
|
||||
case .Free_All:
|
||||
dynamic_pool_free_all(pool);
|
||||
return nil, nil;
|
||||
@@ -786,7 +786,7 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
return nil, nil;
|
||||
|
||||
case .Query_Info:
|
||||
return nil, nil;
|
||||
panic("mem: panic allocator, .Query_Info called");
|
||||
}
|
||||
|
||||
return nil, nil;
|
||||
@@ -908,106 +908,9 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
return nil, nil;
|
||||
|
||||
case .Query_Info:
|
||||
return nil, nil;
|
||||
unreachable();
|
||||
}
|
||||
|
||||
return result, err;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Small_Allocator primary allocates memory from its local buffer of size BUFFER_SIZE
|
||||
// If that buffer's memory is exhausted, it will use the backing allocator (a scratch allocator is recommended)
|
||||
// Memory allocated with Small_Allocator cannot be freed individually using 'free' and must be freed using 'free_all'
|
||||
Small_Allocator :: struct(BUFFER_SIZE: int)
|
||||
where
|
||||
BUFFER_SIZE >= 2*size_of(uintptr),
|
||||
BUFFER_SIZE & (BUFFER_SIZE-1) == 0 {
|
||||
|
||||
buffer: [BUFFER_SIZE]byte,
|
||||
backing: Allocator,
|
||||
start: uintptr,
|
||||
curr: uintptr,
|
||||
end: uintptr,
|
||||
chunk_size: int,
|
||||
}
|
||||
|
||||
small_allocator :: proc(s: ^$S/Small_Allocator, backing := context.allocator) -> (a: Allocator) {
|
||||
if s.backing.procedure == nil {
|
||||
s.backing = backing;
|
||||
}
|
||||
a.data = s;
|
||||
a.procedure = proc(allocator_data: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr {
|
||||
s := (^S)(allocator_data);
|
||||
if s.chunk_size <= 0 {
|
||||
s.chunk_size = 4*1024;
|
||||
}
|
||||
if s.start == 0 {
|
||||
s.start = uintptr(&s.buffer[0]);
|
||||
s.curr = s.start;
|
||||
s.end = s.start + uintptr(S.BUFFER_SIZE);
|
||||
(^rawptr)(s.start)^ = nil;
|
||||
s.curr += size_of(rawptr);
|
||||
}
|
||||
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
s.curr = align_forward_uintptr(s.curr, uintptr(alignment));
|
||||
if size > int(s.end - s.curr) {
|
||||
to_allocate := size_of(rawptr) + size + alignment;
|
||||
if to_allocate < s.chunk_size {
|
||||
to_allocate = s.chunk_size;
|
||||
}
|
||||
s.chunk_size *= 2;
|
||||
|
||||
p := alloc(to_allocate, 16, s.backing, loc);
|
||||
(^rawptr)(s.start)^ = p;
|
||||
s.start = uintptr(p);
|
||||
s.curr = s.start;
|
||||
s.end = s.start + uintptr(to_allocate);
|
||||
|
||||
(^rawptr)(s.start)^ = nil;
|
||||
s.curr += size_of(rawptr);
|
||||
s.curr = align_forward_uintptr(s.curr, uintptr(alignment));
|
||||
}
|
||||
|
||||
p := rawptr(s.curr);
|
||||
s.curr += uintptr(size);
|
||||
return mem_zero(p, size);
|
||||
|
||||
case .Free:
|
||||
// NOP
|
||||
return nil;
|
||||
|
||||
case .Resize:
|
||||
// No need copying the code
|
||||
return default_resize_align(old_memory, old_size, size, alignment, small_allocator(s, s.backing), loc);
|
||||
|
||||
case .Free_All:
|
||||
p := (^rawptr)(&s.buffer[0])^;
|
||||
for p != nil {
|
||||
next := (^rawptr)(p)^;
|
||||
free(next, s.backing, loc);
|
||||
p = next;
|
||||
}
|
||||
// Reset to default
|
||||
s.start = uintptr(&s.buffer[0]);
|
||||
s.curr = s.start;
|
||||
s.end = s.start + uintptr(S.BUFFER_SIZE);
|
||||
|
||||
(^rawptr)(s.start)^ = nil;
|
||||
s.curr += size_of(rawptr);
|
||||
|
||||
|
||||
case .Query_Features:
|
||||
return nil, nil;
|
||||
|
||||
case .Query_Info:
|
||||
return nil, nil;
|
||||
}
|
||||
|
||||
return nil, nil;
|
||||
};
|
||||
return a;
|
||||
}
|
||||
|
||||
+8
-10
@@ -135,18 +135,12 @@ ptr_sub :: proc(a, b: $P/^$T) -> int {
|
||||
}
|
||||
|
||||
slice_ptr :: proc(ptr: ^$T, len: int) -> []T {
|
||||
assert(len >= 0);
|
||||
return transmute([]T)Raw_Slice{data = ptr, len = len};
|
||||
return ([^]T)(ptr)[:len];
|
||||
}
|
||||
|
||||
byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte {
|
||||
return transmute([]u8)Raw_Slice{data=data, len=max(len, 0)};
|
||||
return ([^]u8)(data)[:max(len, 0)];
|
||||
}
|
||||
@(deprecated="use byte_slice")
|
||||
slice_ptr_to_bytes :: proc(data: rawptr, len: int) -> []byte {
|
||||
return transmute([]u8)Raw_Slice{data=data, len=max(len, 0)};
|
||||
}
|
||||
|
||||
|
||||
slice_to_bytes :: proc(slice: $E/[]$T) -> []byte {
|
||||
s := transmute(Raw_Slice)slice;
|
||||
@@ -245,6 +239,10 @@ context_from_allocator :: proc(a: Allocator) -> type_of(context) {
|
||||
return context;
|
||||
}
|
||||
|
||||
reinterpret_copy :: proc($T: typeid, ptr: rawptr) -> (value: T) {
|
||||
copy(&value, ptr, size_of(T));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Fixed_Byte_Buffer :: distinct [dynamic]byte;
|
||||
@@ -291,8 +289,8 @@ calc_padding_with_header :: proc(ptr: uintptr, align: uintptr, header_size: int)
|
||||
|
||||
|
||||
|
||||
clone_slice :: proc(slice: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> T {
|
||||
new_slice := make(T, len(slice), allocator, loc);
|
||||
clone_slice :: proc(slice: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> (new_slice: T) {
|
||||
new_slice, _ = make(T, len(slice), allocator, loc);
|
||||
runtime.copy(new_slice, slice);
|
||||
return new_slice;
|
||||
}
|
||||
|
||||
@@ -58,3 +58,14 @@ raw_dynamic_array_data :: proc(a: $T/[dynamic]$E) -> ^E {
|
||||
raw_data :: proc{raw_array_data, raw_string_data, raw_slice_data, raw_dynamic_array_data};
|
||||
|
||||
|
||||
Poly_Raw_Map_Entry :: struct($Key, $Value: typeid) {
|
||||
hash: uintptr,
|
||||
next: int,
|
||||
key: Key,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
Poly_Raw_Map :: struct($Key, $Value: typeid) {
|
||||
hashes: []int,
|
||||
entries: [dynamic]Poly_Raw_Map_Entry(Key, Value),
|
||||
}
|
||||
+23
-11
@@ -241,15 +241,6 @@ Field_Value :: struct {
|
||||
value: ^Expr,
|
||||
}
|
||||
|
||||
Ternary_Expr :: struct {
|
||||
using node: Expr,
|
||||
cond: ^Expr,
|
||||
op1: tokenizer.Token,
|
||||
x: ^Expr,
|
||||
op2: tokenizer.Token,
|
||||
y: ^Expr,
|
||||
}
|
||||
|
||||
Ternary_If_Expr :: struct {
|
||||
using node: Expr,
|
||||
x: ^Expr,
|
||||
@@ -261,13 +252,26 @@ Ternary_If_Expr :: struct {
|
||||
|
||||
Ternary_When_Expr :: struct {
|
||||
using node: Expr,
|
||||
x: ^Expr,
|
||||
x: ^Expr,
|
||||
op1: tokenizer.Token,
|
||||
cond: ^Expr,
|
||||
op2: tokenizer.Token,
|
||||
y: ^Expr,
|
||||
}
|
||||
|
||||
Or_Else_Expr :: struct {
|
||||
using node: Expr,
|
||||
x: ^Expr,
|
||||
token: tokenizer.Token,
|
||||
y: ^Expr,
|
||||
}
|
||||
|
||||
Or_Return_Expr :: struct {
|
||||
using node: Expr,
|
||||
expr: ^Expr,
|
||||
token: tokenizer.Token,
|
||||
}
|
||||
|
||||
Type_Assertion :: struct {
|
||||
using node: Expr,
|
||||
expr: ^Expr,
|
||||
@@ -542,7 +546,7 @@ Field_Flag :: enum {
|
||||
No_Alias,
|
||||
C_Vararg,
|
||||
Auto_Cast,
|
||||
In,
|
||||
Any_Int,
|
||||
|
||||
Results,
|
||||
Tags,
|
||||
@@ -652,6 +656,14 @@ Pointer_Type :: struct {
|
||||
elem: ^Expr,
|
||||
}
|
||||
|
||||
Multi_Pointer_Type :: struct {
|
||||
using node: Expr,
|
||||
open: tokenizer.Pos,
|
||||
pointer: tokenizer.Pos,
|
||||
close: tokenizer.Pos,
|
||||
elem: ^Expr,
|
||||
}
|
||||
|
||||
Array_Type :: struct {
|
||||
using node: Expr,
|
||||
open: tokenizer.Pos,
|
||||
|
||||
@@ -5,7 +5,7 @@ import "core:fmt"
|
||||
import "core:odin/tokenizer"
|
||||
|
||||
new :: proc($T: typeid, pos, end: tokenizer.Pos) -> ^T {
|
||||
n := mem.new(T);
|
||||
n, _ := mem.new(T);
|
||||
n.pos = pos;
|
||||
n.end = end;
|
||||
n.derived = n^;
|
||||
@@ -129,10 +129,6 @@ clone_node :: proc(node: ^Node) -> ^Node {
|
||||
case Field_Value:
|
||||
r.field = clone(r.field);
|
||||
r.value = clone(r.value);
|
||||
case Ternary_Expr:
|
||||
r.cond = clone(r.cond);
|
||||
r.x = clone(r.x);
|
||||
r.y = clone(r.y);
|
||||
case Ternary_If_Expr:
|
||||
r.x = clone(r.x);
|
||||
r.cond = clone(r.cond);
|
||||
@@ -141,6 +137,11 @@ clone_node :: proc(node: ^Node) -> ^Node {
|
||||
r.x = clone(r.x);
|
||||
r.cond = clone(r.cond);
|
||||
r.y = clone(r.y);
|
||||
case Or_Else_Expr:
|
||||
r.x = clone(r.x);
|
||||
r.y = clone(r.y);
|
||||
case Or_Return_Expr:
|
||||
r.expr = clone(r.expr);
|
||||
case Type_Assertion:
|
||||
r.expr = clone(r.expr);
|
||||
r.type = clone(r.type);
|
||||
@@ -250,6 +251,8 @@ clone_node :: proc(node: ^Node) -> ^Node {
|
||||
r.results = auto_cast clone(r.results);
|
||||
case Pointer_Type:
|
||||
r.elem = clone(r.elem);
|
||||
case Multi_Pointer_Type:
|
||||
r.elem = clone(r.elem);
|
||||
case Array_Type:
|
||||
r.len = clone(r.len);
|
||||
r.elem = clone(r.elem);
|
||||
|
||||
@@ -126,10 +126,6 @@ walk :: proc(v: ^Visitor, node: ^Node) {
|
||||
case Field_Value:
|
||||
walk(v, n.field);
|
||||
walk(v, n.value);
|
||||
case Ternary_Expr:
|
||||
walk(v, n.cond);
|
||||
walk(v, n.x);
|
||||
walk(v, n.y);
|
||||
case Ternary_If_Expr:
|
||||
walk(v, n.x);
|
||||
walk(v, n.cond);
|
||||
@@ -138,6 +134,11 @@ walk :: proc(v: ^Visitor, node: ^Node) {
|
||||
walk(v, n.x);
|
||||
walk(v, n.cond);
|
||||
walk(v, n.y);
|
||||
case Or_Else_Expr:
|
||||
walk(v, n.x);
|
||||
walk(v, n.y);
|
||||
case Or_Return_Expr:
|
||||
walk(v, n.expr);
|
||||
case Type_Assertion:
|
||||
walk(v, n.expr);
|
||||
if n.type != nil {
|
||||
@@ -348,6 +349,8 @@ walk :: proc(v: ^Visitor, node: ^Node) {
|
||||
walk(v, n.results);
|
||||
case Pointer_Type:
|
||||
walk(v, n.elem);
|
||||
case Multi_Pointer_Type:
|
||||
walk(v, n.elem);
|
||||
case Array_Type:
|
||||
if n.tag != nil {
|
||||
walk(v, n.tag);
|
||||
|
||||
@@ -28,11 +28,11 @@ Magic_String :: "odindoc\x00";
|
||||
|
||||
Header_Base :: struct {
|
||||
magic: [8]byte,
|
||||
_: u32le,
|
||||
_: u32le, // padding
|
||||
version: Version_Type,
|
||||
total_size: u32le,
|
||||
header_size: u32le,
|
||||
hash: u32le,
|
||||
total_size: u32le, // in bytes
|
||||
header_size: u32le, // in bytes
|
||||
hash: u32le, // hash of the data after the header (header_size)
|
||||
}
|
||||
|
||||
Header :: struct {
|
||||
@@ -95,16 +95,17 @@ Entity_Flag :: enum u32le {
|
||||
Foreign = 0,
|
||||
Export = 1,
|
||||
|
||||
Param_Using = 2,
|
||||
Param_Const = 3,
|
||||
Param_Auto_Cast = 4,
|
||||
Param_Ellipsis = 5,
|
||||
Param_CVararg = 6,
|
||||
Param_No_Alias = 7,
|
||||
Param_Using = 2, // using
|
||||
Param_Const = 3, // #const
|
||||
Param_Auto_Cast = 4, // auto_cast
|
||||
Param_Ellipsis = 5, // Variadic parameter
|
||||
Param_CVararg = 6, // #c_vararg
|
||||
Param_No_Alias = 7, // #no_alias
|
||||
|
||||
Type_Alias = 8,
|
||||
|
||||
Var_Thread_Local = 9,
|
||||
Var_Static = 10,
|
||||
}
|
||||
|
||||
Entity_Flags :: distinct bit_set[Entity_Flag; u32le];
|
||||
@@ -116,14 +117,25 @@ Entity :: struct {
|
||||
name: String,
|
||||
type: Type_Index,
|
||||
init_string: String,
|
||||
_: u32le,
|
||||
_: u32le, // reserved for init
|
||||
comment: String,
|
||||
docs: String,
|
||||
|
||||
// May used by:
|
||||
// .Variable
|
||||
// .Procedure
|
||||
foreign_library: Entity_Index,
|
||||
// May used by:
|
||||
// .Variable
|
||||
// .Procedure
|
||||
link_name: String,
|
||||
|
||||
attributes: Array(Attribute),
|
||||
grouped_entities: Array(Entity_Index), // Procedure Groups
|
||||
where_clauses: Array(String), // Procedures
|
||||
|
||||
// Used by: .Proc_Group
|
||||
grouped_entities: Array(Entity_Index),
|
||||
// May used by: .Procedure
|
||||
where_clauses: Array(String),
|
||||
}
|
||||
|
||||
Attribute :: struct {
|
||||
@@ -154,26 +166,77 @@ Type_Kind :: enum u32le {
|
||||
SOA_Struct_Dynamic = 19,
|
||||
Relative_Pointer = 20,
|
||||
Relative_Slice = 21,
|
||||
Multi_Pointer = 22,
|
||||
}
|
||||
|
||||
Type_Elems_Cap :: 4;
|
||||
|
||||
Type :: struct {
|
||||
kind: Type_Kind,
|
||||
flags: u32le, // Type_Kind specific
|
||||
name: String,
|
||||
kind: Type_Kind,
|
||||
// Type_Kind specific used by some types
|
||||
// Underlying flag types:
|
||||
// .Basic - Type_Flags_Basic
|
||||
// .Struct - Type_Flags_Struct
|
||||
// .Union - Type_Flags_Union
|
||||
// .Proc - Type_Flags_Proc
|
||||
// .Bit_Set - Type_Flags_Bit_Set
|
||||
flags: u32le,
|
||||
|
||||
// Used by:
|
||||
// .Basic
|
||||
// .Named
|
||||
// .Generic
|
||||
name: String,
|
||||
|
||||
// Used By: .Struct, .Union
|
||||
custom_align: String,
|
||||
|
||||
// Used by some types
|
||||
// Used by:
|
||||
// .Array - 1 count: 0=len
|
||||
// .Enumerated_Array - 1 count: 0=len
|
||||
// .SOA_Struct_Fixed - 1 count: 0=len
|
||||
// .Bit_Set - 2 count: 0=lower, 1=upper
|
||||
// .Simd_Vector - 1 count: 0=len
|
||||
elem_count_len: u32le,
|
||||
elem_counts: [Type_Elems_Cap]i64le,
|
||||
|
||||
// Each of these is esed by some types, not all
|
||||
calling_convention: String, // Procedures
|
||||
types: Array(Type_Index),
|
||||
entities: Array(Entity_Index),
|
||||
polymorphic_params: Type_Index, // Struct, Union
|
||||
where_clauses: Array(String), // Struct, Union
|
||||
// Used by: .Procedures
|
||||
// blank implies the "odin" calling convention
|
||||
calling_convention: String,
|
||||
|
||||
// Used by:
|
||||
// .Named - 1 type: 0=base type
|
||||
// .Generic - <1 type: 0=specialization
|
||||
// .Pointer - 1 type: 0=element
|
||||
// .Array - 1 type: 0=element
|
||||
// .Enumerated_Array - 2 types: 0=index and 1=element
|
||||
// .Slice - 1 type: 0=element
|
||||
// .Dynamic_Array - 1 type: 0=element
|
||||
// .Map - 2 types: 0=key, 1=value
|
||||
// .SOA_Struct_Fixed - 1 type: underlying SOA struct element
|
||||
// .SOA_Struct_Slice - 1 type: underlying SOA struct element
|
||||
// .SOA_Struct_Dynamic - 1 type: underlying SOA struct element
|
||||
// .Union - 0+ types: variants
|
||||
// .Enum - <1 type: 0=base type
|
||||
// .Proc - 2 types: 0=parameters, 1=results
|
||||
// .Bit_Set - <=2 types: 0=element type, 1=underlying type (Underlying_Type flag will be set)
|
||||
// .Simd_Vector - 1 type: 0=element
|
||||
// .Relative_Pointer - 2 types: 0=pointer type, 1=base integer
|
||||
// .Relative_Slice - 2 types: 0=slice type, 1=base integer
|
||||
// .Multi_Pointer - 1 type: 0=element
|
||||
types: Array(Type_Index),
|
||||
|
||||
// Used by:
|
||||
// .Named - 1 field for the definition
|
||||
// .Struct - fields
|
||||
// .Enum - fields
|
||||
// .Tuple - parameters (procedures only)
|
||||
entities: Array(Entity_Index),
|
||||
|
||||
// Used By: .Struct, .Union
|
||||
polymorphic_params: Type_Index,
|
||||
// Used By: .Struct, .Union
|
||||
where_clauses: Array(String),
|
||||
}
|
||||
|
||||
Type_Flags_Basic :: distinct bit_set[Type_Flag_Basic; u32le];
|
||||
|
||||
+264
-83
@@ -44,8 +44,13 @@ Parser :: struct {
|
||||
curr_proc: ^ast.Node,
|
||||
|
||||
error_count: int,
|
||||
|
||||
fix_count: int,
|
||||
fix_prev_pos: tokenizer.Pos,
|
||||
}
|
||||
|
||||
MAX_FIX_COUNT :: 10;
|
||||
|
||||
Stmt_Allow_Flag :: enum {
|
||||
In,
|
||||
Label,
|
||||
@@ -140,9 +145,7 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool {
|
||||
p.line_comment = nil;
|
||||
}
|
||||
|
||||
if .Optional_Semicolons in p.flags {
|
||||
p.tok.flags += {.Insert_Semicolon};
|
||||
}
|
||||
p.tok.flags += {.Insert_Semicolon};
|
||||
|
||||
p.file = file;
|
||||
tokenizer.init(&p.tok, file.src, file.fullpath, p.err);
|
||||
@@ -229,7 +232,7 @@ peek_token :: proc(p: ^Parser, lookahead := 0) -> (tok: tokenizer.Token) {
|
||||
return;
|
||||
}
|
||||
skip_possible_newline :: proc(p: ^Parser) -> bool {
|
||||
if .Insert_Semicolon not_in p.tok.flags {
|
||||
if .Optional_Semicolons not_in p.flags {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -242,7 +245,7 @@ skip_possible_newline :: proc(p: ^Parser) -> bool {
|
||||
}
|
||||
|
||||
skip_possible_newline_for_literal :: proc(p: ^Parser) -> bool {
|
||||
if .Insert_Semicolon not_in p.tok.flags {
|
||||
if .Optional_Semicolons not_in p.flags {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -371,11 +374,14 @@ expect_token_after :: proc(p: ^Parser, kind: tokenizer.Token_Kind, msg: string)
|
||||
|
||||
expect_operator :: proc(p: ^Parser) -> tokenizer.Token {
|
||||
prev := p.curr_tok;
|
||||
if prev.kind == .If || prev.kind == .When {
|
||||
#partial switch prev.kind {
|
||||
case .If, .When, .Or_Else:
|
||||
// okay
|
||||
} else if !tokenizer.is_operator(prev.kind) {
|
||||
g := tokenizer.token_to_string(prev);
|
||||
error(p, prev.pos, "expected an operator, got '%s'", g);
|
||||
case:
|
||||
if !tokenizer.is_operator(prev.kind) {
|
||||
g := tokenizer.token_to_string(prev);
|
||||
error(p, prev.pos, "expected an operator, got '%s'", g);
|
||||
}
|
||||
}
|
||||
advance_token(p);
|
||||
return prev;
|
||||
@@ -389,6 +395,30 @@ allow_token :: proc(p: ^Parser, kind: tokenizer.Token_Kind) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
end_of_line_pos :: proc(p: ^Parser, tok: tokenizer.Token) -> tokenizer.Pos {
|
||||
offset := clamp(tok.pos.offset, 0, len(p.tok.src)-1);
|
||||
s := p.tok.src[offset:];
|
||||
pos := tok.pos;
|
||||
pos.column -= 1;
|
||||
for len(s) != 0 && s[0] != 0 && s[0] != '\n' {
|
||||
s = s[1:];
|
||||
pos.column += 1;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
expect_closing_brace_of_field_list :: proc(p: ^Parser) -> tokenizer.Token {
|
||||
token := p.curr_tok;
|
||||
if allow_token(p, .Close_Brace) {
|
||||
return token;
|
||||
}
|
||||
if allow_token(p, .Semicolon) {
|
||||
str := tokenizer.token_to_string(token);
|
||||
error(p, end_of_line_pos(p, p.prev_tok), "expected a comma, got %s", p);
|
||||
}
|
||||
return expect_token(p, .Close_Brace);
|
||||
}
|
||||
|
||||
|
||||
is_blank_ident :: proc{
|
||||
is_blank_ident_string,
|
||||
@@ -411,6 +441,33 @@ is_blank_ident_node :: proc(node: ^ast.Node) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
fix_advance_to_next_stmt :: proc(p: ^Parser) {
|
||||
for {
|
||||
#partial switch t := p.curr_tok; t.kind {
|
||||
case .EOF, .Semicolon:
|
||||
return;
|
||||
|
||||
case .Package, .Foreign, .Import,
|
||||
.If, .For, .When, .Return, .Switch,
|
||||
.Defer, .Using,
|
||||
.Break, .Continue, .Fallthrough,
|
||||
.Hash:
|
||||
|
||||
|
||||
if t.pos == p.fix_prev_pos && p.fix_count < MAX_FIX_COUNT {
|
||||
p.fix_count += 1;
|
||||
return;
|
||||
}
|
||||
if t.pos.offset < p.fix_prev_pos.offset {
|
||||
p.fix_prev_pos = t.pos;
|
||||
p.fix_count = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
advance_token(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool {
|
||||
if node == nil {
|
||||
@@ -526,6 +583,7 @@ expect_semicolon :: proc(p: ^Parser, node: ^ast.Node) -> bool {
|
||||
}
|
||||
|
||||
error(p, prev.pos, "expected ';', got %s", tokenizer.token_to_string(p.curr_tok));
|
||||
fix_advance_to_next_stmt(p);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -597,12 +655,16 @@ parse_when_stmt :: proc(p: ^Parser) -> ^ast.When_Stmt {
|
||||
}
|
||||
if allow_token(p, .Do) {
|
||||
body = convert_stmt_to_body(p, parse_stmt(p));
|
||||
if cond.pos.line != body.pos.line {
|
||||
error(p, body.pos, "the body of a 'do' must be on the same line as when statement");
|
||||
}
|
||||
} else {
|
||||
body = parse_block_stmt(p, true);
|
||||
}
|
||||
|
||||
skip_possible_newline_for_literal(p);
|
||||
if allow_token(p, .Else) {
|
||||
if p.curr_tok.kind == .Else {
|
||||
else_tok := expect_token(p, .Else);
|
||||
#partial switch p.curr_tok.kind {
|
||||
case .When:
|
||||
else_stmt = parse_when_stmt(p);
|
||||
@@ -611,6 +673,9 @@ parse_when_stmt :: proc(p: ^Parser) -> ^ast.When_Stmt {
|
||||
case .Do:
|
||||
expect_token(p, .Do);
|
||||
else_stmt = convert_stmt_to_body(p, parse_stmt(p));
|
||||
if else_tok.pos.line != else_stmt.pos.line {
|
||||
error(p, else_stmt.pos, "the body of a 'do' must be on the same line as 'else'");
|
||||
}
|
||||
case:
|
||||
error(p, p.curr_tok.pos, "expected when statement block statement");
|
||||
else_stmt = ast.new(ast.Bad_Stmt, p.curr_tok.pos, end_pos(p.curr_tok));
|
||||
@@ -673,6 +738,9 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt {
|
||||
}
|
||||
if allow_token(p, .Do) {
|
||||
body = convert_stmt_to_body(p, parse_stmt(p));
|
||||
if cond.pos.line != body.pos.line {
|
||||
error(p, body.pos, "the body of a 'do' must be on the same line as the if condition");
|
||||
}
|
||||
} else {
|
||||
body = parse_block_stmt(p, false);
|
||||
}
|
||||
@@ -680,7 +748,8 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt {
|
||||
else_tok := p.curr_tok.pos;
|
||||
|
||||
skip_possible_newline_for_literal(p);
|
||||
if allow_token(p, .Else) {
|
||||
if p.curr_tok.kind == .Else {
|
||||
else_tok := expect_token(p, .Else);
|
||||
#partial switch p.curr_tok.kind {
|
||||
case .If:
|
||||
else_stmt = parse_if_stmt(p);
|
||||
@@ -689,6 +758,9 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt {
|
||||
case .Do:
|
||||
expect_token(p, .Do);
|
||||
else_stmt = convert_stmt_to_body(p, parse_stmt(p));
|
||||
if else_tok.pos.line != else_stmt.pos.line {
|
||||
error(p, body.pos, "the body of a 'do' must be on the same line as 'else'");
|
||||
}
|
||||
case:
|
||||
error(p, p.curr_tok.pos, "expected if statement block statement");
|
||||
else_stmt = ast.new(ast.Bad_Stmt, p.curr_tok.pos, end_pos(p.curr_tok));
|
||||
@@ -750,6 +822,10 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
|
||||
if allow_token(p, .Do) {
|
||||
body = convert_stmt_to_body(p, parse_stmt(p));
|
||||
if tok.pos.line != body.pos.line {
|
||||
error(p, body.pos, "the body of a 'do' must be on the same line as 'else'");
|
||||
}
|
||||
|
||||
} else {
|
||||
body = parse_body(p);
|
||||
}
|
||||
@@ -772,18 +848,33 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
if !is_range && parse_control_statement_semicolon_separator(p) {
|
||||
init = cond;
|
||||
cond = nil;
|
||||
if p.curr_tok.kind != .Semicolon {
|
||||
cond = parse_simple_stmt(p, nil);
|
||||
}
|
||||
expect_semicolon(p, cond);
|
||||
if p.curr_tok.kind != .Open_Brace && p.curr_tok.kind != .Do {
|
||||
post = parse_simple_stmt(p, nil);
|
||||
|
||||
|
||||
if p.curr_tok.kind == .Open_Brace || p.curr_tok.kind == .Do {
|
||||
error(p, p.curr_tok.pos, "Expected ';', followed by a condition expression and post statement, got %s", tokenizer.tokens[p.curr_tok.kind]);
|
||||
} else {
|
||||
if p.curr_tok.kind != .Semicolon {
|
||||
cond = parse_simple_stmt(p, nil);
|
||||
}
|
||||
|
||||
if p.curr_tok.text != ";" {
|
||||
error(p, p.curr_tok.pos, "Expected ';', got %s", tokenizer.token_to_string(p.curr_tok));
|
||||
} else {
|
||||
expect_semicolon(p, nil);
|
||||
}
|
||||
|
||||
if p.curr_tok.kind != .Open_Brace && p.curr_tok.kind != .Do {
|
||||
post = parse_simple_stmt(p, nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if allow_token(p, .Do) {
|
||||
body = convert_stmt_to_body(p, parse_stmt(p));
|
||||
if tok.pos.line != body.pos.line {
|
||||
error(p, body.pos, "the body of a 'do' must be on the same line as the 'for' token");
|
||||
}
|
||||
} else {
|
||||
body = parse_body(p);
|
||||
}
|
||||
@@ -1129,6 +1220,9 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast
|
||||
|
||||
if allow_token(p, .Do) {
|
||||
body = convert_stmt_to_body(p, parse_stmt(p));
|
||||
if for_tok.pos.line != body.pos.line {
|
||||
error(p, body.pos, "the body of a 'do' must be on the same line as the 'for' token");
|
||||
}
|
||||
} else {
|
||||
body = parse_block_stmt(p, false);
|
||||
}
|
||||
@@ -1150,7 +1244,6 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast
|
||||
|
||||
parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
#partial switch p.curr_tok.kind {
|
||||
|
||||
case .Inline:
|
||||
if peek_token_kind(p, .For) {
|
||||
inline_tok := expect_token(p, .Inline);
|
||||
@@ -1158,15 +1251,15 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
}
|
||||
fallthrough;
|
||||
// Operands
|
||||
case .Context, // Also allows for 'context = '
|
||||
case .No_Inline,
|
||||
.Context, // Also allows for 'context = '
|
||||
.Proc,
|
||||
.No_Inline,
|
||||
.Asm, // Inline assembly
|
||||
.Ident,
|
||||
.Integer, .Float, .Imag,
|
||||
.Rune, .String,
|
||||
.Open_Paren,
|
||||
.Pointer,
|
||||
.Asm, // Inline assembly
|
||||
// Unary Expressions
|
||||
.Add, .Sub, .Xor, .Not, .And:
|
||||
|
||||
@@ -1175,8 +1268,8 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
return s;
|
||||
|
||||
|
||||
case .Import: return parse_import_decl(p);
|
||||
case .Foreign: return parse_foreign_decl(p);
|
||||
case .Import: return parse_import_decl(p);
|
||||
case .If: return parse_if_stmt(p);
|
||||
case .When: return parse_when_stmt(p);
|
||||
case .For: return parse_for_stmt(p);
|
||||
@@ -1288,9 +1381,9 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
stmt := parse_stmt(p);
|
||||
switch name {
|
||||
case "bounds_check":
|
||||
stmt.state_flags |= {.Bounds_Check};
|
||||
stmt.state_flags += {.Bounds_Check};
|
||||
case "no_bounds_check":
|
||||
stmt.state_flags |= {.No_Bounds_Check};
|
||||
stmt.state_flags += {.No_Bounds_Check};
|
||||
}
|
||||
return stmt;
|
||||
case "partial":
|
||||
@@ -1309,6 +1402,12 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
es := ast.new(ast.Expr_Stmt, ce.pos, ce.end);
|
||||
es.expr = ce;
|
||||
return es;
|
||||
|
||||
case "force_inline", "force_no_inline":
|
||||
expr := parse_inlining_operand(p, true, tok);
|
||||
es := ast.new(ast.Expr_Stmt, expr.pos, expr.end);
|
||||
es.expr = expr;
|
||||
return es;
|
||||
case "unroll":
|
||||
return parse_unrolled_for_loop(p, tag);
|
||||
case "include":
|
||||
@@ -1320,6 +1419,8 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
te.op = tok;
|
||||
te.name = name;
|
||||
te.stmt = stmt;
|
||||
|
||||
fix_advance_to_next_stmt(p);
|
||||
return te;
|
||||
}
|
||||
case .Open_Brace:
|
||||
@@ -1331,8 +1432,31 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
#partial switch p.curr_tok.kind {
|
||||
case .Else:
|
||||
token := expect_token(p, .Else);
|
||||
error(p, token.pos, "'else' unattached to an 'if' statement");
|
||||
#partial switch p.curr_tok.kind {
|
||||
case .If:
|
||||
return parse_if_stmt(p);
|
||||
case .When:
|
||||
return parse_when_stmt(p);
|
||||
case .Open_Brace:
|
||||
return parse_block_stmt(p, true);
|
||||
case .Do:
|
||||
expect_token(p, .Do);
|
||||
return convert_stmt_to_body(p, parse_stmt(p));
|
||||
case:
|
||||
fix_advance_to_next_stmt(p);
|
||||
return ast.new(ast.Bad_Stmt, token.pos, end_pos(p.curr_tok));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tok := advance_token(p);
|
||||
error(p, tok.pos, "expected a statement, got %s", tokenizer.token_to_string(tok));
|
||||
fix_advance_to_next_stmt(p);
|
||||
s := ast.new(ast.Bad_Stmt, tok.pos, end_pos(tok));
|
||||
return s;
|
||||
}
|
||||
@@ -1340,7 +1464,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
|
||||
token_precedence :: proc(p: ^Parser, kind: tokenizer.Token_Kind) -> int {
|
||||
#partial switch kind {
|
||||
case .Question, .If, .When:
|
||||
case .Question, .If, .When, .Or_Else:
|
||||
return 1;
|
||||
case .Ellipsis, .Range_Half, .Range_Full:
|
||||
if !p.allow_range {
|
||||
@@ -1469,8 +1593,8 @@ Field_Prefix :: enum {
|
||||
Using,
|
||||
No_Alias,
|
||||
C_Vararg,
|
||||
In,
|
||||
Auto_Cast,
|
||||
Any_Int,
|
||||
}
|
||||
|
||||
Field_Prefixes :: distinct bit_set[Field_Prefix];
|
||||
@@ -1517,19 +1641,15 @@ convert_to_ident_list :: proc(p: ^Parser, list: []Expr_And_Flags, ignore_flags,
|
||||
}
|
||||
|
||||
is_token_field_prefix :: proc(p: ^Parser) -> Field_Prefix {
|
||||
using Field_Prefix;
|
||||
#partial switch p.curr_tok.kind {
|
||||
case .EOF:
|
||||
return Invalid;
|
||||
return .Invalid;
|
||||
case .Using:
|
||||
advance_token(p);
|
||||
return Using;
|
||||
case .In:
|
||||
advance_token(p);
|
||||
return In;
|
||||
return .Using;
|
||||
case .Auto_Cast:
|
||||
advance_token(p);
|
||||
return Auto_Cast;
|
||||
return .Auto_Cast;
|
||||
case .Hash:
|
||||
advance_token(p);
|
||||
defer advance_token(p);
|
||||
@@ -1537,14 +1657,16 @@ is_token_field_prefix :: proc(p: ^Parser) -> Field_Prefix {
|
||||
case .Ident:
|
||||
switch p.curr_tok.text {
|
||||
case "no_alias":
|
||||
return No_Alias;
|
||||
return .No_Alias;
|
||||
case "c_vararg":
|
||||
return C_Vararg;
|
||||
return .C_Vararg;
|
||||
case "any_int":
|
||||
return .Any_Int;
|
||||
}
|
||||
}
|
||||
return Unknown;
|
||||
return .Unknown;
|
||||
}
|
||||
return Invalid;
|
||||
return .Invalid;
|
||||
}
|
||||
|
||||
parse_field_prefixes :: proc(p: ^Parser) -> ast.Field_Flags {
|
||||
@@ -1568,24 +1690,23 @@ parse_field_prefixes :: proc(p: ^Parser) -> ast.Field_Flags {
|
||||
|
||||
for kind in Field_Prefix {
|
||||
count := counts[kind];
|
||||
using Field_Prefix;
|
||||
switch kind {
|
||||
case Invalid, Unknown: // Ignore
|
||||
case Using:
|
||||
case .Invalid, .Unknown: // Ignore
|
||||
case .Using:
|
||||
if count > 1 { error(p, p.curr_tok.pos, "multiple 'using' in this field list"); }
|
||||
if count > 0 { flags |= {.Using}; }
|
||||
case No_Alias:
|
||||
if count > 0 { flags += {.Using}; }
|
||||
case .No_Alias:
|
||||
if count > 1 { error(p, p.curr_tok.pos, "multiple '#no_alias' in this field list"); }
|
||||
if count > 0 { flags |= {.No_Alias}; }
|
||||
case C_Vararg:
|
||||
if count > 0 { flags += {.No_Alias}; }
|
||||
case .C_Vararg:
|
||||
if count > 1 { error(p, p.curr_tok.pos, "multiple '#c_vararg' in this field list"); }
|
||||
if count > 0 { flags |= {.C_Vararg}; }
|
||||
case In:
|
||||
if count > 1 { error(p, p.curr_tok.pos, "multiple 'in' in this field list"); }
|
||||
if count > 0 { flags |= {.In}; }
|
||||
case Auto_Cast:
|
||||
if count > 0 { flags += {.C_Vararg}; }
|
||||
case .Auto_Cast:
|
||||
if count > 1 { error(p, p.curr_tok.pos, "multiple 'auto_cast' in this field list"); }
|
||||
if count > 0 { flags |= {.Auto_Cast}; }
|
||||
if count > 0 { flags += {.Auto_Cast}; }
|
||||
case .Any_Int:
|
||||
if count > 1 { error(p, p.curr_tok.pos, "multiple '#any_int' in this field list"); }
|
||||
if count > 0 { flags += {.Any_Int}; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1596,7 +1717,7 @@ check_field_flag_prefixes :: proc(p: ^Parser, name_count: int, allowed_flags, se
|
||||
flags = set_flags;
|
||||
if name_count > 1 && .Using in flags {
|
||||
error(p, p.curr_tok.pos, "cannot apply 'using' to more than one of the same type");
|
||||
flags &~= {.Using};
|
||||
flags -= {.Using};
|
||||
}
|
||||
|
||||
for flag in ast.Field_Flag {
|
||||
@@ -1610,12 +1731,12 @@ check_field_flag_prefixes :: proc(p: ^Parser, name_count: int, allowed_flags, se
|
||||
error(p, p.curr_tok.pos, "'#c_vararg' is not allowed within this field list");
|
||||
case .Auto_Cast:
|
||||
error(p, p.curr_tok.pos, "'auto_cast' is not allowed within this field list");
|
||||
case .In:
|
||||
error(p, p.curr_tok.pos, "'in' is not allowed within this field list");
|
||||
case .Any_Int:
|
||||
error(p, p.curr_tok.pos, "'#any_int' is not allowed within this field list");
|
||||
case .Tags, .Ellipsis, .Results, .Default_Parameters, .Typeid_Token:
|
||||
panic("Impossible prefixes");
|
||||
}
|
||||
flags &~= {flag};
|
||||
flags -= {flag};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1953,10 +2074,10 @@ parse_proc_tags :: proc(p: ^Parser) -> (tags: ast.Proc_Tags) {
|
||||
ident := expect_token(p, .Ident);
|
||||
|
||||
switch ident.text {
|
||||
case "bounds_check": tags |= {.Bounds_Check};
|
||||
case "no_bounds_check": tags |= {.No_Bounds_Check};
|
||||
case "optional_ok": tags |= {.Optional_Ok};
|
||||
case "optional_second": tags |= {.Optional_Second};
|
||||
case "bounds_check": tags += {.Bounds_Check};
|
||||
case "no_bounds_check": tags += {.No_Bounds_Check};
|
||||
case "optional_ok": tags += {.Optional_Ok};
|
||||
case "optional_second": tags += {.Optional_Second};
|
||||
case:
|
||||
}
|
||||
}
|
||||
@@ -2158,12 +2279,12 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
|
||||
switch name.text {
|
||||
case "bounds_check":
|
||||
operand.state_flags |= {.Bounds_Check};
|
||||
operand.state_flags += {.Bounds_Check};
|
||||
if .No_Bounds_Check in operand.state_flags {
|
||||
error(p, name.pos, "#bounds_check and #no_bounds_check cannot be applied together");
|
||||
}
|
||||
case "no_bounds_check":
|
||||
operand.state_flags |= {.No_Bounds_Check};
|
||||
operand.state_flags += {.No_Bounds_Check};
|
||||
if .Bounds_Check in operand.state_flags {
|
||||
error(p, name.pos, "#bounds_check and #no_bounds_check cannot be applied together");
|
||||
}
|
||||
@@ -2232,9 +2353,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
}
|
||||
|
||||
type := parse_proc_type(p, tok);
|
||||
tags := parse_proc_tags(p);
|
||||
type.tags = tags;
|
||||
|
||||
tags: ast.Proc_Tags;
|
||||
where_token: tokenizer.Token;
|
||||
where_clauses: []^ast.Expr;
|
||||
|
||||
@@ -2246,8 +2365,10 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
p.expr_level = -1;
|
||||
where_clauses = parse_rhs_expr_list(p);
|
||||
p.expr_level = prev_level;
|
||||
tags = parse_proc_tags(p);
|
||||
}
|
||||
tags = parse_proc_tags(p);
|
||||
type.tags = tags;
|
||||
|
||||
if p.allow_type && p.expr_level < 0 {
|
||||
if where_token.kind != .Invalid {
|
||||
error(p, where_token.pos, "'where' clauses are not allowed on procedure types");
|
||||
@@ -2273,6 +2394,9 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
p.curr_proc = type;
|
||||
body = convert_stmt_to_body(p, parse_stmt(p));
|
||||
p.curr_proc = prev_proc;
|
||||
if type.pos.line != body.pos.line {
|
||||
error(p, body.pos, "the body of a 'do' must be on the same line as the signature");
|
||||
}
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
@@ -2316,18 +2440,26 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
tok := expect_token(p, .Pointer);
|
||||
elem := parse_type(p);
|
||||
ptr := ast.new(ast.Pointer_Type, tok.pos, elem.end);
|
||||
ptr.pointer = tok.pos;
|
||||
ptr.elem = elem;
|
||||
return ptr;
|
||||
|
||||
|
||||
case .Open_Bracket:
|
||||
open := expect_token(p, .Open_Bracket);
|
||||
count: ^ast.Expr;
|
||||
if p.curr_tok.kind == .Question {
|
||||
tok := expect_token(p, .Question);
|
||||
q := ast.new(ast.Unary_Expr, tok.pos, end_pos(tok));
|
||||
q.op = tok;
|
||||
count = q;
|
||||
} else if p.curr_tok.kind == .Dynamic {
|
||||
#partial switch p.curr_tok.kind {
|
||||
case .Pointer:
|
||||
tok := expect_token(p, .Pointer);
|
||||
close := expect_token(p, .Close_Bracket);
|
||||
elem := parse_type(p);
|
||||
t := ast.new(ast.Multi_Pointer_Type, open.pos, elem.end);
|
||||
t.open = open.pos;
|
||||
t.pointer = tok.pos;
|
||||
t.close = close.pos;
|
||||
t.elem = elem;
|
||||
return t;
|
||||
case .Dynamic:
|
||||
tok := expect_token(p, .Dynamic);
|
||||
close := expect_token(p, .Close_Bracket);
|
||||
elem := parse_type(p);
|
||||
@@ -2336,12 +2468,18 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
da.dynamic_pos = tok.pos;
|
||||
da.close = close.pos;
|
||||
da.elem = elem;
|
||||
|
||||
return da;
|
||||
} else if p.curr_tok.kind != .Close_Bracket {
|
||||
case .Question:
|
||||
tok := expect_token(p, .Question);
|
||||
q := ast.new(ast.Unary_Expr, tok.pos, end_pos(tok));
|
||||
q.op = tok;
|
||||
count = q;
|
||||
case:
|
||||
p.expr_level += 1;
|
||||
count = parse_expr(p, false);
|
||||
p.expr_level -= 1;
|
||||
case .Close_Bracket:
|
||||
// handle below
|
||||
}
|
||||
close := expect_token(p, .Close_Bracket);
|
||||
elem := parse_type(p);
|
||||
@@ -2432,7 +2570,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
skip_possible_newline_for_literal(p);
|
||||
expect_token(p, .Open_Brace);
|
||||
fields, name_count = parse_field_list(p, .Close_Brace, ast.Field_Flags_Struct);
|
||||
close := expect_token(p, .Close_Brace);
|
||||
close := expect_closing_brace_of_field_list(p);
|
||||
|
||||
st := ast.new(ast.Struct_Type, tok.pos, end_pos(close));
|
||||
st.poly_params = poly_params;
|
||||
@@ -2495,11 +2633,11 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
p.expr_level = where_prev_level;
|
||||
}
|
||||
|
||||
variants: [dynamic]^ast.Expr;
|
||||
|
||||
skip_possible_newline_for_literal(p);
|
||||
expect_token_after(p, .Open_Brace, "union");
|
||||
|
||||
variants: [dynamic]^ast.Expr;
|
||||
for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
|
||||
type := parse_type(p);
|
||||
if _, ok := type.derived.(ast.Bad_Expr); !ok {
|
||||
@@ -2510,7 +2648,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
}
|
||||
}
|
||||
|
||||
close := expect_token(p, .Close_Brace);
|
||||
close := expect_closing_brace_of_field_list(p);
|
||||
|
||||
ut := ast.new(ast.Union_Type, tok.pos, end_pos(close));
|
||||
ut.poly_params = poly_params;
|
||||
@@ -2532,7 +2670,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
skip_possible_newline_for_literal(p);
|
||||
open := expect_token(p, .Open_Brace);
|
||||
fields := parse_elem_list(p);
|
||||
close := expect_token(p, .Close_Brace);
|
||||
close := expect_closing_brace_of_field_list(p);
|
||||
|
||||
et := ast.new(ast.Enum_Type, tok.pos, end_pos(close));
|
||||
et.base_type = base_type;
|
||||
@@ -2634,7 +2772,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
expect_token(p, .Comma);
|
||||
constraints_string := parse_expr(p, false);
|
||||
allow_token(p, .Comma);
|
||||
close := expect_token(p, .Close_Brace);
|
||||
close := expect_closing_brace_of_field_list(p);
|
||||
|
||||
e := ast.new(ast.Inline_Asm_Expr, tok.pos, end_pos(close));
|
||||
e.tok = tok;
|
||||
@@ -2811,8 +2949,8 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
|
||||
return nil;
|
||||
}
|
||||
error(p, p.curr_tok.pos, "expected an operand");
|
||||
fix_advance_to_next_stmt(p);
|
||||
be := ast.new(ast.Bad_Expr, p.curr_tok.pos, end_pos(p.curr_tok));
|
||||
advance_token(p);
|
||||
operand = be;
|
||||
}
|
||||
|
||||
@@ -2952,6 +3090,14 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
|
||||
|
||||
operand = deref;
|
||||
|
||||
case .Or_Return:
|
||||
token := expect_token(p, .Or_Return);
|
||||
oe := ast.new(ast.Or_Return_Expr, operand.pos, end_pos(token));
|
||||
oe.expr = operand;
|
||||
oe.token = token;
|
||||
|
||||
operand = oe;
|
||||
|
||||
case .Open_Brace:
|
||||
if !is_lhs && is_literal_type(operand) && p.expr_level >= 0 {
|
||||
operand = parse_literal_value(p, operand);
|
||||
@@ -2959,6 +3105,13 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
|
||||
loop = false;
|
||||
}
|
||||
|
||||
case .Increment, .Decrement:
|
||||
if !lhs {
|
||||
tok := advance_token(p);
|
||||
error(p, tok.pos, "postfix '%s' operator is not supported", tok.text);
|
||||
} else {
|
||||
loop = false;
|
||||
}
|
||||
}
|
||||
|
||||
is_lhs = false;
|
||||
@@ -3008,6 +3161,16 @@ parse_unary_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
ue.expr = expr;
|
||||
return ue;
|
||||
|
||||
case .Increment, .Decrement:
|
||||
op := advance_token(p);
|
||||
error(p, op.pos, "unary '%s' operator is not supported", op.text);
|
||||
expr := parse_unary_expr(p, lhs);
|
||||
|
||||
ue := ast.new(ast.Unary_Expr, op.pos, expr.end);
|
||||
ue.op = op;
|
||||
ue.expr = expr;
|
||||
return ue;
|
||||
|
||||
case .Period:
|
||||
op := advance_token(p);
|
||||
field := parse_ident(p);
|
||||
@@ -3027,16 +3190,18 @@ parse_binary_expr :: proc(p: ^Parser, lhs: bool, prec_in: int) -> ^ast.Expr {
|
||||
}
|
||||
|
||||
for prec := token_precedence(p, p.curr_tok.kind); prec >= prec_in; prec -= 1 {
|
||||
for {
|
||||
loop: for {
|
||||
op := p.curr_tok;
|
||||
op_prec := token_precedence(p, op.kind);
|
||||
if op_prec != prec {
|
||||
break;
|
||||
break loop;
|
||||
}
|
||||
if op.kind == .If || op.kind == .When {
|
||||
|
||||
#partial switch op.kind {
|
||||
case .If, .When:
|
||||
if p.prev_tok.pos.line < op.pos.line {
|
||||
// NOTE(bill): Check to see if the `if` or `when` is on the same line of the `lhs` condition
|
||||
break;
|
||||
break loop;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3049,7 +3214,7 @@ parse_binary_expr :: proc(p: ^Parser, lhs: bool, prec_in: int) -> ^ast.Expr {
|
||||
x := parse_expr(p, lhs);
|
||||
colon := expect_token(p, .Colon);
|
||||
y := parse_expr(p, lhs);
|
||||
te := ast.new(ast.Ternary_Expr, expr.pos, end_pos(p.prev_tok));
|
||||
te := ast.new(ast.Ternary_If_Expr, expr.pos, end_pos(p.prev_tok));
|
||||
te.cond = cond;
|
||||
te.op1 = op;
|
||||
te.x = x;
|
||||
@@ -3083,6 +3248,16 @@ parse_binary_expr :: proc(p: ^Parser, lhs: bool, prec_in: int) -> ^ast.Expr {
|
||||
te.y = y;
|
||||
|
||||
expr = te;
|
||||
case .Or_Else:
|
||||
x := expr;
|
||||
y := parse_expr(p, lhs);
|
||||
oe := ast.new(ast.Or_Else_Expr, expr.pos, end_pos(p.prev_tok));
|
||||
oe.x = x;
|
||||
oe.token = op;
|
||||
oe.y = y;
|
||||
|
||||
expr = oe;
|
||||
|
||||
case:
|
||||
right := parse_binary_expr(p, false, prec+1);
|
||||
if right == nil {
|
||||
@@ -3193,6 +3368,12 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt {
|
||||
return ast.new(ast.Bad_Stmt, start_tok.pos, end_pos(p.curr_tok));
|
||||
}
|
||||
|
||||
#partial switch op.kind {
|
||||
case .Increment, .Decrement:
|
||||
advance_token(p);
|
||||
error(p, op.pos, "postfix '%s' statement is not supported", op.text);
|
||||
}
|
||||
|
||||
es := ast.new(ast.Expr_Stmt, lhs[0].pos, lhs[0].end);
|
||||
es.expr = lhs[0];
|
||||
return es;
|
||||
|
||||
@@ -944,12 +944,6 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
|
||||
case Auto_Cast:
|
||||
push_generic_token(p, v.op.kind, 1);
|
||||
visit_expr(p, v.expr);
|
||||
case Ternary_Expr:
|
||||
visit_expr(p, v.cond);
|
||||
push_generic_token(p, v.op1.kind, 1);
|
||||
visit_expr(p, v.x);
|
||||
push_generic_token(p, v.op2.kind, 1);
|
||||
visit_expr(p, v.y);
|
||||
case Ternary_If_Expr:
|
||||
visit_expr(p, v.x);
|
||||
push_generic_token(p, v.op1.kind, 1);
|
||||
@@ -962,6 +956,13 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
|
||||
visit_expr(p, v.cond);
|
||||
push_generic_token(p, v.op2.kind, 1);
|
||||
visit_expr(p, v.y);
|
||||
case Or_Else_Expr:
|
||||
visit_expr(p, v.x);
|
||||
push_generic_token(p, v.token.kind, 1);
|
||||
visit_expr(p, v.y);
|
||||
case Or_Return_Expr:
|
||||
visit_expr(p, v.expr);
|
||||
push_generic_token(p, v.token.kind, 1);
|
||||
case Selector_Call_Expr:
|
||||
visit_expr(p, v.call.expr);
|
||||
push_generic_token(p, .Open_Paren, 1);
|
||||
|
||||
@@ -83,6 +83,8 @@ Token_Kind :: enum u32 {
|
||||
Cmp_Or_Eq, // ||=
|
||||
B_Assign_Op_End,
|
||||
|
||||
Increment, // ++
|
||||
Decrement, // --
|
||||
Arrow_Right, // ->
|
||||
Undef, // ---
|
||||
|
||||
@@ -108,7 +110,6 @@ Token_Kind :: enum u32 {
|
||||
Ellipsis, // ..
|
||||
Range_Half, // ..<
|
||||
Range_Full, // ..=
|
||||
Back_Slash, // \
|
||||
B_Operator_End,
|
||||
|
||||
B_Keyword_Begin,
|
||||
@@ -143,10 +144,12 @@ Token_Kind :: enum u32 {
|
||||
Transmute, // transmute
|
||||
Distinct, // distinct
|
||||
Using, // using
|
||||
Context, // context
|
||||
Or_Else, // or_else
|
||||
Or_Return, // or_return
|
||||
Asm, // asm
|
||||
Inline, // inline
|
||||
No_Inline, // no_inline
|
||||
Context, // context
|
||||
Asm, // asm
|
||||
B_Keyword_End,
|
||||
|
||||
COUNT,
|
||||
@@ -210,6 +213,8 @@ tokens := [Token_Kind.COUNT]string {
|
||||
"||=",
|
||||
"",
|
||||
|
||||
"++",
|
||||
"--",
|
||||
"->",
|
||||
"---",
|
||||
|
||||
@@ -235,7 +240,6 @@ tokens := [Token_Kind.COUNT]string {
|
||||
"..",
|
||||
"..<",
|
||||
"..=",
|
||||
"\\",
|
||||
"",
|
||||
|
||||
"",
|
||||
@@ -270,10 +274,12 @@ tokens := [Token_Kind.COUNT]string {
|
||||
"transmute",
|
||||
"distinct",
|
||||
"using",
|
||||
"context",
|
||||
"or_else",
|
||||
"or_return",
|
||||
"asm",
|
||||
"inline",
|
||||
"no_inline",
|
||||
"context",
|
||||
"asm",
|
||||
"",
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user