Compare commits

..

498 Commits

Author SHA1 Message Date
Christoffer Lerno
83e5a0c2ab Fix CI 2025-03-30 12:51:15 +02:00
Christoffer Lerno
c058c50aef Add releasenotes to zip. 2025-03-30 12:34:04 +02:00
walther chen
46d3e3dc97 readme example typo 2025-03-29 23:08:20 +01:00
Christoffer Lerno
40ff6b1315 Release candidate 0.7.0 2025-03-29 22:36:28 +01:00
Christoffer Lerno
8453270921 Added Wumpus 2025-03-29 00:25:20 +01:00
Christoffer Lerno
6739de3a10 Re-enable everything and use the new release flow. 2025-03-28 11:18:40 +01:00
Christoffer Lerno
010a77816b Test new CI 2025-03-28 01:26:57 +01:00
Christoffer Lerno
61113a8471 Update name 2025-03-28 01:12:13 +01:00
Christoffer Lerno
7b0cc85b2c Update github token 2025-03-28 00:51:43 +01:00
Christoffer Lerno
ea5fec80b0 Try deleting old releases. 2025-03-28 00:37:39 +01:00
Christoffer Lerno
9db316ddac Restore the checkout. 2025-03-27 22:41:28 +01:00
Christoffer Lerno
cb164e2ca2 Remove what seems to be useless tags. 2025-03-27 20:35:54 +01:00
Christoffer Lerno
83ff1da80c Update latest name 2025-03-27 15:12:21 +01:00
Christoffer Lerno
d626dea52a Update timestamp name 2025-03-27 00:04:56 +01:00
Christoffer Lerno
5d026268a7 "Single module" was not enforced when creating a static library using as a project target. 2025-03-27 00:02:51 +01:00
Christoffer Lerno
2ab318a178 Generate tag name update 2025-03-26 21:30:12 +01:00
Christoffer Lerno
638d5332ff Generate tag name 2025-03-26 18:31:08 +01:00
Christoffer Lerno
5c46b0c2a0 Add @each_row for the csv module. 2025-03-26 18:19:19 +01:00
Christoffer Lerno
4538a1f50d - Correctly errors when a generic module contains a self-generic type. 2025-03-26 18:04:00 +01:00
Christoffer Lerno
e0f1919849 Rename latest build 2025-03-26 14:15:46 +01:00
Christoffer Lerno
df175dd48c Added some direction for stdlib contributions. 2025-03-26 11:59:28 +01:00
Christoffer Lerno
6e340f22af Add star history 2025-03-25 23:53:36 +01:00
Christoffer Lerno
ff2809a3ac Fix of regression in @assert_leak. 2025-03-25 22:11:47 +01:00
Maxime Beaudoin
a8554b4233 String.c3 function parameters ambiguous (#2061)
* Some tweaks. Fixes regression in `format`

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-03-25 14:25:23 +01:00
Christoffer Lerno
fa707db078 Do not implicitly convert enums to ordinal in formatting functions. 2025-03-25 12:27:51 +01:00
Christoffer Lerno
439349ceb8 Move dynamic dispatch lowering. 2025-03-25 00:20:01 +01:00
Christoffer Lerno
d760378b02 - Added @format attribute for compile time printf validation #2057.
- Bug when printing a boolean value as an integer using printf.
2025-03-24 13:32:44 +01:00
Christoffer Lerno
50d7919fec - Compile test and benchmark functions when invoking --lsp #2058. 2025-03-23 23:30:50 +01:00
Christoffer Lerno
f53f8bf423 Fix help message for asm-out 2025-03-23 23:19:31 +01:00
Christoffer Lerno
82f1b543ed &self not runtime null-checked in macro #1827. Regression in temp allocators. 2025-03-23 22:50:09 +01:00
walther chen
b48588ca8f improve error message for uninitialized temp allocator pool on new
threads
2025-03-23 22:15:42 +01:00
Christoffer Lerno
a03d821602 - Use @pool_init() to set up a temp pool on a thread. Only the main thread has implicit temp pool setup.
- `tmem` is now a variable.
2025-03-21 17:08:58 +01:00
[ Taha. Dostifam‍ ]
fab00f21a6 Added a progress bar to vendor-fetch in compiler section (#2055)
* added a progress bar to vendor-fetch of compiler section

* Handle ansi settings.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-03-20 21:53:02 +01:00
Christoffer Lerno
49033320e2 Stable path for unpacked libraries. 2025-03-20 20:51:36 +01:00
Christoffer Lerno
207bcfea02 os::exit and os::fastexit added. 2025-03-20 13:36:59 +01:00
Christoffer Lerno
d2c44717f1 Exclude 21 due to packaging errors of LLVM. 2025-03-20 10:09:48 +01:00
Christoffer Lerno
0beb30c979 Updated error message #2037 2025-03-19 23:25:56 +01:00
Christoffer Lerno
de74e97ab1 Fixes to temp allocator. 2025-03-18 19:58:16 +01:00
Christoffer Lerno
7e100472e7 - AnyList now also defaults to the temp allocator.
- `os::getcwd` and `os::get_home_dir` requires an explicit allocator.
- `file::load_new` and `file::load_path_new` removed.
2025-03-18 18:34:52 +01:00
Christoffer Lerno
cfc87a9d66 Update example. 2025-03-18 15:41:37 +01:00
Christoffer Lerno
84753bde6d - Allow inferred type on body parameters. E.g. @stack_mem(1024; alloc) { ... }; 2025-03-18 15:40:26 +01:00
Christoffer Lerno
72608ce01d - Temp allocator now supports more than 2 in-flight stacks.
- Printing stacktrace uses its own temp allocator.
- `@pool` no longer takes an argument.
- `Allocator` interface removes `mark` and `reset`.
- DynamicArenaAllocator has changed init function.
- Added `BackedArenaAllocator` which is allocated to a fixed size, then allocates on the backing allocator and supports mark/reset.
2025-03-18 15:16:22 +01:00
Christoffer Lerno
82cc49b388 - !!foo now works same as as ! ! foo.
- Incorrectly allowed getting pointer to a macro #2049.
2025-03-16 23:57:30 +01:00
Christoffer Lerno
425676a98d Bug due to missing cast when doing $i[$x] = $z.
Added `math::iota`.
2025-03-16 10:58:13 +01:00
Christoffer Lerno
5c77c9a754 - Change distinct -> typedef.
- Order of attribute declaration is changed for `alias`.
- Added `LANGUAGE_DEV_VERSION` env constant.
- Rename `anyfault` -> `fault`.
- Changed `fault` -> `faultdef`.
- Added `attrdef` instead of `alias` for attribute aliases.
2025-03-15 20:10:47 +01:00
Christoffer Lerno
fc5615a7a1 Restore def so that libraries work. 2025-03-13 11:51:21 +01:00
Christoffer Lerno
9707a9694f Temporarily turn off raylib 2025-03-13 11:46:38 +01:00
Christoffer Lerno
8b49e6c14d Rename def to alias. 2025-03-13 11:22:27 +01:00
Christoffer Lerno
ae76839347 Fix @return? parsing. 2025-03-13 11:03:19 +01:00
Christoffer Lerno
b8ae2b06d6 Rename @return! to @return? 2025-03-12 21:40:30 +01:00
Christoffer Lerno
c9dbd86d82 Crash resolving a method on Foo[2] when Foo is distinct #2042. 2025-03-12 15:45:43 +01:00
Christoffer Lerno
d5b211a786 Fault nameof prefixes the first last module path, for instance std::io::EOF is rendered as io::EOF. 2025-03-11 22:46:02 +01:00
Christoffer Lerno
f5d02cd0d2 Crash when trying to convert a struct slice to a vector #2039. 2025-03-11 21:18:19 +01:00
Christoffer Lerno
8c23c5028d Make @public import recursive. #2018
`import` can now both be @public and @norecurse.
2025-03-10 15:33:29 +01:00
Christoffer Lerno
461bd43a22 Fix Android 2 2025-03-10 11:04:43 +01:00
Christoffer Lerno
fbc8168bb9 Improve error message on foo ?? io::EOF with missing '?' #2036. Fix build on Android 2025-03-10 11:02:43 +01:00
Christoffer Lerno
ff75f2c21f Update naming of enum const. 2025-03-10 00:15:50 +01:00
Christoffer Lerno
25bccf4883 New faults and syntax (#2034)
- Remove `[?]` syntax.
- Change `int!` to `int?` syntax.
- New `fault` declarations.
- Enum associated values can reference the calling enum.
2025-03-10 00:11:35 +01:00
Boris Barbulovski
fefce25081 C3 Android logcat interface. (#2030)
* C3 Android logcat interface.
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-03-09 15:52:24 +01:00
Boris Barbulovski
f21cc02320 Initial Android support. (#2024)
* Initial Android support.

* Add Android x86_64

* - Typo api_verion
- Typo in DEFAULT_TARGETS
- Removed unnecessary snprintf

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-03-08 23:38:52 +01:00
BWindey
1461414128 [TEST] Add unit tests for libc (#1974)
Some tests for libc + fixes
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-03-08 22:25:21 +01:00
m0tholith
dc8b35e62f Added is_initalized function to list, linkedlist, and anylist types (#2027)
* Added `is_initalized` function to list, linkedlist, and anylist types
2025-03-08 21:49:17 +01:00
Christoffer Lerno
1dca90b89d Fix tests. 2025-03-07 23:54:01 +01:00
Christoffer Lerno
a088a5057a Remove if (catch foo) { case ... } syntax. 2025-03-07 22:48:28 +01:00
Christoffer Lerno
facaa75083 Remove use of if-catch-case 2025-03-07 13:57:05 +01:00
Christoffer Lerno
cce097fdef Disable mingw compilation. 2025-03-07 12:36:19 +01:00
Christoffer Lerno
5a6884b708 Set linker for mingw64 2025-03-07 12:34:00 +01:00
Christoffer Lerno
5898cad98d Fix typo giving the wrong mem on error, closes #2023. 2025-03-07 12:14:01 +01:00
Christoffer Lerno
5482107ca8 Some formatting and updated test. Updated releasenotes. 2025-03-07 00:14:41 +01:00
Christoffer Lerno
c790ecbca5 Update msys in CI 2025-03-07 00:04:03 +01:00
Stephen Molloy
1fba9a7993 String.to_float("+") fails (#2021)
* When stripping +/- from the start of a string in String.to_real: only do this if the string length is greater than 1.
* Added a test for the String.to_float("+") bug
2025-03-07 00:02:13 +01:00
Christoffer Lerno
ca87ff066b Fix hello world. 2025-03-05 19:28:12 +01:00
Christoffer Lerno
c0b80eccad Change @return! syntax to require ":" after faults. Update all contracts to consistently use ":" before the description. 2025-03-05 17:11:45 +01:00
Christoffer Lerno
cf0405930e Fix case where occasionally atomic_load would miscompile. 2025-03-05 01:58:07 +01:00
Christoffer Lerno
28b1e4d182 Fix issue when doing boolean atomic operations. 2025-03-04 16:58:27 +01:00
Christoffer Lerno
9c65098a91 Fix missing "$switch" update. 2025-03-04 16:39:20 +01:00
Christoffer Lerno
2439405e70 - $foreach "()" replaced by trailing ":" $foreach ($x, $y : $foo) -> $foreach $x, $y : $foo:
- `$for` "()" replaced by trailing ":" `$for (var $x = 0; $x < FOO; $x++)` -> `$for var $x = 0; $x < FOO; $x++:`
- `$switch` "()" replaced by trailing ":" `$switch ($Type)` -> `$switch $Type:`
- Empty `$switch` requires trailing ":" `$switch` -> `$switch:`
2025-03-04 16:13:47 +01:00
Jonas
46b52ec9ce Fix Atomic.max (#2011)
* Fix atomic max and comments in std::atomic
* Updates to `Atomic` to handle distinct types and booleans.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-03-04 15:39:03 +01:00
Nikita Pivkin
c6c78e7709 fix broken link to macOS installation section
Signed-off-by: Nikita Pivkin <nikita.pivkin@smartforce.io>
2025-03-04 14:03:14 +01:00
Zack Puhl
177df6321a add test file for wide string constants
add full UTF-8 to wide-str language capabilities
move wstr language constant expressions to builtins and adjust test cases
2025-03-04 13:46:59 +01:00
Christoffer Lerno
d2a461d270 Updated release notes. 2025-03-04 10:31:47 +01:00
Nikita Pivkin
7a848416f7 docs: update 'Building via Docker' section
Signed-off-by: Nikita Pivkin <nikita.pivkin@smartforce.io>
2025-03-04 10:30:32 +01:00
Erik Bäckman
83bc24f58c Fix libc::stat on FreeBSD (x86_64) 2025-03-04 10:20:13 +01:00
Christoffer Lerno
0ef99c23a8 Allow swizzling assign, eg. abc.xz += { 5, 10 }; 2025-03-04 02:18:24 +01:00
Christoffer Lerno
0a905d8458 Change all hash functions to have a common hash function. 2025-03-03 20:07:02 +01:00
Christoffer Lerno
261bfb97c6 Rename length_sq to sq_magnitude 2025-03-03 18:47:55 +01:00
Christoffer Lerno
cc94199131 Remove Vec2 and other aliases from std::math. 2025-03-03 18:13:39 +01:00
Christoffer Lerno
0925010c07 Removal of any-switches 2025-03-03 15:02:25 +01:00
Christoffer Lerno
13f824e349 Add ONHEAP for List and HashMap 2025-03-03 12:20:18 +01:00
Christoffer Lerno
3d6f28919c Aliases were incorrectly considered compile time constants. 2025-03-03 11:35:18 +01:00
Christoffer Lerno
910fc6e364 Post and pre-decrement operators switched places for vector elements. #2010. 2025-03-03 01:21:01 +01:00
Christoffer Lerno
c40198b016 - new_* functions in general moved to version without new_ prefix.
- `string::new_from_*` changed to `string::from_*`.
- `String.to_utf16_copy` and related changed to `String.to_utf16`.
- `String.to_utf16_tcopy` and related changed to `String.to_temp_utf16`
- `mem::temp_new` changed to `mem::tnew`.
- `mem::temp_alloc` and related changed to `mem::talloc`.
- `mem::temp_new_array` changed to `mem::temp_array`.
2025-03-03 00:32:20 +01:00
Christoffer Lerno
b35aafd3d5 Update for NoSanitize on globals. 2025-03-03 00:32:20 +01:00
Christoffer Lerno
222bfb158b Remove deprecated functions. 2025-03-03 00:32:20 +01:00
Christoffer Lerno
61c67c8f23 Fix address sanitizer to work on MachO targets (e.g. MacOS). 2025-03-03 00:32:20 +01:00
Christoffer Lerno
2b90500c22 Remove container sanitizer unless using the LIBC allocator. 2025-03-03 00:32:20 +01:00
Christoffer Lerno
74a6e9f0c0 Add struct swizzle test. 2025-03-03 00:32:20 +01:00
Christoffer Lerno
fbac2d6df3 Formatting updates. 2025-03-03 00:32:20 +01:00
Christoffer Lerno
8f0de40b3d Update nix. 2025-03-03 00:32:20 +01:00
Christoffer Lerno
8bb99c6f81 Fixes to examples and MSVC compilation. 2025-03-03 00:32:20 +01:00
Christoffer Lerno
cc5f9c6aab Update CI for 0.7 2025-03-03 00:32:20 +01:00
Christoffer Lerno
fb6b048bd0 Remove operator(@construct). Fix sample. 2025-03-03 00:32:20 +01:00
Christoffer Lerno
2a895ec7be First 0.7 update, removing all deprecated features. 2025-03-03 00:32:20 +01:00
Christoffer Lerno
cff6697818 Fix CI for Windows 2025-03-02 01:25:16 +01:00
Christoffer Lerno
453cd295e2 Bundle README 2025-03-02 00:01:56 +01:00
Christoffer Lerno
55c88408be Remove some deprecated use. 2025-03-01 23:58:28 +01:00
Christoffer Lerno
f52c2a6f96 Update information about latest version. 2025-03-01 23:24:17 +01:00
Christoffer Lerno
c430ff5d09 Retry release. 2025-03-01 21:03:21 +01:00
Christoffer Lerno
dd8e280835 Release 0.6.8 2025-03-01 18:47:08 +01:00
Christoffer Lerno
34c2d8ce77 Fix regression with scripts. 2025-03-01 17:24:38 +01:00
Christoffer Lerno
28b9ff8016 Fixed some additional errors due to the BuildParseContext refactoring. 2025-03-01 12:48:22 +01:00
Christoffer Lerno
76f226f536 Fixed error and poor error message when using an invalid target name. 2025-03-01 12:30:36 +01:00
Christoffer Lerno
1b0ac13d76 Deprecation of operator(@construct) 2025-02-28 10:39:08 +01:00
Christoffer Lerno
f134b8b67a Swizzling an inline vector in a struct would cause a crash. 2025-02-27 21:49:20 +01:00
Christoffer Lerno
33b05bcfeb More deprecations in lib6, and updates to lib7 2025-02-27 11:10:41 +01:00
Christoffer Lerno
6d3c1f5d2f Fix final? issues with -o. 2025-02-26 02:51:42 +01:00
Christoffer Lerno
96943ca66f Check exe and lib output so -o works with directories. Removed construct forms from Maybe. 2025-02-26 02:35:28 +01:00
Christoffer Lerno
374d73af12 Cleanup. 2025-02-26 01:49:19 +01:00
Adversing
81397f0726 Add --print-env option to c3c (#1880)
* Add `--build-env` for build environment information.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-02-26 01:48:03 +01:00
Aleksandr Vedeneev
0c33b78a2f Test runner args #1967 (#1988)
* harmonized testrun arguments with c3c --test-* naming
added --test-quiet option
added --test-noleak to disable tracking allocator mem leak detection
added --test-nocapture - tests can print out everything as they run
* Move changes to lib7

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-02-26 00:49:21 +01:00
Christoffer Lerno
ee5b9e5826 @if declarations were missing from -P output #1973. 2025-02-25 17:00:47 +01:00
Christoffer Lerno
5d3c3781e4 Update msys CI 2025-02-25 16:09:23 +01:00
Christoffer Lerno
c13c0d04b1 Run MSVC with "no-terminal" 2025-02-25 16:02:51 +01:00
Christoffer Lerno
88f44f1eac Fix test. 2025-02-25 15:48:44 +01:00
Christoffer Lerno
50680d6893 Fix bug casting bool to int to other int #1995. Use test_suite7 in CI. 2025-02-25 15:36:06 +01:00
Christoffer Lerno
31096531e1 Several fixes for .o files and -o output, improving handling and naming, checking for existence of sub-folders before output. Warning on using .o and -o output when not single module etc. Fix to static lib regression. Info about asm / llvm / obj output. 2025-02-25 14:05:00 +01:00
Christoffer Lerno
062a67fe75 Updates to file:: and path::, Path is now passed an allocator. path::traverse function. mkdir / rmdir / chdir works directly with strings. Strings get file_basepath, path_dirname. Test suite runner now uses lib7. Bug when printing a parameter declaration error. Fix optional jumps in expression lists, #1942. 2025-02-25 02:18:33 +01:00
Christoffer Lerno
1dfc24822e Update pool test. 2025-02-24 22:37:20 +01:00
Christoffer Lerno
e35c7f0b90 Update pool test. 2025-02-24 18:07:36 +01:00
Christoffer Lerno
7083b2b8e5 Added --suppress-run from #1931. 2025-02-24 11:25:07 +01:00
Christoffer Lerno
135213388d Add bigint fixes to lib7 2025-02-24 11:15:50 +01:00
Jonas Quinten
ed62268997 std::math::bigint: Fixed init_with_array with empty array 2025-02-24 11:12:55 +01:00
Jonas Quinten
38110b0269 fix 2025-02-24 11:12:55 +01:00
Jonas Quinten
e34d56327a std::math::bigint: Fixed init_with_u128 and init_with_array 2025-02-24 11:12:55 +01:00
Jonas Quinten
b50e6bd0e4 std::math::bigint: Added unit tests for init_with_u128 and init_with_array 2025-02-24 11:12:55 +01:00
Christoffer Lerno
3ba68f85fe Fix bug checking for @builtin 2025-02-24 10:28:50 +01:00
Christoffer Lerno
9f5c5a9acf Update some examples. 2025-02-24 02:20:02 +01:00
Christoffer Lerno
b6f5938eda Change to {} generics in lib7. Update qoi and other encodings and multiple other small changes. 2025-02-24 01:44:57 +01:00
Christoffer Lerno
87725a3a9e Create a unit7 for all unit tests. 2025-02-24 01:05:45 +01:00
Christoffer Lerno
70029cc4b8 Updated stdlib to experimental allocator first. Updates to DString, String and anything using new_* syntax. 2025-02-23 22:54:48 +01:00
Christoffer Lerno
4f72bc4be9 Fixes to lib7, added parallel test structure. 2025-02-23 13:53:04 +01:00
Christoffer Lerno
3a1aa8bdf0 Fix precedence of braces. Updated to lib2. 2025-02-23 01:30:34 +01:00
Christoffer Lerno
a986d053c0 Add lib2 to allow hacking the stdlib more for 0.7.x 2025-02-23 00:24:01 +01:00
Christoffer Lerno
43943c1f33 Update MSVC paths in CI 2025-02-22 23:02:07 +01:00
Christoffer Lerno
3da9f73338 - Output into /.build/obj/<platform> by default.
- Output llvm/asm into llvm/<platform> and asm/<platform> by default.
- Don't delete .o files not produced by the compiler.
- Correctly handle in/out when interacting with inout.
2025-02-22 22:34:26 +01:00
Christoffer Lerno
855be92881 Regression with .gitkeep in project init. List.init incorrectly didn't have the first argument the allocator. Added .init to priority queue. Created mem thread allocator alias. Correctly handle ident aliases. Allow ident on builtin aliases. 2025-02-21 21:34:48 +01:00
Christoffer Lerno
e674deb486 Fix generics. 2025-02-21 16:12:35 +01:00
Christoffer Lerno
3ef094a3d3 Add some conveniences to Clock and thread. Allow atomic load on booleans. 2025-02-21 15:56:32 +01:00
Christoffer Lerno
9c60c2cb33 Change to avoid thread races during compilation. 2025-02-21 13:15:19 +01:00
Christoffer Lerno
b54d994475 Fix memcmp misuse in parsing asm args. 2025-02-21 09:46:56 +01:00
Christoffer Lerno
80e360d8dd Dispose of copied module. 2025-02-20 23:25:25 +01:00
Christoffer Lerno
9f165342e2 Revert disposal 2025-02-20 22:41:44 +01:00
Christoffer Lerno
a2bfeb156d Dispose of the LLVMModule 2025-02-20 22:36:00 +01:00
Christoffer Lerno
bb8c03777d Fix address overread 2025-02-20 21:56:28 +01:00
Christoffer Lerno
8338888976 Do not use optimization for C++ wrapper. 2025-02-20 18:36:22 +01:00
Christoffer Lerno
79db06ecd1 Crash when trying to define a method macro that isn't @construct but has no arguments. 2025-02-20 15:51:21 +01:00
Christoffer Lerno
341a70bd5d Implicitly unwrapped optional value in defer incorrectly copied #1982. 2025-02-20 03:44:22 +01:00
Christoffer Lerno
8bf9ca89a1 Add a separate job to just run the test suite runner for Mac. 2025-02-20 02:30:43 +01:00
Alex Veden
5046608d1f added io::stdout().flush() - to force printing test name before possible deadlock
mem::scoped() and long jump resilience fixed #1963
fixed --test-nosort argument + extra test for teardown_fn memory leak
Some renaming. Simplify robust test allocator handling. Pop temp allocators in test runner.
`Thread` no longer allocates memory on posix.
Update unprintable struct output.
Correctly give an error if a character literal contains a line break.
2025-02-20 01:15:48 +01:00
Christoffer Lerno
535151a2a5 Fix character literal regex. 2025-02-20 00:59:18 +01:00
Christoffer Lerno
b45cb22950 Some improvements to the test_suite_runner 2025-02-19 20:59:12 +01:00
Christoffer Lerno
d6485ca08b Test new tester script. 2025-02-19 18:01:44 +01:00
Christoffer Lerno
d9e5926d57 Fix error when boolean combined with ??. First checkin of C3 tester (unfinished) 2025-02-19 01:02:58 +01:00
Christoffer Lerno
cbacd64987 Update tests to (Foo) { ... } syntax. 2025-02-18 18:53:30 +01:00
Christoffer Lerno
168c11e006 {| |} expression blocks deprecated. 2025-02-18 12:50:34 +01:00
Mateo Acuña
26362d5068 Improve crtbegin.o Lookup for Linux Targets (#1975)
* refactor linux crtbegin lookup to use architecture-specific glob paths
* Fixed incorrect function call to `get_linux_crt_begin_glob_path` (`get_linux_crt_begin_arch_glob`)
* Formatting

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-02-18 00:30:54 +01:00
Christoffer Lerno
e77d1fb646 - Increase precedence of (Foo) { 1, 2 }
- Add `--enable-new-generics` to enable `Foo{int}` generic syntax.
2025-02-18 00:26:22 +01:00
Christoffer Lerno
c41d551ead Create 0.6.8 2025-02-18 00:26:22 +01:00
Christoffer Lerno
0d7697280c Fix of test (again) 2025-02-17 00:22:21 +01:00
Christoffer Lerno
0a93581695 Fix test. 2025-02-17 00:03:44 +01:00
Christoffer Lerno
0509b40b21 - Fix issue when dereferencing a constant string.
- Fix problem where a line break in a literal was allowed.
2025-02-16 23:55:55 +01:00
Christoffer Lerno
6e11bdbd35 Fix issue where target was ignored for projects. 2025-02-16 23:31:03 +01:00
Christoffer Lerno
8a6e996442 Create release candidate 0.6.7 2025-02-16 01:24:51 +01:00
pekochan069
be00fdb253 Add scoop installation 2025-02-14 20:13:50 +01:00
Christoffer Lerno
0dd1a93d0d Regression String! a; char* b = a.ptr; would incorrectly be allowed. 2025-02-14 16:11:31 +01:00
Christoffer Lerno
7ca70b20be Allow (Foo) { 1, 2 } syntax for compound literals. 2025-02-14 12:51:58 +01:00
Bruno Dias
e0cfe56121 Fixed nix build for macos. (#1914)
* Fixed nix build for macos.
* Fix test.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-02-13 22:44:21 +01:00
Christoffer Lerno
6ca77065d8 Fix issue when parsing bitstructs, preventing them from implementing interfaces. 2025-02-13 21:51:22 +01:00
Christoffer Lerno
e96dce92cd Issue when scalar expanding a boolean from a conditional to a bool vector #1954. 2025-02-13 21:36:28 +01:00
Christoffer Lerno
cec9b21707 Missing end padding when including a packed struct #1966. 2025-02-13 21:15:27 +01:00
Christoffer Lerno
8c58b31bbd Remove <[]> experimental generic syntax. 2025-02-13 12:53:46 +01:00
Christoffer Lerno
c785572467 Add allocator::wrap. 2025-02-13 03:10:53 +01:00
Jamie Wales
a297470887 Add further tests to vector library 2025-02-12 23:31:10 +01:00
Velikiy Kirill
f0682422c0 Add more Windows API types and structs (#1956)
* Add more Windows API types and structs
* Add more Windows API types and structs (merged sock.c3 to wsa.c3)
* Some formatting.

---------

Co-authored-by: Kirill Velikiy <velikoss@vk.com>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-02-12 23:30:07 +01:00
Christoffer Lerno
1f856cacf5 HashMap is now Printable. Fix access inlining for enums. #1958 2025-02-12 23:11:46 +01:00
Ygor Pontelo
c9ecb09cd7 duration fix (#1950)
* duration fix
* Update release notes, explicitly send -1.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-02-12 22:13:07 +01:00
Christoffer Lerno
4961d0433f - Circumvent Aarch64 miscompilations of atomics.
- Fixes to ByteBuffer allocation/free.
- Fix issue where compiling both for asm and object file would corrupt the obj file output.
2025-02-12 12:50:30 +01:00
Christoffer Lerno
ba48627ca0 Fix address out of bounds access in test. 2025-02-11 00:22:01 +01:00
Christoffer Lerno
45eb3acffe Remove unused parameter in mem::clear. 2025-02-11 00:15:33 +01:00
Christoffer Lerno
d3ad533dd6 Fix issue in List with sanitizers. 2025-02-11 00:13:04 +01:00
Christoffer Lerno
9e54014848 Fix issue in GrowableBitSet with sanitizers. 2025-02-10 23:55:02 +01:00
Christoffer Lerno
f8e3ffd267 Fix test 2025-02-10 22:19:16 +01:00
Christoffer Lerno
8b8a2beb0d Fix threading test. 2025-02-10 22:08:54 +01:00
Christoffer Lerno
79a4b6855b - Detect unaligned loads #1951.
- Fix issue where aligned bitstructs did not store/load with the given alignment.
2025-02-10 22:07:15 +01:00
Christoffer Lerno
86680279fa Improve inference on ?? #1943. 2025-02-10 16:20:33 +01:00
Christoffer Lerno
b46d3947dd Postpone Xtensa addition. 2025-02-10 12:00:50 +01:00
Christoffer Lerno
c4212c4649 - Test runner will also check for leaks.
- `write` of qoi would leak memory.
- Issue when having an empty `Path` or just "."
- `set_env` would leak memory.
2025-02-10 00:39:02 +01:00
Christoffer Lerno
63f619e5b6 Add tracking allocator to test runner. #1809 2025-02-09 03:10:35 +01:00
Christoffer Lerno
ce06de4b18 Updates to grammar. 2025-02-09 02:19:27 +01:00
Christoffer Lerno
e1d546225f Update stdlib with new syntax for short function decl. 2025-02-08 23:04:59 +01:00
Christoffer Lerno
c4f9efc8f5 Allow fn int test() => @pool() { return 1; } short function syntax usage #1906. 2025-02-08 22:45:14 +01:00
Christoffer Lerno
69e30c19f8 Distinct inline void causes unexpected error if used in slice #1946. 2025-02-08 20:33:08 +01:00
Christoffer Lerno
940874e349 Cleaner error message when missing comma in struct initializer #1941. 2025-02-08 19:54:44 +01:00
Christoffer Lerno
d3f2180330 bigint::from_int(0) throws assertion #1944. 2025-02-08 19:15:14 +01:00
Christoffer Lerno
68b5c1e1f1 Fix bigint hex parsing #1945. 2025-02-08 19:06:06 +01:00
Christoffer Lerno
c8e671d34b Assert when using optional as init or inc part in a for loop #1942. 2025-02-08 18:58:44 +01:00
Christoffer Lerno
46c7e9aefa Cleanup QOI 2025-02-08 00:32:48 +01:00
Christoffer Lerno
2126be2222 Fix issue number. 2025-02-08 00:15:47 +01:00
Christoffer Lerno
fa4fb44779 Issue with defer copying when triggered by break or continue. 2025-02-08 00:14:01 +01:00
Christoffer Lerno
07e8779d4e Fix fixup ordering in defer. 2025-02-07 23:12:34 +01:00
Christoffer Lerno
77db50bce8 Allow function types to have a calling convention. #1938 2025-02-07 22:03:15 +01:00
Jooris Hadeler
ea4c864d4b Add Socket.peek to allow peeking at the receiving queue. (#1933)
* Add `Socket.peek` to allow peeking at the receiving queue.

This uses the `MSG_PEEK` flag to peek at the beginning of the
receiving queue without removing data from said queue.
2025-02-07 21:00:54 +01:00
rexim
e6ec09f2c5 Remove the need for the socket helper function, and use the new inline enum functionality. 2025-02-07 20:55:40 +01:00
rexim
122179980c Introduce Socket.shutdown() 2025-02-07 20:52:36 +01:00
Christoffer Lerno
27e76fe59e Project view refactoring. 2025-02-07 20:49:51 +01:00
Christoffer Lerno
d13f302ac8 Build options refactoring. 2025-02-07 16:04:44 +01:00
Christoffer Lerno
3e1e3e3e29 Incorrect error message when providing too many associated values for enum #1934. 2025-02-07 10:44:53 +01:00
Christoffer Lerno
0388910c17 Cleanup. 2025-02-07 01:08:28 +01:00
Christoffer Lerno
4b984e12a5 Refactor build options. 2025-02-07 00:11:04 +01:00
Christoffer Lerno
4e717657bd Remove not-yet-supported docs tool. 2025-02-06 23:23:24 +01:00
Fangrui Song
78dcda0bb2 Clean up some linker/C compiler options
-fno-pic/-fno-pie/-fpic/-fPIC/-fpie/-fPIE options belong to the same
famility where the last option wins. These options have no effect in the
link phase.

Clang and GCC usually pass `--eh-frame-hdr` to ld, with the exception
that `gcc -static` does not pass `--eh-frame-hdr`. The difference is a
historical choice related to `__register_frame_info`. We can behavle
like Clang and always pass `--eh-frame-hdr`.

Remove a `-L` that does not specify a directory.
2025-02-06 22:57:49 +01:00
Christoffer Lerno
bc63c16c93 Add @select to perform the equivalent of a ? x : y at compile time. 2025-02-06 22:21:26 +01:00
Christoffer Lerno
e3851f3723 return (any)&foo would not be reported as an escaping variable if foo was a pointer or slice. 2025-02-06 16:33:42 +01:00
Aleksandr Vedeneev
3a502feb1d test::eq/ne/gt syntax (#1928)
* test::eq/ne/gt syntax

* renamed @almost to test::eq_approx
2025-02-05 22:50:25 +01:00
Christoffer Lerno
ef72e19bf0 Remove 20 from docker. 2025-02-05 01:03:07 +01:00
Christoffer Lerno
8b794e8cea Updated test (again!) 2025-02-04 23:36:27 +01:00
Christoffer Lerno
549e27a800 Fix test compatibility with LLVM 21 2025-02-04 23:11:09 +01:00
Christoffer Lerno
d05cc991f5 Support new LLVM, fix max version. 2025-02-04 23:03:02 +01:00
Christoffer Lerno
07be4b0e06 Support new LLVM 2025-02-04 23:01:32 +01:00
Christoffer Lerno
6fcda240b8 Fixes to enum conversions. 2025-02-04 22:26:51 +01:00
Christoffer Lerno
fff3cf33c7 Issue where inlined expr enums weren't properly const folded. 2025-02-04 21:46:23 +01:00
Christoffer Lerno
a862437bac Rephrased the text. 2025-02-04 11:27:44 +01:00
Christoffer Lerno
7a6df10b39 Update error message on casting between distinct types. 2025-02-04 11:24:08 +01:00
Christoffer Lerno
c54c400291 Allow inline enum values to define sizes. 2025-02-04 00:23:59 +01:00
Christoffer Lerno
aaa5c0f743 Fix bug in parsing inline enums. 2025-02-03 23:43:34 +01:00
Christoffer Lerno
9d2f4e72c2 Add inline to enums #1819. 2025-02-03 22:51:50 +01:00
Danyella Strikann
70a849cbb5 Removed the unused command headersfrom the usage
Signed-off-by: Danyella Strikann <danyellastrikann@duck.com>
2025-02-03 00:40:41 +01:00
Christoffer Lerno
ecb25a0010 Remove accidental include. 2025-02-03 00:39:19 +01:00
Christoffer Lerno
300983f831 Compile time array assign ops, e.g. $c[1] += 3 #1890. 2025-02-03 00:35:20 +01:00
Christoffer Lerno
f2df4855ff Improve error message when using ',' in struct declarations. #1920 2025-02-02 22:44:30 +01:00
rexim
50c590bb5f Suggest alternative to reference macro arguments 2025-02-02 22:22:34 +01:00
Radek Micek
4a99ebef51 clock_gettime returns CInt, not void 2025-02-02 22:21:31 +01:00
Christoffer Lerno
20d93ede0c Fix test compatibility with LLVM 20 2025-02-02 02:47:51 +01:00
Christoffer Lerno
f8b2f7f268 Refactor casts and make untyped list conversions not dependent on context. 2025-02-01 23:37:32 +01:00
Christoffer Lerno
dc6d994480 Fixing various issues around shifts, like z <<= { 1, 2 }. 2025-02-01 15:43:42 +01:00
Christoffer Lerno
2b3b7e32b8 Fix bug indexing into a constant array at compile time. 2025-02-01 00:36:30 +01:00
Christoffer Lerno
03e2b30ede Compile time array inc/dec #1890. 2025-01-31 23:58:44 +01:00
Adversing
f3afec61bb Added fmodf function #1875 2025-01-31 22:20:45 +01:00
Christoffer Lerno
bda33ca3f9 Update defer 2025-01-31 16:59:14 +01:00
Christoffer Lerno
50c1aac9bb Usage of @noreturn macro is type-checked as if it returns #1913. 2025-01-31 16:19:12 +01:00
Christoffer Lerno
9092defd46 defer is broken when placed before a $foreach #1912 2025-01-31 14:39:51 +01:00
Christoffer Lerno
7dd9256e2d Update temp path append function. 2025-01-30 23:20:38 +01:00
Christoffer Lerno
a056efce04 Additional cleanup. 2025-01-30 19:32:20 +01:00
Christoffer Lerno
0bad8f92b0 More conservative use of getcwd and some cleanup. 2025-01-30 18:13:12 +01:00
Christoffer Lerno
b040736f7f Additional cleanup. 2025-01-30 14:38:25 +01:00
Christoffer Lerno
778260213e Some minor cleanup and updates. Should improve build issue with Gentoo #1907. 2025-01-30 10:45:41 +01:00
Christoffer Lerno
50385be614 Some minor cleanup and updates. 2025-01-30 01:58:51 +01:00
Christoffer Lerno
3c50376175 New test runner 2025-01-30 01:09:48 +01:00
Christoffer Lerno
6848753a10 Warn on if-catch with just a default case #1904. 2025-01-29 15:29:09 +01:00
Christoffer Lerno
13771cc536 Truncate output from execute and print to stdout on error. 2025-01-29 13:51:33 +01:00
Christoffer Lerno
ac3b2f0fea - Fix bug where in dead code, only the first statement would be turned into a nop.
- Remove unused $inline argument to mem::copy.
2025-01-29 13:07:19 +01:00
Christoffer Lerno
4b61ac7dae Project / build refactoring. 2025-01-29 00:25:22 +01:00
Christoffer Lerno
70d0ad1fcc Missing error when placing a single statement for-body on a new row #1892. 2025-01-28 17:13:37 +01:00
rexim
af2a0ffd3f Fix missing r15 register in inline assembly 2025-01-28 15:10:48 +01:00
Christoffer Lerno
02c3d5419b Add with_padding convenience functions. 2025-01-28 00:18:08 +01:00
Christoffer Lerno
55fba09b3b Fixed STB_WEAK errors when using consts in macros in the stdlib #1871. 2025-01-27 23:51:23 +01:00
Christoffer Lerno
d2a7dc4a9a Fix test take two. 2025-01-27 20:51:41 +01:00
Rene Hangstrup Møller
d8ca0f69f6 fix allocator::new_aligned #1898 2025-01-27 20:38:11 +01:00
Christoffer Lerno
7d0e143224 Remove debug code. 2025-01-27 20:36:41 +01:00
Christoffer Lerno
bd139f73ac Fix test. 2025-01-27 15:25:46 +01:00
Christoffer Lerno
f23dda8d50 Fix regression for exec #1881. 2025-01-27 14:34:05 +01:00
Christoffer Lerno
a88364aaad Fixes miscompilation of nested @jump #1896. 2025-01-27 11:35:55 +01:00
Christoffer Lerno
9530fe8fcd Fix regression for parsing types and switch to the "new" generic syntax that's being tested. 2025-01-26 22:51:27 +01:00
Christoffer Lerno
26dc88e096 Fix issues with @jump on empty default or only default #1893 #1894 2025-01-26 15:38:24 +01:00
Christoffer Lerno
1f1c445a76 Issue where trailing body argument was allowed without type even though the definition specified it #1879. 2025-01-25 23:52:13 +01:00
Christoffer Lerno
3e4f9e875f Add test for casts and append. 2025-01-25 23:18:35 +01:00
BWindey
dab4844195 [FIX] Let c3c project subcommands use same logic as others to get project.json (#1885)
* Extract project JSON loading into its own function
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-01-25 22:52:31 +01:00
Christoffer Lerno
e40bab2d30 Allow (int[*]) { 1, 2 } cast style initialization. Experimental change from [*] to [?]. Fix issue where compile time declarations in expression list would not be handled properly. 2025-01-25 22:10:12 +01:00
Christoffer Lerno
ca91ad4097 Fix bug where .min/.max would fail on a distinct int #1888. 2025-01-25 01:01:52 +01:00
Christoffer Lerno
e2b11c17bc - Compile time array assignment #1806.
- Allow `+++` to work on all types of arrays.
2025-01-25 00:48:06 +01:00
Christoffer Lerno
eda997545a Fix issue in optimized if lowering. 2025-01-24 17:14:34 +01:00
Christoffer Lerno
92b3490210 Fix issue where an if statement would not execute at all. 2025-01-24 16:42:59 +01:00
Christoffer Lerno
ba545b44f0 Change const checking in if lowering. 2025-01-24 16:31:27 +01:00
Christoffer Lerno
4f130cfe56 Further lvalue refactoring. This completely removes CHECK_LVALUE. 2025-01-24 00:19:22 +01:00
Christoffer Lerno
145b76ec75 Cleanup. 2025-01-23 11:03:43 +01:00
Christoffer Lerno
e30952b484 INLINE to static inline for refactored function. 2025-01-23 11:00:35 +01:00
Christoffer Lerno
b145c073f0 VERY experimental <[ ]> syntax for generics. Continue lvalue refactoring. 2025-01-23 01:29:35 +01:00
Christoffer Lerno
948d56b321 Refactor $ct lvalue handling. 2025-01-23 00:00:22 +01:00
Christoffer Lerno
69d0fa8c44 Correctly check jump table size and be generous when compiling it #1877. 2025-01-22 22:33:35 +01:00
Christoffer Lerno
a845a932f5 Change _MSC_VER to PLATFORM_WINDOWS for some exec. Fix to nix. 2025-01-22 00:44:50 +01:00
Christoffer Lerno
3221180315 Fixes to `"exec" use. 2025-01-22 00:26:40 +01:00
BWindey
c326c525be [FEAT] Add CLI flags to filter 'c3c project view' results (#1863)
* Project view feature
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-01-21 23:46:41 +01:00
Christoffer Lerno
16aadae9bd Remove allocator argument 2025-01-21 22:25:42 +01:00
Snikimonkd
b7ffa3b17c [feat] add test tmp files to gitignore 2025-01-21 12:44:13 +01:00
Christoffer Lerno
772b20c26b Fix find_msvc 2025-01-21 01:26:55 +01:00
Christoffer Lerno
c7eb0024c7 Error on switch case fallthough if there is more than one newline #1849. 2025-01-21 00:38:24 +01:00
Christoffer Lerno
1a2dcd07ee Add win-debug setting to be able to pick dwarf for output #1855. 2025-01-21 00:13:11 +01:00
Christoffer Lerno
ab32231cd1 Added releasenotes. 2025-01-20 23:51:54 +01:00
Snikimonkd
a0192a0116 [FEAT] add golang like channel (#1843)
* [feat] add golang like channels
* Updated new_init/init. Some fixes for init.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-01-20 23:51:00 +01:00
Christoffer Lerno
13e3ecbde2 Tab and style 2025-01-20 23:31:49 +01:00
Christoffer Lerno
fefe6d1342 Filter $exec output from \r, which otherwise would cause a compiler assert #1867. 2025-01-20 22:32:53 +01:00
Bram Windey
bbef5656a5 Fix 'strtul' to 'strtoul' 2025-01-20 21:45:15 +01:00
Christoffer Lerno
ad3cd88350 Fix dues to crash when converting a const vector to another vector #1864. 2025-01-20 16:26:26 +01:00
Christoffer Lerno
5183370773 Update mingw llvm/lld 2025-01-20 14:53:59 +01:00
cd-n0
f74891d214 Fix linux-crt and linux-crtbegin not getting recognized as a project parameter (#1865)
* Fix `linux-crt` and `linux-crtbegin` not getting recognized as a project parameter

* Update releasenotes.md
2025-01-20 14:48:41 +01:00
Christoffer Lerno
d2885faa79 Further cleanup. 2025-01-20 04:09:47 +01:00
Christoffer Lerno
c59d47f652 Keep the old behaviour which made the script detect bugs (although indirectly!) 2025-01-20 03:43:12 +01:00
Christoffer Lerno
f863c4ae84 Fix incorrect arg type failing MSVC compilation. Missing ASSERT updated. Update python script. Fix bug printing error duplicate generic module. 2025-01-20 03:35:49 +01:00
Christoffer Lerno
bb2a2526e4 Refactoring access + some macro renaming. 2025-01-20 02:44:39 +01:00
Christoffer Lerno
f9b86226a8 Refactoring identifier and catch unwrap into two different nodes. 2025-01-19 13:23:21 +01:00
Christoffer Lerno
a4f5c97150 Fix typo 2025-01-18 23:53:32 +01:00
Christoffer Lerno
5de03abe0d Concatenating an const empty slice with another array caused a null pointer access. 2025-01-18 23:50:31 +01:00
Christoffer Lerno
c3f5806aa3 Const strings and bytes were not properly converted to compile time bools.
Contracts @require/@ensure are no longer treated as conditionals, but must be explicitly bool.
2025-01-18 23:24:55 +01:00
Christoffer Lerno
5a36f0bc16 Fix issue with @const where the statement $foo = 1; was not considered constant. 2025-01-18 22:40:58 +01:00
Christoffer Lerno
c5dbbf9ff7 Compiler allows a generic module to be declared with different parameters #1856. 2025-01-17 23:24:42 +01:00
Christoffer Lerno
304b604652 Added weakly linked __powidf2 2025-01-17 17:42:39 +01:00
BWindey
b787985bf7 Improve c3c --help/-hh and c3c project (#1851)
* Fix missing newline and incorrect indentation for 'c3c project' help on fetch

* Use a ternary trick :D

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-01-17 12:23:15 +01:00
Christoffer Lerno
d72ec09cee Fix lack of location for reporting lambdas with missing return statement #1857. 2025-01-17 11:55:56 +01:00
Christoffer Lerno
d4bd68c188 Fix bug in SHA1 for longer blocks #1854. 2025-01-17 01:10:40 +01:00
Christoffer Lerno
f51bfa5a44 Update latest version. 2025-01-16 22:45:48 +01:00
Christoffer Lerno
3e4d1de70e Fix issue requiring prefix on a generic interface declaration. 2025-01-16 22:09:53 +01:00
Christoffer Lerno
721aaa28aa Added '%h' and '%H' for printing out binary data in hexadecimal using the formatter. 2025-01-16 01:06:57 +01:00
Christoffer Lerno
15503a9054 Release candidate 0.6.6 2025-01-15 22:10:48 +01:00
Taylor W
660654f9e0 math_tests: pow test (#1842)
* math::nolibc: replaced code with word macros

* math_tests: pow test

Added test for pow and added more test points for the exp and log tests.
2025-01-15 13:35:18 +01:00
Christoffer Lerno
2f7d18bfb8 Quicksort and insertsort incorrectly allowing arrays and vectors by value. #1845. 2025-01-15 13:31:29 +01:00
Christoffer Lerno
29a6a0db32 Fix unavailable LLVM int128 alignment. 2025-01-15 11:49:33 +01:00
Christoffer Lerno
7b2fe92241 Improve error message on incorrect inner struct/union name #1847. 2025-01-15 10:54:50 +01:00
Christoffer Lerno
70da1f748a Enum associated declarations accidentally allowed declaration in function style. #1841 2025-01-14 23:06:17 +01:00
Christoffer Lerno
3033295884 Fix bug with enums with jump tables #1840 also affecting ranged enums entries. 2025-01-14 22:47:12 +01:00
Christoffer Lerno
8c12f92aff Make stringify to recursively enter #hash expressions #1834. 2025-01-14 12:40:42 +01:00
Christoffer Lerno
76da7936e5 Fix issue with inferred vector output to JSON. #1839 2025-01-14 12:25:49 +01:00
Max
2a924ae3b0 fix the link order to support LLVM_20 change (#1838)
* fix the link order to support LLVM_20 change

* Update CI to use LLVM 20

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-01-14 11:23:40 +01:00
Christoffer Lerno
5ba9acad5d Fix bug where &i[0] = null was not detected to be an error. #1833 2025-01-14 01:43:59 +01:00
Christoffer Lerno
4cb984e56d Prevent temp arena scribbling from causing an asan warning. #1825 2025-01-13 16:58:55 +01:00
Christoffer Lerno
70606a2bbe Report the correct type as not having a method when access fails #1828. 2025-01-13 14:11:51 +01:00
Taylor W
259112e178 math: macros to set floating-point numbers with uint (#1826)
* math: Setting the bits of floating-point numbers

Added macros which set all 32 bits of a float, the lower 32 bits of
a double, and the upper 32 bits of a double. Some changes were made to
older code to use these macros.

* Replaced code with bitsetting macros in __tan.c3 and tan.c3

* math: tests for word macros and release notes

Tests were written for the word macros, which include getting and
setting a float with a uint and getting and setting the high or low word
of a double with a uint.

Release notes were updated to include the word setter macros.
2025-01-13 13:37:49 +01:00
Christoffer Lerno
a2cde1e072 Correctly handle known length slices with index checks... and now works with $defined too. 2025-01-13 13:20:59 +01:00
Christoffer Lerno
de04c52379 Correctly handle known length slices with index checks. 2025-01-13 02:30:35 +01:00
Christian Buttner
27970085e5 Fix formatter output length calculation. 2025-01-12 22:54:43 +01:00
Jefferson Amstutz
e0afc0f9ea use WORKING_DIRECTORY to ensure stability of finding the git hash 2025-01-12 22:53:16 +01:00
konimarti
0e44e63fa8 net/url: implement url encoding (RFC 3986) (#1795)
* net/url: implement url encoding (RFC 3986)

Implement url percent-encoding and -decoding functions according to RFC
3986. Add unit tests.

Link: https://datatracker.ietf.org/doc/html/rfc3986

* net/url: ensure correct encoding of URL components

Add encoding and decoding methods to the Url struct components according
to RFC 3986.

An Url can be parsed from a String with `new_parse()` or `temp_parse()`.
The parsed fields are decoded. The only field that is not decoded is
`raw_query`. To access the decoded query values, use
`Url.query_values()`.

`Url.to_string()` will re-assemble the fields into a valid Url string
with proper percent-encoded values.

If the Url struct fields are filled in manually, use the actual
(un-encoded) values. To create a raw query string, initialize an
`UrlQueryValues` map, use `UrlQueryValues.add()` to add the query
parameters and, finally, call `UrlQueryValues.to_string()`.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-01-12 22:52:25 +01:00
Adversing
2623d7d525 Feat: Added exp, log and pow functions as requested in #1632 (#1781)
* Feat: Added exp, log and pow functions as requested in #1632
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-01-12 22:50:10 +01:00
Christoffer Lerno
4e78e32ced Fix regression with contract docs and generics #1821 2025-01-12 14:19:19 +01:00
Christoffer Lerno
f65ca07b62 Fix bug when multiple $else clauses followed an $if #1824. 2025-01-12 13:31:35 +01:00
welrox
7a805340c5 Add more aarch64 instructions for inline asm 2025-01-12 02:49:24 +01:00
Christoffer Lerno
a863d7fe9e Prevent #hash arguments from taking code that modifies ct variables. #1794 2025-01-12 02:20:18 +01:00
Christoffer Lerno
f60bfa8442 Assert concatenating constant slices #1805. Do not link "ld" on Linux with no libc. 2025-01-11 23:46:08 +01:00
Christoffer Lerno
50fdf9900d Regression: Broken type of constant initialized with cast from bitstruct #1811 2025-01-11 23:17:38 +01:00
Christoffer Lerno
8785c2c46f Assert when partially initializing a constant struct containing a slice #1812. 2025-01-11 22:42:33 +01:00
Christoffer Lerno
c8fa7b0cb3 Fix regression with swizzle references for vectors #1810. 2025-01-11 21:36:17 +01:00
Christoffer Lerno
f2e69f8fdc Fix bug with defer assignment in macro #1807. 2025-01-11 20:48:53 +01:00
Christoffer Lerno
8a9edc02b6 Fix bug preventing compile time slices from being iterated over with $foreach. 2025-01-11 03:23:03 +01:00
Christoffer Lerno
d173ba0377 More tests. 2025-01-11 01:51:34 +01:00
Christoffer Lerno
fbb4ae056a Bug when using +++ on value build a slice or array: the rhs cast was not done, corrupting data. 2025-01-11 01:02:13 +01:00
Christoffer Lerno
ae10ae6847 Fix regression when checking a macro constness. 2025-01-11 00:37:34 +01:00
Christoffer Lerno
64ab67bb58 Fixes to JSON output, making it valid. 2025-01-11 00:17:06 +01:00
Christoffer Lerno
dd650bc334 Fixes to JSON output. 2025-01-11 00:07:39 +01:00
Christoffer Lerno
48923a2237 Function comments are stored and displayed with -P. 2025-01-10 23:39:57 +01:00
Christoffer Lerno
1f29110271 Handle bytes and strings the same way in terms of zero termination. 2025-01-10 19:58:00 +01:00
Christoffer Lerno
e133f4406a Extend embedded files to zero terminate. 2025-01-10 19:25:53 +01:00
pekochan069
2fa258a066 Change vswhere command to find to find msvc toolchain correctly 2025-01-10 15:46:16 +01:00
Alex Veden
87d29a62e5 allowing c3c test -- <args1> 2025-01-10 15:45:05 +01:00
konimarti
190dc246b3 mem: add macro to assert on memory leak in scope (#1792)
* mem: add macro to assert on memory leak in scope

Implement `mem::@assert_leak` to assert on a memory leak in the scope of
the macro body. Memory report for the leak can be disabled by setting
the boolean argument to false.

* fix: add conditional compilation flags

* Moved the code into `mem.c3` and made it a builtin.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-01-10 13:33:33 +01:00
Christoffer Lerno
6ae84aac78 Updated releasenotes. 2025-01-10 13:17:45 +01:00
Louis Brauer
c8c58f946c Date/Time formatters (#1782)
* Add .DS_Store to .gitignore
* Allow <= 999_999 as usec on DateTime (was < 999_999)
* Move [Tz]DateTime .format() to std::time::datetime and import only with libc
* Changed name to DateTimeFormat, prefer function over method. Move names to enum.
* Updated tests to the latest standard.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-01-10 13:16:51 +01:00
Christoffer Lerno
0d1fb2843e Improve the error message when running out of memory. 2025-01-10 00:29:08 +01:00
Christoffer Lerno
d67fcb3956 Fix to the successful --lsp output. 2025-01-10 00:09:27 +01:00
Christoffer Lerno
f1325f6539 Added --lsp output. 2025-01-10 00:06:42 +01:00
Christoffer Lerno
3a1bba19af Allow test runners to take String[] arguments. 2025-01-09 22:32:59 +01:00
Christoffer Lerno
0857363470 Fix '\\' in -P output. 2025-01-09 20:46:36 +01:00
Christoffer Lerno
713199d7be Fix -P output. 2025-01-09 20:45:42 +01:00
Christoffer Lerno
b941f93416 Deprecate old void! @benchmark and @test functions. 2025-01-09 20:33:53 +01:00
Christoffer Lerno
c22b7d45c1 Update test failing on LLVM 17 2025-01-09 01:35:16 +01:00
Christoffer Lerno
c78bb45f2f Fix sample. 2025-01-09 01:33:58 +01:00
Christoffer Lerno
11f9365eb0 Remove all TODOs and make them strings to not crash things for -P output. 2025-01-09 01:32:21 +01:00
Christoffer Lerno
cdc1656f3a #foo style arguments were not type checked when given a type. #1790 2025-01-09 01:28:30 +01:00
Christoffer Lerno
8fb3ec73ff Deprecate $varef. 2025-01-08 23:56:10 +01:00
Christoffer Lerno
214e806a33 Deprecate `fn void! main() type main functions. 2025-01-08 23:17:50 +01:00
Christoffer Lerno
8e0d6d11b9 Deprecated '&' macro arguments. 2025-01-08 22:13:49 +01:00
Christoffer Lerno
9412b58d80 Remove trailing comma in project creation. 2025-01-08 13:44:09 +01:00
Christoffer Lerno
dad97fc2d9 Improved #foo resolution inside of the compiler.
Deprecation of several `&` macros.
2025-01-08 12:55:20 +01:00
vssukharev
ff33cc4dad Add Nix build and checks to CI/CD 2025-01-06 23:11:04 +01:00
Christoffer Lerno
51e0e5e66d Change ordering to simplify adding methods to type in conditional modules. 2025-01-06 22:36:29 +01:00
Christoffer Lerno
5fa6ecf9ae Enforce utf-8 on windows. 2025-01-06 20:02:57 +01:00
Christoffer Lerno
34c7f4e6b7 Deref subscripts as needed for macro ref method arguments. #1789 2025-01-06 13:54:02 +01:00
Christoffer Lerno
737559d3f8 Fix typo, minor changes. 2025-01-06 12:36:59 +01:00
Christoffer Lerno
f801372074 Optimize temp variables in LLVM. 2025-01-06 04:25:47 +01:00
Christoffer Lerno
ea2dce0ab4 Make "?:" lower in the frontend. 2025-01-06 03:01:13 +01:00
Christoffer Lerno
314c6f94f0 Remove the last "cast" operations. 2025-01-06 01:51:03 +01:00
Christoffer Lerno
8fd119e546 Refactor vector->array scalar->vector and slice->array casts to expressions. 2025-01-06 00:22:26 +01:00
Christoffer Lerno
8612476103 Refactor float<->float ptr->int and int->enum casts to expressions. 2025-01-05 23:16:48 +01:00
Louis Brauer
35812bd7ba Add String.trim_left() / right() (#1773)
* Add String.trim_left() / right()

* Fix formatting
2025-01-05 21:53:18 +01:00
Christoffer Lerno
f1ef2e8138 Improve @param parse errors #1777 2025-01-05 18:14:30 +01:00
robin
c47cb512ab Additional convenient functions and enums (#1767)
Added some missing API calls and enums when working with the Objective-C
Runtime.
2025-01-05 16:22:46 +01:00
KillerxDBr
fe7d4230d8 Fix 'clean' command on Windows MinGW
'c3c clean' command try to use 'rm' on Windows MinGW compiled executable, since it does not define "_MSC_VER", but define "_WIN32"
There are other uses of "_MSC_VER" in the same file, they probably can be changed to "_WIN32" too
2025-01-05 16:19:12 +01:00
Christoffer Lerno
b6e166f44d Include @name when searching for possible matches to name in the error message. #1779 2025-01-05 16:12:13 +01:00
Christoffer Lerno
ab2d223e71 Macros with trailing bodys aren't allowed as the single statement after a while loop with no body #1772. 2025-01-05 16:00:39 +01:00
Christoffer Lerno
c6c7baa3b4 Refactor casts, removing SLBOOL and PTRBOOL. 2025-01-05 15:45:39 +01:00
Christoffer Lerno
218f293cd4 Introducing int_to_ptr expr. 2025-01-05 15:26:20 +01:00
Christoffer Lerno
4d641d193c Refactoring removing cast types. 2025-01-05 15:09:30 +01:00
Christoffer Lerno
67ff78f1ca Fix not freeing a zero length String 2025-01-05 14:30:00 +01:00
vssukharev
4f0716ab13 Fix issue when building on NixOS, firstly occured under commit 819a85ee 2025-01-05 14:18:56 +01:00
Christoffer Lerno
07c59e6a6c Fix +a = 1 erronously being accepted. Refactorings. 2025-01-05 02:24:11 +01:00
Christoffer Lerno
86a674b87e Remove unused cast. 2025-01-04 23:25:55 +01:00
Christoffer Lerno
9957ab259c Fix vector float -> bool conversion. 2025-01-04 23:16:34 +01:00
Christoffer Lerno
4c3944f626 Refactor vec comparisons. 2025-01-04 23:06:21 +01:00
Christoffer Lerno
6f9b466d7c Optimize recast. 2025-01-04 21:58:04 +01:00
Thomas Adam
61badb6af7 libc: add isatty() 2025-01-04 15:41:51 +01:00
Christoffer Lerno
e31e57c7e7 Improved error message when accessing @private from other modules. Added convenience functions to Maybe. 2025-01-04 14:58:06 +01:00
Christoffer Lerno
469188044d Assert on certain slice to slice casts. #1768. 2025-01-04 13:31:47 +01:00
Christoffer Lerno
38063e5602 Refactor casts. 2025-01-04 13:04:08 +01:00
Bernd
ba5e2b7fa6 socket.c3: optimize poll() duration wrapping (#1762)
* socket.c3: optimize poll() duration wrapping
2025-01-04 00:32:17 +01:00
Christoffer Lerno
eed806962d Allow compile time $foreach iteration over constant Strings and bytes. 2025-01-04 00:26:21 +01:00
Christoffer Lerno
a1ce5e15ce Fix tests. 2025-01-04 00:09:24 +01:00
Christoffer Lerno
f0735c945a Update name to "validation" 2025-01-03 23:48:25 +01:00
Christoffer Lerno
d84e131b73 Add 'warnings' setting. 2025-01-03 23:37:37 +01:00
Christoffer Lerno
ad1511e69c Prohibit raw vaargs in regular functions with a function body. 2025-01-03 15:36:42 +01:00
Christoffer Lerno
db4dc114f2 $vasplat was allowed inside of a function when passed as an argument to a function. 2025-01-03 15:01:24 +01:00
Christoffer Lerno
a7f363ea43 Dynamic function lookup fails after changing type without dummy anycast due to poor tracing of typeid. #1761 2025-01-03 14:39:01 +01:00
Christoffer Lerno
0ccbba61ce Improve error message. 2025-01-03 12:07:54 +01:00
Christoffer Lerno
d921a4e168 Fix case when construct is using vaarg. 2025-01-03 12:05:10 +01:00
Thomas Adam
819a85ee06 build: add /usr/lib to LLVM_LIB search paths
It seems that on Alpine Linux (Edge), the LLVM_LIBRARY_DIRS cmake
variable doesn't look in /usr/lib, which is where the relevant files are
on Alpine.

Only do this for !WIN32 systems.
2025-01-03 11:47:00 +01:00
Christoffer Lerno
a3d15fe16c Fix issue with zero arg @operator(construct). Assert on add to uninitialized ct variable #1765 2025-01-03 11:45:46 +01:00
Christoffer Lerno
56d25cdeeb Remove array->vector casts 2025-01-03 01:35:51 +01:00
Louis Brauer
d027a15b4a add std::net::url - with fixes (#1748)
* add std::net::url for parsing/generating URLs

* Move String.index_of_chars into std

* Fix param contract

* Idiomatic type naming, Allman formatting, slicing, document functions

* Use String.tokenize

* Don't return str_view() from freed dstring

* Change indentation to tabs

* Variable casing according to guidlelines

* Updated API and added line to the releasenotes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-01-02 21:13:42 +01:00
Francesco Alemanno
a16316d7b4 enhance default hashing strategy for basic types (#1758)
* enhance default hashing strategy for basic types

* fix

* `$defined` in a global scope should accept testing normal macros.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-01-02 20:44:33 +01:00
Christoffer Lerno
14d8e93004 Return type inference bugs with macros #1757 2025-01-02 17:34:37 +01:00
Christoffer Lerno
37c62bf9b7 Update releasenotes. 2025-01-02 16:29:16 +01:00
Koni Marti
72839d7654 fix: net::poll() with negative timeout
If the timeout to net::poll() is -1, poll() will block until a requested
event occurs. However, in the poll() function, the Duration timeout is
converted to milliseconds (Duration is in microseconds). The conversion
involves integer arithmetics which results in a zero timeout (= -1 /
1000), i.e. poll() will return immediately and the following accept will
fail.

To fix this, only convert to milliseconds if timeout paramter is
non-negative.

Example to reproduce the error:

```
module echo;
import std::io, std::net;
fn void main()
{
	TcpServerSocket server = tcp::listen("localhost", 6969, 69, REUSEADDR)!!;
	server.sock.set_non_blocking(true)!!;
	Poll[*] polls = {{ .socket = server.sock, .events = net::SUBSCRIBE_READ }};
	if (catch net::poll(polls[..], net::POLL_FOREVER)) io::printn("poll error");
	TcpSocket client = tcp::accept(&server)!!;
	io::printn("OK");
}
```

Poll() will return immediately and the following tcp::accept() will fail
with an "ACCEPT_FAILED" error.

Reported-by: Alexey Kutepov <reximkut@gmail.com>
2025-01-02 16:28:26 +01:00
Christoffer Lerno
1994cba73e Fix default #foo args. 2025-01-02 15:23:21 +01:00
vssukharev
55cdcbb39b Fix typo in libc: SIGABTR -> SIGABRT 2025-01-01 21:36:33 +01:00
Christoffer Lerno
c7ce6230db Update test with target. 2025-01-01 21:02:16 +01:00
Christoffer Lerno
7c45ae24ae Macros with default arguments to &, # and type parameters didn't work as expected. #1754. 2025-01-01 17:52:32 +01:00
Christoffer Lerno
99c350fc43 Fix error where panic would not properly stop the program when stacktrace couldn't be printed #1751. 2025-01-01 13:00:04 +01:00
Christoffer Lerno
faf1c5cb64 Introduce EXPR_RVALUE for some additional refactoring. 2025-01-01 12:19:00 +01:00
Radek Micek
ece6efc75e Fix fputc 2025-01-01 12:17:58 +01:00
Christoffer Lerno
0a809ab5f0 Allow using 'var' to declare lambdas in functions. 2025-01-01 01:02:35 +01:00
Christoffer Lerno
78ff1a4af5 Support experimental @operator(construct) operator overload. 2025-01-01 00:45:42 +01:00
Christoffer Lerno
c0dcae4f1d Improve ordering of method registration to support adding methods to generic modules with method constraints #1746 2024-12-31 18:15:38 +01:00
Christoffer Lerno
5e32c8a828 Improve posix thread error handling. 2024-12-31 17:27:13 +01:00
Christoffer Lerno
4d15a2f45e Improve error reporting when using type names as the function argument #1750. 2024-12-31 16:59:51 +01:00
Christoffer Lerno
a913f21c45 Fix issue with compiling a constant struct containing a string array in a local context. 2024-12-31 16:45:10 +01:00
Christoffer Lerno
322c70433b Refactoring, stop using int to bool as cast. Merge make any. 2024-12-31 16:32:37 +01:00
Christoffer Lerno
ad9cfcdcc7 Fix typo. 2024-12-30 18:24:01 +01:00
Christoffer Lerno
4232c9d2b0 Fix bug when including compile time parameters in trailing body more than once. 2024-12-30 17:57:36 +01:00
antek-bizon
9edd59d280 Add compilation guide for Fedora 2024-12-30 13:56:29 +01:00
Christoffer Lerno
df74cbf06f Fix bug where !! and ! was not recognized to jump out of the current scope. Remove more casts. 2024-12-30 01:43:02 +01:00
Christoffer Lerno
5af224ab16 Remove 3 casts and replace them with a single new normal expression node. 2024-12-30 00:55:40 +01:00
vssukharev
7b734df09e Several nix fixes (#1737)
* Fixed nix c3c development shell

* Fix unknown git hash on nix build
2024-12-29 21:12:26 +01:00
Christoffer Lerno
1340a47bc2 - any_to_int checks value to be int and no longer works with enum.
- Add check in formatter printing "%c".
2024-12-29 17:07:00 +01:00
Book-reader
4f4476ba75 fix typo in @require statement in builtin.c3 2024-12-29 12:24:36 +01:00
Christoffer Lerno
5c7a183f8a Change CBool to be 1 byte. 2024-12-28 22:46:26 +01:00
Taylor W
53bada2a1e math::nolibc: atanh (#1730)
* math::nolibc: log1p

* math::no_libc: atanh

Added atanh nolibc definition and more test points in the math_tests
module.
2024-12-28 21:13:44 +01:00
Totto16
43efb7df2f fix: use helper printing macros, to use the correct printing width (e.g. %llu vs %lu) 2024-12-28 19:21:39 +01:00
Christoffer Lerno
f5cea221a6 Miscompile when indexing an array with small unsigned types. 2024-12-28 17:39:25 +01:00
Christoffer Lerno
b7082f34a1 C backend work. 2024-12-28 17:09:43 +01:00
Christoffer Lerno
d20d957881 Add @enum_from_value 2024-12-27 23:21:36 +01:00
Christoffer Lerno
008274cda5 Update the release notes. 2024-12-27 22:35:28 +01:00
Alexey Kutepov
e07ab7547f Fix Segfault and UX of the c3c project add-target subcommand (#1729)
* Fix UX of the `c3c project add-target` subcommand

- Fix segfault on `project.json` containing empty map `{}`
- Enable `add-target` to operate on non-existing project files
- Extend `add-target` syntax to accept source through CLI args

This enables the following workflow without friction and needless
crashes:

```console
$ cat > main.c3 <<END
import std::io;

fn void main() {
    io::printfn("Hello, World");
}
END
$ c3c project add-target main executable main.c3
$ c3c run
```

* Fix read_project() call in fetch_project()

* Keep the style of curlies consistent
2024-12-27 22:33:57 +01:00
Christoffer Lerno
cf10837eb8 Add static lib for MSVC 2024-12-27 21:46:08 +01:00
Christoffer Lerno
291b26f230 Add static lib. 2024-12-27 02:05:40 +01:00
Christoffer Lerno
08e8c9bf57 Use weak on dyn-symbols on Linux. 2024-12-27 02:05:40 +01:00
Christoffer Lerno
625152440c Use weak_odr rather than weak on Windows which seems to prevent issues such as #1704. Fix regression. 2024-12-26 21:35:41 +01:00
Aaron
fbd51821d1 Fixed -P JSON syntax, string conversion. (#1714)
* Fix JSON formatting in parse command, strings now "JSON-legal"

* Clean up

* Implemented JSON for CONST_BYTES

* Handle empty byte string, slightly more concise

* of course msvc is the *only* compiler that complains about this
2024-12-26 02:18:49 +01:00
Christoffer Lerno
c3ebf51295 Add "name" project property to override the name of the resulting binary. #1719 2024-12-26 02:15:45 +01:00
Christoffer Lerno
9a9ff7f32c Fix bug in OnStackAllocator when freeing overallocated data. # #1720 2024-12-25 23:57:47 +01:00
Christoffer Lerno
7b73eec82b Prevent DString from being initialized with "". 2024-12-25 23:40:58 +01:00
Christoffer Lerno
75ba4a1cdb Incorrectly handles distinct enums and pointers with '+=' and '-=' #1717. 2024-12-25 23:09:10 +01:00
Christoffer Lerno
e5ca9065bd Deprecate cast conversion from enum -> integer. 2024-12-25 20:34:28 +01:00
Taylor Wampler
1042d0825f Fixed typo in atan.c3 2024-12-25 19:15:59 +01:00
Christoffer Lerno
17942925f5 Fix bug in temp allocator when temp memory is exhausted and allocation needs overaligned mem. #1715 2024-12-25 17:56:37 +01:00
Christoffer Lerno
7424317d03 Fix call to copy. 2024-12-25 00:16:35 +01:00
Christoffer Lerno
dbf1d91961 Add --win-vs-dirs to override VS detection dirs. 2024-12-25 00:07:57 +01:00
Christoffer Lerno
eb1644b302 Change included common options. 2024-12-24 20:43:52 +01:00
Christoffer Lerno
cde5bc3263 Change included common options. 2024-12-24 20:39:24 +01:00
Taylor Wampler
e995e289db math::nolibc: acos and asin
There are now nolibc definitions for the inverse cosine and inverse
sine.

More test points were added for acos, asin, and atan in the math_tests module.
This was done becuase the nolibc inverse trigonometric functions have
various branching conditions depending on the provided input value. Several
branches in these functions were neglected.
2024-12-24 11:29:32 +01:00
Taylor W
5020caa9c3 C3_MATH feature (#1709)
* C3_MATH feature

This feature allows the usage of noclib math files even when libc is in use.
If a nolibc symbol exists, it will be used in place of libc, otherwise
it will default to libc.

* Added MIT License notices to atan.c3
2024-12-23 23:42:57 +01:00
Aaron
f34eb7d9f3 Prevent trailing _ for all numbers (#1706)
* moved _ check to scan_number_suffix
* Skipping underscore on list-operators command
* Disallow # from operator, update release notes
2024-12-23 21:23:46 +01:00
Guillaume M.
bf74ef0e5e Fix crt detection for Arch Linux (#1705)
* Fix crt detection for arch linux

On archlinux, the crt is located directly in /usr/lib. Thus, the globbing done in find_linux_crt fails since it expects the crt to be in a subdirectory of /usr/lib. This patch fixes this issue.
2024-12-23 20:03:53 +01:00
Christoffer Lerno
0ff52311c3 - Fix problem where crt1 was linked for dynamic libraries on Linux and BSD. #1710 2024-12-23 20:02:17 +01:00
Christoffer Lerno
e453e6f9ca - Add enum.from_ordinal and fault.from_ordinal
- Deprecate cast-style conversion from integer to enum.
- Make deprecation an error in test mode.
2024-12-23 15:27:59 +01:00
Christoffer Lerno
6078598aff - static-lib and dynamic-lib options from the command line now produces headers.
- Fix bug outputting exported functions without predefined extname.
- Removed 'headers' command line option.
2024-12-22 11:49:11 +01:00
Taylor W
9fdb3b3b4a Adding trigonometric and hyperbolic trigonometric tests (#1699)
* math_tests: rewrote test_atan()

Rewrote the atan test so that analagous checks are symmetrically
performed for all possible inputs: int, float, and double. The total
number of tests has increased while reducing the total amount of
code.

* math_tests: inverse trig and inverse hyperbolic trig

Tests were written for acos, acosh, asin, asinh, and atanh.

* math: cos macro missing values::promote_int

The cosine macro can't take an integer input without this fix. You can
see that some of the other macros, like the sine macro, have this.

* math_tests: trig and exponential

Wrote tests for the trigonometric macros as well as the
exponential macro. The hyperbolic trig macros use the exponential macro
rather than any LLVM instrinsics (for now at least, LLVM 19 has the proper
compiler intrinsics).

* math: float comparison

In the math module two macros were defined to assist in comparing
floating-point numbers for a given tolerance.

The trig, hyperbolic trig, and exponential tests in the math_tests module
were updated to use these macros instead of direct comparison.
2024-12-21 22:25:23 +01:00
Christoffer Lerno
1362aa655f Split help into normal and "full" help, #1703 2024-12-21 16:08:07 +01:00
Christoffer Lerno
9c22ab8925 Add "skip_empty" to split methods. Add split_to_buffer method. 2024-12-21 15:43:37 +01:00
Christoffer Lerno
ca2dbb2f4b Update Mingw build. 2024-12-20 21:56:28 +01:00
Christoffer Lerno
16bbc5a026 Add "tokenizer" to String. 2024-12-20 13:18:40 +01:00
Christoffer Lerno
627f10cd18 Fix bug when a macro calling an extern function was called in another module also declaring and calling the same function. #1690 2024-12-19 20:39:23 +01:00
Christoffer Lerno
13509b9231 Remove LLVM 20 from testing due to broken build. 2024-12-19 19:51:50 +01:00
Tomas Kallup
ca88afbf5b Fix hashmap put all for create + test case
Closes: #1695
2024-12-19 16:15:18 +01:00
vssukharev
42c9c9894b Slight updates in nix 2024-12-18 21:04:42 +01:00
Christoffer Lerno
b4de62cfc2 Added iter() value_iter() and key_iter() to HashMap. 2024-12-18 21:02:28 +01:00
Christoffer Lerno
226fbc191b Update to Slice2d 2024-12-17 23:12:32 +01:00
Denis Palashevskii
4839d8861d increase BitWriter.write_bits nbits limit, add tests (#1692)
* increase BitWriter.write_bits nbits limit, add tests

* update releasenotes.md
2024-12-17 21:21:58 +01:00
Brecht Sanders
789b47d565 Support detection of shared MinGW-w64 LLVM libraries
Support detection of shared MinGW-w64 LLVM libraries by also looking for liblld*.dll.a files.
2024-12-17 11:29:40 +01:00
Walther Chen
c13cdcdd36 in new_struct_to_str, fix uaf 2024-12-17 11:29:18 +01:00
Christoffer Lerno
4ae3d0150f Fix case trying to initialize a char[*]* from a String. 2024-12-15 20:42:42 +01:00
Christoffer Lerno
7d153a162a Prepare 0.6.6 2024-12-14 23:06:08 +01:00
1011 changed files with 38977 additions and 21180 deletions

View File

@@ -11,7 +11,7 @@ env:
LLVM_RELEASE_VERSION_MAC: 17
LLVM_RELEASE_VERSION_LINUX: 17
LLVM_RELEASE_VERSION_UBUNTU20: 17
LLVM_DEV_VERSION: 20
LLVM_DEV_VERSION: 21
jobs:
build-msvc:
@@ -51,46 +51,52 @@ jobs:
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
cd resources/testproject
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32
dir build\llvm_ir
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32 --trust=full
dir build\llvm\windows-x64
..\..\build\${{ matrix.build_type }}\c3c.exe clean
dir build\llvm_ir
dir build\llvm\windows-x64
- name: Build testproject lib
run: |
cd resources/testproject
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv build hello_world_win32_lib
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv build hello_world_win32_lib --trust=full
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
..\..\..\build\${{ matrix.build_type }}\c3c.exe dynamic-lib add.c3
..\..\..\build\${{ matrix.build_type }}\c3c.exe compile-run test.c3 -l ./add.lib
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv dynamic-lib add.c3
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv compile-run test.c3 -l ./add.lib
- name: Compile and run staticlib-test
run: |
cd resources/examples/staticlib-test
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv static-lib add.c3
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv compile-run test.c3 -l ./add.lib
- name: Vendor-fetch
run: |
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55_v7
- name: Try raylib5
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_arkanoid.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_snake.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_tetris.c3
- name: run compiler tests
run: |
cd test
python3.exe src/tester.py ..\build\${{ matrix.build_type }}\c3c.exe test_suite/
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55_v7
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55_v7 --print-linking examples\raylib\raylib_arkanoid.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55_v7 --print-linking examples\raylib\raylib_snake.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55_v7 --print-linking examples\raylib\raylib_tetris.c3
- name: Compile run unit tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
- name: run compiler tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ..\build\${{ matrix.build_type }}\c3c.exe test_suite/ --no-terminal
- name: Test python script
run: |
py msvc_build_libraries.py --accept-license
@@ -106,7 +112,7 @@ jobs:
build-msys2-mingw:
runs-on: windows-latest
# if: ${{ false }}
if: ${{ false }}
strategy:
# Don't abort runners if a single one fails
fail-fast: false
@@ -126,17 +132,18 @@ jobs:
install: git binutils mingw-w64-x86_64-clang mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
- shell: msys2 {0}
run: |
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-19.1.4-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-19.1.4-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-20.1.0-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-20.1.0-1-any.pkg.tar.zst
- name: CMake
run: |
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_LINKER=lld
cmake --build build
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile-run --print-linking examples/hello_world_many.c3
../build/c3c compile-run --print-linking examples/process.c3
../build/c3c compile-run --print-linking examples/time.c3
../build/c3c compile-run --print-linking examples/fannkuch-redux.c3
../build/c3c compile-run --print-linking examples/contextfree/boolerr.c3
@@ -147,22 +154,22 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
../../build/c3c run -vvv --trust=full
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib5
./build/c3c vendor-fetch raylib55_v7
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib --cc cc -vvv
../../build/c3c build hello_world_lib --cc cc -vvv --trust=full
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c.exe test_suite/
../build/c3c.exe compile --target windows-x64 -O1 src/test_suite_runner.c3
./test_suite_runner.exe ../build/c3c.exe test_suite/ --no-terminal
build-msys2-clang:
runs-on: windows-latest
@@ -203,20 +210,20 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
../../build/c3c run -vvv --trust=full
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib -vvv
../../build/c3c build hello_world_lib -vvv --trust=full
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c.exe test_suite/
../build/c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c.exe test_suite/ --no-terminal
build-linux:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
strategy:
# Don't abort runners if a single one fails
fail-fast: false
@@ -320,12 +327,21 @@ jobs:
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
../../../build/c3c dynamic-lib add.c3
../../../build/c3c -vv dynamic-lib add.c3
mv add.so libadd.so
cc test.c -L. -ladd -Wl,-rpath=.
./a.out
../../../build/c3c compile-run test.c3 -L . -l add -z -Wl,-rpath=.
- name: Compile and run staticlib-test
run: |
cd resources/examples/staticlib-test
../../../build/c3c -vv static-lib add.c3
mv add.a libadd.a
cc test.c -L. -ladd
./a.out
../../../build/c3c compile-run test.c3 -L . -l add
- name: Compile run unit tests
run: |
cd test
@@ -334,7 +350,7 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
../../build/c3c run -vvv --trust=full
- name: Test WASM
run: |
@@ -353,7 +369,7 @@ jobs:
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin
../../build/c3c run -vvv --linker=builtin --trust=full
- name: Init a library & a project
run: |
@@ -365,7 +381,7 @@ jobs:
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c test_suite/
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
@@ -374,6 +390,8 @@ jobs:
cp -r lib c3
cp msvc_build_libraries.py c3
cp build/c3c c3
cp README.md c3
cp releasenotes.md c3
tar czf c3-linux-${{matrix.build_type}}.tar.gz c3
- name: upload artifacts
@@ -471,28 +489,30 @@ jobs:
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit
../build/c3c compile-test unit --sanitize=address
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
../../build/c3c run -vvv --trust=full
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin
../../build/c3c run -vvv --linker=builtin --trust=full
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c test_suite/
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
run: |
mkdir c3
cp -r lib c3
cp README.md c3
cp releasenotes.md c3
cp msvc_build_libraries.py c3
cp build/c3c c3
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz c3
@@ -505,13 +525,13 @@ jobs:
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
build-with-docker:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
ubuntu_version: [20.04, 22.04]
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20]
llvm_version: [17, 18, 19]
steps:
- uses: actions/checkout@v4
@@ -568,7 +588,7 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
../../build/c3c run -vvv --trust=full
- name: Test WASM
run: |
@@ -578,7 +598,7 @@ jobs:
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin
../../build/c3c run -vvv --linker=builtin --trust=full
- name: Init a library & a project
run: |
@@ -590,7 +610,7 @@ jobs:
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c test_suite/
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
build-mac:
runs-on: macos-latest
@@ -622,7 +642,7 @@ jobs:
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib5
./build/c3c vendor-fetch raylib55_v7
- name: Compile and run some examples
run: |
@@ -639,33 +659,44 @@ jobs:
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
../../../build/c3c dynamic-lib add.c3
../../../build/c3c -vv dynamic-lib add.c3
../../../build/c3c compile-run test.c3 -l ./add.dylib
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit
../build/c3c compile-test unit -O1
- name: Test WASM
run: |
cd resources/testfragments
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
../../build/c3c run -vvv --trust=full
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin
../../build/c3c run -vvv --linker=builtin --trust=full
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib -vvv
../../build/c3c build hello_world_lib -vvv --trust=full
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c test_suite/
../build/c3c compile -O1 src/test_suite_runner.c3
./test_suite_runner ../build/c3c test_suite/
- name: run build test suite runner
run: |
cd test
../build/c3c compile -O1 src/test_suite_runner.c3
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
@@ -673,6 +704,8 @@ jobs:
mkdir macos
cp -r lib macos
cp msvc_build_libraries.py macos
cp README.md macos
cp releasenotes.md macos
cp build/c3c macos
zip -r c3-macos-${{matrix.build_type}}.zip macos
@@ -683,41 +716,55 @@ jobs:
name: c3-macos-${{matrix.build_type}}
path: c3-macos-${{matrix.build_type}}.zip
build-nix:
runs-on: ubuntu-22.04
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [ Release, Debug ]
nixpkgs: [ Lock, Latest ]
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: Update flake (if necessary)
run: |
if [[ matrix.nixpkgs == "Latest" ]]; then
nix flake update
fi
nix flake info
- name: Build and check
run: |
if [[ ${{ matrix.build_type }} = "Debug" ]]; then
nix build -L ".#c3c-debug-checks"
else
nix build -L ".#c3c-checks"
fi
release:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu20]
if: github.ref == 'refs/heads/master'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: delete tag
continue-on-error: true
uses: actions/github-script@v6
with:
script: |
github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'tags/latest',
sha: context.sha
})
- name: create tag
uses: actions/github-script@v6
with:
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/latest',
sha: context.sha
})
- uses: actions/download-artifact@v4
- run: cp -r lib c3-windows-Release
- run: cp -r lib c3-windows-Debug
- run: cp msvc_build_libraries.py c3-windows-Release
- run: cp msvc_build_libraries.py c3-windows-Debug
- run: cp README.md c3-windows-Release
- run: cp README.md c3-windows-Debug
- run: cp releasenotes.md c3-windows-Release
- run: cp releasenotes.md c3-windows-Debug
- run: zip -r c3-windows.zip c3-windows-Release
- run: zip -r c3-windows-debug.zip c3-windows-Debug
- run: mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux-Release/c3-linux.tar.gz
@@ -726,14 +773,16 @@ jobs:
- run: mv c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
- run: mv c3-macos-Release/c3-macos-Release.zip c3-macos-Release/c3-macos.zip
- run: mv c3-macos-Debug/c3-macos-Debug.zip c3-macos-Debug/c3-macos-debug.zip
- run: gh release delete latest-0.7.0-prerelease --cleanup-tag -y || true
- run: echo "RELEASE_NAME=latest-0.7.0-rc-$(date +'%Y%m%d-%H%M')" >> $GITHUB_ENV
- id: create_release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: latest
release_name: latest
tag_name: latest-0.7.0-prerelease
name: ${{ env.RELEASE_NAME }}
draft: false
prerelease: true
files: |

7
.gitignore vendored
View File

@@ -79,3 +79,10 @@ TAGS
# 'nix build' resulting symlink
result
# macOS
.DS_Store
# tests
/test/tmp/*
/test/testrun
/test/test_suite_runner

View File

@@ -38,14 +38,14 @@ set(CMAKE_CXX_STANDARD 17)
if(MSVC)
message(STATUS "MSVC version ${MSVC_VERSION}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
else()
if (true)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O0 -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O0 -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
else()
@@ -65,6 +65,7 @@ option(C3_USE_TB "Use TB" OFF)
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
option(C3_WITH_LLVM "Build with LLVM" ON)
option(C3_LLD_DIR "Use custom LLD directory" "")
option(LLVM_CRT_LIBRARY_DIR "Use custom llvm's compiler-rt directory" "")
set(C3_USE_MIMALLOC OFF)
if(C3_USE_MIMALLOC)
@@ -84,7 +85,7 @@ if (NOT WIN32)
endif()
if(C3_WITH_LLVM)
if (NOT C3_LLVM_VERSION STREQUAL "auto")
if (${C3_LLVM_VERSION} VERSION_LESS 17 OR ${C3_LLVM_VERSION} VERSION_GREATER 20)
if (${C3_LLVM_VERSION} VERSION_LESS 17 OR ${C3_LLVM_VERSION} VERSION_GREATER 21)
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
endif()
endif()
@@ -150,6 +151,13 @@ if(C3_WITH_LLVM)
endif()
endif()
if (EXISTS /usr/lib)
# Some systems (such as Alpine Linux) seem to put some of the relevant
# LLVM files in /usr/lib, but this doesn't seem to be included in the
# value of LLVM_LIBRARY_DIRS.
list(APPEND LLVM_LIBRARY_DIRS /usr/lib)
endif()
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
@@ -215,12 +223,12 @@ if(C3_WITH_LLVM)
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COFF NAMES liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.dll.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.dll.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.dll.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.dll.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.dll.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
else()
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(llvm_libs ${LLVM})
@@ -245,19 +253,19 @@ if(C3_WITH_LLVM)
find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(lld_libs
${LLD_COFF}
${LLD_COMMON}
${LLD_WASM}
${LLD_MINGW}
${LLD_ELF}
${LLD_MACHO}
${LLD_COMMON}
)
if (APPLE)
set(lld_libs ${lld_libs} xar)
find_file(RT_ASAN_DYNAMIC NAMES libclang_rt.asan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
find_file(RT_TSAN_DYNAMIC NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
find_file(RT_UBSAN_DYNAMIC NAMES libclang_rt.ubsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
find_file(RT_LSAN_DYNAMIC NAMES libclang_rt.lsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
find_file(RT_ASAN_DYNAMIC NAMES libclang_rt.asan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
find_file(RT_TSAN_DYNAMIC NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
find_file(RT_UBSAN_DYNAMIC NAMES libclang_rt.ubsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
find_file(RT_LSAN_DYNAMIC NAMES libclang_rt.lsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
set(sanitizer_runtime_libraries
${RT_ASAN_DYNAMIC}
${RT_TSAN_DYNAMIC}
@@ -342,6 +350,7 @@ add_executable(c3c
src/utils/whereami.c
src/utils/cpus.c
src/utils/unzipper.c
src/compiler/c_codegen.c
src/compiler/decltable.c
src/compiler/mac_support.c
src/compiler/windows_support.c

View File

@@ -74,7 +74,7 @@ No space inside parenthesis:
### Tab vs spaces
Recommendation: tabs, 4 spaces wide. No CRLF in the source.
Use tabs for indentation, no CRLF in the source.
### If, braces and new lines
@@ -147,4 +147,113 @@ Iterating over the elements are done using `VECEACH`.
### Scratch buffer for strings.
There is a scratch buffer for strings in the `global_context` prefer using that
one with related functions when working on temporary strings.
one with related functions when working on temporary strings.
# C3 Standard library style guide.
When contributing to the standard librairy please to your best to follow the following style requirements
as to ensure a consistent style in the stdlib and also make accepting PRs more quickly.
### Braces are placed on the next line
**NO:**
```c
fn void foo(String bar) {
@pool() {
...
};
}
```
**YES:**
```c
fn void foo(String bar)
{
@pool()
{
...
};
}
```
### Indentation with tabs
Use tab for indentation, not spaces, no CRLF in the sources
### Type names
Use `PascalCase` not `Ada_Case` for type names.
**YES:**
```c
enum MyEnum
{
ABC,
DEF
}
```
**NO:**
```c
enum My_Enum
{
ABC,
DEF
}
```
### Type names when binding to OS libraries
When doing bindings (for instance, adding declarations referring to Win32 APIs),
try to retain the original name when possible. If it isn't possible use (consistently)
one of two options:
1. Prefix: `HANDLE` -> `Win32_HANDLE`
2. Change the first letter to upper case: `mode_t` -> `Mode_t`
### Variables, function, methods and globals
Use `snake_case`, not `camelCase`.
**YES:**
```c
int some_global = 1;
fn void open_file(String special_file)
{
...
}
```
**NO:**
```c
int someGlobal = 1;
fn void openFile(String specialFile)
{
...
}
```
### Variables, function, methods and globals when binding to OS libraries
When doing bindings (for instance, adding declarations referring to Win32 APIs),
try to retain the original name when possible. If it isn't possible use (consistently)
one of two options:
1. Prefix: `win32_GetWindowLongPtrW`. However, this is usually only recommended if it is builtin.
2. Change first character to lower case: `GetWindowLongPtrW` -> `getWindowLongPtrW`
### Use `self` as the first method argument
Unless there is a strong reason not to, use `self` for the first parameter in a method.
### The allocator argument
Prefer always calling the allocator parameter `allocator`, and make it the first regular
argument.
## Add tests to your changes
If you add or fix things, then there should always be tests in `test/unit/stdlib` to verify
the functionality.

View File

@@ -11,7 +11,7 @@ Precompiled binaries for the following operating systems are available:
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-mac-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
@@ -36,7 +36,7 @@ whole new language.
The following code shows [generic modules](https://c3-lang.org/references/docs/generics/) (more examples can be found at https://c3-lang.org/references/docs/examples/).
```cpp
module stack (<Type>);
module stack {Type};
// Above: the parameterized type is applied to the entire module.
struct Stack
@@ -80,13 +80,13 @@ import stack;
// Define our new types, the first will implicitly create
// a complete copy of the entire Stack module with "Type" set to "int"
def IntStack = Stack(<int>);
alias IntStack = Stack {int};
// The second creates another copy with "Type" set to "double"
def DoubleStack = Stack(<double>);
alias DoubleStack = Stack {double};
// If we had added "define IntStack2 = Stack(<int>)"
// If we had added "alias IntStack2 = Stack {int}"
// no additional copy would have been made (since we already
// have an parameterization of Stack(<int>)) so it would
// have an parameterization of Stack {int} so it would
// be same as declaring IntStack2 an alias of IntStack
// Importing an external C function is straightforward
@@ -138,9 +138,9 @@ fn void main()
### Current status
The current stable version of the compiler is **version 0.6.4**.
The current stable version of the compiler is **version 0.6.8**.
The upcoming 0.6.x releases will focus on expanding the standard library.
The the next version is 0.7.0 which will be a breaking release.
Follow the issues [here](https://github.com/c3lang/c3c/issues).
If you have suggestions on how to improve the language, either [file an issue](https://github.com/c3lang/c3c/issues)
@@ -249,20 +249,13 @@ makepkg -si
#### Building via Docker
You can build `c3c` using either an Ubuntu 18.04 or 20.04 container:
You can build `c3c` using an Ubuntu container. By default, the script will build through Ubuntu 22.04. You can specify the version by passing the `UBUNTU_VERSION` environment variable.
```
./build-with-docker.sh 18
UBUNTU_VERSION=20.04 ./build-with-docker.sh
```
Replace `18` with `20` to build through Ubuntu 20.04.
For a release build specify:
```
./build-with-docker.sh 20 Release
```
A `c3c` executable will be found under `bin/`.
See the `build-with-docker.sh` script for more information on other configurable environment variables.
#### Installing on OS X using Homebrew
@@ -275,6 +268,14 @@ A `c3c` executable will be found under `bin/`.
8. Set up CMake build for debug: `cmake ..`
9. Build: `cmake --build .`
#### Installing on Windows using Scoop
c3c is included in 'Main' bucket.
```sh
scoop install c3
```
#### Getting started with a "hello world"
Create a `main.c3` file with:
@@ -349,6 +350,20 @@ Your c3c executable should have compiled properly. You may want to test it: `./c
For a sytem-wide installation, run the following as root: `cmake --install .`
#### Compiling on Fedora
1. Install required project dependencies: `dnf install cmake clang git llvm llvm-devel lld lld-devel ncurses-devel`
2. Optionally, install additional dependencies: `dnf install libcurl-devel zlib-devel libzstd-devel libxml2-devel libffi-devel`
3. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
4. Enter the C3C directory: `cd c3c`
5. Create a build directory and navigate into it: `mkdir build && cd build`
6. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake .. -DC3_LINK_DYNAMIC=1`
7. Build the project: `cmake --build .`
The c3c binary should be created in the build directory. You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
#### Compiling on other Linux / Unix variants
1. Install CMake.
@@ -388,3 +403,7 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
A huge **THANK YOU** goes out to all contributors and sponsors.
A special thank you to sponsors [Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad) for going the extra mile.
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=c3lang/c3c&type=Date)](https://www.star-history.com/#c3lang/c3c&Date)

View File

@@ -8,7 +8,7 @@ fn void init() @init
set_benchmark_max_iterations(10_000);
}
fn void! quicksort_bench() @benchmark
fn void quicksort_bench() @benchmark
{
// test set: 500 numbers between 0 and 99;
int[] data = {

12
flake.lock generated
View File

@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1730958623,
"narHash": "sha256-JwQZIGSYnRNOgDDoIgqKITrPVil+RMWHsZH1eE1VGN0=",
"lastModified": 1738297584,
"narHash": "sha256-AYvaFBzt8dU0fcSK2jKD0Vg23K2eIRxfsVXIPCW9a0E=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "85f7e662eda4fa3a995556527c87b2524b691933",
"rev": "9189ac18287c599860e878e905da550aa6dec1cd",
"type": "github"
},
"original": {

View File

@@ -8,26 +8,35 @@
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem
(system:
let pkgs = import inputs.nixpkgs { inherit system; }; in
{
let pkgs = import inputs.nixpkgs { inherit system; };
call = set: pkgs.callPackage ./nix/default.nix (
set // {
rev = self.rev or "unknown";
}
);
in {
packages = {
default = self.packages.${system}.c3c;
c3c = pkgs.callPackage ./nix/default.nix {};
c3c = call {};
c3c-checks = pkgs.callPackage ./nix/default.nix {
checks = true;
};
c3c-debug = pkgs.callPackage ./nix/default.nix {
debug = true;
};
c3c-nochecks = pkgs.callPackage ./nix/default.nix {
c3c-debug-checks = pkgs.callPackage ./nix/default.nix {
debug = true;
checks = false;
checks = true;
};
};
devShells = {
default = pkgs.callPackage ./nix/shell.nix {
c3c = self.packages.${system}.c3c-nochecks;
c3c = self.packages.${system}.c3c-debug;
};
};
}

View File

@@ -4,6 +4,7 @@ set(GIT_HASH "unknown")
if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git")
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY)

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::atomic::types(<Type>);
module std::atomic::types{Type};
struct Atomic
{
@@ -11,7 +11,7 @@ struct Atomic
<*
Loads data atomically, by default this uses SEQ_CONSISTENT ordering.
@param ordering "The ordering, cannot be release or acquire-release."
@param ordering : "The ordering, cannot be release or acquire-release."
@require ordering != RELEASE && ordering != ACQUIRE_RELEASE : "Release and acquire-release are not valid for load"
*>
macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
@@ -31,7 +31,7 @@ macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
<*
Stores data atomically, by default this uses SEQ_CONSISTENT ordering.
@param ordering "The ordering, cannot be acquire or acquire-release."
@param ordering : "The ordering, cannot be acquire or acquire-release."
@require ordering != ACQUIRE && ordering != ACQUIRE_RELEASE : "Acquire and acquire-release are not valid for store"
*>
macro void Atomic.store(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
@@ -76,7 +76,7 @@ macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTEN
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_div, data, value, ordering);
return @atomic_exec(atomic::fetch_max, data, value, ordering);
}
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
@@ -85,36 +85,49 @@ macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTEN
return @atomic_exec(atomic::fetch_min, data, value, ordering);
}
macro Type Atomic.or(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro Type Atomic.or(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_or, data, value, ordering);
}
fn Type Atomic.xor(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
fn Type Atomic.xor(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
}
macro Type Atomic.and(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro Type Atomic.and(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_and, data, value, ordering);
}
macro Type Atomic.shift_right(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro Type Atomic.shr(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
}
macro Type Atomic.shift_left(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro Type Atomic.shl(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
}
macro Type Atomic.set(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
{
Type* data = &self.data;
return @atomic_exec_no_arg(atomic::flag_set, data, ordering);
}
macro Type Atomic.clear(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
{
Type* data = &self.data;
return @atomic_exec_no_arg(atomic::flag_clear, data, ordering);
}
macro @atomic_exec(#func, data, value, ordering) @local
{
switch(ordering)
@@ -128,18 +141,58 @@ macro @atomic_exec(#func, data, value, ordering) @local
}
}
macro @atomic_exec_no_arg(#func, data, ordering) @local
{
switch(ordering)
{
case RELAXED: return #func(data, RELAXED);
case ACQUIRE: return #func(data, ACQUIRE);
case RELEASE: return #func(data, RELEASE);
case ACQUIRE_RELEASE: return #func(data, ACQUIRE_RELEASE);
case SEQ_CONSISTENT: return #func(data, SEQ_CONSISTENT);
default: unreachable("Ordering may not be non-atomic or unordered.");
}
}
module std::atomic;
import std::math;
macro bool @is_native_atomic_value(#value) @private
{
return is_native_atomic_type($typeof(#value));
}
macro bool is_native_atomic_type($Type)
{
$if $Type.sizeof > void*.sizeof:
return false;
$else
$switch $Type.kindof:
$case SIGNED_INT:
$case UNSIGNED_INT:
$case POINTER:
$case FLOAT:
$case BOOL:
return true;
$case DISTINCT:
return is_native_atomic_type($typefrom($Type.inner));
$default:
return false;
$endswitch
$endif
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be added to ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr + y) : "+ must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -150,14 +203,16 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be subtracted from ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr - y) : "- must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -168,13 +223,15 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be multiplied with ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr * y) : "* must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -193,25 +250,28 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
$StorageType storage_old_value;
$StorageType storage_new_value;
do {
do
{
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = old_value * y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
}
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to divide ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr * y) : "/ must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -230,154 +290,80 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
$StorageType storage_old_value;
$StorageType storage_new_value;
do {
do
{
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = old_value / y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
}
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise or with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require types::is_int($typeof(y)) "The value for or must be an int"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr | y) : "| must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
$if types::is_int($typeof(*ptr)):
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
$endif
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value | storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise xor with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require types::is_int($typeof(y)) "The value for or must be an int"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "^ must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
$if types::is_int($typeof(*ptr)):
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
$endif
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value ^ storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise and with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require types::is_int($typeof(y)) "The value for or must be an int"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "& must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
$if types::is_int($typeof(*ptr)):
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
$endif
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value & storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to shift ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require types::is_int($typeof(y)) "The value for or must be an int"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift right must be an integer"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -397,25 +383,29 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
do
{
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value >> storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
}
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to shift ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require types::is_int($typeof(y)) "The value for or must be an int"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift left must be an integer"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -435,64 +425,83 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
do
{
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value << storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
}
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
$typeof(*ptr) old_value;
$typeof(*ptr) new_value = true;
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
do
{
old_value = $$atomic_load(ptr, false, $load_ordering.ordinal);
}
while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
do {
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
return old_value;
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
$typeof(*ptr) old_value;
$typeof(*ptr) new_value = false;
do {
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
do
{
old_value = $$atomic_load(ptr, false, $load_ordering.ordinal);
}
while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
return old_value;
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be compared to ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr > y) : "Only values that are comparable with > may be used"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -503,13 +512,15 @@ macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be compared to ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr > y) : "Only values that are comparable with > may be used"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{

View File

@@ -3,8 +3,9 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::atomic;
macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment) {
switch(failure)
macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment)
{
switch (failure)
{
case AtomicOrdering.RELAXED.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.RELAXED.ordinal, $alignment);
case AtomicOrdering.ACQUIRE.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.ACQUIRE.ordinal, $alignment);
@@ -16,7 +17,7 @@ macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $succe
macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, success, failure, $alignment)
{
switch(success)
switch (success)
{
case AtomicOrdering.RELAXED.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELAXED.ordinal, failure, $alignment);
case AtomicOrdering.ACQUIRE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE.ordinal, failure, $alignment);
@@ -28,7 +29,7 @@ macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, succes
return 0;
}
fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, CInt success, CInt failure) @extern("__atomic_compare_exchange") @export
fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, CInt success, CInt failure) @weak @export("__atomic_compare_exchange")
{
switch (size)
{

View File

@@ -1,12 +1,12 @@
module std::bits;
<*
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
@require types::is_intlike($typeof(i)) : `The input must be an integer or integer vector`
*>
macro reverse(i) => $$bitreverse(i);
<*
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
@require types::is_intlike($typeof(i)) : `The input must be an integer or integer vector`
*>
macro bswap(i) @builtin => $$bswap(i);

View File

@@ -1,11 +1,11 @@
// Copyright (c) 2024 Christoffer Lerno. All rights reserved.
// Copyright (c) 2024-2025 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::anylist;
import std::io,std::math;
def AnyPredicate = fn bool(any value);
def AnyTest = fn bool(any type, any context);
alias AnyPredicate = fn bool(any value);
alias AnyTest = fn bool(any type, any context);
struct AnyList (Printable)
{
@@ -17,18 +17,8 @@ struct AnyList (Printable)
<*
Use `init` for to use a custom allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = null)
{
return self.init(allocator ?: allocator::heap(), initial_capacity) @inline;
}
<*
@param [&inout] allocator "The allocator to use"
@param initial_capacity "The initial capacity to reserve"
@param [&inout] allocator : "The allocator to use"
@param initial_capacity : "The initial capacity to reserve"
*>
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
{
@@ -50,14 +40,16 @@ fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
<*
Initialize the list using the temp allocator.
@param initial_capacity "The initial capacity to reserve"
@param initial_capacity : "The initial capacity to reserve"
*>
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16)
fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16)
{
return self.init(allocator::temp(), initial_capacity) @inline;
return self.init(tmem, initial_capacity) @inline;
}
fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
fn bool AnyList.is_initialized(&self) @inline => self.allocator != null;
fn usz? AnyList.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
@@ -77,25 +69,12 @@ fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
}
}
fn String AnyList.to_new_string(&self, Allocator allocator = null) @dynamic
{
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
}
fn String AnyList.to_string(&self, Allocator allocator) @dynamic
{
return string::format("%s", *self, allocator: allocator);
}
fn String AnyList.to_tstring(&self) => string::tformat("%s", *self);
<*
Push an element on the list by cloning it.
*>
macro void AnyList.push(&self, element)
{
if (!self.allocator) self.allocator = allocator::heap();
if (!self.allocator) self.allocator = tmem;
self.append_internal(allocator::clone(self.allocator, element));
}
@@ -117,56 +96,40 @@ fn void AnyList.free_element(&self, any element) @inline
Pop a value who's type is known. If the type is incorrect, this
will still pop the element.
@return! CastResult.TYPE_MISMATCH, IteratorResult.NO_MORE_ELEMENT
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
macro AnyList.pop(&self, $Type)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return *anycast(self.entries[--self.size], $Type);
}
<*
Pop the last value and allocate the copy using the given allocator.
@return! IteratorResult.NO_MORE_ELEMENT
@return? NO_MORE_ELEMENT
*>
fn any! AnyList.copy_pop(&self, Allocator allocator = allocator::heap())
fn any? AnyList.copy_pop(&self, Allocator allocator)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return allocator::clone_any(allocator, self.entries[--self.size]);
}
<*
Pop the last value and allocate the copy using the given allocator.
@return! IteratorResult.NO_MORE_ELEMENT
@deprecated `use copy_pop`
*>
fn any! AnyList.new_pop(&self, Allocator allocator = allocator::heap())
{
return self.copy_pop(allocator);
}
<*
Pop the last value and allocate the copy using the temp allocator
@return! IteratorResult.NO_MORE_ELEMENT
@deprecated `use tcopy_pop`
@return? NO_MORE_ELEMENT
*>
fn any! AnyList.temp_pop(&self) => self.copy_pop(allocator::temp());
<*
Pop the last value and allocate the copy using the temp allocator
@return! IteratorResult.NO_MORE_ELEMENT
*>
fn any! AnyList.tcopy_pop(&self) => self.copy_pop(allocator::temp());
fn any? AnyList.tcopy_pop(&self) => self.copy_pop(tmem);
<*
Pop the last value. It must later be released using list.free_element()
@return! IteratorResult.NO_MORE_ELEMENT
@return? NO_MORE_ELEMENT
*>
fn any! AnyList.pop_retained(&self)
fn any? AnyList.pop_retained(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
@@ -184,7 +147,7 @@ fn void AnyList.clear(&self)
*>
macro AnyList.pop_first(&self, $Type)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return *anycast(self.entries[0], $Type);
}
@@ -192,28 +155,20 @@ macro AnyList.pop_first(&self, $Type)
<*
Same as pop_retained() but pops the first value instead.
*>
fn any! AnyList.pop_first_retained(&self)
fn any? AnyList.pop_first_retained(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
<*
Same as new_pop() but pops the first value instead.
@deprecated `use copy_pop_first`
*>
fn any! AnyList.new_pop_first(&self, Allocator allocator = allocator::heap())
{
return self.copy_pop_first(allocator) @inline;
}
<*
Same as new_pop() but pops the first value instead.
Same as copy_pop() but pops the first value instead.
*>
fn any! AnyList.copy_pop_first(&self, Allocator allocator = allocator::heap())
fn any? AnyList.copy_pop_first(&self, Allocator allocator)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
defer self.remove_at(0);
return allocator::clone_any(allocator, self.entries[0]);
@@ -222,13 +177,7 @@ fn any! AnyList.copy_pop_first(&self, Allocator allocator = allocator::heap())
<*
Same as temp_pop() but pops the first value instead.
*>
fn any! AnyList.tcopy_pop_first(&self) => self.copy_pop_first(allocator::temp());
<*
Same as temp_pop() but pops the first value instead.
@deprecated `use tcopy_pop_first`
*>
fn any! AnyList.temp_pop_first(&self) => self.new_pop_first(allocator::temp());
fn any? AnyList.tcopy_pop_first(&self) => self.copy_pop_first(tmem);
<*
@require index < self.size
@@ -322,9 +271,9 @@ macro AnyList.first(&self, $Type)
return *anycast(self.first_any(), $Type);
}
fn any! AnyList.first_any(&self) @inline
fn any? AnyList.first_any(&self) @inline
{
return self.size ? self.entries[0] : IteratorResult.NO_MORE_ELEMENT?;
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
}
macro AnyList.last(&self, $Type)
@@ -332,9 +281,9 @@ macro AnyList.last(&self, $Type)
return *anycast(self.last_any(), $Type);
}
fn any! AnyList.last_any(&self) @inline
fn any? AnyList.last_any(&self) @inline
{
return self.size ? self.entries[self.size - 1] : IteratorResult.NO_MORE_ELEMENT?;
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?;
}
fn bool AnyList.is_empty(&self) @inline
@@ -348,7 +297,7 @@ fn usz AnyList.len(&self) @operator(len) @inline
}
<*
@require index < self.size "Index out of range"
@require index < self.size : "Index out of range"
*>
macro AnyList.get(&self, usz index, $Type)
{
@@ -356,7 +305,7 @@ macro AnyList.get(&self, usz index, $Type)
}
<*
@require index < self.size "Index out of range"
@require index < self.size : "Index out of range"
*>
fn any AnyList.get_any(&self, usz index) @inline
{
@@ -380,7 +329,7 @@ fn void AnyList.swap(&self, usz i, usz j)
}
<*
@param filter "The function to determine if it should be removed or not"
@param filter : "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
fn usz AnyList.remove_if(&self, AnyPredicate filter)
@@ -389,7 +338,7 @@ fn usz AnyList.remove_if(&self, AnyPredicate filter)
}
<*
@param selection "The function to determine if it should be kept or not"
@param selection : "The function to determine if it should be kept or not"
@return "the number of deleted elements"
*>
fn usz AnyList.retain_if(&self, AnyPredicate selection)
@@ -466,7 +415,7 @@ fn void AnyList.reserve(&self, usz min_capacity)
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = allocator::heap();
if (!self.allocator) self.allocator = tmem;
min_capacity = math::next_power_of_2(min_capacity);
self.entries = allocator::realloc(self.allocator, self.entries, any.sizeof * min_capacity);
self.capacity = min_capacity;
@@ -478,7 +427,7 @@ macro any AnyList.@item_at(&self, usz index) @operator([])
}
<*
@require index <= self.size "Index out of range"
@require index <= self.size : "Index out of range"
*>
macro void AnyList.set(&self, usz index, value)
{

View File

@@ -1,9 +1,9 @@
<*
@require SIZE > 0
*>
module std::collections::bitset(<SIZE>);
module std::collections::bitset{SIZE};
def Type = uint;
alias Type = uint;
const BITS = Type.sizeof * 8;
const SZ = (SIZE + BITS - 1) / BITS;
@@ -70,12 +70,12 @@ fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
<*
@require Type.kindof == UNSIGNED_INT
*>
module std::collections::growablebitset(<Type>);
module std::collections::growablebitset{Type};
import std::collections::list;
const BITS = Type.sizeof * 8;
def GrowableBitSetList = List(<Type>);
alias GrowableBitSetList = List{Type};
struct GrowableBitSet
{
@@ -84,17 +84,17 @@ struct GrowableBitSet
<*
@param initial_capacity
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
@param [&inout] allocator : "The allocator to use, defaults to the heap allocator"
*>
fn GrowableBitSet* GrowableBitSet.new_init(&self, usz initial_capacity = 1, Allocator allocator = allocator::heap())
fn GrowableBitSet* GrowableBitSet.init(&self, Allocator allocator, usz initial_capacity = 1)
{
self.data.new_init(initial_capacity, allocator);
self.data.init(allocator, initial_capacity);
return self;
}
fn GrowableBitSet* GrowableBitSet.temp_init(&self, usz initial_capacity = 1)
fn GrowableBitSet* GrowableBitSet.tinit(&self, usz initial_capacity = 1)
{
return self.new_init(initial_capacity, allocator::temp()) @inline;
return self.init(tmem, initial_capacity) @inline;
}
fn void GrowableBitSet.free(&self)
@@ -117,15 +117,10 @@ fn void GrowableBitSet.set(&self, usz i)
usz q = i / BITS;
usz r = i % BITS;
usz current_len = self.data.len();
if (q >= current_len)
while (q >= current_len)
{
usz n = q + 1;
self.data.reserve(n);
if (n - 1 >= current_len)
{
self.data.entries[current_len .. (n - 1)] = 0;
}
self.data.size = n;
self.data.push(0);
current_len++;
}
self.data.set(q, self.data[q] | (1 << r));
}

View File

@@ -2,13 +2,13 @@
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@require MAX_SIZE >= 1 `The size must be at least 1 element big.`
@require MAX_SIZE >= 1 : `The size must be at least 1 element big.`
*>
module std::collections::elastic_array(<Type, MAX_SIZE>);
module std::collections::elastic_array {Type, MAX_SIZE};
import std::io, std::math, std::collections::list_common;
def ElementPredicate = fn bool(Type *type);
def ElementTest = fn bool(Type *type, any context);
alias ElementPredicate = fn bool(Type *type);
alias ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
@@ -19,7 +19,7 @@ struct ElasticArray (Printable)
Type[MAX_SIZE] entries;
}
fn usz! ElasticArray.to_format(&self, Formatter* formatter) @dynamic
fn usz? ElasticArray.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
@@ -39,38 +39,28 @@ fn usz! ElasticArray.to_format(&self, Formatter* formatter) @dynamic
}
}
fn String ElasticArray.to_string(&self, Allocator allocator) @dynamic
{
return string::format("%s", *self, allocator: allocator);
}
fn String ElasticArray.to_new_string(&self, Allocator allocator = nul) @dynamic
{
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
}
fn String ElasticArray.to_tstring(&self)
{
return string::tformat("%s", *self);
}
fn void! ElasticArray.push_try(&self, Type element) @inline
fn void? ElasticArray.push_try(&self, Type element) @inline
{
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
self.entries[self.size++] = element;
}
<*
@require self.size < MAX_SIZE `Tried to exceed the max size`
@require self.size < MAX_SIZE : `Tried to exceed the max size`
*>
fn void ElasticArray.push(&self, Type element) @inline
{
self.entries[self.size++] = element;
}
fn Type! ElasticArray.pop(&self)
fn Type? ElasticArray.pop(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
@@ -82,9 +72,9 @@ fn void ElasticArray.clear(&self)
<*
@require self.size > 0
*>
fn Type! ElasticArray.pop_first(&self)
fn Type? ElasticArray.pop_first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
@@ -146,7 +136,7 @@ fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
Add the values of an array to this list.
@param [in] array
@require array.len + self.size <= MAX_SIZE `Size would exceed max.`
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
@ensure self.size >= array.len
*>
fn void ElasticArray.add_array(&self, Type[] array)
@@ -160,28 +150,12 @@ fn void ElasticArray.add_array(&self, Type[] array)
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
fn Type[] ElasticArray.to_new_aligned_array(&self)
{
return list_common::list_to_new_aligned_array(Type, self, allocator::heap());
}
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
fn Type[] ElasticArray.to_aligned_array(&self, Allocator allocator)
{
return list_common::list_to_new_aligned_array(Type, self, allocator);
}
<*
@require !type_is_overaligned() : "This function is not available on overaligned types"
*>
macro Type[] ElasticArray.to_new_array(&self)
{
return list_common::list_to_array(Type, self, allocator::heap());
return list_common::list_to_aligned_array(Type, self, allocator);
}
<*
@@ -189,15 +163,15 @@ macro Type[] ElasticArray.to_new_array(&self)
*>
macro Type[] ElasticArray.to_array(&self, Allocator allocator)
{
return list_common::list_to_new_array(Type, self, allocator);
return list_common::list_to_array(Type, self, allocator);
}
fn Type[] ElasticArray.to_tarray(&self)
{
$if type_is_overaligned():
return self.to_aligned_array(allocator::temp());
return self.to_aligned_array(tmem);
$else
return self.to_array(allocator::temp());
return self.to_array(tmem);
$endif;
}
@@ -215,7 +189,7 @@ fn Type[] ElasticArray.array_view(&self)
}
<*
@require self.size < MAX_SIZE `List would exceed max size`
@require self.size < MAX_SIZE : `List would exceed max size`
*>
fn void ElasticArray.push_front(&self, Type type) @inline
{
@@ -223,9 +197,9 @@ fn void ElasticArray.push_front(&self, Type type) @inline
}
<*
@require self.size < MAX_SIZE `List would exceed max size`
@require self.size < MAX_SIZE : `List would exceed max size`
*>
fn void! ElasticArray.push_front_try(&self, Type type) @inline
fn void? ElasticArray.push_front_try(&self, Type type) @inline
{
return self.insert_at_try(0, type);
}
@@ -233,14 +207,14 @@ fn void! ElasticArray.push_front_try(&self, Type type) @inline
<*
@require index <= self.size
*>
fn void! ElasticArray.insert_at_try(&self, usz index, Type value)
fn void? ElasticArray.insert_at_try(&self, usz index, Type value)
{
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
self.insert_at(index, value);
}
<*
@require self.size < MAX_SIZE `List would exceed max size`
@require self.size < MAX_SIZE : `List would exceed max size`
@require index <= self.size
*>
fn void ElasticArray.insert_at(&self, usz index, Type type)
@@ -261,27 +235,27 @@ fn void ElasticArray.set_at(&self, usz index, Type type)
self.entries[index] = type;
}
fn void! ElasticArray.remove_last(&self) @maydiscard
fn void? ElasticArray.remove_last(&self) @maydiscard
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
self.size--;
}
fn void! ElasticArray.remove_first(&self) @maydiscard
fn void? ElasticArray.remove_first(&self) @maydiscard
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
self.remove_at(0);
}
fn Type! ElasticArray.first(&self)
fn Type? ElasticArray.first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[0];
}
fn Type! ElasticArray.last(&self)
fn Type? ElasticArray.last(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[self.size - 1];
}
@@ -311,7 +285,7 @@ fn void ElasticArray.swap(&self, usz i, usz j)
}
<*
@param filter "The function to determine if it should be removed or not"
@param filter : "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
fn usz ElasticArray.remove_if(&self, ElementPredicate filter)
@@ -320,7 +294,7 @@ fn usz ElasticArray.remove_if(&self, ElementPredicate filter)
}
<*
@param selection "The function to determine if it should be kept or not"
@param selection : "The function to determine if it should be kept or not"
@return "the number of deleted elements"
*>
fn usz ElasticArray.retain_if(&self, ElementPredicate selection)
@@ -356,22 +330,22 @@ fn void ElasticArray.set(&self, usz index, Type value) @operator([]=)
// Functions for equatable types
fn usz! ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn usz? ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
{
if (equals(v, type)) return i;
}
return SearchResult.MISSING?;
return NOT_FOUND?;
}
fn usz! ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach_r (i, v : self)
{
if (equals(v, type)) return i;
}
return SearchResult.MISSING?;
return NOT_FOUND?;
}
fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE)
@@ -387,8 +361,8 @@ fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUAT
<*
Check for presence of a value in a list.
@param [&in] self "the list to find elements in"
@param value "The value to search for"
@param [&in] self : "the list to find elements in"
@param value : "The value to search for"
@return "True if the value is found, false otherwise"
*>
fn bool ElasticArray.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -401,8 +375,8 @@ fn bool ElasticArray.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@return "true if the value was found"
*>
fn bool ElasticArray.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -411,8 +385,8 @@ fn bool ElasticArray.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABL
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@return "true if the value was found"
*>
fn bool ElasticArray.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -421,8 +395,8 @@ fn bool ElasticArray.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATAB
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@return "the number of deleted elements."
*>
fn usz ElasticArray.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)

View File

@@ -1,7 +1,7 @@
<*
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap"
*>
module std::collections::enummap(<Enum, ValueType>);
module std::collections::enummap{Enum, ValueType};
import std::io;
struct EnumMap (Printable)
{
@@ -16,33 +16,18 @@ fn void EnumMap.init(&self, ValueType init_value)
}
}
fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
fn usz? EnumMap.to_format(&self, Formatter* formatter) @dynamic
{
usz n = formatter.print("{ ")!;
foreach (i, &value : self.values)
{
if (i != 0) formatter.print(", ")!;
n += formatter.printf("%s: %s", (Enum)i, *value)!;
n += formatter.printf("%s: %s", Enum.from_ordinal(i), *value)!;
}
n += formatter.print(" }")!;
return n;
}
fn String EnumMap.to_string(&self, Allocator allocator) @dynamic
{
return string::format("%s", *self, allocator: allocator);
}
fn String EnumMap.to_new_string(&self, Allocator allocator = null) @dynamic
{
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
}
fn String EnumMap.to_tstring(&self) @dynamic
{
return string::tformat("%s", *self);
}
<*
@return "The total size of this map, which is the same as the number of enum values"
@pure

View File

@@ -5,20 +5,20 @@
<*
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enumset"
*>
module std::collections::enumset(<Enum>);
module std::collections::enumset{Enum};
import std::io;
def EnumSetType = $typefrom(private::type_for_enum_elements(Enum.elements)) @private;
alias EnumSetType @private = $typefrom(type_for_enum_elements(Enum.elements));
const IS_CHAR_ARRAY = Enum.elements > 128;
distinct EnumSet (Printable) = EnumSetType;
typedef EnumSet (Printable) = EnumSetType;
fn void EnumSet.add(&self, Enum v)
{
$if IS_CHAR_ARRAY:
(*self)[(usz)v / 8] |= (char)(1u << ((usz)v % 8));
(*self)[(usz)v.ordinal / 8] |= (char)(1u << ((usz)v.ordinal % 8));
$else
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v);
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v.ordinal);
$endif
}
@@ -35,11 +35,11 @@ fn bool EnumSet.remove(&self, Enum v)
{
$if IS_CHAR_ARRAY:
if (!self.has(v) @inline) return false;
(*self)[(usz)v / 8] &= (char)~(1u << ((usz)v % 8));
(*self)[(usz)v.ordinal / 8] &= (char)~(1u << ((usz)v.ordinal % 8));
return true;
$else
EnumSetType old = (EnumSetType)*self;
EnumSetType new = old & ~(1u << (EnumSetType)v);
EnumSetType new = old & ~(1u << (EnumSetType)v.ordinal);
*self = (EnumSet)new;
return old != new;
$endif
@@ -48,9 +48,9 @@ fn bool EnumSet.remove(&self, Enum v)
fn bool EnumSet.has(&self, Enum v)
{
$if IS_CHAR_ARRAY:
return (bool)(((*self)[(usz)v / 8] << ((usz)v % 8)) & 0x01);
return (bool)(((*self)[(usz)v.ordinal / 8] << ((usz)v.ordinal % 8)) & 0x01);
$else
return ((EnumSetType)*self & (1u << (EnumSetType)v)) != 0;
return ((EnumSetType)*self & (1u << (EnumSetType)v.ordinal)) != 0;
$endif
}
@@ -126,7 +126,7 @@ fn EnumSet EnumSet.xor_of(&self, EnumSet s)
$endif
}
fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
fn usz? EnumSet.to_format(&set, Formatter* formatter) @dynamic
{
usz n = formatter.print("[")!;
bool found;
@@ -141,26 +141,9 @@ fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
return n;
}
fn String EnumSet.to_new_string(&set, Allocator allocator = allocator::heap()) @dynamic
macro typeid type_for_enum_elements(usz $elements) @local
{
return string::format("%s", *set, allocator: allocator);
}
fn String EnumSet.to_string(&set, Allocator allocator) @dynamic
{
return string::format("%s", *set, allocator: allocator);
}
fn String EnumSet.to_tstring(&set) @dynamic
{
return string::tformat("%s", *set);
}
module std::collections::enumset::private;
macro typeid type_for_enum_elements(usz $elements)
{
$switch
$switch:
$case ($elements > 128):
return char[($elements + 7) / 8].typeid;
$case ($elements > 64):

View File

@@ -2,12 +2,31 @@
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@require $defined(Key{}.hash()) `No .hash function found on the key`
@require $defined((Key){}.hash()) : `No .hash function found on the key`
*>
module std::collections::map(<Key, Value>);
module std::collections::map{Key, Value};
import std::math;
import std::io @norecurse;
struct HashMap
const uint DEFAULT_INITIAL_CAPACITY = 16;
const uint MAXIMUM_CAPACITY = 1u << 31;
const float DEFAULT_LOAD_FACTOR = 0.75;
const VALUE_IS_EQUATABLE = Value.is_eq;
const bool COPY_KEYS = types::implements_copy(Key);
const Allocator MAP_HEAP_ALLOCATOR = (Allocator)&dummy;
const HashMap ONHEAP = { .allocator = MAP_HEAP_ALLOCATOR };
struct Entry
{
uint hash;
Key key;
Value value;
Entry* next;
}
struct HashMap (Printable)
{
Entry*[] table;
Allocator allocator;
@@ -16,24 +35,13 @@ struct HashMap
float load_factor;
}
<*
@param [&inout] allocator "The allocator to use"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.new_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = null)
{
return self.init(allocator ?: allocator::heap(), capacity, load_factor);
}
<*
@param [&inout] allocator "The allocator to use"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
@@ -46,47 +54,59 @@ fn HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INI
}
<*
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
fn HashMap* HashMap.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(allocator::temp(), capacity, load_factor) @inline;
return self.init(tmem, capacity, load_factor) @inline;
}
<*
@param [&inout] allocator "The allocator to use"
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
@param [&inout] allocator : "The allocator to use"
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro HashMap* HashMap.new_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
macro HashMap* HashMap.init_with_key_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.new_init(capacity, load_factor, allocator);
$for (var $i = 0; $i < $vacount; $i += 2)
self.set($vaarg[$i], $vaarg[$i+1]);
self.init(allocator, capacity, load_factor);
$for var $i = 0; $i < $vacount; $i += 2:
self.set($vaarg[$i], $vaarg[$i + 1]);
$endfor
return self;
}
<*
@param [in] keys "The keys for the HashMap entries"
@param [in] values "The values for the HashMap entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.new_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
macro HashMap* HashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.tinit_with_key_values(tmem, capacity, load_factor);
}
<*
@param [in] keys : "The keys for the HashMap entries"
@param [in] values : "The values for the HashMap entries"
@param [&inout] allocator : "The allocator to use"
@require keys.len == values.len : "Both keys and values arrays must be the same length"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
assert(keys.len == values.len);
self.new_init(capacity, load_factor, allocator);
self.init(allocator, capacity, load_factor);
for (usz i = 0; i < keys.len; i++)
{
self.set(keys[i], values[i]);
@@ -94,79 +114,51 @@ fn HashMap* HashMap.new_init_from_keys_and_values(&self, Key[] keys, Value[] val
return self;
}
<*
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro HashMap* HashMap.temp_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.temp_init(capacity, load_factor);
$for (var $i = 0; $i < $vacount; $i += 2)
self.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return self;
}
<*
@param [in] keys "The keys for the HashMap entries"
@param [in] values "The values for the HashMap entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
@param [in] keys : "The keys for the HashMap entries"
@param [in] values : "The values for the HashMap entries"
@require keys.len == values.len : "Both keys and values arrays must be the same length"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.temp_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
fn HashMap* HashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
assert(keys.len == values.len);
self.temp_init(capacity, load_factor);
for (usz i = 0; i < keys.len; i++)
{
self.set(keys[i], values[i]);
}
return self;
return self.init_from_keys_and_values(tmem, keys, values, capacity, load_factor);
}
<*
Has this hash map been initialized yet?
@param [&in] map "The hash map we are testing"
@param [&in] map : "The hash map we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool HashMap.is_initialized(&map)
{
return (bool)map.allocator;
return map.allocator && map.allocator.ptr != &dummy;
}
<*
@param [&in] other_map "The map to copy from."
@param [&inout] allocator : "The allocator to use"
@param [&in] other_map : "The map to copy from."
@require !self.is_initialized() : "Map was already initialized"
*>
fn HashMap* HashMap.new_init_from_map(&self, HashMap* other_map)
fn HashMap* HashMap.init_from_map(&self, Allocator allocator, HashMap* other_map)
{
return self.init_from_map(other_map, allocator::heap()) @inline;
}
<*
@param [&inout] allocator "The allocator to use"
@param [&in] other_map "The map to copy from."
*>
fn HashMap* HashMap.init_from_map(&self, HashMap* other_map, Allocator allocator)
{
self.new_init(other_map.table.len, other_map.load_factor, allocator);
self.init(allocator, other_map.table.len, other_map.load_factor);
self.put_all_for_create(other_map);
return self;
}
<*
@param [&in] other_map "The map to copy from."
@param [&in] other_map : "The map to copy from."
@require !map.is_initialized() : "Map was already initialized"
*>
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map)
fn HashMap* HashMap.tinit_from_map(&map, HashMap* other_map)
{
return map.init_from_map(other_map, allocator::temp()) @inline;
return map.init_from_map(tmem, other_map) @inline;
}
fn bool HashMap.is_empty(&map) @inline
@@ -179,26 +171,26 @@ fn usz HashMap.len(&map) @inline
return map.count;
}
fn Value*! HashMap.get_ref(&map, Key key)
fn Value*? HashMap.get_ref(&map, Key key)
{
if (!map.count) return SearchResult.MISSING?;
if (!map.count) return NOT_FOUND?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
return SearchResult.MISSING?;
return NOT_FOUND?;
}
fn Entry*! HashMap.get_entry(&map, Key key)
fn Entry*? HashMap.get_entry(&map, Key key)
{
if (!map.count) return SearchResult.MISSING?;
if (!map.count) return NOT_FOUND?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return SearchResult.MISSING?;
return NOT_FOUND?;
}
<*
@@ -224,7 +216,7 @@ macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
return val;
}
fn Value! HashMap.get(&map, Key key) @operator([])
fn Value? HashMap.get(&map, Key key) @operator([])
{
return *map.get_ref(key) @inline;
}
@@ -237,10 +229,15 @@ fn bool HashMap.has_key(&map, Key key)
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
if (!map.allocator)
{
map.new_init();
}
switch (map.allocator.ptr)
{
case &dummy:
map.init(mem);
case null:
map.tinit();
default:
break;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
@@ -255,9 +252,9 @@ fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
return false;
}
fn void! HashMap.remove(&map, Key key) @maydiscard
fn void? HashMap.remove(&map, Key key) @maydiscard
{
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
if (!map.remove_entry_for_key(key)) return NOT_FOUND?;
}
fn void HashMap.clear(&map)
@@ -282,37 +279,24 @@ fn void HashMap.clear(&map)
fn void HashMap.free(&map)
{
if (!map.allocator) return;
if (!map.is_initialized()) return;
map.clear();
map.free_internal(map.table.ptr);
map.table = {};
}
fn Key[] HashMap.tcopy_keys(&map)
fn Key[] HashMap.tkeys(&self)
{
return map.copy_keys(allocator::temp()) @inline;
return self.keys(tmem) @inline;
}
fn Key[] HashMap.key_tlist(&map) @deprecated("Use 'tcopy_keys'")
fn Key[] HashMap.keys(&self, Allocator allocator)
{
return map.copy_keys(allocator::temp()) @inline;
}
if (!self.count) return {};
<*
@deprecated "use copy_keys"
*>
fn Key[] HashMap.key_new_list(&map, Allocator allocator = allocator::heap())
{
return map.copy_keys(allocator) @inline;
}
fn Key[] HashMap.copy_keys(&map, Allocator allocator = allocator::heap())
{
if (!map.count) return {};
Key[] list = allocator::alloc_array(allocator, Key, map.count);
Key[] list = allocator::alloc_array(allocator, Key, self.count);
usz index = 0;
foreach (Entry* entry : map.table)
foreach (Entry* entry : self.table)
{
while (entry)
{
@@ -329,53 +313,36 @@ fn Key[] HashMap.copy_keys(&map, Allocator allocator = allocator::heap())
macro HashMap.@each(map; @body(key, value))
{
map.@each_entry(; Entry* entry) {
map.@each_entry(; Entry* entry)
{
@body(entry.key, entry.value);
};
}
macro HashMap.@each_entry(map; @body(entry))
{
if (map.count)
if (!map.count) return;
foreach (Entry* entry : map.table)
{
foreach (Entry* entry : map.table)
while (entry)
{
while (entry)
{
@body(entry);
entry = entry.next;
}
@body(entry);
entry = entry.next;
}
}
}
<*
@deprecated `use tcopy_values`
*>
fn Value[] HashMap.value_tlist(&map)
fn Value[] HashMap.tvalues(&map)
{
return map.copy_values(allocator::temp()) @inline;
return map.values(tmem) @inline;
}
fn Value[] HashMap.tcopy_values(&map)
fn Value[] HashMap.values(&self, Allocator allocator)
{
return map.copy_values(allocator::temp()) @inline;
}
<*
@deprecated `use copy_values`
*>
fn Value[] HashMap.value_new_list(&map, Allocator allocator = allocator::heap())
{
return map.copy_values(allocator);
}
fn Value[] HashMap.copy_values(&map, Allocator allocator = allocator::heap())
{
if (!map.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, map.count);
if (!self.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, self.count);
usz index = 0;
foreach (Entry* entry : map.table)
foreach (Entry* entry : self.table)
{
while (entry)
{
@@ -400,6 +367,21 @@ fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
return false;
}
fn HashMapIterator HashMap.iter(&self)
{
return { .map = self, .index = -1 };
}
fn HashMapValueIterator HashMap.value_iter(&self)
{
return { .map = self, .index = -1 };
}
fn HashMapKeyIterator HashMap.key_iter(&self)
{
return { .map = self, .index = -1 };
}
// --- private methods
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
@@ -431,6 +413,18 @@ fn void HashMap.resize(&map, uint new_capacity) @private
map.threshold = (uint)(new_capacity * map.load_factor);
}
fn usz? HashMap.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each_entry(; Entry* entry)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s: %s", entry.key, entry.value)!;
};
return len + f.print(" }");
}
fn void HashMap.transfer(&map, Entry*[] new_table) @private
{
Entry*[] src = map.table;
@@ -455,8 +449,11 @@ fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
if (!other_map.count) return;
foreach (Entry *e : other_map.table)
{
if (!e) continue;
map.put_for_create(e.key, e.value);
while (e)
{
map.put_for_create(e.key, e.value);
e = e.next;
}
}
}
@@ -529,3 +526,67 @@ fn void HashMap.free_entry(&self, Entry *entry) @local
self.free_internal(entry);
}
struct HashMapIterator
{
HashMap* map;
int top_index;
int index;
Entry* current_entry;
}
typedef HashMapValueIterator = HashMapIterator;
typedef HashMapKeyIterator = HashMapIterator;
<*
@require idx < self.map.count
*>
fn Entry HashMapIterator.get(&self, usz idx) @operator([])
{
if (idx < self.index)
{
self.top_index = 0;
self.current_entry = null;
self.index = -1;
}
while (self.index != idx)
{
if (self.current_entry)
{
self.current_entry = self.current_entry.next;
if (self.current_entry) self.index++;
continue;
}
self.current_entry = self.map.table[self.top_index++];
if (self.current_entry) self.index++;
}
return *self.current_entry;
}
fn Value HashMapValueIterator.get(&self, usz idx) @operator([])
{
return ((HashMapIterator*)self).get(idx).value;
}
fn Key HashMapKeyIterator.get(&self, usz idx) @operator([])
{
return ((HashMapIterator*)self).get(idx).key;
}
fn usz HashMapValueIterator.len(self) @operator(len) => self.map.count;
fn usz HashMapKeyIterator.len(self) @operator(len) => self.map.count;
fn usz HashMapIterator.len(self) @operator(len) => self.map.count;
fn uint rehash(uint hash) @inline @private
{
hash ^= (hash >> 20) ^ (hash >> 12);
return hash ^ ((hash >> 7) ^ (hash >> 4));
}
macro uint index_for(uint hash, uint capacity) @private
{
return hash & (capacity - 1);
}
int dummy @local;

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::linkedlist(<Type>);
module std::collections::linkedlist{Type};
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
@@ -21,7 +21,7 @@ struct LinkedList
}
<*
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
@param [&inout] allocator : "The allocator to use, defaults to the heap allocator"
@return "the initialized list"
*>
fn LinkedList* LinkedList.init(&self, Allocator allocator)
@@ -30,22 +30,15 @@ fn LinkedList* LinkedList.init(&self, Allocator allocator)
return self;
}
<*
@return "the initialized list"
*>
fn LinkedList* LinkedList.new_init(&self)
fn LinkedList* LinkedList.tinit(&self)
{
return self.init(allocator::heap()) @inline;
return self.init(tmem) @inline;
}
fn LinkedList* LinkedList.temp_init(&self)
{
return self.init(allocator::temp()) @inline;
}
fn bool LinkedList.is_initialized(&self) @inline => self.allocator != null;
<*
@require self.allocator
@require self.is_initialized()
*>
macro void LinkedList.free_node(&self, Node* node) @private
{
@@ -54,7 +47,7 @@ macro void LinkedList.free_node(&self, Node* node) @private
macro Node* LinkedList.alloc_node(&self) @private
{
if (!self.allocator) self.allocator = allocator::heap();
if (!self.allocator) self.allocator = tmem;
return allocator::alloc(self.allocator, Node);
}
@@ -92,18 +85,18 @@ fn void LinkedList.push(&self, Type value)
self.size++;
}
fn Type! LinkedList.peek(&self) => self.first() @inline;
fn Type! LinkedList.peek_last(&self) => self.last() @inline;
fn Type? LinkedList.peek(&self) => self.first() @inline;
fn Type? LinkedList.peek_last(&self) => self.last() @inline;
fn Type! LinkedList.first(&self)
fn Type? LinkedList.first(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
if (!self._first) return NO_MORE_ELEMENT?;
return self._first.value;
}
fn Type! LinkedList.last(&self)
fn Type? LinkedList.last(&self)
{
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
if (!self._last) return NO_MORE_ELEMENT?;
return self._last.value;
}
@@ -200,7 +193,7 @@ fn void LinkedList.link_before(&self, Node *succ, Type value) @private
}
<*
@require self._first
@require self._first != null
*>
fn void LinkedList.unlink_first(&self) @private
{
@@ -238,9 +231,9 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
return start - self.size;
}
fn Type! LinkedList.pop(&self)
fn Type? LinkedList.pop(&self)
{
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
if (!self._last) return NO_MORE_ELEMENT?;
defer self.unlink_last();
return self._last.value;
}
@@ -250,22 +243,22 @@ fn bool LinkedList.is_empty(&self)
return !self._first;
}
fn Type! LinkedList.pop_front(&self)
fn Type? LinkedList.pop_front(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
if (!self._first) return NO_MORE_ELEMENT?;
defer self.unlink_first();
return self._first.value;
}
fn void! LinkedList.remove_last(&self) @maydiscard
fn void? LinkedList.remove_last(&self) @maydiscard
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
if (!self._first) return NO_MORE_ELEMENT?;
self.unlink_last();
}
fn void! LinkedList.remove_first(&self) @maydiscard
fn void? LinkedList.remove_first(&self) @maydiscard
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
if (!self._first) return NO_MORE_ELEMENT?;
self.unlink_first();
}
@@ -296,7 +289,7 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
return false;
}
<*
@require self._last
@require self._last != null
*>
fn void LinkedList.unlink_last(&self) @inline @private
{

View File

@@ -1,14 +1,18 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::list(<Type>);
module std::collections::list{Type};
import std::io, std::math, std::collections::list_common;
def ElementPredicate = fn bool(Type *type);
def ElementTest = fn bool(Type *type, any context);
alias ElementPredicate = fn bool(Type *type);
alias ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR };
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
struct List (Printable)
@@ -20,10 +24,10 @@ struct List (Printable)
}
<*
@param initial_capacity "The initial capacity to reserve"
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
@param initial_capacity : "The initial capacity to reserve"
@param [&inout] allocator : "The allocator to use, defaults to the heap allocator"
*>
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
fn List* List.init(&self, Allocator allocator, usz initial_capacity = 16)
{
self.allocator = allocator;
self.size = 0;
@@ -33,25 +37,26 @@ fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = a
return self;
}
<*
Initialize the list using the temp allocator.
@param initial_capacity "The initial capacity to reserve"
@param initial_capacity : "The initial capacity to reserve"
*>
fn List* List.temp_init(&self, usz initial_capacity = 16)
fn List* List.tinit(&self, usz initial_capacity = 16)
{
return self.new_init(initial_capacity, allocator::temp()) @inline;
return self.init(tmem, initial_capacity) @inline;
}
<*
Initialize a new list with an array.
@param [in] values `The values to initialize the list with.`
@require self.size == 0 "The List must be empty"
@param [in] values : `The values to initialize the list with.`
@require self.size == 0 : "The List must be empty"
*>
fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = allocator::heap())
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
{
self.new_init(values.len, allocator) @inline;
self.init(allocator, values.len) @inline;
self.add_array(values) @inline;
return self;
}
@@ -59,20 +64,20 @@ fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = al
<*
Initialize a temporary list with an array.
@param [in] values `The values to initialize the list with.`
@require self.size == 0 "The List must be empty"
@param [in] values : `The values to initialize the list with.`
@require self.size == 0 : "The List must be empty"
*>
fn List* List.temp_init_with_array(&self, Type[] values)
fn List* List.tinit_with_array(&self, Type[] values)
{
self.temp_init(values.len) @inline;
self.tinit(values.len) @inline;
self.add_array(values) @inline;
return self;
}
<*
@require self.capacity == 0 "The List must not be allocated"
@require !self.is_initialized() : "The List must not be allocated"
*>
fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allocator::heap())
fn void List.init_wrapping_array(&self, Allocator allocator, Type[] types)
{
self.allocator = allocator;
self.capacity = types.len;
@@ -80,7 +85,9 @@ fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allo
self.set_size(types.len);
}
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
fn bool List.is_initialized(&self) @inline => self.allocator != null && self.allocator != (Allocator)&dummy;
fn usz? List.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
@@ -100,25 +107,15 @@ fn usz! List.to_format(&self, Formatter* formatter) @dynamic
}
}
fn String List.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return string::format("%s", *self, allocator: allocator);
}
fn String List.to_tstring(&self)
{
return string::tformat("%s", *self);
}
fn void List.push(&self, Type element) @inline
{
self.reserve(1);
self.entries[self.set_size(self.size + 1)] = element;
}
fn Type! List.pop(&self)
fn Type? List.pop(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
defer self.set_size(self.size - 1);
return self.entries[self.size - 1];
}
@@ -128,15 +125,15 @@ fn void List.clear(&self)
self.set_size(0);
}
fn Type! List.pop_first(&self)
fn Type? List.pop_first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
<*
@require index < self.size `Removed element out of bounds`
@require index < self.size : `Removed element out of bounds`
*>
fn void List.remove_at(&self, usz index)
{
@@ -160,25 +157,25 @@ fn void List.add_all(&self, List* other_list)
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
fn Type[] List.to_new_aligned_array(&self, Allocator allocator = allocator::heap())
fn Type[] List.to_aligned_array(&self, Allocator allocator)
{
return list_common::list_to_new_aligned_array(Type, self, allocator);
return list_common::list_to_aligned_array(Type, self, allocator);
}
<*
@require !type_is_overaligned() : "This function is not available on overaligned types"
*>
macro Type[] List.to_new_array(&self, Allocator allocator = allocator::heap())
macro Type[] List.to_array(&self, Allocator allocator)
{
return list_common::list_to_new_array(Type, self, allocator);
return list_common::list_to_array(Type, self, allocator);
}
fn Type[] List.to_tarray(&self)
{
$if type_is_overaligned():
return self.to_new_aligned_array(allocator::temp());
return self.to_aligned_array(tmem);
$else
return self.to_new_array(allocator::temp());
return self.to_array(tmem);
$endif;
}
@@ -215,16 +212,16 @@ fn void List.push_front(&self, Type type) @inline
}
<*
@require index <= self.size `Insert was out of bounds`
@require index <= self.size : `Insert was out of bounds`
*>
fn void List.insert_at(&self, usz index, Type type)
{
self.reserve(1);
for (usz i = self.size; i > index; i--)
self.set_size(self.size + 1);
for (isz i = self.size - 1; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.set_size(self.size + 1);
self.entries[index] = type;
}
@@ -236,27 +233,27 @@ fn void List.set_at(&self, usz index, Type type)
self.entries[index] = type;
}
fn void! List.remove_last(&self) @maydiscard
fn void? List.remove_last(&self) @maydiscard
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
self.set_size(self.size - 1);
}
fn void! List.remove_first(&self) @maydiscard
fn void? List.remove_first(&self) @maydiscard
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
self.remove_at(0);
}
fn Type! List.first(&self)
fn Type? List.first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[0];
}
fn Type! List.last(&self)
fn Type? List.last(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[self.size - 1];
}
@@ -276,7 +273,7 @@ fn usz List.len(&self) @operator(len) @inline
}
<*
@require index < self.size `Access out of bounds`
@require index < self.size : `Access out of bounds`
*>
fn Type List.get(&self, usz index) @inline
{
@@ -285,7 +282,7 @@ fn Type List.get(&self, usz index) @inline
fn void List.free(&self)
{
if (!self.allocator || !self.capacity) return;
if (!self.allocator || self.allocator.ptr == &dummy || !self.capacity) return;
self.pre_free(); // Remove sanitizer annotation
@@ -300,7 +297,7 @@ fn void List.free(&self)
}
<*
@require i < self.size && j < self.size `Access out of bounds`
@require i < self.size && j < self.size : `Access out of bounds`
*>
fn void List.swap(&self, usz i, usz j)
{
@@ -308,7 +305,7 @@ fn void List.swap(&self, usz i, usz j)
}
<*
@param filter "The function to determine if it should be removed or not"
@param filter : "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
fn usz List.remove_if(&self, ElementPredicate filter)
@@ -317,7 +314,7 @@ fn usz List.remove_if(&self, ElementPredicate filter)
}
<*
@param selection "The function to determine if it should be kept or not"
@param selection : "The function to determine if it should be kept or not"
@return "the number of deleted elements"
*>
fn usz List.retain_if(&self, ElementPredicate selection)
@@ -328,7 +325,8 @@ fn usz List.retain_if(&self, ElementPredicate selection)
fn usz List.remove_using_test(&self, ElementTest filter, any context)
{
usz old_size = self.size;
defer {
defer
{
if (old_size != self.size) self._update_size_change(old_size, self.size);
}
return list_common::list_remove_using_test(self, filter, false, context);
@@ -349,7 +347,17 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = allocator::heap();
// Get a proper allocator
switch (self.allocator.ptr)
{
case &dummy:
self.allocator = mem;
case null:
self.allocator = tmem;
default:
break;
}
self.pre_free(); // Remove sanitizer annotation
@@ -365,7 +373,7 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
}
<*
@require index < self.size `Access out of bounds`
@require index < self.size : `Access out of bounds`
*>
macro Type List.@item_at(&self, usz index) @operator([])
{
@@ -373,7 +381,7 @@ macro Type List.@item_at(&self, usz index) @operator([])
}
<*
@require index < self.size `Access out of bounds`
@require index < self.size : `Access out of bounds`
*>
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
{
@@ -381,7 +389,7 @@ fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
}
<*
@require index < self.size `Access out of bounds`
@require index < self.size : `Access out of bounds`
*>
fn void List.set(&self, usz index, Type value) @operator([]=)
{
@@ -402,10 +410,13 @@ fn void List.reserve(&self, usz added)
fn void List._update_size_change(&self,usz old_size, usz new_size)
{
if (old_size == new_size) return;
sanitizer::annotate_contiguous_container(self.entries,
$if env::ADDRESS_SANITIZER:
if (self.allocator.ptr != &allocator::LIBC_ALLOCATOR) return;
sanitizer::annotate_contiguous_container(self.entries,
&self.entries[self.capacity],
&self.entries[old_size],
&self.entries[new_size]);
$endif
}
<*
@require new_size == 0 || self.capacity != 0
@@ -425,7 +436,7 @@ macro void List.pre_free(&self) @private
}
<*
@require self.capacity
@require self.capacity > 0
*>
macro void List.post_alloc(&self) @private
{
@@ -435,22 +446,22 @@ macro void List.post_alloc(&self) @private
// Functions for equatable types
fn usz! List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn usz? List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
{
if (equals(v, type)) return i;
}
return SearchResult.MISSING?;
return NOT_FOUND?;
}
fn usz! List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn usz? List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach_r (i, v : self)
{
if (equals(v, type)) return i;
}
return SearchResult.MISSING?;
return NOT_FOUND?;
}
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
@@ -466,8 +477,8 @@ fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
<*
Check for presence of a value in a list.
@param [&in] self "the list to find elements in"
@param value "The value to search for"
@param [&in] self : "the list to find elements in"
@param value : "The value to search for"
@return "True if the value is found, false otherwise"
*>
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -480,8 +491,8 @@ fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -490,8 +501,8 @@ fn bool List.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -499,8 +510,8 @@ fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
return @ok(self.remove_at(self.index_of(value)));
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@return "the number of deleted elements."
*>
fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -544,35 +555,4 @@ fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
return list_common::list_compact(self);
}
// --> Deprecated
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_last_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_last_item(value) @inline;
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_first_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_first_item(value) @inline;
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "the number of deleted elements."
*>
fn usz List.remove_all_matches(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_item(value) @inline;
}
int dummy @local;

View File

@@ -3,17 +3,17 @@ module std::collections::list_common;
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
macro list_to_new_aligned_array($Type, self, Allocator allocator)
macro list_to_aligned_array($Type, self, Allocator allocator)
{
if (!self.size) return $Type[] {};
if (!self.size) return ($Type[]){};
$Type[] result = allocator::alloc_array_aligned(allocator, $Type, self.size);
result[..] = self.entries[:self.size];
return result;
}
macro list_to_new_array($Type, self, Allocator allocator)
macro list_to_array($Type, self, Allocator allocator)
{
if (!self.size) return $Type[] {};
if (!self.size) return ($Type[]){};
$Type[] result = allocator::alloc_array(allocator, $Type, self.size);
result[..] = self.entries[:self.size];
return result;

View File

@@ -1,505 +0,0 @@
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::map(<Key, Value>);
import std::math;
const uint DEFAULT_INITIAL_CAPACITY = 16;
const uint MAXIMUM_CAPACITY = 1u << 31;
const float DEFAULT_LOAD_FACTOR = 0.75;
const VALUE_IS_EQUATABLE = Value.is_eq;
const bool COPY_KEYS = types::implements_copy(Key);
distinct Map = void*;
struct MapImpl
{
Entry*[] table;
Allocator allocator;
uint count; // Number of elements
uint threshold; // Resize limit
float load_factor;
}
<*
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map new(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
MapImpl* map = allocator::alloc(allocator, MapImpl);
_init(map, capacity, load_factor, allocator);
return (Map)map;
}
<*
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map temp(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
MapImpl* map = mem::temp_alloc(MapImpl);
_init(map, capacity, load_factor, allocator::temp());
return (Map)map;
}
<*
@param [&inout] allocator "The allocator to use"
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro Map new_init_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
Map map = new(capacity, load_factor, allocator);
$for (var $i = 0; $i < $vacount; $i += 2)
map.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return map;
}
<*
@param [in] keys "Array of keys for the Map entries"
@param [in] values "Array of values for the Map entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map new_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
assert(keys.len == values.len);
Map map = new(capacity, load_factor, allocator);
for (usz i = 0; i < keys.len; i++)
{
map.set(keys[i], values[i]);
}
return map;
}
<*
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro Map temp_new_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
Map map = temp(capacity, load_factor);
$for (var $i = 0; $i < $vacount; $i += 2)
map.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return map;
}
<*
@param [in] keys "The keys for the HashMap entries"
@param [in] values "The values for the HashMap entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map temp_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
assert(keys.len == values.len);
Map map = temp(capacity, load_factor);
for (usz i = 0; i < keys.len; i++)
{
map.set(keys[i], values[i]);
}
return map;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn Map new_from_map(Map other_map, Allocator allocator = null)
{
MapImpl* other_map_impl = (MapImpl*)other_map;
if (!other_map_impl)
{
if (allocator) return new(allocator: allocator);
return null;
}
MapImpl* map = (MapImpl*)new(other_map_impl.table.len, other_map_impl.load_factor, allocator ?: allocator::heap());
if (!other_map_impl.count) return (Map)map;
foreach (Entry *e : other_map_impl.table)
{
if (!e) continue;
map._put_for_create(e.key, e.value);
}
return (Map)map;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn Map temp_from_map(Map other_map)
{
return new_from_map(other_map, allocator::temp());
}
fn bool Map.is_empty(map) @inline
{
return !map || !((MapImpl*)map).count;
}
fn usz Map.len(map) @inline
{
return map ? ((MapImpl*)map).count : 0;
}
fn Value*! Map.get_ref(self, Key key)
{
MapImpl *map = (MapImpl*)self;
if (!map || !map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
return SearchResult.MISSING?;
}
fn Entry*! Map.get_entry(map, Key key)
{
MapImpl *map_impl = (MapImpl*)map;
if (!map_impl || !map_impl.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map_impl.table[index_for(hash, map_impl.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return SearchResult.MISSING?;
}
<*
Get the value or update and
@require $assignable(#expr, Value)
*>
macro Value Map.@get_or_set(&self, Key key, Value #expr)
{
MapImpl *map = (MapImpl*)*self;
if (!map || !map.count)
{
Value val = #expr;
map.set(key, val);
return val;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e.value;
}
Value val = #expr;
map.add_entry(hash, key, val, index);
return val;
}
fn Value! Map.get(map, Key key) @operator([])
{
return *map.get_ref(key) @inline;
}
fn bool Map.has_key(map, Key key)
{
return @ok(map.get_ref(key));
}
macro Value Map.set_value_return(&map, Key key, Value value) @operator([]=)
{
map.set(key, value);
return value;
}
fn bool Map.set(&self, Key key, Value value)
{
// If the map isn't initialized, use the defaults to initialize it.
if (!*self) *self = new();
MapImpl* map = (MapImpl*)*self;
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return true;
}
}
map._add_entry(hash, key, value, index);
return false;
}
fn void! Map.remove(map, Key key) @maydiscard
{
if (!map || !((MapImpl*)map)._remove_entry_for_key(key)) return SearchResult.MISSING?;
}
fn void Map.clear(self)
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return;
foreach (Entry** &entry_ref : map.table)
{
Entry* entry = *entry_ref;
if (!entry) continue;
Entry *next = entry.next;
while (next)
{
Entry *to_delete = next;
next = next.next;
map._free_entry(to_delete);
}
map._free_entry(entry);
*entry_ref = null;
}
map.count = 0;
}
fn void Map.free(self)
{
if (!self) return;
MapImpl* map = (MapImpl*)self;
self.clear();
map._free_internal(map.table.ptr);
map.table = {};
allocator::free(map.allocator, map);
}
fn Key[] Map.temp_keys_list(map)
{
return map.new_keys_list(allocator::temp()) @inline;
}
fn Key[] Map.new_keys_list(self, Allocator allocator = allocator::heap())
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return {};
Key[] list = allocator::alloc_array(allocator, Key, map.count);
usz index = 0;
foreach (Entry* entry : map.table)
{
while (entry)
{
list[index++] = entry.key;
entry = entry.next;
}
}
return list;
}
macro Map.@each(map; @body(key, value))
{
map.@each_entry(; Entry* entry) {
@body(entry.key, entry.value);
};
}
macro Map.@each_entry(self; @body(entry))
{
MapImpl *map = (MapImpl*)self;
if (!map || !map.count) return;
foreach (Entry* entry : map.table)
{
while (entry)
{
@body(entry);
entry = entry.next;
}
}
}
fn Value[] Map.temp_values_list(map)
{
return map.new_values_list(allocator::temp()) @inline;
}
fn Value[] Map.new_values_list(self, Allocator allocator = allocator::heap())
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, map.count);
usz index = 0;
foreach (Entry* entry : map.table)
{
while (entry)
{
list[index++] = entry.value;
entry = entry.next;
}
}
return list;
}
fn bool Map.has_value(self, Value v) @if(VALUE_IS_EQUATABLE)
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return false;
foreach (Entry* entry : map.table)
{
while (entry)
{
if (equals(v, entry.value)) return true;
entry = entry.next;
}
}
return false;
}
// --- private methods
fn void MapImpl._add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
{
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry;
if (map.count++ >= map.threshold)
{
map._resize(map.table.len * 2);
}
}
fn void MapImpl._resize(&map, uint new_capacity) @private
{
Entry*[] old_table = map.table;
uint old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
map.threshold = uint.max;
return;
}
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
map._transfer(new_table);
map.table = new_table;
map._free_internal(old_table.ptr);
map.threshold = (uint)(new_capacity * map.load_factor);
}
fn uint rehash(uint hash) @inline @private
{
hash ^= (hash >> 20) ^ (hash >> 12);
return hash ^ ((hash >> 7) ^ (hash >> 4));
}
macro uint index_for(uint hash, uint capacity) @private
{
return hash & (capacity - 1);
}
fn void MapImpl._transfer(&map, Entry*[] new_table) @private
{
Entry*[] src = map.table;
uint new_capacity = new_table.len;
foreach (uint j, Entry *e : src)
{
if (!e) continue;
do
{
Entry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void _init(MapImpl* impl, uint capacity, float load_factor, Allocator allocator) @private
{
capacity = math::next_power_of_2(capacity);
*impl = {
.allocator = allocator,
.load_factor = load_factor,
.threshold = (uint)(capacity * load_factor),
.table = allocator::new_array(allocator, Entry*, capacity)
};
}
fn void MapImpl._put_for_create(&map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
for (Entry *e = map.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return;
}
}
map._create_entry(hash, key, value, i);
}
fn void MapImpl._free_internal(&map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
fn bool MapImpl._remove_entry_for_key(&map, Key key) @private
{
if (!map.count) return false;
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
Entry* prev = map.table[i];
Entry* e = prev;
while (e)
{
Entry *next = e.next;
if (e.hash == hash && equals(key, e.key))
{
map.count--;
if (prev == e)
{
map.table[i] = next;
}
else
{
prev.next = next;
}
map._free_entry(e);
return true;
}
prev = e;
e = next;
}
return false;
}
fn void MapImpl._create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
{
Entry *e = map.table[bucket_index];
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry;
map.count++;
}
fn void MapImpl._free_entry(&self, Entry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
$endif
self._free_internal(entry);
}
struct Entry
{
uint hash;
Key key;
Value value;
Entry* next;
}

View File

@@ -1,11 +1,28 @@
module std::collections::maybe(<Type>);
module std::collections::maybe{Type};
import std::io;
struct Maybe
struct Maybe (Printable)
{
Type value;
bool has_value;
}
fn usz? Maybe.to_format(&self, Formatter* f) @dynamic
{
if (self.has_value) return f.printf("[%s]", self.value);
return f.printf("[EMPTY]");
}
fn void Maybe.set(&self, Type val)
{
*self = { .value = val, .has_value = true };
}
fn void Maybe.reset(&self)
{
*self = {};
}
fn Maybe value(Type val)
{
return { .value = val, .has_value = true };
@@ -13,7 +30,7 @@ fn Maybe value(Type val)
const Maybe EMPTY = { };
macro Type! Maybe.get(self)
macro Type? Maybe.get(self)
{
return self.has_value ? self.value : SearchResult.MISSING?;
return self.has_value ? self.value : NOT_FOUND?;
}

View File

@@ -25,7 +25,7 @@ struct Object (Printable)
}
fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
fn usz? Object.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.type)
{
@@ -50,7 +50,7 @@ fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
usz n = formatter.printf("{")!;
@stack_mem(1024; Allocator mem)
{
foreach (i, key : self.map.copy_keys(mem))
foreach (i, key : self.map.keys(mem))
{
if (i > 0) n += formatter.printf(",")!;
n += formatter.printf(`"%s":`, key)!;
@@ -156,7 +156,7 @@ fn void Object.init_map_if_needed(&self) @private
if (self.is_empty())
{
self.type = ObjectInternalMap.typeid;
self.map.new_init(allocator: self.allocator);
self.map.init(self.allocator);
}
}
@@ -168,7 +168,7 @@ fn void Object.init_array_if_needed(&self) @private
if (self.is_empty())
{
self.type = ObjectInternalList.typeid;
self.array.new_init(allocator: self.allocator);
self.array.init(self.allocator);
}
}
@@ -178,7 +178,7 @@ fn void Object.init_array_if_needed(&self) @private
fn void Object.set_object(&self, String key, Object* new_object) @private
{
self.init_map_if_needed();
ObjectInternalMapEntry*! entry = self.map.get_entry(key);
ObjectInternalMapEntry*? entry = self.map.get_entry(key);
defer
{
(void)entry.value.free();
@@ -189,7 +189,7 @@ fn void Object.set_object(&self, String key, Object* new_object) @private
macro Object* Object.object_from_value(&self, value) @private
{
var $Type = $typeof(value);
$switch
$switch:
$case types::is_int($Type):
return new_int(value, self.allocator);
$case types::is_float($Type):
@@ -201,7 +201,7 @@ macro Object* Object.object_from_value(&self, value) @private
$case $Type.typeid == Object*.typeid:
return value;
$case $Type.typeid == void*.typeid:
if (value != null) return CastResult.TYPE_MISMATCH?;
if (value != null) return TYPE_MISMATCH?;
return &NULL_OBJECT;
$case $assignable(value, String):
return new_string(value, self.allocator);
@@ -242,7 +242,7 @@ macro Object* Object.push(&self, value)
<*
@require self.is_keyable()
*>
fn Object*! Object.get(&self, String key) => self.is_empty() ? SearchResult.MISSING? : self.map.get(key);
fn Object*? Object.get(&self, String key) => self.is_empty() ? NOT_FOUND? : self.map.get(key);
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
@@ -292,7 +292,7 @@ fn void Object.set_object_at(&self, usz index, Object* to_set)
}
<*
@require $Type.kindof.is_int() "Expected an integer type."
@require $Type.kindof.is_int() : "Expected an integer type."
*>
macro get_integer_value(Object* value, $Type)
{
@@ -308,7 +308,7 @@ macro get_integer_value(Object* value, $Type)
return ($Type)value.s.to_uint128();
$endif
}
if (!value.is_int()) return NumberConversion.MALFORMED_INTEGER?;
if (!value.is_int()) return string::MALFORMED_INTEGER?;
return ($Type)value.i;
}
@@ -331,77 +331,77 @@ macro Object.get_integer(&self, $Type, String key) @private
return get_integer_value(self.get(key), $Type);
}
fn ichar! Object.get_ichar(&self, String key) => self.get_integer(ichar, key);
fn short! Object.get_short(&self, String key) => self.get_integer(short, key);
fn int! Object.get_int(&self, String key) => self.get_integer(int, key);
fn long! Object.get_long(&self, String key) => self.get_integer(long, key);
fn int128! Object.get_int128(&self, String key) => self.get_integer(int128, key);
fn ichar? Object.get_ichar(&self, String key) => self.get_integer(ichar, key);
fn short? Object.get_short(&self, String key) => self.get_integer(short, key);
fn int? Object.get_int(&self, String key) => self.get_integer(int, key);
fn long? Object.get_long(&self, String key) => self.get_integer(long, key);
fn int128? Object.get_int128(&self, String key) => self.get_integer(int128, key);
fn ichar! Object.get_ichar_at(&self, usz index) => self.get_integer_at(ichar, index);
fn short! Object.get_short_at(&self, usz index) => self.get_integer_at(short, index);
fn int! Object.get_int_at(&self, usz index) => self.get_integer_at(int, index);
fn long! Object.get_long_at(&self, usz index) => self.get_integer_at(long, index);
fn int128! Object.get_int128_at(&self, usz index) => self.get_integer_at(int128, index);
fn ichar? Object.get_ichar_at(&self, usz index) => self.get_integer_at(ichar, index);
fn short? Object.get_short_at(&self, usz index) => self.get_integer_at(short, index);
fn int? Object.get_int_at(&self, usz index) => self.get_integer_at(int, index);
fn long? Object.get_long_at(&self, usz index) => self.get_integer_at(long, index);
fn int128? Object.get_int128_at(&self, usz index) => self.get_integer_at(int128, index);
fn char! Object.get_char(&self, String key) => self.get_integer(ichar, key);
fn short! Object.get_ushort(&self, String key) => self.get_integer(ushort, key);
fn uint! Object.get_uint(&self, String key) => self.get_integer(uint, key);
fn ulong! Object.get_ulong(&self, String key) => self.get_integer(ulong, key);
fn uint128! Object.get_uint128(&self, String key) => self.get_integer(uint128, key);
fn char? Object.get_char(&self, String key) => self.get_integer(ichar, key);
fn short? Object.get_ushort(&self, String key) => self.get_integer(ushort, key);
fn uint? Object.get_uint(&self, String key) => self.get_integer(uint, key);
fn ulong? Object.get_ulong(&self, String key) => self.get_integer(ulong, key);
fn uint128? Object.get_uint128(&self, String key) => self.get_integer(uint128, key);
fn char! Object.get_char_at(&self, usz index) => self.get_integer_at(char, index);
fn ushort! Object.get_ushort_at(&self, usz index) => self.get_integer_at(ushort, index);
fn uint! Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index);
fn ulong! Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index);
fn uint128! Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
fn char? Object.get_char_at(&self, usz index) => self.get_integer_at(char, index);
fn ushort? Object.get_ushort_at(&self, usz index) => self.get_integer_at(ushort, index);
fn uint? Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index);
fn ulong? Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index);
fn uint128? Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
<*
@require self.is_keyable()
*>
fn String! Object.get_string(&self, String key)
fn String? Object.get_string(&self, String key)
{
Object* value = self.get(key)!;
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
if (!value.is_string()) return TYPE_MISMATCH?;
return value.s;
}
<*
@require self.is_indexable()
*>
fn String! Object.get_string_at(&self, usz index)
fn String? Object.get_string_at(&self, usz index)
{
Object* value = self.get_at(index);
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
if (!value.is_string()) return TYPE_MISMATCH?;
return value.s;
}
<*
@require self.is_keyable()
*>
macro String! Object.get_enum(&self, $EnumType, String key)
macro String? Object.get_enum(&self, $EnumType, String key)
{
Object value = self.get(key)!;
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
return ($EnumType)value.i;
}
<*
@require self.is_indexable()
*>
macro String! Object.get_enum_at(&self, $EnumType, usz index)
macro String? Object.get_enum_at(&self, $EnumType, usz index)
{
Object value = self.get_at(index);
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
return ($EnumType)value.i;
}
<*
@require self.is_keyable()
*>
fn bool! Object.get_bool(&self, String key)
fn bool? Object.get_bool(&self, String key)
{
Object* value = self.get(key)!;
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
if (!value.is_bool()) return TYPE_MISMATCH?;
return value.b;
}
@@ -409,17 +409,17 @@ fn bool! Object.get_bool(&self, String key)
<*
@require self.is_indexable()
*>
fn bool! Object.get_bool_at(&self, usz index)
fn bool? Object.get_bool_at(&self, usz index)
{
Object* value = self.get_at(index);
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
if (!value.is_bool()) return TYPE_MISMATCH?;
return value.b;
}
<*
@require self.is_keyable()
*>
fn double! Object.get_float(&self, String key)
fn double? Object.get_float(&self, String key)
{
Object* value = self.get(key)!;
switch (value.type.kindof)
@@ -431,14 +431,14 @@ fn double! Object.get_float(&self, String key)
case FLOAT:
return value.f;
default:
return CastResult.TYPE_MISMATCH?;
return TYPE_MISMATCH?;
}
}
<*
@require self.is_indexable()
*>
fn double! Object.get_float_at(&self, usz index)
fn double? Object.get_float_at(&self, usz index)
{
Object* value = self.get_at(index);
switch (value.type.kindof)
@@ -450,7 +450,7 @@ fn double! Object.get_float_at(&self, usz index)
case FLOAT:
return value.f;
default:
return CastResult.TYPE_MISMATCH?;
return TYPE_MISMATCH?;
}
}
@@ -462,7 +462,7 @@ fn Object* Object.get_or_create_obj(&self, String key)
return container;
}
def ObjectInternalMap = HashMap(<String, Object*>) @private;
def ObjectInternalList = List(<Object*>) @private;
def ObjectInternalMapEntry = Entry(<String, Object*>) @private;
alias ObjectInternalMap @private = HashMap {String, Object*};
alias ObjectInternalList @private = List {Object*};
alias ObjectInternalMapEntry @private = Entry {String, Object*};

View File

@@ -1,7 +1,7 @@
// priorityqueue.c3
// A priority queue using a classic binary heap for C3.
//
// Copyright (c) 2022 David Kopec
// Copyright (c) 2022-2025 David Kopec
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -20,30 +20,30 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
module std::collections::priorityqueue(<Type>);
module std::collections::priorityqueue{Type};
import std::collections::priorityqueue::private;
distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);
typedef PriorityQueue = inline PrivatePriorityQueue{Type, false};
typedef PriorityQueueMax = inline PrivatePriorityQueue{Type, true};
module std::collections::priorityqueue::private(<Type, MAX>);
module std::collections::priorityqueue::private{Type, MAX};
import std::collections::list, std::io;
def Heap = List(<Type>);
struct PrivatePriorityQueue (Printable)
{
Heap heap;
List{Type} heap;
}
fn void PrivatePriorityQueue.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @inline
fn PrivatePriorityQueue* PrivatePriorityQueue.init(&self, Allocator allocator, usz initial_capacity = 16, ) @inline
{
self.heap.new_init(initial_capacity, allocator);
self.heap.init(allocator, initial_capacity);
return self;
}
fn void PrivatePriorityQueue.temp_init(&self, usz initial_capacity = 16) @inline
fn PrivatePriorityQueue* PrivatePriorityQueue.tinit(&self, usz initial_capacity = 16) @inline
{
self.heap.new_init(initial_capacity, allocator::temp()) @inline;
self.init(tmem, initial_capacity);
return self;
}
@@ -67,6 +67,9 @@ fn void PrivatePriorityQueue.push(&self, Type element)
}
}
<*
@require index < self.len() : "Index out of range"
*>
fn void PrivatePriorityQueue.remove_at(&self, usz index)
{
if (index == 0)
@@ -79,11 +82,11 @@ fn void PrivatePriorityQueue.remove_at(&self, usz index)
<*
@require self != null
*>
fn Type! PrivatePriorityQueue.pop(&self)
fn Type? PrivatePriorityQueue.pop(&self)
{
usz i = 0;
usz len = self.heap.len();
if (!len) return IteratorResult.NO_MORE_ELEMENT?;
if (!len) return NO_MORE_ELEMENT?;
usz new_count = len - 1;
self.heap.swap(0, new_count);
while OUTER: ((2 * i + 1) < new_count)
@@ -117,10 +120,9 @@ fn Type! PrivatePriorityQueue.pop(&self)
return self.heap.pop();
}
fn Type! PrivatePriorityQueue.first(&self)
fn Type? PrivatePriorityQueue.first(&self)
{
if (!self.len()) return IteratorResult.NO_MORE_ELEMENT?;
return self.heap.get(0);
return self.heap.first();
}
fn void PrivatePriorityQueue.free(&self)
@@ -130,12 +132,12 @@ fn void PrivatePriorityQueue.free(&self)
fn usz PrivatePriorityQueue.len(&self) @operator(len)
{
return self.heap.len();
return self.heap.len() @inline;
}
fn bool PrivatePriorityQueue.is_empty(&self)
{
return self.heap.is_empty();
return self.heap.is_empty() @inline;
}
<*
@@ -146,13 +148,8 @@ fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
return self.heap[index];
}
fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
fn usz? PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
{
return self.heap.to_format(formatter);
}
fn String PrivatePriorityQueue.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return self.heap.to_new_string(allocator);
}

View File

@@ -1,7 +1,7 @@
<*
@require Type.is_ordered : "The type must be ordered"
*>
module std::collections::range(<Type>);
module std::collections::range{Type};
import std::io;
struct Range (Printable)
@@ -29,22 +29,7 @@ fn Type Range.get(&self, usz index) @operator([])
return (Type)(self.start + (usz)index);
}
fn String Range.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic @deprecated
{
return string::format("[%s..%s]", self.start, self.end, allocator: allocator);
}
fn String Range.to_string(&self, Allocator allocator) @dynamic
{
return string::format("[%s..%s]", self.start, self.end, allocator: allocator);
}
fn String Range.to_tstring(&self)
{
return self.to_string(allocator::temp());
}
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
fn usz? Range.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.printf("[%s..%s]", self.start, self.end)!;
}
@@ -66,26 +51,11 @@ fn bool ExclusiveRange.contains(&self, Type value) @inline
return value >= self.start && value < self.end;
}
fn usz! ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
fn usz? ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.printf("[%s..<%s]", self.start, self.end)!;
}
fn String ExclusiveRange.to_new_string(&self, Allocator allocator = null) @dynamic
{
return self.to_string(allocator ?: allocator::heap());
}
fn String ExclusiveRange.to_string(&self, Allocator allocator) @dynamic
{
return string::format("[%s..<%s]", self.start, self.end, allocator: allocator);
}
fn String ExclusiveRange.to_tstring(&self)
{
return self.to_new_string(allocator::temp());
}
<*
@require index < self.len() : "Can't index into an empty range"
*>

View File

@@ -1,11 +1,14 @@
<*
@require values::@is_int(SIZE) &&& SIZE > 0 "The size must be positive integer"
@require Type.kindof == ARRAY : "Required an array type"
*>
module std::collections::ringbuffer(<Type, SIZE>);
module std::collections::ringbuffer{Type};
import std::io;
struct RingBuffer
alias Element = $typeof((Type){}[0]);
struct RingBuffer (Printable)
{
Type[SIZE] buf;
Type buf;
usz written;
usz head;
}
@@ -15,9 +18,9 @@ fn void RingBuffer.init(&self) @inline
*self = {};
}
fn void RingBuffer.push(&self, Type c)
fn void RingBuffer.push(&self, Element c)
{
if (self.written < SIZE)
if (self.written < self.buf.len)
{
self.buf[self.written] = c;
self.written++;
@@ -25,14 +28,14 @@ fn void RingBuffer.push(&self, Type c)
else
{
self.buf[self.head] = c;
self.head = (self.head + 1) % SIZE;
self.head = (self.head + 1) % self.buf.len;
}
}
fn Type RingBuffer.get(&self, usz index) @operator([])
fn Element RingBuffer.get(&self, usz index) @operator([])
{
index %= SIZE;
usz avail = SIZE - self.head;
index %= self.buf.len;
usz avail = self.buf.len - self.head;
if (index < avail)
{
return self.buf[self.head + index];
@@ -40,25 +43,31 @@ fn Type RingBuffer.get(&self, usz index) @operator([])
return self.buf[index - avail];
}
fn Type! RingBuffer.pop(&self)
fn Element? RingBuffer.pop(&self)
{
switch
{
case self.written == 0:
return SearchResult.MISSING?;
case self.written < SIZE:
return NO_MORE_ELEMENT?;
case self.written < self.buf.len:
self.written--;
return self.buf[self.written];
default:
self.head = (self.head - 1) % SIZE;
self.head = (self.head - 1) % self.buf.len;
return self.buf[self.head];
}
}
fn usz RingBuffer.read(&self, usz index, Type[] buffer)
fn usz? RingBuffer.to_format(&self, Formatter* format) @dynamic
{
index %= SIZE;
if (self.written < SIZE)
// Improve this?
return format.printf("%s", self.buf);
}
fn usz RingBuffer.read(&self, usz index, Element[] buffer)
{
index %= self.buf.len;
if (self.written < self.buf.len)
{
if (index >= self.written) return 0;
usz end = self.written - index;
@@ -66,7 +75,7 @@ fn usz RingBuffer.read(&self, usz index, Type[] buffer)
buffer[:n] = self.buf[index:n];
return n;
}
usz end = SIZE - self.head;
usz end = self.buf.len - self.head;
if (index >= end)
{
index -= end;
@@ -75,13 +84,13 @@ fn usz RingBuffer.read(&self, usz index, Type[] buffer)
buffer[:n] = self.buf[index:n];
return n;
}
if (buffer.len <= SIZE - index)
if (buffer.len <= self.buf.len - index)
{
usz n = buffer.len;
buffer[:n] = self.buf[self.head + index:n];
return n;
}
usz n1 = SIZE - index;
usz n1 = self.buf.len - index;
buffer[:n1] = self.buf[self.head + index:n1];
buffer = buffer[n1..];
index -= n1;
@@ -90,10 +99,10 @@ fn usz RingBuffer.read(&self, usz index, Type[] buffer)
return n1 + n2;
}
fn void RingBuffer.write(&self, Type[] buffer)
fn void RingBuffer.write(&self, Element[] buffer)
{
usz i;
while (self.written < SIZE && i < buffer.len)
while (self.written < self.buf.len && i < buffer.len)
{
self.buf[self.written] = buffer[i++];
self.written++;
@@ -101,6 +110,6 @@ fn void RingBuffer.write(&self, Type[] buffer)
foreach (c : buffer[i..])
{
self.buf[self.head] = c;
self.head = (self.head + 1) % SIZE;
self.head = (self.head + 1) % self.buf.len;
}
}

View File

@@ -1,4 +1,4 @@
module std::collections::tuple(<Type1, Type2>);
module std::collections::tuple{Type1, Type2};
struct Tuple
{
@@ -6,7 +6,7 @@ struct Tuple
Type2 second;
}
module std::collections::triple(<Type1, Type2, Type3>);
module std::collections::triple{Type1, Type2, Type3};
struct Triple
{

View File

@@ -37,19 +37,11 @@ struct QOIDesc
QOIChannels channels;
QOIColorspace colorspace;
}
<*
QOI Errors.
These are all the possible bad outcomes.
*>
fault QOIError
{
INVALID_PARAMETERS,
FILE_OPEN_FAILED,
FILE_WRITE_FAILED,
INVALID_DATA,
TOO_MANY_PIXELS
}
faultdef INVALID_PARAMETERS, FILE_OPEN_FAILED, FILE_WRITE_FAILED, INVALID_DATA, TOO_MANY_PIXELS;
// Let the user decide if they want to use std::io
@@ -67,27 +59,17 @@ import std::io;
The function returns an optional, which can either be a QOIError
or the number of bytes written on success.
@param [in] filename `The file's name to write the image to`
@param [in] input `The raw RGB or RGBA pixels to encode`
@param [&in] desc `The descriptor of the image`
@param [in] filename : `The file's name to write the image to`
@param [in] input : `The raw RGB or RGBA pixels to encode`
@param [&in] desc : `The descriptor of the image`
*>
fn usz! write(String filename, char[] input, QOIDesc* desc)
fn usz? write(String filename, char[] input, QOIDesc* desc) => @pool()
{
@pool() {
// encode data
char[] output = encode(input, desc)!;
// encode data
char[] output = encode(tmem, input, desc)!;
// open file
File! f = file::open(filename, "wb");
if (catch f) { return QOIError.FILE_OPEN_FAILED?; }
// write data to file and close it
usz! written = f.write(output);
if (catch written) { return QOIError.FILE_WRITE_FAILED?; }
if (catch f.close()) { return QOIError.FILE_WRITE_FAILED?; }
return written;
};
file::save(filename, output)!;
return output.len;
}
@@ -108,22 +90,21 @@ fn usz! write(String filename, char[] input, QOIDesc* desc)
The returned pixel data should be free()d after use, or the decoding
and use of the data should be wrapped in a @pool() { ... }; block.
@param [in] filename `The file's name to read the image from`
@param [&out] desc `The descriptor to fill with the image's info`
@param channels `The channels to be used`
@param [in] filename : `The file's name to read the image from`
@param [&out] desc : `The descriptor to fill with the image's info`
@param channels : `The channels to be used`
@return? FILE_OPEN_FAILED, INVALID_DATA, TOO_MANY_PIXELS
*>
fn char[]! read(String filename, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap())
fn char[]? read(Allocator allocator, String filename, QOIDesc* desc, QOIChannels channels = AUTO) => @pool()
{
// read file
char[]! data = file::load_new(filename);
if (catch data) return QOIError.FILE_OPEN_FAILED?;
defer mem::free(data);
char[] data = file::load_temp(filename) ?? FILE_OPEN_FAILED?!;
// pass data to decode function
return decode(data, desc, channels, allocator);
return decode(allocator, data, desc, channels);
}
// Back to basic non-stdio mode
module std::compression::qoi;
import std::bits;
@@ -138,20 +119,21 @@ import std::bits;
and use of the data should be wrapped in a @pool() { ... }; block.
See the write() function for an example.
@param [in] input `The raw RGB or RGBA pixels to encode`
@param [&in] desc `The descriptor of the image`
@param [in] input : `The raw RGB or RGBA pixels to encode`
@param [&in] desc : `The descriptor of the image`
@return? INVALID_PARAMETERS, TOO_MANY_PIXELS, INVALID_DATA
*>
fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::heap())
fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
{
// check info in desc
if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_PARAMETERS?;
if (desc.channels == AUTO) return QOIError.INVALID_PARAMETERS?;
if (desc.width == 0 || desc.height == 0) return INVALID_PARAMETERS?;
if (desc.channels == AUTO) return INVALID_PARAMETERS?;
uint pixels = desc.width * desc.height;
if (pixels > PIXELS_MAX) return QOIError.TOO_MANY_PIXELS?;
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
// check input data size
uint image_size = pixels * desc.channels.id;
if (image_size != input.len) return QOIError.INVALID_DATA?;
if (image_size != input.len) return INVALID_DATA?;
// allocate memory for encoded data (output)
// header + chunk tag and RGB(A) data for each pixel + end of stream
@@ -191,70 +173,77 @@ fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::
if (desc.channels == RGBA) p.a = input[loc + 3];
// check if we can run the previous pixel
if (prev == p) {
if (prev == p)
{
run_length++;
if (run_length == 62 || loc == loc_end) {
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
run_length = 0;
}
} else {
// end last run if there was one
if (run_length > 0) {
if (run_length == 62 || loc == loc_end)
{
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
run_length = 0;
}
continue;
}
// end last run if there was one
if (run_length > 0)
{
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
run_length = 0;
}
switch {
// check if we can index the palette
case (palette[p.hash()] == p):
*@extract(OpIndex, output, &pos) = {
OP_INDEX,
p.hash()
switch
{
// check if we can index the palette
case (palette[p.hash()] == p):
*@extract(OpIndex, output, &pos) = {
OP_INDEX,
p.hash()
};
// check if we can use diff or luma
case (prev != p && prev.a == p.a):
// diff the pixels
diff = p.rgb - prev.rgb;
if (diff.r > -3 && diff.r < 2
&& diff.g > -3 && diff.g < 2
&& diff.b > -3 && diff.b < 2)
{
*@extract(OpDiff, output, &pos) = {
OP_DIFF,
(char)diff.r + 2,
(char)diff.g + 2,
(char)diff.b + 2
};
// check if we can use diff or luma
case (prev != p && prev.a == p.a):
// diff the pixels
diff = p.rgb - prev.rgb;
if (
diff.r > -3 && diff.r < 2 &&
diff.g > -3 && diff.g < 2 &&
diff.b > -3 && diff.b < 2
) {
*@extract(OpDiff, output, &pos) = {
OP_DIFF,
(char)diff.r + 2,
(char)diff.g + 2,
(char)diff.b + 2
};
palette[p.hash()] = p;
} else {
// check luma eligibility
luma = { diff.r - diff.g, diff.g, diff.b - diff.g };
if (
luma.r >= -8 && luma.r <= 7 &&
luma.g >= -32 && luma.g <= 31 &&
luma.b >= -8 && luma.b <= 7
) {
*@extract(OpLuma, output, &pos) = {
OP_LUMA,
(char)luma.g + 32,
(char)luma.r + 8,
(char)luma.b + 8
};
palette[p.hash()] = p;
} else { nextcase; }
}
// worst case scenario: just encode the raw pixel
default:
if (prev.a != p.a) {
*@extract(OpRGBA, output, &pos) = { OP_RGBA, p.r, p.g, p.b, p.a };
} else {
*@extract(OpRGB, output, &pos) = { OP_RGB, p.r, p.g, p.b };
}
palette[p.hash()] = p;
}
break;
}
// check luma eligibility
luma = { diff.r - diff.g, diff.g, diff.b - diff.g };
if (luma.r >= -8 && luma.r <= 7
&& luma.g >= -32 && luma.g <= 31
&& luma.b >= -8 && luma.b <= 7)
{
*@extract(OpLuma, output, &pos) = {
OP_LUMA,
(char)luma.g + 32,
(char)luma.r + 8,
(char)luma.b + 8
};
palette[p.hash()] = p;
break;
}
nextcase;
// worst case scenario: just encode the raw pixel
default:
if (prev.a != p.a)
{
*@extract(OpRGBA, output, &pos) = { OP_RGBA, p.r, p.g, p.b, p.a };
}
else
{
*@extract(OpRGB, output, &pos) = { OP_RGB, p.r, p.g, p.b };
}
palette[p.hash()] = p;
}
}
@@ -266,6 +255,7 @@ fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::
}
<*
Decode a QOI image from memory.
@@ -283,34 +273,35 @@ fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::
The returned pixel data should be free()d after use, or the decoding
and use of the data should be wrapped in a @pool() { ... }; block.
@param [in] data `The QOI image data to decode`
@param [&out] desc `The descriptor to fill with the image's info`
@param channels `The channels to be used`
@param [in] data : `The QOI image data to decode`
@param [&out] desc : `The descriptor to fill with the image's info`
@param channels : `The channels to be used`
@return? INVALID_DATA, TOO_MANY_PIXELS
*>
fn char[]! decode(char[] data, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap())
fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels channels = AUTO) @nodiscard
{
// check input data
if (data.len < Header.sizeof + END_OF_STREAM.len) return QOIError.INVALID_DATA?;
if (data.len < Header.sizeof + END_OF_STREAM.len) return INVALID_DATA?;
// get header
Header* header = (Header*)data.ptr;
// check magic bytes (FourCC)
if (bswap(header.be_magic) != 'qoif') return QOIError.INVALID_DATA?;
if (bswap(header.be_magic) != 'qoif') return INVALID_DATA?;
// copy header data to desc
desc.width = bswap(header.be_width);
desc.height = bswap(header.be_height);
desc.channels = @enumcast(QOIChannels, header.channels)!; // Rethrow if invalid
desc.colorspace = @enumcast(QOIColorspace, header.colorspace)!; // Rethrow if invalid
if (desc.channels == AUTO) return QOIError.INVALID_DATA?; // Channels must be specified in the header
if (desc.channels == AUTO) return INVALID_DATA?; // Channels must be specified in the header
// check width and height
if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_DATA?;
if (desc.width == 0 || desc.height == 0) return INVALID_DATA?;
// check pixel count
ulong pixels = (ulong)desc.width * (ulong)desc.height;
if (pixels > PIXELS_MAX) return QOIError.TOO_MANY_PIXELS?;
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
uint pos = Header.sizeof; // Current position in data
uint loc; // Current position in image (top-left corner)
@@ -412,16 +403,22 @@ struct Header @packed
const char[*] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1};
// inefficient, but it's only run once at a time
<*
@return? INVALID_DATA
*>
macro @enumcast($Type, raw)
{
foreach (value : $Type.values) {
foreach (value : $Type.values)
{
if (value.id == raw) return value;
}
return QOIError.INVALID_DATA?;
return INVALID_DATA?;
}
distinct Pixel = inline char[<4>];
macro char Pixel.hash(Pixel p) {
typedef Pixel = inline char[<4>];
macro char Pixel.hash(Pixel p)
{
return (p.r * 3 + p.g * 5 + p.b * 7 + p.a * 11) % 64;
}
@@ -452,7 +449,7 @@ bitstruct OpDiff : char
char diff_green : 2..3;
char diff_blue : 0..1;
}
bitstruct OpLuma : ushort
bitstruct OpLuma : ushort @align(1)
{
char tag : 6..7;
char diff_green : 0..5;

View File

@@ -31,6 +31,11 @@ struct ArenaAllocatorHeader @local
char[*] data;
}
macro ArenaAllocator* wrap(char[] bytes)
{
return (ArenaAllocator){}.init(bytes);
}
<*
@require ptr != null
*>
@@ -45,24 +50,25 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
}
}
fn usz ArenaAllocator.mark(&self) @dynamic => self.used;
fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark;
fn usz ArenaAllocator.mark(&self) => self.used;
fn void ArenaAllocator.reset(&self, usz mark) => self.used = mark;
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
usz total_len = self.data.len;
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
void* start_mem = self.data.ptr;
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof;
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz end = (usz)(mem - self.data.ptr) + size;
if (end > total_len) return AllocationFailure.OUT_OF_MEMORY?;
if (end > total_len) return mem::OUT_OF_MEMORY?;
self.used = end;
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
header.size = size;
@@ -72,16 +78,17 @@ fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz a
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require old_pointer != null
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
usz total_len = self.data.len;
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
usz old_size = header.size;
// Do last allocation and alignment match?
@@ -94,7 +101,7 @@ fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
else
{
usz new_used = self.used + size - old_size;
if (new_used > total_len) return AllocationFailure.OUT_OF_MEMORY?;
if (new_used > total_len) return mem::OUT_OF_MEMORY?;
self.used = new_used;
}
header.size = size;

View File

@@ -0,0 +1,210 @@
module std::core::mem::allocator;
import std::io, std::math;
struct AllocChunk @local
{
usz size;
char[*] data;
}
struct BackedArenaAllocator (Allocator)
{
Allocator backing_allocator;
ExtraPage* last_page;
usz used;
usz capacity;
char[*] data;
}
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
struct ExtraPage @local
{
ExtraPage* prev_page;
void* start;
usz mark;
usz size;
usz ident;
char[*] data;
}
macro usz ExtraPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
macro bool ExtraPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
<*
@require size >= 16
*>
fn BackedArenaAllocator*? new_backed_allocator(usz size, Allocator allocator)
{
BackedArenaAllocator* temp = allocator::alloc_with_padding(allocator, BackedArenaAllocator, size)!;
temp.last_page = null;
temp.backing_allocator = allocator;
temp.used = 0;
temp.capacity = size;
return temp;
}
fn void BackedArenaAllocator.destroy(&self)
{
self.reset(0);
if (self.last_page) (void)self._free_page(self.last_page);
allocator::free(self.backing_allocator, self);
}
fn usz BackedArenaAllocator.mark(&self) => self.used;
fn void BackedArenaAllocator.release(&self, void* old_pointer, bool) @dynamic
{
usz old_size = *(usz*)(old_pointer - DEFAULT_SIZE_PREFIX);
if (old_pointer + old_size == &self.data[self.used])
{
self.used -= old_size;
asan::poison_memory_region(&self.data[self.used], old_size);
}
}
fn void BackedArenaAllocator.reset(&self, usz mark)
{
ExtraPage *last_page = self.last_page;
while (last_page && last_page.mark > mark)
{
self.used = last_page.mark;
ExtraPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
}
self.last_page = last_page;
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
if (!last_page)
{
usz cleaned = self.used - mark;
if (cleaned > 0)
{
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
self.data[mark : cleaned] = 0xAA;
$endif
asan::poison_memory_region(&self.data[mark], cleaned);
}
}
$endif
self.used = mark;
}
fn void? BackedArenaAllocator._free_page(&self, ExtraPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
}
fn void*? BackedArenaAllocator._realloc_page(&self, ExtraPage* page, usz size, usz alignment) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
// Walk backwards to find the pointer to this page.
ExtraPage **pointer_to_prev = &self.last_page;
// Remove the page from the list
while (*pointer_to_prev != page)
{
pointer_to_prev = &((*pointer_to_prev).prev_page);
}
*pointer_to_prev = page.prev_page;
usz page_size = page.pagesize();
// Clear on size > original size.
void* data = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
self.backing_allocator.release(real_pointer, page.is_aligned());
return data;
}
fn void*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
{
AllocChunk *chunk = pointer - AllocChunk.sizeof;
if (chunk.size == (usz)-1)
{
assert(self.last_page, "Realloc of unrelated pointer");
// First grab the page
ExtraPage *page = pointer - ExtraPage.sizeof;
return self._realloc_page(page, size, alignment);
}
AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
<*
@require size > 0
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
*>
fn void*? BackedArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
void* start_mem = &self.data;
void* starting_ptr = start_mem + self.used;
void* aligned_header_start = mem::aligned_pointer(starting_ptr, AllocChunk.alignof);
void* mem = aligned_header_start + AllocChunk.sizeof;
if (alignment > AllocChunk.alignof)
{
mem = mem::aligned_pointer(mem, alignment);
}
usz new_usage = (usz)(mem - start_mem) + size;
// Arena allocation, simple!
if (new_usage <= self.capacity)
{
asan::unpoison_memory_region(starting_ptr, new_usage - self.used);
AllocChunk* chunk_start = mem - AllocChunk.sizeof;
chunk_start.size = size;
self.used = new_usage;
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
// Fallback to backing allocator
ExtraPage* page;
// We have something we need to align.
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
// This is actually simpler, since it will create the offset for us.
usz total_alloc_size = mem::aligned_offset(ExtraPage.sizeof + size, alignment);
if (init_type == ZERO)
{
mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
}
else
{
mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
}
void* start = mem;
mem += mem::aligned_offset(ExtraPage.sizeof, alignment);
page = (ExtraPage*)mem - 1;
page.start = start;
page.size = size | PAGE_IS_ALIGNED;
}
else
{
// Here we might need to pad
usz padded_header_size = mem::aligned_offset(ExtraPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT);
usz total_alloc_size = padded_header_size + size;
void* alloc = self.backing_allocator.acquire(total_alloc_size, init_type, 0)!;
// Find the page.
page = alloc + padded_header_size - ExtraPage.sizeof;
assert(mem::ptr_is_aligned(page, BackedArenaAllocator.alignof));
assert(mem::ptr_is_aligned(&page.data[0], mem::DEFAULT_MEM_ALIGNMENT));
page.start = alloc;
page.size = size;
}
// Mark it as a page
page.ident = ~(usz)0;
// Store when it was created
page.mark = ++self.used;
// Hook up the page.
page.prev_page = self.last_page;
self.last_page = page;
return &page.data[0];
}

View File

@@ -16,7 +16,7 @@ struct DynamicArenaAllocator (Allocator)
@param [&inout] allocator
@require page_size >= 128
*>
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator allocator)
fn void DynamicArenaAllocator.init(&self, Allocator allocator, usz page_size)
{
self.page = null;
self.unused_page = null;
@@ -61,8 +61,8 @@ struct DynamicArenaChunk @local
}
<*
@require ptr
@require self.page `tried to free pointer on invalid allocator`
@require ptr != null
@require self.page != null : `tried to free pointer on invalid allocator`
*>
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
{
@@ -75,11 +75,12 @@ fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
}
<*
@require size > 0 `Resize doesn't support zeroing`
@require old_pointer != null `Resize doesn't handle null pointers`
@require self.page `tried to realloc pointer on invalid allocator`
@require size > 0 : `Resize doesn't support zeroing`
@require old_pointer != null : `Resize doesn't handle null pointers`
@require self.page != null : `tried to realloc pointer on invalid allocator`
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
DynamicArenaPage* current_page = self.page;
alignment = alignment_for_allocation(alignment);
@@ -109,9 +110,8 @@ fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a
return new_mem;
}
fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
fn void DynamicArenaAllocator.reset(&self)
{
assert(mark == 0, "Unexpectedly reset dynamic arena allocator with mark %d", mark);
DynamicArenaPage* page = self.page;
DynamicArenaPage** unused_page_ptr = &self.unused_page;
while (page)
@@ -129,15 +129,16 @@ fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
<*
@require math::is_power_of_2(alignment)
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @local
fn void*? DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @local
{
// First, make sure that we can align it, extending the page size if needed.
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + alignment, alignment));
assert(page_size > size + DynamicArenaChunk.sizeof);
// Grab the page without alignment (we do it ourselves)
void* mem = allocator::malloc_try(self.backing_allocator, page_size)!;
DynamicArenaPage*! page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
DynamicArenaPage*? page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
if (catch err = page)
{
allocator::free(self.backing_allocator, mem);
@@ -157,21 +158,29 @@ fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @loca
}
<*
@require size > 0 `acquire expects size > 0`
@require size > 0 : `acquire expects size > 0`
@require !alignment || math::is_power_of_2(alignment)
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
DynamicArenaPage* page = self.page;
void* ptr = {|
void* ptr @noinit;
do SET_DONE:
{
if (!page && self.unused_page)
{
self.page = page = self.unused_page;
self.unused_page = page.prev_arena;
page.prev_arena = null;
}
if (!page) return self._alloc_new(size, alignment);
if (!page)
{
ptr = self._alloc_new(size, alignment)!;
break SET_DONE;
}
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
usz new_used = start - page.memory + size;
if ALLOCATE_NEW: (new_used > page.total)
@@ -188,15 +197,15 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
break ALLOCATE_NEW;
}
}
return self._alloc_new(size, alignment);
ptr = self._alloc_new(size, alignment)!;
break SET_DONE;
}
page.used = new_used;
assert(start + size == page.memory + page.used);
void* mem = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
ptr = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)ptr - 1;
chunk.size = size;
return mem;
|}!;
};
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
return ptr;
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
@@ -12,8 +12,8 @@ struct SimpleHeapAllocator (Allocator)
}
<*
@require allocator "An underlying memory provider must be given"
@require !self.free_list "The allocator may not be already initialized"
@require allocator != null : "An underlying memory provider must be given"
@require !self.free_list : "The allocator may not be already initialized"
*>
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
{
@@ -21,7 +21,7 @@ fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
self.free_list = null;
}
fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*? SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
if (init_type == ZERO)
{
@@ -30,7 +30,7 @@ fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type,
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size);
}
fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*? SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
return alignment > 0
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment)
@@ -52,7 +52,7 @@ fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dyn
<*
@require old_pointer && bytes > 0
*>
fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
fn void*? SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
{
// Find the block header.
Header* block = (Header*)old_pointer - 1;
@@ -64,14 +64,14 @@ fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @loc
return new;
}
fn void*! SimpleHeapAllocator._calloc(&self, usz bytes) @local
fn void*? SimpleHeapAllocator._calloc(&self, usz bytes) @local
{
void* data = self._alloc(bytes)!;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
{
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
if (!self.free_list)
@@ -120,7 +120,7 @@ fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
return self._alloc(aligned_bytes);
}
fn void! SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
fn void? SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
{
assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes);
char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!;

View File

@@ -7,39 +7,39 @@ import std::io;
import libc;
const LibcAllocator LIBC_ALLOCATOR = {};
distinct LibcAllocator (Allocator, Printable) = uptr;
typedef LibcAllocator (Allocator, Printable) = uptr;
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
fn usz! LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
fn usz? LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
module std::core::mem::allocator @if(env::POSIX);
import std::os;
import libc;
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
if (init_type == ZERO)
{
void* data @noinit;
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
}
else
{
void* data @noinit;
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
}
else
{
if (!(data = libc::malloc(bytes))) return AllocationFailure.OUT_OF_MEMORY?;
if (!(data = libc::malloc(bytes))) return mem::OUT_OF_MEMORY?;
}
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
@@ -48,13 +48,13 @@ fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
}
}
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
void* new_ptr;
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return AllocationFailure.OUT_OF_MEMORY?;
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return mem::OUT_OF_MEMORY?;
$switch
$switch:
$case env::DARWIN:
usz old_usable_size = darwin::malloc_size(old_ptr);
$case env::LINUX:
@@ -78,31 +78,31 @@ module std::core::mem::allocator @if(env::WIN32);
import std::os::win32;
import libc;
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
if (init_type == ZERO)
{
if (alignment > 0)
{
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY?;
}
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
}
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
if (!data) return mem::OUT_OF_MEMORY?;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
}
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (alignment)
{
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY?;
}
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
}
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
@@ -118,17 +118,17 @@ fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
module std::core::mem::allocator @if(!env::WIN32 && !env::POSIX && env::LIBC);
import libc;
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
if (init_type == ZERO)
{
void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1);
return data ?: AllocationFailure.OUT_OF_MEMORY?;
return data ?: mem::OUT_OF_MEMORYY?;
}
else
{
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
if (!data) return mem::OUT_OF_MEMORY?;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
@@ -137,14 +137,14 @@ fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
}
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (alignment)
{
void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!;
return data ?: AllocationFailure.OUT_OF_MEMORY?;
return data ?: mem::OUT_OF_MEMORY?;
}
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
}

View File

@@ -56,13 +56,13 @@ struct OnStackAllocatorHeader
}
<*
@require old_pointer
@require old_pointer != null
*>
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{
if (allocation_in_stack_mem(self, old_pointer)) return;
on_stack_allocator_remove_chunk(self, old_pointer);
self.release(old_pointer, aligned);
self.backing_allocator.release(old_pointer, aligned);
}
fn bool allocation_in_stack_mem(OnStackAllocator* a, void* ptr) @local
@@ -102,9 +102,9 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a
<*
@require size > 0
@require old_pointer != null
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
*>
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
if (!allocation_in_stack_mem(self, old_pointer))
{
@@ -121,10 +121,10 @@ fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignm
}
<*
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require size > 0
*>
fn void*! OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*? OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
bool aligned = alignment > 0;
alignment = alignment_for_allocation(alignment);

View File

@@ -1,5 +1,7 @@
module std::core::mem::allocator;
import std::io, std::math;
import std::core::sanitizer::asan;
struct TempAllocatorChunk @local
{
@@ -11,8 +13,11 @@ struct TempAllocator (Allocator)
{
Allocator backing_allocator;
TempAllocatorPage* last_page;
TempAllocator* derived;
bool allocated;
usz used;
usz capacity;
usz original_capacity;
char[*] data;
}
@@ -23,7 +28,6 @@ struct TempAllocatorPage
{
TempAllocatorPage* prev_page;
void* start;
usz mark;
usz size;
usz ident;
char[*] data;
@@ -34,25 +38,98 @@ macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED ==
<*
@require size >= 16
@require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator."
*>
fn TempAllocator*! new_temp_allocator(usz size, Allocator allocator)
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size)
{
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
temp.last_page = null;
temp.backing_allocator = allocator;
temp.used = 0;
temp.capacity = size;
temp.allocated = true;
temp.derived = null;
temp.original_capacity = temp.capacity = size;
return temp;
}
fn void TempAllocator.destroy(&self)
<*
@require !self.derived
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
@require mult > 0 : "The multiple can never be zero"
*>
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz min_size, usz buffer, usz mult)
{
self.reset(0);
if (self.last_page) (void)self._free_page(self.last_page);
allocator::free(self.backing_allocator, self);
usz remaining = self.capacity - self.used;
void* mem @noinit;
usz size @noinit;
if (min_size + buffer > remaining)
{
return self.derived = new_temp_allocator(self.backing_allocator, min_size * mult)!;
}
usz start = mem::aligned_offset(self.used + buffer, mem::DEFAULT_MEM_ALIGNMENT);
void* ptr = &self.data[start];
TempAllocator* temp = (TempAllocator*)ptr;
$if env::ADDRESS_SANITIZER:
asan::unpoison_memory_region(ptr, TempAllocator.sizeof);
$endif
temp.last_page = null;
temp.backing_allocator = self.backing_allocator;
temp.used = 0;
temp.allocated = false;
temp.derived = null;
temp.original_capacity = temp.capacity = self.capacity - start - TempAllocator.sizeof;
self.capacity = start;
self.derived = temp;
return temp;
}
fn usz TempAllocator.mark(&self) @dynamic => self.used;
fn void TempAllocator.reset(&self)
{
TempAllocator* child = self.derived;
if (!child) return;
while (child)
{
TempAllocator* old = child;
child = old.derived;
old.destroy();
}
self.capacity = self.original_capacity;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(&self.data[self.used], self.capacity - self.used);
$endif
self.derived = null;
}
<*
@require self.allocated : "Only a top level allocator should be freed."
*>
fn void TempAllocator.free(&self)
{
self.reset();
self.destroy();
}
fn void TempAllocator.destroy(&self) @local
{
TempAllocatorPage *last_page = self.last_page;
while (last_page)
{
TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
}
if (self.allocated)
{
allocator::free(self.backing_allocator, self);
return;
}
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
self.data[0 : self.used] = 0xAA;
$else
asan::poison_memory_region(&self.data[0], self.used);
$endif
$endif
}
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
{
@@ -63,40 +140,15 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
asan::poison_memory_region(&self.data[self.used], old_size);
}
}
fn void TempAllocator.reset(&self, usz mark) @dynamic
{
TempAllocatorPage *last_page = self.last_page;
while (last_page && last_page.mark > mark)
{
self.used = last_page.mark;
TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
}
self.last_page = last_page;
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
if (!last_page)
{
usz cleaned = self.used - mark;
if (cleaned > 0)
{
$if env::COMPILER_SAFE_MODE:
self.data[mark : cleaned] = 0xAA;
$endif
asan::poison_memory_region(&self.data[mark], cleaned);
}
}
$endif
self.used = mark;
}
fn void! TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
fn void? TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
}
fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
fn void*? TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
@@ -112,12 +164,13 @@ fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size,
usz page_size = page.pagesize();
// Clear on size > original size.
void* data = self.acquire(size, NO_ZERO, alignment)!;
if (page_size > size) page_size = size;
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
self.backing_allocator.release(real_pointer, page.is_aligned());
return data;
}
fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
{
TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof;
if (chunk.size == (usz)-1)
@@ -127,19 +180,47 @@ fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
return self._realloc_page(page, size, alignment);
}
TempAllocatorChunk* data = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
bool is_realloc_of_last = chunk.size + pointer == &self.data[self.used];
if (is_realloc_of_last)
{
isz diff = size - chunk.size;
if (diff == 0) return pointer;
if (self.capacity - self.used > diff)
{
chunk.size += diff;
self.used += diff;
$if env::ADDRESS_SANITIZER:
if (diff < 0)
{
asan::poison_memory_region(pointer + chunk.size, -diff);
}
else
{
asan::unpoison_memory_region(pointer, chunk.size);
}
$endif
return pointer;
}
}
void* data = self.acquire(size, NO_ZERO, alignment)!;
usz len_to_copy = chunk.size > size ? size : chunk.size;
mem::copy(data, pointer, len_to_copy, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
if (is_realloc_of_last)
{
self.used = (uptr)chunk - (uptr)&self.data;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(chunk, TempAllocatorChunk.sizeof + chunk.size);
$endif
}
return data;
}
<*
@require size > 0
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
*>
fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
void* start_mem = &self.data;
@@ -179,8 +260,10 @@ fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
{
mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
}
void* start = mem;
mem += mem::aligned_offset(TempAllocatorPage.sizeof, alignment);
page = (TempAllocatorPage*)mem - 1;
page.start = mem;
page.start = start;
page.size = size | PAGE_IS_ALIGNED;
}
else
@@ -200,29 +283,9 @@ fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
// Mark it as a page
page.ident = ~(usz)0;
// Store when it was created
page.mark = ++self.used;
// Hook up the page.
page.prev_page = self.last_page;
self.last_page = page;
return &page.data[0];
}
fn void! TempAllocator.print_pages(&self, File* f)
{
TempAllocatorPage *last_page = self.last_page;
if (!last_page)
{
io::fprintf(f, "No pages.\n")!;
return;
}
io::fprintf(f, "---Pages----\n")!;
uint index = 0;
while (last_page)
{
bool is_not_aligned = !(last_page.size & (1u64 << 63));
io::fprintf(f, "%d. Alloc: %d %d at %p%s\n", ++index,
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]")!;
last_page = last_page.prev_page;
}
}

View File

@@ -13,7 +13,7 @@ struct Allocation
void*[MAX_BACKTRACE] backtrace;
}
def AllocMap = HashMap(<uptr, Allocation>);
alias AllocMap = HashMap { uptr, Allocation };
// A simple tracking allocator.
// It tracks allocations using a hash map but
@@ -29,12 +29,12 @@ struct TrackingAllocator (Allocator)
<*
Initialize a tracking allocator to wrap (and track) another allocator.
@param [&inout] allocator "The allocator to track"
@param [&inout] allocator : "The allocator to track"
*>
fn void TrackingAllocator.init(&self, Allocator allocator)
{
*self = { .inner_allocator = allocator };
self.map.new_init(allocator: allocator);
self.map.init(allocator);
}
<*
@@ -49,13 +49,10 @@ fn void TrackingAllocator.free(&self)
<*
@return "the total allocated memory not yet freed."
*>
fn usz TrackingAllocator.allocated(&self)
fn usz TrackingAllocator.allocated(&self) => @pool()
{
usz allocated = 0;
@pool()
{
foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
};
foreach (&allocation : self.map.tvalues()) allocated += allocation.size;
return allocated;
}
@@ -71,7 +68,7 @@ fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
{
return self.map.value_tlist();
return self.map.tvalues();
}
<*
@@ -79,7 +76,7 @@ fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
*>
fn usz TrackingAllocator.allocation_count(&self) => self.map.count;
fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*? TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
void* data = self.inner_allocator.acquire(size, init_type, alignment)!;
self.allocs_total++;
@@ -90,7 +87,7 @@ fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, us
return data;
}
fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*? TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
void* data = self.inner_allocator.resize(old_pointer, size, alignment)!;
self.map.remove((uptr)old_pointer);
@@ -116,101 +113,103 @@ fn void TrackingAllocator.clear(&self)
self.map.clear();
}
fn bool TrackingAllocator.has_leaks(&self)
{
return self.map.len() > 0;
}
fn void TrackingAllocator.print_report(&self) => self.fprint_report(io::stdout())!!;
fn void! TrackingAllocator.fprint_report(&self, OutStream out)
{
fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
{
usz total = 0;
usz entries = 0;
bool leaks = false;
@pool()
{
Allocation[] allocs = self.map.value_tlist();
if (allocs.len)
{
if (!allocs[0].backtrace[0])
{
io::fprintn(out, "======== Memory Report ========")!;
io::fprintn(out, "Size in bytes Address")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
}
io::fprintn(out, "===============================")!;
}
else
Allocation[] allocs = self.map.tvalues();
if (allocs.len)
{
if (!allocs[0].backtrace[0])
{
io::fprintn(out, "======== Memory Report ========")!;
io::fprintn(out, "Size in bytes Address")!;
foreach (i, &allocation : allocs)
{
io::fprintn(out, "================================== Memory Report ==================================")!;
io::fprintn(out, "Size in bytes Address Function ")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
BacktraceList backtraces = {};
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
if (allocation.backtrace[3])
{
trace = backtrace::symbolize_backtrace(allocation.backtrace[3:1], allocator::temp()).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
}
if (trace.function.len) leaks = true;
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
allocation.ptr, trace.function.len ? trace.function : "???",
trace.line ? trace.line : 0)!;
}
io::fprintn(out, "===================================================================================")!;
entries++;
total += allocation.size;
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
}
io::fprintn(out, "===============================")!;
}
else
{
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
}
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
io::fprintfn(out, "- Total current allocations: %d", entries)!;
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
if (leaks)
{
io::fprintn(out)!;
io::fprintn(out, "Full leak report:")!;
io::fprintn(out, "================================== Memory Report ==================================")!;
io::fprintn(out, "Size in bytes Address Function ")!;
foreach (i, &allocation : allocs)
{
if (!allocation.backtrace[3])
{
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
continue;
}
entries++;
total += allocation.size;
BacktraceList backtraces = {};
usz end = MAX_BACKTRACE;
foreach (j, val : allocation.backtrace)
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
if (allocation.backtrace[3])
{
if (!val)
{
end = j;
break;
}
trace = backtrace::symbolize_backtrace(tmem, allocation.backtrace[3:1]).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
}
BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], allocator::temp())!;
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
foreach (trace : list)
if (trace.function.len) leaks = true;
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
allocation.ptr, trace.function.len ? trace.function : "???",
trace.line ? trace.line : 0)!;
}
io::fprintn(out, "===================================================================================")!;
}
}
else
{
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
}
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
io::fprintfn(out, "- Total current allocations: %d", entries)!;
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
if (leaks)
{
io::fprintn(out)!;
io::fprintn(out, "Full leak report:")!;
foreach (i, &allocation : allocs)
{
if (!allocation.backtrace[3])
{
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
continue;
}
BacktraceList backtraces = {};
usz end = MAX_BACKTRACE;
foreach (j, val : allocation.backtrace)
{
if (!val)
{
if (trace.has_file())
{
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
continue;
}
if (trace.is_unknown())
{
io::fprintfn(out, " ??? (in unknown)");
continue;
}
io::fprintfn(out, " %s (source unavailable)", trace.function);
end = j;
break;
}
}
BacktraceList list = backtrace::symbolize_backtrace(tmem, allocation.backtrace[3..(end - 1)])!;
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
foreach (trace : list)
{
if (trace.has_file())
{
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
continue;
}
if (trace.is_unknown())
{
io::fprintfn(out, " ??? (in unknown)");
continue;
}
io::fprintfn(out, " %s (source unavailable)", trace.function);
}
}
};
}
}

View File

@@ -5,7 +5,7 @@ import std::core::array::slice;
@param [in] array
@param [in] element
@return "the first index of the element"
@return! SearchResult.MISSING
@return? NOT_FOUND
*>
macro index_of(array, element)
{
@@ -13,19 +13,20 @@ macro index_of(array, element)
{
if (*e == element) return i;
}
return SearchResult.MISSING?;
return NOT_FOUND?;
}
<*
@require @typekind(array) == VECTOR || @typekind(array) == ARRAY
@require @typekind(array[0]) == VECTOR || @typekind(array[0]) == ARRAY
@require @typekind(array_ptr) == POINTER
@require @typekind(*array_ptr) == VECTOR || @typekind(*array_ptr) == ARRAY
@require @typekind((*array_ptr)[0]) == VECTOR || @typekind((*array_ptr)[0]) == ARRAY
*>
macro slice2d(array, x = 0, xlen = 0, y = 0, ylen = 0)
macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
{
if (xlen < 1) xlen = $typeof(array[0]).len + xlen;
if (ylen < 1) ylen = $typeof(array).len + ylen;
var $ElementType = $typeof(array[0][0]);
return Slice2d(<$ElementType>) { ($ElementType*)&array, $typeof(array[0]).len, y, ylen, x, xlen };
if (xlen < 1) xlen = $typeof((*array_ptr)[0]).len + xlen;
if (ylen < 1) ylen = $typeof((*array_ptr)).len + ylen;
var $ElementType = $typeof((*array_ptr)[0][0]);
return Slice2d{$ElementType} { ($ElementType*)array_ptr, $typeof((*array_ptr)[0]).len, y, ylen, x, xlen };
}
@@ -33,7 +34,7 @@ macro slice2d(array, x = 0, xlen = 0, y = 0, ylen = 0)
@param [in] array
@param [in] element
@return "the last index of the element"
@return! SearchResult.MISSING
@return? NOT_FOUND
*>
macro rindex_of(array, element)
{
@@ -41,7 +42,7 @@ macro rindex_of(array, element)
{
if (*e == element) return i;
}
return SearchResult.MISSING?;
return NOT_FOUND?;
}
<*
@@ -49,13 +50,13 @@ macro rindex_of(array, element)
@param [in] arr1
@param [in] arr2
@param [&inout] allocator "The allocator to use, default is the heap allocator"
@param [&inout] allocator : "The allocator to use, default is the heap allocator"
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len
*>
macro concat(arr1, arr2, Allocator allocator) @nodiscard
macro concat(Allocator allocator, arr1, arr2) @nodiscard
{
var $Type = $typeof(arr1[0]);
$Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len);
@@ -69,22 +70,6 @@ macro concat(arr1, arr2, Allocator allocator) @nodiscard
}
return result;
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
@param [in] arr1
@param [in] arr2
@param [&inout] allocator "The allocator to use, default is the heap allocator"
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len
*>
macro concat_new(arr1, arr2, Allocator allocator = allocator::heap()) @nodiscard
{
return concat(arr1, arr2, allocator);
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them,
allocated using the temp allocator.
@@ -93,12 +78,12 @@ macro concat_new(arr1, arr2, Allocator allocator = allocator::heap()) @nodiscard
@param [in] arr2
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len
@require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
@ensure return.len == arr1.len + arr2.len
*>
macro tconcat(arr1, arr2) @nodiscard => concat(arr1, arr2, allocator::temp());
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
module std::core::array::slice(<Type>);
module std::core::array::slice{Type};
struct Slice2d
{
@@ -145,11 +130,41 @@ macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
<*
@require idy >= 0 && idy < self.ylen
*>
macro Type[] Slice2d.get(self, usz idy) @operator([])
macro Type[] Slice2d.get_row(self, usz idy) @operator([])
{
return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen];
}
macro Type Slice2d.get_coord(self, usz[<2>] coord)
{
return *self.get_coord_ref(coord);
}
macro Type Slice2d.get_xy(self, x, y)
{
return *self.get_xy_ref(x, y);
}
macro Type* Slice2d.get_xy_ref(self, x, y)
{
return self.ptr + self.inner_len * (y + self.ystart) + self.xstart + x;
}
macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord)
{
return self.get_xy_ref(coord.x, coord.y);
}
macro void Slice2d.set_coord(self, usz[<2>] coord, Type value)
{
*self.get_coord_ref(coord) = value;
}
macro void Slice2d.set_xy(self, x, y, Type value)
{
*self.get_xy_ref(x, y) = value;
}
<*
@require y >= 0 && y < self.ylen
@require x >= 0 && x < self.xlen

View File

@@ -88,13 +88,13 @@ bitstruct UInt128LE : uint128 @littleendian
}
<*
@require is_array_or_slice_of_char(bytes) "argument must be an array, a pointer to an array or a slice of char"
@require is_bitorder($Type) "type must be a bitorder integer"
@require is_array_or_slice_of_char(bytes) : "argument must be an array, a pointer to an array or a slice of char"
@require is_bitorder($Type) : "type must be a bitorder integer"
*>
macro read(bytes, $Type)
{
char[] s;
$switch (@typekind(bytes))
$switch @typekind(bytes):
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
@@ -104,13 +104,13 @@ macro read(bytes, $Type)
}
<*
@require is_arrayptr_or_slice_of_char(bytes) "argument must be a pointer to an array or a slice of char"
@require is_bitorder($Type) "type must be a bitorder integer"
@require is_arrayptr_or_slice_of_char(bytes) : "argument must be a pointer to an array or a slice of char"
@require is_bitorder($Type) : "type must be a bitorder integer"
*>
macro write(x, bytes, $Type)
{
char[] s;
$switch (@typekind(bytes))
$switch @typekind(bytes):
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
@@ -121,7 +121,7 @@ macro write(x, bytes, $Type)
macro is_bitorder($Type)
{
$switch ($Type)
$switch $Type:
$case UShortLE:
$case ShortLE:
$case UIntLE:
@@ -146,7 +146,7 @@ macro is_bitorder($Type)
macro bool is_array_or_slice_of_char(bytes)
{
$switch (@typekind(bytes))
$switch @typekind(bytes):
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:
@@ -164,7 +164,7 @@ macro bool is_array_or_slice_of_char(bytes)
macro bool is_arrayptr_or_slice_of_char(bytes)
{
$switch (@typekind(bytes))
$switch @typekind(bytes):
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:

View File

@@ -4,101 +4,135 @@
module std::core::builtin;
import libc, std::hash, std::io, std::os::backtrace;
<*
EMPTY_MACRO_SLOT is a value used for implementing optional arguments for macros in an efficient
way. It relies on the fact that distinct types are not implicitly convertable.
You can use `@is_empty_macro_slot()` and `@is_valid_macro_slot()` to figure out whether
the argument has been used or not.
An example:
```c3
macro foo(a, #b = EMPTY_MACRO_SLOT)
{
$if @is_valid_macro_slot(#b):
return invoke_foo2(a, #b);
$else
return invoke_foo1(a);
$endif
}
*>
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
typedef EmptySlot = void*;
macro @is_empty_macro_slot(#arg) @const @builtin => @typeis(#arg, EmptySlot);
macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
/*
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
*>
fault IteratorResult { NO_MORE_ELEMENT }
*/
faultdef NO_MORE_ELEMENT @builtin;
<*
/*
Use `SearchResult` when trying to return a value from some collection but the element is missing.
*>
fault SearchResult { MISSING }
*/
faultdef NOT_FOUND @builtin;
<*
/*
Use `CastResult` when an attempt at conversion fails.
*>
fault CastResult { TYPE_MISMATCH }
*/
faultdef TYPE_MISMATCH @builtin;
def VoidFn = fn void();
alias VoidFn = fn void();
<*
Stores a variable on the stack, then restores it at the end of the
macro scope.
@param variable `the variable to store and restore`
@param #variable : `the variable to store and restore`
@require values::@is_lvalue(#variable)
*>
macro void @scope(&variable; @body) @builtin
macro void @scope(#variable; @body) @builtin
{
var temp = *variable;
defer *variable = temp;
var temp = #variable;
defer #variable = temp;
@body();
}
<*
Swap two variables
@require $assignable(*b, $typeof(*a)) && $assignable(*a, $typeof(*b))
@require $defined(#a = #b, #b = #a) : `The values must be mutually assignable`
*>
macro void @swap(&a, &b) @builtin
macro void @swap(#a, #b) @builtin
{
var temp = *a;
*a = *b;
*b = temp;
var temp = #a;
#a = #b;
#b = temp;
}
<*
Convert an `any` type to a type, returning an failure if there is a type mismatch.
@param v `the any to convert to the given type.`
@param $Type `the type to convert to`
@param v : `the any to convert to the given type.`
@param $Type : `the type to convert to`
@return `The any.ptr converted to its type.`
@ensure @typeis(return, $Type*)
@return! CastResult.TYPE_MISMATCH
@return? TYPE_MISMATCH
*>
macro anycast(any v, $Type) @builtin
{
if (v.type != $Type.typeid) return CastResult.TYPE_MISMATCH?;
if (v.type != $Type.typeid) return TYPE_MISMATCH?;
return ($Type*)v.ptr;
}
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE)
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
{
@pool()
Allocator t = allocator::current_temp;
TempAllocator* new_t = allocator::new_temp_allocator(smem, 0x1000)!!;
allocator::current_temp = new_t;
defer
{
void*[256] buffer;
void*[] backtraces = backtrace::capture_current(&buffer);
backtraces_to_ignore++;
BacktraceList! backtrace = backtrace::symbolize_backtrace(backtraces, allocator::temp());
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
allocator::current_temp = t;
new_t.free();
}
void*[256] buffer;
void*[] backtraces = backtrace::capture_current(&buffer);
backtraces_to_ignore++;
BacktraceList? backtrace = backtrace::symbolize_backtrace(tmem, backtraces);
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
{
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
{
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
{
io::eprintfn(" in ???%s", inline_suffix);
continue;
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
io::eprintfn(" in ???%s", inline_suffix);
continue;
}
return true;
};
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
}
return true;
}
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
{
$if $defined(io::stderr):
if (!print_backtrace(message, 2))
{
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
return;
}
$endif
$$trap();
@@ -130,7 +164,7 @@ fn void default_panic(String message, String file, String function, uint line) @
$$trap();
}
def PanicFn = fn void(String message, String file, String function, uint line);
alias PanicFn = fn void(String message, String file, String function, uint line);
PanicFn panic = &default_panic;
@@ -146,7 +180,7 @@ fn void panicf(String fmt, String file, String function, uint line, args...)
@stack_mem(512; Allocator allocator)
{
DString s;
s.new_init(allocator: allocator);
s.init(allocator);
s.appendf(fmt, ...args);
in_panic = false;
panic(s.str_view(), file, function, line);
@@ -156,7 +190,7 @@ fn void panicf(String fmt, String file, String function, uint line, args...)
<*
Marks the path as unreachable. This will panic in safe mode, and in fast will simply be assumed
never happens.
@param [in] string "The panic message or format string"
@param [in] string : "The panic message or format string"
*>
macro void unreachable(String string = "Unreachable statement reached.", ...) @builtin @noreturn
{
@@ -168,7 +202,7 @@ macro void unreachable(String string = "Unreachable statement reached.", ...) @b
<*
Marks the path as unsupported, this is similar to unreachable.
@param [in] string "The error message"
@param [in] string : "The error message"
*>
macro void unsupported(String string = "Unsupported function invoked") @builtin @noreturn
{
@@ -200,10 +234,10 @@ macro any.as_inner(&self)
}
<*
@param expr "the expression to cast"
@param $Type "the type to cast to"
@param expr : "the expression to cast"
@param $Type : "the type to cast to"
@require $sizeof(expr) == $Type.sizeof "Cannot bitcast between types of different size."
@require $sizeof(expr) == $Type.sizeof : "Cannot bitcast between types of different size."
@ensure @typeis(return, $Type)
*>
macro bitcast(expr, $Type) @builtin
@@ -218,32 +252,50 @@ macro bitcast(expr, $Type) @builtin
}
<*
@param $Type `The type of the enum`
@param [in] enum_name `The name of the enum to search for`
@require $Type.kindof == ENUM `Only enums may be used`
@param $Type : `The type of the enum`
@param [in] enum_name : `The name of the enum to search for`
@require $Type.kindof == ENUM : `Only enums may be used`
@ensure @typeis(return, $Type)
@return! SearchResult.MISSING
@return? NOT_FOUND
*>
macro enum_by_name($Type, String enum_name) @builtin
{
typeid x = $Type.typeid;
foreach (i, name : x.names)
{
if (name == enum_name) return ($Type)i;
if (name == enum_name) return $Type.from_ordinal(i);
}
return SearchResult.MISSING?;
return NOT_FOUND?;
}
<*
@param $Type : `The type of the enum`
@require $Type.kindof == ENUM : `Only enums may be used`
@require $defined($Type.#value) : `Expected '#value' to match an enum associated value`
@require $assignable(value, $typeof(($Type){}.#value)) : `Expected the value to match the type of the associated value`
@ensure @typeis(return, $Type)
@return? NOT_FOUND
*>
macro @enum_from_value($Type, #value, value) @builtin
{
usz elements = $Type.elements;
foreach (e : $Type.values)
{
if (e.#value == value) return e;
}
return NOT_FOUND?;
}
<*
Mark an expression as likely to be true
@param #value "expression to be marked likely"
@param $probability "in the range 0 - 1"
@param #value : "expression to be marked likely"
@param $probability : "in the range 0 - 1"
@require $probability >= 0 && $probability <= 1.0
*>
macro bool @likely(bool #value, $probability = 1.0) @builtin
{
$switch
$switch:
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value;
$case $probability == 1.0:
@@ -256,13 +308,13 @@ macro bool @likely(bool #value, $probability = 1.0) @builtin
<*
Mark an expression as unlikely to be true
@param #value "expression to be marked unlikely"
@param $probability "in the range 0 - 1"
@param #value : "expression to be marked unlikely"
@param $probability : "in the range 0 - 1"
@require $probability >= 0 && $probability <= 1.0
*>
macro bool @unlikely(bool #value, $probability = 1.0) @builtin
{
$switch
$switch:
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value;
$case $probability == 1.0:
@@ -279,7 +331,7 @@ macro bool @unlikely(bool #value, $probability = 1.0) @builtin
*>
macro @expect(#value, expected, $probability = 1.0) @builtin
{
$switch
$switch:
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value == expected;
$case $probability == 1.0:
@@ -304,9 +356,9 @@ enum PrefetchLocality
<*
Prefetch a pointer.
@param [in] ptr `Pointer to prefetch`
@param $locality `Locality ranging from none to extremely local`
@param $write `Prefetch for write, otherwise prefetch for read.`
@param [in] ptr : `Pointer to prefetch`
@param $locality : `Locality ranging from none to extremely local`
@param $write : `Prefetch for write, otherwise prefetch for read.`
*>
macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
{
@@ -330,10 +382,10 @@ macro swizzle2(v, v2, ...) @builtin
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
*>
macro anyfault @catch(#expr) @builtin
macro fault @catch(#expr) @builtin
{
if (catch f = #expr) return f;
return anyfault {};
return {};
}
<*
@@ -348,9 +400,12 @@ macro bool @ok(#expr) @builtin
return true;
}
macro char[] @as_char_view(&value) @builtin
<*
@require $defined(&#value, (char*)&#value) : "This must be a value that can be viewed as a char array"
*>
macro char[] @as_char_view(#value) @builtin
{
return ((char*)value)[:$sizeof(*value)];
return ((char*)&#value)[:$sizeof(#value)];
}
macro isz @str_find(String $string, String $needle) @builtin => $$str_find($string, $needle);
@@ -358,26 +413,40 @@ macro String @str_upper(String $str) @builtin => $$str_upper($str);
macro String @str_lower(String $str) @builtin => $$str_lower($str);
macro uint @str_hash(String $str) @builtin => $$str_hash($str);
macro uint int.hash(int i) => i;
macro uint uint.hash(uint i) => i;
macro uint short.hash(short s) => s;
macro uint ushort.hash(ushort s) => s;
macro uint char.hash(char c) => c;
macro uint ichar.hash(ichar c) => c;
macro uint long.hash(long i) => (uint)((i >> 32) ^ i);
macro uint ulong.hash(ulong i) => (uint)((i >> 32) ^ i);
macro uint int128.hash(int128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^ i);
macro uint uint128.hash(uint128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^ i);
macro uint bool.hash(bool b) => (uint)b;
macro uint typeid.hash(typeid t) => ((ulong)(uptr)t).hash();
macro uint String.hash(String c) => (uint)fnv32a::encode(c);
macro uint char[].hash(char[] c) => (uint)fnv32a::encode(c);
macro uint void*.hash(void* ptr) => ((ulong)(uptr)ptr).hash();
macro @generic_hash_core(h, value)
{
h ^= (uint)value; // insert lowest 32 bits
h *= 0x96f59e5b; // diffuse them up
h ^= h >> 16; // diffuse them down
return h;
}
macro @generic_hash(value)
{
uint h = @generic_hash_core((uint)0x3efd4391, value);
$for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4:
value >>= 32; // reduce value
h = @generic_hash_core(h, value);
$endfor
return h;
}
macro uint int.hash(int i) => @generic_hash(i);
macro uint uint.hash(uint i) => @generic_hash(i);
macro uint short.hash(short s) => @generic_hash(s);
macro uint ushort.hash(ushort s) => @generic_hash(s);
macro uint char.hash(char c) => @generic_hash(c);
macro uint ichar.hash(ichar c) => @generic_hash(c);
macro uint long.hash(long i) => @generic_hash(i);
macro uint ulong.hash(ulong i) => @generic_hash(i);
macro uint int128.hash(int128 i) => @generic_hash(i);
macro uint uint128.hash(uint128 i) => @generic_hash(i);
macro uint bool.hash(bool b) => @generic_hash(b);
macro uint typeid.hash(typeid t) => @generic_hash(((ulong)(uptr)t));
macro uint String.hash(String c) => (uint)fnv32a::hash(c);
macro uint char[].hash(char[] c) => (uint)fnv32a::hash(c);
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
distinct EmptySlot = void*;
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
macro @is_empty_macro_slot(#arg) @builtin => @typeis(#arg, EmptySlot);
macro @is_valid_macro_slot(#arg) @builtin => !@typeis(#arg, EmptySlot);
const MAX_FRAMEADDRESS = 128;
<*

View File

@@ -8,7 +8,7 @@ module std::core::builtin;
*>
macro less(a, b) @builtin
{
$switch
$switch:
$case $defined(a.less):
return a.less(b);
$case $defined(a.compare_to):
@@ -23,7 +23,7 @@ macro less(a, b) @builtin
*>
macro less_eq(a, b) @builtin
{
$switch
$switch:
$case $defined(a.less):
return !b.less(a);
$case $defined(a.compare_to):
@@ -38,7 +38,7 @@ macro less_eq(a, b) @builtin
*>
macro greater(a, b) @builtin
{
$switch
$switch:
$case $defined(a.less):
return b.less(a);
$case $defined(a.compare_to):
@@ -53,7 +53,7 @@ macro greater(a, b) @builtin
*>
macro int compare_to(a, b) @builtin
{
$switch
$switch:
$case $defined(a.compare_to):
return a.compare_to(b);
$case $defined(a.less):
@@ -67,7 +67,7 @@ macro int compare_to(a, b) @builtin
*>
macro greater_eq(a, b) @builtin
{
$switch
$switch:
$case $defined(a.less):
return !a.less(b);
$case $defined(a.compare_to):
@@ -78,11 +78,11 @@ macro greater_eq(a, b) @builtin
}
<*
@require types::@equatable_value(a) && types::@equatable_value(b) `values must be equatable`
@require types::@equatable_value(a) && types::@equatable_value(b) : `values must be equatable`
*>
macro bool equals(a, b) @builtin
{
$switch
$switch:
$case $defined(a.equals, a.equals(b)):
return a.equals(b);
$case $defined(a.compare_to, a.compare_to(b)):
@@ -100,7 +100,7 @@ macro min(x, ...) @builtin
return less(x, $vaarg[0]) ? x : $vaarg[0];
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
$for var $i = 0; $i < $vacount; $i++:
if (less($vaarg[$i], result))
{
result = $vaarg[$i];
@@ -116,7 +116,7 @@ macro max(x, ...) @builtin
return greater(x, $vaarg[0]) ? x : $vaarg[0];
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
$for var $i = 0; $i < $vacount; $i++:
if (greater($vaarg[$i], result))
{
result = $vaarg[$i];

View File

@@ -16,20 +16,20 @@ $assert C_SHORT_SIZE <= C_INT_SIZE;
$assert C_INT_SIZE <= C_LONG_SIZE;
$assert C_LONG_SIZE <= C_LONG_LONG_SIZE;
def CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
def CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
def CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
def CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
def CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
def CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
def CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
def CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
def CSChar = ichar;
def CUChar = char;
alias CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
alias CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
alias CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
alias CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
alias CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
alias CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
alias CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
alias CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
alias CSChar = ichar;
alias CUChar = char;
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
alias CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
enum CBool : CInt
enum CBool : char
{
FALSE,
TRUE
@@ -38,7 +38,7 @@ enum CBool : CInt
// Helper macros
macro typeid signed_int_from_bitsize(usz $bitsize) @private
{
$switch ($bitsize)
$switch $bitsize:
$case 128: return int128.typeid;
$case 64: return long.typeid;
$case 32: return int.typeid;
@@ -50,7 +50,7 @@ macro typeid signed_int_from_bitsize(usz $bitsize) @private
macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
{
$switch ($bitsize)
$switch $bitsize:
$case 128: return uint128.typeid;
$case 64: return ulong.typeid;
$case 32: return uint.typeid;

View File

@@ -10,30 +10,31 @@ const uint UTF16_SURROGATE_LOW_VALUE @private = 0xDC00;
const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
<*
@param c `The utf32 codepoint to convert`
@param [out] output `the resulting buffer`
@param c : `The utf32 codepoint to convert`
@param [out] output : `the resulting buffer`
@return? string::CONVERSION_FAILED
*>
fn usz! char32_to_utf8(Char32 c, char[] output)
fn usz? char32_to_utf8(Char32 c, char[] output)
{
if (!output.len) return UnicodeResult.CONVERSION_FAILED?;
if (!output.len) return string::CONVERSION_FAILED?;
switch (true)
{
case c <= 0x7f:
output[0] = (char)c;
return 1;
case c <= 0x7ff:
if (output.len < 2) return UnicodeResult.CONVERSION_FAILED?;
if (output.len < 2) return string::CONVERSION_FAILED?;
output[0] = (char)(0xC0 | c >> 6);
output[1] = (char)(0x80 | (c & 0x3F));
return 2;
case c <= 0xffff:
if (output.len < 3) return UnicodeResult.CONVERSION_FAILED?;
if (output.len < 3) return string::CONVERSION_FAILED?;
output[0] = (char)(0xE0 | c >> 12);
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
output[2] = (char)(0x80 | (c & 0x3F));
return 3;
case c <= 0x10ffff:
if (output.len < 4) return UnicodeResult.CONVERSION_FAILED?;
if (output.len < 4) return string::CONVERSION_FAILED?;
output[0] = (char)(0xF0 | c >> 18);
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
@@ -41,15 +42,15 @@ fn usz! char32_to_utf8(Char32 c, char[] output)
return 4;
default:
// 0x10FFFF and above is not defined.
return UnicodeResult.CONVERSION_FAILED?;
return string::CONVERSION_FAILED?;
}
}
<*
Convert a code pointer into 1-2 UTF16 characters.
@param c `The character to convert.`
@param [inout] output `the resulting UTF16 buffer to write to.`
@param c : `The character to convert.`
@param [inout] output : `the resulting UTF16 buffer to write to.`
*>
fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
{
@@ -69,11 +70,11 @@ fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
<*
Convert 1-2 UTF16 data points into UTF8.
@param [in] ptr `The UTF16 data to convert.`
@param [inout] available `amount of UTF16 data available.`
@param [inout] output `the resulting utf8 buffer to write to.`
@param [in] ptr : `The UTF16 data to convert.`
@param [inout] available : `amount of UTF16 data available.`
@param [inout] output : `the resulting utf8 buffer to write to.`
*>
fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
{
Char16 high = *ptr;
if (high & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
@@ -83,15 +84,15 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
return;
}
// Low surrogate first is an error
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return UnicodeResult.INVALID_UTF16?;
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return string::INVALID_UTF16?;
// Unmatched high surrogate is an error
if (*available == 1) return UnicodeResult.INVALID_UTF16?;
if (*available == 1) return string::INVALID_UTF16?;
Char16 low = ptr[1];
// Unmatched high surrogate, invalid
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return UnicodeResult.INVALID_UTF16?;
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return string::INVALID_UTF16?;
// The high bits of the codepoint are the value bits of the high surrogate
// The low bits of the codepoint are the value bits of the low surrogate
@@ -101,8 +102,8 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
*available = 2;
}
<*
@param c `The utf32 codepoint to convert`
@param [inout] output `the resulting buffer`
@param c : `The utf32 codepoint to convert`
@param [inout] output : `the resulting buffer`
*>
fn usz char32_to_utf8_unsafe(Char32 c, char** output)
{
@@ -130,14 +131,14 @@ fn usz char32_to_utf8_unsafe(Char32 c, char** output)
}
<*
@param [in] ptr `pointer to the first character to parse`
@param [inout] size `Set to max characters to read, set to characters read`
@param [in] ptr : `pointer to the first character to parse`
@param [inout] size : `Set to max characters to read, set to characters read`
@return `the parsed 32 bit codepoint`
*>
fn Char32! utf8_to_char32(char* ptr, usz* size)
fn Char32? utf8_to_char32(char* ptr, usz* size)
{
usz max_size = *size;
if (max_size < 1) return UnicodeResult.INVALID_UTF8?;
if (max_size < 1) return string::INVALID_UTF8?;
char c = (ptr++)[0];
if ((c & 0x80) == 0)
@@ -147,45 +148,45 @@ fn Char32! utf8_to_char32(char* ptr, usz* size)
}
if ((c & 0xE0) == 0xC0)
{
if (max_size < 2) return UnicodeResult.INVALID_UTF8?;
if (max_size < 2) return string::INVALID_UTF8?;
*size = 2;
Char32 uc = (c & 0x1F) << 6;
c = *ptr;
// Overlong sequence or invalid second.
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
return uc + c & 0x3F;
}
if ((c & 0xF0) == 0xE0)
{
if (max_size < 3) return UnicodeResult.INVALID_UTF8?;
if (max_size < 3) return string::INVALID_UTF8?;
*size = 3;
Char32 uc = (c & 0x0F) << 12;
c = ptr++[0];
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
uc += (c & 0x3F) << 6;
c = ptr++[0];
// Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
return uc + c & 0x3F;
}
if (max_size < 4) return UnicodeResult.INVALID_UTF8?;
if ((c & 0xF8) != 0xF0) return UnicodeResult.INVALID_UTF8?;
if (max_size < 4) return string::INVALID_UTF8?;
if ((c & 0xF8) != 0xF0) return string::INVALID_UTF8?;
*size = 4;
Char32 uc = (c & 0x07) << 18;
c = ptr++[0];
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
uc += (c & 0x3F) << 12;
c = ptr++[0];
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
uc += (c & 0x3F) << 6;
c = ptr++[0];
// Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
return uc + c & 0x3F;
}
<*
@param utf8 `An UTF-8 encoded slice of bytes`
@param utf8 : `An UTF-8 encoded slice of bytes`
@return `the number of encoded code points`
*>
fn usz utf8_codepoints(String utf8)
@@ -200,7 +201,7 @@ fn usz utf8_codepoints(String utf8)
<*
Calculate the UTF8 length required to encode an UTF32 array.
@param [in] utf32 `the utf32 data to calculate from`
@param [in] utf32 : `the utf32 data to calculate from`
@return `the length of the resulting UTF8 array`
*>
fn usz utf8len_for_utf32(Char32[] utf32)
@@ -225,7 +226,7 @@ fn usz utf8len_for_utf32(Char32[] utf32)
<*
Calculate the UTF8 length required to encode an UTF16 array.
@param [in] utf16 `the utf16 data to calculate from`
@param [in] utf16 : `the utf16 data to calculate from`
@return `the length of the resulting UTF8 array`
*>
fn usz utf8len_for_utf16(Char16[] utf16)
@@ -257,7 +258,7 @@ fn usz utf8len_for_utf16(Char16[] utf16)
<*
Calculate the UTF16 length required to encode a UTF8 array.
@param utf8 `the utf8 data to calculate from`
@param utf8 : `the utf8 data to calculate from`
@return `the length of the resulting UTF16 array`
*>
fn usz utf16len_for_utf8(String utf8)
@@ -280,7 +281,7 @@ fn usz utf16len_for_utf8(String utf8)
}
<*
@param [in] utf32 `the UTF32 array to check the length for`
@param [in] utf32 : `the UTF32 array to check the length for`
@return `the required length of an UTF16 array to hold the UTF32 data.`
*>
fn usz utf16len_for_utf32(Char32[] utf32)
@@ -300,7 +301,7 @@ fn usz utf16len_for_utf32(Char32[] utf32)
@param [out] utf8_buffer
@return `the number of bytes written.`
*>
fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
fn usz? utf32to8(Char32[] utf32, char[] utf8_buffer)
{
char[] buffer = utf8_buffer;
foreach (uc : utf32)
@@ -320,7 +321,7 @@ fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
@param [out] utf32_buffer
@return `the number of Char32s written.`
*>
fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
{
usz len = utf8.len;
Char32* ptr = utf32_buffer.ptr;
@@ -328,7 +329,7 @@ fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
usz buf_len = utf32_buffer.len;
for (usz i = 0; i < len;)
{
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED?;
if (len32 == buf_len) return string::CONVERSION_FAILED?;
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
i += width;
@@ -344,10 +345,10 @@ fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
checking. This will assume the buffer is sufficiently large to hold
the converted data.
@param [in] utf16 `The UTF16 array containing the data to convert.`
@param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF16 data.`
@param [in] utf16 : `The UTF16 array containing the data to convert.`
@param [out] utf8_buffer : `the (sufficiently large) buffer to hold the UTF16 data.`
*>
fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
fn void? utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
{
usz len16 = utf16.len;
for (usz i = 0; i < len16;)
@@ -363,10 +364,10 @@ fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
checking. This will assume the buffer is sufficiently large to hold
the converted data.
@param [in] utf8 `The UTF8 buffer containing the data to convert.`
@param [out] utf32_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
@param [in] utf8 : `The UTF8 buffer containing the data to convert.`
@param [out] utf32_buffer : `the (sufficiently large) buffer to hold the UTF8 data.`
*>
fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
fn void? utf8to32_unsafe(String utf8, Char32* utf32_buffer)
{
usz len = utf8.len;
for (usz i = 0; i < len;)
@@ -383,10 +384,10 @@ fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
checking. This will assume the buffer is sufficiently large to hold
the converted data.
@param [in] utf8 `The UTF8 buffer containing the data to convert.`
@param [out] utf16_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
@param [in] utf8 : `The UTF8 buffer containing the data to convert.`
@param [out] utf16_buffer : `the (sufficiently large) buffer to hold the UTF8 data.`
*>
fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
fn void? utf8to16_unsafe(String utf8, Char16* utf16_buffer)
{
usz len = utf8.len;
for (usz i = 0; i < len;)
@@ -403,8 +404,8 @@ fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
checking. This will assume the buffer is sufficiently large to hold
the converted data.
@param [in] utf32 `The UTF32 buffer containing the data to convert.`
@param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
@param [in] utf32 : `The UTF32 buffer containing the data to convert.`
@param [out] utf8_buffer : `the (sufficiently large) buffer to hold the UTF8 data.`
*>
fn void utf32to8_unsafe(Char32[] utf32, char* utf8_buffer)
{

View File

@@ -1,14 +1,15 @@
module std::core::dstring;
import std::io;
distinct DString (OutStream) = void*;
typedef DString (OutStream) = DStringOpaque*;
typedef DStringOpaque = void;
const usz MIN_CAPACITY @private = 16;
<*
@require !self.data() "String already initialized"
@require !self.data() : "String already initialized"
*>
fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap())
fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY)
{
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
@@ -19,25 +20,24 @@ fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator alloca
}
<*
@require !self.data() "String already initialized"
@require !self.data() : "String already initialized"
*>
fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY)
fn DString DString.tinit(&self, usz capacity = MIN_CAPACITY)
{
self.new_init(capacity, allocator::temp()) @inline;
return *self;
return self.init(tmem, capacity) @inline;
}
fn DString new_with_capacity(usz capacity, Allocator allocator = allocator::heap())
fn DString new_with_capacity(Allocator allocator, usz capacity)
{
return DString{}.new_init(capacity, allocator);
return (DString){}.init(allocator, capacity);
}
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, allocator::temp()) @inline;
fn DString temp_with_capacity(usz capacity) => new_with_capacity(tmem, capacity) @inline;
fn DString new(String c = "", Allocator allocator = allocator::heap())
fn DString new(Allocator allocator, String c = "")
{
usz len = c.len;
StringData* data = (StringData*)new_with_capacity(len, allocator);
StringData* data = (StringData*)new_with_capacity(allocator, len);
if (len)
{
data.len = len;
@@ -46,7 +46,7 @@ fn DString new(String c = "", Allocator allocator = allocator::heap())
return (DString)data;
}
fn DString temp_new(String s = "") => new(s, allocator::temp()) @inline;
fn DString temp(String s = "") => new(tmem, s) @inline;
fn void DString.replace_char(self, char ch, char replacement)
@@ -69,7 +69,8 @@ fn void DString.replace(&self, String needle, String replacement)
self.replace_char(needle[0], replacement[0]);
return;
}
@pool(data.allocator) {
@pool()
{
String str = self.tcopy_str();
self.clear();
usz len = str.len;
@@ -98,16 +99,16 @@ fn void DString.replace(&self, String needle, String replacement)
};
}
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
fn DString DString.concat(self, Allocator allocator, DString b)
{
DString string;
string.new_init(self.len() + b.len(), allocator);
string.init(allocator, self.len() + b.len());
string.append(self);
string.append(b);
return string;
}
fn DString DString.temp_concat(self, DString b) => self.new_concat(b, allocator::temp());
fn DString DString.tconcat(self, DString b) => self.concat(tmem, b);
fn ZString DString.zstr_view(&self)
{
@@ -156,7 +157,7 @@ fn String DString.str_view(self)
<*
@require index < self.len()
@require self.data() "Empty string"
@require self.data() != null : "Empty string"
*>
fn char DString.char_at(self, usz index) @operator([])
{
@@ -165,7 +166,7 @@ fn char DString.char_at(self, usz index) @operator([])
<*
@require index < self.len()
@require self.data() "Empty string"
@require self.data() != null : "Empty string"
*>
fn char* DString.char_ref(&self, usz index) @operator(&[])
{
@@ -217,23 +218,18 @@ fn usz DString.append_char32(&self, Char32 c)
return n;
}
fn DString DString.tcopy(&self) => self.copy(allocator::temp());
fn DString DString.tcopy(&self) => self.copy(tmem);
fn DString DString.copy(self, Allocator allocator = null)
fn DString DString.copy(self, Allocator allocator)
{
if (!self)
{
if (allocator) return new_with_capacity(0, allocator);
return (DString)null;
}
if (!self) return new(allocator);
StringData* data = self.data();
if (!allocator) allocator = allocator::heap();
DString new_string = new_with_capacity(data.capacity, allocator);
DString new_string = new_with_capacity(allocator, data.capacity);
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
return new_string;
}
fn ZString DString.copy_zstr(self, Allocator allocator = allocator::heap())
fn ZString DString.copy_zstr(self, Allocator allocator)
{
usz str_len = self.len();
if (!str_len)
@@ -247,12 +243,12 @@ fn ZString DString.copy_zstr(self, Allocator allocator = allocator::heap())
return (ZString)zstr;
}
fn String DString.copy_str(self, Allocator allocator = allocator::heap())
fn String DString.copy_str(self, Allocator allocator)
{
return (String)self.copy_zstr(allocator)[:self.len()];
}
fn String DString.tcopy_str(self) => self.copy_str(allocator::temp()) @inline;
fn String DString.tcopy_str(self) => self.copy_str(tmem) @inline;
fn bool DString.equals(self, DString other_string)
{
@@ -302,7 +298,7 @@ fn void DString.append_chars(&self, String str)
if (!other_len) return;
if (!*self)
{
*self = new(str);
*self = temp(str);
return;
}
self.reserve(other_len);
@@ -311,7 +307,7 @@ fn void DString.append_chars(&self, String str)
data.len += other_len;
}
fn Char32[] DString.copy_utf32(&self, Allocator allocator = allocator::heap())
fn Char32[] DString.copy_utf32(&self, Allocator allocator)
{
return self.str_view().to_utf32(allocator) @inline!!;
}
@@ -329,13 +325,13 @@ fn void DString.clear(self)
self.data().len = 0;
}
fn usz! DString.write(&self, char[] buffer) @dynamic
fn usz? DString.write(&self, char[] buffer) @dynamic
{
self.append_chars((String)buffer);
return buffer.len;
}
fn void! DString.write_byte(&self, char c) @dynamic
fn void? DString.write_byte(&self, char c) @dynamic
{
self.append_char(c);
}
@@ -344,7 +340,7 @@ fn void DString.append_char(&self, char c)
{
if (!*self)
{
*self = new_with_capacity(MIN_CAPACITY);
*self = temp_with_capacity(MIN_CAPACITY);
}
self.reserve(1);
StringData* data = self.data();
@@ -354,7 +350,7 @@ fn void DString.append_char(&self, char c)
<*
@require start < self.len()
@require end < self.len()
@require end >= start "End must be same or equal to the start"
@require end >= start : "End must be same or equal to the start"
*>
fn void DString.delete_range(&self, usz start, usz end)
{
@@ -386,7 +382,7 @@ fn void DString.delete(&self, usz start, usz len = 1)
macro void DString.append(&self, value)
{
var $Type = $typeof(value);
$switch ($Type)
$switch $Type:
$case char:
$case ichar:
self.append_char(value);
@@ -397,7 +393,7 @@ macro void DString.append(&self, value)
$case Char32:
self.append_char32(value);
$default:
$switch
$switch:
$case $defined((Char32)value):
self.append_char32((Char32)value);
$case $defined((String)value):
@@ -518,7 +514,7 @@ fn usz DString.insert_utf32_at(&self, usz index, Char32[] chars)
macro void DString.insert_at(&self, usz index, value)
{
var $Type = $typeof(value);
$switch ($Type)
$switch $Type:
$case char:
$case ichar:
self.insert_char_at(index, value);
@@ -529,7 +525,7 @@ macro void DString.insert_at(&self, usz index, value)
$case Char32:
self.insert_char32_at(index, value);
$default:
$switch
$switch:
$case $defined((Char32)value):
self.insert_char32_at(index, (Char32)value);
$case $defined((String)value):
@@ -540,21 +536,19 @@ macro void DString.insert_at(&self, usz index, value)
$endswitch
}
fn usz! DString.appendf(&self, String format, args...) @maydiscard
import libc;
fn usz? DString.appendf(&self, String format, args...) @maydiscard
{
if (!self.data()) self.new_init(format.len + 20);
@pool(self.data().allocator)
{
Formatter formatter;
formatter.init(&out_string_append_fn, self);
return formatter.vprintf(format, args);
};
if (!self.data()) self.tinit(format.len + 20);
Formatter formatter;
formatter.init(&out_string_append_fn, self);
return formatter.vprintf(format, args);
}
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
fn usz? DString.appendfn(&self, String format, args...) @maydiscard
{
if (!self.data()) self.new_init(format.len + 20);
@pool(self.data().allocator)
if (!self.data()) self.tinit(format.len + 20);
@pool()
{
Formatter formatter;
formatter.init(&out_string_append_fn, self);
@@ -564,15 +558,15 @@ fn usz! DString.appendfn(&self, String format, args...) @maydiscard
};
}
fn DString new_join(String[] s, String joiner, Allocator allocator = allocator::heap())
fn DString join(Allocator allocator, String[] s, String joiner)
{
if (!s.len) return (DString)null;
if (!s.len) return new(allocator);
usz total_size = joiner.len * s.len;
foreach (String* &str : s)
{
total_size += str.len;
}
DString res = new_with_capacity(total_size, allocator);
DString res = new_with_capacity(allocator, total_size);
res.append(s[0]);
foreach (String* &str : s[1..])
{
@@ -582,7 +576,7 @@ fn DString new_join(String[] s, String joiner, Allocator allocator = allocator::
return res;
}
fn void! out_string_append_fn(void* data, char c) @private
fn void? out_string_append_fn(void* data, char c) @private
{
DString* s = data;
s.append_char(c);
@@ -612,7 +606,7 @@ fn void DString.reserve(&self, usz addition)
StringData* data = self.data();
if (!data)
{
*self = dstring::new_with_capacity(addition);
*self = dstring::temp_with_capacity(addition);
return;
}
usz len = data.len + addition;
@@ -624,7 +618,7 @@ fn void DString.reserve(&self, usz addition)
*self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity);
}
fn usz! DString.read_from_stream(&self, InStream reader)
fn usz? DString.read_from_stream(&self, InStream reader)
{
if (&reader.available)
{

View File

@@ -57,6 +57,7 @@ enum OsType
HURD,
WASI,
EMSCRIPTEN,
ANDROID,
}
enum ArchType
@@ -117,13 +118,13 @@ enum ArchType
const String COMPILER_BUILD_HASH = $$BUILD_HASH;
const String COMPILER_BUILD_DATE = $$BUILD_DATE;
const OsType OS_TYPE = (OsType)$$OS_TYPE;
const ArchType ARCH_TYPE = (ArchType)$$ARCH_TYPE;
const OsType OS_TYPE = OsType.from_ordinal($$OS_TYPE);
const ArchType ARCH_TYPE = ArchType.from_ordinal($$ARCH_TYPE);
const bool ARCH_32_BIT = $$REGISTER_SIZE == 32;
const bool ARCH_64_BIT = $$REGISTER_SIZE == 64;
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
const CompilerOptLevel COMPILER_OPT_LEVEL = (CompilerOptLevel)$$COMPILER_OPT_LEVEL;
const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel.from_ordinal($$COMPILER_OPT_LEVEL);
const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED;
const bool F16_SUPPORT = $$PLATFORM_F16_SUPPORTED;
@@ -135,7 +136,7 @@ const bool BACKTRACE = $$BACKTRACE;
const usz LLVM_VERSION = $$LLVM_VERSION;
const bool BENCHMARKING = $$BENCHMARKING;
const bool TESTING = $$TESTING;
const MemoryEnvironment MEMORY_ENV = (MemoryEnvironment)$$MEMORY_ENVIRONMENT;
const MemoryEnvironment MEMORY_ENV = MemoryEnvironment.from_ordinal($$MEMORY_ENVIRONMENT);
const bool TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING);
const bool X86_64 = ARCH_TYPE == X86_64;
const bool X86 = ARCH_TYPE == X86;
@@ -150,15 +151,17 @@ const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
const bool NETBSD = LIBC && OS_TYPE == NETBSD;
const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD;
const bool WASI = LIBC && OS_TYPE == WASI;
const bool ANDROID = LIBC && OS_TYPE == ANDROID;
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
const int LANGUAGE_DEV_VERSION = $$LANGUAGE_DEV_VERSION;
macro bool os_is_darwin() @const
{
$switch (OS_TYPE)
$switch OS_TYPE:
$case IOS:
$case MACOS:
$case TVOS:
@@ -171,7 +174,7 @@ macro bool os_is_darwin() @const
macro bool os_is_posix() @const
{
$switch (OS_TYPE)
$switch OS_TYPE:
$case IOS:
$case MACOS:
$case NETBSD:
@@ -182,6 +185,7 @@ macro bool os_is_posix() @const
$case SOLARIS:
$case TVOS:
$case WATCHOS:
$case ANDROID:
return true;
$case WIN32:
$case WASI:

View File

@@ -8,6 +8,8 @@ import std::math;
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
faultdef OUT_OF_MEMORY, INVALID_ALLOC_SIZE;
macro bool @constant_is_power_of_2($x) @const @private
{
return $x != 0 && ($x & ($x - 1)) == 0;
@@ -16,9 +18,9 @@ macro bool @constant_is_power_of_2($x) @const @private
<*
Load a vector from memory according to a mask assuming default alignment.
@param ptr "The pointer address to load from."
@param mask "The mask for the load"
@param passthru "The value to use for non masked values"
@param ptr : "The pointer address to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@@ -33,10 +35,10 @@ macro masked_load(ptr, bool[<*>] mask, passthru)
<*
Load a vector from memory according to a mask.
@param ptr "The pointer address to load from."
@param mask "The mask for the load"
@param passthru "The value to use for non masked values"
@param $alignment "The alignment to assume for the pointer"
@param ptr : "The pointer address to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@param $alignment : "The alignment to assume for the pointer"
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@@ -53,9 +55,9 @@ macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
<*
Load values from a pointer vector, assuming default alignment.
@param ptrvec "The vector of pointers to load from."
@param mask "The mask for the load"
@param passthru "The value to use for non masked values"
@param ptrvec : "The vector of pointers to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@@ -74,10 +76,10 @@ macro gather(ptrvec, bool[<*>] mask, passthru)
<*
Load values from a pointer vector.
@param ptrvec "The vector of pointers to load from."
@param mask "The mask for the load"
@param passthru "The value to use for non masked values"
@param $alignment "The alignment to assume for the pointers"
@param ptrvec : "The vector of pointers to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@param $alignment : "The alignment to assume for the pointers"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@@ -97,9 +99,9 @@ macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
<*
Store parts of a vector according to the mask, assuming default alignment.
@param ptr "The pointer address to store to."
@param value "The value to store masked"
@param mask "The mask for the store"
@param ptr : "The pointer address to store to."
@param value : "The value to store masked"
@param mask : "The mask for the store"
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@@ -111,10 +113,10 @@ macro masked_store(ptr, value, bool[<*>] mask)
}
<*
@param ptr "The pointer address to store to."
@param value "The value to store masked"
@param mask "The mask for the store"
@param $alignment "The alignment of the pointer"
@param ptr : "The pointer address to store to."
@param value : "The value to store masked"
@param mask : "The mask for the store"
@param $alignment : "The alignment of the pointer"
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@@ -128,9 +130,9 @@ macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
}
<*
@param ptrvec "The vector pointer containing the addresses to store to."
@param value "The value to store masked"
@param mask "The mask for the store"
@param ptrvec : "The vector pointer containing the addresses to store to."
@param value : "The value to store masked"
@param mask : "The mask for the store"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@@ -144,10 +146,10 @@ macro scatter(ptrvec, value, bool[<*>] mask)
}
<*
@param ptrvec "The vector pointer containing the addresses to store to."
@param value "The value to store masked"
@param mask "The mask for the store"
@param $alignment "The alignment of the load"
@param ptrvec : "The vector pointer containing the addresses to store to."
@param value : "The value to store masked"
@param mask : "The mask for the store"
@param $alignment : "The alignment of the load"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@@ -162,42 +164,55 @@ macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
}
<*
@param [in] x "The variable or dereferenced pointer to load."
@param $alignment "The alignment to assume for the load"
@return "The value of x"
@param #x : "The variable or dereferenced pointer to load."
@param $alignment : "The alignment to assume for the load"
@return "The value of the variable"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
*>
macro @unaligned_load(&x, usz $alignment) @builtin
macro @unaligned_load(#x, usz $alignment) @builtin
{
return $$unaligned_load(x, $alignment);
return $$unaligned_load(&#x, $alignment);
}
<*
@param [out] x "The variable or dereferenced pointer to store to."
@param value "The value to store."
@param $alignment "The alignment to assume for the store"
@return "The value of x"
@param #x : "The variable or dereferenced pointer to store to."
@param value : "The value to store."
@param $alignment : "The alignment to assume for the store"
@return "The value stored"
@require $assignable(value, $typeof(*x)) : "The value doesn't match the variable"
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
@require $defined(#x = value) : "The value doesn't match the variable"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
*>
macro @unaligned_store(&x, value, usz $alignment) @builtin
macro @unaligned_store(#x, value, usz $alignment) @builtin
{
return $$unaligned_store(x, ($typeof(*x))value, $alignment);
}
macro @volatile_load(&x) @builtin
{
return $$volatile_load(x);
return $$unaligned_store(&#x, ($typeof(#x))value, $alignment);
}
<*
@require $assignable(y, $typeof(*x)) : "The value doesn't match the variable"
@param #x : "The variable or dereferenced pointer to load."
@return "The value of the variable"
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
*>
macro @volatile_store(&x, y) @builtin
macro @volatile_load(#x) @builtin
{
return $$volatile_store(x, ($typeof(*x))y);
return $$volatile_load(&#x);
}
<*
@param #x : "The variable or dereferenced pointer to store to."
@param value : "The value to store."
@return "The value stored"
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
@require $defined(#x = value) : "The value doesn't match the variable"
*>
macro @volatile_store(#x, value) @builtin
{
return $$volatile_store(&#x, ($typeof(#x))value);
}
enum AtomicOrdering : int
@@ -212,39 +227,41 @@ enum AtomicOrdering : int
}
<*
@param [in] x "the variable or dereferenced pointer to load."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param $volatile "whether the load should be volatile, defaults to 'false'"
@param #x : "the variable or dereferenced pointer to load."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param $volatile : "whether the load should be volatile, defaults to 'false'"
@return "returns the value of x"
@require $ordering != AtomicOrdering.RELEASE "Release ordering is not valid for load."
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid for load."
@require types::may_load_atomic($typeof(x)) "Only integer, float and pointers may be used."
@require @typekind(x) == POINTER "You can only load from a pointer"
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
@require $ordering != AtomicOrdering.RELEASE : "Release ordering is not valid for load."
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid for load."
@require types::may_load_atomic($typeof(#x)) : "Only integer, float and pointers may be used."
*>
macro @atomic_load(&x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
macro @atomic_load(#x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
{
return $$atomic_load(x, $volatile, (int)$ordering);
return $$atomic_load(&#x, $volatile, $ordering.ordinal);
}
<*
@param [out] x "the variable or dereferenced pointer to store to."
@param value "the value to store."
@param $ordering "the atomic ordering of the store, defaults to SEQ_CONSISTENT"
@param $volatile "whether the store should be volatile, defaults to 'false'"
@param #x : "the variable or dereferenced pointer to store to."
@param value : "the value to store."
@param $ordering : "the atomic ordering of the store, defaults to SEQ_CONSISTENT"
@param $volatile : "whether the store should be volatile, defaults to 'false'"
@require $ordering != AtomicOrdering.ACQUIRE "Acquire ordering is not valid for store."
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid for store."
@require types::may_load_atomic($typeof(x)) "Only integer, float and pointers may be used."
@require $ordering != AtomicOrdering.ACQUIRE : "Acquire ordering is not valid for store."
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid for store."
@require types::may_load_atomic($typeof(#x)) : "Only integer, float and pointers may be used."
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
@require $defined(#x = value) : "The value doesn't match the variable"
*>
macro void @atomic_store(&x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
macro void @atomic_store(#x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
{
$$atomic_store(x, value, $volatile, (int)$ordering);
$$atomic_store(&#x, value, $volatile, $ordering.ordinal);
}
<*
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid."
*>
macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT, bool $volatile = true, bool $weak = false, usz $alignment = 0)
{
@@ -252,8 +269,8 @@ macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSIS
}
<*
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid."
*>
macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT)
{
@@ -286,7 +303,7 @@ macro void zero_volatile(char[] data)
$$memset(data.ptr, (char)0, data.len, true, (usz)1);
}
macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false, bool $inlined = false)
macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false)
{
$$memset(dst, (char)0, len, $is_volatile, $dst_align);
}
@@ -299,16 +316,17 @@ macro void clear_inline(void* dst, usz $len, usz $dst_align = 0, bool $is_volati
<*
Copy memory from src to dst efficiently, assuming the memory ranges do not overlap.
@param [&out] dst "The destination to copy to"
@param [&in] src "The source to copy from"
@param len "The number of bytes to copy"
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@param [&out] dst : "The destination to copy to"
@param [in] src : "The source to copy from"
@param len : "The number of bytes to copy"
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@require src != null || len == 0 : "Copying a null with non-zero length is invalid"
@require len == 0 || dst + len <= src || src + len <= dst : "Ranges may not overlap"
*>
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false, bool $inlined = false)
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
{
$$memcpy(dst, src, len, $is_volatile, $dst_align, $src_align);
}
@@ -317,13 +335,14 @@ macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig
Copy memory from src to dst efficiently, assuming the memory ranges do not overlap, it
will always be inlined and never call memcopy
@param [&out] dst "The destination to copy to"
@param [&in] src "The source to copy from"
@param $len "The number of bytes to copy"
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@param [&out] dst : "The destination to copy to"
@param [in] src : "The source to copy from"
@param $len : "The number of bytes to copy"
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@require src != null || len == 0 : "Copying a null with non-zero length is invalid"
@require $len == 0 || dst + $len <= src || src + $len <= dst : "Ranges may not overlap"
*>
macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
@@ -334,12 +353,14 @@ macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $
<*
Copy memory from src to dst but correctly handle the possibility of overlapping ranges.
@param [&out] dst "The destination to copy to"
@param [&in] src "The source to copy from"
@param len "The number of bytes to copy"
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@param [&out] dst : "The destination to copy to"
@param [in] src : "The source to copy from"
@param len : "The number of bytes to copy"
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@require src != null || len == 0 : "Moving a null with non-zero length is invalid"
*>
macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
{
@@ -349,11 +370,11 @@ macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig
<*
Sets all memory in a region to that of the provided byte.
@param [&out] dst "The destination to copy to"
@param val "The value to copy into memory"
@param len "The number of bytes to copy"
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@param [&out] dst : "The destination to copy to"
@param val : "The value to copy into memory"
@param len : "The number of bytes to copy"
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@ensure !len || (dst[0] == val && dst[len - 1] == val)
*>
@@ -365,11 +386,11 @@ macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volati
<*
Sets all memory in a region to that of the provided byte. Never calls OS memset.
@param [&out] dst "The destination to copy to"
@param val "The value to copy into memory"
@param $len "The number of bytes to copy"
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@param [&out] dst : "The destination to copy to"
@param val : "The value to copy into memory"
@param $len : "The number of bytes to copy"
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@ensure !$len || (dst[0] == val && dst[$len - 1] == val)
*>
@@ -406,7 +427,7 @@ macro bool equals(a, b, isz len = -1, usz $align = 0)
if (!len) return true;
var $Type;
$switch ($align)
$switch $align:
$case 1:
$Type = char;
$case 2:
@@ -456,7 +477,7 @@ macro void @scoped(Allocator allocator; @body())
Run the tracking allocator in the scope, then
print out stats.
@param $enabled "Set to false to disable tracking"
@param $enabled : "Set to false to disable tracking"
*>
macro void @report_heap_allocs_in_scope($enabled = true; @body())
{
@@ -475,19 +496,54 @@ macro void @report_heap_allocs_in_scope($enabled = true; @body())
@body();
}
<*
Assert on memory leak in the scope of the macro body.
@param $report : "Set to false to disable memory report"
*>
macro void @assert_leak($report = true; @body()) @builtin
{
$if env::DEBUG_SYMBOLS || $feature(MEMORY_ASSERTS):
TrackingAllocator tracker;
tracker.init(mem);
Allocator old_allocator = mem;
allocator::thread_allocator = &tracker;
defer
{
allocator::thread_allocator = old_allocator;
defer tracker.free();
usz allocated = tracker.allocated();
if (allocated)
{
DString report;
report.init(old_allocator);
defer report.free();
$if $report:
report.append_char('\n');
(void)tracker.fprint_report(&report);
$endif
assert(allocated == 0, "Memory leak detected"
" (%d bytes allocated).%s",
allocated, report.str_view());
}
}
$endif
@body();
}
<*
Allocate [size] bytes on the stack to use for allocation,
with the heap allocator as the backing allocator.
Release everything on scope exit.
@param $size `the size of the buffer`
@param $size : `the size of the buffer`
*>
macro void @stack_mem(usz $size; @body(Allocator mem)) @builtin
{
char[$size] buffer;
OnStackAllocator allocator;
allocator.init(&buffer, allocator::heap());
allocator.init(&buffer, mem);
defer allocator.free();
@body(&allocator);
}
@@ -496,7 +552,7 @@ macro void @stack_pool(usz $size; @body) @builtin
{
char[$size] buffer;
OnStackAllocator allocator;
allocator.init(&buffer, allocator::heap());
allocator.init(&buffer, mem);
defer allocator.free();
mem::@scoped(&allocator)
{
@@ -504,58 +560,44 @@ macro void @stack_pool(usz $size; @body) @builtin
};
}
struct TempState
{
TempAllocator* old;
TempAllocator* current;
usz mark;
}
<*
Push the current temp allocator. A push must always be balanced with a pop using the current state.
*>
fn TempState temp_push(TempAllocator* other = null)
fn PoolState temp_push()
{
TempAllocator* current = allocator::temp();
TempAllocator* old = current;
if (other == current)
{
current = allocator::temp_allocator_next();
}
return { old, current, current.used };
return allocator::push_pool() @inline;
}
<*
Pop the current temp allocator. A pop must always be balanced with a push.
*>
fn void temp_pop(TempState old_state)
fn void temp_pop(PoolState old_state)
{
assert(allocator::thread_temp_allocator == old_state.current, "Tried to pop temp allocators out of order.");
assert(old_state.current.used >= old_state.mark, "Tried to pop temp allocators out of order.");
old_state.current.reset(old_state.mark);
allocator::thread_temp_allocator = old_state.old;
allocator::pop_pool(old_state) @inline;
}
macro void @pool(TempAllocator* #other_temp = null; @body) @builtin
macro void @pool_init(Allocator allocator, usz pool_size, usz buffer_size; @body) @builtin
{
TempAllocator* current = allocator::temp();
var $has_arg = !$is_const(#other_temp);
$if $has_arg:
TempAllocator* original = current;
if (current == (void*)#other_temp) current = allocator::temp_allocator_next();
$endif
usz mark = current.used;
Allocator current = allocator::current_temp;
TempAllocator* top = allocator::top_temp;
allocator::create_temp_allocator(allocator, pool_size, buffer_size);
defer
{
current.reset(mark);
$if $has_arg:
allocator::thread_temp_allocator = original;
$endif;
allocator::destroy_temp_allocators();
allocator::top_temp = top;
allocator::current_temp = current;
}
@body();
}
macro void @pool(;@body) @builtin
{
PoolState state = allocator::push_pool() @inline;
defer
{
allocator::pop_pool(state) @inline;
}
@body();
}
import libc;
module std::core::mem @if(WASM_NOLIBC);
import std::core::mem::allocator @public;
@@ -571,7 +613,6 @@ fn void initialize_wasm_mem() @init(1024) @private
wasm_allocator.init(fn (x) => allocator::wasm_memory.allocate_block(x));
allocator::thread_allocator = &wasm_allocator;
allocator::temp_base_allocator = &wasm_allocator;
allocator::init_default_temp_allocators();
}
module std::core::mem;
@@ -580,7 +621,7 @@ module std::core::mem;
macro TrackingEnv* get_tracking_env()
{
$if env::TRACK_MEMORY:
return &&TrackingEnv { $$FILE, $$FUNC, $$LINE };
return &&(TrackingEnv){ $$FILE, $$FUNC, $$LINE };
$else
return null;
$endif
@@ -588,17 +629,17 @@ macro TrackingEnv* get_tracking_env()
macro @clone(value) @builtin @nodiscard
{
return allocator::clone(allocator::heap(), value);
return allocator::clone(mem, value);
}
macro @tclone(value) @builtin @nodiscard
{
return temp_new($typeof(value), value);
return tnew($typeof(value), value);
}
fn void* malloc(usz size) @builtin @inline @nodiscard
{
return allocator::malloc(allocator::heap(), size);
return allocator::malloc(mem, size);
}
<*
@@ -607,13 +648,13 @@ fn void* malloc(usz size) @builtin @inline @nodiscard
*>
fn void* malloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard
{
return allocator::malloc_aligned(allocator::heap(), size, alignment)!!;
return allocator::malloc_aligned(mem, size, alignment)!!;
}
fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
{
if (!size) return null;
return allocator::temp().acquire(size, NO_ZERO, alignment)!!;
return tmem.acquire(size, NO_ZERO, alignment)!!;
}
<*
@@ -632,6 +673,22 @@ macro new($Type, ...) @nodiscard
$endif
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro new_with_padding($Type, usz padding, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc($Type.sizeof + padding);
$else
$Type* val = malloc($Type.sizeof + padding);
*val = $vaexpr[0];
return val;
$endif
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@@ -657,6 +714,14 @@ macro alloc($Type) @nodiscard
return ($Type*)malloc($Type.sizeof);
}
<*
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro alloc_with_padding($Type, usz padding) @nodiscard
{
return ($Type*)malloc($Type.sizeof + padding);
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@@ -670,7 +735,7 @@ macro alloc_aligned($Type) @nodiscard
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro temp_new($Type, ...) @nodiscard
macro tnew($Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof) @inline;
@@ -681,18 +746,37 @@ macro temp_new($Type, ...) @nodiscard
$endif
}
macro temp_alloc($Type) @nodiscard
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro temp_with_padding($Type, usz padding, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof + padding) @inline;
$else
$Type* val = tmalloc($Type.sizeof + padding) @inline;
*val = $vaexpr[0];
return val;
$endif
}
macro talloc($Type) @nodiscard
{
return tmalloc($Type.sizeof);
}
macro talloc_with_padding($Type, usz padding) @nodiscard
{
return tmalloc($Type.sizeof + padding);
}
<*
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
*>
macro new_array($Type, usz elements) @nodiscard
{
return allocator::new_array(allocator::heap(), $Type, elements);
return allocator::new_array(mem, $Type, elements);
}
<*
@@ -701,7 +785,7 @@ macro new_array($Type, usz elements) @nodiscard
*>
macro new_array_aligned($Type, usz elements) @nodiscard
{
return allocator::new_array_aligned(allocator::heap(), $Type, elements);
return allocator::new_array_aligned(mem, $Type, elements);
}
<*
@@ -709,7 +793,7 @@ macro new_array_aligned($Type, usz elements) @nodiscard
*>
macro alloc_array($Type, usz elements) @nodiscard
{
return allocator::alloc_array(allocator::heap(), $Type, elements);
return allocator::alloc_array(mem, $Type, elements);
}
<*
@@ -718,22 +802,22 @@ macro alloc_array($Type, usz elements) @nodiscard
*>
macro alloc_array_aligned($Type, usz elements) @nodiscard
{
return allocator::alloc_array_aligned(allocator::heap(), $Type, elements);
return allocator::alloc_array_aligned(mem, $Type, elements);
}
macro temp_alloc_array($Type, usz elements) @nodiscard
macro talloc_array($Type, usz elements) @nodiscard
{
return (($Type*)tmalloc($Type.sizeof * elements, $Type.alignof))[:elements];
}
macro temp_new_array($Type, usz elements) @nodiscard
macro temp_array($Type, usz elements) @nodiscard
{
return (($Type*)tcalloc($Type.sizeof * elements, $Type.alignof))[:elements];
}
fn void* calloc(usz size) @builtin @inline @nodiscard
{
return allocator::calloc(allocator::heap(), size);
return allocator::calloc(mem, size);
}
<*
@@ -742,40 +826,40 @@ fn void* calloc(usz size) @builtin @inline @nodiscard
*>
fn void* calloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard
{
return allocator::calloc_aligned(allocator::heap(), size, alignment)!!;
return allocator::calloc_aligned(mem, size, alignment)!!;
}
fn void* tcalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
{
if (!size) return null;
return allocator::temp().acquire(size, ZERO, alignment)!!;
return tmem.acquire(size, ZERO, alignment)!!;
}
fn void* realloc(void *ptr, usz new_size) @builtin @inline @nodiscard
{
return allocator::realloc(allocator::heap(), ptr, new_size);
return allocator::realloc(mem, ptr, new_size);
}
fn void* realloc_aligned(void *ptr, usz new_size, usz alignment) @builtin @inline @nodiscard
{
return allocator::realloc_aligned(allocator::heap(), ptr, new_size, alignment)!!;
return allocator::realloc_aligned(mem, ptr, new_size, alignment)!!;
}
fn void free(void* ptr) @builtin @inline
{
return allocator::free(allocator::heap(), ptr);
return allocator::free(mem, ptr);
}
fn void free_aligned(void* ptr) @builtin @inline
{
return allocator::free_aligned(allocator::heap(), ptr);
return allocator::free_aligned(mem, ptr);
}
fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline @nodiscard
{
if (!size) return null;
if (!ptr) return tmalloc(size, alignment);
return allocator::temp().resize(ptr, size, alignment)!!;
return tmem.resize(ptr, size, alignment)!!;
}
module std::core::mem @if(env::NO_LIBC);
@@ -815,4 +899,3 @@ fn void* __memcpy(void* dst, void* src, usz n) @weak @export("memcpy")
}
return dst;
}

View File

@@ -1,4 +1,5 @@
module std::core::mem::allocator;
import std::math;
const DEFAULT_SIZE_PREFIX = usz.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
@@ -18,34 +19,30 @@ enum AllocInitType
interface Allocator
{
fn void reset(usz mark) @optional;
fn usz mark() @optional;
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*! acquire(usz size, AllocInitType init_type, usz alignment = 0);
fn void*? acquire(usz size, AllocInitType init_type, usz alignment = 0);
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require ptr != null
@require new_size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*! resize(void* ptr, usz new_size, usz alignment = 0);
fn void*? resize(void* ptr, usz new_size, usz alignment = 0);
<*
@require ptr != null
*>
fn void release(void* ptr, bool aligned);
}
def MemoryAllocFn = fn char[]!(usz);
alias MemoryAllocFn = fn char[]?(usz);
fault AllocationFailure
{
OUT_OF_MEMORY,
CHUNK_TOO_LARGE,
}
fn usz alignment_for_allocation(usz alignment) @inline @private
{
@@ -57,7 +54,7 @@ macro void* malloc(Allocator allocator, usz size) @nodiscard
return malloc_try(allocator, size)!!;
}
macro void*! malloc_try(Allocator allocator, usz size) @nodiscard
macro void*? malloc_try(Allocator allocator, usz size) @nodiscard
{
if (!size) return null;
$if env::TESTING:
@@ -74,7 +71,7 @@ macro void* calloc(Allocator allocator, usz size) @nodiscard
return calloc_try(allocator, size)!!;
}
macro void*! calloc_try(Allocator allocator, usz size) @nodiscard
macro void*? calloc_try(Allocator allocator, usz size) @nodiscard
{
if (!size) return null;
return allocator.acquire(size, ZERO);
@@ -85,7 +82,7 @@ macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard
return realloc_try(allocator, ptr, new_size)!!;
}
macro void*! realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
macro void*? realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
{
if (!new_size)
{
@@ -105,7 +102,7 @@ macro void free(Allocator allocator, void* ptr)
allocator.release(ptr, false);
}
macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
macro void*? malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
{
if (!size) return null;
$if env::TESTING:
@@ -117,13 +114,13 @@ macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodis
$endif
}
macro void*! calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
macro void*? calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
{
if (!size) return null;
return allocator.acquire(size, ZERO, alignment);
}
macro void*! realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
macro void*? realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
{
if (!new_size)
{
@@ -184,12 +181,12 @@ macro new_try(Allocator allocator, $Type, ...) @nodiscard
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new_aligned($Type, ...) @nodiscard
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc_aligned(allocator, $Type.sizeof, $Type.alignof);
$else
$Type* val = malloc_aligned(allocator, $Type.sizeof, $Type.alignof);
$Type* val = malloc_aligned(allocator, $Type.sizeof, $Type.alignof)!;
*val = $vaexpr[0];
return val;
$endif
@@ -305,7 +302,7 @@ fn any clone_any(Allocator allocator, any value) @nodiscard
@require alignment > 0
@require bytes <= isz.max
*>
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
{
if (alignment < void*.alignof) alignment = void*.alignof;
usz header = AlignedBlock.sizeof + alignment;
@@ -328,7 +325,7 @@ struct AlignedBlock
void* start;
}
macro void! @aligned_free(#free_fn, void* old_pointer)
macro void? @aligned_free(#free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
@@ -342,7 +339,7 @@ macro void! @aligned_free(#free_fn, void* old_pointer)
@require bytes > 0
@require alignment > 0
*>
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
@@ -358,13 +355,34 @@ macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
// All allocators
alias mem @builtin = thread_allocator ;
tlocal Allocator thread_allocator @private = base_allocator();
Allocator temp_base_allocator @private = base_allocator();
tlocal TempAllocator* thread_temp_allocator @private = null;
tlocal TempAllocator*[2] temp_allocator_pair @private;
typedef PoolState = TempAllocator*;
const LazyTempAllocator LAZY_TEMP @private = {};
tlocal Allocator current_temp = &LAZY_TEMP;
tlocal TempAllocator* top_temp;
tlocal bool auto_create_temp = false;
usz temp_allocator_min_size = temp_allocator_default_min_size();
usz temp_allocator_buffer_size = temp_allocator_default_buffer_size();
usz temp_allocator_new_mult = 4;
fn PoolState push_pool()
{
Allocator old = top_temp ? current_temp : create_temp_allocator_on_demand();
current_temp = ((TempAllocator*)old).derive_allocator(temp_allocator_min_size, temp_allocator_buffer_size, temp_allocator_new_mult)!!;
return (PoolState)old.ptr;
}
fn void pop_pool(PoolState old)
{
TempAllocator* temp = (TempAllocator*)old;
current_temp = temp;
temp.reset();
}
macro Allocator base_allocator() @private
{
@@ -375,36 +393,68 @@ macro Allocator base_allocator() @private
$endif
}
macro TempAllocator* create_default_sized_temp_allocator(Allocator allocator) @local
macro usz temp_allocator_size() @local
{
$switch (env::MEMORY_ENV)
$case NORMAL:
return new_temp_allocator(1024 * 256, allocator)!!;
$case SMALL:
return new_temp_allocator(1024 * 16, allocator)!!;
$case TINY:
return new_temp_allocator(1024 * 2, allocator)!!;
$case NONE:
unreachable("Temp allocator must explicitly created when memory-env is set to 'none'.");
$switch env::MEMORY_ENV:
$case NORMAL: return 256 * 1024;
$case SMALL: return 1024 * 32;
$case TINY: return 1024 * 4;
$case NONE: return 0;
$endswitch
}
macro usz temp_allocator_default_min_size() @local
{
$switch env::MEMORY_ENV:
$case NORMAL: return 16 * 1024;
$case SMALL: return 1024 * 2;
$case TINY: return 256;
$case NONE: return 256;
$endswitch
}
macro usz temp_allocator_default_buffer_size() @local
{
$switch env::MEMORY_ENV:
$case NORMAL: return 1024;
$case SMALL: return 128;
$case TINY: return 64;
$case NONE: return 64;
$endswitch
}
macro Allocator heap() => thread_allocator;
macro TempAllocator* temp()
<*
@require !top_temp : "This should never be called when temp already exists"
*>
fn Allocator create_temp_allocator_on_demand() @private
{
if (!thread_temp_allocator)
if (!auto_create_temp)
{
init_default_temp_allocators();
auto_create_temp = true;
abort("Use '@pool_init()' to enable the temp allocator on a new thread. A temp allocator is only implicitly created on the main thread.");
}
return thread_temp_allocator;
return create_temp_allocator(base_allocator(), temp_allocator_size());
}
<*
@require !top_temp : "This should never be called when temp already exists"
*>
fn Allocator create_temp_allocator(Allocator allocator, usz size, usz buffer = temp_allocator_default_buffer_size()) @private
{
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size)!!;
}
fn void init_default_temp_allocators() @private
macro Allocator temp()
{
temp_allocator_pair[0] = create_default_sized_temp_allocator(temp_base_allocator);
temp_allocator_pair[1] = create_default_sized_temp_allocator(temp_base_allocator);
thread_temp_allocator = temp_allocator_pair[0];
return current_temp;
}
alias tmem @builtin = current_temp;
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC)
{
auto_create_temp = true;
}
fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::LIBC)
@@ -417,35 +467,42 @@ fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::L
*>
fn void destroy_temp_allocators()
{
if (!thread_temp_allocator) return;
temp_allocator_pair[0].destroy();
temp_allocator_pair[1].destroy();
temp_allocator_pair[..] = null;
thread_temp_allocator = null;
if (!top_temp) return;
top_temp.free();
top_temp = null;
current_temp = &LAZY_TEMP;
}
fn TempAllocator* temp_allocator_next() @private
import libc;
typedef LazyTempAllocator (Allocator) @private = uptr;
fn void*? LazyTempAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
if (!top_temp) create_temp_allocator_on_demand();
return top_temp.acquire(bytes, init_type, alignment);
}
fn void*? LazyTempAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (!top_temp) create_temp_allocator_on_demand();
return top_temp.resize(old_ptr, new_bytes, alignment);
}
fn void LazyTempAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
if (!thread_temp_allocator)
{
init_default_temp_allocators();
return thread_temp_allocator;
}
usz index = thread_temp_allocator == temp_allocator_pair[0] ? 1 : 0;
return thread_temp_allocator = temp_allocator_pair[index];
}
const NullAllocator NULL_ALLOCATOR = {};
distinct NullAllocator (Allocator) = uptr;
typedef NullAllocator (Allocator) = uptr;
fn void*! NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
fn void*? NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
return AllocationFailure.OUT_OF_MEMORY?;
return mem::OUT_OF_MEMORY?;
}
fn void*! NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
fn void*? NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
return AllocationFailure.OUT_OF_MEMORY?;
return mem::OUT_OF_MEMORY?;
}
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic

View File

@@ -11,7 +11,7 @@ struct WasmMemory
uptr use;
}
fn char[]! WasmMemory.allocate_block(&self, usz bytes)
fn char[]? WasmMemory.allocate_block(&self, usz bytes)
{
if (!self.allocation)
{
@@ -25,7 +25,7 @@ fn char[]! WasmMemory.allocate_block(&self, usz bytes)
}
usz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE;
if ($$wasm_memory_grow(0, blocks_required) == -1) return AllocationFailure.OUT_OF_MEMORY?;
if ($$wasm_memory_grow(0, blocks_required) == -1) return mem::OUT_OF_MEMORY?;
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
defer self.use += bytes;
return ((char*)self.use)[:bytes];

View File

@@ -150,14 +150,14 @@ fn void x86_initialize_cpu_features()
{
uint max_level = x86_cpuid(0).eax;
CpuId feat = x86_cpuid(1);
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : CpuId {};
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : CpuId {};
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : CpuId {};
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : CpuId {};
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : CpuId {};
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : CpuId {};
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : CpuId {};
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : CpuId {};
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : {};
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : {};
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : {};
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : {};
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : {};
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : {};
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : {};
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : {};
add_feature_if_bit(ADX, leaf7.ebx, 19);
add_feature_if_bit(AES, feat.ecx, 25);
add_feature_if_bit(AMX_BF16, leaf7.edx, 22);

View File

@@ -56,10 +56,7 @@ struct MachHeader64
const LC_SEGMENT_64 = 0x19;
fault MachoSearch
{
NOT_FOUND
}
fn bool name_cmp(char* a, char[16]* b)
{
for (usz i = 0; i < 16; i++)
@@ -70,7 +67,7 @@ fn bool name_cmp(char* a, char[16]* b)
return false;
}
fn SegmentCommand64*! find_segment(MachHeader* header, char* segname)
fn SegmentCommand64*? find_segment(MachHeader* header, char* segname)
{
LoadCommand* command = (void*)header + MachHeader64.sizeof;
for (uint i = 0; i < header.ncmds; i++)
@@ -82,9 +79,9 @@ fn SegmentCommand64*! find_segment(MachHeader* header, char* segname)
}
command = (void*)command + command.cmdsize;
}
return MachoSearch.NOT_FOUND?;
return NOT_FOUND?;
}
fn Section64*! find_section(SegmentCommand64* command, char* sectname)
fn Section64*? find_section(SegmentCommand64* command, char* sectname)
{
Section64* section = (void*)command + SegmentCommand64.sizeof;
for (uint i = 0; i < command.nsects; i++)
@@ -92,22 +89,22 @@ fn Section64*! find_section(SegmentCommand64* command, char* sectname)
if (name_cmp(sectname, &section.sectname)) return section;
section++;
}
return MachoSearch.NOT_FOUND?;
return NOT_FOUND?;
}
macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type)
{
Section64*! section = find_section(find_segment(header, segname), sectname);
Section64*? section = find_section(find_segment(header, segname), sectname);
if (catch section)
{
return $Type[] {};
return ($Type[]){};
}
$Type* ptr = (void*)header + section.offset;
return ptr[:section.size / $Type.sizeof];
}
def DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide);
alias DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide);
extern fn void _dyld_register_func_for_add_image(DyldCallback);
@@ -126,7 +123,7 @@ extern fn void* realloc(void* ptr, usz size);
extern fn void* malloc(usz size);
extern fn void free(void* ptr);
def CallbackFn = fn void();
alias CallbackFn = fn void();
struct Callback
{
uint priority;

View File

@@ -47,6 +47,13 @@ macro int @main_to_int_main_args(#m, int argc, char** argv)
return #m(list);
}
macro int @_main_runner(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
return #m(list) ? 0 : 1;
}
macro int @main_to_void_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
@@ -73,7 +80,7 @@ macro String[] wargs_strings(int argc, Char16** argv) @private
{
Char16* arg = argv[i];
Char16[] argstring = arg[:_strlen(arg)];
list[i] = string::new_from_utf16(argstring) ?? "?".copy();
list[i] = string::from_utf16(mem, argstring) ?? "?".copy(mem);
}
return list[:argc];
}
@@ -157,6 +164,13 @@ macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
return #m(args);
}
macro int @_wmain_runner(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
return #m(args) ? 0 : 1;
}
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);

View File

@@ -22,230 +22,6 @@ struct SliceRaw
usz len;
}
def BenchmarkFn = fn void!();
struct BenchmarkUnit
{
String name;
BenchmarkFn func;
}
fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator = allocator::heap())
{
BenchmarkFn[] fns = $$BENCHMARK_FNS;
String[] names = $$BENCHMARK_NAMES;
BenchmarkUnit[] benchmarks = allocator::alloc_array(allocator, BenchmarkUnit, names.len);
foreach (i, benchmark : fns)
{
benchmarks[i] = { names[i], fns[i] };
}
return benchmarks;
}
const DEFAULT_BENCHMARK_WARMUP_ITERATIONS = 3;
const DEFAULT_BENCHMARK_MAX_ITERATIONS = 10000;
uint benchmark_warmup_iterations @private = DEFAULT_BENCHMARK_WARMUP_ITERATIONS;
uint benchmark_max_iterations @private = DEFAULT_BENCHMARK_MAX_ITERATIONS;
fn void set_benchmark_warmup_iterations(uint value) @builtin
{
benchmark_warmup_iterations = value;
}
fn void set_benchmark_max_iterations(uint value) @builtin
{
assert(value > 0);
benchmark_max_iterations = value;
}
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
{
int benchmarks_passed = 0;
int benchmark_count = benchmarks.len;
usz max_name;
foreach (&unit : benchmarks)
{
if (max_name < unit.name.len) max_name = unit.name.len;
}
usz len = max_name + 9;
DString name = dstring::temp_with_capacity(64);
name.append_repeat('-', len / 2);
name.append(" BENCHMARKS ");
name.append_repeat('-', len - len / 2);
io::printn(name);
name.clear();
long sys_clock_started;
long sys_clock_finished;
long sys_clocks;
Clock clock;
anyfault err;
foreach(unit : benchmarks)
{
defer name.clear();
name.appendf("Benchmarking %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
for (uint i = 0; i < benchmark_warmup_iterations; i++)
{
err = @catch(unit.func()) @inline;
@volatile_load(err);
}
clock = std::time::clock::now();
sys_clock_started = $$sysclock();
for (uint i = 0; i < benchmark_max_iterations; i++)
{
err = @catch(unit.func()) @inline;
@volatile_load(err);
}
sys_clock_finished = $$sysclock();
NanoDuration nano_seconds = clock.mark();
sys_clocks = sys_clock_finished - sys_clock_started;
if (err)
{
io::printfn("[failed] Failed due to: %s", err);
continue;
}
io::printfn("[ok] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
benchmarks_passed++;
}
io::printfn("\n%d benchmark%s run.\n", benchmark_count, benchmark_count > 1 ? "s" : "");
io::printfn("Benchmarks Result: %s. %d passed, %d failed.",
benchmarks_passed < benchmark_count ? "FAILED" : "ok",
benchmarks_passed,
benchmark_count - benchmarks_passed);
return benchmark_count == benchmarks_passed;
}
fn bool default_benchmark_runner()
{
@pool()
{
return run_benchmarks(benchmark_collection_create(allocator::temp()));
};
}
def TestFn = fn void!();
struct TestUnit
{
String name;
TestFn func;
}
fn TestUnit[] test_collection_create(Allocator allocator = allocator::heap())
{
TestFn[] fns = $$TEST_FNS;
String[] names = $$TEST_NAMES;
TestUnit[] tests = allocator::alloc_array(allocator, TestUnit, names.len);
foreach (i, test : fns)
{
tests[i] = { names[i], fns[i] };
}
return tests;
}
struct TestContext
{
JmpBuf buf;
}
// Sort the tests by their name in ascending order.
fn int cmp_test_unit(TestUnit a, TestUnit b)
{
usz an = a.name.len;
usz bn = b.name.len;
if (an > bn) @swap(a, b);
foreach (i, ac : a.name)
{
char bc = b.name[i];
if (ac != bc) return an > bn ? bc - ac : ac - bc;
}
return (int)(an - bn);
}
TestContext* test_context @private;
fn void test_panic(String message, String file, String function, uint line)
{
io::printn("[error]");
io::print("\n Error: ");
io::print(message);
io::printn();
io::printfn(" - in %s %s:%s.\n", function, file, line);
libc::longjmp(&test_context.buf, 1);
}
fn bool run_tests(TestUnit[] tests)
{
usz max_name;
foreach (&unit : tests)
{
if (max_name < unit.name.len) max_name = unit.name.len;
}
quicksort(tests, &cmp_test_unit);
TestContext context;
test_context = &context;
PanicFn old_panic = builtin::panic;
defer builtin::panic = old_panic;
builtin::panic = &test_panic;
int tests_passed = 0;
int test_count = tests.len;
DString name = dstring::temp_with_capacity(64);
usz len = max_name + 9;
name.append_repeat('-', len / 2);
name.append(" TESTS ");
name.append_repeat('-', len - len / 2);
io::printn(name);
name.clear();
foreach(unit : tests)
{
defer name.clear();
name.appendf("Testing %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
(void)io::stdout().flush();
if (libc::setjmp(&context.buf) == 0)
{
if (catch err = unit.func())
{
io::printfn("[failed] Failed due to: %s", err);
continue;
}
io::printn("[ok]");
tests_passed++;
}
}
io::printfn("\n%d test%s run.\n", test_count, test_count > 1 ? "s" : "");
io::printfn("Test Result: %s. %d passed, %d failed.",
tests_passed < test_count ? "FAILED" : "ok", tests_passed, test_count - tests_passed);
return test_count == tests_passed;
}
fn bool default_test_runner()
{
@pool()
{
return run_tests(test_collection_create(allocator::temp()));
};
}
module std::core::runtime @if(WASM_NOLIBC);

View File

@@ -0,0 +1,100 @@
module std::core::runtime;
import libc, std::time, std::io, std::sort;
alias BenchmarkFn = fn void();
struct BenchmarkUnit
{
String name;
BenchmarkFn func;
}
fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator)
{
BenchmarkFn[] fns = $$BENCHMARK_FNS;
String[] names = $$BENCHMARK_NAMES;
BenchmarkUnit[] benchmarks = allocator::alloc_array(allocator, BenchmarkUnit, names.len);
foreach (i, benchmark : fns)
{
benchmarks[i] = { names[i], fns[i] };
}
return benchmarks;
}
const DEFAULT_BENCHMARK_WARMUP_ITERATIONS = 3;
const DEFAULT_BENCHMARK_MAX_ITERATIONS = 10000;
uint benchmark_warmup_iterations @private = DEFAULT_BENCHMARK_WARMUP_ITERATIONS;
uint benchmark_max_iterations @private = DEFAULT_BENCHMARK_MAX_ITERATIONS;
fn void set_benchmark_warmup_iterations(uint value) @builtin
{
benchmark_warmup_iterations = value;
}
fn void set_benchmark_max_iterations(uint value) @builtin
{
assert(value > 0);
benchmark_max_iterations = value;
}
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
{
usz max_name;
foreach (&unit : benchmarks)
{
if (max_name < unit.name.len) max_name = unit.name.len;
}
usz len = max_name + 9;
DString name = dstring::temp_with_capacity(64);
name.append_repeat('-', len / 2);
name.append(" BENCHMARKS ");
name.append_repeat('-', len - len / 2);
io::printn(name);
name.clear();
long sys_clock_started;
long sys_clock_finished;
long sys_clocks;
Clock clock;
foreach(unit : benchmarks)
{
defer name.clear();
name.appendf("Benchmarking %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
for (uint i = 0; i < benchmark_warmup_iterations; i++)
{
unit.func() @inline;
}
clock = std::time::clock::now();
sys_clock_started = $$sysclock();
for (uint i = 0; i < benchmark_max_iterations; i++)
{
unit.func() @inline;
}
sys_clock_finished = $$sysclock();
NanoDuration nano_seconds = clock.mark();
sys_clocks = sys_clock_finished - sys_clock_started;
io::printfn("[COMPLETE] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
}
io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : "");
return true;
}
fn bool default_benchmark_runner(String[] args) => @pool()
{
return run_benchmarks(benchmark_collection_create(tmem));
}

View File

@@ -0,0 +1,334 @@
// Copyright (c) 2025 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::runtime;
import std::core::test @public;
import std::core::mem::allocator @public;
import libc, std::time, std::io, std::sort;
import std::os::env;
alias TestFn = fn void();
TestContext* test_context @private;
struct TestContext
{
JmpBuf buf;
// Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add'
String test_filter;
// Triggers debugger breakpoint when assert or test:: checks failed
bool breakpoint_on_assert;
// internal state
bool assert_print_backtrace;
bool has_ansi_codes;
bool is_in_panic;
bool is_quiet_mode;
bool is_no_capture;
String current_test_name;
TestFn setup_fn;
TestFn teardown_fn;
char* error_buffer;
usz error_buffer_capacity;
File fake_stdout;
struct stored
{
File stdout;
File stderr;
Allocator allocator;
}
}
struct TestUnit
{
String name;
TestFn func;
}
fn TestUnit[] test_collection_create(Allocator allocator)
{
TestFn[] fns = $$TEST_FNS;
String[] names = $$TEST_NAMES;
TestUnit[] tests = allocator::alloc_array(allocator, TestUnit, names.len);
foreach (i, test : fns)
{
tests[i] = { names[i], fns[i] };
}
return tests;
}
// Sort the tests by their name in ascending order.
fn int cmp_test_unit(TestUnit a, TestUnit b)
{
usz an = a.name.len;
usz bn = b.name.len;
if (an > bn) @swap(a, b);
foreach (i, ac : a.name)
{
char bc = b.name[i];
if (ac != bc) return an > bn ? bc - ac : ac - bc;
}
return (int)(an - bn);
}
fn bool terminal_has_ansi_codes() @local => @pool()
{
if (try v = env::tget_var("TERM"))
{
if (v.contains("xterm") || v.contains("vt100") || v.contains("screen")) return true;
}
$if env::WIN32 || env::NO_LIBC:
return false;
$else
return io::stdout().isatty();
$endif
}
fn void test_panic(String message, String file, String function, uint line) @local
{
if (test_context.is_in_panic) return;
test_context.is_in_panic = true;
unmute_output(true);
(void)io::stdout().flush();
if (test_context.assert_print_backtrace)
{
$if env::NATIVE_STACKTRACE:
builtin::print_backtrace(message, 0);
$endif
}
io::printf("\nTest failed ^^^ ( %s:%s ) %s\n", file, line, message);
test_context.assert_print_backtrace = true;
if (test_context.breakpoint_on_assert)
{
breakpoint();
}
if (test_context.teardown_fn)
{
test_context.teardown_fn();
}
test_context.is_in_panic = false;
allocator::thread_allocator = test_context.stored.allocator;
libc::longjmp(&test_context.buf, 1);
}
fn void mute_output() @local
{
if (test_context.is_no_capture || !test_context.fake_stdout.file) return;
File* stdout = io::stdout();
File* stderr = io::stderr();
*stderr = test_context.fake_stdout;
*stdout = test_context.fake_stdout;
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
}
fn void unmute_output(bool has_error) @local
{
if (test_context.is_no_capture || !test_context.fake_stdout.file) return;
File* stdout = io::stdout();
File* stderr = io::stderr();
*stderr = test_context.stored.stderr;
*stdout = test_context.stored.stdout;
usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
if (has_error)
{
io::printf("\nTesting %s ", test_context.current_test_name);
io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
}
if (has_error && log_size > 0)
{
test_context.fake_stdout.write_byte('\n')!!;
test_context.fake_stdout.write_byte('\0')!!;
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
io::printfn("\n========== TEST LOG ============");
io::printfn("%s\n", test_context.current_test_name);
while (try c = test_context.fake_stdout.read_byte())
{
if (@unlikely(c == '\0'))
{
// ignore junk from previous tests
break;
}
libc::putchar(c);
}
io::printf("========== TEST END ============");
}
(void)stdout.flush();
}
fn bool run_tests(String[] args, TestUnit[] tests) @private
{
usz max_name;
bool sort_tests = true;
bool check_leaks = true;
foreach (&unit : tests)
{
if (max_name < unit.name.len) max_name = unit.name.len;
}
TestContext context =
{
.assert_print_backtrace = true,
.breakpoint_on_assert = false,
.test_filter = "",
.has_ansi_codes = terminal_has_ansi_codes(),
.stored.allocator = mem,
.stored.stderr = *io::stderr(),
.stored.stdout = *io::stdout(),
};
for (int i = 1; i < args.len; i++)
{
switch (args[i])
{
case "--test-breakpoint":
context.breakpoint_on_assert = true;
case "--test-nosort":
sort_tests = false;
case "--test-noleak":
check_leaks = false;
case "--test-nocapture":
context.is_no_capture = true;
case "--noansi":
context.has_ansi_codes = false;
case "--useansi":
context.has_ansi_codes = true;
case "--test-quiet":
context.is_quiet_mode = true;
case "--test-filter":
if (i == args.len - 1)
{
io::printn("Invalid arguments to test runner.");
return false;
}
context.test_filter = args[i + 1];
i++;
default:
io::printfn("Unknown argument: %s", args[i]);
}
}
test_context = &context;
if (sort_tests)
{
quicksort(tests, &cmp_test_unit);
}
// Buffer for hijacking the output
$if (!env::NO_LIBC):
context.fake_stdout.file = libc::tmpfile();
$endif
if (context.fake_stdout.file == null)
{
io::print("Failed to hijack stdout, tests will print everything");
}
PanicFn old_panic = builtin::panic;
defer builtin::panic = old_panic;
builtin::panic = &test_panic;
int tests_passed = 0;
int tests_skipped = 0;
int test_count = tests.len;
DString name = dstring::temp_with_capacity(64);
usz len = max_name + 9;
name.append_repeat('-', len / 2);
name.append(" TESTS ");
name.append_repeat('-', len - len / 2);
if (!context.is_quiet_mode) io::printn(name);
name.clear();
PoolState temp_state = mem::temp_push();
defer mem::temp_pop(temp_state);
foreach(unit : tests)
{
mem::temp_pop(temp_state);
if (context.test_filter && !unit.name.contains(context.test_filter))
{
tests_skipped++;
continue;
}
context.setup_fn = null;
context.teardown_fn = null;
context.current_test_name = unit.name;
defer name.clear();
name.appendf("Testing %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
if (context.is_quiet_mode)
{
io::print(".");
}
else
{
io::printf("%s ", name.str_view());
}
(void)io::stdout().flush();
TrackingAllocator mem;
mem.init(context.stored.allocator);
if (libc::setjmp(&context.buf) == 0)
{
mute_output();
mem.clear();
if (check_leaks) allocator::thread_allocator = &mem;
unit.func();
// track cleanup that may take place in teardown_fn
if (context.teardown_fn)
{
context.teardown_fn();
}
if (check_leaks) allocator::thread_allocator = context.stored.allocator;
unmute_output(false); // all good, discard output
if (mem.has_leaks())
{
if (context.is_quiet_mode) io::printf("\n%s ", context.current_test_name);
io::print(context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
io::printn(" LEAKS DETECTED!");
mem.print_report();
}
else
{
if (!context.is_quiet_mode)
{
io::printfn(context.has_ansi_codes ? "[\e[0;32mPASS\e[0m]" : "[PASS]");
}
tests_passed++;
}
}
mem.free();
}
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count > 1 ? "s" : "");
int n_failed = test_count - tests_passed - tests_skipped;
io::printf("Test Result: %s%s%s: ",
context.has_ansi_codes ? (n_failed ? "\e[0;31m" : "\e[0;32m") : "",
n_failed ? "FAILED" : "PASSED",
context.has_ansi_codes ? "\e[0m" : "",
);
io::printfn("%d passed, %d failed, %d skipped.",
tests_passed,
n_failed,
tests_skipped);
// cleanup fake_stdout file
if (context.fake_stdout.file) libc::fclose(context.fake_stdout.file);
context.fake_stdout.file = null;
return n_failed == 0;
}
fn bool default_test_runner(String[] args) => @pool()
{
assert(test_context == null, "test suite is already running");
return run_tests(args, test_collection_create(tmem));
}

View File

@@ -12,7 +12,7 @@
module std::core::sanitizer::asan;
def ErrorCallback = fn void (ZString);
alias ErrorCallback = fn void (ZString);
<*
Marks a memory region ([addr, addr+size)) as unaddressable.
@@ -26,8 +26,8 @@ def ErrorCallback = fn void (ZString);
NOTE This function is not thread-safe because no two threads can poison or
unpoison memory in the same memory region simultaneously.
@param addr "Start of memory region."
@param size "Size of memory region."
@param addr : "Start of memory region."
@param size : "Size of memory region."
*>
macro poison_memory_region(void* addr, usz size)
{
@@ -47,8 +47,8 @@ macro poison_memory_region(void* addr, usz size)
NOTE This function is not thread-safe because no two threads can
poison or unpoison memory in the same memory region simultaneously.
@param addr "Start of memory region."
@param size "Size of memory region."
@param addr : "Start of memory region."
@param size : "Size of memory region."
*>
macro unpoison_memory_region(void* addr, usz size)
{
@@ -60,7 +60,7 @@ macro unpoison_memory_region(void* addr, usz size)
<*
Checks if an address is poisoned.
@return "True if 'addr' is poisoned (that is, 1-byte read/write access to this address would result in an error report from ASan). Otherwise returns false."
@param addr "Address to check."
@param addr : "Address to check."
*>
macro bool address_is_poisoned(void* addr)
{
@@ -77,8 +77,8 @@ macro bool address_is_poisoned(void* addr)
If at least one byte in [beg, beg+size) is poisoned, returns the
address of the first such byte. Otherwise returns 0.
@param beg "Start of memory region."
@param size "Start of memory region."
@param beg : "Start of memory region."
@param size : "Start of memory region."
@return "Address of first poisoned byte."
*>
macro void* region_is_poisoned(void* beg, usz size)

View File

@@ -1,6 +1,6 @@
module std::core::sanitizer::tsan;
distinct MutexFlags = inline CUInt;
typedef MutexFlags = inline CUInt;
const MutexFlags MUTEX_LINKER_INIT = 1 << 0;
const MutexFlags MUTEX_WRITE_REENTRANT = 1 << 1;

File diff suppressed because it is too large Load Diff

View File

@@ -11,22 +11,22 @@ fn void StringIterator.reset(&self)
self.current = 0;
}
fn Char32! StringIterator.next(&self)
fn Char32? StringIterator.next(&self)
{
usz len = self.utf8.len;
usz current = self.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
if (current >= len) return NO_MORE_ELEMENT?;
usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
self.current += read;
return res;
}
fn Char32! StringIterator.peek(&self)
fn Char32? StringIterator.peek(&self)
{
usz len = self.utf8.len;
usz current = self.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
if (current >= len) return NO_MORE_ELEMENT?;
usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
return res;
@@ -37,13 +37,13 @@ fn bool StringIterator.has_next(&self)
return self.current < self.utf8.len;
}
fn Char32! StringIterator.get(&self)
fn Char32? StringIterator.get(&self)
{
usz len = self.utf8.len;
usz current = self.current;
usz read = (len - current < 4 ? len - current : 4);
usz index = current > read ? current - read : 0;
if (index >= len) return IteratorResult.NO_MORE_ELEMENT?;
if (index >= len) return NO_MORE_ELEMENT?;
Char32 res = conv::utf8_to_char32(&self.utf8[index], &read)!;
return res;
}

View File

@@ -34,7 +34,7 @@ const uint[2] B1B_MAX = { 9007199, 254740991 };
<*
@require chars.len > 0
*>
macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
{
uint[KMAX] x;
const uint[2] TH = B1B_MAX;
@@ -64,7 +64,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
got_rad = true;
if (index == last_char)
{
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
if (!got_digit) return MALFORMED_FLOAT?;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
@@ -83,7 +83,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
switch
{
case c == '.':
if (got_rad) return NumberConversion.MALFORMED_FLOAT?;
if (got_rad) return MALFORMED_FLOAT?;
got_rad = true;
lrp = dc;
case k < KMAX - 3:
@@ -113,24 +113,24 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
c = chars[++index];
}
if (!got_rad) lrp = dc;
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
if (!got_digit) return MALFORMED_FLOAT?;
if ((c | 32) == 'e')
{
if (last_char == index) return NumberConversion.MALFORMED_FLOAT?;
long e10 = String.to_long((String)chars[index + 1..]) ?? NumberConversion.MALFORMED_FLOAT?!;
if (last_char == index) return MALFORMED_FLOAT?;
long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT?!;
lrp += e10;
}
else if (index != last_char)
{
return NumberConversion.MALFORMED_FLOAT?;
return MALFORMED_FLOAT?;
}
// Handle zero specially to avoid nasty special cases later
if (!x[0]) return sign * 0.0;
// Optimize small integers (w/no exponent) and over/under-flow
if (lrp == dc && dc < 10 && ($bits > 30 || (ulong)x[0] >> $bits == 0)) return sign * (double)x[0];
if (lrp > - $emin / 2) return NumberConversion.FLOAT_OUT_OF_RANGE?;
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.FLOAT_OUT_OF_RANGE?;
if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE?;
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
// Align incomplete final B1B digit
if (j)
@@ -320,12 +320,12 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
y *= 0.5;
e2++;
}
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return NumberConversion.MALFORMED_FLOAT?;
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT?;
}
return math::scalbn(y, e2);
}
macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
{
double scale = 1;
uint x;
@@ -351,7 +351,7 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
got_rad = true;
if (index == last_char)
{
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
if (!got_digit) return MALFORMED_FLOAT?;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
@@ -369,17 +369,14 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
{
if (c == '.')
{
if (got_rad) return NumberConversion.MALFORMED_FLOAT?;
if (got_rad) return MALFORMED_FLOAT?;
got_rad = true;
rp = dc;
}
else
{
got_digit = true;
int d = {|
if (c > '9') return (c | 32) + 10 - 'a';
return c - '0';
|};
int d = c > '9' ? ((c | 32) + 10 - 'a') : (c - '0');
switch
{
case dc < 8:
@@ -396,20 +393,20 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
if (index == last_char) break;
c = chars[++index];
}
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
if (!got_digit) return MALFORMED_FLOAT?;
if (!got_rad) rp = dc;
for (; dc < 8; dc++) x *= 16;
long e2;
if ((c | 32) == 'p')
{
long e2val = String.to_long((String)chars[index + 1..]) ?? (NumberConversion.MALFORMED_FLOAT?)!;
long e2val = String.to_long((String)chars[index + 1..]) ?? (MALFORMED_FLOAT?)!;
e2 = e2val;
}
e2 += 4 * rp - 32;
if (!x) return sign * 0.0;
if (e2 > -$emin) return NumberConversion.FLOAT_OUT_OF_RANGE?;
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.FLOAT_OUT_OF_RANGE?;
if (e2 > -$emin) return FLOAT_OUT_OF_RANGE?;
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
while (x < 0x80000000)
{
@@ -444,7 +441,7 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
}
y = bias + sign * (double)x + sign * y;
y -= bias;
if (!y) return NumberConversion.FLOAT_OUT_OF_RANGE?;
if (!y) return FLOAT_OUT_OF_RANGE?;
return math::scalbn(y, (int)e2);
}
@@ -452,7 +449,7 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
macro String.to_real(chars, $Type) @private
{
int sign = 1;
$switch ($Type)
$switch $Type:
$case float:
const int BITS = math::FLOAT_MANT_DIG;
const int EMIN = math::FLOAT_MIN_EXP - BITS;
@@ -466,14 +463,18 @@ macro String.to_real(chars, $Type) @private
$endswitch
while (chars.len && chars[0] == ' ') chars = chars[1..];
if (!chars.len) return NumberConversion.MALFORMED_FLOAT?;
switch (chars[0])
if (!chars.len) return MALFORMED_FLOAT?;
if (chars.len != 1)
{
case '-':
sign = -1;
nextcase;
case '+':
chars = chars[1..];
switch (chars[0])
{
case '-':
sign = -1;
nextcase;
case '+':
chars = chars[1..];
}
}
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
if (chars == "NAN" || chars == "nan") return $Type.nan;

220
lib/std/core/test.c3 Normal file
View File

@@ -0,0 +1,220 @@
<*
Unit test module
This module provides a toolset of macros for running unit test checks
Example:
```c3
module sample::m;
import std::io;
faultdef DIVISION_BY_ZERO;
fn double? divide(int a, int b)
{
if (b == 0) return MathError.DIVISION_BY_ZERO?;
return (double)(a) / (double)(b);
}
fn void? test_div() @test
{
test::eq(2, divide(6, 3)!);
test::ne(1, 2);
test::ge(3, 3);
test::gt(2, divide(3, 3)!);
test::lt(2, 3);
test::le(2, 3);
test::eq_approx(m::divide(1, 3)!, 0.333, places: 3);
test::@check(2 == 2, "divide: %d", divide(6, 3)!);
test::@error(m::divide(3, 0), MathError.DIVISION_BY_ZERO);
}
```
*>
// Copyright (c) 2025 Alex Veden <i@alexveden.com>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::test;
import std::core::runtime @public;
import std::math, std::io, libc;
<*
Initializes test case context.
@param setup_fn : `initializer function for test case`
@param teardown_fn : `cleanup function for test context (may be null)`
@require runtime::test_context != null : "Only allowed in @test functions"
@require setup_fn != null : "setup_fn must always be set"
*>
macro @setup(TestFn setup_fn, TestFn teardown_fn = null)
{
runtime::test_context.setup_fn = setup_fn;
runtime::test_context.teardown_fn = teardown_fn;
runtime::test_context.setup_fn();
}
<*
Checks condition and fails assertion if not true
@param #condition : `any boolean condition, will be expanded by text`
@param format : `printf compatible format`
@param args : `vargs for format`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro @check(#condition, String format = "", args...)
{
if (!#condition)
{
@stack_mem(512; Allocator allocator)
{
DString s;
s.init(allocator);
s.appendf("check `%s` failed. ", $stringify(#condition));
s.appendf(format, ...args);
print_panicf(s.str_view());
};
}
}
<*
Check if function returns specific error
@param #funcresult : `result of function execution`
@param error_expected : `expected error of function execution`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro @error(#funcresult, fault error_expected)
{
if (catch err = #funcresult)
{
if (err != error_expected)
{
print_panicf("`%s` expected to return error [%s], got [%s]",
$stringify(#funcresult), error_expected, err);
}
return;
}
print_panicf("`%s` error [%s] was not returned.", $stringify(#funcresult), error_expected);
}
<*
Check if left == right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro eq(left, right)
{
if (!equals(left, right))
{
print_panicf("`%s` != `%s`", left, right);
}
}
<*
Check left floating point value is approximately equals to right value
@param places : `number of decimal places to compare (default: 7)`
@param delta : `minimal allowed difference (overrides places parameter)`
@param equal_nan : `allows comparing nan values, if left and right both nans result is ok`
@require places > 0, places <= 20 : "too many decimal places"
@require delta >= 0, delta <= 1 : "delta must be a small number"
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro void eq_approx(double left, double right, uint places = 7, double delta = 0, bool equal_nan = true)
{
double diff = left - right;
double eps = delta;
if (eps == 0) eps = 1.0 / math::pow(10.0, places);
if (!math::is_approx(left, right, eps))
{
if (equal_nan && math::is_nan(left) && math::is_nan(right)) return;
print_panicf("Not almost equal: `%s` !~~ `%s` delta=%e diff: %e", left, right, eps, diff);
}
}
<*
Check if left != right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro void ne(left, right)
{
if (equals(left, right))
{
print_panicf("`%s` == `%s`", left, right);
}
}
<*
Check if left > right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro gt(left, right)
{
if (!builtin::greater(left, right))
{
print_panicf("`%s` <= `%s`", left, right);
}
}
<*
Check if left >= right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro ge(left, right)
{
if (!builtin::greater_eq(left, right))
{
print_panicf("`%s` < `%s`", left, right);
}
}
<*
Check if left < right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro lt(left, right)
{
if (!builtin::less(left, right))
{
print_panicf("`%s` >= `%s`", left, right);
}
}
<*
Check if left <= right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro le(left, right)
{
if (!builtin::less_eq(left, right))
{
print_panicf("`%s` > `%s`", left, right);
}
}
macro void print_panicf(format, ...) @local
{
runtime::test_context.assert_print_backtrace = false;
builtin::panicf(format, $$FILE, $$FUNC, $$LINE, $vasplat);
}

View File

@@ -3,23 +3,25 @@ module std::core::types;
import libc;
fault ConversionResult
{
VALUE_OUT_OF_RANGE,
VALUE_OUT_OF_UNSIGNED_RANGE,
}
faultdef VALUE_OUT_OF_RANGE, VALUE_OUT_OF_UNSIGNED_RANGE;
<*
@require $Type.kindof.is_int() || $Type.kindof == TypeKind.ENUM "Argument was not an integer"
@require $Type.kindof.is_int() : "Type was not an integer"
@require v.type.kindof == ENUM : "Value was not an enum"
*>
macro any_to_enum_ordinal(any v, $Type)
{
return any_to_int(v.as_inner(), $Type);
}
<*
@require $Type.kindof.is_int() : "Type was not an integer"
@require v.type.kindof.is_int() : "Value was not an integer"
*>
macro any_to_int(any v, $Type)
{
typeid any_type = v.type;
TypeKind kind = any_type.kindof;
if (kind == TypeKind.ENUM)
{
any_type = any_type.inner;
kind = any_type.kindof;
}
bool is_mixed_signed = $Type.kindof != any_type.kindof;
$Type max = $Type.max;
$Type min = $Type.min;
@@ -27,47 +29,47 @@ macro any_to_int(any v, $Type)
{
case ichar:
ichar c = *(char*)v.ptr;
if (is_mixed_signed && c < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
return ($Type)c;
case short:
short s = *(short*)v.ptr;
if (is_mixed_signed && s < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (s > max || s < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && s < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
return ($Type)s;
case int:
int i = *(int*)v.ptr;
if (is_mixed_signed && i < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
return ($Type)i;
case long:
long l = *(long*)v.ptr;
if (is_mixed_signed && l < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (l > max || l < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && l < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
return ($Type)l;
case int128:
int128 i = *(int128*)v.ptr;
if (is_mixed_signed && i < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
return ($Type)i;
case char:
char c = *(char*)v.ptr;
if (c > max) return ConversionResult.VALUE_OUT_OF_RANGE?;
if (c > max) return VALUE_OUT_OF_RANGE?;
return ($Type)c;
case ushort:
ushort s = *(ushort*)v.ptr;
if (s > max || s < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
return ($Type)s;
case uint:
uint i = *(uint*)v.ptr;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
return ($Type)i;
case ulong:
ulong l = *(ulong*)v.ptr;
if (l > max || l < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
return ($Type)l;
case uint128:
uint128 i = *(uint128*)v.ptr;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
return ($Type)i;
default:
unreachable();
@@ -87,7 +89,7 @@ fn bool typeid.is_subtype_of(self, typeid other)
macro bool is_subtype_of($Type, $OtherType)
{
var $typeid = $Type.typeid;
$switch ($Type)
$switch $Type:
$case $OtherType: return true;
$default: return false;
$endswitch
@@ -110,7 +112,7 @@ fn bool TypeKind.is_int(kind) @inline
macro bool is_slice_convertable($Type)
{
$switch ($Type.kindof)
$switch $Type.kindof:
$case SLICE:
return true;
$case POINTER:
@@ -124,11 +126,11 @@ macro bool is_bool($Type) @const => $Type.kindof == TypeKind.BOOL;
macro bool is_int($Type) @const => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT;
<*
@require is_numerical($Type) "Expected a numerical type"
@require is_numerical($Type) : "Expected a numerical type"
*>
macro bool is_signed($Type) @const
{
$switch (inner_kind($Type))
$switch inner_kind($Type):
$case SIGNED_INT:
$case FLOAT:
return true;
@@ -140,11 +142,11 @@ macro bool is_signed($Type) @const
}
<*
@require is_numerical($Type) "Expected a numerical type"
@require is_numerical($Type) : "Expected a numerical type"
*>
macro bool is_unsigned($Type) @const
{
$switch (inner_kind($Type))
$switch inner_kind($Type):
$case UNSIGNED_INT:
return true;
$case VECTOR:
@@ -154,19 +156,37 @@ macro bool is_unsigned($Type) @const
$endswitch
}
macro typeid flat_type($Type) @const
{
$if $Type.kindof == DISTINCT:
return flat_type($typefrom($Type.inner));
$else
return $Type.typeid;
$endif
}
macro TypeKind flat_kind($Type) @const
{
$if $Type.kindof == DISTINCT:
return flat_type($typefrom($Type.inner));
$else
return $Type.kindof;
$endif
}
macro bool is_indexable($Type) @const
{
return $defined($Type{}[0]);
return $defined(($Type){}[0]);
}
macro bool is_ref_indexable($Type) @const
{
return $defined(&$Type{}[0]);
return $defined(&($Type){}[0]);
}
macro bool is_intlike($Type) @const
{
$switch ($Type.kindof)
$switch $Type.kindof:
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
@@ -179,7 +199,7 @@ macro bool is_intlike($Type) @const
macro bool is_underlying_int($Type) @const
{
$switch ($Type.kindof)
$switch $Type.kindof:
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
@@ -194,7 +214,7 @@ macro bool is_float($Type) @const => $Type.kindof == TypeKind.FLOAT;
macro bool is_floatlike($Type) @const
{
$switch ($Type.kindof)
$switch $Type.kindof:
$case FLOAT:
return true;
$case VECTOR:
@@ -234,7 +254,7 @@ macro bool @has_same(#a, #b, ...) @const
$if $type_a != @typeid(#b):
return false;
$endif
$for (var $i = 0; $i < $vacount; $i++)
$for var $i = 0; $i < $vacount; $i++:
$if @typeid($vaexpr[$i]) != $type_a:
return false;
$endif
@@ -244,7 +264,8 @@ macro bool @has_same(#a, #b, ...) @const
macro bool may_load_atomic($Type) @const
{
$switch ($Type.kindof)
$switch $Type.kindof:
$case BOOL:
$case SIGNED_INT:
$case UNSIGNED_INT:
$case POINTER:
@@ -259,15 +280,16 @@ macro bool may_load_atomic($Type) @const
macro lower_to_atomic_compatible_type($Type) @const
{
$switch ($Type.kindof)
$case SIGNED_INT:
$case UNSIGNED_INT:
return $Type.typeid;
$switch $Type.kindof:
$case BOOL:
$case SIGNED_INT:
$case UNSIGNED_INT:
return $Type.typeid;
$case DISTINCT:
return lower_to_atomic_compatible_type($Type.inner);
$case FLOAT:
$switch ($Type)
$case float16:
$switch $Type:
$case float16:
return ushort.typeid;
$case float:
return uint.typeid;
@@ -312,11 +334,6 @@ macro bool implements_copy($Type) @const
return $defined($Type.copy) && $defined($Type.free);
}
macro bool is_equatable_value(value) @deprecated
{
return is_equatable_type($typeof(value));
}
macro bool @equatable_value(#value) @const
{
return is_equatable_type($typeof(#value));
@@ -331,15 +348,6 @@ macro bool @comparable_value(#value) @const
$endif
}
macro bool is_comparable_value(value) @deprecated
{
$if $defined(value.less) || $defined(value.compare_to):
return true;
$else
return $typeof(value).is_ordered;
$endif
}
enum TypeKind : char
{
VOID,
@@ -348,10 +356,9 @@ enum TypeKind : char
UNSIGNED_INT,
FLOAT,
TYPEID,
ANYFAULT,
FAULT,
ANY,
ENUM,
FAULT,
STRUCT,
UNION,
BITSTRUCT,

View File

@@ -16,6 +16,7 @@ macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_flo
macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2));
macro bool @is_lvalue(#value) => $defined(#value = #value);
macro promote_int(x)
{
@@ -26,10 +27,29 @@ macro promote_int(x)
$endif
}
<*
Select between two values at compile time,
the values do not have to be of the same type.
This acts like `$bool ? #value_1 : #value_2` but at compile time.
@param $bool : `true for picking the first value, false for the other`
@param #value_1
@param #value_2
@returns `The selected value.`
*>
macro @select(bool $bool, #value_1, #value_2) @builtin
{
$if $bool:
return #value_1;
$else
return #value_2;
$endif
}
macro promote_int_same(x, y)
{
$if @is_int(x):
$switch
$switch:
$case @is_vector(y) &&& $typeof(y).inner == float.typeid:
return (float)x;
$case $typeof(y).typeid == float.typeid:

View File

@@ -12,8 +12,8 @@ struct Rc4
<*
Initialize the RC4 state.
@param [in] key "The key to use"
@require key.len > 0 "The key must be at least 1 byte long"
@param [in] key : "The key to use"
@require key.len > 0 : "The key must be at least 1 byte long"
*>
fn void Rc4.init(&self, char[] key)
{
@@ -43,9 +43,9 @@ fn void crypt(char[] key, char[] data)
<*
Encrypt or decrypt a sequence of bytes.
@param [in] in "The input"
@param [out] out "The output"
@require in.len <= out.len "Output would overflow"
@param [in] in : "The input"
@param [out] out : "The output"
@require in.len <= out.len : "Output would overflow"
*>
fn void Rc4.crypt(&self, char[] in, char[] out)
{
@@ -67,7 +67,7 @@ fn void Rc4.crypt(&self, char[] in, char[] out)
<*
Clear the rc4 state.
@param [&out] self "The RC4 State"
@param [&out] self : "The RC4 State"
*>
fn void Rc4.destroy(&self)
{

View File

@@ -14,13 +14,13 @@ const char DEFAULT_PAD = '=';
<*
Encode the content of src into a newly allocated string
@param [in] src "The input to be encoded."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@param [in] src : "The input to be encoded."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@return "The encoded string."
*>
fn String! encode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
fn String? encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding));
return encode_buffer(src, dst, padding, alphabet);
@@ -28,28 +28,26 @@ fn String! encode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, B
<*
Decode the content of src into a newly allocated char array.
@param [in] src "The input to be encoded."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@param [in] src : "The input to be encoded."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@return "The decoded data."
*>
fn char[]! decode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
fn char[]? decode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding));
return decode_buffer(src, dst, padding, alphabet);
}
fn String! encode_new(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::heap(), padding, alphabet);
fn String! encode_temp(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::temp(), padding, alphabet);
fn char[]! decode_new(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::heap(), padding, alphabet);
fn char[]! decode_temp(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::temp(), padding, alphabet);
fn String? tencode(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(tmem, code, padding, alphabet);
fn char[]? tdecode(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(tmem, code, padding, alphabet);
<*
Calculate the length in bytes of the decoded data.
@param n "Length in bytes of input."
@param padding "The padding character or 0 if none"
@require padding < 0xFF "Invalid padding character"
@param n : "Length in bytes of input."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@return "Length in bytes of the decoded data."
*>
fn usz decode_len(usz n, char padding)
@@ -62,9 +60,9 @@ fn usz decode_len(usz n, char padding)
<*
Calculate the length in bytes of the encoded data.
@param n "Length in bytes on input."
@param padding "The padding character or 0 if none"
@require padding < 0xFF "Invalid padding character"
@param n : "Length in bytes on input."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@return "Length in bytes of the encoded data."
*>
fn usz encode_len(usz n, char padding)
@@ -79,16 +77,16 @@ fn usz encode_len(usz n, char padding)
<*
Decode the content of src into dst, which must be properly sized.
@param src "The input to be decoded."
@param dst "The decoded input."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@require dst.len >= decode_len(src.len, padding) "Destination buffer too small"
@param src : "The input to be decoded."
@param dst : "The decoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@require dst.len >= decode_len(src.len, padding) : "Destination buffer too small"
@return "The resulting dst buffer"
@return! DecodingFailure
@return? encoding::INVALID_PADDING, encoding::INVALID_CHARACTER
*>
fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return dst[:0];
char* dst_ptr = dst;
@@ -103,12 +101,12 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
{
if (src.len == 0)
{
if (padding > 0) return DecodingFailure.INVALID_PADDING?;
if (padding > 0) return encoding::INVALID_PADDING?;
break;
}
if (src[0] == padding) break;
buf[i] = alphabet.reverse[src[0]];
if (buf[i] == INVALID) return DecodingFailure.INVALID_CHARACTER?;
if (buf[i] == INVALID) return encoding::INVALID_CHARACTER?;
src = src[1..];
}
@@ -152,7 +150,7 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
dst[0] = buf[1] >> 2 | buf[0] << 3;
n++;
default:
return DecodingFailure.INVALID_CHARACTER?;
return encoding::INVALID_CHARACTER?;
}
if (dst.len < 5) break;
dst = dst[5..];
@@ -162,12 +160,12 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
<*
Encode the content of src into dst, which must be properly sized.
@param [in] src "The input to be encoded."
@param [inout] dst "The encoded input."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@require dst.len >= encode_len(src.len, padding) "Destination buffer too small"
@param [in] src : "The input to be encoded."
@param [inout] dst : "The encoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@require dst.len >= encode_len(src.len, padding) : "Destination buffer too small"
@return "The encoded size."
*>
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
@@ -244,138 +242,7 @@ const char INVALID @private = 0xff;
const int STD_PADDING = '=';
const int NO_PADDING = -1;
fault Base32Error
{
DUPLICATE_IN_ALPHABET,
PADDING_IN_ALPHABET,
INVALID_CHARACTER_IN_ALPHABET,
DESTINATION_TOO_SMALL,
INVALID_PADDING,
CORRUPT_INPUT
}
struct Base32Encoder @deprecated
{
Base32Alphabet alphabet;
char padding;
}
<*
@param encoder "The 32-character alphabet for encoding."
@param padding "Set to a negative value to disable padding."
@require padding < 256
*>
fn void! Base32Encoder.init(&self, Alphabet encoder = STD_ALPHABET, int padding = STD_PADDING)
{
encoder.validate(padding)!;
*self = { .alphabet = { .encoding = (char[32])encoder }, .padding = padding < 0 ? (char)0 : (char)padding};
}
<*
Calculate the length in bytes of the encoded data.
@param n "Length in bytes on input."
@return "Length in bytes of the encoded data."
*>
fn usz Base32Encoder.encode_len(&self, usz n)
{
return encode_len(n, self.padding);
}
<*
Encode the content of src into dst, which must be properly sized.
@param [in] src "The input to be encoded."
@param [inout] dst "The encoded input."
@return "The encoded size."
@return! Base32Error.DESTINATION_TOO_SMALL
*>
fn usz! Base32Encoder.encode(&self, char[] src, char[] dst)
{
usz dn = self.encode_len(src.len);
if (dst.len < dn) return Base32Error.DESTINATION_TOO_SMALL?;
return encode_buffer(src, dst, self.padding, &self.alphabet).len;
}
struct Base32Decoder @deprecated
{
Base32Alphabet alphabet;
char padding;
}
<*
@param decoder "The alphabet used for decoding."
@param padding "Set to a negative value to disable padding."
@require padding < 256
*>
fn void! Base32Decoder.init(&self, Alphabet decoder = STD_ALPHABET, int padding = STD_PADDING)
{
decoder.validate(padding)!;
*self = { .alphabet = { .encoding = (char[32])decoder }, .padding = padding < 0 ? (char)0 : (char)padding };
self.alphabet.reverse[..] = INVALID;
foreach (char i, c : decoder)
{
self.alphabet.reverse[c] = i;
}
}
<*
Calculate the length in bytes of the decoded data.
@param n "Length in bytes of input."
@return "Length in bytes of the decoded data."
*>
fn usz Base32Decoder.decode_len(&self, usz n)
{
return decode_len(n, self.padding);
}
<*
Decode the content of src into dst, which must be properly sized.
@param src "The input to be decoded."
@param dst "The decoded input."
@return "The decoded size."
@return! Base32Error.DESTINATION_TOO_SMALL, Base32Error.CORRUPT_INPUT
*>
fn usz! Base32Decoder.decode(&self, char[] src, char[] dst)
{
if (src.len == 0) return 0;
usz dn = self.decode_len(src.len);
if (dst.len < dn) return Base32Error.DESTINATION_TOO_SMALL?;
return decode_buffer(src, dst, self.padding, &self.alphabet).len;
}
// Validate the 32-character alphabet to make sure that no character occurs
// twice and that the padding is not present in the alphabet.
fn void! Alphabet.validate(&self, int padding)
{
bool[256] checked;
foreach (c : self)
{
if (checked[c])
{
return Base32Error.DUPLICATE_IN_ALPHABET?;
}
checked[c] = true;
if (c == '\r' || c == '\n')
{
return Base32Error.INVALID_CHARACTER_IN_ALPHABET?;
}
}
if (padding >= 0)
{
char pad = (char)padding;
if (pad == '\r' || pad == '\n')
{
return Base32Error.INVALID_PADDING?;
}
if (checked[pad])
{
return Base32Error.PADDING_IN_ALPHABET?;
}
}
}
distinct Alphabet = char[32];
typedef Alphabet = char[32];
// Standard base32 Alphabet
const Alphabet STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
// Extended Hex Alphabet

View File

@@ -43,29 +43,27 @@ const Base64Alphabet URL = {
const STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
fn String encode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
fn String encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding));
return encode_buffer(src, dst, padding, alphabet);
}
fn char[]! decode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
fn char[]? decode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding))!;
return decode_buffer(src, dst, padding, alphabet);
}
fn String encode_new(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::heap(), padding, alphabet);
fn String encode_temp(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::temp(), padding, alphabet);
fn char[]! decode_new(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::heap(), padding, alphabet);
fn char[]! decode_temp(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::temp(), padding, alphabet);
fn String tencode(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(tmem, code, padding, alphabet);
fn char[]? tdecode(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(tmem, code, padding, alphabet);
<*
Calculate the size of the encoded data.
@param n "Size of the input to be encoded."
@param padding "The padding character or 0 if none"
@require padding < 0xFF "Invalid padding character"
@param n : "Size of the input to be encoded."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@return "The size of the input once encoded."
*>
fn usz encode_len(usz n, char padding)
@@ -77,35 +75,34 @@ fn usz encode_len(usz n, char padding)
<*
Calculate the size of the decoded data.
@param n "Size of the input to be decoded."
@param padding "The padding character or 0 if none"
@require padding < 0xFF "Invalid padding character"
@param n : "Size of the input to be decoded."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@return "The size of the input once decoded."
@return! DecodingFailure.INVALID_PADDING
@return? encoding::INVALID_PADDING
*>
fn usz! decode_len(usz n, char padding)
fn usz? decode_len(usz n, char padding)
{
usz dn = n / 4 * 3;
usz trailing = n % 4;
if (padding)
{
if (trailing != 0) return DecodingFailure.INVALID_PADDING?;
if (trailing != 0) return encoding::INVALID_PADDING?;
// source size is multiple of 4
return dn;
}
if (trailing == 1) return DecodingFailure.INVALID_PADDING?;
if (trailing == 1) return encoding::INVALID_PADDING?;
return dn + trailing * 3 / 4;
}
<*
Encode the content of src into dst, which must be properly sized.
@param src "The input to be encoded."
@param dst "The encoded input."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@param src : "The input to be encoded."
@param dst : "The encoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@return "The encoded size."
@return! Base64Error.DESTINATION_TOO_SMALL
*>
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
@@ -159,16 +156,16 @@ fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base
<*
Decode the content of src into dst, which must be properly sized.
@param src "The input to be decoded."
@param dst "The decoded input."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require (decode_len(src.len, padding) ?? 0) <= dst.len "Destination buffer too small"
@require padding < 0xFF "Invalid padding character"
@param src : "The input to be decoded."
@param dst : "The decoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require (decode_len(src.len, padding) ?? 0) <= dst.len : "Destination buffer too small"
@require padding < 0xFF : "Invalid padding character"
@return "The decoded data."
@return! DecodingFailure
@return? encoding::INVALID_CHARACTER, encoding::INVALID_PADDING
*>
fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return dst[:0];
usz dn = decode_len(src.len, padding)!;
@@ -199,7 +196,7 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
case c1:
case c2:
case c3:
return DecodingFailure.INVALID_CHARACTER?;
return encoding::INVALID_CHARACTER?;
}
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6 | (uint)c3;
dst[0] = (char)(group >> 16);
@@ -214,7 +211,7 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
src = src[^trailing..];
char c0 = alphabet.reverse[src[0]];
char c1 = alphabet.reverse[src[1]];
if (c0 == 0xFF || c1 == 0xFF) return DecodingFailure.INVALID_PADDING?;
if (c0 == 0xFF || c1 == 0xFF) return encoding::INVALID_PADDING?;
if (!padding)
{
switch (src.len)
@@ -224,7 +221,7 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
dst[0] = (char)(group >> 16);
case 3:
char c2 = alphabet.reverse[src[2]];
if (c2 == 0xFF) return DecodingFailure.INVALID_CHARACTER?;
if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
@@ -238,13 +235,13 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
switch (padding)
{
case src[2]:
if (src[3] != padding) return DecodingFailure.INVALID_PADDING?;
if (src[3] != padding) return encoding::INVALID_PADDING?;
uint group = (uint)c0 << 18 | (uint)c1 << 12;
dst[0] = (char)(group >> 16);
dn -= 2;
case src[3]:
char c2 = alphabet.reverse[src[2]];
if (c2 == 0xFF) return DecodingFailure.INVALID_CHARACTER?;
if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
@@ -256,146 +253,3 @@ fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
const MASK @private = 0b111111;
struct Base64Encoder @deprecated
{
char padding;
String alphabet;
}
fault Base64Error
{
DUPLICATE_IN_ALPHABET,
PADDING_IN_ALPHABET,
DESTINATION_TOO_SMALL,
INVALID_PADDING,
INVALID_CHARACTER,
}
<*
@param alphabet "The alphabet used for encoding."
@param padding "Set to a negative value to disable padding."
@require alphabet.len == 64
@require padding < 256
@return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
*>
fn Base64Encoder*! Base64Encoder.init(&self, String alphabet, int padding = '=')
{
check_alphabet(alphabet, padding)!;
*self = { .padding = padding < 0 ? 0 : (char)padding, .alphabet = alphabet };
return self;
}
<*
Calculate the size of the encoded data.
@param n "Size of the input to be encoded."
@return "The size of the input once encoded."
*>
fn usz Base64Encoder.encode_len(&self, usz n)
{
return encode_len(n, self.padding);
}
<*
Encode the content of src into dst, which must be properly sized.
@param src "The input to be encoded."
@param dst "The encoded input."
@return "The encoded size."
@return! Base64Error.DESTINATION_TOO_SMALL
*>
fn usz! Base64Encoder.encode(&self, char[] src, char[] dst)
{
if (src.len == 0) return 0;
usz dn = self.encode_len(src.len);
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
Base64Alphabet a = { .encoding = self.alphabet[:64] };
return encode_buffer(src, dst, self.padding, &a).len;
}
struct Base64Decoder @deprecated
{
char padding;
Base64Alphabet encoding;
bool init_done;
}
import std;
<*
@param alphabet "The alphabet used for encoding."
@param padding "Set to a negative value to disable padding."
@require alphabet.len == 64
@require padding < 256
@return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
*>
fn void! Base64Decoder.init(&self, String alphabet, int padding = '=')
{
self.init_done = true;
check_alphabet(alphabet, padding)!;
*self = { .padding = padding < 0 ? 0 : (char)padding, .encoding.encoding = alphabet[:64] };
self.encoding.reverse[..] = 0xFF;
foreach (i, c : alphabet)
{
self.encoding.reverse[c] = (char)i;
}
}
<*
Calculate the size of the decoded data.
@param n "Size of the input to be decoded."
@return "The size of the input once decoded."
@return! Base64Error.INVALID_PADDING
*>
fn usz! Base64Decoder.decode_len(&self, usz n)
{
return decode_len(n, self.padding) ?? Base64Error.INVALID_PADDING?;
}
<*
Decode the content of src into dst, which must be properly sized.
@param src "The input to be decoded."
@param dst "The decoded input."
@return "The decoded size."
@return! Base64Error.DESTINATION_TOO_SMALL, Base64Error.INVALID_PADDING, Base64Error.INVALID_CHARACTER
*>
fn usz! Base64Decoder.decode(&self, char[] src, char[] dst)
{
if (src.len == 0) return 0;
usz dn = self.decode_len(src.len)!;
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
char[]! decoded = decode_buffer(src, dst, self.padding, &self.encoding);
if (catch err = decoded)
{
case DecodingFailure.INVALID_PADDING:
return Base64Error.INVALID_PADDING?;
case DecodingFailure.INVALID_CHARACTER:
return Base64Error.INVALID_CHARACTER?;
default:
return err?;
}
return decoded.len;
}
// Make sure that all bytes in the alphabet are unique and
// the padding is not present in the alphabet.
fn void! check_alphabet(String alphabet, int padding) @local
{
bool[256] checked;
if (padding < 0)
{
foreach (c : alphabet)
{
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
checked[c] = true;
}
return;
}
char pad = (char)padding;
foreach (c : alphabet)
{
if (c == pad) return Base64Error.PADDING_IN_ALPHABET?;
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
checked[c] = true;
}
}

View File

@@ -15,7 +15,7 @@ struct CsvRow (Printable)
Allocator allocator;
}
fn usz! CsvRow.to_format(&self, Formatter* f) @dynamic
fn usz? CsvRow.to_format(&self, Formatter* f) @dynamic
{
return f.printf("%s", self.list);
}
@@ -38,30 +38,24 @@ fn void CsvReader.init(&self, InStream stream, String separator = ",")
self.stream = stream;
self.separator = separator;
}
fn CsvRow! CsvReader.read_new_row(self)
{
return self.read_row(allocator::heap()) @inline;
}
<*
@param [&inout] allocator
*>
fn CsvRow! CsvReader.read_row(self, Allocator allocator)
fn CsvRow? CsvReader.read_row(self, Allocator allocator)
{
String row = io::readline(self.stream, allocator: allocator)!;
String row = io::readline(allocator, self.stream)!;
defer catch allocator::free(allocator, row);
String[] list = row.split(self.separator, allocator: allocator);
String[] list = row.split(allocator, self.separator);
return { list, row, allocator };
}
fn CsvRow! CsvReader.read_temp_row(self)
fn CsvRow? CsvReader.tread_row(self)
{
return self.read_row(allocator::temp()) @inline;
return self.read_row(tmem) @inline;
}
<*
@require self.allocator `Row already freed`
@require self.allocator != null : `Row already freed`
*>
fn void CsvRow.free(&self)
{
@@ -70,29 +64,32 @@ fn void CsvRow.free(&self)
self.allocator = null;
}
fn void! CsvReader.skip_row(self) @maydiscard
fn void? CsvReader.skip_row(self) @maydiscard => @pool()
{
@pool()
{
(void)io::treadline(self.stream);
};
(void)io::treadline(self.stream);
}
macro CsvReader.@each_row(self, int rows = int.max; @body(String[] row))
macro void? @each_row(InStream stream, String separator = ",", int max_rows = int.max; @body(String[] row)) @maydiscard
{
InStream stream = self.stream;
String sep = self.separator;
while (rows--)
while (max_rows--)
{
@stack_mem(512; Allocator mem)
@stack_mem(512; mem)
{
String! s = io::readline(stream, mem);
String? s = io::readline(mem, stream);
if (catch err = s)
{
if (err == IoError.EOF) return;
if (err == io::EOF) return;
return err?;
}
@body(s.split(sep, allocator: mem));
@body(s.split(mem, separator));
};
}
}
macro void? CsvReader.@each_row(self, int rows = int.max; @body(String[] row)) @maydiscard
{
return @each_row(self.stream, self.separator, rows; row)
{
@body(row);
};
}

View File

@@ -1,7 +1,3 @@
module std::encoding;
fault DecodingFailure
{
INVALID_CHARACTER,
INVALID_PADDING,
}
faultdef INVALID_CHARACTER, INVALID_PADDING;

View File

@@ -8,41 +8,40 @@ fn String encode_buffer(char[] code, char[] buffer)
return (String)buffer[:encode_bytes(code, buffer)];
}
fn char[]! decode_buffer(char[] code, char[] buffer)
fn char[]? decode_buffer(char[] code, char[] buffer)
{
return buffer[:decode_bytes(code, buffer)!];
}
fn String encode(char[] code, Allocator allocator)
fn String encode(Allocator allocator, char[] code)
{
char[] data = allocator::alloc_array(allocator, char, encode_len(code.len));
return (String)data[:encode_bytes(code, data)];
}
fn char[]! decode(char[] code, Allocator allocator)
fn char[]? decode(Allocator allocator, char[] code)
{
char[] data = allocator::alloc_array(allocator, char, decode_len(code.len));
return data[:decode_bytes(code, data)!];
}
fn String encode_new(char[] code) @inline => encode(code, allocator::heap());
fn String encode_temp(char[] code) @inline => encode(code, allocator::temp());
fn char[]! decode_new(char[] code) @inline => decode(code, allocator::heap());
fn char[]! decode_temp(char[] code) @inline => decode(code, allocator::temp());
fn String tencode(char[] code) @inline => encode(tmem, code);
fn char[]? tdecode(char[] code) @inline => decode(tmem, code);
<*
Calculate the size of the encoded data.
@param n "Size of the input to be encoded."
@param n : "Size of the input to be encoded."
@return "The size of the input once encoded."
*>
fn usz encode_len(usz n) => n * 2;
<*
Encode the content of src into dst, which must be properly sized.
@param src "The input to be encoded."
@param dst "The encoded input."
@param src : "The input to be encoded."
@param dst : "The encoded input."
@return "The encoded size."
@require dst.len >= encode_len(src.len) "Destination array is not large enough"
@require dst.len >= encode_len(src.len) : "Destination array is not large enough"
*>
fn usz encode_bytes(char[] src, char[] dst)
{
@@ -58,7 +57,7 @@ fn usz encode_bytes(char[] src, char[] dst)
<*
Calculate the size of the decoded data.
@param n "Size of the input to be decoded."
@param n : "Size of the input to be decoded."
@return "The size of the input once decoded."
*>
macro usz decode_len(usz n) => n / 2;
@@ -69,20 +68,20 @@ macro usz decode_len(usz n) => n / 2;
Expects that src only contains hexadecimal characters and that src has even
length.
@param src "The input to be decoded."
@param dst "The decoded input."
@require src.len % 2 == 0 "src is not of even length"
@require dst.len >= decode_len(src.len) "Destination array is not large enough"
@return! DecodingFailure.INVALID_CHARACTER
@param src : "The input to be decoded."
@param dst : "The decoded input."
@require src.len % 2 == 0 : "src is not of even length"
@require dst.len >= decode_len(src.len) : "Destination array is not large enough"
@return? encoding::INVALID_CHARACTER
*>
fn usz! decode_bytes(char[] src, char[] dst)
fn usz? decode_bytes(char[] src, char[] dst)
{
usz i;
for (usz j = 1; j < src.len; j += 2)
{
char a = HEXREVERSE[src[j - 1]];
char b = HEXREVERSE[src[j]];
if (a > 0x0f || b > 0x0f) return DecodingFailure.INVALID_CHARACTER?;
if (a > 0x0f || b > 0x0f) return encoding::INVALID_CHARACTER?;
dst[i] = (a << 4) | b;
i++;
}

View File

@@ -6,40 +6,33 @@ import std::io;
import std::ascii;
import std::collections::object;
fault JsonParsingError
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, DUPLICATE_MEMBERS, INVALID_NUMBER;
fn Object*? parse_string(Allocator allocator, String s)
{
EOF,
UNEXPECTED_CHARACTER,
INVALID_ESCAPE_SEQUENCE,
DUPLICATE_MEMBERS,
INVALID_NUMBER,
return parse(allocator, (ByteReader){}.init(s));
}
fn Object*! parse_string(String s, Allocator allocator = allocator::heap())
fn Object*? tparse_string(String s)
{
return parse(ByteReader{}.init(s), allocator);
return parse(tmem, (ByteReader){}.init(s));
}
fn Object*! temp_parse_string(String s)
fn Object*? parse(Allocator allocator, InStream s)
{
return parse(ByteReader{}.init(s), allocator::temp());
}
fn Object*! parse(InStream s, Allocator allocator = allocator::heap())
{
@stack_mem(512; Allocator mem)
@stack_mem(512; Allocator smem)
{
JsonContext context = { .last_string = dstring::new_with_capacity(64, mem), .stream = s, .allocator = allocator };
@pool(allocator)
JsonContext context = { .last_string = dstring::new_with_capacity(smem, 64), .stream = s, .allocator = allocator };
@pool()
{
return parse_any(&context);
};
};
}
fn Object*! temp_parse(InStream s)
fn Object*? tparse(InStream s)
{
return parse(s, allocator::temp());
return parse(tmem, s);
}
// -- Implementation follows --
@@ -78,7 +71,7 @@ struct JsonContext @local
}
fn Object*! parse_from_token(JsonContext* context, JsonTokenType token) @local
fn Object*? parse_from_token(JsonContext* context, JsonTokenType token) @local
{
switch (token)
{
@@ -88,25 +81,25 @@ fn Object*! parse_from_token(JsonContext* context, JsonTokenType token) @local
case COMMA:
case RBRACE:
case RBRACKET:
case COLON: return JsonParsingError.UNEXPECTED_CHARACTER?;
case COLON: return UNEXPECTED_CHARACTER?;
case STRING: return object::new_string(context.last_string.str_view(), context.allocator);
case NUMBER: return object::new_float(context.last_number, context.allocator);
case TRUE: return object::new_bool(true);
case FALSE: return object::new_bool(false);
case NULL: return object::new_null();
case EOF: return JsonParsingError.EOF?;
case EOF: return io::EOF?;
}
}
fn Object*! parse_any(JsonContext* context) @local
fn Object*? parse_any(JsonContext* context) @local
{
return parse_from_token(context, advance(context));
}
fn JsonTokenType! lex_number(JsonContext *context, char c) @local
fn JsonTokenType? lex_number(JsonContext *context, char c) @local
{
@stack_mem(256; Allocator mem)
{
DString t = dstring::new_with_capacity(32, allocator: mem);
DString t = dstring::new_with_capacity(mem, 32);
bool negate = c == '-';
if (negate)
{
@@ -137,7 +130,7 @@ fn JsonTokenType! lex_number(JsonContext *context, char c) @local
t.append(c);
c = read_next(context)!;
}
if (!c.is_digit()) return JsonParsingError.INVALID_NUMBER?;
if (!c.is_digit()) return INVALID_NUMBER?;
while (c.is_digit())
{
t.append(c);
@@ -145,13 +138,13 @@ fn JsonTokenType! lex_number(JsonContext *context, char c) @local
}
}
pushback(context, c);
double! d = t.str_view().to_double() ?? JsonParsingError.INVALID_NUMBER?;
double? d = t.str_view().to_double() ?? INVALID_NUMBER?;
context.last_number = d!;
return NUMBER;
};
}
fn Object*! parse_map(JsonContext* context) @local
fn Object*? parse_map(JsonContext* context) @local
{
Object* map = object::new_obj(context.allocator);
defer catch map.free();
@@ -159,12 +152,12 @@ fn Object*! parse_map(JsonContext* context) @local
@stack_mem(256; Allocator mem)
{
DString temp_key = dstring::new_with_capacity(32, mem);
DString temp_key = dstring::new_with_capacity(mem, 32);
while (token != JsonTokenType.RBRACE)
{
if (token != JsonTokenType.STRING) return JsonParsingError.UNEXPECTED_CHARACTER?;
if (token != JsonTokenType.STRING) return UNEXPECTED_CHARACTER?;
DString string = context.last_string;
if (map.has_key(string.str_view())) return JsonParsingError.DUPLICATE_MEMBERS?;
if (map.has_key(string.str_view())) return DUPLICATE_MEMBERS?;
// Copy the key to our temp holder, since our
// last_string may be used in parse_any
temp_key.clear();
@@ -178,13 +171,13 @@ fn Object*! parse_map(JsonContext* context) @local
token = advance(context)!;
continue;
}
if (token != JsonTokenType.RBRACE) return JsonParsingError.UNEXPECTED_CHARACTER?;
if (token != JsonTokenType.RBRACE) return UNEXPECTED_CHARACTER?;
}
return map;
};
}
fn Object*! parse_array(JsonContext* context) @local
fn Object*? parse_array(JsonContext* context) @local
{
Object* list = object::new_obj(context.allocator);
defer catch list.free();
@@ -199,7 +192,7 @@ fn Object*! parse_array(JsonContext* context) @local
token = advance(context)!;
continue;
}
if (token != JsonTokenType.RBRACKET) return JsonParsingError.UNEXPECTED_CHARACTER?;
if (token != JsonTokenType.RBRACKET) return UNEXPECTED_CHARACTER?;
}
return list;
}
@@ -214,7 +207,7 @@ fn void pushback(JsonContext* context, char c) @local
}
}
fn char! read_next(JsonContext* context) @local
fn char? read_next(JsonContext* context) @local
{
if (context.reached_end) return '\0';
if (context.pushed_back)
@@ -222,14 +215,15 @@ fn char! read_next(JsonContext* context) @local
context.pushed_back = false;
return context.current;
}
char! c = context.stream.read_byte();
char? c = context.stream.read_byte();
if (catch err = c)
{
case IoError.EOF:
if (err == io::EOF)
{
context.reached_end = true;
return '\0';
default:
return err?;
}
return err?;
}
if (c == 0)
{
@@ -238,7 +232,7 @@ fn char! read_next(JsonContext* context) @local
return c;
}
fn JsonTokenType! advance(JsonContext* context) @local
fn JsonTokenType? advance(JsonContext* context) @local
{
char c;
// Skip whitespace
@@ -286,7 +280,7 @@ fn JsonTokenType! advance(JsonContext* context) @local
switch (c)
{
case '\0':
return IoError.EOF?;
return io::EOF?;
case '{':
return LBRACE;
case '}':
@@ -314,25 +308,25 @@ fn JsonTokenType! advance(JsonContext* context) @local
match(context, "ull")!;
return NULL;
default:
return JsonParsingError.UNEXPECTED_CHARACTER?;
return UNEXPECTED_CHARACTER?;
}
}
fn void! match(JsonContext* context, String str) @local
fn void? match(JsonContext* context, String str) @local
{
foreach (c : str)
{
char l = read_next(context)!;
if (l != c) return JsonParsingError.UNEXPECTED_CHARACTER?;
if (l != c) return UNEXPECTED_CHARACTER?;
}
}
fn void! parse_expected(JsonContext* context, JsonTokenType token) @local
fn void? parse_expected(JsonContext* context, JsonTokenType token) @local
{
if (advance(context)! != token) return JsonParsingError.UNEXPECTED_CHARACTER?;
if (advance(context)! != token) return UNEXPECTED_CHARACTER?;
}
fn JsonTokenType! lex_string(JsonContext* context)
fn JsonTokenType? lex_string(JsonContext* context)
{
context.last_string.clear();
while LOOP: (true)
@@ -341,9 +335,9 @@ fn JsonTokenType! lex_string(JsonContext* context)
switch (c)
{
case '\0':
return JsonParsingError.EOF?;
return io::EOF?;
case 1..31:
return JsonParsingError.UNEXPECTED_CHARACTER?;
return UNEXPECTED_CHARACTER?;
case '"':
break LOOP;
case '\\':
@@ -356,9 +350,9 @@ fn JsonTokenType! lex_string(JsonContext* context)
switch (c)
{
case '\0':
return JsonParsingError.EOF?;
return io::EOF?;
case 1..31:
return JsonParsingError.UNEXPECTED_CHARACTER?;
return UNEXPECTED_CHARACTER?;
case '"':
case '\\':
case '/':
@@ -378,13 +372,13 @@ fn JsonTokenType! lex_string(JsonContext* context)
for (int i = 0; i < 4; i++)
{
c = read_next(context)!;
if (!c.is_xdigit()) return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
if (!c.is_xdigit()) return INVALID_ESCAPE_SEQUENCE?;
val = val << 4 + (c > '9' ? (c | 32) - 'a' + 10 : c - '0');
}
context.last_string.append_char32(val);
continue;
default:
return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
return INVALID_ESCAPE_SEQUENCE?;
}
context.last_string.append(c);
}

View File

@@ -0,0 +1,94 @@
module std::experimental::scheduler{Event};
import std::collections, std::thread, std::time;
struct DelayedSchedulerEvent @local
{
inline Event event;
Clock execution_time;
}
fn int DelayedSchedulerEvent.compare_to(self, DelayedSchedulerEvent other) @local
{
switch
{
case self.execution_time < other.execution_time: return -1;
case self.execution_time > other.execution_time: return 1;
default: return 0;
}
}
struct FrameScheduler
{
PriorityQueue{DelayedSchedulerEvent} delayed_events;
List{Event} events;
List{Event} pending_events;
bool pending;
Mutex mtx;
}
fn void FrameScheduler.init(&self)
{
self.events.init(mem);
self.pending_events.init(mem);
self.delayed_events.init(mem);
(void)self.mtx.init();
bool pending;
}
macro void FrameScheduler.@destroy(&self; @destruct(Event e))
{
foreach (e : self.events) @destruct(e);
foreach (e : self.pending_events) @destruct(e);
foreach (e : self.delayed_events.heap) @destruct(e.event);
self.events.free();
self.pending_events.free();
self.delayed_events.free();
(void)self.mtx.destroy();
}
fn void FrameScheduler.queue_delayed_event(&self, Event event, Duration delay)
{
self.mtx.@in_lock()
{
self.delayed_events.push({ event, clock::now().add_duration(delay)});
@atomic_store(self.pending, true);
};
}
fn bool FrameScheduler.has_delayed(&self)
{
self.mtx.@in_lock()
{
return @ok(self.delayed_events.first());
};
}
fn void FrameScheduler.queue_event(&self, Event event)
{
self.mtx.@in_lock()
{
self.pending_events.push(event);
@atomic_store(self.pending, true);
};
}
fn Event? FrameScheduler.pop_event(&self)
{
while (true)
{
if (try event = self.events.pop()) return event;
if (!@atomic_load(self.pending)) return NO_MORE_ELEMENT?;
self.mtx.@in_lock()
{
self.events.add_all(&self.pending_events);
self.pending_events.clear();
Clock c = clock::now();
while (try top = self.delayed_events.first())
{
if (top.execution_time > c) break;
self.events.push(self.delayed_events.pop()!!);
}
@atomic_store(self.pending, self.delayed_events.len() > 0);
if (!self.events.len()) return NO_MORE_ELEMENT?;
};
}
}

View File

@@ -40,7 +40,7 @@ fn uint Adler32.final(&self)
return (self.b << 16) | self.a;
}
fn uint encode(char[] data)
fn uint hash(char[] data)
{
uint a = 1;
uint b = 0;

View File

@@ -33,7 +33,7 @@ fn uint Crc32.final(&self)
return ~self.result;
}
fn uint encode(char[] data)
fn uint hash(char[] data)
{
uint result = ~(uint)(0);
foreach (char x : data)

View File

@@ -33,7 +33,7 @@ fn ulong Crc64.final(&self)
return self.result;
}
fn ulong encode(char[] data)
fn ulong hash(char[] data)
{
ulong result = (ulong)(0);
foreach (char x : data)

View File

@@ -3,12 +3,12 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::hash::fnv32a;
distinct Fnv32a = uint;
typedef Fnv32a = uint;
const FNV32A_START @private = 0x811c9dc5;
const FNV32A_MUL @private = 0x01000193;
macro void @update(&h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV32A_MUL;
macro void update(h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV32A_MUL;
fn void Fnv32a.init(&self)
{
@@ -20,22 +20,22 @@ fn void Fnv32a.update(&self, char[] data)
Fnv32a h = *self;
foreach (char x : data)
{
@update(h, x);
update(&h, x);
}
*self = h;
}
macro void Fnv32a.update_char(&self, char c)
{
@update(*self, c);
update(self, c);
}
fn uint encode(char[] data)
fn uint hash(char[] data)
{
uint h = FNV32A_START;
foreach (char x : data)
{
@update(h, x);
update(&h, x);
}
return h;
}

View File

@@ -3,12 +3,12 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::hash::fnv64a;
distinct Fnv64a = ulong;
typedef Fnv64a = ulong;
const FNV64A_START @private = 0xcbf29ce484222325;
const FNV64A_MUL @private = 0x00000100000001b3;
macro void @update(&h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV64A_MUL;
macro void update(h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV64A_MUL;
fn void Fnv64a.init(&self)
{
@@ -20,22 +20,22 @@ fn void Fnv64a.update(&self, char[] data)
Fnv64a h = *self;
foreach (char x : data)
{
@update(h, x);
update(&h, x);
}
*self = h;
}
macro void Fnv64a.update_char(&self, char c)
{
@update(*self, c);
update(self, c);
}
fn ulong encode(char[] data)
fn ulong hash(char[] data)
{
ulong h = FNV64A_START;
foreach (char x : data)
{
@update(h, x);
update(&h, x);
}
return h;
}

View File

@@ -1,4 +1,4 @@
module std::hash::hmac(<HashAlg, HASH_BYTES, BLOCK_BYTES>);
module std::hash::hmac{HashAlg, HASH_BYTES, BLOCK_BYTES};
import std::crypto;
struct Hmac
@@ -15,8 +15,8 @@ fn char[HASH_BYTES] hash(char[] key, char[] message)
}
<*
@require output.len > 0 "Output must be greater than zero"
@require output.len < int.max / HASH_BYTES "Output is too large"
@require output.len > 0 : "Output must be greater than zero"
@require output.len < int.max / HASH_BYTES : "Output is too large"
*>
fn void pbkdf2(char[] pw, char[] salt, uint iterations, char[] output)
{

View File

@@ -13,9 +13,9 @@ struct Md5
uint[16] block;
}
def HmacMd5 = Hmac(<Md5, HASH_BYTES, BLOCK_BYTES>);
def hmac = hmac::hash(<Md5, HASH_BYTES, BLOCK_BYTES>);
def pbkdf2 = hmac::pbkdf2(<Md5, HASH_BYTES, BLOCK_BYTES>);
alias HmacMd5 = Hmac{Md5, HASH_BYTES, BLOCK_BYTES};
alias hmac = hmac::hash{Md5, HASH_BYTES, BLOCK_BYTES};
alias pbkdf2 = hmac::pbkdf2{Md5, HASH_BYTES, BLOCK_BYTES};
fn char[HASH_BYTES] hash(char[] data)
{
@@ -106,9 +106,9 @@ macro @h(x, y, z) => (x ^ y) ^ z;
macro @h2(x, y, z) => x ^ (y ^ z);
macro @i(x, y, z) => y ^ (x | ~z);
macro @step(#f, &a, b, c, d, ptr, n, t, s)
macro @step(#f, a, b, c, d, ptr, n, t, s)
{
*a += #f(b, c, d) + *(uint *)&ptr[n * 4] + t;
*a += #f(b, c, d) + @unaligned_load(*(uint *)&ptr[n * 4], 2) + t;
*a = (*a << s) | ((*a & 0xffffffff) >> (32 - s));
*a += b;
}
@@ -133,76 +133,76 @@ fn char* body(Md5* ctx, void* data, usz size)
saved_d = d;
/* Round 1 */
@step(@f, a, b, c, d, ptr, 0, 0xd76aa478, 7) ;
@step(@f, d, a, b, c, ptr, 1, 0xe8c7b756, 12) ;
@step(@f, c, d, a, b, ptr, 2, 0x242070db, 17) ;
@step(@f, b, c, d, a, ptr, 3, 0xc1bdceee, 22) ;
@step(@f, a, b, c, d, ptr, 4, 0xf57c0faf, 7) ;
@step(@f, d, a, b, c, ptr, 5, 0x4787c62a, 12) ;
@step(@f, c, d, a, b, ptr, 6, 0xa8304613, 17) ;
@step(@f, b, c, d, a, ptr, 7, 0xfd469501, 22) ;
@step(@f, a, b, c, d, ptr, 8, 0x698098d8, 7) ;
@step(@f, d, a, b, c, ptr, 9, 0x8b44f7af, 12) ;
@step(@f, c, d, a, b, ptr, 10, 0xffff5bb1, 17);
@step(@f, b, c, d, a, ptr, 11, 0x895cd7be, 22);
@step(@f, a, b, c, d, ptr, 12, 0x6b901122, 7) ;
@step(@f, d, a, b, c, ptr, 13, 0xfd987193, 12);
@step(@f, c, d, a, b, ptr, 14, 0xa679438e, 17);
@step(@f, b, c, d, a, ptr, 15, 0x49b40821, 22);
@step(@f, &a, b, c, d, ptr, 0, 0xd76aa478, 7) ;
@step(@f, &d, a, b, c, ptr, 1, 0xe8c7b756, 12) ;
@step(@f, &c, d, a, b, ptr, 2, 0x242070db, 17) ;
@step(@f, &b, c, d, a, ptr, 3, 0xc1bdceee, 22) ;
@step(@f, &a, b, c, d, ptr, 4, 0xf57c0faf, 7) ;
@step(@f, &d, a, b, c, ptr, 5, 0x4787c62a, 12) ;
@step(@f, &c, d, a, b, ptr, 6, 0xa8304613, 17) ;
@step(@f, &b, c, d, a, ptr, 7, 0xfd469501, 22) ;
@step(@f, &a, b, c, d, ptr, 8, 0x698098d8, 7) ;
@step(@f, &d, a, b, c, ptr, 9, 0x8b44f7af, 12) ;
@step(@f, &c, d, a, b, ptr, 10, 0xffff5bb1, 17);
@step(@f, &b, c, d, a, ptr, 11, 0x895cd7be, 22);
@step(@f, &a, b, c, d, ptr, 12, 0x6b901122, 7) ;
@step(@f, &d, a, b, c, ptr, 13, 0xfd987193, 12);
@step(@f, &c, d, a, b, ptr, 14, 0xa679438e, 17);
@step(@f, &b, c, d, a, ptr, 15, 0x49b40821, 22);
/* Round 2 */
@step(@g, a, b, c, d, ptr, 1, 0xf61e2562, 5) ;
@step(@g, d, a, b, c, ptr, 6, 0xc040b340, 9) ;
@step(@g, c, d, a, b, ptr, 11, 0x265e5a51, 14);
@step(@g, b, c, d, a, ptr, 0, 0xe9b6c7aa, 20) ;
@step(@g, a, b, c, d, ptr, 5, 0xd62f105d, 5) ;
@step(@g, d, a, b, c, ptr, 10, 0x02441453, 9) ;
@step(@g, c, d, a, b, ptr, 15, 0xd8a1e681, 14);
@step(@g, b, c, d, a, ptr, 4, 0xe7d3fbc8, 20) ;
@step(@g, a, b, c, d, ptr, 9, 0x21e1cde6, 5) ;
@step(@g, d, a, b, c, ptr, 14, 0xc33707d6, 9) ;
@step(@g, c, d, a, b, ptr, 3, 0xf4d50d87, 14) ;
@step(@g, b, c, d, a, ptr, 8, 0x455a14ed, 20) ;
@step(@g, a, b, c, d, ptr, 13, 0xa9e3e905, 5) ;
@step(@g, d, a, b, c, ptr, 2, 0xfcefa3f8, 9) ;
@step(@g, c, d, a, b, ptr, 7, 0x676f02d9, 14) ;
@step(@g, b, c, d, a, ptr, 12, 0x8d2a4c8a, 20);
@step(@g, &a, b, c, d, ptr, 1, 0xf61e2562, 5) ;
@step(@g, &d, a, b, c, ptr, 6, 0xc040b340, 9) ;
@step(@g, &c, d, a, b, ptr, 11, 0x265e5a51, 14);
@step(@g, &b, c, d, a, ptr, 0, 0xe9b6c7aa, 20) ;
@step(@g, &a, b, c, d, ptr, 5, 0xd62f105d, 5) ;
@step(@g, &d, a, b, c, ptr, 10, 0x02441453, 9) ;
@step(@g, &c, d, a, b, ptr, 15, 0xd8a1e681, 14);
@step(@g, &b, c, d, a, ptr, 4, 0xe7d3fbc8, 20) ;
@step(@g, &a, b, c, d, ptr, 9, 0x21e1cde6, 5) ;
@step(@g, &d, a, b, c, ptr, 14, 0xc33707d6, 9) ;
@step(@g, &c, d, a, b, ptr, 3, 0xf4d50d87, 14) ;
@step(@g, &b, c, d, a, ptr, 8, 0x455a14ed, 20) ;
@step(@g, &a, b, c, d, ptr, 13, 0xa9e3e905, 5) ;
@step(@g, &d, a, b, c, ptr, 2, 0xfcefa3f8, 9) ;
@step(@g, &c, d, a, b, ptr, 7, 0x676f02d9, 14) ;
@step(@g, &b, c, d, a, ptr, 12, 0x8d2a4c8a, 20);
/* Round 3 */
@step(@h, a, b, c, d, ptr, 5, 0xfffa3942, 4);
@step(@h2, d, a, b, c, ptr, 8, 0x8771f681, 11);
@step(@h, c, d, a, b, ptr, 11, 0x6d9d6122, 16);
@step(@h2, b, c, d, a, ptr, 14, 0xfde5380c, 23);
@step(@h, a, b, c, d, ptr, 1, 0xa4beea44, 4);
@step(@h2, d, a, b, c, ptr, 4, 0x4bdecfa9, 11);
@step(@h, c, d, a, b, ptr, 7, 0xf6bb4b60, 16);
@step(@h2, b, c, d, a, ptr, 10, 0xbebfbc70, 23);
@step(@h, a, b, c, d, ptr, 13, 0x289b7ec6, 4) ;
@step(@h2, d, a, b, c, ptr, 0, 0xeaa127fa, 11) ;
@step(@h, c, d, a, b, ptr, 3, 0xd4ef3085, 16) ;
@step(@h2, b, c, d, a, ptr, 6, 0x04881d05, 23) ;
@step(@h, a, b, c, d, ptr, 9, 0xd9d4d039, 4) ;
@step(@h2, d, a, b, c, ptr, 12, 0xe6db99e5, 11) ;
@step(@h, c, d, a, b, ptr, 15, 0x1fa27cf8, 16) ;
@step(@h2, b, c, d, a, ptr, 2, 0xc4ac5665, 23) ;
@step(@h, &a, b, c, d, ptr, 5, 0xfffa3942, 4);
@step(@h2, &d, a, b, c, ptr, 8, 0x8771f681, 11);
@step(@h, &c, d, a, b, ptr, 11, 0x6d9d6122, 16);
@step(@h2, &b, c, d, a, ptr, 14, 0xfde5380c, 23);
@step(@h, &a, b, c, d, ptr, 1, 0xa4beea44, 4);
@step(@h2, &d, a, b, c, ptr, 4, 0x4bdecfa9, 11);
@step(@h, &c, d, a, b, ptr, 7, 0xf6bb4b60, 16);
@step(@h2, &b, c, d, a, ptr, 10, 0xbebfbc70, 23);
@step(@h, &a, b, c, d, ptr, 13, 0x289b7ec6, 4) ;
@step(@h2, &d, a, b, c, ptr, 0, 0xeaa127fa, 11) ;
@step(@h, &c, d, a, b, ptr, 3, 0xd4ef3085, 16) ;
@step(@h2, &b, c, d, a, ptr, 6, 0x04881d05, 23) ;
@step(@h, &a, b, c, d, ptr, 9, 0xd9d4d039, 4) ;
@step(@h2, &d, a, b, c, ptr, 12, 0xe6db99e5, 11) ;
@step(@h, &c, d, a, b, ptr, 15, 0x1fa27cf8, 16) ;
@step(@h2, &b, c, d, a, ptr, 2, 0xc4ac5665, 23) ;
/* Round 4 */
@step(@i, a, b, c, d, ptr, 0, 0xf4292244, 6) ;
@step(@i, d, a, b, c, ptr, 7, 0x432aff97, 10) ;
@step(@i, c, d, a, b, ptr, 14, 0xab9423a7, 15) ;
@step(@i, b, c, d, a, ptr, 5, 0xfc93a039, 21) ;
@step(@i, a, b, c, d, ptr, 12, 0x655b59c3, 6) ;
@step(@i, d, a, b, c, ptr, 3, 0x8f0ccc92, 10) ;
@step(@i, c, d, a, b, ptr, 10, 0xffeff47d, 15) ;
@step(@i, b, c, d, a, ptr, 1, 0x85845dd1, 21) ;
@step(@i, a, b, c, d, ptr, 8, 0x6fa87e4f, 6) ;
@step(@i, d, a, b, c, ptr, 15, 0xfe2ce6e0, 10) ;
@step(@i, c, d, a, b, ptr, 6, 0xa3014314, 15) ;
@step(@i, b, c, d, a, ptr, 13, 0x4e0811a1, 21) ;
@step(@i, a, b, c, d, ptr, 4, 0xf7537e82, 6) ;
@step(@i, d, a, b, c, ptr, 11, 0xbd3af235, 10) ;
@step(@i, c, d, a, b, ptr, 2, 0x2ad7d2bb, 15) ;
@step(@i, b, c, d, a, ptr, 9, 0xeb86d391, 21) ;
@step(@i, &a, b, c, d, ptr, 0, 0xf4292244, 6) ;
@step(@i, &d, a, b, c, ptr, 7, 0x432aff97, 10) ;
@step(@i, &c, d, a, b, ptr, 14, 0xab9423a7, 15) ;
@step(@i, &b, c, d, a, ptr, 5, 0xfc93a039, 21) ;
@step(@i, &a, b, c, d, ptr, 12, 0x655b59c3, 6) ;
@step(@i, &d, a, b, c, ptr, 3, 0x8f0ccc92, 10) ;
@step(@i, &c, d, a, b, ptr, 10, 0xffeff47d, 15) ;
@step(@i, &b, c, d, a, ptr, 1, 0x85845dd1, 21) ;
@step(@i, &a, b, c, d, ptr, 8, 0x6fa87e4f, 6) ;
@step(@i, &d, a, b, c, ptr, 15, 0xfe2ce6e0, 10) ;
@step(@i, &c, d, a, b, ptr, 6, 0xa3014314, 15) ;
@step(@i, &b, c, d, a, ptr, 13, 0x4e0811a1, 21) ;
@step(@i, &a, b, c, d, ptr, 4, 0xf7537e82, 6) ;
@step(@i, &d, a, b, c, ptr, 11, 0xbd3af235, 10) ;
@step(@i, &c, d, a, b, ptr, 2, 0x2ad7d2bb, 15) ;
@step(@i, &b, c, d, a, ptr, 9, 0xeb86d391, 21) ;
a += saved_a;
b += saved_b;

View File

@@ -18,9 +18,9 @@ struct Sha1
char[BLOCK_BYTES] buffer;
}
def HmacSha1 = Hmac(<Sha1, HASH_BYTES, BLOCK_BYTES>);
def hmac = hmac::hash(<Sha1, HASH_BYTES, BLOCK_BYTES>);
def pbkdf2 = hmac::pbkdf2(<Sha1, HASH_BYTES, BLOCK_BYTES>);
alias HmacSha1 = Hmac{Sha1, HASH_BYTES, BLOCK_BYTES};
alias hmac = hmac::hash{Sha1, HASH_BYTES, BLOCK_BYTES};
alias pbkdf2 = hmac::pbkdf2{Sha1, HASH_BYTES, BLOCK_BYTES};
fn char[HASH_BYTES] hash(char[] data)
{
@@ -78,10 +78,10 @@ fn char[HASH_BYTES] Sha1.final(&self)
{
finalcount[i] = (char)((self.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
}
self.update(char[] { 0o200 });
self.update((char[]){ 0o200 });
while ((self.count[0] & 504) != 448)
{
self.update(char[] { 0 });
self.update((char[]){ 0 });
}
self.update(&finalcount);
@@ -103,13 +103,13 @@ union Long16 @local
uint[16] l;
}
macro @blk(&block, i) @local
macro blk(Long16* block, i) @local
{
return (block.l[i & 15] = (block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15]
^ block.l[(i + 2) & 15] ^ block.l[i & 15]).rotl(1));
}
macro @blk0(&block, i) @local
macro blk0(Long16* block, i) @local
{
$if env::BIG_ENDIAN:
return block.l[i];
@@ -119,38 +119,38 @@ macro @blk0(&block, i) @local
$endif
}
macro @r0(&block, v, &wref, x, y, &z, i) @local
macro r0(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += ((w & (x ^ y)) ^ y) + @blk0(*block, i) + 0x5A827999 + v.rotl(5);
*z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + v.rotl(5);
*wref = w.rotl(30);
}
macro @r1(&block, v, &wref, x, y, &z, i) @local
macro r1(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += ((w & (x ^ y)) ^ y) + @blk(*block, i) + 0x5A827999 + v.rotl(5);
*z += ((w & (x ^ y)) ^ y) + blk(block, i) + 0x5A827999 + v.rotl(5);
*wref = w.rotl(30);
}
macro @r2(&block, v, &wref, x, y, &z, i) @local
macro r2(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += (w ^ x ^ y) + @blk(*block, i) + 0x6ED9EBA1 + v.rotl(5);
*z += (w ^ x ^ y) + blk(block, i) + 0x6ED9EBA1 + v.rotl(5);
*wref = w.rotl(30);
}
macro @r3(&block, v, &wref, x, y, &z, i) @local
macro r3(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += (((w | x) & y) | (w & x)) + @blk(*block, i) + 0x8F1BBCDC + v.rotl(5);
*z += (((w | x) & y) | (w & x)) + blk(block, i) + 0x8F1BBCDC + v.rotl(5);
*wref = w.rotl(30);
}
macro @r4(&block, v, &wref, x, y, &z, i) @local
macro r4(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += (w ^ x ^ y) + @blk(*block, i) + 0xCA62C1D6 + v.rotl(5);
*z += (w ^ x ^ y) + blk(block, i) + 0xCA62C1D6 + v.rotl(5);
*wref = w.rotl(30);
}
@@ -158,100 +158,100 @@ macro @r4(&block, v, &wref, x, y, &z, i) @local
@param [&inout] state
@param [&in] buffer
*>
fn void sha1_transform(uint* state, char* buffer) @local
fn void sha1_transform(uint[5]* state, char* buffer) @local
{
Long16 block;
block.c[..] = buffer[:64];
uint a = state[0];
uint b = state[1];
uint c = state[2];
uint d = state[3];
uint e = state[4];
@r0(block, a, b, c, d, e, 0);
@r0(block, e, a, b, c, d, 1);
@r0(block, d, e, a, b, c, 2);
@r0(block, c, d, e, a, b, 3);
@r0(block, b, c, d, e, a, 4);
@r0(block, a, b, c, d, e, 5);
@r0(block, e, a, b, c, d, 6);
@r0(block, d, e, a, b, c, 7);
@r0(block, c, d, e, a, b, 8);
@r0(block, b, c, d, e, a, 9);
@r0(block, a, b, c, d, e, 10);
@r0(block, e, a, b, c, d, 11);
@r0(block, d, e, a, b, c, 12);
@r0(block, c, d, e, a, b, 13);
@r0(block, b, c, d, e, a, 14);
@r0(block, a, b, c, d, e, 15);
@r1(block, e, a, b, c, d, 16);
@r1(block, d, e, a, b, c, 17);
@r1(block, c, d, e, a, b, 18);
@r1(block, b, c, d, e, a, 19);
@r2(block, a, b, c, d, e, 20);
@r2(block, e, a, b, c, d, 21);
@r2(block, d, e, a, b, c, 22);
@r2(block, c, d, e, a, b, 23);
@r2(block, b, c, d, e, a, 24);
@r2(block, a, b, c, d, e, 25);
@r2(block, e, a, b, c, d, 26);
@r2(block, d, e, a, b, c, 27);
@r2(block, c, d, e, a, b, 28);
@r2(block, b, c, d, e, a, 29);
@r2(block, a, b, c, d, e, 30);
@r2(block, e, a, b, c, d, 31);
@r2(block, d, e, a, b, c, 32);
@r2(block, c, d, e, a, b, 33);
@r2(block, b, c, d, e, a, 34);
@r2(block, a, b, c, d, e, 35);
@r2(block, e, a, b, c, d, 36);
@r2(block, d, e, a, b, c, 37);
@r2(block, c, d, e, a, b, 38);
@r2(block, b, c, d, e, a, 39);
@r3(block, a, b, c, d, e, 40);
@r3(block, e, a, b, c, d, 41);
@r3(block, d, e, a, b, c, 42);
@r3(block, c, d, e, a, b, 43);
@r3(block, b, c, d, e, a, 44);
@r3(block, a, b, c, d, e, 45);
@r3(block, e, a, b, c, d, 46);
@r3(block, d, e, a, b, c, 47);
@r3(block, c, d, e, a, b, 48);
@r3(block, b, c, d, e, a, 49);
@r3(block, a, b, c, d, e, 50);
@r3(block, e, a, b, c, d, 51);
@r3(block, d, e, a, b, c, 52);
@r3(block, c, d, e, a, b, 53);
@r3(block, b, c, d, e, a, 54);
@r3(block, a, b, c, d, e, 55);
@r3(block, e, a, b, c, d, 56);
@r3(block, d, e, a, b, c, 57);
@r3(block, c, d, e, a, b, 58);
@r3(block, b, c, d, e, a, 59);
@r4(block, a, b, c, d, e, 60);
@r4(block, e, a, b, c, d, 61);
@r4(block, d, e, a, b, c, 62);
@r4(block, c, d, e, a, b, 63);
@r4(block, b, c, d, e, a, 64);
@r4(block, a, b, c, d, e, 65);
@r4(block, e, a, b, c, d, 66);
@r4(block, d, e, a, b, c, 67);
@r4(block, c, d, e, a, b, 68);
@r4(block, b, c, d, e, a, 69);
@r4(block, a, b, c, d, e, 70);
@r4(block, e, a, b, c, d, 71);
@r4(block, d, e, a, b, c, 72);
@r4(block, c, d, e, a, b, 73);
@r4(block, b, c, d, e, a, 74);
@r4(block, a, b, c, d, e, 75);
@r4(block, e, a, b, c, d, 76);
@r4(block, d, e, a, b, c, 77);
@r4(block, c, d, e, a, b, 78);
@r4(block, b, c, d, e, a, 79);
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
uint a = (*state)[0];
uint b = (*state)[1];
uint c = (*state)[2];
uint d = (*state)[3];
uint e = (*state)[4];
r0(&block, a, &b, c, d, &e, 0);
r0(&block, e, &a, b, c, &d, 1);
r0(&block, d, &e, a, b, &c, 2);
r0(&block, c, &d, e, a, &b, 3);
r0(&block, b, &c, d, e, &a, 4);
r0(&block, a, &b, c, d, &e, 5);
r0(&block, e, &a, b, c, &d, 6);
r0(&block, d, &e, a, b, &c, 7);
r0(&block, c, &d, e, a, &b, 8);
r0(&block, b, &c, d, e, &a, 9);
r0(&block, a, &b, c, d, &e, 10);
r0(&block, e, &a, b, c, &d, 11);
r0(&block, d, &e, a, b, &c, 12);
r0(&block, c, &d, e, a, &b, 13);
r0(&block, b, &c, d, e, &a, 14);
r0(&block, a, &b, c, d, &e, 15);
r1(&block, e, &a, b, c, &d, 16);
r1(&block, d, &e, a, b, &c, 17);
r1(&block, c, &d, e, a, &b, 18);
r1(&block, b, &c, d, e, &a, 19);
r2(&block, a, &b, c, d, &e, 20);
r2(&block, e, &a, b, c, &d, 21);
r2(&block, d, &e, a, b, &c, 22);
r2(&block, c, &d, e, a, &b, 23);
r2(&block, b, &c, d, e, &a, 24);
r2(&block, a, &b, c, d, &e, 25);
r2(&block, e, &a, b, c, &d, 26);
r2(&block, d, &e, a, b, &c, 27);
r2(&block, c, &d, e, a, &b, 28);
r2(&block, b, &c, d, e, &a, 29);
r2(&block, a, &b, c, d, &e, 30);
r2(&block, e, &a, b, c, &d, 31);
r2(&block, d, &e, a, b, &c, 32);
r2(&block, c, &d, e, a, &b, 33);
r2(&block, b, &c, d, e, &a, 34);
r2(&block, a, &b, c, d, &e, 35);
r2(&block, e, &a, b, c, &d, 36);
r2(&block, d, &e, a, b, &c, 37);
r2(&block, c, &d, e, a, &b, 38);
r2(&block, b, &c, d, e, &a, 39);
r3(&block, a, &b, c, d, &e, 40);
r3(&block, e, &a, b, c, &d, 41);
r3(&block, d, &e, a, b, &c, 42);
r3(&block, c, &d, e, a, &b, 43);
r3(&block, b, &c, d, e, &a, 44);
r3(&block, a, &b, c, d, &e, 45);
r3(&block, e, &a, b, c, &d, 46);
r3(&block, d, &e, a, b, &c, 47);
r3(&block, c, &d, e, a, &b, 48);
r3(&block, b, &c, d, e, &a, 49);
r3(&block, a, &b, c, d, &e, 50);
r3(&block, e, &a, b, c, &d, 51);
r3(&block, d, &e, a, b, &c, 52);
r3(&block, c, &d, e, a, &b, 53);
r3(&block, b, &c, d, e, &a, 54);
r3(&block, a, &b, c, d, &e, 55);
r3(&block, e, &a, b, c, &d, 56);
r3(&block, d, &e, a, b, &c, 57);
r3(&block, c, &d, e, a, &b, 58);
r3(&block, b, &c, d, e, &a, 59);
r4(&block, a, &b, c, d, &e, 60);
r4(&block, e, &a, b, c, &d, 61);
r4(&block, d, &e, a, b, &c, 62);
r4(&block, c, &d, e, a, &b, 63);
r4(&block, b, &c, d, e, &a, 64);
r4(&block, a, &b, c, d, &e, 65);
r4(&block, e, &a, b, c, &d, 66);
r4(&block, d, &e, a, b, &c, 67);
r4(&block, c, &d, e, a, &b, 68);
r4(&block, b, &c, d, e, &a, 69);
r4(&block, a, &b, c, d, &e, 70);
r4(&block, e, &a, b, c, &d, 71);
r4(&block, d, &e, a, b, &c, 72);
r4(&block, c, &d, e, a, &b, 73);
r4(&block, b, &c, d, e, &a, 74);
r4(&block, a, &b, c, d, &e, 75);
r4(&block, e, &a, b, c, &d, 76);
r4(&block, d, &e, a, b, &c, 77);
r4(&block, c, &d, e, a, &b, 78);
r4(&block, b, &c, d, e, &a, 79);
(*state)[0] += a;
(*state)[1] += b;
(*state)[2] += c;
(*state)[3] += d;
(*state)[4] += e;
a = b = c = d = e = 0;
buffer[:64] = 0;
block = {};
}

View File

@@ -34,9 +34,9 @@ struct Sha256
char[BLOCK_SIZE] buffer;
}
def HmacSha256 = Hmac(<Sha256, HASH_SIZE, BLOCK_SIZE>);
def hmac = hmac::hash(<Sha256, HASH_SIZE, BLOCK_SIZE>);
def pbkdf2 = hmac::pbkdf2(<Sha256, HASH_SIZE, BLOCK_SIZE>);
alias HmacSha256 = Hmac{Sha256, HASH_SIZE, BLOCK_SIZE};
alias hmac = hmac::hash{Sha256, HASH_SIZE, BLOCK_SIZE};
alias pbkdf2 = hmac::pbkdf2{Sha256, HASH_SIZE, BLOCK_SIZE};
fn char[HASH_SIZE] hash(char[] data)
{

View File

@@ -21,7 +21,7 @@ fn void BitReader.clear(&self) @inline
@require nbits <= 8
@require self.len + nbits <= uint.sizeof * 8
*>
fn char! BitReader.read_bits(&self, uint nbits)
fn char? BitReader.read_bits(&self, uint nbits)
{
uint bits = self.bits;
if (self.len < nbits)
@@ -45,15 +45,21 @@ struct BitWriter
uint len;
}
// c3 doesn't allow to shift more than bit width of a variable,
// so use closest byte boundary of 24 instead of 32
const int WRITER_BITS = 24;
fn void BitWriter.init(&self, OutStream byte_writer)
{
*self = { .writer = byte_writer };
}
fn void! BitWriter.flush(&self)
fn void? BitWriter.flush(&self)
{
if (self.len == 0) return;
uint bits = self.bits << (32 - self.len);
int padding = ($sizeof(self.bits) * 8 - self.len);
uint bits = self.bits << padding;
uint n = (self.len + 7) / 8;
char[4] buffer;
bitorder::write(bits, &buffer, UIntBE);
@@ -62,24 +68,27 @@ fn void! BitWriter.flush(&self)
}
<*
@require nbits <= 8
@require nbits <= 32
*>
fn void! BitWriter.write_bits(&self, uint bits, uint nbits)
fn void? BitWriter.write_bits(&self, uint bits, uint nbits)
{
if (nbits == 0) return;
uint n = self.len + nbits;
uint to_write = n / 8;
uint left = n % 8;
if (to_write > 0)
while (self.len + nbits > WRITER_BITS)
{
ulong lbits;
if (self.len > 0) lbits = (ulong)self.bits << (64 - self.len);
lbits |= (ulong)(bits >> left) << (64 - (n - left));
char[8] buffer;
bitorder::write(lbits, &buffer, ULongBE);
io::write_all(self.writer, buffer[:to_write])!;
uint to_push = WRITER_BITS - self.len;
uint bits_to_push = (bits >> (nbits - to_push)) & ((1 << to_push) - 1);
self.bits <<= to_push;
self.bits |= bits_to_push;
self.len += to_push;
nbits -= to_push;
self.flush()!;
}
self.bits <<= left;
self.bits |= bits & ((1 << left) - 1);
self.len = left;
if (nbits == 0) return;
self.bits <<= nbits;
self.bits |= bits & ((1 << nbits) - 1);
self.len += nbits;
}

View File

@@ -9,16 +9,21 @@ struct File (InStream, OutStream)
module std::io::file;
import libc, std::io::path, std::io::os;
fn File! open(String filename, String mode)
fn File? open(String filename, String mode)
{
return from_handle(os::native_fopen(filename, mode));
}
fn File! open_path(Path path, String mode)
fn File? open_path(Path path, String mode)
{
return from_handle(os::native_fopen(path.str_view(), mode));
}
fn bool exists(String file) => @pool()
{
return os::native_file_or_dir_exists(file);
}
fn File from_handle(CFile file)
{
return { .file = file };
@@ -29,18 +34,26 @@ fn bool is_file(String path)
return os::native_is_file(path);
}
fn usz! get_size(String path)
fn bool is_dir(String path)
{
return os::native_is_dir(path);
}
fn usz? get_size(String path)
{
return os::native_file_size(path);
}
fn void! delete(String filename) => os::native_remove(filename) @inline;
fn void? delete(String filename)
{
return os::native_remove(filename) @inline;
}
<*
@require self.file != null
*>
fn void! File.reopen(&self, String filename, String mode)
fn void? File.reopen(&self, String filename, String mode)
{
self.file = os::native_freopen(self.file, filename, mode)!;
}
@@ -48,7 +61,7 @@ fn void! File.reopen(&self, String filename, String mode)
<*
@require self.file != null
*>
fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
fn usz? File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
{
os::native_fseek(self.file, offset, seek_mode)!;
return os::native_ftell(self.file);
@@ -60,11 +73,11 @@ Implement later
<*
@require self.file == null
*>
fn void! File.memopen(File* file, char[] data, String mode)
fn void? File.memopen(File* file, char[] data, String mode)
{
@pool()
{
file.file = libc::memopen(data.ptr, data.len, mode.zstr_tcopy(), file.file);
file.file = libc::memopen(data.ptr, data.len, mode.to_temp_zstr(), file.file);
// TODO errors
};
}
@@ -74,7 +87,7 @@ fn void! File.memopen(File* file, char[] data, String mode)
<*
@require self.file != null
*>
fn void! File.write_byte(&self, char c) @dynamic
fn void? File.write_byte(&self, char c) @dynamic
{
return os::native_fputc(c, self.file);
}
@@ -82,15 +95,15 @@ fn void! File.write_byte(&self, char c) @dynamic
<*
@param [&inout] self
*>
fn void! File.close(&self) @inline @dynamic
fn void? File.close(&self) @inline @dynamic
{
if (self.file && libc::fclose(self.file))
{
switch (libc::errno())
{
case errno::ECONNRESET:
case errno::EBADF: return IoError.FILE_NOT_VALID?;
case errno::EINTR: return IoError.INTERRUPTED?;
case errno::EBADF: return io::FILE_NOT_VALID?;
case errno::EINTR: return io::INTERRUPTED?;
case errno::EDQUOT:
case errno::EFAULT:
case errno::EAGAIN:
@@ -98,15 +111,15 @@ fn void! File.close(&self) @inline @dynamic
case errno::ENETDOWN:
case errno::ENETUNREACH:
case errno::ENOSPC:
case errno::EIO: return IoError.INCOMPLETE_WRITE?;
default: return IoError.UNKNOWN_ERROR?;
case errno::EIO: return io::INCOMPLETE_WRITE?;
default: return io::UNKNOWN_ERROR?;
}
}
self.file = null;
}
<*
@require self.file
@require self.file != null
*>
fn bool File.eof(&self) @inline
{
@@ -116,41 +129,50 @@ fn bool File.eof(&self) @inline
<*
@param [in] buffer
*>
fn usz! File.read(&self, char[] buffer) @dynamic
fn usz? File.read(&self, char[] buffer) @dynamic
{
return os::native_fread(self.file, buffer);
}
<*
@param [out] buffer
@require self.file `File must be initialized`
@require self.file != null : `File must be initialized`
*>
fn usz! File.write(&self, char[] buffer) @dynamic
fn usz? File.write(&self, char[] buffer) @dynamic
{
return os::native_fwrite(self.file, buffer);
}
fn Fd File.fd(self) @if(env::LIBC)
{
return libc::fileno(self.file);
}
fn char! File.read_byte(&self) @dynamic
fn bool File.isatty(self) @if(env::LIBC)
{
return libc::isatty(self.fd()) > 0;
}
fn char? File.read_byte(&self) @dynamic
{
int c = libc::fgetc(self.file);
if (c == -1) return IoError.EOF?;
if (c == -1) return io::EOF?;
return (char)c;
}
<*
Load up to buffer.len characters. Returns IoError.OVERFLOW if the file is longer
Load up to buffer.len characters. Returns io::OVERFLOW if the file is longer
than the buffer.
@param filename "The path to the file to read"
@param [in] buffer "The buffer to read to"
@param filename : "The path to the file to read"
@param [in] buffer : "The buffer to read to"
*>
fn char[]! load_buffer(String filename, char[] buffer)
fn char[]? load_buffer(String filename, char[] buffer)
{
File file = open(filename, "rb")!;
defer (void)file.close();
usz len = file.seek(0, END)!;
if (len > buffer.len) return IoError.OVERFLOW?;
if (len > buffer.len) return io::OVERFLOW?;
file.seek(0, SET)!;
usz read = 0;
while (read < len)
@@ -161,7 +183,7 @@ fn char[]! load_buffer(String filename, char[] buffer)
}
fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
fn char[]? load(Allocator allocator, String filename)
{
File file = open(filename, "rb")!;
defer (void)file.close();
@@ -177,12 +199,13 @@ fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
return data[:len];
}
fn char[]! load_temp(String filename)
{
return load_new(filename, allocator::temp());
}
fn char[]? load_path(Allocator allocator, Path path) => load(allocator, path.str_view());
fn void! save(String filename, char[] data)
fn char[]? load_temp(String filename) => load(tmem, filename);
fn char[]? load_path_temp(Path path) => load_temp(path.str_view());
fn void? save(String filename, char[] data)
{
File file = open(filename, "wb")!;
defer (void)file.close();
@@ -194,9 +217,9 @@ fn void! save(String filename, char[] data)
}
<*
@require self.file `File must be initialized`
@require self.file != null : `File must be initialized`
*>
fn void! File.flush(&self) @dynamic
fn void? File.flush(&self) @dynamic
{
libc::fflush(self.file);
}

View File

@@ -6,42 +6,34 @@ const int PRINTF_NTOA_BUFFER_SIZE = 256;
interface Printable
{
fn String to_string(Allocator allocator) @optional;
fn String to_new_string(Allocator allocator) @optional @deprecated("Use to_string");
fn usz! to_format(Formatter* formatter) @optional;
fn String to_constant_string() @optional;
fn usz? to_format(Formatter* formatter) @optional;
}
fault PrintFault
{
BUFFER_EXCEEDED,
INTERNAL_BUFFER_EXCEEDED,
INVALID_FORMAT,
NOT_ENOUGH_ARGUMENTS,
INVALID_ARGUMENT,
}
faultdef BUFFER_EXCEEDED, INTERNAL_BUFFER_EXCEEDED, INVALID_FORMAT,
NOT_ENOUGH_ARGUMENTS, INVALID_ARGUMENT;
def OutputFn = fn void!(void* buffer, char c);
def FloatType = double;
alias OutputFn = fn void?(void* buffer, char c);
alias FloatType = double;
macro bool is_struct_with_default_print($Type)
{
return $Type.kindof == STRUCT
&&& !$defined($Type.to_format)
&&& !$defined($Type.to_new_string)
&&& !$defined($Type.to_string);
&&& !$defined($Type.to_constant_string);
}
<*
Introspect a struct and print it to a formatter
@require @typekind(value) == STRUCT `This macro is only valid on macros`
@require @typekind(value) == STRUCT : `This macro is only valid on macros`
*>
macro usz! struct_to_format(value, Formatter* f, bool $force_dump)
macro usz? struct_to_format(value, Formatter* f, bool $force_dump)
{
var $Type = $typeof(value);
usz total = f.print("{ ")!;
$foreach ($i, $member : $Type.membersof)
$foreach $i, $member : $Type.membersof:
$if $i > 0:
total += f.print(", ")!;
$endif
@@ -58,12 +50,12 @@ macro usz! struct_to_format(value, Formatter* f, bool $force_dump)
return total + f.print(" }");
}
fn usz! ReflectedParam.to_format(&self, Formatter* f) @dynamic
fn usz? ReflectedParam.to_format(&self, Formatter* f) @dynamic
{
return f.printf("[Parameter '%s']", self.name);
}
fn usz! Formatter.printf(&self, String format, args...)
fn usz? Formatter.printf(&self, String format, args...)
{
return self.vprintf(format, args) @inline;
}
@@ -78,7 +70,7 @@ struct Formatter
uint width;
uint prec;
usz idx;
anyfault first_fault;
fault first_fault;
}
}
@@ -98,7 +90,7 @@ fn void Formatter.init(&self, OutputFn out_fn, void* data = null)
*self = { .data = data, .out_fn = out_fn};
}
fn usz! Formatter.out(&self, char c) @private
fn usz? Formatter.out(&self, char c) @private
{
if (catch err = self.out_fn(self.data, c))
{
@@ -109,7 +101,7 @@ fn usz! Formatter.out(&self, char c) @private
return 1;
}
fn usz! Formatter.print_with_function(&self, Printable arg)
fn usz? Formatter.print_with_function(&self, Printable arg)
{
if (&arg.to_format)
{
@@ -125,7 +117,7 @@ fn usz! Formatter.print_with_function(&self, Printable arg)
if (!arg) return self.out_substr("(null)");
return arg.to_format(self);
}
if (&arg.to_string)
if (&arg.to_constant_string)
{
PrintFlags old = self.flags;
uint old_width = self.width;
@@ -137,16 +129,16 @@ fn usz! Formatter.print_with_function(&self, Printable arg)
self.prec = old_prec;
}
if (!arg) return self.out_substr("(null)");
@stack_mem(1024; Allocator mem)
{
return self.out_substr(arg.to_string(mem));
};
return self.out_substr(arg.to_constant_string());
}
return SearchResult.MISSING?;
return NOT_FOUND?;
}
fn usz! Formatter.out_str(&self, any arg) @private
fn usz? Formatter.out_unknown(&self, String category, any arg) @private
{
return self.out_substr("[") + self.out_substr(category) + self.out_substr(" type:") + self.ntoa((iptr)arg.type, false, 16) + self.out_substr(", addr:") + self.ntoa((iptr)arg.ptr, false, 16) + self.out_substr("]");
}
fn usz? Formatter.out_str(&self, any arg) @private
{
switch (arg.type.kindof)
{
@@ -154,9 +146,8 @@ fn usz! Formatter.out_str(&self, any arg) @private
return self.out_substr("typeid");
case VOID:
return self.out_substr("void");
case ANYFAULT:
case FAULT:
return self.out_substr((*(anyfault*)arg.ptr).nameof);
return self.out_substr((*(fault*)arg.ptr).nameof);
case INTERFACE:
case ANY:
return self.out_str(*(any*)arg);
@@ -189,23 +180,23 @@ fn usz! Formatter.out_str(&self, any arg) @private
return self.out_substr(*(bool*)arg.ptr ? "true" : "false");
default:
}
usz! n = self.print_with_function((Printable)arg);
usz? n = self.print_with_function((Printable)arg);
if (try n) return n;
if (@catch(n) != SearchResult.MISSING) n!;
if (@catch(n) != NOT_FOUND) n!;
switch (arg.type.kindof)
{
case ENUM:
usz i = types::any_to_int(arg, usz)!!;
usz i = types::any_to_enum_ordinal(arg, usz)!!;
assert(i < arg.type.names.len, "Illegal enum value found, numerical value was %d.", i);
return self.out_substr(arg.type.names[i]);
case STRUCT:
return self.out_substr("<struct>");
return self.out_unknown("struct", arg);
case UNION:
return self.out_substr("<union>");
return self.out_unknown("union", arg);
case BITSTRUCT:
return self.out_substr("<bitstruct>");
return self.out_unknown("bitstruct", arg);
case FUNC:
return self.out_substr("<function>");
return self.out_unknown("function", arg);
case DISTINCT:
if (arg.type == String.typeid)
{
@@ -228,7 +219,7 @@ fn usz! Formatter.out_str(&self, any arg) @private
any deref = any_make(*pointer, inner);
n = self.print_with_function((Printable)deref);
if (try n) return n;
if (@catch(n) != SearchResult.MISSING) n!;
if (@catch(n) != NOT_FOUND) n!;
}
PrintFlags flags = self.flags;
uint width = self.width;
@@ -238,8 +229,7 @@ fn usz! Formatter.out_str(&self, any arg) @private
self.width = width;
}
self.width = 0;
self.out_substr("0x")!;
return self.ntoa_any(arg, 16);
return self.out_substr("0x")! + self.ntoa_any(arg, 16);
case ARRAY:
// this is SomeType[*] so grab the "SomeType"
PrintFlags flags = self.flags;
@@ -328,33 +318,36 @@ fn usz! Formatter.out_str(&self, any arg) @private
fn void! out_null_fn(void* data @unused, char c @unused) @private
fn void? out_null_fn(void* data @unused, char c @unused) @private
{
}
macro usz! @report_fault(Formatter* f, $fault)
macro usz? @report_fault(Formatter* f, $fault)
{
(void)f.out_substr($fault);
return PrintFault.INVALID_FORMAT?;
return INVALID_FORMAT?;
}
macro usz! @wrap_bad(Formatter* f, #action)
macro usz? @wrap_bad(Formatter* f, #action)
{
usz! len = #action;
usz? len = #action;
if (catch err = len)
{
case PrintFault.BUFFER_EXCEEDED:
case PrintFault.INTERNAL_BUFFER_EXCEEDED:
return f.first_err(err)?;
default:
err = f.first_err(PrintFault.INVALID_ARGUMENT);
f.out_substr("<INVALID>")!;
return err?;
switch (err)
{
case BUFFER_EXCEEDED:
case INTERNAL_BUFFER_EXCEEDED:
return f.first_err(err)?;
default:
err = f.first_err(INVALID_ARGUMENT);
f.out_substr("<INVALID>")!;
return err?;
}
}
return len;
}
fn usz! Formatter.vprintf(&self, String format, any[] anys)
fn usz? Formatter.vprintf(&self, String format, any[] anys)
{
self.first_fault = {};
if (!self.out_fn)
@@ -400,7 +393,7 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
c = format[i];
}
// evaluate width field
int! w = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
int? w = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
if (catch w) return @report_fault(self, "%ERR");
c = format[i];
if (w < 0)
@@ -415,7 +408,7 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
{
self.flags.precision = true;
if (++i >= format_len) return @report_fault(self, "<BAD FORMAT>");
int! prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
int? prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
if (catch prec) return @report_fault(self, "<BAD FORMAT>");
self.prec = prec < 0 ? 0 : prec;
c = format[i];
@@ -425,7 +418,7 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
uint base = 0;
if (variant_index >= anys.len)
{
self.first_err(PrintFault.NOT_ENOUGH_ARGUMENTS);
self.first_err(NOT_ENOUGH_ARGUMENTS);
total_len += self.out_substr("<MISSING>")!;
continue;
}
@@ -477,6 +470,38 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
case 'c':
total_len += self.out_char(current)!;
continue;
case 'H':
self.flags.uppercase = true;
nextcase;
case 'h':
char[] out @noinit;
switch (current.type)
{
case char[]:
case ichar[]:
out = *(char[]*)current;
default:
if (current.type.kindof == ARRAY && (current.type.inner == char.typeid || current.type.inner == ichar.typeid))
{
out = ((char*)current.ptr)[:current.type.sizeof];
break;
}
total_len += self.out_substr("<INVALID>")!;
continue;
}
if (self.flags.left)
{
usz len = print_hex_chars(self, out, self.flags.uppercase)!;
total_len += len;
total_len += self.pad(' ', self.width, len)!;
continue;
}
if (self.width)
{
total_len += self.pad(' ', self.width, out.len * 2)!;
}
total_len += print_hex_chars(self, out, self.flags.uppercase)!;
continue;
case 's':
if (self.flags.left)
{
@@ -485,11 +510,14 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
total_len += self.pad(' ', self.width, len)!;
continue;
}
OutputFn out_fn = self.out_fn;
self.out_fn = (OutputFn)&out_null_fn;
usz len = self.out_str(current)!;
self.out_fn = out_fn;
total_len += self.pad(' ', self.width, len)!;
if (self.width)
{
OutputFn out_fn = self.out_fn;
self.out_fn = (OutputFn)&out_null_fn;
usz len = self.out_str(current)!;
self.out_fn = out_fn;
total_len += self.pad(' ', self.width, len)!;
}
total_len += self.out_str(current)!;
continue;
case 'p':
@@ -497,7 +525,7 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
self.flags.hash = true;
base = 16;
default:
self.first_err(PrintFault.INVALID_FORMAT);
self.first_err(INVALID_FORMAT);
total_len += self.out_substr("<BAD FORMAT>")!;
continue;
}
@@ -521,7 +549,7 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
}
fn usz! Formatter.print(&self, String str)
fn usz? Formatter.print(&self, String str)
{
if (!self.out_fn)
{

View File

@@ -4,25 +4,38 @@ import std::math;
const char[16] XDIGITS_H = "0123456789ABCDEF";
const char[16] XDIGITS_L = "0123456789abcdef";
fault FormattingFault
faultdef BAD_FORMAT;
fn usz? print_hex_chars(Formatter* f, char[] out, bool uppercase) @inline
{
BAD_FORMAT
char past_10 = (uppercase ? 'A' : 'a') - 10;
usz len = 0;
foreach (c : out)
{
char digit = c >> 4;
f.out(digit + (digit < 10 ? '0' : past_10))!;
len++;
digit = c & 0xf;
f.out(digit + (digit < 10 ? '0' : past_10))!;
len++;
}
return len;
}
macro Formatter.first_err(&self, anyfault f)
macro Formatter.first_err(&self, fault f)
{
if (self.first_fault) return self.first_fault;
self.first_fault = f;
return f;
}
fn usz! Formatter.adjust(&self, usz len) @local
fn usz? Formatter.adjust(&self, usz len) @local
{
if (!self.flags.left) return 0;
return self.pad(' ', self.width, len);
}
fn uint128! int_from_any(any arg, bool *is_neg) @private
fn uint128? int_from_any(any arg, bool *is_neg) @private
{
switch (arg.type.kindof)
{
@@ -30,53 +43,52 @@ fn uint128! int_from_any(any arg, bool *is_neg) @private
*is_neg = false;
return (uint128)(uptr)*(void**)arg.ptr;
case TypeKind.DISTINCT:
case TypeKind.ENUM:
return int_from_any(arg.as_inner(), is_neg);
default:
break;
}
*is_neg = false;
switch (arg)
switch (arg.type)
{
case bool:
return (uint128)*arg;
return (uint128)*(bool*)arg;
case ichar:
int val = *arg;
int val = *(ichar*)arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case short:
int val = *arg;
int val = *(short*)arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case int:
int val = *arg;
int val = *(int*)arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case long:
long val = *arg;
long val = *(long*)arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case int128:
int128 val = *arg;
int128 val = *(int128*)arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case char:
return *arg;
return *(char*)arg;
case ushort:
return *arg;
return *(ushort*)arg;
case uint:
return *arg;
return *(uint*)arg;
case ulong:
return *arg;
return *(ulong*)arg;
case uint128:
return *arg;
return *(uint128*)arg;
case float:
float f = *arg;
float f = *(float*)arg;
return (uint128)((*is_neg = f < 0) ? -f : f);
case double:
double d = *arg;
double d = *(double*)arg;
return (uint128)((*is_neg = d < 0) ? -d : d);
default:
return FormattingFault.BAD_FORMAT?;
return BAD_FORMAT?;
}
}
fn FloatType! float_from_any(any arg) @private
fn FloatType? float_from_any(any arg) @private
{
$if env::F128_SUPPORT:
if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr);
@@ -85,36 +97,36 @@ fn FloatType! float_from_any(any arg) @private
{
return float_from_any(arg.as_inner());
}
switch (arg)
switch (arg.type)
{
case bool:
return (FloatType)*arg;
return (FloatType)*(bool*)arg;
case ichar:
return *arg;
return *(ichar*)arg;
case short:
return *arg;
return *(short*)arg;
case int:
return *arg;
return *(int*)arg;
case long:
return *arg;
return *(long*)arg;
case int128:
return *arg;
return *(int128*)arg;
case char:
return *arg;
return *(char*)arg;
case ushort:
return *arg;
return *(ushort*)arg;
case uint:
return *arg;
return *(uint*)arg;
case ulong:
return *arg;
return *(ulong*)arg;
case uint128:
return *arg;
return *(uint128*)arg;
case float:
return (FloatType)*arg;
return (FloatType)*(float*)arg;
case double:
return (FloatType)*arg;
return (FloatType)*(double*)arg;
default:
return FormattingFault.BAD_FORMAT?;
return BAD_FORMAT?;
}
}
@@ -122,9 +134,9 @@ fn FloatType! float_from_any(any arg) @private
<*
Read a simple integer value, typically for formatting.
@param [inout] len_ptr "the length remaining."
@param [in] buf "the buf to read from."
@param maxlen "the maximum len that can be read."
@param [inout] len_ptr : "the length remaining."
@param [in] buf : "the buf to read from."
@param maxlen : "the maximum len that can be read."
@return "The result of the atoi."
*>
fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
@@ -142,7 +154,7 @@ fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
return i;
}
fn usz! Formatter.out_substr(&self, String str) @private
fn usz? Formatter.out_substr(&self, String str) @private
{
usz l = conv::utf8_codepoints(str);
uint prec = self.prec;
@@ -161,7 +173,7 @@ fn usz! Formatter.out_substr(&self, String str) @private
return index;
}
fn usz! Formatter.pad(&self, char c, isz width, isz len) @inline
fn usz? Formatter.pad(&self, char c, isz width, isz len) @inline
{
isz delta = width - len;
for (isz i = 0; i < delta; i++) self.out(c)!;
@@ -175,7 +187,7 @@ fn char* fmt_u(uint128 x, char* s)
return s;
}
fn usz! Formatter.out_chars(&self, char[] s)
fn usz? Formatter.out_chars(&self, char[] s)
{
foreach (c : s) self.out(c)!;
return s.len;
@@ -189,12 +201,12 @@ enum FloatFormatting
HEX
}
fn usz! Formatter.etoa(&self, double y) => self.floatformat(EXPONENTIAL, y);
fn usz! Formatter.ftoa(&self, double y) => self.floatformat(FLOAT, y);
fn usz! Formatter.gtoa(&self, double y) => self.floatformat(ADAPTIVE, y);
fn usz! Formatter.atoa(&self, double y) => self.floatformat(HEX, y);
fn usz? Formatter.etoa(&self, double y) => self.floatformat(EXPONENTIAL, y);
fn usz? Formatter.ftoa(&self, double y) => self.floatformat(FLOAT, y);
fn usz? Formatter.gtoa(&self, double y) => self.floatformat(ADAPTIVE, y);
fn usz? Formatter.atoa(&self, double y) => self.floatformat(HEX, y);
fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @private
fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @private
{
// This code is heavily based on musl's printf code
const BUF_SIZE = (math::DOUBLE_MANT_DIG + 28) / 29 + 1
@@ -215,7 +227,6 @@ fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
String s = self.flags.uppercase ? "INF" : "inf";
if (math::is_nan(y)) s = self.flags.uppercase ? "NAN" : "nan";
len += s.len;
if (pl) len += self.out(is_neg ? '-' : '+')!;
len += self.out_chars(s)!;
if (self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
@@ -270,7 +281,7 @@ fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
} while (y);
isz outlen = s - buf;
isz explen = ebuf - estr;
if (p > int.max - 2 - explen - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (p > int.max - 2 - explen - pl) return INTERNAL_BUFFER_EXCEEDED?;
usz len;
usz l = p && outlen - 2 < p
? p + 2 + explen
@@ -438,12 +449,12 @@ fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
}
}
}
if (p > int.max - 1 - (isz)(p || self.flags.hash)) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (p > int.max - 1 - (isz)(p || self.flags.hash)) return INTERNAL_BUFFER_EXCEEDED?;
int l = (int)(1 + p + (isz)(p || self.flags.hash));
char* estr @noinit;
if (formatting == FLOAT)
{
if (e > int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (e > int.max - l) return INTERNAL_BUFFER_EXCEEDED?;
if (e > 0) l += e;
}
else
@@ -452,10 +463,10 @@ fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
while (ebuf - estr < 2) (--estr)[0] = '0';
*--estr = (e < 0 ? '-' : '+');
*--estr = self.flags.uppercase ? 'E' : 'e';
if (ebuf - estr > (isz)int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (ebuf - estr > (isz)int.max - l) return INTERNAL_BUFFER_EXCEEDED?;
l += (int)(ebuf - estr);
}
if (l > int.max - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (l > int.max - pl) return INTERNAL_BUFFER_EXCEEDED?;
usz len;
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
@@ -513,7 +524,7 @@ fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
return len;
}
fn usz! Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
fn usz? Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
{
char[PRINTF_NTOA_BUFFER_SIZE] buf @noinit;
usz len;
@@ -527,7 +538,7 @@ fn usz! Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
char past_10 = (self.flags.uppercase ? 'A' : 'a') - 10;
do
{
if (len >= PRINTF_NTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED?;
char digit = (char)(value % base);
buf[len++] = digit + (digit < 10 ? '0' : past_10);
value /= base;
@@ -537,7 +548,7 @@ fn usz! Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
return self.ntoa_format((String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
}
fn usz! Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint base) @private
fn usz? Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint base) @private
{
// pad leading zeros
if (!self.flags.left)
@@ -545,12 +556,12 @@ fn usz! Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
if (self.width && self.flags.zeropad && (negative || self.flags.plus || self.flags.space)) self.width--;
while (len < self.prec)
{
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '0';
}
while (self.flags.zeropad && len < self.width)
{
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '0';
}
}
@@ -565,7 +576,7 @@ fn usz! Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
}
if (base != 10)
{
if (len + 1 >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (len + 1 >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
switch (base)
{
case 16:
@@ -584,13 +595,13 @@ fn usz! Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
switch (true)
{
case negative:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '-';
case self.flags.plus:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '+';
case self.flags.space:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = ' ';
}
if (len) self.out_reverse(buf[:len])!;
@@ -598,14 +609,18 @@ fn usz! Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
}
fn usz! Formatter.ntoa_any(&self, any arg, uint base) @private
fn usz? Formatter.ntoa_any(&self, any arg, uint base) @private
{
bool is_neg;
return self.ntoa(int_from_any(arg, &is_neg)!!, is_neg, base) @inline;
}
fn usz! Formatter.out_char(&self, any arg) @private
fn usz? Formatter.out_char(&self, any arg) @private
{
if (!arg.type.kindof.is_int())
{
return self.out_substr("<NOT CHAR>");
}
usz len = 1;
uint l = 1;
// pre padding
@@ -634,7 +649,7 @@ fn usz! Formatter.out_char(&self, any arg) @private
}
fn usz! Formatter.out_reverse(&self, char[] buf) @private
fn usz? Formatter.out_reverse(&self, char[] buf) @private
{
usz n;
usz buffer_start_idx = self.idx;
@@ -653,7 +668,7 @@ fn usz! Formatter.out_reverse(&self, char[] buf) @private
}
fn int! printf_parse_format_field(
fn int? printf_parse_format_field(
any* args_ptr, usz args_len, usz* args_index_ptr,
char* format_ptr, usz format_len, usz* index_ptr) @inline @private
{
@@ -661,10 +676,10 @@ fn int! printf_parse_format_field(
if (c.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr);
if (c != '*') return 0;
usz len = ++(*index_ptr);
if (len >= format_len) return FormattingFault.BAD_FORMAT?;
if (*args_index_ptr >= args_len) return FormattingFault.BAD_FORMAT?;
if (len >= format_len) return BAD_FORMAT?;
if (*args_index_ptr >= args_len) return BAD_FORMAT?;
any val = args_ptr[(*args_index_ptr)++];
if (!val.type.kindof.is_int()) return FormattingFault.BAD_FORMAT?;
uint! intval = types::any_to_int(val, int);
return intval ?? FormattingFault.BAD_FORMAT?;
if (!val.type.kindof.is_int()) return BAD_FORMAT?;
uint? intval = types::any_to_int(val, int);
return intval ?? BAD_FORMAT?;
}

View File

@@ -11,8 +11,7 @@ enum Seek
END
}
fault IoError
{
faultdef
ALREADY_EXISTS,
BUSY,
CANNOT_READ_DIR,
@@ -41,8 +40,7 @@ fault IoError
UNEXPECTED_EOF,
UNKNOWN_ERROR,
UNSUPPORTED_OPERATION,
WOULD_BLOCK,
}
WOULD_BLOCK;
<*
@@ -50,12 +48,12 @@ fault IoError
or to the end of the stream, whatever comes first.
"\r" will be filtered from the String.
@param stream `The stream to read from.`
@require @is_instream(stream) `The stream must implement InStream.`
@param [inout] allocator `the allocator to use.`
@param stream : `The stream to read from.`
@require @is_instream(stream) : `The stream must implement InStream.`
@param [inout] allocator : `the allocator to use.`
@return `The string containing the data read.`
*>
macro String! readline(stream = io::stdin(), Allocator allocator = allocator::heap())
macro String? readline(Allocator allocator, stream = io::stdin())
{
bool $is_stream = @typeis(stream, InStream);
$if $is_stream:
@@ -66,20 +64,20 @@ macro String! readline(stream = io::stdin(), Allocator allocator = allocator::he
$endif
if (val == '\n') return "";
@pool(allocator)
@pool()
{
DString str = dstring::temp_with_capacity(256);
if (val != '\r') str.append(val);
while (1)
{
$if $is_stream:
char! c = func((void*)stream);
char? c = func((void*)stream);
$else
char! c = stream.read_byte();
char? c = stream.read_byte();
$endif
if (catch err = c)
{
if (err == IoError.EOF) break;
if (err == io::EOF) break;
return err?;
}
if (c == '\r') continue;
@@ -94,27 +92,27 @@ macro String! readline(stream = io::stdin(), Allocator allocator = allocator::he
Reads a string, see `readline`, except the it is allocated
on the temporary allocator and does not need to be freed.
@param stream `The stream to read from.`
@require @is_instream(stream) `The stream must implement InStream.`
@param stream : `The stream to read from.`
@require @is_instream(stream) : `The stream must implement InStream.`
@return `The temporary string containing the data read.`
*>
macro String! treadline(stream = io::stdin())
macro String? treadline(stream = io::stdin())
{
return readline(stream, allocator::temp()) @inline;
return readline(tmem, stream) @inline;
}
<*
Print a value to a stream.
@param out `the stream to print to`
@param x `the value to print`
@require @is_outstream(out) `The output must implement OutStream.`
@param out : `the stream to print to`
@param x : `the value to print`
@require @is_outstream(out) : `The output must implement OutStream.`
@return `the number of bytes printed.`
*>
macro usz! fprint(out, x)
macro usz? fprint(out, x)
{
var $Type = $typeof(x);
$switch ($Type)
$switch $Type:
$case String: return out.write(x);
$case ZString: return out.write(x.str_view());
$case DString: return out.write(x.str_view());
@@ -137,11 +135,11 @@ macro usz! fprint(out, x)
Prints using a 'printf'-style formatting string.
See `printf` for details on formatting.
@param [inout] out `The OutStream to print to`
@param [in] format `The printf-style format string`
@param [inout] out : `The OutStream to print to`
@param [in] format : `The printf-style format string`
@return `the number of characters printed`
*>
fn usz! fprintf(OutStream out, String format, args...)
fn usz? fprintf(OutStream out, String format, args...) @format(1)
{
Formatter formatter;
formatter.init(&out_putstream_fn, &out);
@@ -152,11 +150,11 @@ fn usz! fprintf(OutStream out, String format, args...)
Prints using a 'printf'-style formatting string,
appending '\n' at the end. See `printf`.
@param [inout] out `The OutStream to print to`
@param [in] format `The printf-style format string`
@param [inout] out : `The OutStream to print to`
@param [in] format : `The printf-style format string`
@return `the number of characters printed`
*>
fn usz! fprintfn(OutStream out, String format, args...) @maydiscard
fn usz? fprintfn(OutStream out, String format, args...) @format(1) @maydiscard
{
Formatter formatter;
formatter.init(&out_putstream_fn, &out);
@@ -167,13 +165,13 @@ fn usz! fprintfn(OutStream out, String format, args...) @maydiscard
}
<*
@require @is_outstream(out) "The output must implement OutStream"
@require @is_outstream(out) : "The output must implement OutStream"
*>
macro usz! fprintn(out, x = "")
macro usz? fprintn(out, x = "")
{
usz len = fprint(out, x)!;
out.write_byte('\n')!;
$switch
$switch:
$case @typeid(out) == OutStream.typeid:
if (&out.flush) out.flush()!;
$case $defined(out.flush):
@@ -193,7 +191,7 @@ macro void print(x)
<*
Print any value to stdout, appending an '\n after.
@param x "The value to print"
@param x : "The value to print"
*>
macro void printn(x = "")
{
@@ -211,7 +209,7 @@ macro void eprint(x)
<*
Print any value to stderr, appending an '\n after.
@param x "The value to print"
@param x : "The value to print"
*>
macro void eprintn(x)
{
@@ -219,15 +217,22 @@ macro void eprintn(x)
}
fn void! out_putstream_fn(void* data, char c) @private
fn void? out_putstream_fn(void* data, char c) @private
{
OutStream* stream = data;
return (*stream).write_byte(c);
}
fn void! out_putchar_fn(void* data @unused, char c) @private
fn void? out_putchar_fn(void* data @unused, char c) @private
{
libc::putchar(c);
$if env::TESTING:
// HACK: this is used for the purpose of unit test output hijacking
File* stdout = io::stdout();
assert(stdout.file);
libc::fputc(c, stdout.file);
$else
libc::putchar(c);
$endif
}
<*
@@ -241,10 +246,10 @@ fn void! out_putchar_fn(void* data @unused, char c) @private
To create a custom output for a type, implement
the Printable interface.
@param [in] format `The printf-style format string`
@param [in] format : `The printf-style format string`
@return `the number of characters printed`
*>
fn usz! printf(String format, args...) @maydiscard
fn usz? printf(String format, args...) @format(0) @maydiscard
{
Formatter formatter;
formatter.init(&out_putchar_fn);
@@ -255,15 +260,15 @@ fn usz! printf(String format, args...) @maydiscard
Prints using a 'printf'-style formatting string,
appending '\n' at the end. See `printf`.
@param [in] format `The printf-style format string`
@param [in] format : `The printf-style format string`
@return `the number of characters printed`
*>
fn usz! printfn(String format, args...) @maydiscard
fn usz? printfn(String format, args...) @format(0) @maydiscard
{
Formatter formatter;
formatter.init(&out_putchar_fn);
usz! len = formatter.vprintf(format, args);
putchar('\n');
usz? len = formatter.vprintf(format, args);
out_putchar_fn(null, '\n')!;
io::stdout().flush()!;
return len + 1;
}
@@ -272,10 +277,10 @@ fn usz! printfn(String format, args...) @maydiscard
Prints using a 'printf'-style formatting string
to stderr.
@param [in] format `The printf-style format string`
@param [in] format : `The printf-style format string`
@return `the number of characters printed`
*>
fn usz! eprintf(String format, args...) @maydiscard
fn usz? eprintf(String format, args...) @maydiscard
{
Formatter formatter;
OutStream stream = stderr();
@@ -288,15 +293,15 @@ fn usz! eprintf(String format, args...) @maydiscard
Prints using a 'printf'-style formatting string,
to stderr appending '\n' at the end. See `printf`.
@param [in] format `The printf-style format string`
@param [in] format : `The printf-style format string`
@return `the number of characters printed`
*>
fn usz! eprintfn(String format, args...) @maydiscard
fn usz? eprintfn(String format, args...) @maydiscard
{
Formatter formatter;
OutStream stream = stderr();
formatter.init(&out_putstream_fn, &stream);
usz! len = formatter.vprintf(format, args);
usz? len = formatter.vprintf(format, args);
stderr().write_byte('\n')!;
stderr().flush()!;
return len + 1;
@@ -306,11 +311,11 @@ fn usz! eprintfn(String format, args...) @maydiscard
Prints using a 'printf'-style formatting string,
to a string buffer. See `printf`.
@param [inout] buffer `The buffer to print to`
@param [in] format `The printf-style format string`
@param [inout] buffer : `The buffer to print to`
@param [in] format : `The printf-style format string`
@return `a slice formed from the "buffer" with the resulting length.`
*>
fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
fn char[]? bprintf(char[] buffer, String format, args...) @maydiscard
{
Formatter formatter;
BufferData data = { .buffer = buffer };
@@ -320,10 +325,10 @@ fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
}
// Used to print to a buffer.
fn void! out_buffer_fn(void *data, char c) @private
fn void? out_buffer_fn(void *data, char c) @private
{
BufferData *buffer_data = data;
if (buffer_data.written >= buffer_data.buffer.len) return PrintFault.BUFFER_EXCEEDED?;
if (buffer_data.written >= buffer_data.buffer.len) return BUFFER_EXCEEDED?;
buffer_data.buffer[buffer_data.written++] = c;
}

View File

@@ -1,20 +1,20 @@
module std::io::os;
import std::io::path, libc, std::os;
macro void! native_chdir(Path path)
macro void? native_chdir(Path path)
{
$switch
$switch:
$case env::POSIX:
if (posix::chdir(path.as_zstr()))
{
switch (libc::errno())
{
case errno::EACCES: return IoError.NO_PERMISSION?;
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
case errno::ENOTDIR: return IoError.FILE_NOT_DIR?;
case errno::ENOENT: return IoError.FILE_NOT_FOUND?;
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
default: return IoError.GENERAL_ERROR?;
case errno::EACCES: return io::NO_PERMISSION?;
case errno::ENAMETOOLONG: return io::NAME_TOO_LONG?;
case errno::ENOTDIR: return io::FILE_NOT_DIR?;
case errno::ENOENT: return io::FILE_NOT_FOUND?;
case errno::ELOOP: return io::SYMLINK_FAILED?;
default: return io::GENERAL_ERROR?;
}
}
$case env::WIN32:
@@ -23,8 +23,8 @@ macro void! native_chdir(Path path)
// TODO improve with better error handling.
if (win32::setCurrentDirectoryW(path.str_view().to_temp_utf16()!!)) return;
};
return IoError.GENERAL_ERROR?;
return io::GENERAL_ERROR?;
$default:
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
$endswitch
}

View File

@@ -5,130 +5,121 @@ import libc;
@require mode.len > 0
@require filename.len > 0
*>
fn void*! native_fopen(String filename, String mode) @inline
fn void*? native_fopen(String filename, String mode) @inline => @pool()
{
@pool()
{
$if env::WIN32:
void* file = libc::_wfopen(filename.to_temp_wstring(), mode.to_temp_wstring())!;
$else
void* file = libc::fopen(filename.zstr_tcopy(), mode.zstr_tcopy());
$endif
return file ?: file_open_errno()?;
};
$if env::WIN32:
void* file = libc::_wfopen(filename.to_temp_wstring(), mode.to_temp_wstring())!;
$else
void* file = libc::fopen(filename.zstr_tcopy(), mode.zstr_tcopy());
$endif
return file ?: file_open_errno()?;
}
fn void! native_remove(String filename)
fn void? native_remove(String filename) => @pool()
{
@pool()
$if env::WIN32:
CInt result = libc::_wremove(filename.to_temp_wstring())!;
$else
CInt result = libc::remove(filename.zstr_tcopy());
$endif
if (result)
{
$if env::WIN32:
CInt result = libc::_wremove(filename.to_temp_wstring())!;
$else
CInt result = libc::remove(filename.zstr_tcopy());
$endif
if (result)
switch (libc::errno())
{
switch (libc::errno())
{
case errno::ENOENT:
return IoError.FILE_NOT_FOUND?;
case errno::EACCES:
default:
return IoError.FILE_CANNOT_DELETE?;
}
case errno::ENOENT:
return io::FILE_NOT_FOUND?;
case errno::EACCES:
default:
return io::FILE_CANNOT_DELETE?;
}
};
}
}
<*
@require mode.len > 0
@require filename.len > 0
*>
fn void*! native_freopen(void* file, String filename, String mode) @inline
fn void*? native_freopen(void* file, String filename, String mode) @inline => @pool()
{
@pool()
{
$if env::WIN32:
file = libc::_wfreopen(filename.to_temp_wstring(), mode.to_temp_wstring(), file)!;
$else
file = libc::freopen(filename.zstr_tcopy(), mode.zstr_tcopy(), file);
$endif
return file ?: file_open_errno()?;
};
$if env::WIN32:
file = libc::_wfreopen(filename.to_temp_wstring(), mode.to_temp_wstring(), file)!;
$else
file = libc::freopen(filename.zstr_tcopy(), mode.zstr_tcopy(), file);
$endif
return file ?: file_open_errno()?;
}
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
fn void? native_fseek(void* file, isz offset, Seek seek_mode) @inline
{
if (libc::fseek(file, (SeekIndex)offset, (CInt)seek_mode)) return file_seek_errno()?;
if (libc::fseek(file, (SeekIndex)offset, seek_mode.ordinal)) return file_seek_errno()?;
}
fn usz! native_ftell(CFile file) @inline
fn usz? native_ftell(CFile file) @inline
{
long index = libc::ftell(file);
return index >= 0 ? (usz)index : file_seek_errno()?;
}
fn usz! native_fwrite(CFile file, char[] buffer) @inline
fn usz? native_fwrite(CFile file, char[] buffer) @inline
{
return libc::fwrite(buffer.ptr, 1, buffer.len, file);
}
fn void! native_fputc(CInt c, CFile stream) @inline
fn void? native_fputc(CInt c, CFile stream) @inline
{
if (!libc::fputc(c, stream)) return IoError.EOF?;
if (libc::fputc(c, stream) == libc::EOF) return io::EOF?;
}
fn usz! native_fread(CFile file, char[] buffer) @inline
fn usz? native_fread(CFile file, char[] buffer) @inline
{
return libc::fread(buffer.ptr, 1, buffer.len, file);
}
macro anyfault file_open_errno() @local
macro fault file_open_errno() @local
{
switch (libc::errno())
{
case errno::EACCES: return IoError.NO_PERMISSION;
case errno::EDQUOT: return IoError.OUT_OF_SPACE;
case errno::EBADF: return IoError.FILE_NOT_VALID;
case errno::EEXIST: return IoError.ALREADY_EXISTS;
case errno::EINTR: return IoError.INTERRUPTED;
case errno::EFAULT: return IoError.GENERAL_ERROR;
case errno::EISDIR: return IoError.FILE_IS_DIR;
case errno::ELOOP: return IoError.SYMLINK_FAILED;
case errno::EMFILE: return IoError.TOO_MANY_DESCRIPTORS;
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG;
case errno::ENFILE: return IoError.OUT_OF_SPACE;
case errno::ENOTDIR: return IoError.FILE_NOT_DIR;
case errno::ENOENT: return IoError.FILE_NOT_FOUND;
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
case errno::EOVERFLOW: return IoError.OVERFLOW;
case errno::EROFS: return IoError.READ_ONLY;
case errno::EOPNOTSUPP: return IoError.UNSUPPORTED_OPERATION;
case errno::EIO: return IoError.INCOMPLETE_WRITE;
case errno::EWOULDBLOCK: return IoError.WOULD_BLOCK;
default: return IoError.UNKNOWN_ERROR;
case errno::EACCES: return io::NO_PERMISSION;
case errno::EDQUOT: return io::OUT_OF_SPACE;
case errno::EBADF: return io::FILE_NOT_VALID;
case errno::EEXIST: return io::ALREADY_EXISTS;
case errno::EINTR: return io::INTERRUPTED;
case errno::EFAULT: return io::GENERAL_ERROR;
case errno::EISDIR: return io::FILE_IS_DIR;
case errno::ELOOP: return io::SYMLINK_FAILED;
case errno::EMFILE: return io::TOO_MANY_DESCRIPTORS;
case errno::ENAMETOOLONG: return io::NAME_TOO_LONG;
case errno::ENFILE: return io::OUT_OF_SPACE;
case errno::ENOTDIR: return io::FILE_NOT_DIR;
case errno::ENOENT: return io::FILE_NOT_FOUND;
case errno::ENOSPC: return io::OUT_OF_SPACE;
case errno::ENXIO: return io::FILE_NOT_FOUND;
case errno::EOVERFLOW: return io::OVERFLOW;
case errno::EROFS: return io::READ_ONLY;
case errno::EOPNOTSUPP: return io::UNSUPPORTED_OPERATION;
case errno::EIO: return io::INCOMPLETE_WRITE;
case errno::EWOULDBLOCK: return io::WOULD_BLOCK;
default: return io::UNKNOWN_ERROR;
}
}
macro anyfault file_seek_errno() @local
macro fault file_seek_errno() @local
{
switch (libc::errno())
{
case errno::ESPIPE: return IoError.FILE_IS_PIPE;
case errno::EPIPE: return IoError.FILE_IS_PIPE;
case errno::EOVERFLOW: return IoError.OVERFLOW;
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
case errno::EIO: return IoError.INCOMPLETE_WRITE;
case errno::EINVAL: return IoError.INVALID_POSITION;
case errno::EINTR: return IoError.INTERRUPTED;
case errno::EFBIG: return IoError.OUT_OF_SPACE;
case errno::EBADF: return IoError.FILE_NOT_VALID;
case errno::EAGAIN: return IoError.WOULD_BLOCK;
default: return IoError.UNKNOWN_ERROR;
case errno::ESPIPE: return io::FILE_IS_PIPE;
case errno::EPIPE: return io::FILE_IS_PIPE;
case errno::EOVERFLOW: return io::OVERFLOW;
case errno::ENXIO: return io::FILE_NOT_FOUND;
case errno::ENOSPC: return io::OUT_OF_SPACE;
case errno::EIO: return io::INCOMPLETE_WRITE;
case errno::EINVAL: return io::INVALID_POSITION;
case errno::EINTR: return io::INTERRUPTED;
case errno::EFBIG: return io::OUT_OF_SPACE;
case errno::EBADF: return io::FILE_NOT_VALID;
case errno::EAGAIN: return io::WOULD_BLOCK;
default: return io::UNKNOWN_ERROR;
}
}

View File

@@ -1,15 +1,15 @@
module std::io::os @if(env::NO_LIBC);
import libc;
def FopenFn = fn void*!(String, String);
def FreopenFn = fn void*!(void*, String, String);
def FcloseFn = fn void!(void*);
def FseekFn = fn void!(void*, isz, Seek);
def FtellFn = fn usz!(void*);
def FwriteFn = fn usz!(void*, char[] buffer);
def FreadFn = fn usz!(void*, char[] buffer);
def RemoveFn = fn void!(String);
def FputcFn = fn void!(int, void*);
alias FopenFn = fn void*?(String, String);
alias FreopenFn = fn void*?(void*, String, String);
alias FcloseFn = fn void?(void*);
alias FseekFn = fn void?(void*, isz, Seek);
alias FtellFn = fn usz?(void*);
alias FwriteFn = fn usz?(void*, char[] buffer);
alias FreadFn = fn usz?(void*, char[] buffer);
alias RemoveFn = fn void?(String);
alias FputcFn = fn void?(int, void*);
FopenFn native_fopen_fn @weak @if(!$defined(native_fopen_fn));
FcloseFn native_fclose_fn @weak @if(!$defined(native_fclose_fn));
@@ -25,10 +25,10 @@ FputcFn native_fputc_fn @weak @if(!$defined(native_fputc_fn));
@require mode.len > 0
@require filename.len > 0
*>
fn void*! native_fopen(String filename, String mode) @inline
fn void*? native_fopen(String filename, String mode) @inline
{
if (native_fopen_fn) return native_fopen_fn(filename, mode);
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
}
<*
@@ -36,48 +36,48 @@ fn void*! native_fopen(String filename, String mode) @inline
@require filename.len > 0
*>
fn void! native_remove(String filename) @inline
fn void? native_remove(String filename) @inline
{
if (native_remove_fn) return native_remove_fn(filename);
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
}
<*
@require mode.len > 0
@require filename.len > 0
*>
fn void*! native_freopen(void* file, String filename, String mode) @inline
fn void*? native_freopen(void* file, String filename, String mode) @inline
{
if (native_freopen_fn) return native_freopen_fn(file, filename, mode);
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
}
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
fn void? native_fseek(void* file, isz offset, Seek seek_mode) @inline
{
if (native_fseek_fn) return native_fseek_fn(file, offset, seek_mode);
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
}
fn usz! native_ftell(CFile file) @inline
fn usz? native_ftell(CFile file) @inline
{
if (native_ftell_fn) return native_ftell_fn(file);
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
}
fn usz! native_fwrite(CFile file, char[] buffer) @inline
fn usz? native_fwrite(CFile file, char[] buffer) @inline
{
if (native_fwrite_fn) return native_fwrite_fn(file, buffer);
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
}
fn usz! native_fread(CFile file, char[] buffer) @inline
fn usz? native_fread(CFile file, char[] buffer) @inline
{
if (native_fread_fn) return native_fread_fn(file, buffer);
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
}
fn void! native_fputc(CInt c, CFile stream) @inline
fn void? native_fputc(CInt c, CFile stream) @inline
{
if (native_fputc_fn) return native_fputc_fn(c, stream);
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
}

View File

@@ -1,66 +1,60 @@
module std::io::os;
import libc, std::os, std::io;
fn void! native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX || env::BSD_FAMILY)
fn void? native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX || env::BSD_FAMILY) => @pool()
{
@pool()
$if env::DARWIN || env::LINUX || env::BSD_FAMILY:
int res = libc::stat(path.zstr_tcopy(), stat);
$else
unreachable("Stat unimplemented");
int res = 0;
$endif
if (res != 0)
{
$if env::DARWIN || env::LINUX || env::BSD_FAMILY:
int res = libc::stat(path.zstr_tcopy(), stat);
$else
unreachable("Stat unimplemented");
int res = 0;
$endif
if (res != 0)
switch (libc::errno())
{
switch (libc::errno())
{
case errno::EBADF:
return IoError.FILE_NOT_VALID?;
case errno::EFAULT:
unreachable("Invalid stat");
case errno::EIO:
return IoError.GENERAL_ERROR?;
case errno::EACCES:
return IoError.NO_PERMISSION?;
case errno::ELOOP:
return IoError.NO_PERMISSION?;
case errno::ENAMETOOLONG:
return IoError.NAME_TOO_LONG?;
case errno::ENOENT:
return IoError.FILE_NOT_FOUND?;
case errno::ENOTDIR:
return IoError.FILE_NOT_DIR?;
case errno::EOVERFLOW:
return IoError.GENERAL_ERROR?;
default:
return IoError.UNKNOWN_ERROR?;
}
case errno::EBADF:
return io::FILE_NOT_VALID?;
case errno::EFAULT:
unreachable("Invalid stat");
case errno::EIO:
return io::GENERAL_ERROR?;
case errno::EACCES:
return io::NO_PERMISSION?;
case errno::ELOOP:
return io::NO_PERMISSION?;
case errno::ENAMETOOLONG:
return io::NAME_TOO_LONG?;
case errno::ENOENT:
return io::FILE_NOT_FOUND?;
case errno::ENOTDIR:
return io::FILE_NOT_DIR?;
case errno::EOVERFLOW:
return io::GENERAL_ERROR?;
default:
return io::UNKNOWN_ERROR?;
}
};
}
}
fn usz! native_file_size(String path) @if(env::WIN32)
fn usz? native_file_size(String path) @if(env::WIN32) => @pool()
{
@pool()
{
Win32_FILE_ATTRIBUTE_DATA data;
win32::getFileAttributesExW(path.to_temp_wstring()!, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data);
Win32_LARGE_INTEGER size;
size.lowPart = data.nFileSizeLow;
size.highPart = data.nFileSizeHigh;
return (usz)size.quadPart;
};
Win32_FILE_ATTRIBUTE_DATA data;
win32::getFileAttributesExW(path.to_temp_wstring()!, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data);
Win32_LARGE_INTEGER size;
size.lowPart = data.nFileSizeLow;
size.highPart = data.nFileSizeHigh;
return (usz)size.quadPart;
}
fn usz! native_file_size(String path) @if(!env::WIN32 && !env::DARWIN)
fn usz? native_file_size(String path) @if(!env::WIN32 && !env::DARWIN)
{
File f = file::open(path, "r")!;
defer (void)f.close();
return f.seek(0, Seek.END)!;
}
fn usz! native_file_size(String path) @if(env::DARWIN)
fn usz? native_file_size(String path) @if(env::DARWIN)
{
Stat stat;
native_stat(&stat, path)!;
@@ -69,7 +63,7 @@ fn usz! native_file_size(String path) @if(env::DARWIN)
fn bool native_file_or_dir_exists(String path)
{
$switch
$switch:
$case env::DARWIN:
$case env::FREEBSD:
$case env::NETBSD:
@@ -94,7 +88,7 @@ fn bool native_file_or_dir_exists(String path)
fn bool native_is_file(String path)
{
$switch
$switch:
$case env::DARWIN:
$case env::FREEBSD:
$case env::NETBSD:
@@ -103,7 +97,7 @@ fn bool native_is_file(String path)
Stat stat;
return @ok(native_stat(&stat, path)) && libc_S_ISTYPE(stat.st_mode, libc::S_IFREG);
$default:
File! f = file::open(path, "r");
File? f = file::open(path, "r");
defer (void)f.close();
return @ok(f);
$endswitch

View File

@@ -1,9 +1,9 @@
module std::io::os;
import libc, std::os;
macro String! getcwd(Allocator allocator = allocator::heap())
macro String? getcwd(Allocator allocator)
{
$switch
$switch:
$case env::WIN32:
const DEFAULT_BUFFER = 256;
Char16[DEFAULT_BUFFER] buffer;
@@ -12,12 +12,12 @@ macro String! getcwd(Allocator allocator = allocator::heap())
defer if (free) libc::free(res);
if (!res)
{
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR?;
if (libc::errno() != errno::ERANGE) return io::GENERAL_ERROR?;
res = win32::_wgetcwd(null, 0);
free = true;
}
Char16[] str16 = res[:win32::wcslen(res)];
return string::new_from_utf16(str16, allocator);
return string::from_utf16(allocator, str16);
$case env::POSIX:
const usz DEFAULT_BUFFER = 256;
@@ -27,7 +27,7 @@ macro String! getcwd(Allocator allocator = allocator::heap())
if (!res)
{
// Improve error
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR?;
if (libc::errno() != errno::ERANGE) return io::GENERAL_ERROR?;
res = posix::getcwd(null, 0);
free = true;
}
@@ -35,7 +35,7 @@ macro String! getcwd(Allocator allocator = allocator::heap())
return res.copy(allocator);
$default:
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
$endswitch
}

View File

@@ -1,13 +1,13 @@
module std::io::os @if(env::POSIX);
import std::io, std::os;
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
fn PathList? native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
{
PathList list;
list.new_init(allocator: allocator);
list.init(allocator);
DIRPtr directory = posix::opendir(dir.str_view() ? dir.as_zstr() : (ZString)".");
defer if (directory) posix::closedir(directory);
if (!directory) return (path::is_dir(dir) ? IoError.CANNOT_READ_DIR : IoError.FILE_NOT_DIR)?;
if (!directory) return (path::is_dir(dir) ? io::CANNOT_READ_DIR : io::FILE_NOT_DIR)?;
Posix_dirent* entry;
while ((entry = posix::readdir(directory)))
{
@@ -15,7 +15,7 @@ fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Al
if (!name || name == "." || name == "..") continue;
if (entry.d_type == posix::DT_LNK && no_symlinks) continue;
if (entry.d_type == posix::DT_DIR && no_dirs) continue;
Path path = path::new(name, allocator)!!;
Path path = path::new(allocator, name)!!;
list.push(path);
}
return list;
@@ -24,26 +24,26 @@ fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Al
module std::io::os @if(env::WIN32);
import std::time, std::os, std::io;
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
fn PathList? native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
{
PathList list;
list.new_init(allocator: allocator);
list.init(allocator);
@pool(allocator)
@pool()
{
WString result = dir.str_view().tconcat(`\*`).to_temp_wstring()!!;
Win32_WIN32_FIND_DATAW find_data;
Win32_HANDLE find = win32::findFirstFileW(result, &find_data);
if (find == win32::INVALID_HANDLE_VALUE) return IoError.CANNOT_READ_DIR?;
if (find == win32::INVALID_HANDLE_VALUE) return io::CANNOT_READ_DIR?;
defer win32::findClose(find);
do
{
if (no_dirs && (find_data.dwFileAttributes & win32::FILE_ATTRIBUTE_DIRECTORY)) continue;
@pool(allocator)
@pool()
{
String filename = string::temp_from_wstring((WString)&find_data.cFileName)!;
String filename = string::tfrom_wstring((WString)&find_data.cFileName)!;
if (filename == ".." || filename == ".") continue;
list.push(path::new(filename, allocator)!);
list.push(path::new(allocator, filename)!);
};
} while (win32::findNextFileW(find, &find_data));
return list;

View File

@@ -5,9 +5,9 @@ import std::os::win32;
import std::os::posix;
macro bool! native_mkdir(Path path, MkdirPermissions permissions)
macro bool? native_mkdir(Path path, MkdirPermissions permissions)
{
$switch
$switch:
$case env::POSIX:
if (!posix::mkdir(path.as_zstr(), permissions == NORMAL ? 0o777 : 0o700)) return true;
switch (libc::errno())
@@ -15,15 +15,15 @@ macro bool! native_mkdir(Path path, MkdirPermissions permissions)
case errno::EACCES:
case errno::EPERM:
case errno::EROFS:
case errno::EFAULT: return IoError.NO_PERMISSION?;
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
case errno::EFAULT: return io::NO_PERMISSION?;
case errno::ENAMETOOLONG: return io::NAME_TOO_LONG?;
case errno::EDQUOT:
case errno::ENOSPC: return IoError.OUT_OF_SPACE?;
case errno::ENOSPC: return io::OUT_OF_SPACE?;
case errno::EISDIR:
case errno::EEXIST: return false;
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
case errno::ENOTDIR: return IoError.FILE_NOT_FOUND?;
default: return IoError.GENERAL_ERROR?;
case errno::ELOOP: return io::SYMLINK_FAILED?;
case errno::ENOTDIR: return io::FILE_NOT_FOUND?;
default: return io::GENERAL_ERROR?;
}
$case env::WIN32:
@pool()
@@ -33,18 +33,18 @@ macro bool! native_mkdir(Path path, MkdirPermissions permissions)
switch (win32::getLastError())
{
case win32::ERROR_ACCESS_DENIED:
return IoError.NO_PERMISSION?;
return io::NO_PERMISSION?;
case win32::ERROR_DISK_FULL:
return IoError.OUT_OF_SPACE?;
return io::OUT_OF_SPACE?;
case win32::ERROR_ALREADY_EXISTS:
return false;
case win32::ERROR_PATH_NOT_FOUND:
return IoError.FILE_NOT_FOUND?;
return io::FILE_NOT_FOUND?;
default:
return IoError.GENERAL_ERROR?;
return io::GENERAL_ERROR?;
}
};
$default:
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
$endswitch
}

View File

@@ -4,24 +4,24 @@ import std::io::path;
import std::os::win32;
import std::os::posix;
macro bool! native_rmdir(Path path)
macro bool? native_rmdir(Path path)
{
$switch
$switch:
$case env::POSIX:
if (!posix::rmdir(path.as_zstr())) return true;
switch (libc::errno())
{
case errno::EBUSY: return IoError.BUSY?;
case errno::EBUSY: return io::BUSY?;
case errno::EACCES:
case errno::EPERM:
case errno::EROFS:
case errno::EFAULT: return IoError.NO_PERMISSION?;
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
case errno::EFAULT: return io::NO_PERMISSION?;
case errno::ENAMETOOLONG: return io::NAME_TOO_LONG?;
case errno::ENOTDIR:
case errno::ENOENT: return false;
case errno::ENOTEMPTY: return IoError.DIR_NOT_EMPTY?;
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
default: return IoError.GENERAL_ERROR?;
case errno::ENOTEMPTY: return io::DIR_NOT_EMPTY?;
case errno::ELOOP: return io::SYMLINK_FAILED?;
default: return io::GENERAL_ERROR?;
}
$case env::WIN32:
@pool()
@@ -30,19 +30,19 @@ macro bool! native_rmdir(Path path)
switch (win32::getLastError())
{
case win32::ERROR_ACCESS_DENIED:
return IoError.NO_PERMISSION?;
return io::NO_PERMISSION?;
case win32::ERROR_CURRENT_DIRECTORY:
return IoError.BUSY?;
return io::BUSY?;
case win32::ERROR_DIR_NOT_EMPTY:
return IoError.DIR_NOT_EMPTY?;
return io::DIR_NOT_EMPTY?;
case win32::ERROR_DIRECTORY:
case win32::ERROR_PATH_NOT_FOUND:
return false;
default:
return IoError.GENERAL_ERROR?;
return io::GENERAL_ERROR?;
}
};
$default:
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
$endswitch
}

View File

@@ -2,13 +2,13 @@ module std::io::os @if(env::POSIX);
import std::io, std::os, libc;
<*
@require dir.str_view()
@require dir.str_view().len > 0
*>
fn void! native_rmtree(Path dir)
fn void? native_rmtree(Path dir)
{
DIRPtr directory = posix::opendir(dir.as_zstr());
defer if (directory) posix::closedir(directory);
if (!directory) return path::is_dir(dir) ? IoError.CANNOT_READ_DIR? : IoError.FILE_NOT_DIR?;
if (!directory) return path::is_dir(dir) ? io::CANNOT_READ_DIR? : io::FILE_NOT_DIR?;
Posix_dirent* entry;
while ((entry = posix::readdir(directory)))
{
@@ -16,7 +16,7 @@ fn void! native_rmtree(Path dir)
{
String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue;
Path new_path = dir.temp_append(name)!;
Path new_path = dir.tappend(name)!;
if (entry.d_type == posix::DT_DIR)
{
native_rmtree(new_path)!;
@@ -25,7 +25,7 @@ fn void! native_rmtree(Path dir)
if (libc::remove(new_path.as_zstr()))
{
// TODO improve
return IoError.GENERAL_ERROR?;
return io::GENERAL_ERROR?;
}
};
}
@@ -35,21 +35,21 @@ fn void! native_rmtree(Path dir)
module std::io::os @if(env::WIN32);
import std::io, std::time, std::os;
fn void! native_rmtree(Path path)
fn void? native_rmtree(Path path)
{
Win32_WIN32_FIND_DATAW find_data;
String s = path.str_view().tconcat("\\*");
Win32_HANDLE find = win32::findFirstFileW(s.to_temp_utf16(), &find_data)!;
if (find == win32::INVALID_HANDLE_VALUE) return IoError.CANNOT_READ_DIR?;
if (find == win32::INVALID_HANDLE_VALUE) return io::CANNOT_READ_DIR?;
defer win32::findClose(find);
do
{
@pool()
{
String filename = string::new_from_wstring((WString)&find_data.cFileName, allocator::temp())!;
String filename = string::tfrom_wstring((WString)&find_data.cFileName)!;
if (filename == "." || filename == "..") continue;
Path file_path = path.temp_append(filename)!;
Path file_path = path.tappend(filename)!;
if (find_data.dwFileAttributes & win32::FILE_ATTRIBUTE_DIRECTORY)
{
native_rmtree(file_path)!;

View File

@@ -1,32 +1,29 @@
module std::io::os @if(env::LIBC);
import std::io::path, std::os;
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(!env::WIN32)
fn Path? native_temp_directory(Allocator allocator) @if(!env::WIN32)
{
foreach (String env : { "TMPDIR", "TMP", "TEMP", "TEMPDIR" })
{
String tmpdir = env::get_var(env) ?? "";
if (tmpdir) return path::new(tmpdir, allocator);
String tmpdir = env::tget_var(env) ?? "";
if (tmpdir) return path::new(allocator, tmpdir);
}
return path::new("/tmp", allocator);
return path::new(allocator, "/tmp");
}
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(env::WIN32)
fn Path? native_temp_directory(Allocator allocator) @if(env::WIN32) => @pool()
{
@pool(allocator)
{
Win32_DWORD len = win32::getTempPathW(0, null);
if (!len) return IoError.GENERAL_ERROR?;
Char16[] buff = mem::temp_alloc_array(Char16, len + (usz)1);
if (!win32::getTempPathW(len, buff)) return IoError.GENERAL_ERROR?;
return path::new(string::temp_from_utf16(buff[:len]), allocator);
};
Win32_DWORD len = win32::getTempPathW(0, null);
if (!len) return io::GENERAL_ERROR?;
Char16[] buff = mem::talloc_array(Char16, len + (usz)1);
if (!win32::getTempPathW(len, buff)) return io::GENERAL_ERROR?;
return path::new(allocator, string::tfrom_utf16(buff[:len]));
}
module std::io::os @if(env::NO_LIBC);
import std::io::path;
macro Path! native_temp_directory(Allocator allocator = allocator::heap())
macro Path? native_temp_directory(Allocator allocator)
{
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
}

View File

@@ -2,25 +2,22 @@ module std::io::path;
import std::collections::list, std::io::os;
import std::os::win32;
const PathEnv DEFAULT_PATH_ENV = env::WIN32 ? PathEnv.WIN32 : PathEnv.POSIX;
const PathEnv DEFAULT_ENV = env::WIN32 ? PathEnv.WIN32 : PathEnv.POSIX;
const char PREFERRED_SEPARATOR_WIN32 = '\\';
const char PREFERRED_SEPARATOR_POSIX = '/';
const char PREFERRED_SEPARATOR = env::WIN32 ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX;
def PathList = List(<Path>);
alias PathList = List { Path };
fault PathResult
{
INVALID_PATH,
NO_PARENT,
}
faultdef INVALID_PATH, NO_PARENT;
def Path = PathImp;
alias Path = PathImp;
struct PathImp (Printable)
{
String path_string;
PathEnv env;
Allocator allocator;
}
enum PathEnv
@@ -29,63 +26,55 @@ enum PathEnv
POSIX
}
fn Path! new_cwd(Allocator allocator = allocator::heap())
fn Path? cwd(Allocator allocator)
{
@pool(allocator)
@pool()
{
return new(os::getcwd(allocator::temp()), allocator);
};
}
fn Path! getcwd(Allocator allocator = allocator::heap()) @deprecated("Use new_cwd()")
{
@pool(allocator)
{
return new(os::getcwd(allocator::temp()), allocator);
return new(allocator, os::getcwd(tmem));
};
}
fn bool is_dir(Path path) => os::native_is_dir(path.str_view());
fn bool is_file(Path path) => os::native_is_file(path.str_view());
fn usz! file_size(Path path) => os::native_file_size(path.str_view());
fn usz? file_size(Path path) => os::native_file_size(path.str_view());
fn bool exists(Path path) => os::native_file_or_dir_exists(path.str_view());
fn Path! temp_cwd() => new_cwd(allocator::temp()) @inline;
fn Path! tgetcwd() @deprecated("Use temp_cwd()") => new_cwd(allocator::temp()) @inline;
fn void! chdir(Path path) => os::native_chdir(path) @inline;
fn Path! temp_directory(Allocator allocator = allocator::heap()) => os::native_temp_directory(allocator);
fn void! delete(Path path) => os::native_remove(path.str_view()) @inline;
fn Path? tcwd() => cwd(tmem) @inline;
macro bool is_separator(char c, PathEnv path_env = DEFAULT_PATH_ENV)
<*
@require @is_pathlike(path) : "Expected a Path or String to chdir"
*>
macro void? chdir(path)
{
$if @typeis(path, String):
@pool()
{
return os::native_chdir(temp(path));
};
$else
return os::native_chdir(path) @inline;
$endif
}
fn Path? temp_directory(Allocator allocator) => os::native_temp_directory(allocator);
fn void? delete(Path path) => os::native_remove(path.str_view()) @inline;
macro bool @is_pathlike(#path) => @typeis(#path, String) || @typeis(#path, Path);
macro bool is_separator(char c, PathEnv path_env = DEFAULT_ENV)
{
return c == '/' || (c == '\\' && path_env == PathEnv.WIN32);
}
macro bool is_posix_separator(char c)
{
return c == '/' || c == '\\';
}
macro bool is_posix_separator(char c) => c == '/';
macro bool is_win32_separator(char c) => c == '/' || c == '\\';
macro bool is_win32_separator(char c)
{
return c == '/' || c == '\\';
}
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap()) @deprecated("use new_ls")
{
return new_ls(dir, no_dirs, no_symlinks, mask, allocator);
}
fn PathList! temp_ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "")
{
return new_ls(dir, no_dirs, no_symlinks, mask, allocator::temp()) @inline;
}
fn PathList! new_ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap())
fn PathList? ls(Allocator allocator, Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "")
{
$if $defined(os::native_ls):
return os::native_ls(dir, no_dirs, no_symlinks, mask, allocator);
$else
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
$endif
}
@@ -99,87 +88,84 @@ enum MkdirPermissions
<*
Create a directory on a given path, optionally recursive.
@param path `The path to create`
@param recursive `If directories in between should be created if they're missing, defaults to false`
@param permissions `The permissions to set on the directory`
@param path : `The path to create`
@require @is_pathlike(path) : "Expected a Path or String to chdir"
@param recursive : `If directories in between should be created if they're missing, defaults to false`
@param permissions : `The permissions to set on the directory`
*>
fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
macro bool? mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
if (is_dir(path)) return false;
if (exists(path)) return IoError.FILE_NOT_DIR?;
if (recursive)
{
if (try parent = path.parent()) mkdir(parent, true, permissions)!;
}
if (!is_dir(path.parent()) ?? false) return IoError.CANNOT_READ_DIR?;
return os::native_mkdir(path, permissions);
$if @typeis(path, String):
@pool() { return _mkdir(temp(path), recursive, permissions); };
$else
return _mkdir(path, recursive, permissions);
$endif
}
<*
Tries to delete directory, which must be empty.
@param path `The path to delete`
@param path : `The path to delete`
@require @is_pathlike(path) : "Expected a Path or String to chdir"
@return `true if there was a directory to delete, false otherwise`
@return! PathResult.INVALID_PATH `if the path was invalid`
@return? INVALID_PATH : `if the path was invalid`
*>
fn bool! rmdir(Path path)
macro bool? rmdir(path)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
return os::native_rmdir(path);
$if @typeis(path, String):
@pool() { return _rmdir(temp(path)); };
$else
return _mkdir(path);
$endif
}
<*
Like [rmdir] but deletes a directory even if it contains items.
*>
fn void! rmtree(Path path)
fn void? rmtree(Path path)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
if (!path.path_string.len) return INVALID_PATH?;
$if $defined(os::native_rmtree):
return os::native_rmtree(path);
$else
return IoError.UNSUPPORTED_OPERATION?;
return io::UNSUPPORTED_OPERATION?;
$endif
}
<*
Creates a new path.
@return! PathResult.INVALID_PATH `if the path was invalid`
@return? INVALID_PATH : `if the path was invalid`
*>
fn Path! new(String path, Allocator allocator = allocator::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
fn Path? new(Allocator allocator, String path, PathEnv path_env = DEFAULT_ENV)
{
return { normalize(path.copy(allocator), path_env), path_env };
return { normalize(path.copy(allocator), path_env), path_env, allocator };
}
<*
Creates a new path using the temp allocator.
@return! PathResult.INVALID_PATH `if the path was invalid`
@return? INVALID_PATH : `if the path was invalid`
*>
fn Path! temp_new(String path, PathEnv path_env = DEFAULT_PATH_ENV)
fn Path? temp(String path, PathEnv path_env = DEFAULT_ENV)
{
return new(path, allocator::temp(), path_env);
return new(tmem, path, path_env);
}
fn Path! new_win32_wstring(WString path, Allocator allocator = allocator::heap())
fn Path? from_win32_wstring(Allocator allocator, WString path) => @pool()
{
@pool(allocator)
{
return path::new(string::temp_from_wstring(path)!, allocator: allocator);
};
return path::new(allocator, string::tfrom_wstring(path)!);
}
fn Path! new_windows(String path, Allocator allocator = allocator::heap())
fn Path? for_windows(Allocator allocator, String path)
{
return new(path, allocator, WIN32);
return new(allocator, path, WIN32);
}
fn Path! new_posix(String path, Allocator allocator = allocator::heap())
fn Path? for_posix(Allocator allocator, String path)
{
return new(path, allocator, POSIX);
return new(allocator, path, POSIX);
}
fn bool Path.equals(self, Path p2)
@@ -187,59 +173,56 @@ fn bool Path.equals(self, Path p2)
return self.env == p2.env && self.path_string == p2.path_string;
}
fn Path! Path.append(self, String filename, Allocator allocator = allocator::heap()) @deprecated("Use path.new_append(...)")
{
return self.new_append(filename, allocator) @inline;
}
<*
Append the string to the current path.
@param [in] filename
*>
fn Path! Path.new_append(self, String filename, Allocator allocator = allocator::heap())
fn Path? Path.append(self, Allocator allocator, String filename)
{
if (!self.path_string.len) return new(filename, allocator, self.env)!;
if (!self.path_string.len) return new(allocator, filename, self.env)!;
assert(!is_separator(self.path_string[^1], self.env));
@pool(allocator)
@pool()
{
DString dstr = dstring::temp_with_capacity(self.path_string.len + 1 + filename.len);
dstr.append(self.path_string);
dstr.append(PREFERRED_SEPARATOR);
dstr.append(filename);
return { normalize(dstr.copy_str(allocator), self.env), self.env };
return new(allocator, dstr.str_view(), self.env);
};
}
fn Path! Path.temp_append(self, String filename) => self.new_append(filename, allocator::temp());
fn Path? Path.tappend(self, String filename) => self.append(tmem, filename);
fn Path! Path.tappend(self, String filename) @deprecated("Use path.temp_append(...)") => self.new_append(filename, allocator::temp());
fn usz Path.start_of_base_name(self) @local
fn usz? start_of_base_name(String str, PathEnv path_env) @local
{
String path_str = self.path_string;
if (!path_str.len) return 0;
if (self.env == PathEnv.WIN32)
if (!str.len) return 0;
usz? start_slash = str.rindex_of_char('/');
if (path_env != PathEnv.WIN32) return start_slash + 1 ?? 0;
if (try index = str.rindex_of_char('\\'))
{
if (try index = path_str.rindex_of_char('\\'))
{
// c:\ style path, we're done!
if (path_str[0] != '\\') return index + 1;
// Handle \\server\foo
// Find the \ before "foo"
usz last_index = 2 + path_str[2..].index_of_char('\\')!!;
// If they don't match, we're done
assert(last_index <= index, "Invalid normalized, path %d vs %s in %s", last_index, index, path_str);
if (last_index != index) return index + 1;
// Otherwise just default to the volume length.
}
return volume_name_len(path_str, self.env)!!;
if (try start_slash && start_slash > index) return start_slash + 1;
// c:\ style path, we're done!
if (str[0] != '\\') return index + 1;
// Handle \\server\foo
// Find the \ before "foo"
usz last_index = 2 + str[2..].index_of_char('\\')!;
// If they don't match, we're done
if (last_index > index) return INVALID_PATH?;
if (last_index != index) return index + 1;
// Otherwise just default to the volume length.
}
return path_str.rindex_of_char('/') + 1 ?? 0;
return volume_name_len(str, path_env)!!;
}
fn bool! Path.is_absolute(self)
fn bool? String.is_absolute_path(self) => @pool()
{
return temp(self).is_absolute();
}
fn bool? Path.is_absolute(self)
{
String path_str = self.str_view();
if (!path_str.len) return false;
@@ -248,55 +231,69 @@ fn bool! Path.is_absolute(self)
return path_start < path_str.len && is_separator(path_str[path_start], self.env);
}
fn Path! Path.absolute(self, Allocator allocator = allocator::heap()) @deprecated("Use path.new_absolute()")
fn Path? String.to_absolute_path(self, Allocator allocator) => @pool()
{
return self.new_absolute(allocator) @inline;
return temp(self).absolute(allocator);
}
<*
@require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
@require self.env == DEFAULT_ENV : "This method is only available on native paths"
*>
fn Path! Path.new_absolute(self, Allocator allocator = allocator::heap())
fn Path? Path.absolute(self, Allocator allocator)
{
String path_str = self.str_view();
if (!path_str.len) return PathResult.INVALID_PATH?;
if (self.is_absolute()!) return new(path_str, allocator, self.env);
if (!path_str.len) return INVALID_PATH?;
if (self.is_absolute()!) return new(allocator, path_str, self.env);
if (path_str == ".")
{
@pool(allocator)
@pool()
{
String cwd = os::getcwd(allocator::temp())!;
return new(cwd, allocator, self.env);
String cwd = os::getcwd(tmem)!;
return new(allocator, cwd, self.env);
};
}
$if DEFAULT_PATH_ENV == WIN32:
@pool(allocator)
$if DEFAULT_ENV == WIN32:
@pool()
{
const usz BUFFER_LEN = 4096;
WString buffer = (WString)mem::temp_alloc_array(Char16, BUFFER_LEN);
WString buffer = (WString)mem::talloc_array(Char16, BUFFER_LEN);
buffer = win32::_wfullpath(buffer, path_str.to_temp_wstring()!, BUFFER_LEN);
if (!buffer) return PathResult.INVALID_PATH?;
return { string::new_from_wstring(buffer, allocator), WIN32 };
if (!buffer) return INVALID_PATH?;
return { string::from_wstring(allocator, buffer), WIN32, allocator };
};
$else
String cwd = os::getcwd(allocator::temp())!;
return Path { cwd, self.env }.new_append(path_str, allocator)!;
String cwd = os::getcwd(tmem)!;
return (Path){ cwd, self.env, tmem }.append(allocator, path_str)!;
$endif
}
fn String? String.file_basename(self, Allocator allocator) => @pool()
{
return temp(self).basename().copy(allocator);
}
fn String? String.file_tbasename(self) => self.file_basename(tmem);
fn String Path.basename(self)
{
usz basename_start = self.start_of_base_name();
usz basename_start = start_of_base_name(self.path_string, self.env)!!;
String path_str = self.path_string;
if (basename_start == path_str.len) return "";
return path_str[basename_start..];
}
fn String? String.path_tdirname(self) => self.path_dirname(tmem);
fn String? String.path_dirname(self, Allocator allocator) => @pool()
{
return temp(self).dirname().copy(allocator);
}
fn String Path.dirname(self)
{
usz basename_start = self.start_of_base_name();
String path_str = self.path_string;
usz basename_start = start_of_base_name(path_str, self.env)!!;
if (basename_start == 0) return ".";
usz start = volume_name_len(path_str, self.env)!!;
if (basename_start <= start + 1)
@@ -310,11 +307,12 @@ fn String Path.dirname(self)
return path_str[:basename_start - 1];
}
<*
Test if the path has the given extension, so given the path /foo/bar.c3
this would be true matching the extension "c3"
@param [in] extension `The extension name (not including the leading '.')`
@param [in] extension : `The extension name (not including the leading '.')`
@require extension.len > 0 : `The extension cannot be empty`
@return `true if the extension matches`
*>
@@ -326,13 +324,12 @@ fn bool Path.has_extension(self, String extension)
return basename[^extension.len..] == extension;
}
fn String! Path.extension(self)
fn String? Path.extension(self)
{
String basename = self.basename();
usz index = basename.rindex_of(".")!;
// Plain ".foo" does not have an
if (index == 0) return SearchResult.MISSING?;
if (index == basename.len) return "";
// Plain ".foo" does not have an extension
if (index == 0 || index == basename.len) return "";
return basename[index + 1..];
}
@@ -343,7 +340,17 @@ fn String Path.volume_name(self)
return self.path_string[:len];
}
fn usz! volume_name_len(String path, PathEnv path_env) @local
fn Path? String.to_path(self, Allocator allocator)
{
return new(allocator, self);
}
fn Path? String.to_tpath(self)
{
return new(tmem, self);
}
fn usz? volume_name_len(String path, PathEnv path_env) @local
{
usz len = path.len;
if (len < 2 || path_env != PathEnv.WIN32) return 0;
@@ -367,10 +374,10 @@ fn usz! volume_name_len(String path, PathEnv path_env) @local
base_found = i;
continue;
}
if (is_reserved_win32_path_char(c)) return PathResult.INVALID_PATH?;
if (is_reserved_win32_path_char(c)) return INVALID_PATH?;
}
if (base_found > 0 && base_found + 1 < len) return len;
return PathResult.INVALID_PATH?;
return INVALID_PATH?;
case 'A'..'Z':
case 'a'..'z':
return path[1] == ':' ? 2 : 0;
@@ -384,24 +391,24 @@ fn usz! volume_name_len(String path, PathEnv path_env) @local
of the path itself.
@return `The parent of the path as a non-allocated path`
@return! PathResult.NO_PARENT `if this path does not have a parent`
@return? NO_PARENT : `if this path does not have a parent`
*>
fn Path! Path.parent(self)
fn Path? Path.parent(self)
{
if (self.path_string.len == 1 && is_separator(self.path_string[0], self.env)) return PathResult.NO_PARENT?;
if (self.path_string.len == 1 && is_separator(self.path_string[0], self.env)) return NO_PARENT?;
foreach_r(i, c : self.path_string)
{
if (is_separator(c, self.env))
{
return { self.path_string[:i], self.env };
return { self.path_string[:i], self.env, null };
}
}
return PathResult.NO_PARENT?;
return NO_PARENT?;
}
fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
fn String? normalize(String path_str, PathEnv path_env = DEFAULT_ENV)
{
if (!path_str.len) return "";
if (!path_str.len) return path_str;
usz path_start = volume_name_len(path_str, path_env)!;
if (path_start > 0 && path_env == PathEnv.WIN32)
{
@@ -438,7 +445,7 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
// The rest are names of the path elements, so check that the
// characters are valid.
if (is_reserved_path_char(c, path_env)) return PathResult.INVALID_PATH?;
if (is_reserved_path_char(c, path_env)) return INVALID_PATH?;
// If we have '.' after a separator
if (c == '.' && previous_was_separator)
@@ -471,7 +478,7 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
continue;
case 2:
// This is an error: /a/../..
if (len == path_start && has_root) return PathResult.INVALID_PATH?;
if (len == path_start && has_root) return INVALID_PATH?;
// If this .. at the start, or after ../? If so, we just copy ..
if (len == path_start ||
@@ -512,7 +519,11 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
if (len > path_start + 1 && is_separator(path_str[len - 1], path_env)) len--;
if (path_str.len > len) path_str.ptr[len] = 0;
// Empty path after normalization -> "."
if (!len) return ".";
if (!len)
{
path_str[0] = '.';
return path_str[:1];
}
return path_str[:len];
}
@@ -541,24 +552,24 @@ fn String Path.root_directory(self)
return path_str;
}
def PathWalker = fn bool! (Path, bool is_dir, void*);
alias PathWalker = fn bool? (Path, bool is_dir, void*);
<*
Walk the path recursively. PathWalker is run on every file and
directory found. Return true to abort the walk.
@require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
@require self.env == DEFAULT_ENV : "This method is only available on native paths"
*>
fn bool! Path.walk(self, PathWalker w, void* data)
fn bool? Path.walk(self, PathWalker w, void* data)
{
const PATH_MAX = 512;
@stack_mem(PATH_MAX; Allocator allocator)
{
Path abs = self.new_absolute(allocator)!;
PathList files = new_ls(abs, allocator: allocator)!;
Path abs = self.absolute(allocator)!;
PathList files = ls(allocator, abs)!;
foreach (f : files)
{
if (f.str_view() == "." || f.str_view() == "..") continue;
f = abs.new_append(f.str_view(), allocator)!;
f = abs.append(allocator, f.str_view())!;
bool is_directory = is_dir(f);
if (w(f, is_directory, data)!) return true;
if (is_directory && f.walk(w, data)!) return true;
@@ -567,6 +578,35 @@ fn bool! Path.walk(self, PathWalker w, void* data)
return false;
}
alias TraverseCallback = fn bool? (Path, bool is_dir, any data);
<*
Walk the path recursively. TraverseCallback is run for every file and
directory found. Return true to abort the walk.
@require path.env == DEFAULT_ENV : "This method is only available on native paths"
*>
fn bool? traverse(Path path, TraverseCallback callback, any data)
{
const PATH_MAX = 512;
@stack_mem(PATH_MAX; Allocator allocator)
{
Path abs = path.absolute(allocator)!;
PathList files = ls(allocator, abs)!;
foreach (f : files)
{
if (f.str_view() == "." || f.str_view() == "..") continue;
@stack_mem(128; Allocator smem)
{
f = abs.append(smem, f.str_view())!;
bool is_directory = is_dir(f);
if (callback(f, is_directory, data)!) return true;
if (is_directory && traverse(f, callback, data)!) return true;
};
}
};
return false;
}
fn String Path.str_view(self) @inline
{
return self.path_string;
@@ -578,26 +618,19 @@ fn bool Path.has_suffix(self, String str)
return self.str_view().ends_with(str);
}
fn void Path.free_with_allocator(self, Allocator allocator)
{
allocator::free(allocator, self.path_string.ptr);
}
<*
@require self.allocator != null : "This Path should never be freed"
*>
fn void Path.free(self)
{
free(self.path_string.ptr);
allocator::free(self.allocator, self.path_string.ptr);
}
fn usz! Path.to_format(&self, Formatter* formatter) @dynamic
fn usz? Path.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.print(self.str_view());
}
fn String Path.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return self.str_view().copy(allocator);
}
const bool[256] RESERVED_PATH_CHAR_POSIX = {
[0] = true,
@@ -621,9 +654,29 @@ macro bool is_reserved_win32_path_char(char c)
return RESERVED_PATH_CHAR_WIN32[c];
}
macro bool is_reserved_path_char(char c, PathEnv path_env = DEFAULT_PATH_ENV)
macro bool is_reserved_path_char(char c, PathEnv path_env = DEFAULT_ENV)
{
return path_env == PathEnv.WIN32
? RESERVED_PATH_CHAR_WIN32[c]
: RESERVED_PATH_CHAR_POSIX[c];
}
}
fn bool? _mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL) @private
{
if (!path.path_string.len) return INVALID_PATH?;
if (is_dir(path)) return false;
if (exists(path)) return io::FILE_NOT_DIR?;
if (recursive)
{
if (try parent = path.parent()) mkdir(parent, true, permissions)!;
}
if (!is_dir(path.parent()) ?? false) return io::CANNOT_READ_DIR?;
return os::native_mkdir(path, permissions);
}
fn bool? _rmdir(Path path) @private
{
if (!path.path_string.len) return INVALID_PATH?;
return os::native_rmdir(path);
}

View File

@@ -3,28 +3,28 @@ import std::math;
interface InStream
{
fn void! close() @optional;
fn usz! seek(isz offset, Seek seek) @optional;
fn void? close() @optional;
fn usz? seek(isz offset, Seek seek) @optional;
fn usz len() @optional;
fn usz! available() @optional;
fn usz! read(char[] buffer);
fn char! read_byte();
fn usz! write_to(OutStream out) @optional;
fn void! pushback_byte() @optional;
fn usz? available() @optional;
fn usz? read(char[] buffer);
fn char? read_byte();
fn usz? write_to(OutStream out) @optional;
fn void? pushback_byte() @optional;
}
interface OutStream
{
fn void! destroy() @optional;
fn void! close() @optional;
fn void! flush() @optional;
fn usz! write(char[] bytes);
fn void! write_byte(char c);
fn usz! read_to(InStream in) @optional;
fn void? destroy() @optional;
fn void? close() @optional;
fn void? flush() @optional;
fn usz? write(char[] bytes);
fn void? write_byte(char c);
fn usz? read_to(InStream in) @optional;
}
fn usz! available(InStream s)
fn usz? available(InStream s)
{
if (&s.available) return s.available();
if (&s.seek)
@@ -51,17 +51,17 @@ macro bool @is_outstream(#expr)
@param [&out] ref
@require @is_instream(stream)
*>
macro usz! read_any(stream, any ref)
macro usz? read_any(stream, any ref)
{
return read_all(stream, ((char*)ref)[:ref.type.sizeof]);
}
<*
@param [&in] ref "the object to write."
@param [&in] ref : "the object to write."
@require @is_outstream(stream)
@ensure return == ref.type.sizeof
*>
macro usz! write_any(stream, any ref)
macro usz? write_any(stream, any ref)
{
return write_all(stream, ((char*)ref)[:ref.type.sizeof]);
}
@@ -69,18 +69,18 @@ macro usz! write_any(stream, any ref)
<*
@require @is_instream(stream)
*>
macro usz! read_all(stream, char[] buffer)
macro usz? read_all(stream, char[] buffer)
{
if (buffer.len == 0) return 0;
usz n = stream.read(buffer)!;
if (n != buffer.len) return IoError.UNEXPECTED_EOF?;
if (n != buffer.len) return UNEXPECTED_EOF?;
return n;
}
<*
@require @is_instream(stream)
*>
macro char[]! read_new_fully(stream, Allocator allocator = allocator::heap())
macro char[]? read_fully(Allocator allocator, stream)
{
usz len = available(stream)!;
char* data = allocator::malloc_try(allocator, len)!;
@@ -96,24 +96,24 @@ macro char[]! read_new_fully(stream, Allocator allocator = allocator::heap())
<*
@require @is_outstream(stream)
*>
macro usz! write_all(stream, char[] buffer)
macro usz? write_all(stream, char[] buffer)
{
if (buffer.len == 0) return 0;
usz n = stream.write(buffer)!;
if (n != buffer.len) return IoError.INCOMPLETE_WRITE?;
if (n != buffer.len) return INCOMPLETE_WRITE?;
return n;
}
macro usz! @read_using_read_byte(&s, char[] buffer)
macro usz? read_using_read_byte(s, char[] buffer)
{
usz len = 0;
foreach (&cptr : buffer)
{
char! c = s.read_byte();
char? c = s.read_byte();
if (catch err = c)
{
case IoError.EOF: return len;
default: return err?;
if (err == io::EOF) return len;
return err?;
}
*cptr = c;
len++;
@@ -121,66 +121,65 @@ macro usz! @read_using_read_byte(&s, char[] buffer)
return len;
}
macro void! @write_byte_using_write(&s, char c)
macro void? write_byte_using_write(s, char c)
{
char[1] buff = { c };
(*s).write(&buff)!;
s.write(&buff)!;
}
macro char! @read_byte_using_read(&s)
macro char? read_byte_using_read(s)
{
char[1] buffer;
usz read = (*s).read(&buffer)!;
if (read != 1) return IoError.EOF?;
usz read = s.read(&buffer)!;
if (read != 1) return io::EOF?;
return buffer[0];
}
def ReadByteFn = fn char!();
alias ReadByteFn = fn char?();
macro usz! @write_using_write_byte(&s, char[] bytes)
macro usz? write_using_write_byte(s, char[] bytes)
{
foreach (c : bytes) s.write_byte(self, c)!;
return bytes.len;
}
macro void! @pushback_using_seek(&s)
macro void? pushback_using_seek(s)
{
s.seek(-1, CURSOR)!;
}
fn usz! copy_to(InStream in, OutStream dst, char[] buffer = {})
fn usz? copy_to(InStream in, OutStream dst, char[] buffer = {})
{
if (buffer.len) return copy_through_buffer(in, dst, buffer);
if (&in.write_to) return in.write_to(dst);
if (&dst.read_to) return dst.read_to(in);
$switch (env::MEMORY_ENV)
$switch env::MEMORY_ENV:
$case NORMAL:
return copy_through_buffer(in, dst, &&char[4096]{});
return copy_through_buffer(in, dst, &&(char[4096]){});
$case SMALL:
return copy_through_buffer(in, dst, &&char[1024]{});
return copy_through_buffer(in, dst, &&(char[1024]){});
$case TINY:
$case NONE:
return copy_through_buffer(in, dst, &&(char[256]{}));
return copy_through_buffer(in, dst, &&(char[256]){});
$endswitch
}
macro usz! copy_through_buffer(InStream in, OutStream dst, char[] buffer) @local
macro usz? copy_through_buffer(InStream in, OutStream dst, char[] buffer) @local
{
usz total_copied;
while (true)
{
usz! len = in.read(buffer);
usz? len = in.read(buffer);
if (catch err = len)
{
case IoError.EOF: return total_copied;
default: return err?;
if (err == io::EOF) return total_copied;
return err?;
}
if (!len) return total_copied;
usz written = dst.write(buffer[:len])!;
total_copied += len;
if (written != len) return IoError.INCOMPLETE_WRITE?;
if (written != len) return INCOMPLETE_WRITE?;
}
}
@@ -190,7 +189,7 @@ const char[*] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 };
@require @is_instream(stream)
@require @typekind(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
*>
macro usz! read_varint(stream, x_ptr)
macro usz? read_varint(stream, x_ptr)
{
var $Type = $typefrom($typeof(x_ptr).inner);
const MAX = MAX_VARS[$Type.sizeof];
@@ -199,13 +198,11 @@ macro usz! read_varint(stream, x_ptr)
usz n;
for (usz i = 0; i < MAX; i++)
{
char! c = stream.read_byte();
char? c = stream.read_byte();
if (catch err = c)
{
case IoError.EOF:
return IoError.UNEXPECTED_EOF?;
default:
return err?;
if (err == io::EOF) return io::UNEXPECTED_EOF?;
return err?;
}
n++;
if (c & 0x80 == 0)
@@ -221,13 +218,13 @@ macro usz! read_varint(stream, x_ptr)
x |= (c & 0x7F) << shift;
shift += 7;
}
return MathError.OVERFLOW?;
return math::OVERFLOW?;
}
<*
@require @is_outstream(stream)
@require @typekind(x).is_int()
*>
macro usz! write_varint(stream, x)
macro usz? write_varint(stream, x)
{
var $Type = $typeof(x);
const MAX = MAX_VARS[$Type.sizeof];
@@ -246,7 +243,7 @@ macro usz! write_varint(stream, x)
<*
@require @is_instream(stream)
*>
macro ushort! read_be_ushort(stream)
macro ushort? read_be_ushort(stream)
{
char hi_byte = stream.read_byte()!;
char lo_byte = stream.read_byte()!;
@@ -256,7 +253,7 @@ macro ushort! read_be_ushort(stream)
<*
@require @is_instream(stream)
*>
macro short! read_be_short(stream)
macro short? read_be_short(stream)
{
return read_be_ushort(stream);
}
@@ -264,7 +261,7 @@ macro short! read_be_short(stream)
<*
@require @is_outstream(stream)
*>
macro void! write_be_short(stream, ushort s)
macro void? write_be_short(stream, ushort s)
{
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
@@ -273,7 +270,7 @@ macro void! write_be_short(stream, ushort s)
<*
@require @is_instream(stream)
*>
macro uint! read_be_uint(stream)
macro uint? read_be_uint(stream)
{
uint val = stream.read_byte()! << 24;
val += stream.read_byte()! << 16;
@@ -284,7 +281,7 @@ macro uint! read_be_uint(stream)
<*
@require @is_instream(stream)
*>
macro int! read_be_int(stream)
macro int? read_be_int(stream)
{
return read_be_uint(stream);
}
@@ -292,7 +289,7 @@ macro int! read_be_int(stream)
<*
@require @is_outstream(stream)
*>
macro void! write_be_int(stream, uint s)
macro void? write_be_int(stream, uint s)
{
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 16))!;
@@ -303,7 +300,7 @@ macro void! write_be_int(stream, uint s)
<*
@require @is_instream(stream)
*>
macro ulong! read_be_ulong(stream)
macro ulong? read_be_ulong(stream)
{
ulong val = (ulong)stream.read_byte()! << 56;
val += (ulong)stream.read_byte()! << 48;
@@ -318,7 +315,7 @@ macro ulong! read_be_ulong(stream)
<*
@require @is_instream(stream)
*>
macro long! read_be_long(stream)
macro long? read_be_long(stream)
{
return read_be_ulong(stream);
}
@@ -326,7 +323,7 @@ macro long! read_be_long(stream)
<*
@require @is_outstream(stream)
*>
macro void! write_be_long(stream, ulong s)
macro void? write_be_long(stream, ulong s)
{
stream.write_byte((char)(s >> 56))!;
stream.write_byte((char)(s >> 48))!;
@@ -341,7 +338,7 @@ macro void! write_be_long(stream, ulong s)
<*
@require @is_instream(stream)
*>
macro uint128! read_be_uint128(stream)
macro uint128? read_be_uint128(stream)
{
uint128 val = (uint128)stream.read_byte()! << 120;
val += (uint128)stream.read_byte()! << 112;
@@ -364,7 +361,7 @@ macro uint128! read_be_uint128(stream)
<*
@require @is_instream(stream)
*>
macro int128! read_be_int128(stream)
macro int128? read_be_int128(stream)
{
return read_be_uint128(stream);
}
@@ -372,7 +369,7 @@ macro int128! read_be_int128(stream)
<*
@require @is_outstream(stream)
*>
macro void! write_be_int128(stream, uint128 s)
macro void? write_be_int128(stream, uint128 s)
{
stream.write_byte((char)(s >> 120))!;
stream.write_byte((char)(s >> 112))!;
@@ -394,9 +391,9 @@ macro void! write_be_int128(stream, uint128 s)
<*
@require @is_outstream(stream)
@require data.len < 256 "Data exceeded 255"
@require data.len < 256 : "Data exceeded 255"
*>
macro usz! write_tiny_bytearray(stream, char[] data)
macro usz? write_tiny_bytearray(stream, char[] data)
{
stream.write_byte((char)data.len)!;
return stream.write(data) + 1;
@@ -405,7 +402,7 @@ macro usz! write_tiny_bytearray(stream, char[] data)
<*
@require @is_instream(stream)
*>
macro char[]! read_tiny_bytearray(stream, Allocator allocator)
macro char[]? read_tiny_bytearray(stream, Allocator allocator)
{
int len = stream.read_byte()!;
if (!len) return {};
@@ -416,9 +413,9 @@ macro char[]! read_tiny_bytearray(stream, Allocator allocator)
<*
@require @is_outstream(stream)
@require data.len < 0x1000 "Data exceeded 65535"
@require data.len < 0x1000 : "Data exceeded 65535"
*>
macro usz! write_short_bytearray(stream, char[] data)
macro usz? write_short_bytearray(stream, char[] data)
{
io::write_be_short(stream, (ushort)data.len)!;
return stream.write(data) + 2;
@@ -427,7 +424,7 @@ macro usz! write_short_bytearray(stream, char[] data)
<*
@require @is_instream(stream)
*>
macro char[]! read_short_bytearray(stream, Allocator allocator)
macro char[]? read_short_bytearray(stream, Allocator allocator)
{
int len = io::read_be_ushort(stream)!;
if (!len) return {};

View File

@@ -12,7 +12,7 @@ struct ReadBuffer (InStream)
Buffer reads from a stream.
@param [inout] self
@require bytes.len > 0
@require self.bytes.len == 0 "Init may not run on already initialized data"
@require self.bytes.len == 0 : "Init may not run on already initialized data"
*>
fn ReadBuffer* ReadBuffer.init(&self, InStream wrapped_stream, char[] bytes)
{
@@ -24,12 +24,12 @@ fn String ReadBuffer.str_view(&self) @inline
return (String)self.bytes[self.read_idx:self.write_idx - self.read_idx];
}
fn void! ReadBuffer.close(&self) @dynamic
fn void? ReadBuffer.close(&self) @dynamic
{
if (&self.wrapped_stream.close) self.wrapped_stream.close()!;
}
fn usz! ReadBuffer.read(&self, char[] bytes) @dynamic
fn usz? ReadBuffer.read(&self, char[] bytes) @dynamic
{
if (self.read_idx == self.write_idx)
{
@@ -46,16 +46,16 @@ fn usz! ReadBuffer.read(&self, char[] bytes) @dynamic
return n;
}
fn char! ReadBuffer.read_byte(&self) @dynamic
fn char? ReadBuffer.read_byte(&self) @dynamic
{
if (self.read_idx == self.write_idx) self.refill()!;
if (self.read_idx == self.write_idx) return IoError.EOF?;
if (self.read_idx == self.write_idx) return io::EOF?;
char c = self.bytes[self.read_idx];
self.read_idx++;
return c;
}
fn void! ReadBuffer.refill(&self) @local @inline
fn void? ReadBuffer.refill(&self) @local @inline
{
self.read_idx = 0;
self.write_idx = self.wrapped_stream.read(self.bytes)!;
@@ -71,8 +71,8 @@ struct WriteBuffer (OutStream)
<*
Buffer writes to a stream. Call `flush` when done writing to the buffer.
@param [inout] self
@require bytes.len > 0 "Non-empty buffer required"
@require self.bytes.len == 0 "Init may not run on already initialized data"
@require bytes.len > 0 : "Non-empty buffer required"
@require self.bytes.len == 0 : "Init may not run on already initialized data"
*>
fn WriteBuffer* WriteBuffer.init(&self, OutStream wrapped_stream, char[] bytes)
{
@@ -85,18 +85,18 @@ fn String WriteBuffer.str_view(&self) @inline
return (String)self.bytes[:self.index];
}
fn void! WriteBuffer.close(&self) @dynamic
fn void? WriteBuffer.close(&self) @dynamic
{
if (&self.wrapped_stream.close) return self.wrapped_stream.close();
}
fn void! WriteBuffer.flush(&self) @dynamic
fn void? WriteBuffer.flush(&self) @dynamic
{
self.write_pending()!;
if (&self.wrapped_stream.flush) self.wrapped_stream.flush()!;
}
fn usz! WriteBuffer.write(&self, char[] bytes) @dynamic
fn usz? WriteBuffer.write(&self, char[] bytes) @dynamic
{
usz n = self.bytes.len - self.index;
if (bytes.len < n)
@@ -118,7 +118,7 @@ fn usz! WriteBuffer.write(&self, char[] bytes) @dynamic
return bytes.len;
}
fn void! WriteBuffer.write_byte(&self, char c) @dynamic
fn void? WriteBuffer.write_byte(&self, char c) @dynamic
{
usz n = self.bytes.len - self.index;
if (n == 0)
@@ -129,8 +129,8 @@ fn void! WriteBuffer.write_byte(&self, char c) @dynamic
self.index += 1;
}
fn void! WriteBuffer.write_pending(&self) @local
fn void? WriteBuffer.write_pending(&self) @local
{
self.index -= self.wrapped_stream.write(self.bytes[:self.index])!;
if (self.index != 0) return IoError.INCOMPLETE_WRITE?;
if (self.index != 0) return INCOMPLETE_WRITE?;
}

View File

@@ -14,26 +14,26 @@ struct ByteBuffer (InStream, OutStream)
<*
ByteBuffer provides a streamable read/write buffer.
max_read defines how many bytes might be kept before its internal buffer is shrinked.
@require self.bytes.len == 0 "Buffer already initialized."
@require self.bytes.len == 0 : "Buffer already initialized."
*>
fn ByteBuffer*! ByteBuffer.new_init(&self, usz max_read, usz initial_capacity = 16, Allocator allocator = allocator::heap())
fn ByteBuffer* ByteBuffer.init(&self, Allocator allocator, usz max_read, usz initial_capacity = 16)
{
*self = { .allocator = allocator, .max_read = max_read };
initial_capacity = max(initial_capacity, 16);
self.grow(initial_capacity)!;
self.grow(initial_capacity);
return self;
}
fn ByteBuffer*! ByteBuffer.temp_init(&self, usz max_read, usz initial_capacity = 16)
fn ByteBuffer* ByteBuffer.tinit(&self, usz max_read, usz initial_capacity = 16)
{
return self.new_init(max_read, initial_capacity, allocator::temp());
return self.init(tmem, max_read, initial_capacity);
}
<*
@require buf.len > 0
@require self.bytes.len == 0 "Buffer already initialized."
@require self.bytes.len == 0 : "Buffer already initialized."
*>
fn ByteBuffer*! ByteBuffer.init_with_buffer(&self, char[] buf)
fn ByteBuffer* ByteBuffer.init_with_buffer(&self, char[] buf)
{
*self = { .max_read = buf.len, .bytes = buf };
return self;
@@ -45,30 +45,30 @@ fn void ByteBuffer.free(&self)
*self = {};
}
fn usz! ByteBuffer.write(&self, char[] bytes) @dynamic
fn usz? ByteBuffer.write(&self, char[] bytes) @dynamic
{
usz cap = self.bytes.len - self.write_idx;
if (cap < bytes.len) self.grow(bytes.len)!;
if (cap < bytes.len) self.grow(bytes.len);
self.bytes[self.write_idx:bytes.len] = bytes[..];
self.write_idx += bytes.len;
return bytes.len;
}
fn void! ByteBuffer.write_byte(&self, char c) @dynamic
fn void? ByteBuffer.write_byte(&self, char c) @dynamic
{
usz cap = self.bytes.len - self.write_idx;
if (cap == 0) self.grow(1)!;
if (cap == 0) self.grow(1);
self.bytes[self.write_idx] = c;
self.write_idx++;
}
fn usz! ByteBuffer.read(&self, char[] bytes) @dynamic
fn usz? ByteBuffer.read(&self, char[] bytes) @dynamic
{
usz readable = self.write_idx - self.read_idx;
if (readable == 0)
{
self.has_last = false;
return IoError.EOF?;
return io::EOF?;
}
usz n = min(readable, bytes.len);
bytes[:n] = self.bytes[self.read_idx:n];
@@ -78,13 +78,13 @@ fn usz! ByteBuffer.read(&self, char[] bytes) @dynamic
return n;
}
fn char! ByteBuffer.read_byte(&self) @dynamic
fn char? ByteBuffer.read_byte(&self) @dynamic
{
usz readable = self.write_idx - self.read_idx;
if (readable == 0)
{
self.has_last = false;
return IoError.EOF?;
return io::EOF?;
}
char c = self.bytes[self.read_idx];
self.read_idx++;
@@ -96,42 +96,42 @@ fn char! ByteBuffer.read_byte(&self) @dynamic
<*
Only the last byte of a successful read can be pushed back.
*>
fn void! ByteBuffer.pushback_byte(&self) @dynamic
fn void? ByteBuffer.pushback_byte(&self) @dynamic
{
if (!self.has_last) return IoError.EOF?;
if (!self.has_last) return io::EOF?;
assert(self.read_idx > 0);
self.read_idx--;
self.has_last = false;
}
fn usz! ByteBuffer.seek(&self, isz offset, Seek seek) @dynamic
fn usz? ByteBuffer.seek(&self, isz offset, Seek seek) @dynamic
{
switch (seek)
{
case SET:
if (offset < 0 || offset > self.write_idx) return IoError.INVALID_POSITION?;
if (offset < 0 || offset > self.write_idx) return INVALID_POSITION?;
self.read_idx = offset;
return offset;
case CURSOR:
if ((offset < 0 && self.read_idx < -offset) ||
(offset > 0 && self.read_idx + offset > self.write_idx)) return IoError.INVALID_POSITION?;
(offset > 0 && self.read_idx + offset > self.write_idx)) return INVALID_POSITION?;
self.read_idx += offset;
case END:
if (offset < 0 || offset > self.write_idx) return IoError.INVALID_POSITION?;
if (offset < 0 || offset > self.write_idx) return INVALID_POSITION?;
self.read_idx = self.write_idx - offset;
}
return self.read_idx;
}
fn usz! ByteBuffer.available(&self) @inline @dynamic
fn usz? ByteBuffer.available(&self) @inline @dynamic
{
return self.write_idx - self.read_idx;
}
fn void! ByteBuffer.grow(&self, usz n)
fn void ByteBuffer.grow(&self, usz n)
{
n = math::next_power_of_2(n);
char* p = allocator::realloc_aligned(self.allocator, self.bytes, n, alignment: char.alignof)!;
char* p = allocator::realloc(self.allocator, self.bytes, n);
self.bytes = p[:n];
}

View File

@@ -17,9 +17,9 @@ fn ByteReader* ByteReader.init(&self, char[] bytes)
return self;
}
fn usz! ByteReader.read(&self, char[] bytes) @dynamic
fn usz? ByteReader.read(&self, char[] bytes) @dynamic
{
if (self.index >= self.bytes.len) return IoError.EOF?;
if (self.index >= self.bytes.len) return io::EOF?;
usz len = min(self.bytes.len - self.index, bytes.len);
if (len == 0) return 0;
mem::copy(bytes.ptr, &self.bytes[self.index], len);
@@ -27,19 +27,19 @@ fn usz! ByteReader.read(&self, char[] bytes) @dynamic
return len;
}
fn char! ByteReader.read_byte(&self) @dynamic
fn char? ByteReader.read_byte(&self) @dynamic
{
if (self.index >= self.bytes.len) return IoError.EOF?;
if (self.index >= self.bytes.len) return io::EOF?;
return self.bytes[self.index++];
}
fn void! ByteReader.pushback_byte(&self) @dynamic
fn void? ByteReader.pushback_byte(&self) @dynamic
{
if (!self.index) return IoError.INVALID_PUSHBACK?;
if (!self.index) return INVALID_PUSHBACK?;
self.index--;
}
fn usz! ByteReader.seek(&self, isz offset, Seek seek) @dynamic
fn usz? ByteReader.seek(&self, isz offset, Seek seek) @dynamic
{
isz new_index;
switch (seek)
@@ -48,12 +48,12 @@ fn usz! ByteReader.seek(&self, isz offset, Seek seek) @dynamic
case CURSOR: new_index = self.index + offset;
case END: new_index = self.bytes.len + offset;
}
if (new_index < 0) return IoError.INVALID_POSITION?;
if (new_index < 0) return INVALID_POSITION?;
self.index = new_index;
return new_index;
}
fn usz! ByteReader.write_to(&self, OutStream writer) @dynamic
fn usz? ByteReader.write_to(&self, OutStream writer) @dynamic
{
if (self.index >= self.bytes.len) return 0;
usz written = writer.write(self.bytes[self.index..])!;
@@ -62,7 +62,7 @@ fn usz! ByteReader.write_to(&self, OutStream writer) @dynamic
return written;
}
fn usz! ByteReader.available(&self) @inline @dynamic
fn usz? ByteReader.available(&self) @inline @dynamic
{
return max(0, self.bytes.len - self.index);
}

View File

@@ -11,10 +11,10 @@ struct ByteWriter (OutStream)
<*
@param [&inout] self
@param [&inout] allocator
@require self.bytes.len == 0 "Init may not run on already initialized data"
@require self.bytes.len == 0 : "Init may not run on already initialized data"
@ensure (bool)allocator, self.index == 0
*>
fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap())
fn ByteWriter* ByteWriter.init(&self, Allocator allocator)
{
*self = { .bytes = {}, .allocator = allocator };
return self;
@@ -22,12 +22,12 @@ fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap(
<*
@param [&inout] self
@require self.bytes.len == 0 "Init may not run on already initialized data"
@require self.bytes.len == 0 : "Init may not run on already initialized data"
@ensure self.index == 0
*>
fn ByteWriter* ByteWriter.temp_init(&self)
fn ByteWriter* ByteWriter.tinit(&self)
{
return self.new_init(allocator::temp()) @inline;
return self.init(tmem) @inline;
}
fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
@@ -36,7 +36,7 @@ fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
return self;
}
fn void! ByteWriter.destroy(&self) @dynamic
fn void? ByteWriter.destroy(&self) @dynamic
{
if (!self.allocator) return;
if (void* ptr = self.bytes.ptr) allocator::free(self.allocator, ptr);
@@ -48,17 +48,17 @@ fn String ByteWriter.str_view(&self) @inline
return (String)self.bytes[:self.index];
}
fn void! ByteWriter.ensure_capacity(&self, usz len) @inline
fn void? ByteWriter.ensure_capacity(&self, usz len) @inline
{
if (self.bytes.len > len) return;
if (!self.allocator) return IoError.OUT_OF_SPACE?;
if (!self.allocator) return OUT_OF_SPACE?;
if (len < 16) len = 16;
usz new_capacity = math::next_power_of_2(len);
char* new_ptr = allocator::realloc_try(self.allocator, self.bytes.ptr, new_capacity)!;
self.bytes = new_ptr[:new_capacity];
}
fn usz! ByteWriter.write(&self, char[] bytes) @dynamic
fn usz? ByteWriter.write(&self, char[] bytes) @dynamic
{
self.ensure_capacity(self.index + bytes.len)!;
mem::copy(&self.bytes[self.index], bytes.ptr, bytes.len);
@@ -66,7 +66,7 @@ fn usz! ByteWriter.write(&self, char[] bytes) @dynamic
return bytes.len;
}
fn void! ByteWriter.write_byte(&self, char c) @dynamic
fn void? ByteWriter.write_byte(&self, char c) @dynamic
{
self.ensure_capacity(self.index + 1)!;
self.bytes[self.index++] = c;
@@ -76,7 +76,7 @@ fn void! ByteWriter.write_byte(&self, char c) @dynamic
@param [&inout] self
@param reader
*>
fn usz! ByteWriter.read_from(&self, InStream reader) @dynamic
fn usz? ByteWriter.read_from(&self, InStream reader) @dynamic
{
usz start_index = self.index;
if (&reader.available)

Some files were not shown because too many files have changed in this diff Show More