Compare commits

..

637 Commits

Author SHA1 Message Date
gingerBill b2cf0755f2 Add vendor to nightly.yml 2021-09-01 13:08:26 +01:00
gingerBill d399d2256b Change to [^][N] to just [^] 2021-08-31 20:39:32 +01:00
Jeroen van Rijn 27fd702692 Merge pull request #1111 from Kelimion/libtommath
Slim down LibTomMath compile.
2021-08-29 16:01:42 +02:00
gingerBill 5f29288254 Remove Syscall type 2021-08-29 15:00:35 +01:00
gingerBill 7c108dbf48 Update usage of syscall to use the intrinsics 2021-08-29 14:56:47 +01:00
Jeroen van Rijn 5c7cb393dc Slim down LibTomMath compile. 2021-08-29 15:54:56 +02:00
gingerBill 54b37573c9 Add intrinsics.syscall (for Linux and Darwin only) 2021-08-29 13:17:06 +01:00
gingerBill c3a64c2a59 Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-29 11:45:16 +01:00
gingerBill a5c31bbee0 Add map_insert which returns the pointer to inserted value 2021-08-29 11:45:11 +01:00
Jeroen van Rijn d6bd56da2c Merge pull request #1109 from Kelimion/makefile
Fix `Makefile`.
2021-08-28 22:41:06 +02:00
Jeroen van Rijn 17d31bfad6 Fix Makefile. 2021-08-28 22:40:38 +02:00
Jeroen van Rijn 89ffd40d70 Merge pull request #1108 from Kelimion/bigint
big: Add two more asymptotically optimal multiplication methods.
2021-08-28 18:19:55 +02:00
Jeroen van Rijn 737b4fde1c big: Add _private_int_mul_balance. 2021-08-28 18:17:57 +02:00
Jeroen van Rijn 2cfd6b7024 big: Add _private_int_mul_high. 2021-08-28 14:59:13 +02:00
gingerBill 713cd728ba Merge pull request #1107 from odin-lang/mv/libc-errors
Fix libc errors in Windows and add some tests.
2021-08-28 12:51:22 +01:00
vassvik f9bea5b791 Updated comment for Windows version of setjmp. 2021-08-28 13:41:14 +02:00
Jeroen van Rijn 586641d77f Merge pull request #1106 from Kelimion/bigint
big: Add `int_is_square` and Montgomery Reduction.
2021-08-28 13:36:36 +02:00
vassvik 8ca4286624 Add core:c/libc tests 2021-08-28 13:32:32 +02:00
vassvik 165118c641 Fix runtime crash for setjmp in Windows related to an hidden second argument not normally accessible needing to be set to 0. 2021-08-28 13:32:13 +02:00
Jeroen van Rijn 852643e6ba Add tests for `internal_int_is_square'. 2021-08-28 13:27:46 +02:00
vassvik 102d080a31 Fix core:c/libc Windows compilation errors by linking to the right libraries.
Fix some name typos and missing types in Windows.
Add explicit cast on MB_CUR_MAX
2021-08-28 13:27:41 +02:00
Jeroen van Rijn ec4cae4f04 big: Add int_is_square. 2021-08-27 16:41:16 +02:00
Jeroen van Rijn 4153898c55 big: Add Montgomery Reduction. 2021-08-27 16:41:16 +02:00
Jeroen van Rijn 33df335ec9 big: Add internal_int_montgomery_calc_normalization. 2021-08-27 16:41:16 +02:00
Jeroen van Rijn 893cc013b5 big: Add Montgomery reduction. 2021-08-27 16:41:16 +02:00
gingerBill b88e945268 ERROR_BLOCK() any usages of "Did you mean?" like behaviour whilst iterating across a scope entry map 2021-08-27 12:14:51 +01:00
gingerBill bf56e3ea8d Improve strings.index_any and strings.last_index_any 2021-08-27 12:07:57 +01:00
gingerBill 284acc37f9 Update SDL scancodes to have the same C enums equivalent global values 2021-08-27 11:38:29 +01:00
gingerBill 582559f7ac Correct did you mean logic and make thread-safe-er 2021-08-27 11:18:38 +01:00
gingerBill 53556d9bd2 Disable local mutex for the time being. 2021-08-26 23:16:57 +01:00
gingerBill da79124e5d Use local mutex for each AstFile.arena 2021-08-26 23:10:15 +01:00
gingerBill 2f34f1283a Make thread_join be more correct 2021-08-26 22:42:56 +01:00
gingerBill f973d271cf Add mutex around condition_broadcast 2021-08-26 22:28:09 +01:00
gingerBill 4625b25287 Wrap linux specific code for internal_thread_proc 2021-08-26 22:26:51 +01:00
gingerBill 8d8b3fd071 Add missing header 2021-08-26 22:24:26 +01:00
gingerBill a852c17614 Don't permit any signal delivery to threads on Linux 2021-08-26 22:20:10 +01:00
gingerBill b33bf3f704 Correct race condition and incorrect usage of condition_signal outside of a mutex lock 2021-08-26 22:17:51 +01:00
gingerBill 726788a483 Treat Type_Tuple closer to a Type_Struct in lb_type 2021-08-26 22:01:02 +01:00
gingerBill cdd3560702 Merge pull request #1103 from odin-lang/new-thread-pool
Improved Thread Pool implementation for the Compiler
2021-08-26 21:44:02 +01:00
gingerBill 6d49df1d87 Don't use the thread pool if worker count is 0 2021-08-26 21:40:54 +01:00
gingerBill ac191bd31f Simplify logic for -thread-count:1 2021-08-26 21:30:23 +01:00
gingerBill ad3a3547d6 Unify thread pool logic across the rest of the compiler, using a global thread pool 2021-08-26 21:22:30 +01:00
gingerBill aba14c43ac Fix typo 2021-08-26 17:57:29 +01:00
gingerBill 25c3fd48f0 Improved ThreadPool implementation 2021-08-26 17:56:28 +01:00
gingerBill e45aa68c14 Remove unneeded +1 for outstanding_task_count 2021-08-26 16:10:32 +01:00
gingerBill 6dfab34aca Merge pull request #1101 from odin-lang/compiler-allocator-improvements
Compiler Allocator Improvements
2021-08-26 16:06:37 +01:00
gingerBill d3d805ffb3 Fix typo 2021-08-26 15:58:34 +01:00
gingerBill 05b9724c85 Correct platform_virtual_memory_init on Unix 2021-08-26 15:55:09 +01:00
gingerBill 5053f0179c Implement virtual memory code for *nix and make generic 2021-08-26 15:53:08 +01:00
gingerBill 3e4d615983 Minor fixes 2021-08-26 15:41:32 +01:00
gingerBill aa8777ee47 Change the implementation of Arena to use virtual memory, and remove the old gbArena code 2021-08-26 15:38:34 +01:00
gingerBill 98dd59e412 Fix return value 2021-08-25 22:28:15 +01:00
gingerBill 7b2f6aaa1c Add [^] to GameControllerGetSensorData 2021-08-25 16:10:39 +01:00
gingerBill ad943f0189 Use [^] on PeepEvents 2021-08-25 15:50:35 +01:00
gingerBill 224496dca7 Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-25 15:21:14 +01:00
gingerBill 1ef59417ef Add gl_set_proc_address to both sdl2 and glfw as utility loaders for OpenGL 2021-08-25 15:21:06 +01:00
Jeroen van Rijn 0d5a160409 Add PortMidi to vendor:README.txt. 2021-08-25 13:54:39 +02:00
gingerBill 82facb387c Add vendor:portmidi 2021-08-25 12:46:18 +01:00
gingerBill da7a0df7a1 Simplify logic for parse_binary_expr 2021-08-25 11:43:02 +01:00
gingerBill fcbd94b924 Update enums 2021-08-24 20:05:01 +01:00
gingerBill 5bb3912001 Update licences for GLFW 2021-08-24 20:04:35 +01:00
gingerBill f4248b159d Add vendor:glfw 2021-08-24 20:00:24 +01:00
gingerBill 2e4edcc7e9 Merge pull request #1099 from Kelimion/vendor
vendor: Add `README.md` and clarify licensing.
2021-08-24 18:34:47 +01:00
gingerBill 0d3272d914 Remove ICD from generation 2021-08-24 18:33:35 +01:00
Jeroen van Rijn 50f3e77b43 vendor: Add README.md and clarify licensing. 2021-08-24 19:33:10 +02:00
gingerBill 964d91b855 Add vendor:vulkan 2021-08-24 17:08:03 +01:00
gingerBill bdac3ee120 Add gamecontrollerdb.txt to vendor:sdl
https://github.com/gabomdq/SDL_GameControllerDB
2021-08-24 14:04:27 +01:00
gingerBill b81c670597 Remove debug message 2021-08-24 11:08:41 +01:00
gingerBill 4489df2871 Make sync_t distinct 2021-08-24 00:22:11 +01:00
gingerBill 766c17a6a3 Change [^][1]T usage to [^]T 2021-08-24 00:20:51 +01:00
gingerBill 018f8a82d6 Correct indentation 2021-08-24 00:19:47 +01:00
gingerBill d5a0c004b6 More [^][N]T usage for UniformNuiv 2021-08-24 00:16:39 +01:00
gingerBill 0b6e45c9a2 More [^][N]T usage 2021-08-24 00:15:34 +01:00
gingerBill 5c41f64829 Improve signatures for [^][N*M]T like matrix parameters 2021-08-24 00:14:09 +01:00
gingerBill ad56cf0038 Change some usages of [^]T to ^[N]T where appropriate 2021-08-24 00:06:49 +01:00
gingerBill 58b5e92c2f Update more uses of [^]T types 2021-08-23 23:57:16 +01:00
gingerBill 0a0752db7c Correct int to i32 usage 2021-08-23 23:46:36 +01:00
gingerBill 49fbdd6188 Add more indirect command structs to the procedure calls 2021-08-23 23:45:00 +01:00
gingerBill 1048553e78 More corrections; Add DrawArraysIndirectCommand and DrawElementsIndirectCommand data structures 2021-08-23 23:02:19 +01:00
gingerBill 63282290db Remove dead procedure 2021-08-23 21:42:11 +01:00
gingerBill 7a1498e7dc Remove space prefix 2021-08-23 21:40:17 +01:00
gingerBill 0d3cbb8883 Add vendor:OpenGL
Based off a heavily modified version of: https://github.com/vassvik/odin-gl
2021-08-23 21:33:39 +01:00
gingerBill ce7698c20e Correct core library usage of the new mem.new behaviour 2021-08-23 19:29:01 +01:00
gingerBill 382ca20916 Correct procedure checking flag handling, and correct the (bodge) handle of unchecked procedure bodies 2021-08-23 19:24:53 +01:00
gingerBill fe2ad54f60 Fix bug in check_is_terminating 2021-08-23 17:42:54 +01:00
gingerBill b014879159 Add extra message to assert 2021-08-23 16:45:52 +01:00
gingerBill 81623861c0 Correct mem.clone_slice 2021-08-23 14:33:54 +01:00
gingerBill bd86993035 Remove the old inline and no_inline tokens 2021-08-23 12:37:42 +01:00
gingerBill 4ccf135892 Unify new/make the internal logic between runtime and mem 2021-08-23 12:35:29 +01:00
gingerBill cba0bd30f5 Add suggestions when trying to take the address the a value from a for/switch statement 2021-08-23 11:50:02 +01:00
gingerBill 276d4b8f0d Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-23 11:31:09 +01:00
gingerBill 7bdbaca938 Make SDL_image.Init return InitFlags 2021-08-23 11:31:00 +01:00
Jeroen van Rijn 7f34080b69 Merge pull request #1097 from nakst/master
Thread pool: create threads in thread_pool_wait
2021-08-23 11:17:54 +02:00
nakst 9397555c91 Thread pool: create threads in thread_pool_wait 2021-08-23 10:11:24 +01:00
gingerBill 7a00ef1879 Merge pull request #1096 from nakst/master
thread_pool.cpp: fix with 1 thread; gb.h: remove buggy /proc/cpuinfo code
2021-08-23 09:32:41 +01:00
nakst 35204e3cc5 thread_pool.cpp: fix with 1 thread; gb.h: remove buggy /proc/cpuinfo code 2021-08-23 09:18:18 +01:00
gingerBill daced956e3 Make ThreadPool use std::atomic and heap_allocator() 2021-08-22 23:01:01 +01:00
gingerBill 5a2d582a09 Merge pull request #1095 from nakst/master
Rewrite thread_pool.cpp
2021-08-22 22:55:31 +01:00
nakst b878be6f79 Thread pool fix on Win32 2021-08-22 21:24:56 +01:00
nakst 1f25f60a68 Rewrite thread_pool.cpp 2021-08-22 21:13:41 +01:00
gingerBill 2e921c88fb Add debug information for [^]T 2021-08-22 19:02:52 +01:00
gingerBill abaf8c127d Correct build.bat 2021-08-22 17:12:19 +01:00
gingerBill 0c5fa2cdd5 Fix build.bat 2021-08-22 17:09:40 +01:00
gingerBill 36cb1f868b Embed the SDL2 libraries into vendor:sdl2 2021-08-22 17:05:06 +01:00
gingerBill 037cc679c4 Keep -vet happy 2021-08-22 16:58:12 +01:00
gingerBill db6fad7396 Fix indexing code gen for multi-pointers 2021-08-22 16:55:57 +01:00
gingerBill 07bfb55658 Fix code gen for compare against nil for multi pointers 2021-08-22 16:53:26 +01:00
gingerBill 56078ee099 Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-22 16:49:48 +01:00
gingerBill ae4a378294 Merge pull request #1094 from graphitemaster/master
Project all of libc
2021-08-22 16:06:15 +01:00
Dale Weiler 791d7f764b cleanup 2021-08-22 10:05:27 -04:00
Dale Weiler b39a4f3e3b Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-22 09:49:20 -04:00
Dale Weiler 389b50f735 libc projection 2021-08-22 09:48:34 -04:00
gingerBill 2f6e566a32 Remove the deprecated fmt.print*_err procedures 2021-08-22 12:57:08 +01:00
gingerBill d5bad374d9 Remove deprecated procedure slice_ptr_to_bytes 2021-08-22 12:55:57 +01:00
gingerBill 445ed9be2b Use multi-pointers when appropriate 2021-08-22 12:54:04 +01:00
gingerBill 8694a0f68a Update signature_parameter_similar_enough for multi pointers 2021-08-22 12:31:03 +01:00
gingerBill d3fee9d761 Merge pull request #1093 from odin-lang/multi-pointers
Multi Pointers `[^]T`
2021-08-22 11:50:47 +01:00
gingerBill 36a6805b7c Update doc format for multi-pointers 2021-08-22 11:46:26 +01:00
gingerBill 19bf12aa09 Update odin/ast for multi pointers 2021-08-22 11:46:12 +01:00
gingerBill 0decdaed1a Merge branch 'master' into multi-pointers 2021-08-22 11:28:44 +01:00
gingerBill 93b5befe45 Improve error handling for missing semicolon separators in a for loop 2021-08-22 11:27:24 +01:00
gingerBill d72f4a8a79 Correct Multi Pointer Type handling in expressions 2021-08-22 00:59:42 +01:00
gingerBill 91247a8fe1 Fix multi pointers for parapoly 2021-08-21 23:16:30 +01:00
gingerBill 18a0fa02c5 Add multi pointers to core 2021-08-21 23:16:14 +01:00
gingerBill 932f330a51 Add comparisons to LLVM backend for multi pointers 2021-08-21 23:12:15 +01:00
gingerBill 6a77fc4cdd Add multi-pointer types [^]T 2021-08-21 23:10:21 +01:00
gingerBill 01a888fced Update import path 2021-08-21 14:09:48 +01:00
gingerBill f0437a4242 Enforce core:builtin and core:intrinsics for imports 2021-08-21 13:44:16 +01:00
gingerBill 32bdad322a Improve parsing for or_return in core:odin/parser 2021-08-21 13:43:38 +01:00
gingerBill bf130087e7 Change to strings.clone_from_cstring_bounded 2021-08-21 13:43:03 +01:00
gingerBill 38e038a1ab Add strings.clone_from and strings.clone_from_nul_terminated 2021-08-21 13:42:06 +01:00
gingerBill 6504607adf Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-21 13:32:12 +01:00
gingerBill c8378fce95 Add library collection vendor 2021-08-21 13:32:07 +01:00
Jeroen van Rijn 1aeaec8d5c Merge pull request #1091 from Kelimion/win11
os: Add Windows 11 detection.
2021-08-21 14:12:26 +02:00
gingerBill 2aaf927beb Improve error message for Cannot assign value if they have the same name but are from different packages 2021-08-21 13:09:23 +01:00
Jeroen van Rijn bb86b0f526 os: Add Windows 11 detection. 2021-08-21 14:08:22 +02:00
gingerBill 2f5edebefa Rename mem.reinterpret to mem.reinterpret_copy 2021-08-20 10:19:30 +01:00
gingerBill b5cdb331b0 Add mem.reinterpret 2021-08-20 10:18:34 +01:00
gingerBill fa4f3aa7ad Correct atomic usage 2021-08-19 17:51:19 +01:00
gingerBill a90fe7211c Make global_entity_id atomic 2021-08-19 17:44:26 +01:00
gingerBill ac6cc5191a Make ThreadPool.is_running atomic 2021-08-19 17:43:15 +01:00
gingerBill 38841dd46e Fix race condition from add_entity_use due to Entity.identifier 2021-08-19 17:38:18 +01:00
gingerBill e722af7f61 Remove unneeded disabled warnings from build.bat 2021-08-19 15:43:51 +01:00
gingerBill df372dbd5b Migrate and remove more from gb.h 2021-08-19 15:38:21 +01:00
gingerBill 5c4d95d539 Move more of gb.h's Synchronization code into common.cpp 2021-08-19 15:19:36 +01:00
gingerBill 9ae4de2ab8 Remove unused code from gb.h (which means it is heavily modified now) 2021-08-19 15:09:39 +01:00
gingerBill 7845769d4b Remove unused code 2021-08-19 15:03:10 +01:00
gingerBill 33239324b8 Improve the C++ to be more correct for clang on Windows, still requiring the same disabled warnings as on *nix 2021-08-19 14:44:53 +01:00
gingerBill 82a74ebfa9 Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-19 11:40:32 +01:00
gingerBill 35026000bb Fix deadlock caused by typo 2021-08-19 11:40:26 +01:00
Jeroen van Rijn 55cf3d26bf Merge pull request #1090 from Kelimion/bigint
big: Update license to BSD-3.
2021-08-19 12:13:39 +02:00
Jeroen van Rijn 23d29be4d8 big: Update license to BSD-3. 2021-08-19 12:12:59 +02:00
Jeroen van Rijn 1ad0743a52 big: Nicely align test suite results. 2021-08-19 12:12:59 +02:00
gingerBill 54af47a138 Remove useless code 2021-08-18 23:38:06 +01:00
gingerBill b84ee3ab8f Fix odin test not executing any tests 2021-08-18 23:33:27 +01:00
gingerBill e023b96737 Remove json import from demo 2021-08-18 22:33:18 +01:00
gingerBill 3fde4616e0 Correct mutex usage for path_to_fullpath; make ThreadPool use BlockingMutex 2021-08-18 21:21:30 +01:00
Mikkel Hjortshøj 4812006eb8 Update ci.yml 2021-08-18 22:09:44 +02:00
gingerBill 740995df3d Remove attribute parameter for pthread_mutex_init 2021-08-18 21:07:06 +01:00
gingerBill aa5c3da414 Make BlockingMutex non-recursive on *nix systems 2021-08-18 20:57:03 +01:00
gingerBill d419d81841 Remove unused code 2021-08-18 20:48:26 +01:00
gingerBill 08942714a2 Make Arena allocate virtual memory directory; make it use a BlockingMutex 2021-08-18 20:46:10 +01:00
gingerBill 3c443babb2 Extra sanity check around static 2021-08-18 20:32:54 +01:00
gingerBill 326e5cd046 Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-18 20:31:39 +01:00
gingerBill 79e98b71d3 Remove dead code, and add an extra mutex 2021-08-18 20:31:34 +01:00
Jeroen van Rijn 96605f700b Merge pull request #1089 from Kelimion/bigint
big: Enable Toom again.
2021-08-18 15:36:45 +02:00
Jeroen van Rijn 38b5e01343 Merge branch 'master' into bigint 2021-08-18 15:35:49 +02:00
Jeroen van Rijn 06cde91ba3 big: Enable Toom again. 2021-08-18 15:35:01 +02:00
Jeroen van Rijn 3891d6a483 Merge pull request #1088 from Kelimion/bigint
big: Fix signed multiplication for some inputs.
2021-08-18 15:32:49 +02:00
Jeroen van Rijn 5fd7a5f32a big: Fix signed multiplication for some inputs. 2021-08-18 15:24:43 +02:00
gingerBill a01c946c20 Add mutex to Scope lookups and insertions 2021-08-18 11:17:14 +01:00
gingerBill 84b0da44db Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-17 19:54:17 +01:00
gingerBill cdb3a5205c Add mutex for add_type_and_value 2021-08-17 19:54:09 +01:00
Jeroen van Rijn eae98feb7a Merge pull request #1086 from Kelimion/bigint
big: Temporarily disable Toom.
2021-08-16 22:19:10 +02:00
Jeroen van Rijn 3af078e941 Merge branch 'master' into bigint 2021-08-16 22:16:03 +02:00
Jeroen van Rijn 48c1f0ab59 big: Disable Toom for a moment. 2021-08-16 22:13:54 +02:00
Jeroen van Rijn 19386814b3 Merge pull request #1085 from Kelimion/bigint
Add `_private_int_mul_toom` + `_private_int_mul_karatsuba`.
2021-08-16 21:29:40 +02:00
Jeroen van Rijn 706e58c1c7 big: Add _private_int_mul_toom`. 2021-08-16 21:17:21 +02:00
gingerBill 9ab94650c8 Allow + in import paths 2021-08-16 18:21:58 +01:00
gingerBill fce86ff3d5 Correct struct tag bug 2021-08-16 18:17:26 +01:00
gingerBill 0051cd12e2 Make flags atomic for Entity and Type 2021-08-16 16:30:49 +01:00
gingerBill df159dbae7 Add some missing files to sync2 for linux and darwin 2021-08-16 15:48:54 +01:00
gingerBill 94d298755a Fix race condition when adding a dependency 2021-08-16 15:33:26 +01:00
Jeroen van Rijn 8b49bbb0fc big: Add _private_mul_karatsuba. 2021-08-16 16:10:10 +02:00
gingerBill 5f072591ba Merge pull request #1082 from odin-lang/or_else-or_return-operator
`or_else` and `or_return` operators
2021-08-16 12:03:43 +01:00
gingerBill e3fef2dade Improve parsing for or_return; allow #force_inline foo() or_return; 2021-08-16 11:58:50 +01:00
gingerBill 4c306a6f99 Correct or_return logic for debug printing and expression is not used checking 2021-08-16 11:08:37 +01:00
gingerBill 0996cc82a7 Keep -vet happy 2021-08-15 23:17:12 +01:00
gingerBill f293d7c997 Update package encoding/json for or_return 2021-08-15 23:11:15 +01:00
gingerBill b2097604d5 Add clone_ast for or_else and or_return 2021-08-15 23:10:52 +01:00
gingerBill 4e1c9b71f4 Update core:odin/* for or_else and or_return 2021-08-15 19:05:55 +01:00
gingerBill 3e2788afdc Add extra example to or_return_operator 2021-08-15 18:51:20 +01:00
gingerBill 1a7f508dd9 Improve comment 2021-08-15 18:44:50 +01:00
gingerBill dc8cfcf92a Fix typos and improve clarity of or_return_operator 2021-08-15 18:36:34 +01:00
gingerBill ac08d37ca0 Add or_return_operator to examples/demo 2021-08-15 18:29:49 +01:00
gingerBill 4035fec784 Add more uses of or_return 2021-08-15 18:13:56 +01:00
gingerBill b071a07c86 Replace uses of err != nil with or_return where appropriate 2021-08-15 17:56:24 +01:00
gingerBill c27b8a71fd Replace err != nil with or_return where appropriate 2021-08-15 17:52:10 +01:00
gingerBill b8661e0ae0 Update semi-colon insertion rules for or_return 2021-08-15 17:16:37 +01:00
gingerBill 21cbac755e Make or_else and or_return operators (binary and suffix respectively) 2021-08-15 17:14:35 +01:00
gingerBill a3a20f09e2 or_return built-in procedure 2021-08-15 16:55:30 +01:00
gingerBill d62f189d72 Remove some dead code 2021-08-15 15:09:47 +01:00
gingerBill 84713b58e0 Improve error handling for field list prefixes 2021-08-15 13:29:44 +01:00
gingerBill 294c8426e6 Minor clean up of parse_field_prefixes 2021-08-15 13:18:06 +01:00
gingerBill 7bbc9a4634 Add #any_int directive to replace auto_cast uses on parameters. 2021-08-15 12:56:59 +01:00
gingerBill 1cd3b693ae Update LICENSE 2021-08-15 12:12:48 +01:00
gingerBill d1b9b06614 Add core:math/big to examples/all 2021-08-15 11:14:51 +01:00
gingerBill 19aefa6a40 Add assignment statement to #no_bounds_check etc rules. Correct os_linux.odin usage. 2021-08-15 11:14:34 +01:00
gingerBill effecf8595 Fix typo 2021-08-15 11:13:59 +01:00
gingerBill d5e3f72a0b Define where #bounds_check/#no_bounds_check can be applied 2021-08-15 11:09:41 +01:00
gingerBill 9fb486b2ad Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-15 11:08:35 +01:00
gingerBill d70fa4329c Move #no_bounds_check to before return 2021-08-15 11:08:28 +01:00
Jeroen van Rijn 3f29a0d6dd Merge pull request #1078 from Kelimion/bigint
Add recursive division.
2021-08-14 15:58:27 +02:00
Jeroen van Rijn 0db86a0638 big: Add workaround for DLL globals bug. 2021-08-14 13:51:17 +02:00
Jeroen van Rijn dc02566a84 big: Add _private_int_div_recursive. 2021-08-13 23:45:00 +02:00
gingerBill 0e84e06756 Fix lower and upper values for a bit_set[Enum] type. 2021-08-13 14:17:27 +01:00
gingerBill e6b2df4b2b Add extra error message check to lb_big_int_to_llvm 2021-08-13 14:16:53 +01:00
Jeroen van Rijn 37be8d4091 big: Add internal_invmod. 2021-08-13 14:55:53 +02:00
gingerBill a3930cb470 Improve fmt._user_formatters logic 2021-08-13 12:44:55 +01:00
gingerBill ad402726f1 Fix #1026 2021-08-13 12:21:14 +01:00
gingerBill 8ff9f2e44f Fix #1077 2021-08-13 11:49:52 +01:00
gingerBill 799a56bbcb Fix column in tokenizer (due to removed line) 2021-08-13 10:50:05 +01:00
gingerBill fbbd43a6d8 Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-13 10:45:35 +01:00
gingerBill 367bf0c7ae Fix #1076 2021-08-13 10:45:29 +01:00
Jeroen van Rijn f72a0de074 big: Add inverse mod. 2021-08-13 01:41:33 +02:00
Jeroen van Rijn 07baae04c9 Merge pull request #1075 from Kelimion/bigint
Add `core:math/big`, an arbitrary precision library.
2021-08-11 21:43:54 +02:00
Jeroen van Rijn eb22a49b02 big: Add int_from_bytes_*. 2021-08-11 20:59:54 +02:00
Jeroen van Rijn ee24f2dd37 big: Improve int_to_bytes_*. 2021-08-11 20:59:54 +02:00
Jeroen van Rijn 12f9b6db63 big: Add int_to_bytes_{big, little} + Python compatible variants. 2021-08-11 20:59:54 +02:00
Jeroen van Rijn 851780b8f4 big: Add arguments and usage to test.py. 2021-08-11 20:59:54 +02:00
Jeroen van Rijn 5f34ff9f9f big: Add _private_int_sqr_toom. 2021-08-11 20:59:54 +02:00
Jeroen van Rijn 2b274fefbb big: Add _private_int_sqr_karatsuba. 2021-08-11 20:59:54 +02:00
Jeroen van Rijn 6c681b258c big: Add _private_int_sqr_comba. 2021-08-11 20:59:54 +02:00
Jeroen van Rijn 1f91a2fe65 big: Finish refactor. 2021-08-11 20:59:54 +02:00
Jeroen van Rijn 19ff27788c big: Refactoring. 2021-08-11 20:59:54 +02:00
Jeroen van Rijn 1ebb0bd9d6 big: More refactoring. 2021-08-11 20:59:54 +02:00
Jeroen van Rijn d505a05d36 big: More refactoring. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 07dca737f0 big: More refactoring. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 6d34a8344a big: Refactor helpers. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 40b7b9ecdf big: Refactor exponents and such. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 53bf66ce1e big: Prettify internal_cmp_digit. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn fd95f50c56 big: Split up int_is_* comparison tests. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 777e17d80f big: Improve tunables. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn d4a03acbc3 big: Split up int_mod_bits (res = val % (1 << bits)) 2021-08-11 20:59:53 +02:00
Jeroen van Rijn c3db24f834 big: Split up gcd + lcm. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 62dcccd7ef big: Move division internals. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn e288a563e1 big: Move _mul private functions. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 6298226238 big: Switch choose over to internal implementations. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 9321616c80 big: Split more into public and internal. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 9890e7cfeb big: Improved zero_unused helper. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 4be48973ad big: Squashed shl1 bug when a larger dest was reused for a smaller result. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn f8442e0524 big: Split up mul into internal and public parts. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 9858989b1c big: Split up add and sub into public and internal parts. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 511057ca36 big: Improve timing code. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 35d8976de4 bit: Optimized int_bitfield_extract. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 463003e86a bit: Improved bitfield extraction. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 85a2a8815e big: Some more work on constants. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 47397a6a48 Add faster divison. 2021-08-11 20:59:53 +02:00
Jeroen van Rijn 2323ca1622 big: Add MATH_BIG_FORCE_64/32_BIT flags. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn fc0a92f8ac big: Add constants. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 97d80d03f9 big: Error.None -> nil 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 627872db97 big: Timed factorial. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn a27612ec6a Add _mul_comba path. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 491e4ecc74 big: Add binary split factorial. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn cd0ce7b76e big: Add choose. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 320387c4ee big: Add gcd_lcm fast path in wrapper. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 6424a5a8dd big: Refactored gcm and lcm to use a common function. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 06f5a6c785 big: Special case gcd(0,0) + lcm(0,0). 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 8b1d8c8453 big: Add lcm and its test. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 0028cb0258 big: Test gcd. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn b15ee059ad big: Add gcd. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 50feeaa285 big: Add test for factorial. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn e80ac18324 big: Add factorial, have tests use hex strings. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn db0196abc7 big: Test root_n. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 149c7b88df big: Fix sqrt, div, add with certain inputs. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 7afd1b15a8 big: test_pow for larger ints. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn f12672727d big: Add test_pow and some more switches. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 2179cc2bc7 big: Improved test driver. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 961adfedd9 big: Test negative inputs as well. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 385b9c9922 big: Add tests for log. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 922df6a438 big: Add more exhaustive tests. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn c1a001c331 big: Add randomized testing. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 13fab36639 big: Fix mul. 2021-08-11 20:59:52 +02:00
Jeroen van Rijn 708389a7ee big: Improve test driver. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn fb6c9af1ae big: Improve tests. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 85aa4dd670 big: Start test suite. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 74258a170a big: fix itoa base PoT other than 16. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 2fbff25a18 big: Improve int_bitfield_extract. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 9c150381bf big: Add rand. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 531c4936dd big: Add root_n. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 2aae1016ab big: Add sqrt. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 9c2468ecf7 big: Add atoi. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 0a431eef19 big: Add another way to estimate radix size. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 9646d1f2b8 big: Add submod, mulmod, sqrmod. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 5f7aeb3045 big: Add mod and addmod. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 1ebaa9fb3b big: itoa now works for arbitrary radixes. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn c2255c6c19 big: Add div. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 2884fa5506 big: add div by 3. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 31c94bd7f8 big: Finish log, fix sqr. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 5f63e3952e big: Correct pow bugs from the original. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn d953e40fb3 big: Add pow. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn c3a4d7dda2 big: Fast square method. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn b4a29844e9 big: Add multiplication. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 0254057f1b big: Add swap. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn f34ba44bf8 big: Add shl, shr and shrmod. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn d4d863c4db big: Add mod_power_of_two. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 78c0877994 big: Add get(a, type) and get_float. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 1d0b37c1d8 big: add shl1, shr1. 2021-08-11 20:59:51 +02:00
Jeroen van Rijn 7648f2e655 big: Finish big ZII refactor. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn d9efa6c8b5 big: More ZII refactoring. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn 2e372b33a3 big: More ZII refactoring. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn 687c211a58 big: ZII. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn 4eadd0867d big: Continuing to refactor. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn 9dba17cf87 bigint: refactor to big.Int instead of bigint.Int. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn baef0c291d bigint: Added some more helpers. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn cccd290834 bigint: Add is_power_of_two helper. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn 5af85aed3d bigint: itoa support for arbitrary precision if is_power_of_two(radix) 2021-08-11 20:59:50 +02:00
Jeroen van Rijn e600e5947b bigint: remove unnecessary boundary checks. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn d7ae611f76 bigint: itoa now writes backwards directly, no need to reverse after. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn 04a83eb9f7 bigint: pass size to itoa_raw. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn 341e8a3c99 bigint: itoa works for numbers <= 120 bits. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn e3d8ac559d bigint: Fast paths for radix code. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn 767948ab46 bigint: log_n for bases that fit within one DIGIT or are a power of two. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn dbcd8da733 bigint: Working on itoa and logn. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn 905d5459a9 bigint: Add count_bits and more prep. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn dfd5a993a2 bigint: Prepare for multiplication. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn daceaa65f5 bigint: Add substractin with immediate. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn c2c07f07db Add single DIGIT addition. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn c5cbd3260a bigint: Add prototypes for immediate add+sub. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn d57e1be89f bigint: Improve add. 2021-08-11 20:59:50 +02:00
Jeroen van Rijn 18dda6ff9d Start of core:math/bigint
We have:
- `init` to create a new `Int`
- `init(from_integer)` to create a new `Int` and set it to `from_integer`.
- `set(Int, from_integer)` to set an `Int` to `from_integer`
- `add(dest, a, b)` to add `a` and `b` into `dest`.
- `sub(dest, a, b)` to subtract `b` from `a` and put the result in `dest`.

And a few helper functions, like:
- `is_zero`, `is_negative`, ...
- `grow`, `shrink`, `clear`, `zero`
2021-08-11 20:59:50 +02:00
gingerBill 7afc367275 Update examples/all/all_main.odin 2021-08-11 19:11:00 +01:00
gingerBill c465171b45 Fix #1061 2021-08-09 21:41:19 +01:00
gingerBill 02f22a0b3f Correct DllMain behaviour 2021-08-09 21:23:24 +01:00
gingerBill 193fd0eecb Correct and improve type inference for swizzling expressions 2021-08-09 20:13:58 +01:00
gingerBill 01f431b01f Unify semantics of the built-in swizzle procedure with the selector expression semantics e.g. .xyz 2021-08-09 19:37:58 +01:00
gingerBill aebfa4b28e Allow len and cap to return a uint if a type hint is uint to aid people wanting to use unsigned integers 2021-08-09 17:39:38 +01:00
gingerBill a3abe991a4 Add package core:encoding/hxa 2021-08-09 16:26:51 +01:00
gingerBill e793f92e67 Improve parsing handling for the { return } cases 2021-08-09 13:01:47 +01:00
gingerBill 042f376626 Minor code changes to Map/StringMap 2021-08-09 13:01:26 +01:00
gingerBill d99ed692ba Add utility procedures: io.read_ptr; io.write_ptr; io.read_ptr_at; io.write_ptr_at 2021-08-09 12:33:21 +01:00
gingerBill 4d00c2b800 Allocator_Error.Mode_Not_Implemented; Minor improvement to map runtime procedures 2021-08-08 14:29:45 +01:00
gingerBill a5605e94b1 Simplify Map and StringMap in the compiler to reuse the hashes' array data if possible. 2021-08-08 13:56:40 +01:00
gingerBill 9cfe20cfb4 Correct error message for add_import_dependency_node 2021-08-08 13:13:31 +01:00
gingerBill db3501f61b [Breaking] Change the layout json.Value to be a union rather than a struct of a json.Pos and the union 2021-08-08 12:59:35 +01:00
gingerBill 48538aa792 Remove package core:encoding/cel 2021-08-08 12:50:38 +01:00
gingerBill 5fd64f48ee Minor procedure rename 2021-08-08 12:48:44 +01:00
gingerBill a3b7126875 Simplify init_tokenizer_with_data 2021-08-08 12:47:45 +01:00
gingerBill 5756c8a7c6 Merge branch 'master' of https://github.com/odin-lang/Odin 2021-08-08 12:45:33 +01:00
gingerBill cdd0061869 Prefer ..= over .. 2021-08-08 12:45:27 +01:00
Mikkel Hjortshøj e6adfd8054 Update create_nightly_json.py 2021-08-08 11:29:56 +02:00
Jeroen van Rijn 6d59223efd Merge pull request #1072 from Kelimion/cel_fix_ise
CEL: Fix by changing `using enum` to ISE.
2021-08-08 02:21:04 +02:00
Jeroen van Rijn ebd034fff9 CEL: Fix ISE. 2021-08-08 02:19:32 +02:00
gingerBill 000bda8419 Reduce superfluous error messages for return statements expecting not-1 return values 2021-08-07 16:29:00 +01:00
gingerBill 423b842347 Fix typo 2021-08-07 15:07:29 +01:00
gingerBill 16eeae36d7 Inline heap_allocator resize logic on *nix platforms 2021-08-07 15:05:46 +01:00
gingerBill 5453e92bcb Minor test on array_set_capacity 2021-08-07 14:44:48 +01:00
gingerBill c16c9535d9 Reorder ci.yml from run check version to version check run 2021-08-07 14:40:54 +01:00
gingerBill 662c7b1e71 Minor clean-up 2021-08-07 14:39:26 +01:00
gingerBill 571170fd30 Improve and simplify the memory layout of MPMCQueue 2021-08-07 14:25:48 +01:00
gingerBill 911c428dac Remove dead code in queue.cpp; clean up initialization code 2021-08-07 13:54:08 +01:00
gingerBill 40822be595 Reorganize llvm_backend.cpp into separate files for easier maintenance 2021-08-07 12:01:48 +01:00
gingerBill f5e51a29b5 Fix #1070 2021-08-07 11:23:40 +01:00
gingerBill beaad719ad Reallow using on enum declarations temporarily but with a warning. 2021-08-05 17:51:45 +01:00
gingerBill 0d257c61cd Disallow using on an enum declaration. 2021-08-05 17:46:42 +01:00
gingerBill dd8fa1d690 Prefer ..= over .. 2021-08-04 00:36:10 +01:00
gingerBill af6df7d7c9 Improve error cases in core:odin/parser 2021-08-04 00:23:31 +01:00
gingerBill 57a17a708b Improve core:odin/parser 2021-08-04 00:10:41 +01:00
gingerBill 1f79082921 Remove dead comments 2021-08-04 00:10:05 +01:00
gingerBill afff9478c8 Make core:odin/tokenizer be consistent with the compiler's version 2021-08-03 23:27:26 +01:00
gingerBill b352b42afc Remove old comment 2021-08-03 16:31:50 +01:00
gingerBill c3e4509d17 Improve error message when using a comma rather than a semicolon in a bit_set 2021-08-03 15:46:14 +01:00
gingerBill 664be28941 Fix typo 2021-08-03 15:45:32 +01:00
gingerBill d7e970ac32 Add extra debugf message for generate missing procedure 2021-08-03 14:36:15 +01:00
gingerBill 810dcfc602 Remove debug gb_printf_err and replace with debugf 2021-08-03 14:30:32 +01:00
gingerBill 14645b147f Reorganize missing_procedures_to_check generation code 2021-08-03 14:17:39 +01:00
gingerBill b036cc9013 Add missing_procedures_to_check to lbModule 2021-08-03 14:13:38 +01:00
gingerBill 3a8ac92995 Add sanity check in lb_create_procedure 2021-08-03 13:49:01 +01:00
gingerBill 67bedcba4b Reorganize stages in checker 2021-08-03 13:41:28 +01:00
gingerBill 4987ef89f1 Reorganize some of the checker stages 2021-08-03 13:40:47 +01:00
gingerBill 545b345eea Remove optimizations in tokenizer and default to older code (same logic) (optimize later) 2021-08-03 13:33:01 +01:00
gingerBill 3e961af5f1 Sort files by name in packages 2021-08-02 22:55:22 +01:00
gingerBill 9e6e769141 Add sanity checks to checker 2021-08-02 18:32:17 +01:00
Jeroen van Rijn c0f746a251 Merge pull request #1065 from Kelimion/string_cut
Add `strings.cut`, which returns a substring.
2021-08-02 18:09:59 +02:00
gingerBill ccbdf086ff Add @(warning=<string>) 2021-08-02 16:58:50 +01:00
Jeroen van Rijn d260ca6738 string.cut uses context.allocator by default. 2021-08-02 17:58:42 +02:00
gingerBill b0e64ca7e8 Prepare tokenizer for optimizations 2021-08-02 16:47:32 +01:00
gingerBill 7f3d4cb504 Remove the literal conversion logic to the parser from the tokenizer 2021-08-02 00:53:45 +01:00
gingerBill 97be36d18a Replace line with column_minus_one in Tokenizer; Remove dead code 2021-08-02 00:26:54 +01:00
gingerBill be76da2c90 Begin optimizing tokenizer; Replace gb_utf8_decode with utf8_decode (CC but easier to change later) 2021-08-01 23:56:17 +01:00
gingerBill b1a8357f50 Clean up a bit of the tokenizer code so that the semicolon insertion rules are in one place 2021-08-01 22:41:00 +01:00
Jeroen van Rijn 0dc900ba34 Add strings.cut, which returns a substring. 2021-08-01 19:41:20 +02:00
gingerBill 700624119b Give begin_error_block its own recursive mutex 2021-07-29 12:35:11 +01:00
gingerBill af32aba7fc Modify MPMCQueue behaviour to use i32 over isize; Correct cache line padding within MPMCQueue 2021-07-28 00:59:30 +01:00
gingerBill 541c79c01a Add mutex for @(builtin) attributes 2021-07-28 00:27:16 +01:00
gingerBill 358226468d EXPERIMENT: Set DEFAULT_TO_THREADED_CHECKER on all platforms 2021-07-27 23:55:55 +01:00
gingerBill d1e5f34f76 Use DEFAULT_TO_THREADED_CHECKER 2021-07-27 23:55:19 +01:00
gingerBill 416dd93bf7 Add accidentally removed call 2021-07-27 23:39:35 +01:00
gingerBill 7c80577160 Correct race condition on untyped expr info map logic on global evaluations 2021-07-27 23:37:55 +01:00
gingerBill a5d6fda433 Define which mutexes are blocking and recursive explicitly 2021-07-27 23:14:01 +01:00
gingerBill 4bc3796f9b Short circuit check_with_workers logic on worker_count == 0 2021-07-27 23:00:51 +01:00
gingerBill 5e12f5a746 Unify threading logic 2021-07-27 22:55:32 +01:00
gingerBill 4080ba4026 Remove global semaphore and place in CheckerInfo 2021-07-27 21:42:43 +01:00
gingerBill e17593be94 Remove dead mutex 2021-07-27 21:14:06 +01:00
gingerBill 44aa69748c Correct logic for check_import_entities - collect file decls 2021-07-27 21:13:03 +01:00
gingerBill 9cd5ea59dd Big simplification and improvement of the entity collection system, reducing unneeded steps for packages 2021-07-27 20:45:50 +01:00
gingerBill 116e98b378 Improve default scope size 2021-07-27 10:59:39 +01:00
gingerBill ae25787f48 Fix syntax error for a missing package name 2021-07-26 11:43:17 +01:00
gingerBill 08dc829b70 Manually short circuit in lb_build_if_stmt for constant conditions 2021-07-26 10:59:07 +01:00
gingerBill 3ac674cf02 Fix typo 2021-07-25 13:12:09 +01:00
gingerBill 6cd06ab95f Minor fix 2021-07-25 13:07:31 +01:00
gingerBill 99080d41f3 INTERNAL USE ONLY: //+lazy build flag 2021-07-25 13:06:09 +01:00
gingerBill 92f3567ee6 Default to using a threaded checker on Windows; Add -no-threaded-checker for Windows 2021-07-24 18:00:19 +01:00
Jeroen van Rijn 481fc8a5b6 Merge pull request #1060 from Kelimion/win_sys
Allow `core:sys/windows` to build on Windows only
2021-07-15 12:15:10 +02:00
Jeroen van Rijn 5dfff51a40 Allow core:sys/windows to build on Windows only 2021-07-15 12:13:57 +02:00
gingerBill 2938ec028f Remove dead code 2021-07-15 00:37:59 +01:00
gingerBill 981b9fb7a8 Remove need for scope mutex, make Scope singly linked list tree with atomic pointers 2021-07-14 23:49:35 +01:00
gingerBill 10f4d8df32 Override libtommath allocation procedures 2021-07-14 23:36:23 +01:00
gingerBill e15858e2be Remove random load balancing in thread_proc_body 2021-07-14 22:53:02 +01:00
gingerBill 6d8302825c Add Greed Work Stealing and Random Load Balancing for check_procedure_bodies 2021-07-14 01:13:39 +01:00
gingerBill fae8bf96dd Muilthread check_collect_entities_all 2021-07-14 00:46:03 +01:00
gingerBill bc59dc6389 Remove duplicate code 2021-07-14 00:36:48 +01:00
gingerBill bd8e2f82be Replace non-recursive mutexes with BlockingMutex; Minor improves to initialization improves 2021-07-14 00:34:34 +01:00
gingerBill 69027b6840 Remove dead mutexes 2021-07-13 23:10:12 +01:00
gingerBill 7a9b7af078 Reduce mutex usage and convert things to queues from arrays 2021-07-13 23:09:24 +01:00
gingerBill 31c7afce1b Minor code clean up to reuse the ProcInfo consumption code 2021-07-13 21:19:13 +01:00
gingerBill bab1873416 Require throughput pass results with -show-more-timings -show-debug-messages rather than just the former flag 2021-07-13 18:39:11 +01:00
gingerBill 5e2950e9fb Move asserts around 2021-07-13 18:35:23 +01:00
gingerBill 74c019f271 Correct lb_big_int_to_llvm 2021-07-13 18:32:53 +01:00
gingerBill a745bb8f42 Add extra message to assert 2021-07-13 18:21:53 +01:00
gingerBill da9870c77d Do manual byte swapping for endianness in lb_big_int_to_llvm 2021-07-13 18:15:47 +01:00
gingerBill 698eeaf7c3 Add (internal flag) -show-debug-messages 2021-07-13 17:40:06 +01:00
gingerBill bd954d9990 Minor code reorganization 2021-07-13 17:28:39 +01:00
gingerBill 4ded42a33b Split up cycle check and adding type info timings 2021-07-13 17:24:20 +01:00
gingerBill 1877965ac3 Short on -threaded-checker 2021-07-13 17:04:08 +01:00
gingerBill cec2309504 Big improvement to the -threaded-checker code, unifying the logic and simplify behaviour 2021-07-13 16:58:40 +01:00
gingerBill ed5a4afc8c Temporarily disable -threaded-checker; Restructure the untyped-expr-info system to be much more thread-friendly 2021-07-13 15:54:56 +01:00
gingerBill f29b51efdd Fix gb_shuffle 2021-07-13 13:09:55 +01:00
gingerBill 3930a32b0c enum Addressing_Mode : u8; 2021-07-12 16:45:54 +01:00
gingerBill 76707e1d2f Add sanity casts for 32/64 bit correctness 2021-07-12 11:03:12 +01:00
gingerBill ff2e5c3efe Simplify big_int_not for negative inputs 2021-07-11 17:43:56 +01:00
gingerBill 3600b2e209 Merge pull request #1057 from odin-lang/new-big-int-library-integration
New Big Int Library Integration
2021-07-11 17:20:57 +01:00
gingerBill eb36a0f3b1 Remove dead file 2021-07-11 16:35:04 +01:00
gingerBill b397254696 Rename libtommath.c to libtommath.cpp 2021-07-11 16:33:58 +01:00
gingerBill 9a37d3b6e5 Add -Wno-unused-value 2021-07-11 16:29:51 +01:00
gingerBill 51c4a19234 Fix tab and space issue in Makefile
FUCK DO I HATE Makefile whitespace sensitivity between spaces and tabs
2021-07-11 16:26:21 +01:00
gingerBill a1a1668dcf Update build.bat and Makefile 2021-07-11 16:23:25 +01:00
gingerBill e308098f18 Add libtommath.c 2021-07-11 16:19:20 +01:00
gingerBill 63b572a0ab Clean up big int to LLVM integer code 2021-07-11 16:18:30 +01:00
gingerBill e90e7d4af9 Change mp_clear calls to big_int_dealloc 2021-07-11 16:10:04 +01:00
gingerBill 460e14e586 Change the compiler's big integer library to use libTomMath
This now replaces Bill's crappy big int implementation
2021-07-11 16:08:16 +01:00
gingerBill ebcabb8a27 Add sanity conversion check for integer to quaternion 2021-07-11 00:51:56 +01:00
gingerBill 257b749e9d Minimize mutex usage in update_expr_type 2021-07-11 00:21:31 +01:00
gingerBill d9e6ade030 Add experimental support for a threaded semantic checker to -threaded-checker 2021-07-10 23:51:37 +01:00
gingerBill 690374d4de Fix typo 2021-07-10 23:07:42 +01:00
gingerBill adb25d9d19 Convert constant tag to the correct type for LLVMAddCase 2021-07-10 22:29:52 +01:00
gingerBill 2949e4b0c7 Fix floattidf typo 2021-07-10 22:23:22 +01:00
gingerBill 6de0181c75 Minor improvements to Map and StringMap 2021-07-10 21:51:39 +01:00
gingerBill 8a6b743d2a Simplify thread_pool_wait_to_process 2021-07-10 21:50:19 +01:00
gingerBill ed8a6f872d Move things around for sanity checking for multithread preparation 2021-07-10 21:29:49 +01:00
gingerBill 0a61d4bf2b Use next_pow2_isize 2021-07-10 19:57:54 +01:00
gingerBill 332461c0d2 Add prototypes for next_pow2 2021-07-10 19:52:26 +01:00
gingerBill d8abe7fc4d Implement MPMCQueue for procedure body checking
This is preparation for basic multithreading in the semantic checker
2021-07-10 19:50:34 +01:00
gingerBill ec9667ef5a Remove debug code 2021-07-10 17:11:54 +01:00
gingerBill 9f7154a039 Prepare for multithreading the semantic checker by giving mutexes to variables of contention
NOTE(bill): I know this is dodgy, but I want to make sure it is correct logic before improve those data structures
2021-07-10 15:14:25 +01:00
gingerBill 4a932616fc Improve CheckerContext usage 2021-07-10 13:02:13 +01:00
gingerBill 73fe36f19c Remove dead variable 2021-07-10 11:59:41 +01:00
gingerBill 4167ae95ae Fix #1050 2021-07-10 11:48:28 +01:00
gingerBill 13c3c5be95 Fix #1028 2021-07-10 11:46:22 +01:00
gingerBill 3afec0bcbe Fix #1054 2021-07-10 11:42:21 +01:00
gingerBill 8b1bfc80fb Fix #1051 2021-07-10 11:27:38 +01:00
gingerBill 3662275119 Allow x in ptr_to_map_or_bit_set 2021-07-10 11:18:19 +01:00
gingerBill 141573c18c Enable Damerau-Levenshtein 2021-07-10 11:09:24 +01:00
gingerBill e692efbe09 Improve update expr type semantics for ternary expressions 2021-07-10 11:08:51 +01:00
gingerBill f6c1a5bf6e Merge branch 'master' of https://github.com/odin-lang/Odin 2021-07-09 15:33:30 +01:00
gingerBill 6afc28f827 Use builtin.min and builtin.max in package slice 2021-07-09 15:33:25 +01:00
gingerBill df6681ad4e Merge pull request #1055 from streof/fix-typos-container-package
Fix typos container package
2021-07-09 13:27:03 +01:00
streof 114efbc57c Fix error: Cannot assign to a procedure parameter 2021-07-09 14:03:49 +02:00
streof 2c71494ad1 Fix error: Undeclared name: p 2021-07-09 13:54:27 +02:00
gingerBill 35230b1a11 Add "Suggestion: Did you mean?" for selector expression typos 2021-07-08 23:15:07 +01:00
gingerBill 7acbf8b7b9 Add slice.min and add slice.max 2021-07-08 11:23:07 +01:00
gingerBill f7413ca974 Fix thread_unix.odin 2021-07-05 16:36:07 +01:00
gingerBill 9b3a0251ca Use or_else in the core library when it makes sense 2021-07-05 16:33:01 +01:00
gingerBill 3b9ca8535f Fix comments 2021-07-05 16:26:11 +01:00
gingerBill a98eee145d Remove try; Replace try x else y with or_else(x, y) 2021-07-05 16:23:13 +01:00
gingerBill c6b9b3b9a4 Fix try parsing for expression statements 2021-07-04 22:52:12 +01:00
gingerBill a4be1a5e4c delete_key now returns the deleted key and deleted value (if found) 2021-07-04 18:52:47 +01:00
gingerBill ee908c00de Add documentation for the overview of package fmt 2021-07-04 18:21:41 +01:00
gingerBill 46264032aa Improve error messages for try expressions 2021-07-04 12:50:44 +01:00
gingerBill 4b831dbddd Try try and or_else built-in procedures with operators try and try else 2021-07-04 12:37:21 +01:00
gingerBill a01d6dcea7 Refactor return logic to be more reusable with lb_emit_try 2021-07-04 01:57:38 +01:00
gingerBill 01a15f78e6 Simplify lb_emit_try return logic 2021-07-04 01:50:37 +01:00
gingerBill 5f71c41582 Fix lb_emit_try 2021-07-04 01:47:43 +01:00
gingerBill e8f2c5a48a [Experimental] Add 'try' and `or_else' built-in procedures 2021-07-04 01:38:43 +01:00
gingerBill 1c76577918 Add slice.sort_by_cmp Ordering based sorting algorithms 2021-07-03 15:16:44 +01:00
gingerBill 4285b58aaa Add #no_bounds_check to linalg procedures 2021-07-03 14:38:41 +01:00
gingerBill 9cc366de97 Be more consistent with runtime intrinsics usage 2021-07-03 14:38:15 +01:00
gingerBill 212d294b84 Deprecate sort.slice and sort.reverse_slice 2021-07-03 14:37:55 +01:00
gingerBill d6125f05d4 Correct does_field_type_allow_using 2021-06-29 12:35:15 +01:00
gingerBill ad22eda87c Improve linalg.transpose type determination for square matrices 2021-06-28 13:23:28 +01:00
gingerBill 8d31ba492d Fix #1042 2021-06-28 11:32:23 +01:00
gingerBill 8f611b3399 Fix #1043 2021-06-28 11:21:03 +01:00
gingerBill 618f858930 Merge branch 'master' of https://github.com/odin-lang/Odin 2021-06-28 11:06:00 +01:00
gingerBill 185277a2b6 Fix swizzling of pointers to arrays 2021-06-28 11:05:52 +01:00
Jeroen van Rijn 8e5c3141f6 Merge pull request #1047 from Kelimion/zlib_optimize
ZLIB: Fix edge case where initial buffer < 258 bytes.
2021-06-27 18:49:09 +02:00
Jeroen van Rijn 87aaa9c3f0 ZLIB: Fix edge case where initial buffer < 258 bytes. 2021-06-27 18:44:36 +02:00
Jeroen van Rijn 095605b7db Merge pull request #1046 from Kelimion/zlib_optimize
ZLIB: Optimize
2021-06-27 16:54:15 +02:00
Jeroen van Rijn 6836b501af Merge branch 'master' into zlib_optimize 2021-06-27 16:50:27 +02:00
Jeroen van Rijn d949d5a046 ZLIB: cleanup. 2021-06-27 16:48:12 +02:00
Jeroen van Rijn 064516bf0b PNG: Inform inflate about expected output size for extra speed. 2021-06-27 13:57:12 +02:00
Jeroen van Rijn eaf88bcc4d PNG: Let PNG use the new compress I/O routines. 2021-06-27 13:51:52 +02:00
gingerBill 76d3bab955 Modify scope finding rules for distinct types in lb_debug_type 2021-06-27 12:21:11 +01:00
Jeroen van Rijn 02f9668185 ZLIB: Split up input from stream and memory into own code paths. 2021-06-27 13:19:24 +02:00
gingerBill abda75feee Add bufio.Lookahead_Reader 2021-06-26 23:45:45 +01:00
gingerBill a779cb2798 Fix #1044 2021-06-26 23:31:04 +01:00
gingerBill 5e42675b42 Allow alternative syntax for offset_of: offset_of(Type, field), offset_of(value.field) 2021-06-26 23:19:46 +01:00
gingerBill 11c565e199 Fix semicolon insertion rule for --- 2021-06-26 22:47:12 +01:00
Jeroen van Rijn 4689a6b341 Refactor compress.Context struct. 2021-06-26 22:25:55 +02:00
Jeroen van Rijn 30a5808460 ZLIB: Moar faster. 2021-06-26 20:40:39 +02:00
Jeroen van Rijn 8ba1c9a6cd ZLIB: Remove superfluous code. 2021-06-26 17:44:12 +02:00
Jeroen van Rijn 65b78b1aa9 So far, so good. 2021-06-26 15:11:15 +02:00
Jeroen van Rijn c369719362 Merge branch 'master' into zlib_optimize 2021-06-26 13:40:22 +02:00
Jeroen van Rijn 40a12cca53 ZLIB: If output size is known, reserve that much. 2021-06-26 13:17:14 +02:00
gingerBill d8940f5fd7 Support compound literals for struct #raw_union types 2021-06-26 12:08:14 +01:00
gingerBill 74dee82dbf Fix compiler errors 2021-06-25 14:33:42 +01:00
gingerBill f1cf724bd4 Add bufio.Scanner 2021-06-25 14:31:35 +01:00
gingerBill fc809d3fad Merge branch 'master' of https://github.com/odin-lang/Odin 2021-06-24 19:41:36 +01:00
gingerBill 42d135aade Change logic for comparison against nil for array-like data types (compare the pointer rather than the length/capacity) 2021-06-24 19:39:51 +01:00
Jeroen van Rijn ab12ca69af Merge pull request #1040 from Kelimion/zlib_optimize
Adler32 speedup.
2021-06-24 19:15:22 +02:00
Jeroen van Rijn 17748f18b9 Adler32 speedup. 2021-06-24 19:06:39 +02:00
gingerBill 3803bdff5f Allow bufio.Reader and bufio.Writer to have a configurable max_consecutive_empty_(reads|writes) field 2021-06-24 15:56:58 +01:00
gingerBill 0a94a67190 Merge branch 'master' of https://github.com/odin-lang/Odin 2021-06-24 15:55:50 +01:00
gingerBill 8dcb14fbc2 Add helper procedures for strings.Reader creation 2021-06-24 15:55:42 +01:00
Jeroen van Rijn f62f40e508 Merge pull request #1039 from Kelimion/zlib_optimize
ZLIB: Another 10%+ faster.
2021-06-24 15:02:02 +02:00
Jeroen van Rijn 980aa37bee ZLIB: Another 10%+ faster. 2021-06-24 14:56:28 +02:00
gingerBill 1e9cc058a0 Update hash.crc32 to use slicing-by-8 algorithm to improve throughput by ~3.5x 2021-06-24 00:03:59 +01:00
Jeroen van Rijn 824efc82b9 Merge pull request #1036 from Kelimion/zlib_optimize
ZLIB: More faster.
2021-06-23 22:25:02 +02:00
Jeroen van Rijn 1cfe226686 ZLIB: More faster. 2021-06-23 22:18:17 +02:00
Jeroen van Rijn ea0ce7bd2c Merge pull request #1035 from Kelimion/zlib_optimize
Zlib optimize
2021-06-23 20:15:32 +02:00
Jeroen van Rijn 342adb627d All reads now skip stream if in memory. 2021-06-23 16:32:48 +02:00
Jeroen van Rijn a70635d2f6 Most reads now go through buffer for zlib. 2021-06-23 16:32:48 +02:00
Jeroen van Rijn 5cb16c4cd1 All reads now go through read_slice. 2021-06-23 16:32:48 +02:00
Jeroen van Rijn 538004ba5f Introduce read_slice, make read_u8 use it. 2021-06-23 16:32:48 +02:00
Jeroen van Rijn 8663c64e47 Refactor ZLIB structs. 2021-06-23 16:32:47 +02:00
gingerBill bb3ffdbdfb Merge branch 'master' of https://github.com/odin-lang/Odin 2021-06-23 14:55:53 +01:00
gingerBill fcf7cf973b Add documentation for -verbose-errors 2021-06-23 14:55:40 +01:00
Jeroen van Rijn fb2e1c32bd Merge pull request #1034 from Kelimion/bytes_util
Move `bytes` utils back to EXR code for the time being.
2021-06-22 16:48:07 +02:00
Jeroen van Rijn ae0b8fce44 Move bytes utils back to EXR code for the time being.
Also, allow PNG example to be run directly from `core:image/png` directory.
2021-06-22 16:39:00 +02:00
Jeroen van Rijn d2e55f9ffa Merge pull request #1032 from Kelimion/png_fix
PNG: Fix leak if you don't ask for metadata.
2021-06-21 22:56:39 +02:00
Jeroen van Rijn d5e2b387fa PNG: Fix leak if you don't ask for metadata. 2021-06-21 22:47:54 +02:00
Jeroen van Rijn 922b511a24 Merge pull request #1031 from Kelimion/zlib_fix
ZLIB: fix.
2021-06-21 22:16:56 +02:00
Jeroen van Rijn 9de9111082 ZLIB: fix. 2021-06-21 22:15:04 +02:00
Jeroen van Rijn 5a7fe2e3d9 Merge pull request #1030 from Kelimion/zlib_optimize
Comment out tracy imports.
2021-06-21 21:43:57 +02:00
Jeroen van Rijn 1e8c12c2a3 Comment out tracy imports. 2021-06-21 21:41:56 +02:00
Jeroen van Rijn b92c70e55c Merge pull request #1029 from Kelimion/zlib_optimize
ZLIB: Start optimization.
2021-06-21 21:26:52 +02:00
Jeroen van Rijn 352494cbb4 ZLIB: Start optimization. 2021-06-21 21:05:52 +02:00
Jeroen van Rijn 797c41950a Merge pull request #1027 from Kelimion/png
Replace `core:image`'s `sidecar` with explicit pointer and type
2021-06-21 16:41:38 +02:00
Jeroen van Rijn e036a321a0 Replace core:image's sidecar with explicit metadata_ptr and metadata_type.
To unpack, use:
```odin

v: ^png.Info;

if img.metadata_ptr != nil && img.metadata_type == png.Info {
	v = (^png.Info)(img.metadata_ptr);
	...
}
```
2021-06-21 16:32:42 +02:00
Jeroen van Rijn 18471f358e Merge pull request #1025 from Kelimion/png_info
Change PNG's img.sidecar to ^Info, make img.depth an int.
2021-06-20 18:40:46 +02:00
Jeroen van Rijn 55d09251d8 Change PNG's img.sidecar to ^Info, make img.depth an int.
For compatibility with the upcoming OpenEXR code, img.depth is now an int.
Like OpenEXR's code, it will now also return metadata as ^Info instead of Info.

The example was updated to retrieve the metadata this way.

It regrettably does not fix: #1018. That seems to be a codegen issue in the test runner or elsewhere.
2021-06-20 18:27:23 +02:00
Jeroen van Rijn d66fd71d21 Merge pull request #1024 from Kelimion/defer_fix
GZIP defer diverging fix in gzip example.
2021-06-20 17:40:15 +02:00
Jeroen van Rijn f10fc2a494 Merge pull request #1023 from Kelimion/test_runner_fix
Fix Windows test runner.
2021-06-20 17:37:04 +02:00
Jeroen van Rijn 055d8c5370 Fix Windows test runner. 2021-06-20 17:33:39 +02:00
Jeroen van Rijn 955472bd21 GZIP defer diverging fix in gzip example. 2021-06-20 17:21:18 +02:00
gingerBill a2d5f660ed Merge pull request #1022 from Kelimion/buffer_convert
Add `bytes.buffer_create_of_type` and `bytes.buffer_convert_to_type`.
2021-06-18 16:41:48 +01:00
Jeroen van Rijn 8a4b9ddaa3 Fix comment. 2021-06-18 15:42:04 +02:00
Jeroen van Rijn 54a2b6f00e Add bytes.buffer_create_of_type and bytes.buffer_convert_to_type.
Convenience functions to reinterpret or cast one buffer to another type, or create a buffer of a specific type.

	Example:
```odin
	fmt.println("Convert []f16le (x2) to []f32 (x2).");
	b := []u8{0, 60, 0, 60}; // == []f16{1.0, 1.0}

	res, backing, had_to_allocate, err := bytes.buffer_convert_to_type(2, f32, f16le, b);
	fmt.printf("res      : %v\n", res);              // [1.000, 1.000]
	fmt.printf("backing  : %v\n", backing);          // &Buffer{buf = [0, 0, 128, 63, 0, 0, 128, 63], off = 0, last_read = Invalid}
	fmt.printf("allocated: %v\n", had_to_allocate);  // true
	fmt.printf("err      : %v\n", err);              // false

	if had_to_allocate { defer bytes.buffer_destroy(backing); }

	fmt.println("\nConvert []f16le (x2) to []u16 (x2).");

	res2: []u16;
	res2, backing, had_to_allocate, err = bytes.buffer_convert_to_type(2, u16, f16le, b);
	fmt.printf("res      : %v\n", res2);             // [15360, 15360]
	fmt.printf("backing  : %v\n", backing);          // Buffer.buf points to `b` because it could be converted in-place.
	fmt.printf("allocated: %v\n", had_to_allocate);  // false
	fmt.printf("err      : %v\n", err);              // false

	if had_to_allocate { defer bytes.buffer_destroy(backing); }

	fmt.println("\nConvert []f16le (x2) to []u16 (x2), force_convert=true.");

	res2, backing, had_to_allocate, err = bytes.buffer_convert_to_type(2, u16, f16le, b, true);
	fmt.printf("res      : %v\n", res2);             // [1, 1]
	fmt.printf("backing  : %v\n", backing);          // Buffer.buf points to `b` because it could be converted in-place.
	fmt.printf("allocated: %v\n", had_to_allocate);  // false
	fmt.printf("err      : %v\n", err);              // false

	if had_to_allocate { defer bytes.buffer_destroy(backing); }
```
2021-06-18 15:25:36 +02:00
gingerBill abe728dbbb Add intrinsics.type_is_endian_platform 2021-06-17 20:39:00 +01:00
gingerBill 574ceb37a9 Correct selector call expression chaining behaviour (a bit of a hack) 2021-06-16 17:04:05 +01:00
gingerBill dbdc4471c2 Fix double evaluation bug with selector call expressions x->y(z) 2021-06-16 14:03:12 +01:00
gingerBill af95381bf8 Add missing -> ! annotation 2021-06-16 12:12:24 +01:00
gingerBill 41f2539484 Improve logic for diverging procedures by checking if it terminates 2021-06-16 12:07:24 +01:00
gingerBill 8f57bb0799 Add unreachable detection for deferred statements in a scope which contains a diverging procedure call
```odin
{
    defer foo(); // Unreachable defer statement due to diverging procedure call at the end of the current scope
    os.exit(0);
}
```
2021-06-16 11:41:29 +01:00
gingerBill 84a4188c72 Fix #1017 2021-06-15 01:16:19 +01:00
gingerBill 31f1e0aeae Fix #1019 2021-06-15 01:13:16 +01:00
gingerBill 4b8cbb5a3b Fix #1015 2021-06-14 13:26:28 +01:00
gingerBill 3e7aabe6d8 Change uses for parapoly records to use $ always 2021-06-14 11:43:35 +01:00
gingerBill d4df325e0a Just create context when required 2021-06-14 11:41:50 +01:00
gingerBill 9f8a63cb43 More minor stylization changes (remove unneeded parentheses) 2021-06-14 11:34:31 +01:00
gingerBill 6f745677b4 Minor formatting changes 2021-06-14 11:30:00 +01:00
gingerBill 86649e6b44 Core library clean up: Make range expressions more consistent and replace uses of .. with ..= 2021-06-14 11:15:25 +01:00
gingerBill 3ca887a60a Add struct_fields_zipped and enum_fields_zipped (allowing for iteration through an #soa slice) 2021-06-14 11:04:51 +01:00
gingerBill 312a1e8a94 Fix context logic 2021-06-13 16:00:20 +01:00
gingerBill 9a311ab9e7 Remove dead code 2021-06-12 16:47:20 +01:00
gingerBill 7d92eaaeb2 Correct context logic in lb_build_addr 2021-06-12 16:45:44 +01:00
gingerBill 582f423b67 Improve vector arithmetic generation for array programming operations 2021-06-12 16:37:20 +01:00
gingerBill c2524464f9 Fix remove_temp_files 2021-06-12 16:23:41 +01:00
gingerBill 55e472cdb6 Fix linkage problem for procedures required by LLVM 2021-06-10 12:23:08 +01:00
gingerBill e6ad773a88 Minor code clean up 2021-06-09 23:47:44 +01:00
gingerBill 82eae32bca Improve code generation for type switch statements to use a jump table by default 2021-06-09 23:46:00 +01:00
gingerBill b0e21bd616 Allow trivial optimizations for switch statements of typeid 2021-06-09 23:05:37 +01:00
gingerBill 7b88bed098 Do trivial SwitchInstr optimization for constant case switch statements 2021-06-09 22:55:08 +01:00
gingerBill 28abf5d33b Correct minimum dependency for complex32 2021-06-08 21:20:33 +01:00
gingerBill fb8ad338d0 Keep -vet happy 2021-06-08 18:26:38 +01:00
gingerBill ee60be0137 Improve code generation hints for return statements which return by pointer 2021-06-08 17:17:53 +01:00
gingerBill 9efd4c5097 Aid code generation on non-release builds 2021-06-08 17:17:24 +01:00
gingerBill f30e6f50bd Reorganize code to improve code generation 2021-06-08 16:21:19 +01:00
gingerBill 8ec2ca9dcd Remove context.thread_id 2021-06-08 15:57:00 +01:00
gingerBill f19bb0f4d4 Make default calling convention code more correct to read 2021-06-08 14:33:49 +01:00
gingerBill 76bb82a726 Minor alignment cleanup for swizzle load 2021-06-08 14:27:36 +01:00
gingerBill 8e62f9c83c Correct is_operand_value for Swizzle addressing modes 2021-06-08 14:23:44 +01:00
gingerBill 696f758435 Fix and improve swizzle loads for ordered indices 2021-06-08 14:19:27 +01:00
gingerBill 6421152104 Fix show-timings header for -lld on windows 2021-06-08 13:19:19 +01:00
gingerBill 1e989f5c10 Fix -lld on Windows 2021-06-08 13:10:22 +01:00
gingerBill 3eb42ecb55 Minor improvements to -use-separate-modules 2021-06-08 13:00:20 +01:00
gingerBill 286cb60c45 Minor changes to tools/odinfmt 2021-06-08 12:18:55 +01:00
gingerBill 28e9a4f79c Replace js_wasm32 with freestanding_wasm32 2021-06-08 12:18:26 +01:00
gingerBill e79fb68291 Correct #soa type creation 2021-06-08 11:23:23 +01:00
gingerBill 16eaa17ed9 Fix -target:js_wasm32 for core:runtime 2021-06-08 11:20:39 +01:00
gingerBill 9491c13a5c Fix #1011 by unifying the logic 2021-06-08 11:09:41 +01:00
gingerBill 8d8adac1b4 Fix lb_build_defer_stmt 2021-06-08 10:43:11 +01:00
gingerBill ba6c63e366 Fix full_path_from_name allocator behaviour 2021-06-08 10:14:35 +01:00
gingerBill 963b1a12d7 Correct code for #simd in unions 2021-06-06 16:56:03 +01:00
gingerBill 89890d7900 Correct union tag size for large alignments 2021-06-06 16:34:51 +01:00
gingerBill 661fcad895 Add examples/all which imports every package
This is useful for knowing what exists and producing documentation with `odin doc`
2021-06-06 13:05:54 +01:00
gingerBill cef16feb0b Fix #861 - Add extra check on missing main 2021-06-06 12:46:59 +01:00
gingerBill 84b851f578 Add warning to variables which may overflow the stack on declaration; #Fix 661 2021-06-06 12:42:39 +01:00
gingerBill 785c27daa7 Fix 128-bit integer to float cast by explicitly calling the procedure direct; Fix #781 2021-06-06 12:35:38 +01:00
gingerBill 795a5910cf Add support for Addressing_OptionalOkPtr
Allowing for `i, ok := &x.(T);` (type assertions) and `v, ok := &m[k];` (map indexing)
2021-06-06 12:18:45 +01:00
gingerBill 4c21f9495d Clean up lbAddr_Swizzle logic for load and store 2021-06-05 23:56:59 +01:00
gingerBill f119fd1ee1 Use shufflevector when possible for lbAddr_Swizzle load 2021-06-05 22:50:23 +01:00
gingerBill 46ab822316 Improved lb_build_assign_stmt_array logic 2021-06-05 22:07:39 +01:00
gingerBill 104aea9f42 Improve error message for addressing a swizzle intermediate array value 2021-06-05 18:25:51 +01:00
gingerBill a2f2041aa6 Fix lb_build_assign_stmt_array for lbAddr_Swizzle 2021-06-05 17:33:42 +01:00
gingerBill 599d18f26f Experimental support for inline swizzling for array types of len <= 4 e.g. v.xyz, v.argb, v.xxx 2021-06-05 17:22:39 +01:00
gingerBill 61084d832d Add missing doc_format flags for entities and improve docs for the odin package 2021-06-05 15:55:19 +01:00
gingerBill b957996577 Add extra documentation to doc_format.odin 2021-06-05 15:26:05 +01:00
gingerBill f41150f8e9 Fix transposing 2021-06-04 15:10:53 +01:00
gingerBill 21adad4e09 Fix typo 2021-06-04 15:09:55 +01:00
gingerBill 47f9e8f850 Fix ast_end_token for [?]T types 2021-06-03 11:35:27 +01:00
gingerBill ba3f2a6a0c All spaces in import-like paths 2021-06-03 10:28:45 +01:00
gingerBill b9888f8f68 Fix linalg.transpose 2021-06-03 10:05:05 +01:00
gingerBill 32cda5d56a Or did it?! 2021-06-02 22:12:38 +01:00
gingerBill a4d9847f45 FINALLY fix lazy_buffer_destroy 2021-06-02 22:12:20 +01:00
gingerBill 8aa6d70dec Fix filepath.lazy_buffer 2021-06-02 12:21:20 +01:00
gingerBill ea6b222430 Clean up filepath.lazy_buffer memory leak 2021-06-02 12:19:25 +01:00
gingerBill 91b4bf3daa Minor clean up 2021-06-02 10:39:47 +01:00
519 changed files with 90483 additions and 26419 deletions
+24 -15
View File
@@ -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
+3
View File
@@ -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
+9 -5
View File
@@ -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
+7 -7
View File
@@ -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
+4 -6
View File
@@ -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
+1 -1
View File
@@ -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')
+83
View File
@@ -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);
}
+8 -2
View File
@@ -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;
+340
View File
@@ -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;
}
+9 -5
View File
@@ -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;
+1 -1
View File
@@ -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;
}
+4 -4
View File
@@ -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;
+4 -4
View File
@@ -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;
+4 -4
View File
@@ -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);
}
+73
View File
@@ -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>.
```
+82
View File
@@ -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);
}
+30
View File
@@ -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 ---;
}
+47
View File
@@ -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();
}
+400
View File
@@ -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};
+65
View File
@@ -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, };
+43
View File
@@ -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;
}
+43
View File
@@ -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.
+416
View File
@@ -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);
}
+135
View File
@@ -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) ---;
}
+109
View File
@@ -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 ---;
}
+43
View File
@@ -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 ---;
}
+50
View File
@@ -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();
}
+138
View File
@@ -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 ---;
}
}
+81
View File
@@ -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,
}
}
+82
View File
@@ -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;
+21
View File
@@ -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;
+108
View File
@@ -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);
+48
View File
@@ -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
View File
@@ -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};
+28 -9
View File
@@ -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
View File
@@ -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};
+13 -3
View File
@@ -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
View File
@@ -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};
+1 -1
View File
@@ -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,
+3 -3
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
package container
Queue :: struct(T: typeid) {
Queue :: struct($T: typeid) {
data: Array(T),
len: int,
offset: int,
+3 -2
View File
@@ -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 -1
View File
@@ -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,
}
-852
View File
@@ -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;
}
-51
View File
@@ -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
-523
View File
@@ -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};
}
+10 -31
View File
@@ -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);
}
+83
View File
@@ -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
+193
View File
@@ -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);
}
+215
View File
@@ -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;
}
+193
View File
@@ -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);
}
}
}
+61 -60
View File
@@ -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;
+22 -54
View File
@@ -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;
+15 -8
View File
@@ -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;
+9 -19
View File
@@ -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);
+141
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+401
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+14 -5
View File
@@ -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
View File
@@ -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};
+1
View File
@@ -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
View File
@@ -1,6 +1,6 @@
package io
import "intrinsics"
import "core:intrinsics"
import "core:runtime"
import "core:unicode/utf8"
+18
View File
@@ -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);
+152
View File
@@ -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,
};
+9
View File
@@ -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%
+218
View File
@@ -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,
};
+232
View File
@@ -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);
}
}
}
+800
View File
@@ -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
+144
View File
@@ -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, };
+256
View File
@@ -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
+573
View File
@@ -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);
}
+480
View File
@@ -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;
}
+390
View File
@@ -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};
}
+707
View File
@@ -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)
+81
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
package math_bits
import "intrinsics"
import "core:intrinsics"
U8_MIN :: 0;
U16_MIN :: 0;
+2 -2
View File
@@ -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 -1
View File
@@ -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)) {
+11 -17
View File
@@ -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]);
+6 -6
View File
@@ -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
View File
@@ -1,6 +1,6 @@
package math
import "intrinsics"
import "core:intrinsics"
_ :: intrinsics;
Float_Class :: enum {
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+11
View File
@@ -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
View File
@@ -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,
+8 -5
View File
@@ -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);
+7 -4
View File
@@ -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);
+86 -23
View File
@@ -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
View File
@@ -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;
+7 -6
View File
@@ -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);
+12 -6
View File
@@ -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