Compare commits

...

1066 Commits
test ... v0.6.7

Author SHA1 Message Date
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
Christoffer Lerno
7bc3e94ff3 Build 0.6.5 release. Fixed. 2024-12-14 17:38:02 +01:00
Christoffer Lerno
9c1fb26660 Build 0.6.5 release. 2024-12-14 17:33:00 +01:00
Christoffer Lerno
3dd725a0f0 Crash when a constant null typeid is checked for properties. #1679 2024-12-14 15:13:43 +01:00
Christoffer Lerno
a8aad53038 It was possible to create 0 length arrays using byte literals. #1678 2024-12-14 02:49:45 +01:00
konimarti
68c60f58c0 math: fix adjoint of Matrix2 (#1676)
* math: fix adjoint of Matrix2

Fix the adjoint of the Matrix2x2 implementation in the math module. This
also fixes the calculation of the inverse which depends on the adjoint.

* update release notes
2024-12-13 11:16:47 +01:00
Radek Micek
c9c3f33acc Fix weekday 2024-12-12 22:30:23 +01:00
Christoffer Lerno
5ffc5187eb Update Win LLVM library. 2024-12-12 21:52:13 +01:00
Christoffer Lerno
5d31cdfa16 Incorrect no-libc definition of cos, making it unavailable for wasm. 2024-12-12 21:50:56 +01:00
Christoffer Lerno
e8ff4af5b9 Fix test for LLVM 20 2024-12-11 23:05:57 +01:00
Christoffer Lerno
723e1dd9a6 Fix issue with overloaded *= etc 2024-12-11 20:56:11 +01:00
Christoffer Lerno
369a4558a3 Remove mention of install_win_reqs.bat 2024-12-11 13:57:03 +01:00
Christoffer Lerno
5e6a3d9d8e Fix tabs in readme. 2024-12-10 23:14:20 +01:00
Koni Marti
62dca4f1c5 math: add gcd and lcm
Add gcd and lcm functions to calculate the greatest common divisor (gcd)
and the least common multiple (lcm) to the math module. This will also
work for BigInts that implements its own gcd/lcm.
2024-12-10 15:44:52 +01:00
Christoffer Lerno
061c02306f Cast removing arbitrary array indices and converting them to pointers should always be fine #1664 2024-12-08 18:58:43 +01:00
Christoffer Lerno
f006b05010 Fix issue with accessing arrays in access-overloaded types, e.g. list[1][2] #1665. 2024-12-08 18:04:29 +01:00
Koni Marti
c5a727aa9b math: update complex numbers
Add inverse, conjugate, and equals functions to the Complex numbers. Add
an IMAGINARY constant to represent the imaginary unit. Also, add unit
tests for different types.
2024-12-08 16:51:44 +01:00
konimarti
e67e9d3bbf Fix fnv a hashes (#1667)
* fix fnv32a

* fix fnv64a

* Simplify code

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2024-12-07 15:39:45 +01:00
Christoffer Lerno
ea86c9d37a Fix of issue where multiple methods were accepted for the same type. Fix of issue where a method was linked to a type alias instead of the underlying type. #1661 2024-12-06 13:09:47 +01:00
Christoffer Lerno
d1a2e6e5bd Update Windows debug output. 2024-12-06 02:18:36 +01:00
konimarti
c96985f1db sort: add is_sorted (#1660)
* sort: add is_sorted
Add is_sorted function to check whether a list is sorted or not. Sort
order (ascending or descending) will be detected by looking at the data.
Add tests.
* update the release notes
* refactor: use lambda
2024-12-05 22:37:13 +01:00
Christoffer Lerno
b7010c83e0 Fix windows emit loc. 2024-12-04 21:33:03 +01:00
Christoffer Lerno
20a3d19ac7 Update Windows build to use LLVM 19.1.4 2024-12-04 14:00:18 +01:00
Christoffer Lerno
0ded93ab9b Do not produce expression locations for windows. 2024-12-04 12:21:12 +01:00
Christoffer Lerno
ec82ec0426 Fix CI. 2024-12-04 00:02:36 +01:00
Christoffer Lerno
6281f8ff89 Add -q option, make --run-once implicitly -q.
Add `-v`, `-vv` and `-vvv` options for increasing verbosity, replacing debug-log and debug-stats options. #1601
2024-12-03 23:37:31 +01:00
Koni Marti
2c9d2d4fd7 update the release notes 2024-12-03 21:21:38 +01:00
Christoffer Lerno
8569239bc1 Crash when using --no-obj without compile-only. #1653 2024-12-03 19:48:24 +01:00
konimarti
5463c398cb Add quickselect (#1654)
* sort: extract partition from quicksort

Extract the partition logic from quicksort into a macro. This allows to
reuse the partition logic for, e.g., the quickselect algorithm.

* sort: implement quickselect

implement Hoare's selection algorithm (quickselect) on the basis of the
already implemented quicksort. Quickselect allows to find the kth
smallest element in a unordered list with an average time complexity of
O(N) (worst case: O(N^2)).

* add quicksort benchmark

Create a top-level benchmarks folder. Add the benchmark implementation
for the quicksort algorithm.

Benchmarks can then be run in the same way as unit tests from the
root folder with:

	c3c compile-benchmarks benchmarks/stdlib/sort
2024-12-03 19:27:26 +01:00
Nexus
7381734913 fix: prevent infinite read-loop by updating left_to_read after write (#1652)
* fix not updated `left_to_read` after the buffer has been written
2024-12-02 14:34:57 +01:00
Christoffer Lerno
462322026f Fix bug with missing target in test and crash in benchmark. Note that this doesn't resolve the issues with these yet. 2024-11-30 20:26:04 +01:00
Christoffer Lerno
b5e5c719ed Enforce single module compilation for static libraries to make constructors run properly. 2024-11-30 15:34:54 +01:00
neokeld
a0f4976b07 Add char_at method in DString 2024-11-30 13:30:20 +01:00
Christoffer Lerno
44c2486a74 Update test for LLVM 20 2024-11-30 12:53:58 +01:00
Christoffer Lerno
5fc6672784 Crash compiling for arm64 when returning 16 byte and smaller structs by value not a power of 2 #1649. 2024-11-30 11:47:49 +01:00
Christoffer Lerno
bcb1edba90 Update tests. 2024-11-28 23:32:34 +01:00
Christoffer Lerno
8099e7a75d Update LLVM debug info. 2024-11-28 21:59:20 +01:00
Christoffer Lerno
cc9a501351 Fix bug preventing optionals from being used in ranges or as indices. 2024-11-28 00:48:58 +01:00
Christoffer Lerno
b536a23124 Updated release notes. 2024-11-27 13:46:39 +01:00
Christoffer Lerno
6ca5bcc6b8 Add simple memcpy, memcmp and memset functions for nolibc. 2024-11-27 13:45:41 +01:00
Christoffer Lerno
ac966f118a Updated base32 / base64 API. 2024-11-27 11:58:28 +01:00
Christoffer Lerno
f13472a8c3 Contracts on generic modules would evaluate too late, sometimes not catching the error until it already occurred elsewhere. Add file::save. 2024-11-27 00:02:43 +01:00
Christoffer Lerno
0e213ae777 Disable report heap allocs using parameter. 2024-11-26 03:11:10 +01:00
Christoffer Lerno
a0c82a6a47 Updated base32 API. 2024-11-26 03:01:45 +01:00
Christoffer Lerno
a087ba608b Begin unifying baseXX encodings. b64 / hex data strings can now be used with \` as well. 2024-11-25 16:20:10 +01:00
Tim Jurcka
9112d63655 Fix args passed to __asan_region_is_poisoned 2024-11-25 11:44:39 +01:00
Koni Marti
3f7f7a0aa7 base64: use url encoding with updated api
Ensure that the URL alphabet for base64 is used with the urlencode
functions (urlencode, urlencode_buffer, urlencode_temp and
urlencode_new) are used. Add a new test.
2024-11-25 11:44:24 +01:00
Koni Marti
8d03aafe72 base32: update base32 api
Update the base32 api to be consistent with the recent changes to the
base64 api introduced by commit 60101830 ("Updated base64 encoding
api").
2024-11-25 11:43:40 +01:00
Koni Marti
b0c0fd7dc8 encoding: implement hex encoding (base16)
Implement hex encoding and decoding (base16) according to RFC 4648.
Add unit tests.

Link: https://www.rfc-editor.org/rfc/rfc4648
2024-11-25 11:41:22 +01:00
Nexus
c273f26cb3 Add "sources" option support for library. (#1631)
* Add "sources" support for library manifest
* Add "sources" to library manifest creation
* Add "sources" key to target manifest
* Added fallback for already made libraries
* Remove src/ in library creation
* add changes to releasenotes.md
2024-11-24 15:37:15 +01:00
Christoffer Lerno
60101830cc Updated base64 encoding api. 2024-11-24 00:14:31 +01:00
Ellipse12
a58d782704 added check for to_string in is_struct_with_default_print
The function wasn't checking if the struct had the method `to_string` which made it segfault when trying to override the default to_string on a struct
2024-11-23 23:08:45 +01:00
Koni Marti
9b94c1dda9 fix: base64 decoding
Fix the base64 decoding. If there's an 'A' character in the encoded
text, the base64 decode function returns an INVALID_PADDING error. The
reason lies in the way Base64Decoder.init tries to find a suitable
invalid character. Fix this by defining the invalid character as 0xff
(which is already the case for a decoding without padding).

This error has not been caught by the test harness, because no test
contains an 'A' character in the the encoded text yet. Add a new test.
2024-11-23 23:06:44 +01:00
Christoffer Lerno
201a6b350e Support MSVCRT and OLDNAMES.lib in python script. 2024-11-23 18:54:27 +01:00
Christoffer Lerno
b2724caeda Begin work on asm label support. 2024-11-23 17:10:42 +01:00
Sander van den Bosch
9d99d556a1 Add .tlb file extention from msvc files to .gitignore 2024-11-22 22:37:38 +01:00
Christoffer Lerno
a1a6511e26 Remove "Timespec" 2024-11-22 16:50:29 +01:00
Christoffer Lerno
652456646f Prevent methods from using names of properties or fields. #1638 2024-11-22 16:40:33 +01:00
Christoffer Lerno
ca0dc49f64 Improve support for Windows cross compilation on targets with case sensitive file systems. 2024-11-21 23:28:58 +01:00
Christoffer Lerno
ae1b39eb60 Not possible to alias or take reference for extension methods on non-user defined types. #1637 2024-11-21 14:48:13 +01:00
Christoffer Lerno
22f7faf60e SimpleHeapAllocator bug when splitting blocks allowed memory overrun. 2024-11-21 13:36:24 +01:00
Christoffer Lerno
f3bf9eb14d Update mingw packages. 2024-11-21 11:31:55 +01:00
Christoffer Lerno
347a1a48d4 Indexing an Optional slice would crash in codegen #1636. 2024-11-21 11:30:53 +01:00
Christoffer Lerno
c9793457f3 Fix issue with properties in different targets not being respected. #1633 2024-11-21 01:24:44 +01:00
Christoffer Lerno
50d31ba398 Fix issue with overloaded subscript and ++/--. 2024-11-20 23:44:42 +01:00
Sander van den Bosch
2788c4cc00 Add new AMX and other feature flags 2024-11-20 23:43:30 +01:00
Christoffer Lerno
ba54232b8d Fix issue with overloaded subscript and ++/--. 2024-11-20 00:23:08 +01:00
Christoffer Lerno
489bb70901 Updated cast rules 2024-11-19 00:04:10 +01:00
Christoffer Lerno
dd06dfa5ba Fix issue with resolved try-unwrap in defer. 2024-11-18 15:53:27 +01:00
Walther Chen
f39e339726 Fix error when HashMap.remove on uninitialized HashMap (#1629)
* HashMap: test removal on uninitialized

* HashMap.remove_entry_for_key: return false on unintialized

* test: switch to temp_init

* release note
2024-11-18 14:20:32 +01:00
Christoffer Lerno
295b374b48 Support &a[0] returning the distinct type when applying it to a distinct of a pointer. 2024-11-17 22:25:57 +01:00
Christoffer Lerno
8ed390c394 A distinct inline pointer type can now participate in pointer arithmetics. 2024-11-16 23:08:54 +01:00
Christoffer Lerno
f9e9cac6e8 Cleanup and better contract error messages. 2024-11-16 00:02:03 +01:00
konimarti
f3304acc93 Add io stream primitives (#1626)
* io: implement MultiReader struct

Implement a MultiReader (InStream) which sequentially read from the
provided readers (InStreams). Return IoError.EOF when all of the readers
are read.

* io: implement MultiWriter struct

Implement a MultiWriter (OutStream). The MultiWriter duplicates its
writes to all the provided writers (OutStream).

* io: implement TeeReader struct

Implement a TeeReader (InStream) which reads from a wrapped reader
(InStream) and writes data to the provided writer (OutStream).
2024-11-15 23:18:29 +01:00
Walther Chen
a233771433 Fix WriteBuffer.write_bytes off-by-one (#1625)
* fix WriteBuffer.write_bytes off-by-one

* test for WriteBuffer.write_bytes off-by-one
2024-11-14 14:58:09 +01:00
Christoffer Lerno
ea9a871d90 Fix incorrect doc contracts on interfaces. 2024-11-14 11:47:00 +01:00
Christoffer Lerno
84d010bb2f Remove accidental doc comment. 2024-11-14 11:14:02 +01:00
Christoffer Lerno
e0ba468b7e Update mingw libs. 2024-11-14 01:26:29 +01:00
Christoffer Lerno
f88c0dd645 Tweak the error message on unexpectedly getting a non-type identifier. #1622 2024-11-14 00:19:17 +01:00
Walther Chen
758918c077 fix WriteBuffer.write_byte 2024-11-14 00:06:54 +01:00
Christoffer Lerno
7b516e6113 @builtin was not respected for generic modules #1617. 2024-11-13 23:34:34 +01:00
Matteo Cardinaletti
61a76bb834 Init command will now add test-sources to project.json #1520 2024-11-12 15:17:09 +01:00
vssukharev
e6b6edefaf Add support for nix flakes (#1614)
* Add support for nix flakes
* Added debug build type for flake.nix; Got rid of redundant version check in nix/default.nix
* Added dev shell with compile_commands.json into flake.nix
* Fixed issue with generated compile_commands.json while creating c3c derivation with nix. Deduced devShells in flake.nix to nix/shell.nix
2024-11-12 12:33:39 +01:00
Christoffer Lerno
a228eb020d Allow splat in initializers. 2024-11-11 23:54:35 +01:00
Christoffer Lerno
c46933a81a Refactor "splat" parsing. 2024-11-11 15:43:17 +01:00
Christoffer Lerno
746046c8c0 Fix bug where a > 0 ? f() : g() could cause a compiler crash if both returned void!. 2024-11-10 01:18:56 +01:00
Christoffer Lerno
acab95792f Improve error message when incorrectly using Type as an rvalue. 2024-11-10 01:18:56 +01:00
Christoffer Lerno
b882265e52 Start work on 0.6.5 2024-11-10 01:18:56 +01:00
Christoffer Lerno
547f2ef189 Tighten up conversion rules for arrays and slices. 2024-11-10 01:16:01 +01:00
Christoffer Lerno
69004943a7 Update to 0.6.4. 2024-11-09 17:09:54 +01:00
Christoffer Lerno
658fb4b1f9 Updated for 0.6.4 release. 2024-11-08 12:38:23 +01:00
Christoffer Lerno
e675b0c00a Fix last test for LLVM20 2024-11-07 13:49:03 +01:00
Christoffer Lerno
95ac29559c Fix some tests for LLVM20 2024-11-07 12:43:26 +01:00
konimarti
b7a095b4b4 Fix Formatter.floatformat and Object.to_format (#1602)
* fix: change float format specifier in Object.to_format

Fix the float format specifier in Object.to_format. If there is a float
stored in a Object such as 3.14, it would be printed out as 3 because
the format specifier is %d but should be %g.

* fix: print nan in floatformat

Fix floatformat to print 'nan' if float is nan. Currently,
io::printn(float.nan) will produce 'inf' instead of 'nan'.
2024-11-07 00:01:14 +01:00
Christoffer Lerno
08d1b29301 Add contracts on @catch / @ok. Taking the $typeof of a wildcard optional returns void! 2024-11-05 10:50:38 +01:00
Christoffer Lerno
741707273d Add "prepare" target in project.json #1577 2024-11-04 22:46:19 +01:00
Tomas Kallup
bdd6ed0e83 Fix: Unify termios types for actions & flags
The type for `Tcflags` was used instead of the CInt (now `Tcactions`)
and vice versa.
2024-11-04 17:15:40 +01:00
konimarti
8154e275fa encoding: implement RFC4648 base32 encoding (#1596)
Implement base32 encoding and decoding according to RFC 4648 with the
standard and extended hex alphabets. Add unit tests.

Link: https://www.rfc-editor.org/rfc/rfc4648
Signed-off-by: Koni Marti <koni.marti@gmail.com>
---------

Signed-off-by: Koni Marti <koni.marti@gmail.com>
2024-11-04 12:19:28 +01:00
Christoffer Lerno
6258cba79a @tag on macros cannot be retrieved with tagof #1582 2024-11-04 02:51:58 +01:00
Waqar Ahmed
213831289a Fix compile with gcc 14.2.1 (#1594)
* Fix compile with gcc 14.2.1 Fixes -Werror=maybe-uninitialized warnings
* Updated to use INVALID_PTR rather than NULL.
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2024-11-04 00:20:14 +01:00
Christoffer Lerno
ba34b45651 Fix LLVM 20 compilation 2024-11-03 23:52:48 +01:00
rexim
4be5c74798 Relative DESTINATION path when installing man page
This makes the install() function install the file with respect to
`CMAKE_INSTALL_PREFIX` which may not always be `/usr/local/` especially
when some people install the compiler locally in their `$HOME` folder.

https://cmake.org/cmake/help/latest/command/install.html
2024-11-03 23:20:15 +01:00
Christoffer Lerno
b06a611e69 Improve error message when using void aliases as variable storage type. 2024-11-01 14:56:29 +01:00
Christoffer Lerno
fd5b8d1374 Add bounds checking on List access. 2024-11-01 14:18:29 +01:00
Christoffer Lerno
4d84811629 Fixed test. 2024-10-31 21:41:50 +01:00
Christoffer Lerno
cd4fd02ee3 Sometimes generating introspection info would not be in the global scope causing a crash #1586. 2024-10-31 21:27:46 +01:00
Ygor Pontelo
475972aecd allow current directory as project (#1580)
* allow current directory as project
* avoid multiple underscores in module name

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2024-10-30 21:23:51 +01:00
Christoffer Lerno
b7a23e558a Fixes with error handling recursive @tag #1583. 2024-10-30 20:57:17 +01:00
Christoffer Lerno
827440686f $define would occasionally not properly evaluate declarations it encountered. 2024-10-30 11:59:18 +01:00
aiko
7f70145f55 wrote manpage in c3c.1 and added manpage install to CMakeLists.txt 2024-10-28 22:37:56 +01:00
MohMaGen
0639659270 fix: remove requirements for pop_first. 2024-10-27 20:35:09 +01:00
Christoffer Lerno
4be08ee0bd string::new_struct_to_str and io::struct_to_format to dump struct data. io::print will now print structs. 2024-10-26 21:00:07 +02:00
Christoffer Lerno
cc6a24cf80 Null-check function pointer invocation #1573. 2024-10-26 18:41:47 +02:00
Christoffer Lerno
b187d5a3fc Named vector component access would not fold at compile time. #1574 2024-10-26 13:22:35 +02:00
Christoffer Lerno
83f8d24892 Update sponsors. More comments in path.c3 2024-10-26 13:00:54 +02:00
Walther Chen
fd1898b70a copy out keys also in HashMap.copy_keys (#1569)
* copy out keys also in HashMap.copy_keys
* test for copying keys out
2024-10-26 01:57:00 +02:00
Walther Chen
8ce63106b9 Update .editorconfig to reflect current style (#1571)
* Update .editorconfig to reflect current style
* .editorconfig use tabs for python
* update msvc_build_libraries.py from two-space to tab indent
* update test/src/tester.py from 4-space to tab indent
2024-10-25 17:44:32 +02:00
Christoffer Lerno
c0c571ffe0 Incorrect error message when $eval is provided an invalid string. #1570 2024-10-25 10:31:45 +02:00
Christoffer Lerno
d8f4da4d90 Incorporating change from #1568 and the other fix needed. 2024-10-23 23:48:27 +02:00
Matteo Cardinaletti
f02387073d Json AST: Adding 'constants' and Improving 'globals' #1540, #1541 2024-10-23 23:31:28 +02:00
Christoffer Lerno
d344cc6020 (uptr)&((Foo*)null).a incorrectly inserts a null check. #1544.
Fix regression handling typedefs from generic modules.
2024-10-22 12:15:39 +02:00
Christoffer Lerno
9100638400 &self argument not implicitly null checked. #1556. 2024-10-21 00:45:40 +02:00
Christoffer Lerno
d2085654a7 if (try foo) was handled incorrectly inside a defer. 2024-10-20 18:30:03 +02:00
Christoffer Lerno
78d5939d9d Show error when declarations do not start with fn in interfaces. #1565. Some added functionality to lists and time. 2024-10-18 17:10:00 +02:00
Christoffer Lerno
c013006671 Improve infer conversions on constants, e.g. ZString a = foo ? "a" : "b"; #1561 2024-10-17 01:20:52 +02:00
Fernando López Guevara
e09a9f0d80 chore(cmake): improve lld resolution on apple 2024-10-17 00:46:16 +02:00
Christoffer Lerno
705856d51a - Disallow casting a void* to any or an interface, unless it is null.
- Defer resolution of declarations when looked up in `def` aliased #1559.
2024-10-16 12:50:47 +02:00
Christoffer Lerno
4445b6c054 Improve error messages on expressions like var $type = int; #1553. 2024-10-15 11:50:15 +02:00
Christoffer Lerno
cf03bc0a0a Formatting fixes 2024-10-14 16:13:51 +02:00
Christoffer Lerno
f37f1769ae c3.l grammar fixed. 2024-10-14 12:37:53 +02:00
Christoffer Lerno
31cd839063 Switch to <* *> docs. Fix issue with dynamically loaded C3 libs with other C3 code. 2024-10-14 02:14:34 +02:00
Christoffer Lerno
9f6a4eb300 Empty expression block would crash compiler with debug on #1554. 2024-10-13 02:19:26 +02:00
PavelBlinnikov
6a2957faf7 fix: segfault in panic when statically compiled
Co-authored-by: RoadToLP <ilya.titoff2013@yandex.ru>
2024-10-12 22:18:38 +02:00
Walther Chen
e6c9cfed42 Small fixes to AST json (#1550)
* fix ast json

* INERT_COMMA -> INSERT_COMMA

* fix struct field types
2024-10-12 19:28:47 +02:00
Christoffer Lerno
0da7f1b4de Wrong error message for interface methods with body #1536. 2024-10-12 02:51:07 +02:00
Christoffer Lerno
8ce171877e Also weaken libc and generic templates. 2024-10-12 02:13:35 +02:00
Christoffer Lerno
a38c7f6e49 Fix tests. 2024-10-12 01:19:15 +02:00
Christoffer Lerno
d19f628c73 Return missing MSVC fix. 2024-10-12 00:05:50 +02:00
Christoffer Lerno
526d15b804 Stop using linkonce and use weak instead. 2024-10-11 23:56:29 +02:00
Christoffer Lerno
c308397ed6 Fix weak linking for windows. 2024-10-11 23:35:12 +02:00
Christoffer Lerno
0cf93a8c32 Fix test. 2024-10-11 22:13:53 +02:00
Christoffer Lerno
cba25710fe Refactor and unify linker visibility and fix issues with weak stdlib. 2024-10-11 21:45:44 +02:00
Christoffer Lerno
1b471283c9 - Don't weakly link in exe. 2024-10-11 18:11:27 +02:00
Christoffer Lerno
09fee2aa4b - Standard library is now correctly weakly linked, fixing the use of C3 .so together with executable. #1549, #1107. 2024-10-11 17:14:02 +02:00
Koni Marti
2739c86881 Add csv unit tests
Add unit tests for std::encoding::csv.

Signed-off-by: Koni Marti <koni.marti@gmail.com>
2024-10-11 16:11:45 +02:00
Christoffer Lerno
cdf67684cc Add releasenote for latest fix. 2024-10-11 13:53:29 +02:00
Koni Marti
4e5ba327fe Fix mem leak in HashMap.keys_new_list
Fix a memory leak in HashMap.key_new_list(). The custom memory allocator
will not be used, since key_new_list() will call HashMap.copy_keys()
without passing the memory allocator along. Hence, HashMap.copy_keys()
will allocate on the heap and these memory blocks will not be freed.

To fix this, pass the custom allocator to HashMap.copy_keys(). Also,
since HashMap.key_new_list() is deprecated anyways, replace it by
HashMap.copy_keys().

Affected from this leak is Object.to_format() from
std::collection::object (for an ObjectInternalMap) which is used in the
JSON parser.

The tests for the JSON parser show the memory leak:

$ c3c compile-test test/unit/stdlib/encoding
$ valgrind --leak-check=yes ./testrun
==1454708==
==1454708== HEAP SUMMARY:
==1454708==     in use at exit: 384 bytes in 8 blocks
==1454708==   total heap usage: 69 allocs, 61 frees, 528,672 bytes allocated
==1454708==
==1454708== 48 bytes in 1 blocks are definitely lost in loss record 1 of 8
==1454708==    at 0x48447A8: malloc (vg_replace_malloc.c:446)
==1454708==    by 0x12CDBF: std.core.mem.allocator.LibcAllocator.acquire (libc_allocator.c3:42)
==1454708==    by 0x1790FD: malloc_try (mem_allocator.c3:64)
==1454708==    by 0x1790FD: alloc_array_try (mem_allocator.c3:286)
==1454708==    by 0x1790FD: alloc_array (mem_allocator.c3:269)
==1454708==    by 0x1790FD: copy_keys (hashmap.c3:310)
==1454708==    by 0x1790FD: std_collections_map$String$p$std.collections.object.Object$.HashMap.key
==1454708==    by 0x14D593: std.collections.object.Object.to_format (object.c3:53)
==1454708==    by 0x164556: std.io.Formatter.print_with_function (formatter.c3:86)
==1454708==    by 0x165B49: std.io.Formatter.out_str (formatter.c3:152)
==1454708==    by 0x16E2B0: std.io.Formatter.vprintf (formatter.c3:456)
==1454708==    by 0x12696B: std.core.dstring.DString.appendf (dstring.c3:532)
==1454708==    by 0x124EA9: std.core.string.tformat (string.c3:79)
==1454708==    by 0x113C79: json_test.test_string (json.c3:34)
==1454708==    by 0x118AA1: std.core.runtime.run_tests (runtime.c3:227)
==1454708==    by 0x1190B1: std.core.runtime.default_test_runner (runtime.c3:246)
==1454708==

[..snip..]

==1454708==
==1454708== LEAK SUMMARY:
==1454708==    definitely lost: 384 bytes in 8 blocks
==1454708==    indirectly lost: 0 bytes in 0 blocks
==1454708==      possibly lost: 0 bytes in 0 blocks
==1454708==    still reachable: 0 bytes in 0 blocks
==1454708==         suppressed: 0 bytes in 0 blocks
==1454708==
==1454708== For lists of detected and suppressed errors, rerun with: -s
==1454708== ERROR SUMMARY: 8 errors from 8 contexts (suppressed: 0 from 0)

Signed-off-by: Koni Marti <koni.marti@gmail.com>
2024-10-11 13:52:34 +02:00
Christoffer Lerno
efeb9e627e Interfaces not correctly copied with generics #1545. 2024-10-11 13:26:22 +02:00
Christoffer Lerno
8e24f15d58 Cannot use void as a generic parameter #1546. Interfaces now support .ptr and .type directly without casting to any. 2024-10-11 12:10:35 +02:00
Christoffer Lerno
1adad860f4 Update AST output. 2024-10-11 11:24:01 +02:00
Christoffer Lerno
cf07570871 Improved error message when declaring a variable void!. 2024-10-11 00:15:49 +02:00
Christoffer Lerno
bf30e52993 Improved error message on invalid subscript index type #1535. 2024-10-10 14:59:16 +02:00
Christoffer Lerno
a91ddd40dd Infer now works across ternary. Crash returning struct or vector from function using ternary expression #1537. 2024-10-10 14:44:40 +02:00
Christoffer Lerno
57ecadd23e Updated CSV API 2024-10-10 14:14:24 +02:00
Christoffer Lerno
967f14148f Bug when defers and $if were combined in a macro, which would cause miscompilation. 2024-10-10 13:42:12 +02:00
Christoffer Lerno
7a6544b17c Constant bytes <=> char[] conversion should work #1514 2024-10-09 19:38:06 +02:00
Christoffer Lerno
bf59efd3f4 Fix sincos to be different on Darwin compared to other. 2024-10-09 16:14:24 +02:00
Christoffer Lerno
c1266e9d06 Compiler error when any/interface initialized using {} #1533. 2024-10-09 13:12:40 +02:00
Christoffer Lerno
557adb6ed9 Update the init function of the arena allocator. 2024-10-09 12:45:50 +02:00
Christoffer Lerno
9f10996ac7 Bug when a continue is copied in a defer. 2024-10-08 22:13:30 +02:00
Christoffer Lerno
31f48829b0 Added CBool #1530. 2024-10-08 22:00:06 +02:00
Christoffer Lerno
39d4a97e24 Fix broken sincos function. 2024-10-08 20:34:41 +02:00
Christoffer Lerno
a665978b64 Fixing some whitespace issues. 2024-10-08 19:38:31 +02:00
Koni Marti
0cc62058a9 fix(string): use heap allocator for ZString.copy
Use the heap allocator for ZString.copy() instead of the temp allocator
to stay consistent across the code base.

The temp allocator is used for ZString.tcopy().
2024-10-08 17:33:29 +02:00
Christoffer Lerno
7dfdb9d061 Fix to ad hoc generic types. 2024-10-08 11:48:20 +02:00
Christoffer Lerno
e3ea1d5049 Deprecate @adhoc, allow non-nested ad hoc generic types. 2024-10-08 11:02:10 +02:00
Christoffer Lerno
19a96acac8 Improve error message in the case of MyInterface x = foo; #1522 2024-10-07 21:00:17 +02:00
Christoffer Lerno
80d016e076 Unintended deref of pointers with methods caused regression with hash function. 2024-10-07 20:43:37 +02:00
Fernando López Guevara
ac214b97df chore(cmake): switch from LLVM_LIBRARY_DIRS (list) to LLVM_LIBRARY_DIR for single path usage 2024-10-07 19:12:05 +02:00
Fernando López Guevara
1a948e4341 fix(time): update month 2024-10-07 19:11:14 +02:00
Christoffer Lerno
6bbc77a69c Segfault with passing a program with - using stdin. Using no module with - would reject the program. #1523 2024-10-06 12:19:13 +02:00
Denis Palashevskii
217151be8d fix int formatting in std::collections::object 2024-10-05 14:03:27 +02:00
Christoffer Lerno
2ef1465244 Improved ObjC support. 2024-10-04 23:25:51 +02:00
Christoffer Lerno
cfc1d0d8f8 Incorrect subscript resolution #1519 2024-10-04 20:50:48 +02:00
Christoffer Lerno
6fabecac1a Better error for int Foo(int a) declarations #1516 2024-10-04 18:15:06 +02:00
Christoffer Lerno
77ac864995 Unexpected compile error using a typed constant with copysign #1517 2024-10-04 18:01:57 +02:00
Christoffer Lerno
f95769541d Better error for int a[4] = .... #1518 2024-10-04 17:52:32 +02:00
Christoffer Lerno
fa4ca7944f - Add read/write to stream with big endian ints.
- Move accidently hidden "wrap_bytes".
2024-10-03 20:42:25 +02:00
Christoffer Lerno
02e9bfaf31 Separate const slice. Fix #1489. Fix const slice appending. Remove unintended print of char[] as String. Support const conversion of array -> slice. 2024-10-03 15:04:33 +02:00
Christoffer Lerno
7f66d5992f Update latest version. 2024-10-03 13:10:25 +02:00
Christoffer Lerno
84e10cf635 0.6.3 Release version. 2024-10-03 10:11:31 +02:00
Christoffer Lerno
1b8f8c5f5a Compiler crash when compiling c code in a library without --obj-out #1503. 2024-10-03 00:56:01 +02:00
Fernando López Guevara
131a783e89 feat(hash): added test for sha256 2024-10-02 16:55:17 +02:00
chri-k
2233f24c8f Add variants of DString.insert_at to match .append (#1510) 2024-10-02 10:22:59 +02:00
Christoffer Lerno
607a625641 Updated bigint. 2024-10-02 01:13:34 +02:00
Christoffer Lerno
9b49d19224 DString reverse and an initial BigInt implementation (untested), 2024-10-01 22:51:48 +02:00
Christoffer Lerno
46ae4353e0 Assume XTensa in 21+ instead. 2024-10-01 16:06:23 +02:00
Christoffer Lerno
44fcba2e3a Remove LLVM 20 2024-10-01 15:58:23 +02:00
Christoffer Lerno
f434795ee5 Test enable 19 and 20 2024-10-01 15:51:44 +02:00
Christoffer Lerno
0ea423d022 Foreach over distinct iterable would ignore operator(len). 2024-10-01 13:00:54 +02:00
Fernando López Guevara
c9b9de2838 fix(string): remove allocator argument on temp_ascii_to_lower 2024-09-30 22:01:44 +02:00
Christoffer Lerno
5918d5120f Foreach over distinct pointer failed to be caught as error #1506. 2024-09-30 21:32:33 +02:00
DanyDollaro
0d73f2fffa Added mutex tests (#1501)
* Added mutex tests. Add errorcheck in safe mode for Posix threads. Make non-recursive locks fail when used recursively on Windows. Fix thread pool tests. Simple locking count.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2024-09-30 20:57:16 +02:00
Christoffer Lerno
c8018c5543 Added generic PBKDF2 implementation. 2024-09-30 00:07:49 +02:00
Christoffer Lerno
071bd0ebf2 Fix bug when reading zip manifest, that would not return a zero terminated string. #1490 2024-09-29 21:14:03 +02:00
Christoffer Lerno
a00fce516e Added test and releasenotes for #1498. 2024-09-29 11:23:40 +02:00
chri-k
94abb3bd0c Fix escape sequence handling in encoding::json 2024-09-29 11:22:15 +02:00
Christoffer Lerno
2e94ea1a0d Improved error messages on Foo a = foo { 1 }; #1496 2024-09-28 23:53:31 +02:00
Christoffer Lerno
3b009e0b50 Added generic HMAC. 2024-09-28 23:28:11 +02:00
Christoffer Lerno
cc130e04dd Added MD5 and crypto::safe_compare. 2024-09-28 22:16:25 +02:00
Christoffer Lerno
2eca868540 Add UUID generation. 2024-09-28 20:58:03 +02:00
Christoffer Lerno
eeba5f020a Bad error on parameterized type without parameter #1495. 2024-09-28 19:44:57 +02:00
Christoffer Lerno
ded8bce8e6 Change no recursive import to use attribute. 2024-09-28 13:48:43 +02:00
Christoffer Lerno
8e7efaae99 ThreadPool is now adhoc available. 2024-09-28 13:28:39 +02:00
Alex Ling
fe9e434020 Fix incorrect to_gmt_offset result 2024-09-28 13:23:48 +02:00
Christoffer Lerno
8a0b0f5cf5 Disable fixed_pool where no threads are available. 2024-09-28 03:50:22 +02:00
Christoffer Lerno
5df321816b Unintended commit reverse. 2024-09-28 01:42:06 +02:00
Christoffer Lerno
7ff645c423 Free if broadcast fails. 2024-09-28 01:33:12 +02:00
Christoffer Lerno
93f290d57c Added a simple fixed threadpool which allocates. 2024-09-28 01:25:08 +02:00
Rachad ADEKAMBI
2146a76795 fix typo#1492 (#1493)
* fix typo#1492 
* Fix missing update in sema_decls

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2024-09-28 00:22:15 +02:00
chopsticks-user
b071e24d7e Minimal GL and GLFW bindings to render a triangle 2024-09-27 13:12:32 +02:00
Christoffer Lerno
a99e4b602a Error when slicing a struct with an inline array #1488. 2024-09-27 13:10:48 +02:00
Alex Ling
4cdea865f0 TzDateTime enhancements #1473 2024-09-26 12:32:05 +02:00
Christoffer Lerno
bf9ae2f0d3 Bad error message aliasing an ident with a path. #1481. 2024-09-25 21:41:40 +02:00
alex_s168
da2f958614 add x86 APX features (#1482)
add apx features Update cpu_detect.c3
2024-09-25 21:11:01 +02:00
Christoffer Lerno
b47201fe61 Fix handling of default target with libc. 2024-09-25 21:09:33 +02:00
Christoffer Lerno
a8932910d9 wasm32 / wasm64 targets are use-libc=no by default. 2024-09-25 21:01:00 +02:00
Christoffer Lerno
413877b59d Allow ^ suffix for non-recursive imports #1480. 2024-09-25 15:23:21 +02:00
Christoffer Lerno
da47588502 Make methods be available in earlier stages of analysis. Add @adhoc attribute to allow types with ad hoc generic declarations. 2024-09-25 14:26:49 +02:00
Christoffer Lerno
6f7ffbeb3c Add rand_in_range random function. Fix methodsof to apply to more types. Prevent methodsof in the wrong stage. 2024-09-25 00:18:11 +02:00
Christoffer Lerno
a258f2084f Allow specifying an import module using @wasm #1305. 2024-09-24 21:31:48 +02:00
Christoffer Lerno
d067a31ce6 Fix entropy 2024-09-24 20:15:10 +02:00
Christoffer Lerno
c6e4eee789 Added rnd function. 2024-09-24 19:02:03 +02:00
Christoffer Lerno
07c49f832e Safer seed of rand with WASM. 2024-09-24 18:38:11 +02:00
Christoffer Lerno
01b087238a Const initializer refactoring. Improve error on "Foo![]" #1477 2024-09-24 18:04:39 +02:00
Christoffer Lerno
d4832812ef Fix regression. 2024-09-23 14:13:55 +02:00
Christoffer Lerno
9c098fd79f Always flatten cont initializer inner type. 2024-09-23 02:51:53 +02:00
Christoffer Lerno
029d5e9068 Const initializer further cleanup. 2024-09-23 00:29:43 +02:00
Christoffer Lerno
f30486adf9 Const initializer cleanup. 2024-09-22 22:31:33 +02:00
Brian Sinquin
e21c337d3d Project fetch missing libraries (#1469)
Project fetch missing libs
2024-09-21 23:49:26 +02:00
Christoffer Lerno
f66f324e0e Suppor slicing of bytes. 2024-09-21 22:56:27 +02:00
Christoffer Lerno
885acdac24 Support compile time slicing of untyped lists. 2024-09-21 20:20:56 +02:00
Christoffer Lerno
ccb04c317c Fix bug due to enum associated values not being checked for liveness. 2024-09-21 18:05:20 +02:00
Christoffer Lerno
abbedeec4f Allow the "self" parameter to be $/# for macro methods. Fix bug when passing a type as a compile time value. 2024-09-21 15:55:39 +02:00
Christoffer Lerno
cdae3ec936 Some refactoring of the bitstruct representation. Correctly represent inner types. #1471 2024-09-21 13:43:52 +02:00
Christoffer Lerno
d727696830 Segfault using ternary with no assignment #1468. 2024-09-21 00:57:36 +02:00
Christoffer Lerno
2a9078a3b4 Also depend on Ubuntu20 2024-09-20 23:14:19 +02:00
Christoffer Lerno
ac479c7e40 llvm issue with try when bool is combined #1467 2024-09-20 20:15:44 +02:00
Christoffer Lerno
cda6ffea1e Slicing constant strings at compile time works. 2024-09-20 19:34:00 +02:00
Caleb-o
0900f401c0 Add Enum constraint to enummap
Fix typo in enumset require
2024-09-20 19:17:09 +02:00
Christoffer Lerno
d5a96ed637 Disabling LLVM 19 again. 2024-09-20 19:16:33 +02:00
Christoffer Lerno
8d9eff5297 Re-enable LLVM 19 2024-09-20 16:22:04 +02:00
Christoffer Lerno
19c1511901 Support linking .o files in compilation command. #1417 2024-09-20 16:21:29 +02:00
Christoffer Lerno
8e37e54645 Add env::COMPILER_BUILD_HASH and env::COMPILER_BUILD_DATE 2024-09-20 12:29:23 +02:00
Christoffer Lerno
c25645eab1 Add .gitkeep files to project subfolders. 2024-09-20 10:47:13 +02:00
Chandler
b9f7711f21 Update Arch Linux install instructions
c3c was recently added to the official Arch 'extra' repo. Updated the README to reflect this.
2024-09-20 00:40:58 +02:00
Christoffer Lerno
9447913de6 Use arena with JSON parser. Slightly altered output for json printing. 2024-09-20 00:39:10 +02:00
Brian Sinquin
8a9834cac0 Vendor-fetch download to lib directory specified in project.json (#1422) (#1441)
Vendor-fetch download default destination set to dependency-search-path in project.json Add fetched libraries in the project configuration file dependency entry.
2024-09-19 23:44:05 +02:00
Christoffer Lerno
2fec1c83a4 Enum attributes would be overwritten by enum value attributes. 2024-09-19 23:02:06 +02:00
Christoffer Lerno
ff36380ddf Allow user-defined attributes to have typed parameters. Folding a constant array of structs at compile time would cause an assert. 2024-09-19 22:21:29 +02:00
Christoffer Lerno
f2cfa61a39 User defined attributes could not have more than 1 parameter due to bug. 2024-09-19 21:14:08 +02:00
Christoffer Lerno
41156cc45d Temp allocator overwrites data when doing reset on extra allocated pages. #1462 2024-09-19 20:50:14 +02:00
Christoffer Lerno
9f51bfcc10 Support casting bitstructs to bool. 2024-09-19 01:03:06 +02:00
Christoffer Lerno
9426e813be Add test. 2024-09-18 23:16:07 +02:00
Christoffer Lerno
20fd7aba9b Regression when passing types as #expr arguments. #1461 2024-09-18 22:16:26 +02:00
Christoffer Lerno
3bada4560e Correctly print interfaces. 2024-09-18 14:53:55 +02:00
Christoffer Lerno
9719abe99a Better slice error message, and enable slice on non-vaarg. 2024-09-18 10:55:18 +02:00
Christoffer Lerno
5540519e52 Cleanup. 2024-09-18 10:20:57 +02:00
Christoffer Lerno
0b94e73c0b Regression fix arguments needed when presenting error on a method. 2024-09-18 10:07:39 +02:00
Christoffer Lerno
5e2a06bfd6 Update shell argument escape. 2024-09-18 00:48:39 +02:00
Christoffer Lerno
ac95e411bc Make str_eq safe to use with NULL. 2024-09-17 23:48:36 +02:00
Christoffer Lerno
08219fc57e Fix reordering semantics in struct assignment. 2024-09-17 22:41:49 +02:00
Josh Ring
09643f3c8b fix install instructions on ubuntu's latest LTS (#1449)
* fix install instructions on ubuntu's latest LTS
* cleanup arch linux and fixed some typos
2024-09-17 01:38:50 +02:00
Christoffer Lerno
08a575fa82 Crash invoking a @body argument with the wrong number of parameters. 2024-09-17 00:33:38 +02:00
Christoffer Lerno
62887a6ce8 Temporarily disable building with LLVM 19 and 20 2024-09-17 00:28:53 +02:00
Christoffer Lerno
297a6c9348 Support C3C_LIB and C3C_CC environment variables. Enable compiling against 20 to see if it works. 2024-09-17 00:12:50 +02:00
Christoffer Lerno
1181edc48e Fix error message when not finding a particular function 2024-09-16 22:43:30 +02:00
Christoffer Lerno
81f1930349 Code cleanup. Correct deprecation notice on '$or'. Allow "self" param on macro method to be constant. 2024-09-16 10:40:34 +02:00
RealPacket
54a1819d46 chore: Remove overrides for C3 files (#1450) 2024-09-16 10:21:41 +02:00
Christoffer Lerno
1b5472cc94 Add paramsof. 2024-09-15 23:43:09 +02:00
Christoffer Lerno
06a083bafc Lambda / function type would accidentally be processed as a method. 2024-09-15 22:12:03 +02:00
Christoffer Lerno
9bb45cb6a3 Add missing concat. Fix error message location on not enough arguments. 2024-09-15 15:56:13 +02:00
Real-Packet
1bfe9c568e chore(ci): Update actions/*-artifact to v4 (simple find & replace)
Because v3 is being deprecated and uses Node 16 instead of Node 20.
v4 is also a drop in replacement so we can just find & replace the usages without any changes to easily upgrade to v4.
2024-09-15 02:11:00 +02:00
Christoffer Lerno
4f5e5fcdba Remove mem tap. 2024-09-15 02:10:19 +02:00
Christoffer Lerno
bca44d1c14 Bug with casting anyfault to error. 2024-09-15 00:55:22 +02:00
Christoffer Lerno
e6d1d66c8f Updated grammar script and fix concat op 2024-09-14 23:13:06 +02:00
Christoffer Lerno
c3a5f5c0f0 Build macos version with LLVM 17 2024-09-14 22:18:33 +02:00
Christoffer Lerno
c0875d7987 Fix macos release. 2024-09-14 22:15:16 +02:00
Christoffer Lerno
6b6ac2bcb3 Rename release files. 2024-09-14 20:19:12 +02:00
Christoffer Lerno
f78466452a Updated grammar.y 2024-09-14 19:24:21 +02:00
Christoffer Lerno
f51230e793 Fix bugs in "trap-on-wrap" #1434. 2024-09-14 16:19:40 +02:00
Christoffer Lerno
3ab201ce10 Use atexit to fix finalizers on Windows #1361. 2024-09-14 16:17:57 +02:00
Christoffer Lerno
45a94cfe86 Asserts are now correctly included and traced in when running tests. Removed accidental debug trace. 2024-09-14 13:48:27 +02:00
Christoffer Lerno
f16cc999bd Fix bug where inline index access to array in a struct would crash the compiler. 2024-09-14 12:58:37 +02:00
Christoffer Lerno
d39f25efd3 Support inline struct designated init as if inline was anonymous. 2024-09-13 20:31:21 +02:00
Christoffer Lerno
6b2ce6de6f Fix unreachable. 2024-09-13 15:32:26 +02:00
Christoffer Lerno
3f1738e0fe Unified constant handling. 2024-09-13 15:11:15 +02:00
Real-Packet
3ceaf2ab81 chore: migrate to softprops/action-gh-release
Hopefully this works. Had to generate a new GPG key because GPG requires a password for signing stuff with GPG and I forgot to use it to sign something so I could save it in the password manager.
Fixes c3lang/c3c#1437
2024-09-13 15:07:45 +02:00
wilsonk
4c7d61ae82 Bsd family fixes (#1435)
Some small fixes for the BSD's
Try fcntl for NetBSD
Fixes for stdin, etc. and setjmp/longjmp
2024-09-13 14:49:51 +02:00
Alexey Kutepov
d53dd57b84 Introduce os::native_fputc() abstraction layer for File.write_byte() (#1440)
Introduce os::native_fputc() abstraction layer for File.write_byte()
2024-09-13 12:25:41 +02:00
Christoffer Lerno
6ff5ac5592 Removed unused functions. 2024-09-12 15:28:19 +02:00
Christoffer Lerno
9ce1bbe3cd Update README.md
Fix link
2024-09-12 14:02:10 +02:00
Christoffer Lerno
65c48419d0 Minor refactorings. Added "Thank you" section to readme. Some fixes to hostinfo. 2024-09-12 13:47:49 +02:00
alex_s168
d376ee6671 ability to disable llvm at compile time (#1433)
ability to disable llvm at compile time
2024-09-12 13:36:00 +02:00
Christoffer Lerno
eaa419a48d Correct '.so' suffix on dynamic libraries on Linux. 2024-09-12 09:21:23 +02:00
Christoffer Lerno
1b6ec34c61 Refactor alignment code. Change deprecated function in test. 2024-09-12 08:30:01 +02:00
Christoffer Lerno
9f4da339c3 Support int[*] { 1, 2, 3 } expressions. 2024-09-12 00:11:09 +02:00
wilsonk
1b54a99f6a Add initial FreeBSD support (#1430)
Add initial FreeBSD support
2024-09-11 22:38:53 +02:00
ElaDeCode
2b0d2892af move macro matrix_look_at to matrix module 2024-09-11 14:58:42 +02:00
Fernando López Guevara
27f2d201ed fix: cast native thread 2024-09-11 10:12:33 +02:00
Christoffer Lerno
d6cf622e49 Make subscript use its own "index" type rather than reuse Range. 2024-09-10 22:11:19 +02:00
Christoffer Lerno
2092e2167e Add io::read_new_fully for reading to the end of a stream. Add io::wrap_bytes for reading bytes with io functions. 2024-09-10 13:21:07 +02:00
Christoffer Lerno
503032cbcf Update range checking. 2024-09-10 13:21:07 +02:00
Christoffer Lerno
6f90e13502 Fix regression for $include. 2024-09-10 12:15:45 +02:00
Christoffer Lerno
b22bd459dd Fix regression for splat. 2024-09-10 00:21:01 +02:00
Christoffer Lerno
f67147a405 Fix bug in new splat code, fixes #1423. 2024-09-09 22:12:30 +02:00
Christoffer Lerno
df4eb3d0f0 Allow var in lambdas in macros. Allow ad hoc generic declaration in lambdas and type definitions. Fix deprecation flag. 2024-09-09 21:46:06 +02:00
Christoffer Lerno
32cc4bcd03 Fix issues for compiling on 32-bit. 2024-09-09 00:55:50 +02:00
Christoffer Lerno
1502c6d660 Limit object filename lengths. #1415 2024-09-07 23:38:20 +02:00
Christoffer Lerno
d4fb5b747b Update QOI type names. 2024-09-07 16:10:15 +02:00
Hema2
7581651011 Add QOI to the standard library (#1409)
Add QOI to the standard library
2024-09-07 15:55:26 +02:00
Christoffer Lerno
4f54e273ab Asserts are retained regardless of optimization when running tests. 2024-09-07 15:40:32 +02:00
Christoffer Lerno
1cc1b83b6f format functions are now functions and work better with splat. 2024-09-07 14:34:30 +02:00
Christoffer Lerno
8e9199f453 Untyped splat. 2024-09-07 14:26:42 +02:00
Christoffer Lerno
223501eeca Support splat for varargs #1352. 2024-09-07 05:26:43 +02:00
Christoffer Lerno
7649738618 Improve lvalue handling in the compiler. #1357 2024-09-07 03:19:35 +02:00
Christoffer Lerno
78c60ae695 Increase stack size for msys. 2024-09-07 01:37:45 +02:00
Christoffer Lerno
f5f122d5a5 Reduce recursion depth. Improve error message. 2024-09-07 00:48:16 +02:00
Christoffer Lerno
4b27a33a10 Refactor vasplat. 2024-09-07 00:29:41 +02:00
id3nom
15aca2eb84 Add CMake option C3_ENABLE_CLANGD_LSP (#1414)
* CMake option C3_ENABLE_CLANGD_LSP

The CMake option enables the generation of compile_commands.json in
the build directory and the creation of a symlink in the root
directory targeting the new file,
this will allow the Clangd Language Server Protocol (LSP) to
function properly.

* Added .editorconfig

EditorConfig helps maintain consistent coding styles:
https://editorconfig.org/
2024-09-06 23:06:09 +02:00
Christoffer Lerno
1cb91c0ac9 Fold default args in non-debug. 2024-09-06 23:04:09 +02:00
Christoffer Lerno
840b3b3161 "optsize" did not work correctly in project.json. 2024-09-06 22:55:15 +02:00
Christoffer Lerno
a5cf3ce2f1 Update releasenotes. 2024-09-06 20:56:38 +02:00
Lars Nilsson
04c85eb9ce Adding hashmap and map initialization functions with data (#1402)
Adding hashmap and map initialization functions with data to populate them with
2024-09-06 20:55:42 +02:00
Christoffer Lerno
82364d2e3c Function vasplat refactoring. 2024-09-06 20:54:28 +02:00
Christoffer Lerno
3db7bf5dfd Crash when reading an empty 'manifest.json'. 2024-09-06 18:05:43 +02:00
Christoffer Lerno
de13023981 Converting a slice to a vector/array would copy too little data. 2024-09-06 15:36:43 +02:00
Christoffer Lerno
35b825c78a Function vasplat refactoring. 2024-09-06 11:44:48 +02:00
Christoffer Lerno
28428fcf30 Handle "splice splat" in the vararg slot as an expression. 2024-09-06 10:43:03 +02:00
Christoffer Lerno
1e570bf506 Rename vec_erase_ptr_at to vec_erase_at. 2024-09-06 00:41:07 +02:00
Christoffer Lerno
ad0e97ab7b Deprecated inline generic types, deprecated tuple / triple types. 2024-09-05 23:42:20 +02:00
Christoffer Lerno
ed5d338a39 Added new style named arguments. 2024-09-05 22:13:22 +02:00
Christoffer Lerno
e795745e43 lvalue refactoring. 2024-09-05 22:09:35 +02:00
Christoffer Lerno
5e4d790fc3 Fixing incorrectly solved generic module name collision bug. 2024-09-04 21:51:03 +02:00
Christoffer Lerno
7e47f4ed08 Generic methods were incorrectly registered as functions, leading to naming collisions. #1402 2024-09-04 15:13:29 +02:00
Christoffer Lerno
63fc77a861 Move of const to separate file and removal of old concat code. 2024-09-04 09:34:51 +02:00
Christoffer Lerno
59ff94c005 Issue where a lambda wasn't correctly registered as external. #1408 2024-09-03 23:25:47 +02:00
Christoffer Lerno
bbc199cda3 Some cleanup of asm and assert 2024-09-03 13:53:15 +02:00
Christoffer Lerno
df91ee3d2a Update version. 2024-09-03 11:48:43 +02:00
Christoffer Lerno
528fecef4d Create release. 2024-09-02 23:21:01 +02:00
Christoffer Lerno
d39f1a6af0 Fix to test. 2024-09-02 22:53:54 +02:00
Christoffer Lerno
4367ef11fa Further fixing storeload. 2024-09-02 22:37:50 +02:00
Ikko Eltociear Ashimine
b8d77d2490 chore: update linux.c3
Recieve -> Receive
2024-09-02 11:07:34 +02:00
Christoffer Lerno
2600c3116c Do not add the libc allocator if it isn't available. 2024-09-02 01:44:35 +02:00
Christoffer Lerno
2506c2579b Prevent loading / storing large structs with LLVM. 2024-09-02 01:16:48 +02:00
Lars Nilsson
fe7392a656 Adding check of HTTP response code so that Windows will remove up the opened file if the download was not successful 2024-09-01 21:44:09 +02:00
Christoffer Lerno
8b7a3f1835 Optimize the value after foo()!! 2024-09-01 21:14:30 +02:00
Christoffer Lerno
e6acc56c1f Bug where if try would work incorrectly in a macro. 2024-08-31 23:09:36 +02:00
Christoffer Lerno
d635cfb90f printf will now show errors in the output when there are errors. 2024-08-31 19:36:18 +02:00
Christoffer Lerno
6cb6113c57 - Memory leak in Object when not using temp allocators.
- Tracking allocator would double the allocations in the report.
2024-08-31 03:35:39 +02:00
Alexey Kutepov
6aea0f12cd Properly persist git hash on each build (#1391)
Properly persist git hash on each build

Rebuild `git_hash.h` only when `.git` folder changes

`add_custom_target()` always considers its target out-of-date which
leads to rebuilding of `git_hash.h` on every build (which is
ironically what we wanted) and consequently rebuilding of
build_options.c and relinking of c3c even when no changes are made,
which is mildly annoying.

We are replacing `add_custom_target()` with `add_custom_command()`
which depends on `.git`, so `git_hash.h` is only rebuilt if any git
commands are performed. Which is less annoying.

In case of no `.git` we simply do not depend on it which leads to
`git_hash.h` being rebuilt only once.
2024-08-30 15:01:08 +02:00
Christoffer Lerno
99ace59b45 Create a build library on --test. 2024-08-30 14:55:35 +02:00
Christoffer Lerno
31f9ed3e6b Methods can now properly be aliased using def #1393. 2024-08-30 12:50:39 +02:00
Christoffer Lerno
573c0881e9 Correctly use wincrt setting for in libraries. 2024-08-29 23:53:14 +02:00
Christoffer Lerno
7134b3ba35 Update Raylib examples to use Raylib5. 2024-08-29 23:34:31 +02:00
Christoffer Lerno
bc267e22bd Add fmod implementation for nolibc. 2024-08-29 20:04:59 +02:00
Christoffer Lerno
3d83316b03 Regression: backtrace accidentally turned off by default. 2024-08-29 19:15:50 +02:00
Christoffer Lerno
dfe80eb050 Improve the error message when the compilation does not produce any files #1390. 2024-08-28 11:16:39 +02:00
Christoffer Lerno
22151a0a03 Fix bug with defer (catch err) when used together with regular defer. 2024-08-28 10:41:59 +02:00
rexim
484a9acc6f Print Git Hash on --version 2024-08-27 04:41:39 +02:00
Christoffer Lerno
26acce246d Fixed int128 div/mod. Fix WASM memory init priority. 2024-08-27 04:31:14 +02:00
Christoffer Lerno
388578c209 Too restrictive compile time checks for @const. Fixes to wasm nolibc in the standard library. 2024-08-26 13:33:15 +02:00
Christoffer Lerno
b33cce385c Fix of bug in defer (catch err) with a direct return error. 2024-08-26 11:49:41 +02:00
Christoffer Lerno
4b2019cf20 Add "allocator-required" functions. 2024-08-25 21:53:54 +02:00
Christoffer Lerno
61246d713d Print linking in CI and fix win linking. 2024-08-25 21:10:24 +02:00
Christoffer Lerno
40455f5260 Print linking in CI 2024-08-25 20:30:21 +02:00
Christoffer Lerno
78ce03bd62 Prefer \ to concat windows paths. 2024-08-25 20:14:36 +02:00
Christoffer Lerno
61645d14fa Only output -pie to the linker for executables. Fix issue assembling paths using concat_file_arg 2024-08-25 19:54:38 +02:00
Christoffer Lerno
d465ba5356 --test will now provide the full filename and the column. 2024-08-25 18:26:44 +02:00
Christoffer Lerno
8fde7cd6f5 --path is now properly respected. 2024-08-25 18:15:33 +02:00
Itzerr
734e0f350a Fixed gencontext_begin_module using wrong reloc_model. (#1384)
Fixed gencontext_begin_module using wrong reloc_model.
2024-08-25 18:08:33 +02:00
Chuck Benedict
e1bbab3831 RISCV: Correct auipc imm; clarify signed imm error; add imm negative t… (#1378)
RISCV: Correct auipc imm; claify signed imm error; add imm negative tests. Allow fitted int asm imm const in uints; add rv regs
2024-08-25 11:19:30 +02:00
Christoffer Lerno
a870881fff Allow "project.json5" to be used. 2024-08-25 00:19:08 +02:00
Christoffer Lerno
1ed3eab010 Assigning a const zero to an aliased distinct caused an error. 2024-08-24 14:37:27 +02:00
theunixer
dd16ecf63a Essential dependency to build(llvm17-devel) on void linux was not mentioned. 2024-08-24 11:10:59 +02:00
Christoffer Lerno
d1a0ec5a35 Compiler didn't detect when a module name was used both as a generic and regular module. 2024-08-23 19:31:49 +02:00
Nikita Pivkin
cb790b4672 Remove unused parameters from check_col and check_row
Signed-off-by: Nikita Pivkin <nikita.pivkin@smartforce.io>
2024-08-23 19:03:03 +02:00
Christian Buttner
19d37ef641 Add types::is_signed, is_unsigned and inner_type. (#1365)
Add `types::is_signed`, `is_unsigned` and `inner_type`.
2024-08-23 19:01:05 +02:00
Christoffer Lerno
0722011385 Fix of compile arguments. 2024-08-23 16:47:19 +02:00
Christian Buttner
59ed118e66 Address/memory/thread sanitizer. 2024-08-23 16:06:22 +02:00
Christoffer Lerno
d54468d7ed Simplify some asm code and update releasenotes for RISCV 2024-08-23 10:23:17 +02:00
Christoffer Lerno
218f1a6ead Add support for vendor specific extensions in project.json and manifest.json. 2024-08-22 12:49:14 +02:00
Christoffer Lerno
abbd94e89b Add iOS and Android targets. 2024-08-22 00:31:03 +02:00
Christoffer Lerno
b46463563e Add path test windows and escape in double quote. 2024-08-21 10:37:50 +02:00
Christoffer Lerno
33ce8e8a75 Add path test windows. 2024-08-21 10:15:45 +02:00
Chuck Benedict
05ab0707fc Add RISC-V block asm support 2024-08-20 22:42:38 +02:00
Christoffer Lerno
d32861193b DynamicArenaAllocator would not correctly free. 2024-08-20 22:42:01 +02:00
Christoffer Lerno
fb4a231703 Add $member.get(value) to replace value.$eval($member.nameof) 2024-08-20 14:24:12 +02:00
Christoffer Lerno
0963ab4cc0 Update readme. 2024-08-19 23:52:39 +02:00
Christoffer Lerno
a248511d7b Added ElasticArray 2024-08-19 23:20:14 +02:00
Christoffer Lerno
79a1639f8a Fix aligned alloc for Win32 targets. 2024-08-19 15:25:00 +02:00
Christoffer Lerno
476a6424ee insert_at incorrectly prevented inserts at the end of a list. 2024-08-19 11:44:06 +02:00
Christoffer Lerno
6de17b9ae9 Fix use of deprecated function. Fix bug when compile time subtracting a distinct type. Fix test/benchmark debug info use. 2024-08-19 09:36:45 +02:00
Christoffer Lerno
cb7116f08b New linker options handling 2024-08-19 01:28:57 +02:00
Christoffer Lerno
15a4e23b22 Benchmark / test no longer suppresses debug info. #1364 2024-08-18 22:29:41 +02:00
Christoffer Lerno
2b0857baf9 Add connection reset error. 2024-08-18 20:01:54 +02:00
Christoffer Lerno
20b0bf43ad Fix of dstring. 2024-08-18 09:50:54 +02:00
Christoffer Lerno
17d6f03bae New hashmap type, Map 2024-08-18 00:37:24 +02:00
Owen Shepherd
4edaf603c9 fix: Guard against uninitialized hashmap in key removal
Removing non-present keys is a supported operation on HashMaps,
and most other operations are well-defined on uninitialized
HashMaps.

Currently, removing any key on an uninitialized HashMap will
result in an 'Array index out of bounds' error.

This change guards against such a case.
2024-08-17 02:35:04 +02:00
Christoffer Lerno
74b8da1e15 Avoid any constants that have the "untyped list" type but isn't a CONST_UNTYPED_LIST. 2024-08-16 21:49:21 +02:00
Christoffer Lerno
16cb756d3f Bug converting untyped list #1360 2024-08-16 18:33:30 +02:00
Christoffer Lerno
f1efdf3d98 Incorrect zero analysis on foo["test"] = {} #1360 2024-08-16 16:50:58 +02:00
Christoffer Lerno
edfea639cf - Introduce $vaarg[...] syntax and deprecate the old $vaarg(...).
- Similar change to `$vasplat`: `$vasplat` and `$vasplat[1..]`.
2024-08-16 09:28:28 +02:00
Christoffer Lerno
9fd9280132 Fix incorrect parsing of $exec. 2024-08-16 00:07:09 +02:00
Christoffer Lerno
d0bb69516a Missing check on optional left hand side for s.x. #1360 2024-08-15 21:15:59 +02:00
Christoffer Lerno
dc44254ba1 Debug info with recursive canonical type usage could cause segfault. 2024-08-15 20:22:56 +02:00
Christian Buttner
85c682f7e6 Escape arguments to platform linker/compiler. (#1358)
* Escape arguments to platform linker/compiler.
2024-08-15 15:01:53 +02:00
Christoffer Lerno
2a69f93605 Issues with wincrt linking. 2024-08-15 13:33:08 +02:00
Christoffer Lerno
ad4950130c Remove use of tappend in rmtree on windows. 2024-08-15 00:40:01 +02:00
Christoffer Lerno
3ccb4b9ec3 $exec may now provide a stdin parameter. Deprecated path.append, path.tappend, getcwd, tgetcwd, path.absolute, ls. Deprecated env::get_config_dir, replaced by env::new_get_config_dir. Added path.has_extension, path.new_append, path.temp_append, new_cwd, temp_cwd, path.new_absolute, new_ls, temp_ls. Added dstring.replace Updated win escapes for exec. 2024-08-15 00:31:47 +02:00
Christoffer Lerno
6bc486400c Add globals to -P output. 2024-08-13 22:50:45 +02:00
Christoffer Lerno
9228dbb8b8 Fix ordering issues with $include / $exec by adding a pass #1302. 2024-08-13 22:19:53 +02:00
Christoffer Lerno
e68b453218 Do not bundle output with docker. 2024-08-13 15:23:56 +02:00
Christoffer Lerno
1dd2b0ec19 Add a few newlines to maybe keep some compilers happy. 2024-08-13 14:33:33 +02:00
Tom Clesius
e7e9d3b8c7 Adapt Docker script and Dockerfile (#1347)
Adapt Docker script and Dockerfile
2024-08-13 13:37:16 +02:00
kostyavechkanov
800ad9e898 Feature/add-target (#1) (#1350)
Feature/add target (#1) project add-target command
2024-08-13 13:34:53 +02:00
Christoffer Lerno
ddecf2d5f0 Correctly show macOS version settings for project.json 2024-08-13 10:25:15 +02:00
Mikhail Shimanov
09da17dab7 Update README.md
Duplicate package removed
2024-08-12 23:26:38 +02:00
Christoffer Lerno
1678e2a939 Assert not properly traced #1354. Update interface fix. 2024-08-12 21:01:04 +02:00
Christoffer Lerno
9aab962ebc Interface resolution when part of generics #1348. 2024-08-12 10:25:53 +02:00
Christoffer Lerno
baf6e71a80 Fix interface lazy resolution errors. Fix i128 change in LLVM. 2024-08-12 01:25:30 +02:00
Christoffer Lerno
412fa4b12f Use PIE/PIC on Linux 2024-08-11 23:00:38 +02:00
Christoffer Lerno
3cae557b88 Int128 alignment fixed on x64 Linux. 2024-08-11 22:48:21 +02:00
Christoffer Lerno
f7c39ae4a9 Recursively follow interfaces when looking up method. 2024-08-11 21:16:02 +02:00
Christoffer Lerno
6d93ce9d33 Update to libc::setjmp on Win32, to do no stack unwinding. 2024-08-11 18:22:14 +02:00
Sergwest
031cbae0d6 added the necessary library to build on void linux in the README.md 2024-08-11 17:07:48 +02:00
Christoffer Lerno
5fbee47c2b Update version information, 2024-08-11 17:03:21 +02:00
Christoffer Lerno
2cd25a489a Fix of global state init. 2024-08-11 16:55:40 +02:00
Christoffer Lerno
e67586b8b0 Fixes to library loading and test sources. 2024-08-11 16:46:53 +02:00
Christoffer Lerno
7d643942b4 Fix issues when checking methods and interfaces hasn't been resolved yet. 2024-08-11 16:16:16 +02:00
Christoffer Lerno
2257a7f4ec Comment out decl size. 2024-08-11 15:14:52 +02:00
Christoffer Lerno
f8ca173fd8 Refactoring a bit. 2024-08-11 15:05:36 +02:00
Christoffer Lerno
b08e6743be When resolving inherited interfaces, the interface type wasn't always resolved. 2024-08-11 10:19:20 +02:00
Christoffer Lerno
a97e4fe42d Add temp allocator scribble. Make bufferstream safer. 2024-08-11 01:17:25 +02:00
Christoffer Lerno
2706495668 Add temp allocator scribble. Make bufferstream safer. 2024-08-11 01:17:03 +02:00
Christoffer Lerno
224c3f4123 Printable values passed to the Formatter as pointers, will print as if passed by value. Pointers are rendered with "0x" prefix when passed to '%s'. 2024-08-11 00:27:06 +02:00
Christoffer Lerno
f2911be116 Assertion when has_tagof is accidentally called on fn type #1343 2024-08-10 21:59:41 +02:00
Christoffer Lerno
05c5eaed48 Add deprecation notice for $and, $or, $concat, $append. 2024-08-10 21:25:13 +02:00
Christoffer Lerno
8541e9535e Fix print when a tag is not found. 2024-08-10 21:15:19 +02:00
Christoffer Lerno
811cb2b95c Add string methods to json, and fix issue in dstring when the formatter uses temp. Remove unnecessary use of temp allocator in to_format for json. 2024-08-10 19:13:58 +02:00
Christoffer Lerno
05421223be Add --silence-deprecation 2024-08-10 09:54:59 +02:00
Christoffer Lerno
808a6b82f3 Add simple UTF16 detection. 2024-08-10 02:50:42 +02:00
Christian Buttner
30af7f1ca6 Add c-include-dirs project/manifest setting. (#1338)
Set the include directories to be used when compiling C sources.
2024-08-10 01:51:59 +02:00
Christoffer Lerno
274e5280cb Rename muldiv and update tests for LLVM 20 2024-08-09 23:56:26 +02:00
Samuel Goad
f85c4cd79f Update string_iterator.c3 to include extra convenience methods (#1327)
Update string_iterator.c3 to include extra convenience methods

Added peek: returns the next character without incrementing current
Added has_next: checks if the iterator has another element
Added get: gets the current element (the same one that was returned with the previous call to next).
2024-08-09 23:10:46 +02:00
Lexi
696d39b922 Move safe_mul_div macro and make it generic on integer types (#1334)
Move safe_mul_div macro and make it generic on integer types
2024-08-09 22:54:26 +02:00
Christoffer Lerno
d997445284 The compiler now skips UTF8 BOM. 2024-08-09 22:39:24 +02:00
Christoffer Lerno
f3e5268083 % analysis was incorrect for int vectors. 2024-08-09 15:23:40 +02:00
Christoffer Lerno
44db4a21fc Add @tag and .tagof .has_tagof. Allow bitstructs to have attributes. 2024-08-09 15:03:44 +02:00
Christoffer Lerno
c8a113384c Better precision with Clock on Win32 2024-08-08 23:03:04 +02:00
Lexi Allen
07e7bc0a94 Fix win32 native_clock() by converting native performance counter value to nanoseconds using previously gotten frequency value 2024-08-08 21:14:58 +02:00
Prithviraj Renjella Rajendra Prasad
7c8acbe485 Implemented arg passing to clean-run and run commands (#1328)
* added clean-run and run commands to list of commands that pass args

* Updated compiler usage message to reflect that commands run and clean-run accept args
2024-08-08 16:24:00 +02:00
Christoffer Lerno
65c2126202 Removing tb codegen info, because it's sure to have code-rotted by now. 2024-08-08 12:55:40 +02:00
Christoffer Lerno
0ef0f62b69 Only destroy temp allocators on env::LIBC. 2024-08-08 12:48:01 +02:00
Christoffer Lerno
921422a189 Fix Vec2.angle 2024-08-08 01:48:39 +02:00
Christoffer Lerno
56b771a7ad Support destroying temp allocators, and destroy temp allocators on exit. 2024-08-07 16:24:26 +02:00
Christoffer Lerno
d2988e6a88 With single module, name the .o file after what -o provides. #1306 2024-08-07 01:35:09 +02:00
Velikiy Kirill
16510d2400 Update tcp.c3 to finally (i guess) fix Windows Sockets (#1324)
Update tcp.c3
2024-08-06 21:20:59 +02:00
Christoffer Lerno
6f790598ef Update manifest.json template. #1321 2024-08-06 17:42:23 +02:00
Christoffer Lerno
63f0c7b2fe 'wincrt' in manifest.json should now be respected #1322 2024-08-06 17:37:43 +02:00
Christoffer Lerno
800f7970a7 Fixes to the socket functions. Improved output when pointer is out of range. Better error when casting to a distinct fails. 2024-08-06 17:08:03 +02:00
Velikiy Kirill
b7381fc075 Adding win32_WSACleanup() 2024-08-06 15:59:36 +02:00
Christoffer Lerno
b1785606cc LLVM codegen for constants in enums could fail. 2024-08-06 00:28:03 +02:00
Christoffer Lerno
387d7d5508 Fix defer on 19/20 2024-08-05 23:15:55 +02:00
Christoffer Lerno
6adacf8892 Fix module name regression. 2024-08-05 23:04:29 +02:00
Christoffer Lerno
f7d6f93f1b Refactoring -> ensure built in aliases have a valid unit. 2024-08-05 22:48:18 +02:00
Dodzey
9daa173ab7 Add methodsof to type info (#1303)
Add `methodsof` to type info for struct, union and bitstruct
2024-08-05 21:58:13 +02:00
Lexi
e748f72447 Project view command (#1314)
Add parsing for the project command and view subcommand. Add basic implementation of c3c project view. Move get_valid_integer into common build.

Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
2024-08-05 21:45:15 +02:00
Christoffer Lerno
14358417c8 Refactoring -> ensure built in aliases have a valid unit. 2024-08-05 21:44:02 +02:00
Oloruntobi1
8aa3461bf6 [DOCS] updated readme with issue 1086 link 2024-08-05 19:55:04 +02:00
Christoffer Lerno
04c37a98b5 Formatting. 2024-08-05 19:53:39 +02:00
rexim
4850f1e94b Ignore EINTR return from waitpid
Apparently it is a thing that can happen and for example musl just
ignores such situations and tries to wait again.

dd1e63c363/src/process/system.c (L39)
2024-08-05 19:53:05 +02:00
Christoffer Lerno
60945ffe58 Fix of tests. 2024-08-05 19:52:34 +02:00
Christoffer Lerno
746016996c Variable in if-try / if-catch cannot be a reused variable name. 2024-08-05 18:43:04 +02:00
Christoffer Lerno
67a2734777 Issue where a if (catch e = ...) in a defer would be incorrectly copied. Causing codegen error. 2024-08-05 15:20:50 +02:00
Christoffer Lerno
b208fc7cf5 Add unreachable. 2024-08-04 23:40:28 +02:00
Christoffer Lerno
2748cf99b3 - Fix issue where a compile time parameter is followed by "...".
- Fix issue with some conversions to untyped list.
- Experimental change: add `+++` `&&&` `|||` as replacement for `$concat`, `$and` and `$or`.
2024-08-04 23:16:25 +02:00
Christoffer Lerno
b49b60ab5f Fix compiler timings. 2024-08-04 11:21:23 +02:00
Christoffer Lerno
620c67b04e Bug in List add_array when reserving memory. 2024-08-04 01:34:45 +02:00
Alexey Kutepov
a5b5f315d1 Implement passing arguments to program via compile-run (#1296) 2024-08-03 19:47:52 +02:00
Christoffer Lerno
43ea05aad2 Remove $expand. 2024-08-03 12:47:19 +02:00
Halen84
0ec1c80221 Fix typo in parse_global.c
In parse_def_ident()
2024-08-03 03:50:55 +02:00
Christoffer Lerno
d91c289bf6 Distinct inline can now be called if it is aliasing a function pointer. 2024-08-03 03:08:38 +02:00
Christoffer Lerno
74b9971494 Add wincrt setting to libraries. 2024-08-02 20:15:40 +02:00
Christoffer Lerno
f8f116109a Use back-off strategy when allocating virtual memory. 2024-08-02 15:45:16 +02:00
Christoffer Lerno
8498cb6258 Add @const attribute for macros, for better error messages with constant macros #1293 2024-08-02 15:01:02 +02:00
Yhya Ibrahim
7a72f44f64 Add --run-once option to delete the output file after running it (#1295)
Add `run-once` option to delete the output file after running it
2024-08-02 12:47:36 +02:00
Christoffer Lerno
a90e3c440b Distinct inline would not implement protocol if the inlined implemented it. This closes #1292 2024-08-02 11:37:38 +02:00
Christoffer Lerno
1aab8b87ec Add experimental @noalias attribute. 2024-08-01 22:57:26 +02:00
Christoffer Lerno
ebf071ac51 Fix incorrect override of optimization levels when using projects. 2024-08-01 21:40:51 +02:00
Christoffer Lerno
3d0fc33441 && doesn't work correctly with lambdas #1279. 2024-08-01 21:16:23 +02:00
Christoffer Lerno
c50df85976 Fix docs to match the update in supporting LLVM 17+ only. 2024-08-01 20:04:54 +02:00
Christoffer Lerno
db9fc20acf Wrapper RTTI now follows LLVM RTTI. 2024-08-01 19:58:52 +02:00
Christoffer Lerno
10058cf271 - Distinct func type would not accept direct function address assign. #1287 2024-08-01 16:26:50 +02:00
Christoffer Lerno
310dadef45 No type_lowering in the frontend. 2024-08-01 13:26:47 +02:00
Christoffer Lerno
3159f036a2 Update lowering for function pointers. 2024-08-01 11:37:40 +02:00
Christoffer Lerno
c3e426c82a Assertion with duplicate function pointer signatures #1286 2024-08-01 01:52:26 +02:00
Christoffer Lerno
b83d388523 Incorrect justify formatting of integers. 2024-08-01 01:20:42 +02:00
Christoffer Lerno
d8820259d2 Enable LLVM 19 2024-08-01 00:21:14 +02:00
Christoffer Lerno
354d78e893 Temporarily disable LLVM for Linux 2024-08-01 00:12:01 +02:00
Christoffer Lerno
7f00f35f4b $expand macro, to expand a string into code. opt project setting now properly documented. 2024-08-01 00:07:16 +02:00
Yhya Ibrahim
8c33b073c2 Fix a warning/error where C compilers can not predict that a variable is initialized 2024-07-31 18:05:26 +02:00
Christoffer Lerno
d6490c9bab Make it possible to set max limit for memory pages. 2024-07-31 18:03:52 +02:00
PalsFreniers
b0e104bfd0 Adding Termios library as std::libc::termios (posix libc functions) (#1272)
Adding libc's termios to lib/std/libc
2024-07-31 14:45:04 +02:00
Chuck Benedict
563e677b08 Add Riscv Example (#1268)
Add Riscv example. Risc-V CI. Install baremetal toolchain. Prevent imported crt file from messing up linker search.
2024-07-31 14:43:47 +02:00
Ikko Eltociear Ashimine
7664d0568e Update bytewriter.c3
minor fix
2024-07-31 01:37:12 +02:00
Christoffer Lerno
e1a13e433f Experimental xtensa support 2024-07-31 01:36:28 +02:00
Dodzey
d212f7d946 Remove extra item 2024-07-30 17:52:14 +02:00
Christoffer Lerno
8d6dabf65c Struct members declared in a single line declaration were not sharing attributes. #1266 2024-07-30 02:45:50 +02:00
Dmitry Atamanov
a4c5b85db8 Remove extra space. 2024-07-29 15:16:14 +02:00
Christoffer Lerno
e66001c182 Using winmain would call the wrong definition #1265. 2024-07-29 15:04:32 +02:00
Christoffer Lerno
08c7b35731 Improve the error message when typing fn void Foo(). 2024-07-28 21:08:47 +02:00
Christoffer Lerno
35cb36fcea Fix incorrect linker selection. 2024-07-28 17:55:59 +02:00
Christoffer Lerno
bf8ca989d6 Add --show-backtrace option to disable backtrace for even smaller binary. 2024-07-28 01:10:59 +02:00
Christoffer Lerno
4976ebcef4 Permit foreach values to be optional. Update matching algorithm. 2024-07-27 21:53:44 +02:00
Christoffer Lerno
51661f5c55 c3c init-lib does not create the directory with the .c3l suffix #1253 2024-07-27 11:52:56 +02:00
Christoffer Lerno
3cbb10392c Don't generate .o files on compile and compile-run if there is no main. 2024-07-27 05:00:27 +02:00
Christoffer Lerno
168ce752d1 Package Linux binaries in a folder called "c3" and not "linux" 2024-07-27 02:49:09 +02:00
Christoffer Lerno
8fcf9bc6bf Give some symbol name suggestions when the path is matched. 2024-07-27 01:21:02 +02:00
Christoffer Lerno
56f43f55f3 Add WASM test. 2024-07-26 21:39:45 +02:00
Christoffer Lerno
9386ac026d dbghelp.lib was linked even on nolibc on Windows. 2024-07-26 20:54:57 +02:00
Christoffer Lerno
e1565ccdc5 Regression: Invalid is_random implementation due to changes in 0.6. 2024-07-26 20:49:17 +02:00
Christoffer Lerno
34993a20fd Fix broken WASM std library code. 2024-07-26 19:20:58 +02:00
Christoffer Lerno
ea0124433a Remove "EXPR_GROUP" to simplify the code somewhat. 2024-07-26 14:34:08 +02:00
Christoffer Lerno
73b15c691d Deprecate *-add settings, use without -add. Updated CI. 2024-07-26 03:23:30 +02:00
Christoffer Lerno
623dd9f3b3 Added "weak" type aliases def Foo = my_foo::Foo @weak; 2024-07-26 01:13:48 +02:00
Christoffer Lerno
379637f214 Scalar -> vector not implicit in call or assign. 2024-07-24 14:00:09 +02:00
Christian Buttner
26ca8f7777 Add type property is_substruct. 2024-07-24 14:00:09 +02:00
Christoffer Lerno
237f7e7f1a Updated stats. 2024-07-24 14:00:09 +02:00
Christoffer Lerno
34fc9851bf Update wrapper 2024-07-24 14:00:09 +02:00
Christoffer Lerno
abdaca08fe Add new optimizer runner. 2024-07-24 14:00:09 +02:00
Christoffer Lerno
bdc9f339c9 The msvc_sdk script failed to work properly on windows when run in folders with spaces 2024-07-24 13:18:26 +02:00
Christoffer Lerno
3188d4d858 Reference parameter doesn't work with vector subscript #1250. 2024-07-23 21:20:40 +02:00
Christoffer Lerno
1bb76b1a49 Unsplat with named parameters was accidentally disallowed. 2024-07-22 11:44:34 +02:00
Christoffer Lerno
9584efd84c Update AVX support. 2024-07-22 02:34:33 +02:00
Christoffer Lerno
c84bc8a8f3 Add convenience function. 2024-07-21 00:43:16 +02:00
Christian Buttner
edc55a2afd Small fixes to stdlib. (#1247)
Small fixes to stdlib. Match the signature of `NativeConditionVariable.wait_timeout` and `NativeMutex.lock_timeout` of thread_win32.c3 to `ConditionVariable.wait_timeout` and `TimedMutex.lock_timeout` to avoid casting errors. Add `time::us`.
2024-07-20 19:19:16 +02:00
Christoffer Lerno
480325177c Remove accidental debug code. 2024-07-20 18:40:25 +02:00
Christoffer Lerno
03cfa42eb6 Duplicate symbols with static variable declared in macro #1248. Improved error message when trying user foreach with an untyped list. 2024-07-20 03:39:33 +02:00
Christoffer Lerno
b25c573ae3 Indexing into a constant array / struct now works at compile time. Constants defined by indexing into another constant could fail codegen. Stdlib nolibc code bugs fixed. 2024-07-20 01:20:03 +02:00
Christoffer Lerno
7f5757d66b Add .dot to integer vectors. 2024-07-19 11:34:05 +02:00
Christoffer Lerno
a3a275c3d5 Updated linux build 2024-07-19 11:10:59 +02:00
Christoffer Lerno
557f007b12 Spelling 2024-07-19 10:38:52 +02:00
Christoffer Lerno
542406c16f Exclude 18 for linux for now. 2024-07-19 01:13:49 +02:00
Christoffer Lerno
1fa870411f Separate LLVM18 compile for Linux in CI 2024-07-19 00:36:04 +02:00
Christoffer Lerno
c096487eea Test if this fixes LLVM 18 compilation. 2024-07-19 00:32:27 +02:00
Christoffer Lerno
97a8e0cdd4 Retain backwards compatibility with old manifest.json. 2024-07-19 00:23:54 +02:00
Christoffer Lerno
eb20a5c051 mainfest.json is now checked for incorrect keys. Added --list-manifest-properties to list the available properties in manifest.json. 2024-07-19 00:03:05 +02:00
Christoffer Lerno
5c6acf89da Added docs to io.c3 2024-07-18 20:44:36 +02:00
Christoffer Lerno
9dfe7ddbde Add wrapper methods, use LLVM-transforms directly. 2024-07-18 20:44:36 +02:00
Christian Buttner
8285720180 Add tests and improvements for @nopadding and @compact. 2024-07-17 17:00:36 +02:00
Christoffer Lerno
a4a1a42842 Update llvm build to use on windows. 2024-07-17 16:55:55 +02:00
Christoffer Lerno
3c3217ab2b Fix PIE. 2024-07-16 14:58:48 +02:00
Alex Anderson
17ee3887dd Use usz and fix out of bounds access in branchless loop 2024-07-16 13:22:11 +02:00
Alex Anderson
db75da65db Make countingsort.c3's recursion stage branchless
Tracks the three potential cases for each fallback, item counts ranging from [2,32], [33,128], [128, ...] and uses a loop specifically for each fallback.
2024-07-15 22:25:59 +02:00
Christoffer Lerno
cf95257c81 Fix test (again). 2024-07-15 17:30:42 +02:00
Christoffer Lerno
b40036c203 Fix test. 2024-07-15 17:04:06 +02:00
Christian Buttner
b18661a8b0 Make stdlib mem::allocator more complete. (#1238)
Make stdlib mem::allocator more complete. Fill in some gaps and docstrings. List.to_new_array. Handle overalignment smoothly in list.
2024-07-15 16:35:40 +02:00
Christoffer Lerno
bc0d52142a Added pull request #1189: Fix os::native_is_{file,dir} bug. Add tests. 2024-07-15 03:02:54 +02:00
Christoffer Lerno
24041ed80d Macro $case statements now pick the first match and does not evaluate the rest. Added countingsort tests #1234. 2024-07-15 02:01:26 +02:00
Christoffer Lerno
1a03e6b22e Prevent implicit array casts to pointers with higher alignment. #1237 2024-07-14 23:44:05 +02:00
Christoffer Lerno
68fb916195 Fix when memcmp is defined. 2024-07-14 16:51:38 +02:00
Christoffer Lerno
dfb8a1b8cb Improved bool and float array comparisons. 2024-07-14 14:16:17 +02:00
Christoffer Lerno
6c38409c57 Array comparison now uses built-in memcmp on LLVM to enable optimizations. 2024-07-14 01:35:19 +02:00
Christoffer Lerno
27fd7a9088 - Fix problem where a $$FUNC would return "<GLOBAL>" when evaluated for a static in a function #1236. 2024-07-13 19:57:04 +02:00
Christoffer Lerno
0e62423e06 Bitstruct in struct fix. 2024-07-13 01:54:45 +02:00
Christoffer Lerno
3f45ed14b9 Compare @compact structs. 2024-07-12 23:54:07 +02:00
Christoffer Lerno
ca4b782912 MemberIndex -> ArrayIndex 2024-07-12 18:27:05 +02:00
Christian Buttner
1976a11154 @nopadding and @compact attributes (#1235)
Add `@nopadding` attribute. `@compact`
2024-07-12 18:25:09 +02:00
Christoffer Lerno
e7d8f64a49 Compile c files to separate directories. Add compressed library to example test project. 2024-07-10 13:35:01 +02:00
Christoffer Lerno
5cf1f13328 Private function called from nested macro not visible to linker #1232 2024-07-09 22:01:39 +02:00
Christoffer Lerno
fba706f10b Updated sorting code. 2024-07-09 01:04:11 +02:00
Alex Anderson
c50630989e draft: add countingsort.c3 (#1230)
Draft countingsort.c3
2024-07-08 21:08:57 +02:00
Christoffer Lerno
3832be94d0 Added sort helper function. 2024-07-08 21:02:49 +02:00
Alex Anderson
900c1152d3 add insertion sort (#1225) 2024-07-08 18:53:47 +02:00
Christoffer Lerno
4ea50a8a85 Update version. 2024-07-08 17:39:31 +02:00
Christoffer Lerno
0132fd4101 Bad error message when using a generic method without generic parameters #1228 2024-07-08 17:32:39 +02:00
Christoffer Lerno
0e90ce3b8a Prevent accidental delete of lib folder when building. 2024-07-08 14:05:09 +02:00
Christoffer Lerno
9368ebfbd3 Allow using $defined(&a[1]) to check if the operation is supported. 2024-07-08 01:42:34 +02:00
Christoffer Lerno
8381dbbd8f Fix incorrect INLINE on const init function. 2024-07-07 23:29:57 +02:00
Christoffer Lerno
343ccaa2ef Support c-file compilation in libraries. 2024-07-07 11:21:31 +02:00
Christoffer Lerno
3f62775f4b Support c-file compilation in libraries. 2024-07-07 02:04:37 +02:00
Christoffer Lerno
c3ecad96b7 Update CI, add example. 2024-07-05 16:53:49 +02:00
Christoffer Lerno
2ffb0cf5f7 Fix ABI lowering for 128 bit vectors on Linux. 2024-07-05 16:07:17 +02:00
Christoffer Lerno
ef716f3a69 Pull requests to dev also have a test action. 2024-07-05 15:17:23 +02:00
Christoffer Lerno
cc935862b7 Build using LLVM 18 2024-07-05 02:06:37 +02:00
Christoffer Lerno
85a535dd0c $typeof(*x) should be valid when x is an [out] parameter #1226 2024-07-04 16:50:35 +02:00
Christoffer Lerno
ab626fe3eb Update avoid warning in FetchContent 2024-07-04 12:07:01 +02:00
Christoffer Lerno
05011df13a Update flags to mac compile 2024-07-04 02:36:17 +02:00
Christoffer Lerno
fcdb25c426 Update some comments and variable names. 2024-07-04 02:15:08 +02:00
Christian Buttner
cc9ca35e04 Add $debugtrap builtin. (#1220)
Add `$breakpoint` builtin.
2024-07-04 00:50:29 +02:00
Christoffer Lerno
4a50de8318 Use LLVM 18 by default. Update MSVC to LLVM 18.1.8. 2024-07-04 00:48:35 +02:00
Christian Buttner
12051e7544 Fix $$unaligned_store arg check and add test. (#1224)
Fix `$$unaligned_store` arg check and add test.
2024-07-04 00:44:32 +02:00
Christoffer Lerno
210508fe4f Updated test. 2024-07-03 15:59:46 +02:00
Christoffer Lerno
ba5b045351 Fix Type->$Type in allocator #1223 2024-07-03 15:57:17 +02:00
Christoffer Lerno
9a19eeacb3 Added further tests to #1219 2024-07-03 15:14:50 +02:00
Christian Buttner
10ed03d6bf Extend win32 stdlib API. 2024-07-03 11:11:34 +02:00
Christoffer Lerno
3be1bf4384 Added test and updated releasenotes for formatter changes. 2024-07-02 23:28:23 +02:00
Christian Buttner
3396b20661 Fix formatter crash for null ZString, print "(null)" for null pointers. 2024-07-02 23:24:18 +02:00
Christoffer Lerno
c9e1140189 Reorganizing the Windows OS files. 2024-07-02 17:37:45 +02:00
Christoffer Lerno
416cd30b42 Wrong size for structs containing overaligned structs #1219 2024-07-02 15:17:41 +02:00
Christoffer Lerno
d66a07cc55 Add defer catch test. 2024-07-02 13:57:48 +02:00
Christoffer Lerno
ce17dbe240 Bug fix for rethrow + defer catch. More types and functions for win32 2024-07-02 02:48:48 +02:00
Christoffer Lerno
326fc501e2 Simplified @is_comparer 2024-07-02 00:36:05 +02:00
Christoffer Lerno
91ad3ee0a2 Fix regression for math::log 2024-07-01 16:52:39 +02:00
Christoffer Lerno
2993c422c1 Fix to scalar -> vector conversions. 2024-07-01 15:03:40 +02:00
Christian Buttner
6f8cdde7e4 Added a --no-headers option. 2024-07-01 13:38:58 +02:00
Christoffer Lerno
f521a0dd77 FOREACH_BEGIN / VECEACH replaced by FOREACH / FOREACH_IDX 2024-07-01 13:31:41 +02:00
Christian Buttner
12fdb58da6 Implicitly cast distinct inline to index. (#1218)
Implicitly cast distinct inline to index.
2024-07-01 13:16:39 +02:00
Christoffer Lerno
09876cefde @unaligned_store and @unaligned_load 2024-06-30 01:05:57 +02:00
Christoffer Lerno
d1e2ea7635 Require MSVC 17.7 or higher. 2024-06-29 20:47:25 +02:00
Christoffer Lerno
7b131f2a45 Print MSVC version 2024-06-29 20:35:23 +02:00
Christoffer Lerno
f3d5e3d4c2 Set minimum LLVM version for compilation. 2024-06-29 20:30:37 +02:00
Christoffer Lerno
492f83f5e2 Bit negating const zero flags would give an incorrect result. #1213 2024-06-28 16:43:57 +02:00
Christoffer Lerno
7dcd1618d8 Fixes to header gen. 2024-06-28 11:28:05 +02:00
Christoffer Lerno
e2a39aa12e Updated mangling code. 2024-06-28 00:57:14 +02:00
Christoffer Lerno
043833be7b Fixes to casts. 2024-06-27 19:32:45 +02:00
Christoffer Lerno
ad394c19d5 Remove asserts from header gen. 2024-06-27 17:21:08 +02:00
Christoffer Lerno
05592183b1 Fixed distinct comparison behaviour. 2024-06-27 15:06:23 +02:00
Christoffer Lerno
079cbb8f68 Updated module mangling, restrict module names. 2024-06-27 13:37:37 +02:00
Christoffer Lerno
3bddde20ab Fixes to distinct inline conversions. 2024-06-26 21:48:10 +02:00
Christoffer Lerno
0a8a63bc15 Fix to headergen. Updated module name store. 2024-06-26 11:43:14 +02:00
Christoffer Lerno
fd2491446a Update mangling. 2024-06-24 21:55:49 +02:00
Christoffer Lerno
26f3fe37f4 Fix of built in aliases for headers. 2024-06-24 17:23:59 +02:00
Christoffer Lerno
4cff80ecea Header exports implicit. 2024-06-24 15:04:44 +02:00
Christian Buttner
83fe94d497 Fix posix NativeConditionVariable.wait_timeout. (#1211)
Fix posix NativeConditionVariable.wait_timeout. TimeSpec::ns may not exceed one second.
2024-06-24 11:52:21 +02:00
Christoffer Lerno
616bde2c4d Further header updates. 2024-06-24 11:34:23 +02:00
Christian Buttner
0b971c2bd0 Fix off-by-one errors for stdlib unicode conversions. 2024-06-23 23:46:19 +02:00
Christoffer Lerno
201b1b7fbc - Bitstructs, unions and flexible arrays now correctly emitted in headers.
- Require `@export` functions to have `@export` types.
2024-06-23 23:39:58 +02:00
Christoffer Lerno
b0b976ee52 Fix JSON and compile issue. 2024-06-23 17:40:56 +02:00
Christoffer Lerno
7020569f45 Cleanup. 2024-06-23 16:36:04 +02:00
Christoffer Lerno
e153c76719 Bit negate now properly does type promotion. 2024-06-23 16:13:37 +02:00
Christoffer Lerno
e7f9c11a14 "panic-msg" setting to suppress panic message output. 2024-06-23 10:42:03 +02:00
Christoffer Lerno
f2e5c5e9b9 - Fix bug with @jump miscompile
- Remove "panic" text from unreachable() when safe mode is turned off.
2024-06-22 23:20:23 +02:00
Christoffer Lerno
e02f73417c Trailing body arguments may now be &ref, #hash, $const and $Type arguments. 2024-06-22 22:04:20 +02:00
Christian Buttner
41db9c43e5 Allow omitting = true for designated initializers of bitstruct bools. 2024-06-22 15:57:41 +02:00
Christoffer Lerno
0dc2f0e923 Make function pointers comparable with null again. 2024-06-22 15:38:19 +02:00
Christoffer Lerno
5940d5ddad Removal of unused code. 2024-06-21 23:24:05 +02:00
Christoffer Lerno
684850dda1 Fixing flexible array resolution. 2024-06-21 18:36:39 +02:00
Christoffer Lerno
e8e615f4db Remove superfluous code and flags for type resolution. 2024-06-21 17:45:33 +02:00
Christoffer Lerno
559b060b6b Fix bug in header gen. 2024-06-21 12:31:50 +02:00
Christoffer Lerno
581262d736 Try LLVM 19 support. 2024-06-21 11:44:27 +02:00
Christoffer Lerno
8878a49a1d Introduction of TYPE_FUNC_PTR / TYPE_FUNC_RAW. Fixed rules for function pointers. 2024-06-21 10:46:28 +02:00
Christoffer Lerno
3a7bc4d253 Return the typekind "FUNC" for a function pointer. 2024-06-20 20:47:24 +02:00
Christoffer Lerno
316982fb8f Added test and removed todo. 2024-06-19 01:17:43 +02:00
Christoffer Lerno
cfaea34053 Some additional cleanup. 2024-06-19 00:57:38 +02:00
Christoffer Lerno
8fd1d895d6 Cleanup ct_call parsing. 2024-06-19 00:28:24 +02:00
Christoffer Lerno
b592ecf6f5 Fixed crash on certain recursive function definitions #1209. 2024-06-18 22:33:10 +02:00
Christoffer Lerno
65a8826158 Fix of missing copy of parameterized custom attributes. 2024-06-17 22:05:32 +02:00
Christoffer Lerno
c9fab898cc Improved error notes when call expressions have errors. 2024-06-16 23:33:37 +02:00
Christoffer Lerno
819049d596 @str_hash, @str_upper, @str_lower, @str_find compile time macros. 2024-06-16 21:16:03 +02:00
Christoffer Lerno
147dee6ec7 Addition of $append and $concat functions. Added $$str_hash builtin. Fix to the macho runtime. 2024-06-16 01:57:05 +02:00
Christoffer Lerno
b0b885d506 Prevent Mach-O from removing @init and @dynamic in a more reliable way #1200. 2024-06-15 15:58:12 +02:00
Christoffer Lerno
c94610f8a9 Error with unsigned compare in @ensure when early returning 0 #1207. Added remove_first_item remove_last_item and remove_item as aliases for the match functions. 2024-06-14 17:29:46 +02:00
Christoffer Lerno
21fa006850 Merge 0.5.6 changes into 0.6.0 2024-06-12 11:39:52 +02:00
Christoffer Lerno
e293c435af 0.6.0: init_new/init_temp removed. LinkedList API rewritten. List "pop" and "remove" function now return Optionals. RingBuffer API rewritten. Allocator interface changed. Deprecated Allocator, DString and mem functions removed. "identity" functions are now constants for Matrix and Complex numbers. @default implementations for interfaces removed. any* => any, same for interfaces. Emit local/private globals as "private" in LLVM, following C "static". Updated enum syntax. Add support [rgba] properties in vectors. Improved checks of aliased "void". Subarray -> slice. Fix of llvm codegen enum check. Improved alignment handling. Add --output-dir #1155. Removed List/Object append. GenericList renamed AnyList. Remove unused "unwrap". Fixes to cond. Optimize output in dead branches. Better checking of operator methods. Disallow any from implementing dynamic methods. Check for operator mismatch. Remove unnecessary bitfield. Remove numbering in --list* commands Old style enum declaration for params/type, but now the type is optional. Add note on #1086. Allow making distinct types out of "void", "typeid", "anyfault" and faults. Remove system linker build options. "Try" expressions must be simple expressions. Add optimized build to Mac tests. Register int. assert(false) only allowed in unused branches or in tests. Compile time failed asserts is a compile time error. Remove current_block_is_target. Bug when assigning an optional from an optional. Remove unused emit_zstring. Simplify phi code. Remove unnecessary unreachable blocks and remove unnecessary current_block NULL assignments. Proper handling of '.' and Win32 '//server' paths. Add "no discard" to expression blocks with a return value. Detect "unsigned >= 0" as errors. Fix issue with distinct void as a member #1147. Improve callstack debug information #1184. Fix issue with absolute output-dir paths. Lambdas were not type checked thoroughly #1185. Fix compilation warning #1187. Request jump table using @jump for switches. Path normalization - fix possible null terminator out of bounds. Improved error messages on inlined macros.
Upgrade of mingw in CI. Fix problems using reflection on interface types #1203. Improved debug information on defer. $foreach doesn't create an implicit syntactic scope.
Error if `@if` depends on `@if`. Updated Linux stacktrace. Fix of default argument stacktrace. Allow linking libraries directly by file path. Improve inlining warning messages. Added `index_of_char_from`. Compiler crash using enum nameof from different module #1205. Removed unused fields in find_msvc. Use vswhere to find msvc. Update tests for LLVM 19
2024-06-12 10:14:26 +02:00
Christoffer Lerno
321c5ec756 Update mingw version and funding. 2024-05-27 12:03:45 +02:00
Christoffer Lerno
f04d93f9aa Fix flaw in bitstruct check. 2024-05-20 22:20:33 +02:00
Christoffer Lerno
9436efe554 Compiler crash on designated initializer for structs with bitstruct. 2024-05-20 14:42:09 +02:00
Christoffer Lerno
3acbf708d3 Fix location on foreach debug output. 2024-05-19 23:27:57 +02:00
Christoffer Lerno
92979984ea Fix mutex and wait signatures for Win32. 2024-05-18 22:24:45 +02:00
Christoffer Lerno
a16d41a1e1 Do not elide memory storage on variable for debug. 2024-05-17 19:51:35 +02:00
Christoffer Lerno
ff8b78fc99 Correct debug info on parameters without storage. 2024-05-17 16:26:12 +02:00
Christoffer Lerno
97c9bd7ce0 Assertion failed when casting argument to enum #1196 2024-05-16 16:07:55 +02:00
Christoffer Lerno
c40c93340d Compile time fmod evaluates to 0 #1195 2024-05-16 14:33:11 +02:00
Christoffer Lerno
094c105464 Union is not properly zero-initialized #1194 2024-05-16 11:28:04 +02:00
Christoffer Lerno
e36c696624 Update CI. 2024-05-15 21:49:18 +02:00
Christoffer Lerno
555a4ab4c5 Casting a slice address to its pointer type should not compile #1193. 2024-05-15 21:36:53 +02:00
Christoffer Lerno
7d8cc8776d Duplicate emit of expressions on negation would incorrectly compile negated macros. 2024-05-14 23:30:39 +02:00
Christoffer Lerno
960646ac8a Patch test. 2024-05-09 09:53:48 +02:00
Christoffer Lerno
ed9f15becf Foreach uses non-wrapping add/dec. 2024-05-08 23:05:12 +02:00
Christoffer Lerno
b09aa74f2f Generic modules parameterized with constants would sometimes get the wrong parameterized module name causing conversion errors #1192. 2024-05-04 23:34:37 +02:00
Christoffer Lerno
60805fd11d Bounds checking on length for foo[1:2] slicing #1191 2024-04-28 18:55:26 +02:00
Christoffer Lerno
89ecd4b33d Default to AVX on x64. 2024-04-26 19:29:32 +02:00
Christoffer Lerno
237f142a87 Default CPU actually defaults to a value instead of picking the native CPU. 2024-04-26 19:03:25 +02:00
Christoffer Lerno
a21647a1aa Do not default to native vector capability. 2024-04-26 18:45:14 +02:00
Christoffer Lerno
e9afe4ee25 Update CI script for mac. 2024-04-26 18:09:03 +02:00
Christoffer Lerno
acd067582a Update CI script. 2024-04-26 18:07:16 +02:00
Christoffer Lerno
82227e8901 Incorrect cast of bitstructs #1186 2024-04-26 17:39:30 +02:00
Christoffer Lerno
8b6735a6aa Allow recursive function definitions as long as they are pointers #1182. Add 'zstr' variants for string::new_format / string::tformat. 2024-04-16 19:42:32 +02:00
Christoffer Lerno
9ed8831500 Updated link 2024-04-14 23:07:48 +02:00
Christoffer Lerno
e7d726cc2c Fixup of scratch buffer code. 2024-04-09 14:27:52 +02:00
Christoffer Lerno
11a3dd26c8 Update mingw version. 2024-04-09 14:10:54 +02:00
Christoffer Lerno
04738586b9 Fix bug in scratch_buffer_printf. 2024-04-09 13:26:08 +02:00
cpiernikowski
18b4fce1ca Change return type of next_bool(random) from void to bool 2024-03-28 09:44:47 +01:00
Christoffer Lerno
3b9babe745 0.5.6 Add grammar for defer (catch err). 2024-03-26 09:36:45 +01:00
Brennan Cottrell
a4a85b7bbf Added print-input command line argument (#1175)
* added print-input command line argument
2024-03-26 09:33:47 +01:00
Christoffer Lerno
e8f0275d8e 0.5.6 Add defer (catch err) feature. 2024-03-25 11:35:16 +01:00
Christoffer Lerno
3251f58d46 Change version for MinGW 2024-03-25 09:40:52 +01:00
David
204fb211ac Fix x86_64 ABI small issue (#1174)
* Fix x86_64 ABI small issue Update tests for fix.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2024-03-25 09:39:26 +01:00
Christoffer Lerno
6cade814e1 Update includes for FreeBSD. 2024-03-22 09:11:18 +01:00
Christoffer Lerno
eb2fbabbb1 Update version to 0.5.6 2024-03-19 16:15:13 +01:00
Christoffer Lerno
ee9c5db719 0.5.5 release. 2024-03-18 22:05:16 +01:00
Christoffer Lerno
d8af01dc46 Update release notes and change how versions are reported. 2024-03-18 11:51:31 +01:00
David Gonzalez Martin
5bead069f2 Fix aarch64 return type ABI bug 2024-03-17 20:06:53 +01:00
shv187
63e1345780 Add getModuleHandleA + W to win32 2024-03-16 09:47:42 +01:00
Christoffer Lerno
3df988d0b8 Allow String constants -> ichar*, and allow integer pointers to explicitly convert between unsigned signed. 2024-03-15 23:08:52 +01:00
Christoffer Lerno
656202dc0d Convert paths to backslash before running on Windows. 2024-03-15 20:09:06 +01:00
Christoffer Lerno
eec3253669 Mingw -> 18.1.1-3 2024-03-15 13:23:41 +01:00
Christoffer Lerno
d9423201b8 Change mingw version. 2024-03-15 13:20:05 +01:00
Christoffer Lerno
c6087bc369 Fix underlying type of llvm.used and update section. 2024-03-15 13:02:39 +01:00
Christoffer Lerno
b7077c7967 Fix Win32 with compile-run. 2024-03-14 12:05:01 +01:00
Christoffer Lerno
4acb07f1cb compile-run and run now returns the proper return code. 2024-03-14 11:55:55 +01:00
Christoffer Lerno
5207022a4a For MacOS, running with higher optimization would crash as initializers were removed. 2024-03-14 09:33:24 +01:00
Christoffer Lerno
1a25746343 Regression: no stacktrace. 2024-03-12 17:31:06 +01:00
Christoffer Lerno
0d7ceb625b Fixed link on msvc. 2024-03-12 10:24:48 +01:00
Christoffer Lerno
95fb5f904f New linker build option. "system-linker" deprecated and removed from project settings. 2024-03-12 10:09:02 +01:00
Christoffer Lerno
a0309855d7 Added @link attribute. 2024-03-11 18:10:40 +01:00
Christoffer Lerno
546754e803 'output' directory for projects was incorrect in templates. 2024-03-08 15:34:04 +01:00
Christoffer Lerno
86461909d3 Remove initial './' in Win32 paths when running a binary. 2024-03-04 17:04:57 +01:00
Christoffer Lerno
feebd2a733 Bug in time.add_seconds #1162. 2024-03-01 12:09:47 +01:00
Christoffer Lerno
75e7176675 Bitstruct cast to other bitstruct by way of underlying type would fail #1159. 2024-02-26 23:51:14 +01:00
Christoffer Lerno
bae5d9c7f8 Improved checks of aliased "void". 2024-02-26 18:45:55 +01:00
Christoffer Lerno
4ba033fc84 Fix of int.min incorrect behaviour #1154. 2024-02-26 18:13:40 +01:00
Christoffer Lerno
f0dd0e8f92 Fix of CT named arguments #1156. 2024-02-26 17:47:50 +01:00
Christoffer Lerno
7ea3d230bb 0.5.5 features (#1151)
0.5.5 Disallow multiple `_` in a row in digits, e.g. `1__000`. #1138. Fixed toposort example. Struct/union members now correctly rejects members without storage size #1147. `math::pow` will now correctly promote integer arguments. `math::pow` will now correctly promote integer arguments. Added `new_aligned` and `alloc_aligned` functions to prevent accidental under-alignment when allocating simd. Pointer difference would fail where alignment != size (structs etc) #1150. Add test that overalignment actually works for lists. Fixed array calculation for npot2 vectors. Use native aligned alloc on Windows and POSIX. Deprecates "offset". Simplification of the Allocator interface.
2024-02-22 17:13:51 +01:00
Christoffer Lerno
b7f4fd9074 Create FUNDING.yml 2024-02-20 09:27:21 +01:00
Christoffer Lerno
9a114b38d3 Updated retry and test.c3 examples. 2024-02-17 15:19:27 +01:00
Christoffer Lerno
bec1116f86 Fixes to scoped mem report. 2024-02-17 11:52:18 +01:00
Christoffer Lerno
d7cc37b951 Important fixes to project settings. 2024-02-17 11:39:08 +01:00
Christoffer Lerno
4ce62cf221 Fix of allocator::new. 2024-02-16 21:55:30 +01:00
Christoffer Lerno
1f052da0b9 Clock.c3 fix. 2024-02-16 21:31:49 +01:00
Christoffer Lerno
812dc0c292 Update memory test code. 2024-02-16 14:19:42 +01:00
Christoffer Lerno
798fe0dce9 Updated lex file. 2024-02-16 12:01:13 +01:00
Christoffer Lerno
3f6fe55f9a Grammar fix. 2024-02-15 23:18:11 +01:00
Christoffer Lerno
aee4aecfe7 Remove install_win_reqs.bat from releases. 2024-02-15 22:41:44 +01:00
Christoffer Lerno
748c737e8f 0.5.4: Hash variables accept designated initializers. @safemacro overrides the need for @ in macro names. Fixes to macro context evaluation. Updated allocator api. Removed install_win_reqs.bat. Deterministic @init for MacOS. Fixed temp memory issue with formatter. Support LLVM 19. Add support to compare bitstructs using == and !=. Support Windows .def files. Removed invalid grammar from grammar.y. Support compile time folding of &|^~ for bitstructs. output project setting now respected. Fix issue where constants were not properly constant folded. Add temp_push/pop. Aliased declarations caused errors when used in initializers. Fix export output. Fix of const ternary #1118. Fix of $$MODULE in nested macros #1117. Fix debug info on globals. out now correctly detects subscript[] use #1116. Lateral implicit imports removed. Default to '.' if no libdir is specified. Improved error messages for --lib. Fix raylib snake example. Overzealous local escape check corrected #1127. Improved yacc grammar #1128. --linker argument #1067. Fixes to the matrix operations #1130. Added GenericList. 2024-02-15 21:39:33 +01:00
Christoffer Lerno
c673101bbb Fix incorrect code in sample. 2024-02-14 09:16:36 +01:00
Poly2it
d66674655c Update compilation instructions for Void Linux 2024-02-13 16:45:51 +01:00
Christian Clauss
da292e41bd msvc_build_libraries.py: Remove unused import an f-strings with no placeholder
% `ruff ` # https://docs.astral.sh/ruff
```
 Error: msvc_build_libraries.py:9:8: F401 `os` imported but unused
Error: msvc_build_libraries.py:10:8: F401 `sys` imported but unused
Error: msvc_build_libraries.py:19:8: F401 `re` imported but unused
Error: msvc_build_libraries.py:179:3: F541 f-string without any placeholders
Error: msvc_build_libraries.py:180:3: F541 f-string without any placeholders
Error: msvc_build_libraries.py:182:3: F541 f-string without any placeholders
Error: Process completed with exit code 1.
```
2024-01-18 11:24:03 +01:00
Christoffer Lerno
deb4cc7c4b 0.5.3: Single-module not respected. Fix issue with compiler defined types. Fix optimization levels for projects. Use GEP i8 on offsets. Optimize foreach on len 1 arrays. Move panic blocks last. Fix generic module wildcard imports. Deprecate init_temp / init_new. Fix issue with macro vaarg and untyped lists. Fix extern const globals. 2024-01-14 15:34:54 +01:00
Christoffer Lerno
e91f6e268e 0.5.2: Allow trailing comma in calls and parameter declarations #1092. Fixes issue where single character filenames like 'a.c3' would be rejected. Improve error messages for incorrect user defined foreach. Fix bug with generics in generics. Fix to error with modified vector parameters. Crash with lhs vector inference. Fixes to priority queue. 2023-12-23 23:15:51 +01:00
Poly2it
2595ed5cc9 Add compilation instructions for Void Linux 2023-12-09 15:15:17 +01:00
Christoffer Lerno
1d61ace302 Do not link with debug libc on win32 when using cross compile libs. Add delete methods to dstring. Fixes to macOS aarch64 codegen. Use glibc backtrace when available. Add load_* methods to file. The cast (int[8])int_slice[:8] now works. 2023-12-08 18:59:57 +01:00
Christoffer Lerno
a50c5f4f7c Fixes to the grammar. 2023-11-26 20:06:38 +01:00
Christoffer Lerno
a46bf4fbe0 Improve "const" error message #1079. 2023-11-22 19:12:04 +01:00
Christoffer Lerno
0d1eab5c15 Fix of incorrect error recovery leading to confusing errors #1080 2023-11-22 19:12:04 +01:00
1136 changed files with 86020 additions and 40290 deletions

25
.editorconfig Normal file
View File

@@ -0,0 +1,25 @@
# EditorConfig is awesome: https://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
[CMakeLists.txt]
indent_style = space
indent_size = 4
[*.{c,cc,h}]
indent_style = tab
[*.{c3}]
indent_style = tab
[*.{json,toml,yml,gyp}]
indent_style = space
indent_size = 2
[*.{py,pyi}]
indent_style = tab

2
.gitattributes vendored
View File

@@ -1,4 +1,2 @@
$ cat .gitattributes $ cat .gitattributes
* text=auto * text=auto
*.c3 linguist-language=C
*.c3t linguist-language=C

14
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
# These are supported funding model platforms
github: [c3lang]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: c3lang
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -2,13 +2,16 @@ name: CI
on: on:
push: push:
branches: [ master, dev, ci_testing ] branches: [ master, dev, ci_testing, experiments ]
pull_request: pull_request:
branches: [ master ] branches: [ master, dev ]
env: env:
LLVM_RELEASE_VERSION: 16 LLVM_RELEASE_VERSION_WINDOWS: 18
LLVM_RELEASE_VERSION_MAC: 17
LLVM_RELEASE_VERSION_LINUX: 17
LLVM_RELEASE_VERSION_UBUNTU20: 17
LLVM_DEV_VERSION: 21
jobs: jobs:
build-msvc: build-msvc:
@@ -33,49 +36,80 @@ jobs:
- name: Compile and run some examples - name: Compile and run some examples
run: | run: |
cd resources cd resources
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\hello_world_many.c3 ..\build\${{ matrix.build_type }}\c3c.exe compile-run -L C:\ --print-linking examples\hello_world_many.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\time.c3 ..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\time.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\fannkuch-redux.c3 ..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\fannkuch-redux.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\contextfree\boolerr.c3 ..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\contextfree\boolerr.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\ls.c3 ..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\ls.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\load_world.c3 ..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\load_world.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\process.c3 ..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\process.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3 ..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\args.c3 -- foo -bar "baz baz"
..\build\${{ matrix.build_type }}\c3c.exe compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run msvc_stack.c3 ..\build\${{ matrix.build_type }}\c3c.exe compile-run msvc_stack.c3
- name: Build testproject - name: Build testproject
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
cd resources/testproject cd resources/testproject
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log --emit-llvm run hello_world_win32 ..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32 --trust=full
dir build\llvm_ir dir build\llvm_ir
..\..\build\${{ matrix.build_type }}\c3c.exe clean ..\..\build\${{ matrix.build_type }}\c3c.exe clean
dir build\llvm_ir dir build\llvm_ir
- name: Build testproject lib - name: Build testproject lib
run: | run: |
cd resources/testproject cd resources/testproject
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log build hello_world_win32_lib 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 --trust=full
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
..\..\..\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 - name: Vendor-fetch
run: | run: |
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
- name: run compiler tests - name: Try raylib5
run: | run: |
cd test cd resources
python3.exe src/tester.py ..\build\${{ matrix.build_type }}\c3c.exe test_suite/ ..\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: Compile run unit tests - name: Compile run unit tests
run: | run: |
cd test cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1 ..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
- name: run compiler tests
run: |
cd test
python3.exe src/tester.py ..\build\${{ matrix.build_type }}\c3c.exe test_suite/
- name: Test python script
run: |
py msvc_build_libraries.py --accept-license
dir msvc_sdk
- name: upload artifacts - name: upload artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: c3-windows-${{ matrix.build_type }} name: c3-windows-${{ matrix.build_type }}
path: build\${{ matrix.build_type }}\c3c.exe path: |
build\${{ matrix.build_type }}\c3c.exe
build\${{ matrix.build_type }}\c3c_rt
build-msys2-mingw: build-msys2-mingw:
runs-on: windows-latest runs-on: windows-latest
@@ -99,8 +133,8 @@ 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 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} - shell: msys2 {0}
run: | run: |
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-17.0.4-1-any.pkg.tar.zst pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-19.1.7-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-17.0.4-1-any.pkg.tar.zst pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-19.1.7-1-any.pkg.tar.zst
- name: CMake - name: CMake
run: | 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 }}
@@ -109,26 +143,27 @@ jobs:
- name: Compile and run some examples - name: Compile and run some examples
run: | run: |
cd resources cd resources
../build/c3c compile-run examples/hello_world_many.c3 ../build/c3c compile-run --print-linking examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3 ../build/c3c compile-run --print-linking examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3 ../build/c3c compile-run --print-linking examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3 ../build/c3c compile-run --print-linking examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3 ../build/c3c compile-run --print-linking examples/load_world.c3
../build/c3c compile --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3 ../build/c3c compile-run --print-linking examples/args.c3 -- foo -bar "baz baz"
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject - name: Build testproject
run: | run: |
cd resources/testproject cd resources/testproject
../../build/c3c run --debug-log ../../build/c3c run -vvv --trust=full
- name: Vendor-fetch - name: Vendor-fetch
run: | run: |
./build/c3c vendor-fetch raylib ./build/c3c vendor-fetch raylib5
- name: Build testproject lib - name: Build testproject lib
run: | run: |
cd resources/testproject cd resources/testproject
../../build/c3c build hello_world_lib --debug-log ../../build/c3c build hello_world_lib --cc cc -vvv --trust=full
- name: run compiler tests - name: run compiler tests
run: | run: |
@@ -170,16 +205,17 @@ jobs:
../build/c3c compile-run examples/fannkuch-redux.c3 ../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3 ../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3 ../build/c3c compile-run examples/load_world.c3
../build/c3c compile --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3 ../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject - name: Build testproject
run: | run: |
cd resources/testproject cd resources/testproject
../../build/c3c run --debug-log ../../build/c3c run -vvv --trust=full
- name: Build testproject lib - name: Build testproject lib
run: | run: |
cd resources/testproject cd resources/testproject
../../build/c3c build hello_world_lib --debug-log ../../build/c3c build hello_world_lib -vvv --trust=full
- name: run compiler tests - name: run compiler tests
run: | run: |
@@ -187,13 +223,13 @@ jobs:
python3 src/tester.py ../build/c3c.exe test_suite/ python3 src/tester.py ../build/c3c.exe test_suite/
build-linux: build-linux:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
strategy: strategy:
# Don't abort runners if a single one fails # Don't abort runners if a single one fails
fail-fast: false fail-fast: false
matrix: matrix:
build_type: [Release, Debug] build_type: [Release, Debug]
llvm_version: [15, 16, 17, 18] llvm_version: [17, 18, 19, 20, 21]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -204,24 +240,29 @@ jobs:
- name: Install Clang ${{matrix.llvm_version}} - name: Install Clang ${{matrix.llvm_version}}
run: | run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
if [[ "${{matrix.llvm_version}}" < 16 ]]; then
sudo apt remove libllvm15
fi
if [[ "${{matrix.llvm_version}}" < 18 ]]; then if [[ "${{matrix.llvm_version}}" < 18 ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main" sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
sudo apt-get update sudo apt-get update
sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \ sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \ clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \ lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
else else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main" if [[ "${{matrix.llvm_version}}" < "${{env.LLVM_DEV_VERSION}}" ]]; then
sudo apt-get install -y -t llvm-toolchain-focal libpolly-${{matrix.llvm_version}}-dev \ sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \ sudo apt-get update
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \ sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
sudo apt-get install -y -t llvm-toolchain-focal libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
fi
fi fi
- name: CMake - name: CMake
if: matrix.llvm_version < 18 || matrix.llvm_version == env.LLVM_DEV_VERSION
run: | run: |
cmake -B build \ cmake -B build \
-G Ninja \ -G Ninja \
@@ -234,6 +275,20 @@ jobs:
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \ -DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}} -DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build cmake --build build
- name: CMake18
if: matrix.llvm_version >= 18 && matrix.llvm_version != env.LLVM_DEV_VERSION
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
- name: Compile and run some examples - name: Compile and run some examples
run: | run: |
@@ -245,9 +300,9 @@ jobs:
../build/c3c compile examples/fasta.c3 ../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3 ../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3 ../build/c3c compile examples/hash.c3
../build/c3c compile examples/levenshtein.c3 ../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile examples/load_world.c3 ../build/c3c compile examples/load_world.c3
../build/c3c compile examples/map.c3 ../build/c3c compile-only examples/map.c3
../build/c3c compile examples/mandelbrot.c3 ../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3 ../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3 ../build/c3c compile examples/nbodies.c3
@@ -256,8 +311,8 @@ jobs:
../build/c3c compile examples/contextfree/boolerr.c3 ../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3 ../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3 ../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3 ../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3 ../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3 ../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3 ../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3 ../build/c3c compile-run examples/fannkuch-redux.c3
@@ -265,8 +320,27 @@ jobs:
../build/c3c compile-run examples/load_world.c3 ../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/process.c3 ../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/ls.c3 ../build/c3c compile-run examples/ls.c3
../build/c3c compile-run --system-linker=no linux_stack.c3 ../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3 ../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
../../../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 - name: Compile run unit tests
run: | run: |
@@ -276,12 +350,33 @@ jobs:
- name: Build testproject - name: Build testproject
run: | run: |
cd resources/testproject cd resources/testproject
../../build/c3c run --debug-log ../../build/c3c run -vvv --trust=full
- name: Test WASM
run: |
cd resources/testfragments
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
- name: Install QEMU and Risc-V toolchain
run: |
sudo apt-get install opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf
- name: Compile and run Baremetal Risc-V Example
run: |
cd resources/examples/embedded/riscv-qemu
make C3C_PATH=../../../../build/ run
- name: Build testproject direct linker - name: Build testproject direct linker
run: | run: |
cd resources/testproject cd resources/testproject
../../build/c3c run --debug-log --system-linker=no ../../build/c3c run -vvv --linker=builtin --trust=full
- name: Init a library & a project
run: |
./build/c3c init-lib mylib
ls mylib.c3l
./build/c3c init myproject
ls myproject
- name: run compiler tests - name: run compiler tests
run: | run: |
@@ -289,17 +384,17 @@ jobs:
python3 src/tester.py ../build/c3c test_suite/ python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output - name: bundle_output
if: matrix.llvm_version == 16 if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
run: | run: |
mkdir linux mkdir c3
cp -r lib linux cp -r lib c3
cp msvc_build_libraries.py linux cp msvc_build_libraries.py c3
cp build/c3c linux cp build/c3c c3
tar czf c3-linux-${{matrix.build_type}}.tar.gz linux tar czf c3-linux-${{matrix.build_type}}.tar.gz c3
- name: upload artifacts - name: upload artifacts
if: matrix.llvm_version == 16 if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: c3-linux-${{matrix.build_type}} name: c3-linux-${{matrix.build_type}}
path: c3-linux-${{matrix.build_type}}.tar.gz path: c3-linux-${{matrix.build_type}}.tar.gz
@@ -311,8 +406,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
build_type: [Release, Debug] build_type: [Release, Debug]
llvm_version: [16] llvm_version: [17, 18, 19, 20, 21]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install common deps - name: Install common deps
@@ -322,16 +416,17 @@ jobs:
- name: Install Clang ${{matrix.llvm_version}} - name: Install Clang ${{matrix.llvm_version}}
run: | run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
if [[ "${{matrix.llvm_version}}" < 17 ]]; then if [[ "${{matrix.llvm_version}}" < "${{env.LLVM_DEV_VERSION}}" ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main" sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
else else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main" sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
fi fi
sudo apt-get update sudo apt-get update
sudo apt-get install -y clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev sudo apt-get install -y clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
sudo apt-get install -y libpolly-${{matrix.llvm_version}}-dev sudo apt-get install -y libpolly-${{matrix.llvm_version}}-dev
- name: CMake - name: CMake Old
if: matrix.llvm_version < 18 || matrix.llvm_version == env.LLVM_DEV_VERSION
run: | run: |
cmake -B build \ cmake -B build \
-G Ninja \ -G Ninja \
@@ -344,13 +439,26 @@ jobs:
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \ -DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}} -DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build cmake --build build
- name: CMake
if: matrix.llvm_version >= 18 && matrix.llvm_version != env.LLVM_DEV_VERSION
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
- name: Compile and run some examples - name: Compile and run some examples
run: | run: |
cd resources cd resources
../build/c3c compile examples/gameoflife.c3 ../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/levenshtein.c3 ../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile examples/map.c3 ../build/c3c compile-only examples/map.c3
../build/c3c compile examples/mandelbrot.c3 ../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3 ../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/spectralnorm.c3 ../build/c3c compile examples/spectralnorm.c3
@@ -360,8 +468,8 @@ jobs:
../build/c3c compile-run examples/nbodies.c3 ../build/c3c compile-run examples/nbodies.c3
../build/c3c compile-run examples/contextfree/boolerr.c3 ../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/contextfree/dynscope.c3 ../build/c3c compile-run examples/contextfree/dynscope.c3
../build/c3c compile-run examples/contextfree/multi.c3 ../build/c3c compile-run examples/contextfree/multi.c3
../build/c3c compile-run examples/contextfree/cleanup.c3 ../build/c3c compile-run examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3 ../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3 ../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3 ../build/c3c compile-run examples/fannkuch-redux.c3
@@ -372,8 +480,101 @@ jobs:
../build/c3c compile-run examples/factorial_macro.c3 ../build/c3c compile-run examples/factorial_macro.c3
../build/c3c compile-run examples/fasta.c3 ../build/c3c compile-run examples/fasta.c3
../build/c3c compile-run examples/process.c3 ../build/c3c compile-run examples/process.c3
../build/c3c compile-run --system-linker=no linux_stack.c3 ../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3 ../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit --sanitize=address
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
run: |
mkdir c3
cp -r lib c3
cp msvc_build_libraries.py c3
cp build/c3c c3
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz c3
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
uses: actions/upload-artifact@v4
with:
name: c3-ubuntu-20-${{matrix.build_type}}
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
build-with-docker:
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]
steps:
- uses: actions/checkout@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Make script executable
run: chmod +x ./build-with-docker.sh
- name: Run build
run: |
LLVM_VERSION=${{ matrix.llvm_version }} UBUNTU_VERSION=${{ matrix.ubuntu_version }} CMAKE_BUILD_TYPE=${{ matrix.build_type }} ./build-with-docker.sh
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile examples/base64.c3
../build/c3c compile examples/binarydigits.c3
../build/c3c compile examples/brainfk.c3
../build/c3c compile examples/factorial_macro.c3
../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile examples/load_world.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile run unit tests - name: Compile run unit tests
run: | run: |
@@ -383,34 +584,30 @@ jobs:
- name: Build testproject - name: Build testproject
run: | run: |
cd resources/testproject cd resources/testproject
../../build/c3c run --debug-log ../../build/c3c run -vvv --trust=full
- name: Test WASM
run: |
cd resources/testfragments
../../build/c3c compile --reloc=none --target wasm32 -g0 --no-entry -Os wasm4.c3
- name: Build testproject direct linker - name: Build testproject direct linker
run: | run: |
cd resources/testproject cd resources/testproject
../../build/c3c run --debug-log --system-linker=no ../../build/c3c run -vvv --linker=builtin --trust=full
- name: Init a library & a project
run: |
./build/c3c init-lib mylib
ls mylib.c3l
./build/c3c init myproject
ls myproject
- name: run compiler tests - name: run compiler tests
run: | run: |
cd test cd test
python3 src/tester.py ../build/c3c test_suite/ python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == 16
run: |
mkdir linux
cp -r lib linux
cp msvc_build_libraries.py linux
cp build/c3c linux
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz linux
- name: upload artifacts
if: matrix.llvm_version == 16
uses: actions/upload-artifact@v3
with:
name: c3-ubuntu-20-${{matrix.build_type}}
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
build-mac: build-mac:
runs-on: macos-latest runs-on: macos-latest
strategy: strategy:
@@ -418,24 +615,30 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
build_type: [Release, Debug] build_type: [Release, Debug]
llvm_version: [15, 16] llvm_version: [17, 18]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Download LLVM - name: Download LLVM
run: | run: |
brew install llvm@${{ matrix.llvm_version }} ninja curl brew install llvm@${{ matrix.llvm_version }} ninja curl
echo "/usr/local/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH echo "/opt/homebrew/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
TMP_PATH=$(xcrun --show-sdk-path)/user/include TMP_PATH=$(xcrun --show-sdk-path)/user/include
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
- name: CMake - name: CMake
if: matrix.llvm_version < 18
run: | run: |
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build cmake --build build
- name: CMake18
if: matrix.llvm_version >= 18
run: |
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}}.1 -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
- name: Vendor-fetch - name: Vendor-fetch
run: | run: |
./build/c3c vendor-fetch raylib ./build/c3c vendor-fetch raylib5
- name: Compile and run some examples - name: Compile and run some examples
run: | run: |
@@ -446,26 +649,39 @@ jobs:
../build/c3c compile-run examples/contextfree/boolerr.c3 ../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/process.c3 ../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/load_world.c3 ../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run -O5 examples/load_world.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
../../../build/c3c -vv dynamic-lib add.c3
../../../build/c3c compile-run test.c3 -l ./add.dylib
- name: Compile run unit tests - name: Compile run unit tests
run: | run: |
cd test 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 - name: Build testproject
run: | run: |
cd resources/testproject cd resources/testproject
../../build/c3c run --debug-log ../../build/c3c run -vvv --trust=full
- name: Build testproject direct linker - name: Build testproject direct linker
run: | run: |
cd resources/testproject cd resources/testproject
../../build/c3c run --debug-log --system-linker=no ../../build/c3c run -vvv --linker=builtin --trust=full
- name: Build testproject lib - name: Build testproject lib
run: | run: |
cd resources/testproject cd resources/testproject
../../build/c3c build hello_world_lib --debug-log ../../build/c3c build hello_world_lib -vvv --trust=full
- name: run compiler tests - name: run compiler tests
run: | run: |
@@ -473,7 +689,7 @@ jobs:
python3 src/tester.py ../build/c3c test_suite/ python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output - name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
run: | run: |
mkdir macos mkdir macos
cp -r lib macos cp -r lib macos
@@ -482,16 +698,46 @@ jobs:
zip -r c3-macos-${{matrix.build_type}}.zip macos zip -r c3-macos-${{matrix.build_type}}.zip macos
- name: upload artifacts - name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: c3-macos-${{matrix.build_type}} name: c3-macos-${{matrix.build_type}}
path: c3-macos-${{matrix.build_type}}.zip 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: release:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
needs: [build-msvc, build-linux, build-mac] needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu20]
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
steps: steps:
@@ -518,18 +764,22 @@ jobs:
sha: context.sha sha: context.sha
}) })
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v4
- run: cp -r lib c3-windows-Release - run: cp -r lib c3-windows-Release
- run: cp -r lib c3-windows-Debug - run: cp -r lib c3-windows-Debug
- run: cp msvc_build_libraries.py c3-windows-Release - run: cp msvc_build_libraries.py c3-windows-Release
- run: cp msvc_build_libraries.py c3-windows-Debug - run: cp msvc_build_libraries.py c3-windows-Debug
- run: cp install_win_reqs.bat c3-windows-Release - run: zip -r c3-windows.zip c3-windows-Release
- run: cp install_win_reqs.bat c3-windows-Debug - run: zip -r c3-windows-debug.zip c3-windows-Debug
- run: zip -r c3-windows-Release.zip c3-windows-Release - run: mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux-Release/c3-linux.tar.gz
- run: zip -r c3-windows-Debug.zip c3-windows-Debug - run: mv c3-linux-Debug/c3-linux-Debug.tar.gz c3-linux-Debug/c3-linux-debug.tar.gz
- run: mv c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
- 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
- id: create_release - id: create_release
uses: actions/create-release@v1 uses: softprops/action-gh-release@v2
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
@@ -537,84 +787,12 @@ jobs:
release_name: latest release_name: latest
draft: false draft: false
prerelease: true prerelease: true
files: |
- name: upload windows c3-windows.zip
uses: actions/upload-release-asset@v1 c3-windows-debug.zip
env: c3-linux-Release/c3-linux.tar.gz
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} c3-linux-Debug/c3-linux-debug.tar.gz
with: c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
upload_url: ${{ steps.create_release.outputs.upload_url }} c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
asset_path: c3-windows-Release.zip c3-macos-Release/c3-macos.zip
asset_name: c3-windows.zip c3-macos-Debug/c3-macos-debug.zip
asset_content_type: application/zip
- name: upload windows debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-windows-Debug.zip
asset_name: c3-windows-debug.zip
asset_content_type: application/zip
- name: upload linux
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-linux-Release/c3-linux-Release.tar.gz
asset_name: c3-linux.tar.gz
asset_content_type: application/gzip
- name: upload linux debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-linux-Debug/c3-linux-Debug.tar.gz
asset_name: c3-linux-debug.tar.gz
asset_content_type: application/gzip
- name: upload ubuntu 20
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz
asset_name: c3-ubuntu-20.tar.gz
asset_content_type: application/gzip
- name: upload ubuntu 20 debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz
asset_name: c3-ubuntu-20-debug.tar.gz
asset_content_type: application/gzip
- name: upload macos
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-macos-Release/c3-macos-Release.zip
asset_name: c3-macos.zip
asset_content_type: application/zip
- name: upload macos debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-macos-Debug/c3-macos-Debug.zip
asset_name: c3-macos-debug.zip
asset_content_type: application/zip

18
.gitignore vendored
View File

@@ -19,6 +19,7 @@
# Libraries # Libraries
*.lib *.lib
*.tlb
*.a *.a
*.la *.la
*.lo *.lo
@@ -67,3 +68,20 @@ out/
/cmake-build-debug/ /cmake-build-debug/
/cmake-build-release/ /cmake-build-release/
# Emacs files
TAGS
# Clangd LSP files
/.cache/
/compile_commands.json
# 'nix build' resulting symlink
result
# macOS
.DS_Store
# tests
/test/tmp/*
/test/testrun

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.15) cmake_minimum_required(VERSION 3.20)
# Grab the version # Grab the version
file(READ "src/version.h" ver) file(READ "src/version.h" ver)
@@ -10,6 +10,11 @@ endif()
project(c3c VERSION ${CMAKE_MATCH_1}) project(c3c VERSION ${CMAKE_MATCH_1})
message("C3C version: ${CMAKE_PROJECT_VERSION}") message("C3C version: ${CMAKE_PROJECT_VERSION}")
# Avoid warning for FetchContent
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
cmake_policy(SET CMP0135 NEW)
endif()
if (NOT DEFINED CMAKE_INSTALL_LIBDIR) if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
if (MSVC) if (MSVC)
set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib") set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib")
@@ -32,10 +37,11 @@ set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
if(MSVC) if(MSVC)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc") message(STATUS "MSVC version ${MSVC_VERSION}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa") 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() else()
if (true) if (true)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions")
@@ -57,6 +63,9 @@ set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF) option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF)
option(C3_USE_TB "Use TB" OFF) option(C3_USE_TB "Use TB" OFF)
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc") 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) set(C3_USE_MIMALLOC OFF)
if(C3_USE_MIMALLOC) if(C3_USE_MIMALLOC)
@@ -65,18 +74,20 @@ if(C3_USE_MIMALLOC)
option(MI_PADDING OFF) option(MI_PADDING OFF)
option(MI_DEBUG_FULL OFF) option(MI_DEBUG_FULL OFF)
FetchContent_Declare( FetchContent_Declare(
mimalloc mimalloc
GIT_REPOSITORY https://github.com/microsoft/mimalloc.git GIT_REPOSITORY https://github.com/microsoft/mimalloc.git
GIT_TAG ${C3_MIMALLOC_TAG} GIT_TAG ${C3_MIMALLOC_TAG}
) )
FetchContent_MakeAvailable(mimalloc) FetchContent_MakeAvailable(mimalloc)
endif() endif()
if (NOT WIN32) if (NOT WIN32)
find_package(CURL) find_package(CURL)
endif() endif()
if (NOT C3_LLVM_VERSION STREQUAL "auto") if(C3_WITH_LLVM)
if (${C3_LLVM_VERSION} VERSION_LESS 15 OR ${C3_LLVM_VERSION} VERSION_GREATER 18) if (NOT C3_LLVM_VERSION STREQUAL "auto")
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!") 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() endif()
endif() endif()
@@ -95,150 +106,194 @@ if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
endif() endif()
endif() endif()
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") # Clangd LSP support
if (C3_LLVM_VERSION STREQUAL "auto") option(C3_ENABLE_CLANGD_LSP "Enable/Disable output of compile commands during generation." OFF)
set(C3_LLVM_VERSION "16") if(C3_ENABLE_CLANGD_LSP)
endif() set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
FetchContent_Declare( execute_process(
LLVM_Windows COMMAND ${CMAKE_COMMAND} -E create_symlink
URL https://github.com/c3lang/win-llvm/releases/download/llvm_16_0_2/llvm-16.0.2-windows-amd64-msvc17-libcmt.7z ${CMAKE_BINARY_DIR}/compile_commands.json
${CMAKE_SOURCE_DIR}/compile_commands.json
) )
FetchContent_Declare( endif(C3_ENABLE_CLANGD_LSP)
LLVM_Windows_debug
URL https://github.com/c3lang/win-llvm/releases/download/llvm_16_0_2/llvm-16.0.2-windows-amd64-msvc17-libcmt-dbg.7z if(C3_WITH_LLVM)
) if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if (C3_LLVM_VERSION STREQUAL "auto")
message("Loading Windows LLVM debug libraries, this may take a while...") set(C3_LLVM_VERSION "19")
FetchContent_MakeAvailable(LLVM_Windows_debug) endif()
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_windows_debug_SOURCE_DIR} ${CMAKE_SYSTEM_PREFIX_PATH}) FetchContent_Declare(
else() LLVM_Windows
message("Loading Windows LLVM libraries, this may take a while...") URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt.7z
FetchContent_MakeAvailable(LLVM_Windows) )
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_windows_SOURCE_DIR} ${CMAKE_SYSTEM_PREFIX_PATH}) FetchContent_Declare(
endif() LLVM_Windows_debug
find_package(LLVM REQUIRED CONFIG) URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt-dbg.7z
find_package(LLD REQUIRED CONFIG) )
else() if(CMAKE_BUILD_TYPE STREQUAL "Debug")
if (NOT C3_LLVM_VERSION STREQUAL "auto") message("Loading Windows LLVM debug libraries, this may take a while...")
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG) FetchContent_MakeAvailable(LLVM_Windows_debug)
else() set(llvm_dir ${llvm_windows_debug_SOURCE_DIR})
else()
message("Loading Windows LLVM libraries, this may take a while...")
FetchContent_MakeAvailable(LLVM_Windows)
set(llvm_dir ${llvm_windows_SOURCE_DIR})
endif()
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH})
find_package(LLVM REQUIRED CONFIG) find_package(LLVM REQUIRED CONFIG)
find_package(LLD REQUIRED CONFIG)
else()
if (NOT C3_LLVM_VERSION STREQUAL "auto")
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
else()
find_package(LLVM REQUIRED CONFIG)
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}")
if (NOT LLVM_PACKAGE_VERSION VERSION_GREATER_EQUAL 15.0)
message(FATAL_ERROR "LLVM version 15.0 or later is required.")
endif()
if(LLVM_ENABLE_RTTI)
message(STATUS "LLVM was built with RTTI")
else()
message(STATUS "LLVM was not built with RTTI")
endif()
string(REPLACE "." ";" VERSION_LIST ${LLVM_PACKAGE_VERSION})
list(GET VERSION_LIST 0 LLVM_MAJOR_VERSION)
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})
add_definitions(${LLVM_DEFINITIONS})
if(NOT C3_LINK_DYNAMIC)
set(LLVM_LINK_COMPONENTS
AllTargetsAsmParsers
AllTargetsCodeGens
AllTargetsDescs
AllTargetsDisassemblers
AllTargetsInfos
Analysis
AsmPrinter
BitReader
Core
DebugInfoPDB
InstCombine
IrReader
LibDriver
Linker
LTO
MC
MCDisassembler
native
nativecodegen
Object
Option
ScalarOpts
Support
Target
TransformUtils
WindowsManifest
WindowsDriver
)
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
if(NOT ${C3_LLD_DIR} EQUAL "" AND EXISTS ${C3_LLD_DIR})
message("C3_LLD_DIR: " ${C3_LLD_DIR})
set(LLVM_LIBRARY_DIRS
"${LLVM_LIBRARY_DIRS}"
"${C3_LLD_DIR}"
)
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
endif()
# 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.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})
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
endif() endif()
endif() endif()
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") if (NOT(${CMAKE_BINARY_DIR} EQUAL ${CMAKE_SOURCE_DIR}))
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}") file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
if(LLVM_ENABLE_RTTI)
message(STATUS "LLVM was built with RTTI")
else()
message(STATUS "LLVM was not built with RTTI")
endif() endif()
include_directories(${LLVM_INCLUDE_DIRS}) if(C3_WITH_LLVM)
link_directories(${LLVM_LIBRARY_DIRS})
add_definitions(${LLVM_DEFINITIONS})
if(NOT C3_LINK_DYNAMIC)
set(LLVM_LINK_COMPONENTS
AllTargetsAsmParsers
AllTargetsCodeGens
AllTargetsDescs
AllTargetsDisassemblers
AllTargetsInfos
Analysis
AsmPrinter
BitReader
Core
DebugInfoPDB
InstCombine
IrReader
LibDriver
Linker
LTO
MC
MCDisassembler
native
nativecodegen
Object
Option
ScalarOpts
Support
Target
TransformUtils
WindowsManifest
WindowsDriver
)
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES lldCOFF.lib lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES lldCommon.lib lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES lldELF.lib lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES lldMachO.lib lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES lldMinGW.lib lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES lldWasm.lib lldWasm.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})
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
endif()
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
if (${LLVM_PACKAGE_VERSION} VERSION_GREATER_EQUAL 16)
find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH) find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(lld_libs set(lld_libs
${LLD_COFF} ${LLD_COFF}
${LLD_COMMON} ${LLD_WASM}
${LLD_WASM} ${LLD_MINGW}
${LLD_MINGW} ${LLD_ELF}
${LLD_ELF} ${LLD_MACHO}
${LLD_MACHO} ${LLD_COMMON}
) )
else()
set(lld_libs if (APPLE)
${LLD_COFF} set(lld_libs ${lld_libs} xar)
${LLD_COMMON} 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})
${LLD_WASM} 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})
${LLD_MINGW} 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})
${LLD_ELF} 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})
${LLD_MACHO} set(sanitizer_runtime_libraries
${RT_ASAN_DYNAMIC}
${RT_TSAN_DYNAMIC}
# Unused
# ${RT_UBSAN_DYNAMIC}
# ${RT_LSAN_DYNAMIC}
) )
endif()
message(STATUS "linking to llvm libs ${lld_libs}")
message(STATUS "Found lld libs ${lld_libs}")
endif() endif()
if (APPLE)
set(lld_libs ${lld_libs} xar)
endif ()
message(STATUS "linking to llvm libs ${lld_libs}")
message(STATUS "Found lld libs ${lld_libs}")
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
add_library(miniz STATIC dependencies/miniz/miniz.c) add_library(miniz STATIC dependencies/miniz/miniz.c)
add_executable(c3c add_executable(c3c
src/build/builder.c src/build/builder.c
src/build/build_options.c src/build/build_options.c
src/build/project_creation.c src/build/project_creation.c
src/build/project_manipulation.c
src/build/libraries.c
src/compiler/ast.c src/compiler/ast.c
src/compiler/bigint.c src/compiler/bigint.c
src/compiler/codegen_general.c src/compiler/codegen_general.c
src/compiler/compiler.c src/compiler/compiler.c
src/compiler/compiler.h src/compiler/compiler.h
src/compiler/subprocess.c
src/compiler/subprocess.h
src/compiler/context.c src/compiler/context.c
src/compiler/copying.c src/compiler/copying.c
src/compiler/diagnostics.c src/compiler/diagnostics.c
@@ -246,9 +301,7 @@ add_executable(c3c
src/compiler/headers.c src/compiler/headers.c
src/compiler/json_output.c src/compiler/json_output.c
src/compiler/lexer.c src/compiler/lexer.c
src/compiler/libraries.c
src/compiler/linker.c src/compiler/linker.c
src/compiler/llvm_codegen.c
src/compiler/abi/c_abi_aarch64.c src/compiler/abi/c_abi_aarch64.c
src/compiler/abi/c_abi.c src/compiler/abi/c_abi.c
src/compiler/abi/c_abi_riscv.c src/compiler/abi/c_abi_riscv.c
@@ -256,14 +309,6 @@ add_executable(c3c
src/compiler/abi/c_abi_win64.c src/compiler/abi/c_abi_win64.c
src/compiler/abi/c_abi_x64.c src/compiler/abi/c_abi_x64.c
src/compiler/abi/c_abi_x86.c src/compiler/abi/c_abi_x86.c
src/compiler/llvm_codegen_debug_info.c
src/compiler/llvm_codegen_expr.c
src/compiler/llvm_codegen_function.c
src/compiler/llvm_codegen_instr.c
src/compiler/llvm_codegen_module.c
src/compiler/llvm_codegen_stmt.c
src/compiler/llvm_codegen_type.c
src/compiler/llvm_codegen_value.c
src/compiler/module.c src/compiler/module.c
src/compiler/number.c src/compiler/number.c
src/compiler/parse_expr.c src/compiler/parse_expr.c
@@ -305,18 +350,61 @@ add_executable(c3c
src/utils/whereami.c src/utils/whereami.c
src/utils/cpus.c src/utils/cpus.c
src/utils/unzipper.c src/utils/unzipper.c
src/compiler/c_codegen.c
src/compiler/decltable.c src/compiler/decltable.c
src/compiler/mac_support.c src/compiler/mac_support.c
src/compiler/llvm_codegen_storeload.c
src/compiler/windows_support.c src/compiler/windows_support.c
src/compiler/codegen_asm.c src/compiler/codegen_asm.c
src/compiler/asm_target.c src/compiler/asm_target.c
src/compiler/llvm_codegen_builtins.c
src/compiler/expr.c src/compiler/expr.c
src/utils/time.c src/utils/time.c
src/utils/http.c src/utils/http.c
src/compiler/sema_liveness.c) src/compiler/sema_liveness.c
src/build/common_build.c
src/compiler/sema_const.c
${CMAKE_BINARY_DIR}/git_hash.h
)
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
# We are inside of a git repository so rebuilding the hash every time something changes.
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/git_hash.h
COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_LIST_DIR}/git_hash.cmake"
DEPENDS "${CMAKE_CURRENT_LIST_DIR}/.git")
else()
# We are NOT inside of a git repository. Building the has only once.
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/git_hash.h
COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_LIST_DIR}/git_hash.cmake")
endif()
if(C3_WITH_LLVM)
target_sources(c3c PRIVATE
src/compiler/llvm_codegen.c
src/compiler/llvm_codegen_debug_info.c
src/compiler/llvm_codegen_expr.c
src/compiler/llvm_codegen_function.c
src/compiler/llvm_codegen_instr.c
src/compiler/llvm_codegen_module.c
src/compiler/llvm_codegen_stmt.c
src/compiler/llvm_codegen_type.c
src/compiler/llvm_codegen_value.c
src/compiler/llvm_codegen_storeload.c
src/compiler/llvm_codegen_builtins.c)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=1)
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
else()
target_sources(c3c PRIVATE src/utils/hostinfo.c)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0)
endif()
target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/src/"
"${CMAKE_BINARY_DIR}")
target_include_directories(miniz PUBLIC
"${CMAKE_SOURCE_DIR}/dependencies/miniz/")
if (C3_USE_TB) if (C3_USE_TB)
file(GLOB tilde-sources file(GLOB tilde-sources
@@ -330,7 +418,7 @@ if (C3_USE_TB)
tilde-backend/src/tb/x64/*.c tilde-backend/src/tb/x64/*.c
tilde-backend/src/tb/wasm/*.c tilde-backend/src/tb/wasm/*.c
tilde-backend/src/tb/aarch64/*.c tilde-backend/src/tb/aarch64/*.c
) )
target_sources(c3c PRIVATE target_sources(c3c PRIVATE
src/compiler/tilde_codegen.c src/compiler/tilde_codegen.c
src/compiler/tilde_codegen_instr.c src/compiler/tilde_codegen_instr.c
@@ -350,23 +438,29 @@ if (C3_USE_TB)
target_include_directories(c3c PRIVATE target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/tilde-backend/include/") "${CMAKE_SOURCE_DIR}/tilde-backend/include/")
else() else()
target_compile_definitions(c3c PUBLIC TB_AVAILABLE=0) target_compile_definitions(c3c PUBLIC TB_AVAILABLE=0)
endif() endif()
if(C3_WITH_LLVM)
target_link_libraries(c3c ${llvm_libs} miniz c3c_wrappers ${lld_libs})
target_include_directories(c3c PRIVATE target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/src/") "${CMAKE_SOURCE_DIR}/wrapper/include/")
target_include_directories(c3c_wrappers PRIVATE
"${CMAKE_SOURCE_DIR}/wrapper/include/")
target_include_directories(c3c_wrappers PRIVATE target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
"${CMAKE_SOURCE_DIR}/wrapper/src/")
target_include_directories(miniz PUBLIC else()
"${CMAKE_SOURCE_DIR}/dependencies/miniz/")
target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs}) target_link_libraries(c3c ${llvm_libs} miniz ${lld_libs})
target_link_libraries(c3c ${llvm_libs} miniz c3c_wrappers ${lld_libs})
endif()
if(C3_USE_MIMALLOC) if(C3_USE_MIMALLOC)
target_link_libraries(c3c mimalloc-static) target_link_libraries(c3c mimalloc-static)
@@ -376,6 +470,11 @@ if (WIN32)
target_link_libraries(c3c Winhttp.lib) target_link_libraries(c3c Winhttp.lib)
endif() endif()
if(MINGW)
message("Increase stack for msys")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,8388608")
endif ()
if (CURL_FOUND) if (CURL_FOUND)
target_link_libraries(c3c ${CURL_LIBRARIES}) target_link_libraries(c3c ${CURL_LIBRARIES})
target_include_directories(c3c PRIVATE ${CURL_INCLUDES}) target_include_directories(c3c PRIVATE ${CURL_INCLUDES})
@@ -384,36 +483,78 @@ else()
target_compile_definitions(c3c PUBLIC CURL_FOUND=0) target_compile_definitions(c3c PUBLIC CURL_FOUND=0)
endif() endif()
if(MSVC) if(MSVC)
message("Adding MSVC options") message("Adding MSVC options")
target_compile_options(c3c PRIVATE /wd4068 /wd4090 /WX /Wv:18) target_compile_options(c3c PRIVATE /wd4068 /wd4090 /WX /Wv:18)
target_compile_options(c3c_wrappers PUBLIC /wd4624 /wd4267 /wd4244 /WX /Wv:18) if(C3_WITH_LLVM)
target_link_options(c3c_wrappers PUBLIC /ignore:4099) target_compile_options(c3c_wrappers PUBLIC /wd4624 /wd4267 /wd4244 /WX /Wv:18)
if(NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PUBLIC /GR-)
endif()
target_link_options(c3c_wrappers PUBLIC /ignore:4099)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(c3c PUBLIC /MTd) target_compile_options(c3c PUBLIC /MTd)
target_compile_options(c3c_wrappers PUBLIC /MTd) if (C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /MTd)
endif()
target_compile_options(miniz PUBLIC /MTd) target_compile_options(miniz PUBLIC /MTd)
if (C3_USE_TB) if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MTd) target_compile_options(tilde-backend PUBLIC /MTd)
endif() endif()
else() else()
target_compile_options(c3c PUBLIC /MT) target_compile_options(c3c PUBLIC /MT)
target_compile_options(c3c_wrappers PUBLIC /MT) if (C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /MT)
endif()
target_compile_options(miniz PUBLIC /MT) target_compile_options(miniz PUBLIC /MT)
if (C3_USE_TB) if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MT) target_compile_options(tilde-backend PUBLIC /MT)
endif() endif()
endif() endif()
if(C3_WITH_LLVM)
set(clang_lib_dir ${llvm_dir}/lib/clang/${C3_LLVM_VERSION}/lib/windows)
set(sanitizer_runtime_libraries
${clang_lib_dir}/clang_rt.asan-x86_64.lib
${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.lib
${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.dll
${clang_lib_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
endif()
else() else()
message(STATUS "using gcc/clang warning switches") message(STATUS "using gcc/clang warning switches")
target_link_options(c3c PRIVATE -pthread) target_link_options(c3c PRIVATE -pthread)
if (C3_WITH_LLVM AND NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PRIVATE -fno-rtti)
endif()
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter) -Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)
endif() endif()
install(TARGETS c3c DESTINATION bin) install(TARGETS c3c DESTINATION bin)
install(DIRECTORY lib/ DESTINATION lib/c3) install(DIRECTORY lib/ DESTINATION lib/c3)
# Man page install (OSX/Linux only)
if (NOT WIN32)
install(FILES c3c.1 DESTINATION "share/man/man1")
endif()
if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
add_custom_command(TARGET c3c POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E rm -rf -- $<TARGET_FILE_DIR:c3c>/c3c_rt
COMMAND "${CMAKE_COMMAND}" -E make_directory $<TARGET_FILE_DIR:c3c>/c3c_rt
COMMAND "${CMAKE_COMMAND}" -E copy ${sanitizer_runtime_libraries} $<TARGET_FILE_DIR:c3c>/c3c_rt
VERBATIM
COMMENT "Copying sanitizer runtime libraries to output directory")
if (APPLE)
# Change LC_ID_DYLIB to be rpath-based instead of having an absolute path
add_custom_command(TARGET c3c POST_BUILD
COMMAND find $<TARGET_FILE_DIR:c3c>/c3c_rt -type f -name "*.dylib" -execdir ${LLVM_TOOLS_BINARY_DIR}/llvm-install-name-tool -id @rpath/{} {} $<SEMICOLON>
VERBATIM)
endif()
install(DIRECTORY $<TARGET_FILE_DIR:c3c>/c3c_rt/ DESTINATION bin/c3c_rt)
endif()
feature_summary(WHAT ALL) feature_summary(WHAT ALL)

106
README.md
View File

@@ -10,7 +10,8 @@ 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). - 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). - Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- MacOS x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-mac-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).
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org). The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
@@ -32,10 +33,10 @@ whole new language.
### Example code ### Example code
The following code shows [generic modules](http://www.c3-lang.org/generics/) (more examples can be found at http://www.c3-lang.org/examples/). 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/).
```c++ ```cpp
module stack <Type>; module stack (<Type>);
// Above: the parameterized type is applied to the entire module. // Above: the parameterized type is applied to the entire module.
struct Stack struct Stack
@@ -54,7 +55,7 @@ fn void Stack.push(Stack* this, Type element)
if (this.capacity == this.size) if (this.capacity == this.size)
{ {
this.capacity *= 2; this.capacity *= 2;
if (this.capacity < 16) this.capacity = 16; if (this.capacity < 16) this.capacity = 16;
this.elems = realloc(this.elems, Type.sizeof * this.capacity); this.elems = realloc(this.elems, Type.sizeof * this.capacity);
} }
this.elems[this.size++] = element; this.elems[this.size++] = element;
@@ -122,7 +123,7 @@ fn void main()
- No mandatory header files - No mandatory header files
- New semantic macro system - New semantic macro system
- Module based name spacing - Module based name spacing
- Subarrays (slices) - Slices
- Compile time reflection - Compile time reflection
- Enhanced compile time execution - Enhanced compile time execution
- Generics based on generic modules - Generics based on generic modules
@@ -137,9 +138,9 @@ fn void main()
### Current status ### Current status
The current stable version of the compiler is **version 0.5**. The current stable version of the compiler is **version 0.6.6**.
The upcoming 0.6 release will focus on expanding the standard library. The upcoming 0.6.x releases will focus on expanding the standard library.
Follow the issues [here](https://github.com/c3lang/c3c/issues). 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) If you have suggestions on how to improve the language, either [file an issue](https://github.com/c3lang/c3c/issues)
@@ -205,17 +206,32 @@ More platforms will be supported in the future.
2. Unpack executable and standard lib. 2. Unpack executable and standard lib.
3. Run `./c3c`. 3. Run `./c3c`.
#### Installing on Mac with precompiled binaries #### Installing on Ubuntu with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20-debug.tar.gz))
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on MacOS with precompiled binaries
1. Make sure you have XCode with command line tools installed. 1. Make sure you have XCode with command line tools installed.
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip) 2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-macos-debug.zip)) (debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-macos-debug.zip))
3. Unzip executable and standard lib. 3. Unzip executable and standard lib.
4. Run `./c3c`. 4. Run `./c3c`.
#### Installing on Arch Linux (*Note that there is a known issue with debug symbol generation on MacOS 13, see [issue #1086](https://github.com/c3lang/c3c/issues/1086))
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
Due to some issues with the LLVM packaged for Arch Linux, the AUR package will download and use LLVM 16 for Ubuntu-23.04 to compile the c3c compiler. #### Installing on Arch Linux
Arch includes c3c in the official 'extra' repo. It can be easily installed the usual way:
```sh
sudo pacman -S c3c
# or paru -S c3c
# or yay -S c3c
# or aura -A c3c
```
There is also an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
You can use your AUR package manager: You can use your AUR package manager:
```sh ```sh
@@ -251,7 +267,7 @@ A `c3c` executable will be found under `bin/`.
#### Installing on OS X using Homebrew #### Installing on OS X using Homebrew
2. Install CMake: `brew install cmake` 2. Install CMake: `brew install cmake`
3. Install LLVM 15: `brew install llvm` 3. Install LLVM 17+: `brew install llvm`
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git` 4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
5. Enter the C3C directory `cd c3c`. 5. Enter the C3C directory `cd c3c`.
6. Create a build directory `mkdir build` 6. Create a build directory `mkdir build`
@@ -259,6 +275,14 @@ A `c3c` executable will be found under `bin/`.
8. Set up CMake build for debug: `cmake ..` 8. Set up CMake build for debug: `cmake ..`
9. Build: `cmake --build .` 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" #### Getting started with a "hello world"
Create a `main.c3` file with: Create a `main.c3` file with:
@@ -287,7 +311,7 @@ called `hello_world` or `hello_world.exe`depending on platform.
#### Compiling on Windows #### Compiling on Windows
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++" (there is also `c3c/resources/install_win_reqs.bat` to automate this) 1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++"
2. Install CMake 2. Install CMake
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git` 3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`. 4. Enter the C3C directory `cd c3c`.
@@ -302,27 +326,55 @@ You can try it out by running some sample code: `c3c.exe compile ../resources/ex
*Note that if you run into linking issues when building, make sure that you are using the latest version of VS17.* *Note that if you run into linking issues when building, make sure that you are using the latest version of VS17.*
#### Compiling on Ubuntu 20.10 #### Compiling on Ubuntu 24.04 LTS
1. Make sure you have a C compiler that handles C11 and a C++ compiler, such as GCC or Clang. Git also needs to be installed. 1. Make sure you have a C compiler that handles C11 and a C++ compiler, such as GCC or Clang. Git also needs to be installed.
2. Install CMake: `sudo apt install cmake` 2. Install LLVM 18 `sudo apt-get install cmake git clang zlib1g zlib1g-dev libllvm18 llvm llvm-dev llvm-runtime liblld-dev liblld-18 libpolly-18-dev`
3. Install LLVM 15 (or greater: C3C supports LLVM 15-17): `sudo apt-get install clang-15 zlib1g zlib1g-dev libllvm15 llvm-15 llvm-15-dev llvm-15-runtime liblld-15-dev liblld-15` 3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git` 4. Enter the C3C directory `cd c3c`.
5. Enter the C3C directory `cd c3c`. 5. Create a build directory `mkdir build`
6. Create a build directory `mkdir build` 6. Change directory to the build directory `cd build`
7. Change directory to the build directory `cd build` 7. Set up CMake build: `cmake ..`
8. Set up CMake build: `cmake ..` 8. Build: `cmake --build .`
9. Build: `cmake --build .`
You should now have a `c3c` executable. You should now have a `c3c` executable.
You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3` You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
#### Compiling on Void Linux
1. As root, ensure that all project dependencies are installed: `xbps-install git cmake llvm17 llvm17-devel lld17-devel libcurl-devel ncurses-devel zlib-devel libzstd-devel libxml2-devel`
2. 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 instead: `git clone https://github.com/c3lang/c3c.git --depth=1`
3. Enter the directory: `cd c3c`
4. Create a build directory: `mkdir build`
5. Enter the build directory: `cd build`
6. Create the CMake build cache: `cmake ..`
7. Build: `cmake --build .`
Your c3c executable should have compiled properly. You may want to test it: `./c3c compile ../resources/examples/hash.c3`
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 #### Compiling on other Linux / Unix variants
1. Install CMake. 1. Install CMake.
2. Install or compile LLVM and LLD *libraries* (version 15+ or higher) 2. Install or compile LLVM and LLD *libraries* (version 17+ or higher)
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git` 3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`. 4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build` 5. Create a build directory `mkdir build`
@@ -352,3 +404,9 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
3. Run tests and see that they pass. (Recommended settings: `c3c compile-test -O0 test/unit`. 3. Run tests and see that they pass. (Recommended settings: `c3c compile-test -O0 test/unit`.
- in this example `test/unit/` is the relative path to the test directory, so adjust as required) - in this example `test/unit/` is the relative path to the test directory, so adjust as required)
4. Make a pull request for the new tests. 4. Make a pull request for the new tests.
## Thank yous
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.

View File

@@ -0,0 +1,69 @@
module sort_bench;
import std::sort;
fn void init() @init
{
set_benchmark_warmup_iterations(5);
set_benchmark_max_iterations(10_000);
}
fn void quicksort_bench() @benchmark
{
// test set: 500 numbers between 0 and 99;
int[] data = {
71, 28, 2, 13, 62, 10, 54, 78, 63, 86,
33, 65, 89, 51, 58, 0, 51, 16, 87, 30,
89, 14, 52, 41, 88, 25, 83, 91, 56, 86,
14, 64, 76, 18, 39, 24, 79, 62, 34, 58,
90, 24, 56, 73, 85, 82, 79, 63, 47, 69,
78, 29, 49, 28, 43, 47, 56, 53, 79, 56,
19, 63, 29, 52, 71, 93, 61, 46, 30, 11,
21, 26, 37, 86, 93, 74, 62, 0, 41, 17,
26, 27, 34, 11, 54, 69, 72, 44, 74, 3,
61, 62, 80, 90, 3, 82, 16, 12, 28, 1,
2, 49, 4, 44, 57, 86, 63, 74, 33, 41,
76, 77, 56, 57, 56, 88, 74, 71, 6, 59,
40, 42, 94, 55, 21, 17, 17, 63, 21, 83,
73, 19, 39, 88, 93, 74, 21, 0, 63, 45,
69, 66, 22, 68, 86, 86, 85, 67, 8, 50,
23, 98, 64, 80, 64, 36, 40, 30, 73, 36,
23, 14, 1, 77, 82, 8, 18, 73, 37, 86,
29, 70, 27, 87, 64, 81, 13, 0, 4, 83,
90, 17, 71, 66, 38, 39, 54, 22, 86, 18,
84, 66, 77, 25, 64, 93, 80, 91, 2, 92,
47, 32, 90, 16, 46, 29, 56, 87, 70, 73,
89, 41, 5, 54, 93, 63, 16, 39, 71, 84,
74, 91, 69, 59, 49, 87, 74, 37, 75, 83,
77, 19, 51, 44, 79, 62, 94, 20, 24, 83,
37, 70, 57, 32, 93, 8, 29, 11, 7, 92,
8, 23, 20, 21, 7, 70, 28, 20, 96, 6,
50, 58, 30, 61, 66, 42, 50, 54, 64, 7,
10, 53, 63, 44, 16, 39, 83, 73, 3, 29,
97, 32, 36, 68, 84, 64, 73, 5, 29, 13,
48, 3, 84, 65, 75, 68, 66, 22, 39, 33,
39, 24, 27, 85, 18, 34, 3, 63, 32, 9,
29, 66, 24, 90, 75, 50, 11, 95, 47, 14,
92, 1, 76, 45, 76, 41, 55, 54, 38, 67,
43, 40, 5, 61, 97, 11, 61, 24, 92, 24,
76, 53, 60, 34, 78, 80, 70, 75, 30, 90,
65, 99, 80, 61, 94, 75, 63, 67, 10, 35,
23, 42, 31, 48, 14, 68, 84, 14, 79, 1,
25, 94, 23, 53, 49, 69, 44, 73, 63, 51,
44, 96, 88, 51, 94, 24, 64, 72, 59, 81,
73, 93, 14, 35, 9, 53, 25, 48, 50, 88,
46, 97, 67, 40, 27, 17, 2, 42, 11, 82,
0, 46, 44, 38, 31, 88, 63, 88, 10, 82,
77, 61, 24, 39, 27, 33, 10, 91, 69, 22,
42, 74, 71, 13, 32, 56, 12, 46, 81, 74,
17, 26, 45, 50, 76, 84, 76, 36, 43, 65,
81, 64, 0, 49, 70, 11, 76, 19, 60, 55,
15, 98, 31, 91, 56, 8, 97, 9, 3, 94,
3, 88, 7, 2, 3, 98, 10, 51, 21, 79,
99, 3, 8, 76, 52, 13, 40, 90, 85, 15,
70, 77, 43, 30, 4, 89, 18, 21, 59, 17,
};
sort::quicksort(data);
}

View File

@@ -1,43 +1,44 @@
#!/bin/bash #!/bin/bash
## build-with-docker.sh
## @author gdm85
## @modified by Kenta
##
## Script to build c3c for Ubuntu 22
##
#
read -p "Select Build Type: Debug/Release: " config : ${DOCKER:=docker}
: ${IMAGE:="c3c-builder"}
: ${CMAKE_BUILD_TYPE:=Release}
: ${LLVM_VERSION:=18}
: ${UBUNTU_VERSION:="22.04"}
: ${CMAKE_VERSION:="3.20.0"}
set -e cd docker || exit 1 # Exit if the 'docker' directory doesn't exist
DOCKER=docker $DOCKER build \
DOCKER_RUN="" --build-arg LLVM_VERSION=$LLVM_VERSION \
IMAGE="c3c-builder" --build-arg CMAKE_VERSION=$CMAKE_VERSION \
if type podman 2>/dev/null >/dev/null; then --build-arg UBUNTU_VERSION=$UBUNTU_VERSION \
DOCKER=podman -t $IMAGE .
DOCKER_RUN="--userns=keep-id"
IMAGE="localhost/$IMAGE" if [ $? -ne 0 ]; then
echo "Docker image build failed. Exiting."
exit 1
fi fi
if [ $config == "Debug" ]; then
CMAKE_BUILD_TYPE=Debug
else
CMAKE_BUILD_TYPE="$config"
fi
UBUNTU_VERSION="22.10"
LLVM_VERSION="15"
IMAGE="$IMAGE:22"
cd docker && $DOCKER build -t $IMAGE\
--build-arg DEPS="llvm-$LLVM_VERSION-dev liblld-$LLVM_VERSION-dev clang-$LLVM_VERSION libllvm$LLVM_VERSION llvm-$LLVM_VERSION-runtime" \
--build-arg UBUNTU_VERSION="$UBUNTU_VERSION" .
cd .. cd ..
rm -rf build bin rm -rf build bin
mkdir -p build bin mkdir -p build bin
exec $DOCKER run -ti --rm --tmpfs=/tmp $DOCKER_RUN -v "$PWD":/home/c3c/source -w /home/c3c/source $IMAGE bash -c \ chmod -R 777 build bin
"cd build && cmake -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE -DC3_LLVM_VERSION=$LLVM_VERSION .. && cmake --build . && mv c3c lib ../bin/"
exec $DOCKER run -i --rm \
-v "$PWD":/home/c3c/source \
-w /home/c3c/source $IMAGE bash -c \
"cmake -S . -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
-DCMAKE_C_COMPILER=clang-$LLVM_VERSION \
-DCMAKE_CXX_COMPILER=clang++-$LLVM_VERSION \
-DCMAKE_LINKER=lld-$LLVM_VERSION \
-DCMAKE_OBJCOPY=llvm-objcopy-$LLVM_VERSION \
-DCMAKE_STRIP=llvm-strip-$LLVM_VERSION \
-DCMAKE_DLLTOOL=llvm-dlltool-$LLVM_VERSION \
-DC3_LLVM_VERSION=auto && \
cmake --build build && \
cp -r build/c3c build/lib bin"

552
c3c.1 Normal file
View File

@@ -0,0 +1,552 @@
.TH "c3c" "1" "2024-10-27" "C3 Compiler" "User Commands"
.SH NAME
c3c \- Compiler for the C3 programming language
.SH SYNOPSIS
.B c3c
[\fIoptions\fR ...] \fIcommand\fR [\fIargs\fR ...]
.SH DESCRIPTION
.B c3c
is the compiler for the C3 language, providing commands for compilation, project
management, testing, and distribution. The available commands allow users to
compile files, initialize projects, build targets, run benchmarks, clean build
files, and more.
.SH COMMANDS
.PP
.B c3c compile
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files without a project into an executable.
.RE
.PP
.B c3c init
\fIproject name\fR
.RS
Initialize a new project structure.
.RE
.PP
.B c3c init-lib
\fIlibrary name\fR
.RS
Initialize a new library structure.
.RE
.PP
.B c3c build
[\fItarget\fR]
.RS
Build the target in the current project.
.RE
.PP
.B c3c benchmark
.RS
Run the benchmarks in the current project.
.RE
.PP
.B c3c test
.RS
Run the unit tests in the current project.
.RE
.PP
.B c3c clean
.RS
Clean all build files.
.RE
.PP
.B c3c run
[\fItarget\fR] [-- [\fIarg1\fR ...]]
.RS
Run (and build if needed) the target in the current project.
.RE
.PP
.B c3c dist
[\fItarget\fR]
.RS
Clean and build a target for distribution.
.RE
.PP
.B c3c directives
[\fItarget\fR]
.RS
Generate documentation for the target.
.RE
.PP
.B c3c bench
[\fItarget\fR]
.RS
Benchmark a target.
.RE
.PP
.B c3c clean-run
[\fItarget\fR] [-- [\fIarg1\fR ...]]
.RS
Clean, then run the target.
.RE
.PP
.B c3c compile-run
\fIfile1\fR [\fIfile2\fR ...] [-- [\fIarg1\fR ...]]
.RS
Compile files then immediately run the result.
.RE
.PP
.B c3c compile-only
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files but do not perform linking.
.RE
.PP
.B c3c compile-benchmark
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files into an executable and run benchmarks.
.RE
.PP
.B c3c compile-test
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files into an executable and run unit tests.
.RE
.PP
.B c3c static-lib
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files without a project into a static library.
.RE
.PP
.B c3c dynamic-lib
\fIfile1\fR [\fIfile2\fR ...]
.RS
Compile files without a project into a dynamic library.
.RE
.PP
.B c3c headers
\fIfile1\fR [\fIfile2\fR ...]
.RS
Analyze files and generate C headers for public methods.
.RE
.PP
.B c3c vendor-fetch
\fIlibrary\fR ...
.RS
Fetch one or more libraries from the vendor collection.
.RE
.PP
.B c3c project
\fIsubcommand\fR ...
.RS
Manipulate or view project files.
.RE
.SH OPTIONS
.PP
.B --stdlib
\fIdir\fR
.RS
Use this directory as the C3 standard library path.
.RE
.PP
.B --no-entry
.RS
Do not generate (or require) a main function.
.RE
.PP
.B --libdir
\fIdir\fR
.RS
Add this directory to the C3 library search paths.
.RE
.PP
.B --lib
\fIname\fR
.RS
Add this library to the compilation.
.RE
.PP
.B --path
\fIdir\fR
.RS
Use this as the base directory for the current command.
.RE
.PP
.B --template
\fItemplate\fR
.RS
Select template for 'init': "exe", "static-lib", "dynamic-lib" or a path.
.RE
.PP
.B --about
Prints a short description of C3.
.PP
.B --symtab
\fIvalue\fR
.RS
Sets the preferred symtab size.
.RE
.PP
.B --max-mem
\fIvalue\fR
.RS
Sets the preferred max memory size.
.RE
.PP
.B --run-once
.RS
After running the output file, delete it immediately.
.RE
.PP
.B -V, --version
Print version information.
.PP
.B -E
Lex only.
.PP
.B -P
Only parse and output the AST as JSON.
.PP
.B -C
Only lex, parse and check.
.PP
.B -
\fIcode\fR...
.RS
Read code from standard in.
.RE
.PP
.B -o
\fIfile\fR
.RS
Write output to \fIfile\fR.
.RE
.PP
.B -O0
Safe, no optimizations, emit debug info.
.PP
.B -O1
Safe, high optimization, emit debug info.
.PP
.B -O2
Unsafe, high optimization, emit debug info.
.PP
.B -O3
Unsafe, high optimization, single module, emit debug info.
.PP
.B -O4
Unsafe, highest optimization, relaxed maths, single module, emit debug info, no panic messages.
.PP
.B -O5
Unsafe, highest optimization, fast maths, single module, emit debug info, no panic messages, no backtrace.
.PP
.B -Os
Unsafe, high optimization, small code, single module, no debug info, no panic messages.
.PP
.B -Oz
Unsafe, high optimization, tiny code, single module, no debug info, no panic messages, no backtrace.
.PP
.B -D
\fIname\fR
.RS
Add feature flag \fIname\fR.
.RE
.PP
.B -U
\fIname\fR
.RS
Remove feature flag \fIname\fR.
.RE
.PP
.B --trust=
\fIoption\fR
.RS
Trust level: none (default), include ($include allowed), full ($exec / exec allowed).
.RE
.PP
.B --output-dir
\fIdir\fR
.RS
Override general output directory.
.RE
.PP
.B --threads
\fInumber\fR
.RS
Set the number of threads to use for compilation.
.RE
.PP
.B --show-backtrace=
\fIyes|no\fR
.RS
Show detailed backtrace on segfaults.
.RE
.PP
.B -g
Emit debug info.
.PP
.B -g0
Emit no debug info.
.PP
.B -l
\fIlibrary\fR
.RS
Link with the library provided.
.RE
.PP
.B -L
\fIlibrary\fR \fIdir\fR
.RS
Append the directory to the linker search paths.
.RE
.PP
.B -z
\fIargument\fR
.RS
Send the \fIargument\fR as a parameter to the linker.
.RE
.PP
.B --cc
\fIpath\fR
.RS
Set C compiler (for C files in projects and use as system linker).
.RE
.PP
.B --linker=
\fIoption\fR [\fIpath\fR]
.RS
Specify the linker: builtin, cc, custom (default is 'cc'). 'Custom' requires a path.
.RE
.PP
.B --use-stdlib=
\fIyes|no\fR
.RS
Include the standard library (default: yes).
.RE
.PP
.B --link-libc=
\fIyes|no\fR
.RS
Link libc and other default libraries (default: yes).
.RE
.PP
.B --emit-stdlib=
\fIyes|no\fR
.RS
Output files for the standard library (default: yes).
.RE
.PP
.B --panicfn
\fIname\fR
.RS
Override the panic function name.
.RE
.PP
.B --testfn
\fIname\fR
.RS
Override the test runner function name.
.RE
.PP
.B --benchfn
\fIname\fR
.RS
Override the benchmark runner function name.
.RE
.PP
.B --reloc=
\fIoption\fR
.RS
Specify the relocation model: none, pic, PIC, pie, PIE.
.RE
.PP
.B --x86cpu=
\fIoption\fR
.RS
Set general level of x64 CPU: baseline, ssse3, sse4, avx1, avx2-v1, avx2-v2 (Skylake/Zen1+), avx512 (Icelake/Zen4+), native.
.RE
.PP
.B --x86vec=
\fIoption\fR
.RS
Set maximum type of vector use: none, mmx, sse, avx, avx512, default.
.RE
.PP
.B --riscvfloat=
\fIoption\fR
.RS
Set type of RISC-V float support: none, float, double.
.RE
.PP
.B --memory-env=
\fIoption\fR
.RS
Set the memory environment: normal, small, tiny, none.
.RE
.PP
.B --strip-unused=
\fIyes|no\fR
.RS
Strip unused code and globals from the output (default: yes).
.RE
.PP
.B --fp-math=
\fIoption\fR
.RS
Specify floating-point math behavior: strict, relaxed, fast.
.RE
.PP
.B --win64-simd=
\fIoption\fR
.RS
Windows SIMD ABI: array, full.
.RE
.PP
.B --debug-stats
Print debug statistics.
.PP
.B --print-linking
Print linker arguments.
.PP
.B --debug-log
Print debug logging to stdout.
.PP
.B --benchmarking
Run built-in benchmarks.
.PP
.B --testing
Run built-in tests.
.PP
.B --list-attributes
List all attributes.
.PP
.B --list-builtins
List all builtins.
.PP
.B --list-keywords
List all keywords.
.PP
.B --list-operators
List all operators.
.PP
.B --list-precedence
List operator precedence order.
.PP
.B --list-project-properties
List all available keys used in project.json files.
.PP
.B --list-manifest-properties
List all available keys used in manifest.json files.
.PP
.B --list-targets
List all architectures the compiler supports.
.PP
.B --list-type-properties
List all type properties.
.PP
.B --print-output
Print the object files created to stdout.
.PP
.B --print-input
Print inputted C3 files to stdout.
.PP
.B --winsdk
\fIdir\fR
.RS
Set the directory for Windows system library files for cross-compilation.
.RE
.PP
.B --wincrt=
\fIoption\fR
.RS
Windows CRT linking: none, static-debug, static, dynamic-debug (default if debug info enabled), dynamic (default).
.RE
.PP
.B --windef
\fIfile\fR
.RS
Use Windows 'def' file for function exports instead of 'dllexport'.
.RE
.PP
.B --macossdk
\fIdir\fR
.RS
Set the directory for the MacOS SDK for cross-compilation.
.RE
.PP
.B --macos-min-version
\fIver\fR
.RS
Set the minimum MacOS version to compile for.
.RE
.PP
.B --macos-sdk-version
\fIver\fR
.RS
Set the MacOS SDK version to compile for.
.RE
.PP
.B --linux-crt
\fIdir\fR
.RS
Set the directory to use for finding crt1.o and related files.
.RE
.PP
.B --linux-crtbegin
\fIdir\fR
.RS
Set the directory to use for finding crtbegin.o and related files.
.RE
.PP
.B --vector-conv=
\fIoption\fR
.RS
Set vector conversion behavior: default, old.
.RE
.PP
.B --sanitize=
\fIoption\fR
.RS
Enable a sanitizer: address, memory, thread.
.RE
.SH EXAMPLES
.PP
Create a project:
.RS
.B c3c init new_project
.RE
.PP
Create a library project:
.RS
.B c3c init-lib new_library
.RE
.PP
Compile a file:
.RS
.B c3c compile main.c3
.RE
.PP
Build the current project:
.RS
.B c3c build
.RE
.PP
Run tests for the current project:
.RS
.B c3c test
.RE

View File

@@ -1,16 +1,49 @@
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION}
ARG UBUNTU_VERSION ARG LLVM_VERSION=18
FROM ubuntu:$UBUNTU_VERSION ENV LLVM_DEV_VERSION=20
ARG DEPS ARG CMAKE_VERSION=3.20
RUN export DEBIAN_FRONTEND=noninteractive && export TERM=xterm && apt-get update && apt-get install -y build-essential cmake zlib1g zlib1g-dev \ RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ && \
$DEPS && \ wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-$CMAKE_VERSION-linux-x86_64.sh && \
rm -rf /var/lib/apt/lists/* mkdir -p /opt/cmake && \
sh cmake-${CMAKE_VERSION}-linux-x86_64.sh --prefix=/opt/cmake --skip-license && \
rm cmake-${CMAKE_VERSION}-linux-x86_64.sh && \
ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake
ARG GID=1000 RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \
ARG UID=1000 if [ "${LLVM_VERSION}" -lt 18 ]; then \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev libmlir-${LLVM_VERSION} \
libmlir-${LLVM_VERSION}-dev mlir-${LLVM_VERSION}-tools; \
elif [ "${LLVM_VERSION}" -lt "${LLVM_DEV_VERSION}" ]; then \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} clang++-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
else \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
fi && \
rm -rf /var/lib/apt/lists/*
RUN groupadd -o --gid=$GID c3c && useradd --gid=$GID --uid=$GID --create-home --shell /bin/bash c3c RUN groupadd -g 1337 c3c && \
useradd -m -u 1337 -g c3c c3c
# Add cmake to PATH for user c3c
USER c3c USER c3c
ENV PATH="/opt/cmake/bin:${PATH}"
WORKDIR /home/c3c

61
flake.lock generated Normal file
View File

@@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1738297584,
"narHash": "sha256-AYvaFBzt8dU0fcSK2jKD0Vg23K2eIRxfsVXIPCW9a0E=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "9189ac18287c599860e878e905da550aa6dec1cd",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

44
flake.nix Normal file
View File

@@ -0,0 +1,44 @@
{
description = "C3 compiler flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem
(system:
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 = call {};
c3c-checks = pkgs.callPackage ./nix/default.nix {
checks = true;
};
c3c-debug = pkgs.callPackage ./nix/default.nix {
debug = true;
};
c3c-debug-checks = pkgs.callPackage ./nix/default.nix {
debug = true;
checks = true;
};
};
devShells = {
default = pkgs.callPackage ./nix/shell.nix {
c3c = self.packages.${system}.c3c-debug;
};
};
}
);
}

15
git_hash.cmake Normal file
View File

@@ -0,0 +1,15 @@
find_package(Git QUIET)
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)
endif()
message("Git Hash: ${GIT_HASH}")
file(WRITE ${CMAKE_BINARY_DIR}/git_hash.h "#pragma once\n#define GIT_HASH \"${GIT_HASH}\"\n")

View File

@@ -1,17 +0,0 @@
@echo off
set DOWNLOAD_URL=https://aka.ms/vs/17/release
mkdir tmp 2> NUL
if not exist "tmp\vs_buildtools.exe" (
bitsadmin /transfer /download /priority foreground %DOWNLOAD_URL%/vs_buildtools.exe %CD%\tmp\vs_buildtools.exe
)
echo Preparing Build Tools, please wait...
tmp\vs_BuildTools.exe --quiet --wait --layout tmp\ --add Microsoft.VisualStudio.Component.Windows10SDK.19041
echo Installing Build Tools, please wait...
tmp\vs_BuildTools.exe --quiet --wait --noweb --add Microsoft.VisualStudio.Component.Windows10SDK.19041
REM rmdir tmp /s /q

View File

@@ -53,9 +53,9 @@ fn bool char.is_blank(char c) => is_blank_m(c);
fn bool char.is_cntrl(char c) => is_cntrl_m(c); fn bool char.is_cntrl(char c) => is_cntrl_m(c);
fn char char.to_lower(char c) => (char)to_lower_m(c); fn char char.to_lower(char c) => (char)to_lower_m(c);
fn char char.to_upper(char c) => (char)to_upper_m(c); fn char char.to_upper(char c) => (char)to_upper_m(c);
/** <*
* @require c.is_xdigit() @require c.is_xdigit()
**/ *>
fn char char.from_hex(char c) => c.is_digit() ? c - '0' : 10 + (c | 0x20) - 'a'; fn char char.from_hex(char c) => c.is_digit() ? c - '0' : 10 + (c | 0x20) - 'a';
fn bool uint.in_range(uint c, uint start, uint len) => in_range_m(c, start, len); fn bool uint.in_range(uint c, uint start, uint len) => in_range_m(c, start, len);

View File

@@ -8,12 +8,12 @@ struct Atomic
Type data; Type data;
} }
/** <*
* Loads data atomically, by default this uses SEQ_CONSISTENT ordering. 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" @require ordering != RELEASE && ordering != ACQUIRE_RELEASE : "Release and acquire-release are not valid for load"
**/ *>
macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT) macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
{ {
Type* data = &self.data; Type* data = &self.data;
@@ -28,12 +28,12 @@ macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
case RELEASE: unreachable("Invalid ordering."); case RELEASE: unreachable("Invalid ordering.");
} }
} }
/** <*
* Stores data atomically, by default this uses SEQ_CONSISTENT ordering. 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" @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) macro void Atomic.store(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{ {
Type* data = &self.data; Type* data = &self.data;
@@ -124,23 +124,23 @@ macro @atomic_exec(#func, data, value, ordering) @local
case RELEASE: return #func(data, value, RELEASE); case RELEASE: return #func(data, value, RELEASE);
case ACQUIRE_RELEASE: return #func(data, value, ACQUIRE_RELEASE); case ACQUIRE_RELEASE: return #func(data, value, ACQUIRE_RELEASE);
case SEQ_CONSISTENT: return #func(data, value, SEQ_CONSISTENT); case SEQ_CONSISTENT: return #func(data, value, SEQ_CONSISTENT);
default: assert(false, "Ordering may not be non-atomic or unordered."); default: unreachable("Ordering may not be non-atomic or unordered.");
} }
} }
module std::atomic; module std::atomic;
import std::math; import std::math;
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr." @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @return "returns the old value of ptr"
*
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two." @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 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 $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) macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{ {
$if $alignment == 0: $if $alignment == 0:
@@ -149,16 +149,16 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
return $$atomic_fetch_add(ptr, y, $volatile, $ordering.ordinal, $alignment); return $$atomic_fetch_add(ptr, y, $volatile, $ordering.ordinal, $alignment);
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr." @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @return "returns the old value of ptr"
*
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two." @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 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 $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) macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{ {
$if $alignment == 0: $if $alignment == 0:
@@ -167,15 +167,15 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
return $$atomic_fetch_sub(ptr, y, $volatile, $ordering.ordinal, $alignment); return $$atomic_fetch_sub(ptr, y, $volatile, $ordering.ordinal, $alignment);
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr." @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @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 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 $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/ *>
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT) macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{ {
var $load_ordering = $ordering; var $load_ordering = $ordering;
@@ -204,15 +204,15 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
return old_value; return old_value;
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr." @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @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 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 $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/ *>
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT) macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{ {
var $load_ordering = $ordering; var $load_ordering = $ordering;
@@ -240,17 +240,17 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
return old_value; return old_value;
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr." @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @return "returns the old value of ptr"
*
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two." @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(*ptr)) "Only integer pointers may be used."
* @require types::is_int($typeof(y)) "The value for or must be an int" @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 $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) macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{ {
$if types::is_int($typeof(*ptr)): $if types::is_int($typeof(*ptr)):
@@ -283,17 +283,17 @@ macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile
return old_value; return old_value;
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr." @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @return "returns the old value of ptr"
*
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two." @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(*ptr)) "Only integer pointers may be used."
* @require types::is_int($typeof(y)) "The value for or must be an int" @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 $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) macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{ {
$if types::is_int($typeof(*ptr)): $if types::is_int($typeof(*ptr)):
@@ -326,17 +326,17 @@ macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
return old_value; return old_value;
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr." @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @return "returns the old value of ptr"
*
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two." @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(*ptr)) "Only integer pointers may be used."
* @require types::is_int($typeof(y)) "The value for or must be an int" @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 $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) macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{ {
$if types::is_int($typeof(*ptr)): $if types::is_int($typeof(*ptr)):
@@ -369,16 +369,16 @@ macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
return old_value; return old_value;
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr." @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used." @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 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 $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/ *>
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT) macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{ {
var $load_ordering = $ordering; var $load_ordering = $ordering;
@@ -407,16 +407,16 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
return old_value; return old_value;
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr." @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used." @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 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 $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/ *>
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT) macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{ {
var $load_ordering = $ordering; var $load_ordering = $ordering;
@@ -445,14 +445,14 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
return old_value; return old_value;
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used." @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 $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/ *>
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT) macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{ {
$typeof(*ptr) old_value; $typeof(*ptr) old_value;
@@ -465,14 +465,14 @@ macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
return old_value; return old_value;
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used." @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 $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/ *>
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT) macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{ {
$typeof(*ptr) old_value; $typeof(*ptr) old_value;
@@ -485,15 +485,15 @@ macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
return old_value; return old_value;
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr." @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @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 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 $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) macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{ {
$if $alignment == 0: $if $alignment == 0:
@@ -502,15 +502,15 @@ macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
return $$atomic_fetch_max(ptr, y, $volatile, $ordering.ordinal, $alignment); return $$atomic_fetch_max(ptr, y, $volatile, $ordering.ordinal, $alignment);
} }
/** <*
* @param [&in] ptr "the variable or dereferenced pointer to the data." @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr." @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT" @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr" @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 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 $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) macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{ {
$if $alignment == 0: $if $alignment == 0:

View File

@@ -9,7 +9,7 @@ macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $succe
case AtomicOrdering.RELAXED.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.RELAXED.ordinal, $alignment); 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); case AtomicOrdering.ACQUIRE.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.ACQUIRE.ordinal, $alignment);
case AtomicOrdering.SEQ_CONSISTENT.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.SEQ_CONSISTENT.ordinal, $alignment); case AtomicOrdering.SEQ_CONSISTENT.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.SEQ_CONSISTENT.ordinal, $alignment);
default: assert(false, "Unrecognized failure ordering"); default: unreachable("Unrecognized failure ordering");
} }
return 0; return 0;
} }
@@ -23,12 +23,12 @@ macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, succes
case AtomicOrdering.RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELEASE.ordinal, failure, $alignment); case AtomicOrdering.RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELEASE.ordinal, failure, $alignment);
case AtomicOrdering.ACQUIRE_RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE_RELEASE.ordinal, failure, $alignment); case AtomicOrdering.ACQUIRE_RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE_RELEASE.ordinal, failure, $alignment);
case AtomicOrdering.SEQ_CONSISTENT.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.SEQ_CONSISTENT.ordinal, failure, $alignment); case AtomicOrdering.SEQ_CONSISTENT.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.SEQ_CONSISTENT.ordinal, failure, $alignment);
default: assert(false, "Unrecognized success ordering"); default: unreachable("Unrecognized success ordering");
} }
return 0; 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) @extern("__atomic_compare_exchange") @export
{ {
switch (size) switch (size)
{ {
@@ -57,7 +57,7 @@ fn CInt __atomic_compare_exchange(CInt size, any* ptr, any* expected, any* desir
nextcase; nextcase;
$endif $endif
default: default:
assert(false, "Unsuported size (%d) for atomic_compare_exchange", size); unreachable("Unsuported size (%d) for atomic_compare_exchange", size);
} }
return 0; return 0;
} }

View File

@@ -1,94 +1,94 @@
module std::bits; 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); 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); macro bswap(i) @builtin => $$bswap(i);
macro uint[<*>].popcount(self) => $$popcount(self); macro uint[<?>].popcount(self) => $$popcount(self);
macro uint[<*>].ctz(self) => $$ctz(self); macro uint[<?>].ctz(self) => $$ctz(self);
macro uint[<*>].clz(self) => $$clz(self); macro uint[<?>].clz(self) => $$clz(self);
macro uint[<*>] uint[<*>].fshl(hi, uint[<*>] lo, uint[<*>] shift) => $$fshl(hi, lo, shift); macro uint[<?>] uint[<?>].fshl(hi, uint[<?>] lo, uint[<?>] shift) => $$fshl(hi, lo, shift);
macro uint[<*>] uint[<*>].fshr(hi, uint[<*>] lo, uint[<*>] shift) => $$fshr(hi, lo, shift); macro uint[<?>] uint[<?>].fshr(hi, uint[<?>] lo, uint[<?>] shift) => $$fshr(hi, lo, shift);
macro uint[<*>] uint[<*>].rotl(self, uint[<*>] shift) => $$fshl(self, self, shift); macro uint[<?>] uint[<?>].rotl(self, uint[<?>] shift) => $$fshl(self, self, shift);
macro uint[<*>] uint[<*>].rotr(self, uint[<*>] shift) => $$fshr(self, self, shift); macro uint[<?>] uint[<?>].rotr(self, uint[<?>] shift) => $$fshr(self, self, shift);
macro int[<*>].popcount(self) => $$popcount(self); macro int[<?>].popcount(self) => $$popcount(self);
macro int[<*>].ctz(self) => $$ctz(self); macro int[<?>].ctz(self) => $$ctz(self);
macro int[<*>].clz(self) => $$clz(self); macro int[<?>].clz(self) => $$clz(self);
macro int[<*>] int[<*>].fshl(hi, int[<*>] lo, int[<*>] shift) => $$fshl(hi, lo, shift); macro int[<?>] int[<?>].fshl(hi, int[<?>] lo, int[<?>] shift) => $$fshl(hi, lo, shift);
macro int[<*>] int[<*>].fshr(hi, int[<*>] lo, int[<*>] shift) => $$fshr(hi, lo, shift); macro int[<?>] int[<?>].fshr(hi, int[<?>] lo, int[<?>] shift) => $$fshr(hi, lo, shift);
macro int[<*>] int[<*>].rotl(self, int[<*>] shift) => $$fshl(self, self, shift); macro int[<?>] int[<?>].rotl(self, int[<?>] shift) => $$fshl(self, self, shift);
macro int[<*>] int[<*>].rotr(self, int[<*>] shift) => $$fshr(self, self, shift); macro int[<?>] int[<?>].rotr(self, int[<?>] shift) => $$fshr(self, self, shift);
macro ushort[<*>].popcount(self) => $$popcount(self); macro ushort[<?>].popcount(self) => $$popcount(self);
macro ushort[<*>].ctz(self) => $$ctz(self); macro ushort[<?>].ctz(self) => $$ctz(self);
macro ushort[<*>].clz(self) => $$clz(self); macro ushort[<?>].clz(self) => $$clz(self);
macro ushort[<*>] ushort[<*>].fshl(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshl(hi, lo, shift); macro ushort[<?>] ushort[<?>].fshl(hi, ushort[<?>] lo, ushort[<?>] shift) => $$fshl(hi, lo, shift);
macro ushort[<*>] ushort[<*>].fshr(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshr(hi, lo, shift); macro ushort[<?>] ushort[<?>].fshr(hi, ushort[<?>] lo, ushort[<?>] shift) => $$fshr(hi, lo, shift);
macro ushort[<*>] ushort[<*>].rotl(self, ushort[<*>] shift) => $$fshl(self, self, shift); macro ushort[<?>] ushort[<?>].rotl(self, ushort[<?>] shift) => $$fshl(self, self, shift);
macro ushort[<*>] ushort[<*>].rotr(self, ushort[<*>] shift) => $$fshr(self, self, shift); macro ushort[<?>] ushort[<?>].rotr(self, ushort[<?>] shift) => $$fshr(self, self, shift);
macro short[<*>].popcount(self) => $$popcount(self); macro short[<?>].popcount(self) => $$popcount(self);
macro short[<*>].ctz(self) => $$ctz(self); macro short[<?>].ctz(self) => $$ctz(self);
macro short[<*>].clz(self) => $$clz(self); macro short[<?>].clz(self) => $$clz(self);
macro short[<*>] short[<*>].fshl(hi, short[<*>] lo, short[<*>] shift) => $$fshl(hi, lo, shift); macro short[<?>] short[<?>].fshl(hi, short[<?>] lo, short[<?>] shift) => $$fshl(hi, lo, shift);
macro short[<*>] short[<*>].fshr(hi, short[<*>] lo, short[<*>] shift) => $$fshr(hi, lo, shift); macro short[<?>] short[<?>].fshr(hi, short[<?>] lo, short[<?>] shift) => $$fshr(hi, lo, shift);
macro short[<*>] short[<*>].rotl(self, short[<*>] shift) => $$fshl(self, self, shift); macro short[<?>] short[<?>].rotl(self, short[<?>] shift) => $$fshl(self, self, shift);
macro short[<*>] short[<*>].rotr(self, short[<*>] shift) => $$fshr(self, self, shift); macro short[<?>] short[<?>].rotr(self, short[<?>] shift) => $$fshr(self, self, shift);
macro char[<*>].popcount(self) => $$popcount(self); macro char[<?>].popcount(self) => $$popcount(self);
macro char[<*>].ctz(self) => $$ctz(self); macro char[<?>].ctz(self) => $$ctz(self);
macro char[<*>].clz(self) => $$clz(self); macro char[<?>].clz(self) => $$clz(self);
macro char[<*>] char[<*>].fshl(hi, char[<*>] lo, char[<*>] shift) => $$fshl(hi, lo, shift); macro char[<?>] char[<?>].fshl(hi, char[<?>] lo, char[<?>] shift) => $$fshl(hi, lo, shift);
macro char[<*>] char[<*>].fshr(hi, char[<*>] lo, char[<*>] shift) => $$fshr(hi, lo, shift); macro char[<?>] char[<?>].fshr(hi, char[<?>] lo, char[<?>] shift) => $$fshr(hi, lo, shift);
macro char[<*>] char[<*>].rotl(self, char[<*>] shift) => $$fshl(self, self, shift); macro char[<?>] char[<?>].rotl(self, char[<?>] shift) => $$fshl(self, self, shift);
macro char[<*>] char[<*>].rotr(self, char[<*>] shift) => $$fshr(self, self, shift); macro char[<?>] char[<?>].rotr(self, char[<?>] shift) => $$fshr(self, self, shift);
macro ichar[<*>].popcount(self) => $$popcount(self); macro ichar[<?>].popcount(self) => $$popcount(self);
macro ichar[<*>].ctz(self) => $$ctz(self); macro ichar[<?>].ctz(self) => $$ctz(self);
macro ichar[<*>].clz(self) => $$clz(self); macro ichar[<?>].clz(self) => $$clz(self);
macro ichar[<*>] ichar[<*>].fshl(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshl(hi, lo, shift); macro ichar[<?>] ichar[<?>].fshl(hi, ichar[<?>] lo, ichar[<?>] shift) => $$fshl(hi, lo, shift);
macro ichar[<*>] ichar[<*>].fshr(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshr(hi, lo, shift); macro ichar[<?>] ichar[<?>].fshr(hi, ichar[<?>] lo, ichar[<?>] shift) => $$fshr(hi, lo, shift);
macro ichar[<*>] ichar[<*>].rotl(self, ichar[<*>] shift) => $$fshl(self, self, shift); macro ichar[<?>] ichar[<?>].rotl(self, ichar[<?>] shift) => $$fshl(self, self, shift);
macro ichar[<*>] ichar[<*>].rotr(self, ichar[<*>] shift) => $$fshr(self, self, shift); macro ichar[<?>] ichar[<?>].rotr(self, ichar[<?>] shift) => $$fshr(self, self, shift);
macro ulong[<*>].popcount(self) => $$popcount(self); macro ulong[<?>].popcount(self) => $$popcount(self);
macro ulong[<*>].ctz(self) => $$ctz(self); macro ulong[<?>].ctz(self) => $$ctz(self);
macro ulong[<*>].clz(self) => $$clz(self); macro ulong[<?>].clz(self) => $$clz(self);
macro ulong[<*>] ulong[<*>].fshl(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshl(hi, lo, shift); macro ulong[<?>] ulong[<?>].fshl(hi, ulong[<?>] lo, ulong[<?>] shift) => $$fshl(hi, lo, shift);
macro ulong[<*>] ulong[<*>].fshr(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshr(hi, lo, shift); macro ulong[<?>] ulong[<?>].fshr(hi, ulong[<?>] lo, ulong[<?>] shift) => $$fshr(hi, lo, shift);
macro ulong[<*>] ulong[<*>].rotl(self, ulong[<*>] shift) => $$fshl(self, self, shift); macro ulong[<?>] ulong[<?>].rotl(self, ulong[<?>] shift) => $$fshl(self, self, shift);
macro ulong[<*>] ulong[<*>].rotr(self, ulong[<*>] shift) => $$fshr(self, self, shift); macro ulong[<?>] ulong[<?>].rotr(self, ulong[<?>] shift) => $$fshr(self, self, shift);
macro long[<*>].popcount(self) => $$popcount(self); macro long[<?>].popcount(self) => $$popcount(self);
macro long[<*>].ctz(self) => $$ctz(self); macro long[<?>].ctz(self) => $$ctz(self);
macro long[<*>].clz(self) => $$clz(self); macro long[<?>].clz(self) => $$clz(self);
macro long[<*>] long[<*>].fshl(hi, long[<*>] lo, long[<*>] shift) => $$fshl(hi, lo, shift); macro long[<?>] long[<?>].fshl(hi, long[<?>] lo, long[<?>] shift) => $$fshl(hi, lo, shift);
macro long[<*>] long[<*>].fshr(hi, long[<*>] lo, long[<*>] shift) => $$fshr(hi, lo, shift); macro long[<?>] long[<?>].fshr(hi, long[<?>] lo, long[<?>] shift) => $$fshr(hi, lo, shift);
macro long[<*>] long[<*>].rotl(self, long[<*>] shift) => $$fshl(self, self, shift); macro long[<?>] long[<?>].rotl(self, long[<?>] shift) => $$fshl(self, self, shift);
macro long[<*>] long[<*>].rotr(self, long[<*>] shift) => $$fshr(self, self, shift); macro long[<?>] long[<?>].rotr(self, long[<?>] shift) => $$fshr(self, self, shift);
macro uint128[<*>].popcount(self) => $$popcount(self); macro uint128[<?>].popcount(self) => $$popcount(self);
macro uint128[<*>].ctz(self) => $$ctz(self); macro uint128[<?>].ctz(self) => $$ctz(self);
macro uint128[<*>].clz(self) => $$clz(self); macro uint128[<?>].clz(self) => $$clz(self);
macro uint128[<*>] uint128[<*>].fshl(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshl(hi, lo, shift); macro uint128[<?>] uint128[<?>].fshl(hi, uint128[<?>] lo, uint128[<?>] shift) => $$fshl(hi, lo, shift);
macro uint128[<*>] uint128[<*>].fshr(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshr(hi, lo, shift); macro uint128[<?>] uint128[<?>].fshr(hi, uint128[<?>] lo, uint128[<?>] shift) => $$fshr(hi, lo, shift);
macro uint128[<*>] uint128[<*>].rotl(self, uint128[<*>] shift) => $$fshl(self, self, shift); macro uint128[<?>] uint128[<?>].rotl(self, uint128[<?>] shift) => $$fshl(self, self, shift);
macro uint128[<*>] uint128[<*>].rotr(self, uint128[<*>] shift) => $$fshr(self, self, shift); macro uint128[<?>] uint128[<?>].rotr(self, uint128[<?>] shift) => $$fshr(self, self, shift);
macro int128[<*>].popcount(self) => $$popcount(self); macro int128[<?>].popcount(self) => $$popcount(self);
macro int128[<*>].ctz(self) => $$ctz(self); macro int128[<?>].ctz(self) => $$ctz(self);
macro int128[<*>].clz(self) => $$clz(self); macro int128[<?>].clz(self) => $$clz(self);
macro int128[<*>] int128[<*>].fshl(hi, int128[<*>] lo, int128[<*>] shift) => $$fshl(hi, lo, shift); macro int128[<?>] int128[<?>].fshl(hi, int128[<?>] lo, int128[<?>] shift) => $$fshl(hi, lo, shift);
macro int128[<*>] int128[<*>].fshr(hi, int128[<*>] lo, int128[<*>] shift) => $$fshr(hi, lo, shift); macro int128[<?>] int128[<?>].fshr(hi, int128[<?>] lo, int128[<?>] shift) => $$fshr(hi, lo, shift);
macro int128[<*>] int128[<*>].rotl(self, int128[<*>] shift) => $$fshl(self, self, shift); macro int128[<?>] int128[<?>].rotl(self, int128[<?>] shift) => $$fshl(self, self, shift);
macro int128[<*>] int128[<*>].rotr(self, int128[<*>] shift) => $$fshr(self, self, shift); macro int128[<?>] int128[<?>].rotr(self, int128[<?>] shift) => $$fshr(self, self, shift);
macro uint.popcount(self) => $$popcount(self); macro uint.popcount(self) => $$popcount(self);
macro uint.ctz(self) => $$ctz(self); macro uint.ctz(self) => $$ctz(self);

View File

@@ -0,0 +1,503 @@
// Copyright (c) 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::anylist;
import std::io,std::math;
def AnyPredicate = fn bool(any value);
def AnyTest = fn bool(any type, any context);
struct AnyList (Printable)
{
usz size;
usz capacity;
Allocator allocator;
any* entries;
}
<*
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"
*>
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
{
self.allocator = allocator;
self.size = 0;
if (initial_capacity > 0)
{
initial_capacity = math::next_power_of_2(initial_capacity);
self.entries = allocator::alloc_array(allocator, any, initial_capacity);
}
else
{
self.entries = null;
}
self.capacity = initial_capacity;
return self;
}
<*
Initialize the list using the temp allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16)
{
return self.init(allocator::temp(), initial_capacity) @inline;
}
fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
case 0:
return formatter.print("[]")!;
case 1:
return formatter.printf("[%s]", self.entries[0])!;
default:
usz n = formatter.print("[")!;
foreach (i, element : self.entries[:self.size])
{
if (i != 0) formatter.print(", ")!;
n += formatter.printf("%s", element)!;
}
n += formatter.print("]")!;
return n;
}
}
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();
self.append_internal(allocator::clone(self.allocator, element));
}
fn void AnyList.append_internal(&self, any element) @local
{
self.ensure_capacity();
self.entries[self.size++] = element;
}
<*
Free a retained element removed using *_retained.
*>
fn void AnyList.free_element(&self, any element) @inline
{
allocator::free(self.allocator, element.ptr);
}
<*
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
*>
macro AnyList.pop(&self, $Type)
{
if (!self.size) return IteratorResult.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
*>
fn any! AnyList.copy_pop(&self, Allocator allocator = allocator::heap())
{
if (!self.size) return IteratorResult.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`
*>
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());
<*
Pop the last value. It must later be released using list.free_element()
@return! IteratorResult.NO_MORE_ELEMENT
*>
fn any! AnyList.pop_retained(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
fn void AnyList.clear(&self)
{
for (usz i = 0; i < self.size; i++)
{
self.free_element(self.entries[i]);
}
self.size = 0;
}
<*
Same as pop() but pops the first value instead.
*>
macro AnyList.pop_first(&self, $Type)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return *anycast(self.entries[0], $Type);
}
<*
Same as pop_retained() but pops the first value instead.
*>
fn any! AnyList.pop_first_retained(&self)
{
if (!self.size) return IteratorResult.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.
*>
fn any! AnyList.copy_pop_first(&self, Allocator allocator = allocator::heap())
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
defer self.remove_at(0);
return allocator::clone_any(allocator, self.entries[0]);
}
<*
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());
<*
@require index < self.size
*>
fn void AnyList.remove_at(&self, usz index)
{
if (!--self.size || index == self.size) return;
self.free_element(self.entries[index]);
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
}
fn void AnyList.add_all(&self, AnyList* other_list)
{
if (!other_list.size) return;
self.reserve(other_list.size);
foreach (value : other_list)
{
self.entries[self.size++] = allocator::clone_any(self.allocator, value);
}
}
<*
Reverse the elements in a list.
*>
fn void AnyList.reverse(&self)
{
if (self.size < 2) return;
usz half = self.size / 2U;
usz end = self.size - 1;
for (usz i = 0; i < half; i++)
{
self.swap(i, end - i);
}
}
fn any[] AnyList.array_view(&self)
{
return self.entries[:self.size];
}
<*
Push an element to the front of the list.
*>
macro void AnyList.push_front(&self, type)
{
self.insert_at(0, type);
}
<*
@require index < self.size
*>
macro void AnyList.insert_at(&self, usz index, type) @local
{
any value = allocator::copy(self.allocator, type);
self.insert_at_internal(self, index, value);
}
<*
@require index < self.size
*>
fn void AnyList.insert_at_internal(&self, usz index, any value) @local
{
self.ensure_capacity();
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.size++;
self.entries[index] = value;
}
<*
@require self.size > 0
*>
fn void AnyList.remove_last(&self)
{
self.free_element(self.entries[--self.size]);
}
<*
@require self.size > 0
*>
fn void AnyList.remove_first(&self)
{
self.remove_at(0);
}
macro AnyList.first(&self, $Type)
{
return *anycast(self.first_any(), $Type);
}
fn any! AnyList.first_any(&self) @inline
{
return self.size ? self.entries[0] : IteratorResult.NO_MORE_ELEMENT?;
}
macro AnyList.last(&self, $Type)
{
return *anycast(self.last_any(), $Type);
}
fn any! AnyList.last_any(&self) @inline
{
return self.size ? self.entries[self.size - 1] : IteratorResult.NO_MORE_ELEMENT?;
}
fn bool AnyList.is_empty(&self) @inline
{
return !self.size;
}
fn usz AnyList.len(&self) @operator(len) @inline
{
return self.size;
}
<*
@require index < self.size "Index out of range"
*>
macro AnyList.get(&self, usz index, $Type)
{
return *anycast(self.entries[index], $Type);
}
<*
@require index < self.size "Index out of range"
*>
fn any AnyList.get_any(&self, usz index) @inline
{
return self.entries[index];
}
fn void AnyList.free(&self)
{
if (!self.allocator) return;
self.clear();
allocator::free(self.allocator, self.entries);
self.capacity = 0;
self.entries = null;
}
fn void AnyList.swap(&self, usz i, usz j)
{
any temp = self.entries[i];
self.entries[i] = self.entries[j];
self.entries[j] = temp;
}
<*
@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)
{
return self._remove_if(filter, false);
}
<*
@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)
{
return self._remove_if(selection, true);
}
macro usz AnyList._remove_if(&self, AnyPredicate filter, bool $invert) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && filter(&self.entries[i - 1])) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$endif
}
return size - self.size;
}
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
{
return self._remove_using_test(filter, false, context);
}
fn usz AnyList.retain_using_test(&self, AnyTest filter, any context)
{
return self._remove_using_test(filter, true, context);
}
macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$endif
}
return size - self.size;
}
<*
Reserve at least min_capacity
*>
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();
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;
}
macro any AnyList.@item_at(&self, usz index) @operator([])
{
return self.entries[index];
}
<*
@require index <= self.size "Index out of range"
*>
macro void AnyList.set(&self, usz index, value)
{
if (index == self.size)
{
self.push(value);
return;
}
self.free_element(self.entries[index]);
self.entries[index] = allocator::copy(self.allocator, value);
}
fn void AnyList.ensure_capacity(&self, usz added = 1) @inline @private
{
usz new_size = self.size + added;
if (self.capacity >= new_size) return;
assert(new_size < usz.max / 2U);
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
while (new_capacity < new_size) new_capacity *= 2U;
self.reserve(new_capacity);
}

View File

@@ -1,6 +1,6 @@
/** <*
* @require SIZE > 0 @require SIZE > 0
**/ *>
module std::collections::bitset(<SIZE>); module std::collections::bitset(<SIZE>);
def Type = uint; def Type = uint;
@@ -23,9 +23,9 @@ fn usz BitSet.cardinality(&self)
return n; return n;
} }
/** <*
* @require i < SIZE @require i < SIZE
**/ *>
fn void BitSet.set(&self, usz i) fn void BitSet.set(&self, usz i)
{ {
usz q = i / BITS; usz q = i / BITS;
@@ -33,9 +33,9 @@ fn void BitSet.set(&self, usz i)
self.data[q] |= 1 << r; self.data[q] |= 1 << r;
} }
/** <*
* @require i < SIZE @require i < SIZE
**/ *>
fn void BitSet.unset(&self, usz i) fn void BitSet.unset(&self, usz i)
{ {
usz q = i / BITS; usz q = i / BITS;
@@ -43,9 +43,9 @@ fn void BitSet.unset(&self, usz i)
self.data[q] &= ~(1 << r); self.data[q] &= ~(1 << r);
} }
/** <*
* @require i < SIZE @require i < SIZE
**/ *>
fn bool BitSet.get(&self, usz i) @operator([]) @inline fn bool BitSet.get(&self, usz i) @operator([]) @inline
{ {
usz q = i / BITS; usz q = i / BITS;
@@ -58,18 +58,18 @@ fn usz BitSet.len(&self) @operator(len) @inline
return SZ * BITS; return SZ * BITS;
} }
/** <*
* @require i < SIZE @require i < SIZE
**/ *>
fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
{ {
if (value) return self.set(i); if (value) return self.set(i);
self.unset(i); self.unset(i);
} }
/** <*
* @require Type.kindof == UNSIGNED_INT @require Type.kindof == UNSIGNED_INT
**/ *>
module std::collections::growablebitset(<Type>); module std::collections::growablebitset(<Type>);
import std::collections::list; import std::collections::list;
@@ -82,19 +82,19 @@ struct GrowableBitSet
GrowableBitSetList data; GrowableBitSetList data;
} }
/** <*
* @param initial_capacity @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.init_new(&self, usz initial_capacity = 1, Allocator* allocator = mem::heap()) fn GrowableBitSet* GrowableBitSet.new_init(&self, usz initial_capacity = 1, Allocator allocator = allocator::heap())
{ {
self.data.init_new(initial_capacity, allocator); self.data.new_init(initial_capacity, allocator);
return self; return self;
} }
fn GrowableBitSet* GrowableBitSet.init_temp(&self, usz initial_capacity = 1) fn GrowableBitSet* GrowableBitSet.temp_init(&self, usz initial_capacity = 1)
{ {
return self.init_new(initial_capacity, mem::temp()) @inline; return self.new_init(initial_capacity, allocator::temp()) @inline;
} }
fn void GrowableBitSet.free(&self) fn void GrowableBitSet.free(&self)
@@ -117,15 +117,10 @@ fn void GrowableBitSet.set(&self, usz i)
usz q = i / BITS; usz q = i / BITS;
usz r = i % BITS; usz r = i % BITS;
usz current_len = self.data.len(); usz current_len = self.data.len();
if (q >= current_len) while (q >= current_len)
{ {
usz n = q + 1; self.data.push(0);
self.data.reserve(n); current_len++;
if (n - 1 >= current_len)
{
self.data.entries[current_len .. (n - 1)] = 0;
}
self.data.size = n;
} }
self.data.set(q, self.data[q] | (1 << r)); self.data.set(q, self.data[q] | (1 << r));
} }

View File

@@ -0,0 +1,455 @@
// 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.
<*
@require MAX_SIZE >= 1 `The size must be at least 1 element big.`
*>
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);
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;
struct ElasticArray (Printable)
{
usz size;
Type[MAX_SIZE] entries;
}
fn usz! ElasticArray.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
case 0:
return formatter.print("[]")!;
case 1:
return formatter.printf("[%s]", self.entries[0])!;
default:
usz n = formatter.print("[")!;
foreach (i, element : self.entries[:self.size])
{
if (i != 0) formatter.print(", ")!;
n += formatter.printf("%s", element)!;
}
n += formatter.print("]")!;
return n;
}
}
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
{
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
self.entries[self.size++] = element;
}
<*
@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)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
fn void ElasticArray.clear(&self)
{
self.size = 0;
}
<*
@require self.size > 0
*>
fn Type! ElasticArray.pop_first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
<*
@require index < self.size
*>
fn void ElasticArray.remove_at(&self, usz index)
{
if (!--self.size || index == self.size) return;
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
}
<*
@require other_list.size + self.size <= MAX_SIZE
*>
fn void ElasticArray.add_all(&self, ElasticArray* other_list)
{
if (!other_list.size) return;
foreach (&value : other_list)
{
self.entries[self.size++] = *value;
}
}
<*
Add as many elements as possible to the new array,
returning the number of elements that didn't fit.
*>
fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
{
if (!other_list.size) return 0;
foreach (i, &value : other_list)
{
if (self.size == MAX_SIZE) return other_list.size - i;
self.entries[self.size++] = *value;
}
return 0;
}
<*
Add as many values from this array as possible, returning the
number of elements that didn't fit.
@param [in] array
*>
fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
{
if (!array.len) return 0;
foreach (i, &value : array)
{
if (self.size == MAX_SIZE) return array.len - i;
self.entries[self.size++] = *value;
}
return 0;
}
<*
Add the values of an array to this list.
@param [in] array
@require array.len + self.size <= MAX_SIZE `Size would exceed max.`
@ensure self.size >= array.len
*>
fn void ElasticArray.add_array(&self, Type[] array)
{
if (!array.len) return;
foreach (&value : array)
{
self.entries[self.size++] = *value;
}
}
<*
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());
}
<*
@require !type_is_overaligned() : "This function is not available on overaligned types"
*>
macro Type[] ElasticArray.to_array(&self, Allocator allocator)
{
return list_common::list_to_new_array(Type, self, allocator);
}
fn Type[] ElasticArray.to_tarray(&self)
{
$if type_is_overaligned():
return self.to_aligned_array(allocator::temp());
$else
return self.to_array(allocator::temp());
$endif;
}
<*
Reverse the elements in a list.
*>
fn void ElasticArray.reverse(&self)
{
list_common::list_reverse(self);
}
fn Type[] ElasticArray.array_view(&self)
{
return self.entries[:self.size];
}
<*
@require self.size < MAX_SIZE `List would exceed max size`
*>
fn void ElasticArray.push_front(&self, Type type) @inline
{
self.insert_at(0, type);
}
<*
@require self.size < MAX_SIZE `List would exceed max size`
*>
fn void! ElasticArray.push_front_try(&self, Type type) @inline
{
return self.insert_at_try(0, type);
}
<*
@require index <= self.size
*>
fn void! ElasticArray.insert_at_try(&self, usz index, Type value)
{
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
self.insert_at(index, value);
}
<*
@require self.size < MAX_SIZE `List would exceed max size`
@require index <= self.size
*>
fn void ElasticArray.insert_at(&self, usz index, Type type)
{
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.size++;
self.entries[index] = type;
}
<*
@require index < self.size
*>
fn void ElasticArray.set_at(&self, usz index, Type type)
{
self.entries[index] = type;
}
fn void! ElasticArray.remove_last(&self) @maydiscard
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.size--;
}
fn void! ElasticArray.remove_first(&self) @maydiscard
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.remove_at(0);
}
fn Type! ElasticArray.first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[0];
}
fn Type! ElasticArray.last(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[self.size - 1];
}
fn bool ElasticArray.is_empty(&self) @inline
{
return !self.size;
}
fn usz ElasticArray.byte_size(&self) @inline
{
return Type.sizeof * self.size;
}
fn usz ElasticArray.len(&self) @operator(len) @inline
{
return self.size;
}
fn Type ElasticArray.get(&self, usz index) @inline
{
return self.entries[index];
}
fn void ElasticArray.swap(&self, usz i, usz j)
{
@swap(self.entries[i], self.entries[j]);
}
<*
@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)
{
return list_common::list_remove_if(self, filter, false);
}
<*
@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)
{
return list_common::list_remove_if(self, selection, true);
}
fn usz ElasticArray.remove_using_test(&self, ElementTest filter, any context)
{
return list_common::list_remove_using_test(self, filter, false, context);
}
fn usz ElasticArray.retain_using_test(&self, ElementTest filter, any context)
{
return list_common::list_remove_using_test(self, filter, true, context);
}
macro Type ElasticArray.@item_at(&self, usz index) @operator([])
{
return self.entries[index];
}
fn Type* ElasticArray.get_ref(&self, usz index) @operator(&[]) @inline
{
return &self.entries[index];
}
fn void ElasticArray.set(&self, usz index, Type value) @operator([]=)
{
self.entries[index] = value;
}
// Functions for equatable types
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?;
}
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?;
}
fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE)
{
if (self.size != other_list.size) return false;
foreach (i, v : self)
{
if (!equals(v, other_list.entries[i])) return false;
}
return true;
}
<*
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"
@return "True if the value is found, false otherwise"
*>
fn bool ElasticArray.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
{
if (equals(v, value)) return true;
}
return false;
}
<*
@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)
{
return @ok(self.remove_at(self.rindex_of(value)));
}
<*
@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)
{
return @ok(self.remove_at(self.index_of(value)));
}
<*
@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)
{
return list_common::list_remove_item(self, value);
}
fn void ElasticArray.remove_all_from(&self, ElasticArray* other_list) @if(ELEMENT_IS_EQUATABLE)
{
if (!other_list.size) return;
foreach (v : other_list) self.remove_item(v);
}
<*
@param [&in] self
@return "The number non-null values in the list"
*>
fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER)
{
usz vals = 0;
foreach (v : self) if (v) vals++;
return vals;
}
fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER)
{
return list_common::list_compact(self);
}

View File

@@ -1,5 +1,8 @@
<*
@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) struct EnumMap (Printable)
{ {
ValueType[Enum.len] values; ValueType[Enum.len] values;
@@ -19,15 +22,20 @@ fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
foreach (i, &value : self.values) foreach (i, &value : self.values)
{ {
if (i != 0) formatter.print(", ")!; 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(" }")!; n += formatter.print(" }")!;
return n; return n;
} }
fn String EnumMap.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic fn String EnumMap.to_string(&self, Allocator allocator) @dynamic
{ {
return string::new_format("%s", *self, .allocator = allocator); 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 fn String EnumMap.to_tstring(&self) @dynamic
@@ -35,18 +43,18 @@ fn String EnumMap.to_tstring(&self) @dynamic
return string::tformat("%s", *self); return string::tformat("%s", *self);
} }
/** <*
* @return "The total size of this map, which is the same as the number of enum values" @return "The total size of this map, which is the same as the number of enum values"
* @pure @pure
**/ *>
fn usz EnumMap.len(&self) @operator(len) @inline fn usz EnumMap.len(&self) @operator(len) @inline
{ {
return self.values.len; return self.values.len;
} }
/** <*
* @return "Retrieve a value given the underlying enum, if there is no entry, then the zero value for the value is returned." @return "Retrieve a value given the underlying enum, if there is no entry, then the zero value for the value is returned."
**/ *>
fn ValueType EnumMap.get(&self, Enum key) @operator([]) @inline fn ValueType EnumMap.get(&self, Enum key) @operator([]) @inline
{ {
return self.values[key.ordinal]; return self.values[key.ordinal];

View File

@@ -1,13 +1,14 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license // Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
/** <*
* @require Enum.kindof == TypeKind.ENUM : "Only enums maybe be used with an enumset" @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 ; def EnumSetType = $typefrom(private::type_for_enum_elements(Enum.elements)) @private;
const IS_CHAR_ARRAY = Enum.elements > 128; const IS_CHAR_ARRAY = Enum.elements > 128;
distinct EnumSet (Printable) = EnumSetType; distinct EnumSet (Printable) = EnumSetType;
@@ -15,9 +16,9 @@ distinct EnumSet (Printable) = EnumSetType;
fn void EnumSet.add(&self, Enum v) fn void EnumSet.add(&self, Enum v)
{ {
$if IS_CHAR_ARRAY: $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 $else
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v); *self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v.ordinal);
$endif $endif
} }
@@ -34,11 +35,11 @@ fn bool EnumSet.remove(&self, Enum v)
{ {
$if IS_CHAR_ARRAY: $if IS_CHAR_ARRAY:
if (!self.has(v) @inline) return false; 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; return true;
$else $else
EnumSetType old = (EnumSetType)*self; EnumSetType old = (EnumSetType)*self;
EnumSetType new = old & ~(1u << (EnumSetType)v); EnumSetType new = old & ~(1u << (EnumSetType)v.ordinal);
*self = (EnumSet)new; *self = (EnumSet)new;
return old != new; return old != new;
$endif $endif
@@ -47,9 +48,9 @@ fn bool EnumSet.remove(&self, Enum v)
fn bool EnumSet.has(&self, Enum v) fn bool EnumSet.has(&self, Enum v)
{ {
$if IS_CHAR_ARRAY: $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 $else
return ((EnumSetType)*self & (1u << (EnumSetType)v)) != 0; return ((EnumSetType)*self & (1u << (EnumSetType)v.ordinal)) != 0;
$endif $endif
} }
@@ -140,9 +141,14 @@ fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
return n; return n;
} }
fn String EnumSet.to_new_string(&set, Allocator* allocator = mem::heap()) @dynamic fn String EnumSet.to_new_string(&set, Allocator allocator = allocator::heap()) @dynamic
{ {
return string::new_format("%s", *set, .allocator = allocator); 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 fn String EnumSet.to_tstring(&set) @dynamic

View File

@@ -0,0 +1,613 @@
// 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.
<*
@require $defined(Key{}.hash()) `No .hash function found on the key`
*>
module std::collections::map(<Key, Value>);
import std::math;
import std::io @norecurse;
struct HashMap (Printable)
{
Entry*[] table;
Allocator allocator;
uint count; // Number of elements
uint threshold; // Resize limit
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"
*>
fn HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
self.load_factor = load_factor;
self.threshold = (uint)(capacity * load_factor);
self.table = allocator::new_array(allocator, Entry*, capacity);
return self;
}
<*
@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.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(allocator::temp(), 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"
*>
macro HashMap* HashMap.new_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
self.new_init(capacity, load_factor, allocator);
$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"
*>
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())
{
assert(keys.len == values.len);
self.new_init(capacity, load_factor, allocator);
for (usz i = 0; i < keys.len; i++)
{
self.set(keys[i], values[i]);
}
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"
*>
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())
{
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;
}
<*
Has this hash map been initialized yet?
@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;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn HashMap* HashMap.new_init_from_map(&self, 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.put_all_for_create(other_map);
return self;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map)
{
return map.init_from_map(other_map, allocator::temp()) @inline;
}
fn bool HashMap.is_empty(&map) @inline
{
return !map.count;
}
fn usz HashMap.len(&map) @inline
{
return map.count;
}
fn Value*! HashMap.get_ref(&map, Key key)
{
if (!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*! HashMap.get_entry(&map, Key key)
{
if (!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;
}
return SearchResult.MISSING?;
}
<*
Get the value or update and
@require $assignable(#expr, Value)
*>
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
{
if (!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! HashMap.get(&map, Key key) @operator([])
{
return *map.get_ref(key) @inline;
}
fn bool HashMap.has_key(&map, Key key)
{
return @ok(map.get_ref(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();
}
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! HashMap.remove(&map, Key key) @maydiscard
{
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
}
fn void HashMap.clear(&map)
{
if (!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 HashMap.free(&map)
{
if (!map.allocator) return;
map.clear();
map.free_internal(map.table.ptr);
map.table = {};
}
fn Key[] HashMap.tcopy_keys(&map)
{
return map.copy_keys(allocator::temp()) @inline;
}
fn Key[] HashMap.key_tlist(&map) @deprecated("Use 'tcopy_keys'")
{
return map.copy_keys(allocator::temp()) @inline;
}
<*
@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);
usz index = 0;
foreach (Entry* entry : map.table)
{
while (entry)
{
$if COPY_KEYS:
list[index++] = entry.key.copy(allocator);
$else
list[index++] = entry.key;
$endif
entry = entry.next;
}
}
return list;
}
macro HashMap.@each(map; @body(key, value))
{
map.@each_entry(; Entry* entry) {
@body(entry.key, entry.value);
};
}
macro HashMap.@each_entry(map; @body(entry))
{
if (map.count)
{
foreach (Entry* entry : map.table)
{
while (entry)
{
@body(entry);
entry = entry.next;
}
}
}
}
<*
@deprecated `use tcopy_values`
*>
fn Value[] HashMap.value_tlist(&map)
{
return map.copy_values(allocator::temp()) @inline;
}
fn Value[] HashMap.tcopy_values(&map)
{
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);
usz index = 0;
foreach (Entry* entry : map.table)
{
while (entry)
{
list[index++] = entry.value;
entry = entry.next;
}
}
return list;
}
fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
{
if (!map.count) return false;
foreach (Entry* entry : map.table)
{
while (entry)
{
if (equals(v, entry.value)) return true;
entry = entry.next;
}
}
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
{
$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 HashMap.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 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;
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 HashMap.put_all_for_create(&map, HashMap* other_map) @private
{
if (!other_map.count) return;
foreach (Entry *e : other_map.table)
{
while (e)
{
map.put_for_create(e.key, e.value);
e = e.next;
}
}
}
fn void HashMap.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 HashMap.free_internal(&map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
fn bool HashMap.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 HashMap.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 HashMap.free_entry(&self, Entry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
$endif
self.free_internal(entry);
}
struct HashMapIterator
{
HashMap* map;
int top_index;
int index;
Entry* current_entry;
}
distinct HashMapValueIterator = HashMapIterator;
distinct 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;

View File

@@ -1,8 +1,10 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license // Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // 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);
struct Node @private struct Node @private
{ {
Node *next; Node *next;
@@ -12,51 +14,51 @@ struct Node @private
struct LinkedList struct LinkedList
{ {
Allocator *allocator; Allocator allocator;
usz size; usz size;
Node *_first; Node *_first;
Node *_last; Node *_last;
} }
fn void LinkedList.push(&self, Type value) <*
{ @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
self.link_first(value); @return "the initialized list"
} *>
fn LinkedList* LinkedList.init(&self, Allocator allocator)
fn void LinkedList.push_last(&self, Type value)
{
self.link_last(value);
}
/**
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
* @return "the initialized list"
**/
fn LinkedList* LinkedList.init_new(&self, Allocator* allocator = mem::heap())
{ {
*self = { .allocator = allocator }; *self = { .allocator = allocator };
return self; return self;
} }
fn LinkedList* LinkedList.init_temp(&self) <*
@return "the initialized list"
*>
fn LinkedList* LinkedList.new_init(&self)
{ {
return self.init_new(mem::temp()) @inline; return self.init(allocator::heap()) @inline;
} }
/**
* @require self.allocator fn LinkedList* LinkedList.temp_init(&self)
**/ {
return self.init(allocator::temp()) @inline;
}
<*
@require self.allocator != null
*>
macro void LinkedList.free_node(&self, Node* node) @private macro void LinkedList.free_node(&self, Node* node) @private
{ {
self.allocator.free(node); allocator::free(self.allocator, node);
}
macro Node* LinkedList.alloc_node(&self) @private
{
if (!self.allocator) self.allocator = mem::heap();
return self.allocator.new(Node);
} }
fn void LinkedList.link_first(&self, Type value) @private macro Node* LinkedList.alloc_node(&self) @private
{
if (!self.allocator) self.allocator = allocator::heap();
return allocator::alloc(self.allocator, Node);
}
fn void LinkedList.push_front(&self, Type value)
{ {
Node *first = self._first; Node *first = self._first;
Node *new_node = self.alloc_node(); Node *new_node = self.alloc_node();
@@ -73,7 +75,7 @@ fn void LinkedList.link_first(&self, Type value) @private
self.size++; self.size++;
} }
fn void LinkedList.link_last(&self, Type value) @private fn void LinkedList.push(&self, Type value)
{ {
Node *last = self._last; Node *last = self._last;
Node *new_node = self.alloc_node(); Node *new_node = self.alloc_node();
@@ -122,9 +124,9 @@ fn void LinkedList.clear(&self)
fn usz LinkedList.len(&self) @inline => self.size; fn usz LinkedList.len(&self) @inline => self.size;
/** <*
* @require index < self.size @require index < self.size
**/ *>
macro Node* LinkedList.node_at_index(&self, usz index) macro Node* LinkedList.node_at_index(&self, usz index)
{ {
if (index * 2 >= self.size) if (index * 2 >= self.size)
@@ -138,48 +140,48 @@ macro Node* LinkedList.node_at_index(&self, usz index)
while (index--) node = node.next; while (index--) node = node.next;
return node; return node;
} }
/** <*
* @require index < self.size @require index < self.size
**/ *>
fn Type LinkedList.get(&self, usz index) fn Type LinkedList.get(&self, usz index)
{ {
return self.node_at_index(index).value; return self.node_at_index(index).value;
} }
/** <*
* @require index < self.size @require index < self.size
**/ *>
fn void LinkedList.set(&self, usz index, Type element) fn void LinkedList.set(&self, usz index, Type element)
{ {
self.node_at_index(index).value = element; self.node_at_index(index).value = element;
} }
/** <*
* @require index < self.size @require index < self.size
**/ *>
fn void LinkedList.remove(&self, usz index) fn void LinkedList.remove_at(&self, usz index)
{ {
self.unlink(self.node_at_index(index)); self.unlink(self.node_at_index(index));
} }
/** <*
* @require index <= self.size @require index <= self.size
**/ *>
fn void LinkedList.insert(&self, usz index, Type element) fn void LinkedList.insert_at(&self, usz index, Type element)
{ {
switch (index) switch (index)
{ {
case 0: case 0:
self.push(element); self.push_front(element);
case self.size: case self.size:
self.push_last(element); self.push(element);
default: default:
self.link_before(self.node_at_index(index), element); self.link_before(self.node_at_index(index), element);
} }
} }
/** <*
* @require succ != null @require succ != null
**/ *>
fn void LinkedList.link_before(&self, Node *succ, Type value) @private fn void LinkedList.link_before(&self, Node *succ, Type value) @private
{ {
Node* pred = succ.prev; Node* pred = succ.prev;
@@ -197,9 +199,9 @@ fn void LinkedList.link_before(&self, Node *succ, Type value) @private
self.size++; self.size++;
} }
/** <*
* @require self._first @require self._first != null
**/ *>
fn void LinkedList.unlink_first(&self) @private fn void LinkedList.unlink_first(&self) @private
{ {
Node* f = self._first; Node* f = self._first;
@@ -217,7 +219,58 @@ fn void LinkedList.unlink_first(&self) @private
self.size--; self.size--;
} }
fn bool LinkedList.remove_value(&self, Type t) fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
usz start = self.size;
Node* node = self._first;
while (node)
{
switch
{
case equals(node.value, t):
Node* next = node.next;
self.unlink(node);
node = next;
default:
node = node.next;
}
}
return start - self.size;
}
fn Type! LinkedList.pop(&self)
{
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
defer self.unlink_last();
return self._last.value;
}
fn bool LinkedList.is_empty(&self)
{
return !self._first;
}
fn Type! LinkedList.pop_front(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
defer self.unlink_first();
return self._first.value;
}
fn void! LinkedList.remove_last(&self) @maydiscard
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
self.unlink_last();
}
fn void! LinkedList.remove_first(&self) @maydiscard
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
self.unlink_first();
}
fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{ {
for (Node* node = self._first; node != null; node = node.next) for (Node* node = self._first; node != null; node = node.next)
{ {
@@ -230,7 +283,7 @@ fn bool LinkedList.remove_value(&self, Type t)
return false; return false;
} }
fn bool LinkedList.remove_last_value(&self, Type t) fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{ {
for (Node* node = self._last; node != null; node = node.prev) for (Node* node = self._last; node != null; node = node.prev)
{ {
@@ -242,29 +295,9 @@ fn bool LinkedList.remove_last_value(&self, Type t)
} }
return false; return false;
} }
<*
fn Type! LinkedList.pop(&self) @require self._last != null
{ *>
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
defer self.unlink_first();
return self._first.value;
}
fn void! LinkedList.remove_last(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
self.unlink_last();
}
fn void! LinkedList.remove_first(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
self.unlink_first();
}
/**
* @require self._last
**/
fn void LinkedList.unlink_last(&self) @inline @private fn void LinkedList.unlink_last(&self) @inline @private
{ {
Node* l = self._last; Node* l = self._last;
@@ -282,9 +315,9 @@ fn void LinkedList.unlink_last(&self) @inline @private
self.size--; self.size--;
} }
/** <*
* @require x != null @require x != null
**/ *>
fn void LinkedList.unlink(&self, Node* x) @private fn void LinkedList.unlink(&self, Node* x) @private
{ {
Node* next = x.next; Node* next = x.next;

View File

@@ -1,63 +1,97 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license // Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // 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; import std::io, std::math, std::collections::list_common;
import std::math;
def ElementPredicate = fn bool(Type *type); def ElementPredicate = fn bool(Type *type);
def ElementTest = fn bool(Type *type, any* context); def ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type); const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER; const ELEMENT_IS_POINTER = Type.kindof == POINTER;
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
struct List (Printable) struct List (Printable)
{ {
usz size; usz size;
usz capacity; usz capacity;
Allocator *allocator; Allocator allocator;
Type *entries; Type *entries;
} }
/** <*
* @param initial_capacity "The initial capacity to reserve" @param initial_capacity "The initial capacity to reserve"
* @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 List* List.init_new(&self, usz initial_capacity = 16, Allocator* allocator = mem::heap()) fn List* List.init(&self, usz initial_capacity = 16, Allocator allocator)
{ {
self.allocator = allocator; self.allocator = allocator;
self.size = 0; self.size = 0;
if (initial_capacity > 0) self.capacity = 0;
{ self.entries = null;
initial_capacity = math::next_power_of_2(initial_capacity); self.reserve(initial_capacity);
self.entries = allocator.alloc_aligned(Type.sizeof * initial_capacity, .alignment = Type[1].alignof)!!;
}
else
{
self.entries = null;
}
self.capacity = initial_capacity;
return self; return self;
} }
/** <*
* Initialize the list using the temp allocator. @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" *>
**/ fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
fn List* List.init_temp(&self, usz initial_capacity = 16) {
{ self.allocator = allocator;
return self.init_new(initial_capacity, mem::temp()) @inline; self.size = 0;
} self.capacity = 0;
self.entries = null;
/** self.reserve(initial_capacity);
* @require self.size == 0 "The List must be empty" return self;
**/ }
fn void List.init_wrapping_array(&self, Type[] types, Allocator* allocator = mem::heap())
<*
Initialize the list using the temp allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn List* List.temp_init(&self, usz initial_capacity = 16)
{
return self.init(initial_capacity, allocator::temp()) @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"
*>
fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = allocator::heap())
{
self.new_init(values.len, allocator) @inline;
self.add_array(values) @inline;
return self;
}
<*
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"
*>
fn List* List.temp_init_with_array(&self, Type[] values)
{
self.temp_init(values.len) @inline;
self.add_array(values) @inline;
return self;
}
<*
@require self.capacity == 0 "The List must not be allocated"
*>
fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allocator::heap())
{ {
self.allocator = allocator; self.allocator = allocator;
self.size = types.len;
self.capacity = types.len; self.capacity = types.len;
self.entries = types.ptr; self.entries = types.ptr;
self.set_size(types.len);
} }
fn usz! List.to_format(&self, Formatter* formatter) @dynamic fn usz! List.to_format(&self, Formatter* formatter) @dynamic
@@ -80,9 +114,9 @@ fn usz! List.to_format(&self, Formatter* formatter) @dynamic
} }
} }
fn String List.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic fn String List.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{ {
return string::new_format("%s", *self, .allocator = allocator); return string::format("%s", *self, allocator: allocator);
} }
fn String List.to_tstring(&self) fn String List.to_tstring(&self)
@@ -92,86 +126,82 @@ fn String List.to_tstring(&self)
fn void List.push(&self, Type element) @inline fn void List.push(&self, Type element) @inline
{ {
self.append(element); self.reserve(1);
self.entries[self.set_size(self.size + 1)] = element;
} }
fn void List.append(&self, Type element) fn Type! List.pop(&self)
{ {
self.ensure_capacity(); if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.entries[self.size++] = element; defer self.set_size(self.size - 1);
} return self.entries[self.size - 1];
/**
* @require self.size > 0
**/
fn Type List.pop(&self)
{
return self.entries[--self.size];
} }
fn void List.clear(&self) fn void List.clear(&self)
{ {
self.size = 0; self.set_size(0);
} }
/** fn Type! List.pop_first(&self)
* @require self.size > 0
**/
fn Type List.pop_first(&self)
{ {
Type value = self.entries[0]; if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.remove_at(0); defer self.remove_at(0);
return value; return self.entries[0];
} }
/** <*
* @require index < self.size @require index < self.size `Removed element out of bounds`
**/ *>
fn void List.remove_at(&self, usz index) fn void List.remove_at(&self, usz index)
{ {
for (usz i = index + 1; i < self.size; i++) self.set_size(self.size - 1);
{ if (!self.size || index == self.size) return;
self.entries[i - 1] = self.entries[i]; self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
}
self.size--;
} }
fn void List.add_all(&self, List* other_list) fn void List.add_all(&self, List* other_list)
{ {
if (!other_list.size) return; if (!other_list.size) return;
self.reserve(other_list.size); self.reserve(other_list.size);
usz index = self.set_size(self.size + other_list.size);
foreach (&value : other_list) foreach (&value : other_list)
{ {
self.entries[self.size++] = *value; self.entries[index++] = *value;
} }
} }
fn Type[] List.to_new_array(&self, Allocator* allocator = mem::heap()) <*
IMPORTANT The returned array must be freed using free_aligned.
*>
fn Type[] List.to_new_aligned_array(&self, Allocator allocator = allocator::heap())
{ {
if (!self.size) return Type[] {}; return list_common::list_to_new_aligned_array(Type, self, allocator);
Type[] result = allocator.new_array(Type, self.size); }
result[..] = self.entries[:self.size];
return result; <*
@require !type_is_overaligned() : "This function is not available on overaligned types"
*>
macro Type[] List.to_new_array(&self, Allocator allocator = allocator::heap())
{
return list_common::list_to_new_array(Type, self, allocator);
} }
fn Type[] List.to_tarray(&self) fn Type[] List.to_tarray(&self)
{ {
return self.to_new_array(mem::temp()); $if type_is_overaligned():
return self.to_new_aligned_array(allocator::temp());
$else
return self.to_new_array(allocator::temp());
$endif;
} }
/** <*
* Reverse the elements in a list. Reverse the elements in a list.
**/ *>
fn void List.reverse(&self) fn void List.reverse(&self)
{ {
if (self.size < 2) return; list_common::list_reverse(self);
usz half = self.size / 2U;
usz end = self.size - 1;
for (usz i = 0; i < half; i++)
{
@swap(self.entries[i], self.entries[end - i]);
}
} }
fn Type[] List.array_view(&self) fn Type[] List.array_view(&self)
@@ -179,14 +209,18 @@ fn Type[] List.array_view(&self)
return self.entries[:self.size]; return self.entries[:self.size];
} }
<*
Add the values of an array to this list.
@param [in] array
@ensure self.size >= array.len
*>
fn void List.add_array(&self, Type[] array) fn void List.add_array(&self, Type[] array)
{ {
if (!array.len) return; if (!array.len) return;
self.reserve(array.len); self.reserve(array.len);
foreach (&value : array) usz index = self.set_size(self.size + array.len);
{ self.entries[index : array.len] = array[..];
self.entries[self.size++] = *value;
}
} }
fn void List.push_front(&self, Type type) @inline fn void List.push_front(&self, Type type) @inline
@@ -194,52 +228,50 @@ fn void List.push_front(&self, Type type) @inline
self.insert_at(0, type); self.insert_at(0, type);
} }
/** <*
* @require index < self.size @require index <= self.size `Insert was out of bounds`
**/ *>
fn void List.insert_at(&self, usz index, Type type) fn void List.insert_at(&self, usz index, Type type)
{ {
self.ensure_capacity(); 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.entries[i] = self.entries[i - 1];
} }
self.size++;
self.entries[index] = type; self.entries[index] = type;
} }
/** <*
* @require index < self.size @require index < self.size
**/ *>
fn void List.set_at(&self, usz index, Type type) fn void List.set_at(&self, usz index, Type type)
{ {
self.entries[index] = type; self.entries[index] = type;
} }
/** fn void! List.remove_last(&self) @maydiscard
* @require self.size > 0
**/
fn void List.remove_last(&self)
{ {
self.size--; if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.set_size(self.size - 1);
} }
/** fn void! List.remove_first(&self) @maydiscard
* @require self.size > 0
**/
fn void List.remove_first(&self)
{ {
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.remove_at(0); self.remove_at(0);
} }
fn Type* List.first(&self) fn Type! List.first(&self)
{ {
return self.size ? &self.entries[0] : null; if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[0];
} }
fn Type* List.last(&self) fn Type! List.last(&self)
{ {
return self.size ? &self.entries[self.size - 1] : null; if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[self.size - 1];
} }
fn bool List.is_empty(&self) @inline fn bool List.is_empty(&self) @inline
@@ -247,11 +279,19 @@ fn bool List.is_empty(&self) @inline
return !self.size; return !self.size;
} }
fn usz List.byte_size(&self) @inline
{
return Type.sizeof * self.size;
}
fn usz List.len(&self) @operator(len) @inline fn usz List.len(&self) @operator(len) @inline
{ {
return self.size; return self.size;
} }
<*
@require index < self.size `Access out of bounds`
*>
fn Type List.get(&self, usz index) @inline fn Type List.get(&self, usz index) @inline
{ {
return self.entries[index]; return self.entries[index];
@@ -259,125 +299,111 @@ fn Type List.get(&self, usz index) @inline
fn void List.free(&self) fn void List.free(&self)
{ {
if (!self.allocator) return; if (!self.allocator || !self.capacity) return;
self.allocator.free_aligned(self.entries);
self.pre_free(); // Remove sanitizer annotation
$if type_is_overaligned():
allocator::free_aligned(self.allocator, self.entries);
$else
allocator::free(self.allocator, self.entries);
$endif;
self.capacity = 0; self.capacity = 0;
self.size = 0; self.size = 0;
self.entries = null; self.entries = null;
} }
<*
@require i < self.size && j < self.size `Access out of bounds`
*>
fn void List.swap(&self, usz i, usz j) fn void List.swap(&self, usz i, usz j)
{ {
@swap(self.entries[i], self.entries[j]); @swap(self.entries[i], self.entries[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" @return "the number of deleted elements"
**/ *>
fn usz List.remove_if(&self, ElementPredicate filter) fn usz List.remove_if(&self, ElementPredicate filter)
{ {
return self._remove_if(filter, false); return list_common::list_remove_if(self, filter, false);
} }
/** <*
* @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" @return "the number of deleted elements"
**/ *>
fn usz List.retain_if(&self, ElementPredicate selection) fn usz List.retain_if(&self, ElementPredicate selection)
{ {
return self._remove_if(selection, true); return list_common::list_remove_if(self, selection, true);
} }
macro usz List._remove_if(&self, ElementPredicate filter, bool $invert) @local fn usz List.remove_using_test(&self, ElementTest filter, any context)
{ {
usz size = self.size; usz old_size = self.size;
for (usz i = size, usz k = size; k > 0; k = i) defer
{ {
// Find last index of item to be deleted. if (old_size != self.size) self._update_size_change(old_size, self.size);
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && filter(&self.entries[i - 1])) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$endif
} }
return size - self.size; return list_common::list_remove_using_test(self, filter, false, context);
} }
fn usz List.remove_using_test(&self, ElementTest filter, any* context)
{
return self._remove_using_test(filter, false, context);
}
fn usz List.retain_using_test(&self, ElementTest filter, any* context)
{
return self._remove_using_test(filter, true, context);
}
macro usz List._remove_using_test(&self, ElementTest filter, bool $invert, ctx) @local fn usz List.retain_using_test(&self, ElementTest filter, any context)
{ {
usz size = self.size; usz old_size = self.size;
for (usz i = size, usz k = size; k > 0; k = i) defer {
{ if (old_size != self.size) self._update_size_change(old_size, self.size);
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$endif
} }
return size - self.size; return list_common::list_remove_using_test(self, filter, true, context);
} }
/** fn void List.ensure_capacity(&self, usz min_capacity) @local
* Reserve at least min_capacity
**/
fn void List.reserve(&self, usz min_capacity)
{ {
if (!min_capacity) return; if (!min_capacity) return;
if (self.capacity >= min_capacity) return; if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = mem::heap(); if (!self.allocator) self.allocator = allocator::heap();
self.pre_free(); // Remove sanitizer annotation
min_capacity = math::next_power_of_2(min_capacity); min_capacity = math::next_power_of_2(min_capacity);
self.entries = self.allocator.realloc_aligned(self.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof) ?? null; $if type_is_overaligned():
self.entries = allocator::realloc_aligned(self.allocator, self.entries, Type.sizeof * min_capacity, alignment: Type[1].alignof)!!;
$else
self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity);
$endif;
self.capacity = min_capacity; self.capacity = min_capacity;
self.post_alloc(); // Add sanitizer annotation
} }
<*
@require index < self.size `Access out of bounds`
*>
macro Type List.@item_at(&self, usz index) @operator([]) macro Type List.@item_at(&self, usz index) @operator([])
{ {
return self.entries[index]; return self.entries[index];
} }
<*
@require index < self.size `Access out of bounds`
*>
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
{ {
return &self.entries[index]; return &self.entries[index];
} }
<*
@require index < self.size `Access out of bounds`
*>
fn void List.set(&self, usz index, Type value) @operator([]=) fn void List.set(&self, usz index, Type value) @operator([]=)
{ {
self.entries[index] = value; self.entries[index] = value;
} }
fn void List.ensure_capacity(&self, usz added = 1) @inline @private fn void List.reserve(&self, usz added)
{ {
usz new_size = self.size + added; usz new_size = self.size + added;
if (self.capacity >= new_size) return; if (self.capacity >= new_size) return;
@@ -385,7 +411,40 @@ fn void List.ensure_capacity(&self, usz added = 1) @inline @private
assert(new_size < usz.max / 2U); assert(new_size < usz.max / 2U);
usz new_capacity = self.capacity ? 2U * self.capacity : 16U; usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
while (new_capacity < new_size) new_capacity *= 2U; while (new_capacity < new_size) new_capacity *= 2U;
self.reserve(new_capacity); self.ensure_capacity(new_capacity);
}
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,
&self.entries[self.capacity],
&self.entries[old_size],
&self.entries[new_size]);
}
<*
@require new_size == 0 || self.capacity != 0
*>
fn usz List.set_size(&self, usz new_size) @inline @private
{
usz old_size = self.size;
self._update_size_change(old_size, new_size);
self.size = new_size;
return old_size;
}
macro void List.pre_free(&self) @private
{
if (!self.capacity) return;
self._update_size_change(self.size, self.capacity);
}
<*
@require self.capacity > 0
*>
macro void List.post_alloc(&self) @private
{
self._update_size_change(self.capacity, self.size);
} }
// Functions for equatable types // Functions for equatable types
@@ -419,13 +478,13 @@ fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
return true; return true;
} }
/** <*
* Check for presence of a value in a list. Check for presence of a value in a list.
*
* @param [&in] self "the list to find elements in" @param [&in] self "the list to find elements in"
* @param value "The value to search for" @param value "The value to search for"
* @return "True if the value is found, false otherwise" @return "True if the value is found, false otherwise"
**/ *>
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE) fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{ {
foreach (i, v : self) foreach (i, v : self)
@@ -435,37 +494,55 @@ fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
return false; return false;
} }
<*
/** @param [&inout] self "The list to remove elements from"
* @param [&inout] self "The list to remove elements from" @param value "The value to remove"
* @param value "The value to remove" @return "true if the value was found"
* @return "the number of deleted elements." *>
**/ fn bool List.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
fn usz List.remove(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{ {
usz size = self.size; return @ok(self.remove_at(self.rindex_of(value)));
for (usz i = size; i > 0; i--)
{
if (!equals(self.entries[i - 1], value)) continue;
for (usz j = i; j < size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return size - self.size;
} }
fn void List.remove_all(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE) <*
@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)
{
return @ok(self.remove_at(self.index_of(value)));
}
<*
@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)
{
usz old_size = self.size;
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
}
return list_common::list_remove_item(self, value);
}
fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
{ {
if (!other_list.size) return; if (!other_list.size) return;
foreach (v : other_list) self.remove(v); usz old_size = self.size;
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
}
foreach (v : other_list) self.remove_item(v);
} }
/** <*
* @param [&in] self @param [&in] self
* @return "The number non-null values in the list" @return "The number non-null values in the list"
**/ *>
fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER) fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
{ {
usz vals = 0; usz vals = 0;
@@ -475,15 +552,42 @@ fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
fn usz List.compact(&self) @if(ELEMENT_IS_POINTER) fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
{ {
usz size = self.size; usz old_size = self.size;
for (usz i = size; i > 0; i--) defer {
{ if (old_size != self.size) self._update_size_change(old_size, self.size);
if (self.entries[i - 1]) continue;
for (usz j = i; j < size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
} }
return size - self.size; 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;
} }

View File

@@ -0,0 +1,112 @@
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)
{
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)
{
if (!self.size) return $Type[] {};
$Type[] result = allocator::alloc_array(allocator, $Type, self.size);
result[..] = self.entries[:self.size];
return result;
}
macro void list_reverse(self)
{
if (self.size < 2) return;
usz half = self.size / 2U;
usz end = self.size - 1;
for (usz i = 0; i < half; i++)
{
@swap(self.entries[i], self.entries[end - i]);
}
}
macro usz list_remove_using_test(self, filter, bool $invert, ctx)
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$endif
}
return size - self.size;
}
macro usz list_compact(self)
{
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (self.entries[i - 1]) continue;
for (usz j = i; j < size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return size - self.size;
}
macro usz list_remove_item(self, value)
{
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (!equals(self.entries[i - 1], value)) continue;
for (usz j = i; j < self.size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return size - self.size;
}
macro usz list_remove_if(self, filter, bool $invert)
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && filter(&self.entries[i - 1])) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$endif
}
return size - self.size;
}

View File

@@ -10,86 +10,158 @@ const float DEFAULT_LOAD_FACTOR = 0.75;
const VALUE_IS_EQUATABLE = Value.is_eq; const VALUE_IS_EQUATABLE = Value.is_eq;
const bool COPY_KEYS = types::implements_copy(Key); const bool COPY_KEYS = types::implements_copy(Key);
struct HashMap distinct Map = void*;
struct MapImpl
{ {
Entry*[] table; Entry*[] table;
Allocator* allocator; Allocator allocator;
uint count; // Number of elements uint count; // Number of elements
uint threshold; // Resize limit uint threshold; // Resize limit
float load_factor; float load_factor;
} }
/** <*
* @param [&inout] allocator "The allocator to use" @require capacity > 0 "The capacity must be 1 or higher"
* @require capacity > 0 "The capacity must be 1 or higher" @require load_factor > 0.0 "The load factor must be higher than 0"
* @require load_factor > 0.0 "The load factor must be higher than 0" @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
* @require !map.allocator "Map was already initialized" *>
* @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())
**/
fn HashMap* HashMap.init_new(&map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator* allocator = mem::heap())
{ {
capacity = math::next_power_of_2(capacity); MapImpl* map = allocator::alloc(allocator, MapImpl);
map.allocator = allocator; _init(map, capacity, load_factor, allocator);
map.load_factor = load_factor; return (Map)map;
map.threshold = (uint)(capacity * load_factor); }
map.table = allocator.new_zero_array(Entry*, capacity);
<*
@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; return map;
} }
/** <*
* @require capacity > 0 "The capacity must be 1 or higher" @param [in] keys "Array of keys for the Map entries"
* @require load_factor > 0.0 "The load factor must be higher than 0" @param [in] values "Array of values for the Map entries"
* @require !map.allocator "Map was already initialized" @param [&inout] allocator "The allocator to use"
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum" @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"
fn HashMap* HashMap.init_temp(&map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) @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())
{ {
return map.init_new(capacity, load_factor, mem::temp()); 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;
} }
/** <*
* Has this hash map been initialized yet? @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"
* @param [&in] map "The hash map we are testing" @require load_factor > 0.0 "The load factor must be higher than 0"
* @return "Returns true if it has been initialized, false otherwise" @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
**/ *>
fn bool HashMap.is_initialized(&map) macro Map temp_new_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{ {
return (bool)map.allocator; 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 [&inout] allocator "The allocator to use" @param [in] keys "The keys for the HashMap entries"
* @param [&in] other_map "The map to copy from." @param [in] values "The values for the HashMap entries"
**/ @param [&inout] allocator "The allocator to use"
fn HashMap* HashMap.init_new_from_map(&self, HashMap* other_map, Allocator* allocator = mem::heap()) @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())
{ {
self.init_new(other_map.table.len, other_map.load_factor, allocator); assert(keys.len == values.len);
self.put_all_for_create(other_map); Map map = temp(capacity, load_factor);
return self; 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." @param [&in] other_map "The map to copy from."
**/ *>
fn HashMap* HashMap.init_temp_from_map(&map, HashMap* other_map) fn Map new_from_map(Map other_map, Allocator allocator = null)
{ {
return map.init_new_from_map(other_map, mem::temp()) @inline; 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)
{
while (e)
{
map._put_for_create(e.key, e.value);
e = e.next;
}
}
return (Map)map;
} }
fn bool HashMap.is_empty(&map) @inline <*
@param [&in] other_map "The map to copy from."
*>
fn Map temp_from_map(Map other_map)
{ {
return !map.count; return new_from_map(other_map, allocator::temp());
} }
fn usz HashMap.len(&map) @inline fn bool Map.is_empty(map) @inline
{ {
return map.count; return !map || !((MapImpl*)map).count;
} }
fn Value*! HashMap.get_ref(&map, Key key) fn usz Map.len(map) @inline
{ {
if (!map.count) return SearchResult.MISSING?; 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()); uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next) for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{ {
@@ -98,23 +170,26 @@ fn Value*! HashMap.get_ref(&map, Key key)
return SearchResult.MISSING?; return SearchResult.MISSING?;
} }
fn Entry*! HashMap.get_entry(&map, Key key) fn Entry*! Map.get_entry(map, Key key)
{ {
if (!map.count) return SearchResult.MISSING?; MapImpl *map_impl = (MapImpl*)map;
if (!map_impl || !map_impl.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next) 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; if (e.hash == hash && equals(key, e.key)) return e;
} }
return SearchResult.MISSING?; return SearchResult.MISSING?;
} }
/** <*
* Get the value or update and Get the value or update and
**/ @require $assignable(#expr, Value)
macro Value HashMap.@get_or_set(&map, Key key, Value #expr) *>
macro Value Map.@get_or_set(&self, Key key, Value #expr)
{ {
if (!map.count) MapImpl *map = (MapImpl*)*self;
if (!map || !map.count)
{ {
Value val = #expr; Value val = #expr;
map.set(key, val); map.set(key, val);
@@ -131,23 +206,27 @@ macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
return val; return val;
} }
fn Value! HashMap.get(&map, Key key) @operator([]) fn Value! Map.get(map, Key key) @operator([])
{ {
return *map.get_ref(key) @inline; return *map.get_ref(key) @inline;
} }
fn bool HashMap.has_key(&map, Key key) fn bool Map.has_key(map, Key key)
{ {
return @ok(map.get_ref(key)); return @ok(map.get_ref(key));
} }
fn bool HashMap.set(&map, Key key, Value value) @operator([]=) 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 the map isn't initialized, use the defaults to initialize it.
if (!map.allocator) if (!*self) *self = new();
{ MapImpl* map = (MapImpl*)*self;
map.init_new();
}
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len); uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next) for (Entry *e = map.table[index]; e != null; e = e.next)
@@ -158,18 +237,19 @@ fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
return true; return true;
} }
} }
map.add_entry(hash, key, value, index); map._add_entry(hash, key, value, index);
return false; return false;
} }
fn void! HashMap.remove(&map, Key key) @maydiscard fn void! Map.remove(map, Key key) @maydiscard
{ {
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?; if (!map || !((MapImpl*)map)._remove_entry_for_key(key)) return SearchResult.MISSING?;
} }
fn void HashMap.clear(&map) fn void Map.clear(self)
{ {
if (!map.count) return; MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return;
foreach (Entry** &entry_ref : map.table) foreach (Entry** &entry_ref : map.table)
{ {
Entry* entry = *entry_ref; Entry* entry = *entry_ref;
@@ -179,32 +259,35 @@ fn void HashMap.clear(&map)
{ {
Entry *to_delete = next; Entry *to_delete = next;
next = next.next; next = next.next;
map.free_entry(to_delete); map._free_entry(to_delete);
} }
map.free_entry(entry); map._free_entry(entry);
*entry_ref = null; *entry_ref = null;
} }
map.count = 0; map.count = 0;
} }
fn void HashMap.free(&map) fn void Map.free(self)
{ {
if (!map.allocator) return; if (!self) return;
map.clear(); MapImpl* map = (MapImpl*)self;
map.free_internal(map.table.ptr); self.clear();
map._free_internal(map.table.ptr);
map.table = {}; map.table = {};
allocator::free(map.allocator, map);
} }
fn Key[] HashMap.key_tlist(&map) fn Key[] Map.temp_keys_list(map)
{ {
return map.key_new_list(mem::temp()) @inline; return map.new_keys_list(allocator::temp()) @inline;
} }
fn Key[] HashMap.key_new_list(&map, Allocator* allocator = mem::heap()) fn Key[] Map.new_keys_list(self, Allocator allocator = allocator::heap())
{ {
if (!map.count) return {}; MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return {};
Key[] list = allocator.new_array(Key, map.count); Key[] list = allocator::alloc_array(allocator, Key, map.count);
usz index = 0; usz index = 0;
foreach (Entry* entry : map.table) foreach (Entry* entry : map.table)
{ {
@@ -217,37 +300,37 @@ fn Key[] HashMap.key_new_list(&map, Allocator* allocator = mem::heap())
return list; return list;
} }
macro HashMap.@each(map; @body(key, value)) macro Map.@each(map; @body(key, value))
{ {
map.@each_entry(; Entry* entry) { map.@each_entry(; Entry* entry) {
@body(entry.key, entry.value); @body(entry.key, entry.value);
}; };
} }
macro HashMap.@each_entry(map; @body(entry)) macro Map.@each_entry(self; @body(entry))
{ {
if (map.count) MapImpl *map = (MapImpl*)self;
if (!map || !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;
}
} }
} }
} }
fn Value[] HashMap.value_tlist(&map) fn Value[] Map.temp_values_list(map)
{ {
return map.value_new_list(mem::temp()) @inline; return map.new_values_list(allocator::temp()) @inline;
} }
fn Value[] HashMap.value_new_list(&map, Allocator* allocator = mem::heap()) fn Value[] Map.new_values_list(self, Allocator allocator = allocator::heap())
{ {
if (!map.count) return {}; MapImpl* map = (MapImpl*)self;
Value[] list = allocator.new_array(Value, map.count); if (!map || !map.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, map.count);
usz index = 0; usz index = 0;
foreach (Entry* entry : map.table) foreach (Entry* entry : map.table)
{ {
@@ -260,9 +343,10 @@ fn Value[] HashMap.value_new_list(&map, Allocator* allocator = mem::heap())
return list; return list;
} }
fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE) fn bool Map.has_value(self, Value v) @if(VALUE_IS_EQUATABLE)
{ {
if (!map.count) return false; MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return false;
foreach (Entry* entry : map.table) foreach (Entry* entry : map.table)
{ {
while (entry) while (entry)
@@ -276,21 +360,20 @@ fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
// --- private methods // --- private methods
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private fn void MapImpl._add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
{ {
Entry* entry = map.allocator.new(Entry);
$if COPY_KEYS: $if COPY_KEYS:
key = key.copy(map.allocator); key = key.copy(map.allocator);
$endif $endif
*entry = { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] }; Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry; map.table[bucket_index] = entry;
if (map.count++ >= map.threshold) if (map.count++ >= map.threshold)
{ {
map.resize(map.table.len * 2); map._resize(map.table.len * 2);
} }
} }
fn void HashMap.resize(&map, uint new_capacity) @private fn void MapImpl._resize(&map, uint new_capacity) @private
{ {
Entry*[] old_table = map.table; Entry*[] old_table = map.table;
uint old_capacity = old_table.len; uint old_capacity = old_table.len;
@@ -299,10 +382,10 @@ fn void HashMap.resize(&map, uint new_capacity) @private
map.threshold = uint.max; map.threshold = uint.max;
return; return;
} }
Entry*[] new_table = map.allocator.new_zero_array(Entry*, new_capacity); Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
map.transfer(new_table); map._transfer(new_table);
map.table = new_table; map.table = new_table;
map.free_internal(old_table.ptr); map._free_internal(old_table.ptr);
map.threshold = (uint)(new_capacity * map.load_factor); map.threshold = (uint)(new_capacity * map.load_factor);
} }
@@ -317,7 +400,7 @@ macro uint index_for(uint hash, uint capacity) @private
return hash & (capacity - 1); return hash & (capacity - 1);
} }
fn void HashMap.transfer(&map, Entry*[] new_table) @private fn void MapImpl._transfer(&map, Entry*[] new_table) @private
{ {
Entry*[] src = map.table; Entry*[] src = map.table;
uint new_capacity = new_table.len; uint new_capacity = new_table.len;
@@ -336,17 +419,18 @@ fn void HashMap.transfer(&map, Entry*[] new_table) @private
} }
} }
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private fn void _init(MapImpl* impl, uint capacity, float load_factor, Allocator allocator) @private
{ {
if (!other_map.count) return; capacity = math::next_power_of_2(capacity);
foreach (Entry *e : other_map.table) *impl = {
{ .allocator = allocator,
if (!e) continue; .load_factor = load_factor,
map.put_for_create(e.key, e.value); .threshold = (uint)(capacity * load_factor),
} .table = allocator::new_array(allocator, Entry*, capacity)
};
} }
fn void HashMap.put_for_create(&map, Key key, Value value) @private fn void MapImpl._put_for_create(&map, Key key, Value value) @private
{ {
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len); uint i = index_for(hash, map.table.len);
@@ -358,16 +442,17 @@ fn void HashMap.put_for_create(&map, Key key, Value value) @private
return; return;
} }
} }
map.create_entry(hash, key, value, i); map._create_entry(hash, key, value, i);
} }
fn void HashMap.free_internal(&map, void* ptr) @inline @private fn void MapImpl._free_internal(&map, void* ptr) @inline @private
{ {
map.allocator.free(ptr); allocator::free(map.allocator, ptr);
} }
fn bool HashMap.remove_entry_for_key(&map, Key key) @private fn bool MapImpl._remove_entry_for_key(&map, Key key) @private
{ {
if (!map.count) return false;
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len); uint i = index_for(hash, map.table.len);
Entry* prev = map.table[i]; Entry* prev = map.table[i];
@@ -386,7 +471,7 @@ fn bool HashMap.remove_entry_for_key(&map, Key key) @private
{ {
prev.next = next; prev.next = next;
} }
map.free_entry(e); map._free_entry(e);
return true; return true;
} }
prev = e; prev = e;
@@ -395,24 +480,23 @@ fn bool HashMap.remove_entry_for_key(&map, Key key) @private
return false; return false;
} }
fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private fn void MapImpl._create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
{ {
Entry *e = map.table[bucket_index]; Entry *e = map.table[bucket_index];
Entry* entry = map.allocator.new(Entry);
$if COPY_KEYS: $if COPY_KEYS:
key = key.copy(map.allocator); key = key.copy(map.allocator);
$endif $endif
*entry = { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] }; Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry; map.table[bucket_index] = entry;
map.count++; map.count++;
} }
fn void HashMap.free_entry(&self, Entry *entry) @local fn void MapImpl._free_entry(&self, Entry *entry) @local
{ {
$if COPY_KEYS: $if COPY_KEYS:
self.allocator.free(entry.key); allocator::free(self.allocator, entry.key);
$endif $endif
self.free_internal(entry); self._free_internal(entry);
} }
struct Entry struct Entry
@@ -421,4 +505,4 @@ struct Entry
Key key; Key key;
Value value; Value value;
Entry* next; Entry* next;
} }

View File

@@ -1,16 +1,43 @@
module std::collections::maybe(<Type>); module std::collections::maybe(<Type>);
import std::io;
struct Maybe struct Maybe (Printable)
{ {
Type value; Type value;
bool has_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) fn Maybe value(Type val)
{ {
return { .value = val, .has_value = true }; return { .value = val, .has_value = true };
} }
fn Maybe Maybe.with_value(Type val) @operator(construct)
{
return { .value = val, .has_value = true };
}
fn Maybe Maybe.empty() @operator(construct)
{
return { };
}
const Maybe EMPTY = { }; const Maybe EMPTY = { };
macro Type! Maybe.get(self) macro Type! Maybe.get(self)

View File

@@ -2,9 +2,7 @@
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::object; module std::collections::object;
import std::collections::map; import std::collections::map, std::collections::list, std::io;
import std::collections::list;
import std::io;
const Object TRUE_OBJECT = { .b = true, .type = bool.typeid }; const Object TRUE_OBJECT = { .b = true, .type = bool.typeid };
const Object FALSE_OBJECT = { .b = false, .type = bool.typeid }; const Object FALSE_OBJECT = { .b = false, .type = bool.typeid };
@@ -13,7 +11,7 @@ const Object NULL_OBJECT = { .type = void*.typeid };
struct Object (Printable) struct Object (Printable)
{ {
typeid type; typeid type;
Allocator* allocator; Allocator allocator;
union union
{ {
uint128 i; uint128 i;
@@ -50,9 +48,9 @@ fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
return n; return n;
case ObjectInternalMap: case ObjectInternalMap:
usz n = formatter.printf("{")!; usz n = formatter.printf("{")!;
@pool() @stack_mem(1024; Allocator mem)
{ {
foreach (i, key : self.map.key_tlist()) foreach (i, key : self.map.copy_keys(mem))
{ {
if (i > 0) n += formatter.printf(",")!; if (i > 0) n += formatter.printf(",")!;
n += formatter.printf(`"%s":`, key)!; n += formatter.printf(`"%s":`, key)!;
@@ -65,11 +63,11 @@ fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
switch (self.type.kindof) switch (self.type.kindof)
{ {
case SIGNED_INT: case SIGNED_INT:
return formatter.printf("%d", self.i)!; return formatter.printf("%d", (int128)self.i)!;
case UNSIGNED_INT: case UNSIGNED_INT:
return formatter.printf("%d", (uint128)self.i)!; return formatter.printf("%d", (uint128)self.i)!;
case FLOAT: case FLOAT:
return formatter.printf("%d", self.f)!; return formatter.printf("%g", self.f)!;
case ENUM: case ENUM:
return formatter.printf("%d", self.i)!; return formatter.printf("%d", self.i)!;
default: default:
@@ -78,11 +76,9 @@ fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
} }
} }
fn Object* new_obj(Allocator* allocator) fn Object* new_obj(Allocator allocator)
{ {
Object* o = allocator.new(Object); return allocator::new(allocator, Object, { .allocator = allocator, .type = void.typeid });
*o = { .allocator = allocator, .type = void.typeid };
return o;
} }
fn Object* new_null() fn Object* new_null()
@@ -90,32 +86,24 @@ fn Object* new_null()
return &NULL_OBJECT; return &NULL_OBJECT;
} }
fn Object* new_int(int128 i, Allocator* allocator) fn Object* new_int(int128 i, Allocator allocator)
{ {
Object* o = allocator.new(Object); return allocator::new(allocator, Object, { .i = i, .allocator = allocator, .type = int128.typeid });
*o = { .i = i, .allocator = allocator, .type = int128.typeid };
return o;
} }
macro Object* new_enum(e, Allocator* allocator) macro Object* new_enum(e, Allocator allocator)
{ {
Object* o = allocator.new(Object); return allocator::new(allocator, Object, { .i = (int128)e, .allocator = allocator, .type = @typeid(e) });
*o = { .i = (int128)e, .allocator = allocator, .type = @typeid(e) };
return o;
} }
fn Object* new_float(double f, Allocator* allocator) fn Object* new_float(double f, Allocator allocator)
{ {
Object* o = allocator.new(Object); return allocator::new(allocator, Object, { .f = f, .allocator = allocator, .type = double.typeid });
*o = { .f = f, .allocator = allocator, .type = double.typeid };
return o;
} }
fn Object* new_string(String s, Allocator* allocator) fn Object* new_string(String s, Allocator allocator)
{ {
Object* o = allocator.new(Object); return allocator::new(allocator, Object, { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid });
*o = { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid };
return o;
} }
@@ -131,7 +119,7 @@ fn void Object.free(&self)
case void: case void:
break; break;
case String: case String:
self.allocator.free(self.s); allocator::free(self.allocator, self.s);
case ObjectInternalList: case ObjectInternalList:
foreach (ol : self.array) foreach (ol : self.array)
{ {
@@ -140,13 +128,13 @@ fn void Object.free(&self)
self.array.free(); self.array.free();
case ObjectInternalMap: case ObjectInternalMap:
self.map.@each_entry(; ObjectInternalMapEntry* entry) { self.map.@each_entry(; ObjectInternalMapEntry* entry) {
self.allocator.free(entry.key);
entry.value.free(); entry.value.free();
}; };
self.map.free();
default: default:
break; break;
} }
if (self.allocator) self.allocator.free(self); if (self.allocator) allocator::free(self.allocator, self);
} }
fn bool Object.is_null(&self) @inline => self == &NULL_OBJECT; fn bool Object.is_null(&self) @inline => self == &NULL_OBJECT;
@@ -160,49 +148,47 @@ fn bool Object.is_int(&self) @inline => self.type == int128.typeid;
fn bool Object.is_keyable(&self) => self.is_empty() || self.is_map(); fn bool Object.is_keyable(&self) => self.is_empty() || self.is_map();
fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array(); fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array();
/** <*
* @require self.is_keyable() @require self.is_keyable()
**/ *>
fn void Object.init_map_if_needed(&self) @private fn void Object.init_map_if_needed(&self) @private
{ {
if (self.is_empty()) if (self.is_empty())
{ {
self.type = ObjectInternalMap.typeid; self.type = ObjectInternalMap.typeid;
self.map.init_new(.allocator = self.allocator); self.map.new_init(allocator: self.allocator);
} }
} }
/** <*
* @require self.is_indexable() @require self.is_indexable()
**/ *>
fn void Object.init_array_if_needed(&self) @private fn void Object.init_array_if_needed(&self) @private
{ {
if (self.is_empty()) if (self.is_empty())
{ {
self.type = ObjectInternalList.typeid; self.type = ObjectInternalList.typeid;
self.array.init_new(.allocator = self.allocator); self.array.new_init(allocator: self.allocator);
} }
} }
/** <*
* @require self.is_keyable() @require self.is_keyable()
**/ *>
fn void Object.set_object(&self, String key, Object* new_object) @private fn void Object.set_object(&self, String key, Object* new_object) @private
{ {
self.init_map_if_needed(); self.init_map_if_needed();
ObjectInternalMapEntry*! entry = self.map.get_entry(key); ObjectInternalMapEntry*! entry = self.map.get_entry(key);
defer defer
{ {
(void)self.allocator.free(entry.key);
(void)entry.value.free(); (void)entry.value.free();
} }
self.map.set(key.copy(self.map.allocator), new_object); self.map.set(key, new_object);
} }
macro Object* Object.object_from_value(&self, value) @private macro Object* Object.object_from_value(&self, value) @private
{ {
var $Type = $typeof(value); var $Type = $typeof(value);
$switch $switch
$case types::is_int($Type): $case types::is_int($Type):
return new_int(value, self.allocator); return new_int(value, self.allocator);
@@ -232,9 +218,9 @@ macro Object* Object.set(&self, String key, value)
return val; return val;
} }
/** <*
* @require self.is_indexable() @require self.is_indexable()
**/ *>
macro Object* Object.set_at(&self, usz index, String key, value) macro Object* Object.set_at(&self, usz index, String key, value)
{ {
Object* val = self.object_from_value(value); Object* val = self.object_from_value(value);
@@ -242,72 +228,72 @@ macro Object* Object.set_at(&self, usz index, String key, value)
return val; return val;
} }
/** <*
* @require self.is_indexable() @require self.is_indexable()
* @ensure return != null @ensure return != null
**/ *>
macro Object* Object.append(&self, value) macro Object* Object.push(&self, value)
{ {
Object* val = self.object_from_value(value); Object* val = self.object_from_value(value);
self.append_object(val); self.push_object(val);
return val; return val;
} }
/** <*
* @require self.is_keyable() @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() ? SearchResult.MISSING? : self.map.get(key);
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key); fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
/** <*
* @require self.is_indexable() @require self.is_indexable()
**/ *>
fn Object* Object.get_at(&self, usz index) fn Object* Object.get_at(&self, usz index)
{ {
return self.array.get(index); return self.array.get(index);
} }
/** <*
* @require self.is_indexable() @require self.is_indexable()
**/ *>
fn usz Object.get_len(&self) fn usz Object.get_len(&self)
{ {
return self.array.len(); return self.array.len();
} }
/** <*
* @require self.is_indexable() @require self.is_indexable()
**/ *>
fn void Object.append_object(&self, Object* to_append) fn void Object.push_object(&self, Object* to_append)
{ {
self.init_array_if_needed(); self.init_array_if_needed();
self.array.append(to_append); self.array.push(to_append);
} }
/** <*
* @require self.is_indexable() @require self.is_indexable()
**/ *>
fn void Object.set_object_at(&self, usz index, Object* to_set) fn void Object.set_object_at(&self, usz index, Object* to_set)
{ {
self.init_array_if_needed(); self.init_array_if_needed();
while (self.array.len() < index) while (self.array.len() < index)
{ {
self.array.append(&NULL_OBJECT); self.array.push(&NULL_OBJECT);
} }
if (self.array.len() == index) if (self.array.len() == index)
{ {
self.array.append(to_set); self.array.push(to_set);
return; return;
} }
self.array.get(index).free(); self.array.get(index).free();
self.array.set_at(index, to_set); self.array.set_at(index, 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) macro get_integer_value(Object* value, $Type)
{ {
if (value.is_float()) if (value.is_float())
@@ -327,19 +313,19 @@ macro get_integer_value(Object* value, $Type)
} }
/** <*
* @require self.is_indexable() @require self.is_indexable()
* @require $Type.kindof.is_int() : "Expected an integer type" @require $Type.kindof.is_int() : "Expected an integer type"
**/ *>
macro Object.get_integer_at(&self, $Type, usz index) @private macro Object.get_integer_at(&self, $Type, usz index) @private
{ {
return get_integer_value(self.get_at(index), $Type); return get_integer_value(self.get_at(index), $Type);
} }
/** <*
* @require self.is_keyable() @require self.is_keyable()
* @require $Type.kindof.is_int() : "Expected an integer type" @require $Type.kindof.is_int() : "Expected an integer type"
**/ *>
macro Object.get_integer(&self, $Type, String key) @private macro Object.get_integer(&self, $Type, String key) @private
{ {
return get_integer_value(self.get(key), $Type); return get_integer_value(self.get(key), $Type);
@@ -369,9 +355,9 @@ 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 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 uint128! Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
/** <*
* @require self.is_keyable() @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)!; Object* value = self.get(key)!;
@@ -379,9 +365,9 @@ fn String! Object.get_string(&self, String key)
return value.s; return value.s;
} }
/** <*
* @require self.is_indexable() @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); Object* value = self.get_at(index);
@@ -389,9 +375,9 @@ fn String! Object.get_string_at(&self, usz index)
return value.s; return value.s;
} }
/** <*
* @require self.is_keyable() @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)!; Object value = self.get(key)!;
@@ -399,9 +385,9 @@ macro String! Object.get_enum(&self, $EnumType, String key)
return ($EnumType)value.i; return ($EnumType)value.i;
} }
/** <*
* @require self.is_indexable() @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); Object value = self.get_at(index);
@@ -409,9 +395,9 @@ macro String! Object.get_enum_at(&self, $EnumType, usz index)
return ($EnumType)value.i; return ($EnumType)value.i;
} }
/** <*
* @require self.is_keyable() @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)!; Object* value = self.get(key)!;
@@ -420,9 +406,9 @@ fn bool! Object.get_bool(&self, String key)
} }
/** <*
* @require self.is_indexable() @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); Object* value = self.get_at(index);
@@ -430,9 +416,9 @@ fn bool! Object.get_bool_at(&self, usz index)
return value.b; return value.b;
} }
/** <*
* @require self.is_keyable() @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)!; Object* value = self.get(key)!;
@@ -449,9 +435,9 @@ fn double! Object.get_float(&self, String key)
} }
} }
/** <*
* @require self.is_indexable() @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); Object* value = self.get_at(index);

View File

@@ -27,7 +27,7 @@ distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>); distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);
module std::collections::priorityqueue::private(<Type, MAX>); module std::collections::priorityqueue::private(<Type, MAX>);
import std::collections::list; import std::collections::list, std::io;
def Heap = List(<Type>); def Heap = List(<Type>);
@@ -36,16 +36,17 @@ struct PrivatePriorityQueue (Printable)
Heap heap; Heap heap;
} }
fn void PrivatePriorityQueue.init_new(&self, usz initial_capacity = 16, Allocator* allocator = mem::heap()) @inline fn void PrivatePriorityQueue.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @inline
{ {
self.heap.init_new(initial_capacity, allocator); self.heap.new_init(initial_capacity, allocator);
} }
fn void PrivatePriorityQueue.init_temp(&self, usz initial_capacity = 16) @inline fn void PrivatePriorityQueue.temp_init(&self, usz initial_capacity = 16) @inline
{ {
self.heap.init_new(initial_capacity, mem::temp()) @inline; self.heap.new_init(initial_capacity, allocator::temp()) @inline;
} }
fn void PrivatePriorityQueue.push(&self, Type element) fn void PrivatePriorityQueue.push(&self, Type element)
{ {
self.heap.push(element); self.heap.push(element);
@@ -66,37 +67,49 @@ fn void PrivatePriorityQueue.push(&self, Type element)
} }
} }
/** fn void PrivatePriorityQueue.remove_at(&self, usz index)
* @require self != null {
*/ if (index == 0)
{
self.pop()!!;
return;
}
self.heap.remove_at(index);
}
<*
@require self != null
*>
fn Type! PrivatePriorityQueue.pop(&self) fn Type! PrivatePriorityQueue.pop(&self)
{ {
usz i = 0; usz i = 0;
usz len = self.heap.len(); usz len = self.heap.len();
if (!len) return IteratorResult.NO_MORE_ELEMENT?; if (!len) return IteratorResult.NO_MORE_ELEMENT?;
usz newCount = len - 1; usz new_count = len - 1;
self.heap.swap(0, newCount); self.heap.swap(0, new_count);
while ((2 * i + 1) < newCount) while OUTER: ((2 * i + 1) < new_count)
{ {
usz j = 2 * i + 1; usz j = 2 * i + 1;
Type itemj = self.heap[j]; Type left = self.heap[j];
if ((j + 1) < newCount)
{
Type nextj = self.heap[j + 1];
$if MAX:
bool ok = greater(nextj, itemj);
$else
bool ok = less(nextj, itemj);
$endif
if (ok) j++;
}
Type item = self.heap[i]; Type item = self.heap[i];
$if MAX: switch
bool ok = less(item, itemj); {
$else case j + 1 < new_count:
bool ok = greater(item, itemj); Type right = self.heap[j + 1];
$endif $if MAX:
if (!ok) break; if (!greater(right, left)) nextcase;
if (!greater(right, item)) break OUTER;
$else
if (!greater(left, right)) nextcase;
if (!greater(item, right)) break OUTER;
$endif
j++;
default:
$if MAX:
if (!greater(left, item)) break OUTER;
$else
if (!greater(item, left)) break OUTER;
$endif
}
self.heap.swap(i, j); self.heap.swap(i, j);
i = j; i = j;
} }
@@ -104,7 +117,7 @@ fn Type! PrivatePriorityQueue.pop(&self)
return self.heap.pop(); return self.heap.pop();
} }
fn Type! PrivatePriorityQueue.peek(&self) fn Type! PrivatePriorityQueue.first(&self)
{ {
if (!self.len()) return IteratorResult.NO_MORE_ELEMENT?; if (!self.len()) return IteratorResult.NO_MORE_ELEMENT?;
return self.heap.get(0); return self.heap.get(0);
@@ -125,10 +138,10 @@ fn bool PrivatePriorityQueue.is_empty(&self)
return self.heap.is_empty(); return self.heap.is_empty();
} }
/** <*
* @require index < self.len() @require index < self.len()
*/ *>
fn Type PrivatePriorityQueue.peek_at(&self, usz index) @operator([]) fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
{ {
return self.heap[index]; return self.heap[index];
} }
@@ -138,7 +151,7 @@ fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
return self.heap.to_format(formatter); return self.heap.to_format(formatter);
} }
fn String PrivatePriorityQueue.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic fn String PrivatePriorityQueue.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{ {
return self.heap.to_new_string(allocator); return self.heap.to_new_string(allocator);
} }

View File

@@ -1,7 +1,8 @@
/** <*
* @require Type.is_ordered : "The type must be ordered" @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) struct Range (Printable)
{ {
@@ -20,22 +21,27 @@ fn bool Range.contains(&self, Type value) @inline
return value >= self.start && value <= self.end; return value >= self.start && value <= self.end;
} }
/** <*
* @require index < self.len() : "Can't index into an empty range" @require index < self.len() : "Can't index into an empty range"
**/ *>
fn Type Range.get(&self, usz index) @operator([]) fn Type Range.get(&self, usz index) @operator([])
{ {
return (Type)(self.start + (usz)index); return (Type)(self.start + (usz)index);
} }
fn String Range.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic fn String Range.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic @deprecated
{ {
return string::new_format("[%s..%s]", self.start, self.end, .allocator = allocator); 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) fn String Range.to_tstring(&self)
{ {
return self.to_new_string(mem::temp()); return self.to_string(allocator::temp());
} }
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
@@ -65,19 +71,24 @@ fn usz! ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
return formatter.printf("[%s..<%s]", self.start, self.end)!; return formatter.printf("[%s..<%s]", self.start, self.end)!;
} }
fn String ExclusiveRange.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic fn String ExclusiveRange.to_new_string(&self, Allocator allocator = null) @dynamic
{ {
return string::new_format("[%s..<%s]", self.start, self.end, .allocator = allocator); 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) fn String ExclusiveRange.to_tstring(&self)
{ {
return self.to_new_string(mem::temp()); return self.to_new_string(allocator::temp());
} }
/** <*
* @require index < self.len() : "Can't index into an empty range" @require index < self.len() : "Can't index into an empty range"
**/ *>
fn Type ExclusiveRange.get(&self, usz index) @operator([]) fn Type ExclusiveRange.get(&self, usz index) @operator([])
{ {
return (Type)(self.start + index); return (Type)(self.start + index);

View File

@@ -1,3 +1,6 @@
<*
@require values::@is_int(SIZE) &&& SIZE > 0 "The size must be positive integer"
*>
module std::collections::ringbuffer(<Type, SIZE>); module std::collections::ringbuffer(<Type, SIZE>);
struct RingBuffer struct RingBuffer
@@ -12,7 +15,7 @@ fn void RingBuffer.init(&self) @inline
*self = {}; *self = {};
} }
fn void RingBuffer.putc(&self, Type c) fn void RingBuffer.push(&self, Type c)
{ {
if (self.written < SIZE) if (self.written < SIZE)
{ {
@@ -26,7 +29,7 @@ fn void RingBuffer.putc(&self, Type c)
} }
} }
fn Type RingBuffer.getc(&self, usz index) fn Type RingBuffer.get(&self, usz index) @operator([])
{ {
index %= SIZE; index %= SIZE;
usz avail = SIZE - self.head; usz avail = SIZE - self.head;
@@ -37,7 +40,7 @@ fn Type RingBuffer.getc(&self, usz index)
return self.buf[index - avail]; return self.buf[index - avail];
} }
fn Type! RingBuffer.popc(&self) fn Type! RingBuffer.pop(&self)
{ {
switch switch
{ {
@@ -52,7 +55,7 @@ fn Type! RingBuffer.popc(&self)
} }
} }
fn usz RingBuffer.get(&self, usz index, Type[] buffer) fn usz RingBuffer.read(&self, usz index, Type[] buffer)
{ {
index %= SIZE; index %= SIZE;
if (self.written < SIZE) if (self.written < SIZE)
@@ -87,7 +90,7 @@ fn usz RingBuffer.get(&self, usz index, Type[] buffer)
return n1 + n2; return n1 + n2;
} }
fn void RingBuffer.push(&self, Type[] buffer) fn void RingBuffer.write(&self, Type[] buffer)
{ {
usz i; usz i;
while (self.written < SIZE && i < buffer.len) while (self.written < SIZE && i < buffer.len)

495
lib/std/compression/qoi.c3 Normal file
View File

@@ -0,0 +1,495 @@
module std::compression::qoi;
const uint PIXELS_MAX = 400000000;
<*
Colorspace.
Purely informative. It will be saved to the file header,
but does not affect how chunks are en-/decoded.
*>
enum QOIColorspace : char (char id)
{
SRGB = 0, // sRGB with linear alpha
LINEAR = 1 // all channels linear
}
<*
Channels.
The channels used in an image.
AUTO can be used when decoding to automatically determine
the channels from the file's header.
*>
enum QOIChannels : char (char id)
{
AUTO = 0,
RGB = 3,
RGBA = 4
}
<*
Descriptor.
Contains information about an image.
*>
struct QOIDesc
{
uint width;
uint height;
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
}
// Let the user decide if they want to use std::io
module std::compression::qoi @if(!$feature(QOI_NO_STDIO));
import std::io;
<*
Encode raw RGB or RGBA pixels into a QOI image and write it to the
file system.
The desc struct must be filled with the image width, height, the
used channels (QOIChannels.RGB or RGBA) and the colorspace
(QOIColorspace.SRGB or LINEAR).
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`
*>
fn usz! write(String filename, char[] input, QOIDesc* desc) => @pool()
{
// encode data
char[] output = new_encode(input, desc, allocator: allocator::temp())!;
// 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;
}
<*
Read and decode a QOI image from the file system.
If channels is set to QOIChannels.AUTO, the function will
automatically determine the channels from the file's header.
However, if channels is RGB or RGBA, the output format will be
forced into this number of channels.
The desc struct will be filled with the width, height,
channels and colorspace of the image.
The function returns an optional, which can either be a QOIError
or a char[] pointing to the decoded pixels on success.
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`
*>
fn char[]! new_read(String filename, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap()) => @pool(allocator)
{
// read file
char[] data = file::load_temp(filename) ?? QOIError.FILE_OPEN_FAILED?!;
// pass data to decode function
return new_decode(data, desc, channels, allocator);
}
fn char[]! read(String filename, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap()) @deprecated("Use new_read")
{
return new_read(filename, desc, channels, allocator);
}
// Back to basic non-stdio mode
module std::compression::qoi;
import std::bits;
fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::heap()) @deprecated("use encode_new")
{
return new_encode(input, desc, allocator);
}
<*
Encode raw RGB or RGBA pixels into a QOI image in memory.
The function returns an optional, which can either be a QOIError
or a char[] pointing to the encoded data on success.
The returned qoi data should be free()d after use, or the encoding
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`
*>
fn char[]! new_encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::heap()) @nodiscard
{
// check info in desc
if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_PARAMETERS?;
if (desc.channels == AUTO) return QOIError.INVALID_PARAMETERS?;
uint pixels = desc.width * desc.height;
if (pixels > PIXELS_MAX) return QOIError.TOO_MANY_PIXELS?;
// check input data size
uint image_size = pixels * desc.channels.id;
if (image_size != input.len) return QOIError.INVALID_DATA?;
// allocate memory for encoded data (output)
// header + chunk tag and RGB(A) data for each pixel + end of stream
uint max_size = Header.sizeof + pixels + image_size + END_OF_STREAM.len;
char[] output = allocator::alloc_array(allocator, char, max_size); // no need to init
defer catch allocator::free(allocator, output);
// write header
*(Header*)output.ptr = {
.be_magic = bswap('qoif'),
.be_width = bswap(desc.width),
.be_height = bswap(desc.height),
.channels = desc.channels.id,
.colorspace = desc.colorspace.id
};
uint pos = Header.sizeof; // Current position in output
uint loc; // Current position in image (top-left corner)
uint loc_end = image_size - desc.channels.id; // End of image data
char run_length = 0; // Length of the current run
Pixel[64] palette; // Zero-initialized by default
Pixel prev = { 0, 0, 0, 255 };
Pixel p = { 0, 0, 0, 255 };
ichar[<3>] diff; // pre-allocate for diff
ichar[<3>] luma; // ...and luma
// write chunks
for (loc = 0; loc < image_size; loc += desc.channels.id)
{
// set previous pixel
prev = p;
// get current pixel
p[:3] = input[loc:3]; // cutesy slices :3
if (desc.channels == RGBA) p.a = input[loc + 3];
// check if we can run the previous pixel
if (prev == p)
{
run_length++;
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()
};
// 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;
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;
}
}
// write end of stream
output[pos:END_OF_STREAM.len] = END_OF_STREAM;
pos += END_OF_STREAM.len;
return output[:pos];
}
fn char[]! decode(char[] data, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap())
{
return new_decode(data, desc, channels, allocator);
}
<*
Decode a QOI image from memory.
If channels is set to QOIChannels.AUTO, the function will
automatically determine the channels from the file's header.
However, if channels is RGB or RGBA, the output format will be
forced into this number of channels.
The desc struct will be filled with the width, height,
channels and colorspace of the image.
The function returns an optional, which can either be a QOIError
or a char[] pointing to the decoded pixels on success.
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`
*>
fn char[]! new_decode(char[] data, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap()) @nodiscard
{
// check input data
if (data.len < Header.sizeof + END_OF_STREAM.len) return QOIError.INVALID_DATA?;
// get header
Header* header = (Header*)data.ptr;
// check magic bytes (FourCC)
if (bswap(header.be_magic) != 'qoif') return QOIError.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
// check width and height
if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_DATA?;
// check pixel count
ulong pixels = (ulong)desc.width * (ulong)desc.height;
if (pixels > PIXELS_MAX) return QOIError.TOO_MANY_PIXELS?;
uint pos = Header.sizeof; // Current position in data
uint loc; // Current position in image (top-left corner)
char run_length = 0; // Length of the current run
char tag; // Current chunk tag
Pixel[64] palette; // Zero-initialized by default
Pixel p = { 0, 0, 0, 255 };
if (channels == AUTO) channels = desc.channels;
// allocate memory for image data
usz image_size = (usz)pixels * channels.id;
char[] image = allocator::alloc_array(allocator, char, image_size);
defer catch allocator::free(allocator, image);
for (loc = 0; loc < image_size; loc += channels.id)
{
// get chunk tag
tag = data[pos];
// check for chunk type
switch
{
case run_length > 0:
run_length--;
case tag == OP_RGB:
OpRGB* op = @extract(OpRGB, data, &pos);
p = { op.red, op.green, op.blue, p.a };
palette[p.hash()] = p;
case tag == OP_RGBA:
OpRGBA* op = @extract(OpRGBA, data, &pos);
p = { op.red, op.green, op.blue, op.alpha };
palette[p.hash()] = p;
case tag >> 6 == OP_INDEX:
OpIndex* op = @extract(OpIndex, data, &pos);
p = palette[op.index];
case tag >> 6 == OP_DIFF:
OpDiff* op = @extract(OpDiff, data, &pos);
p.r += op.diff_red - 2;
p.g += op.diff_green - 2;
p.b += op.diff_blue - 2;
palette[p.hash()] = p;
case tag >> 6 == OP_LUMA:
OpLuma* op = @extract(OpLuma, data, &pos);
int diff_green = op.diff_green - 32;
p.r += (char)(op.diff_red_minus_green - 8 + diff_green);
p.g += (char)(diff_green);
p.b += (char)(op.diff_blue_minus_green - 8 + diff_green);
palette[p.hash()] = p;
case tag >> 6 == OP_RUN:
OpRun* op = @extract(OpRun, data, &pos);
run_length = op.run;
}
// draw the pixel
if (channels == RGBA) { image[loc:4] = p.rgba; } else { image[loc:3] = p.rgb; }
}
return image;
}
// ***************************************************************************
// *** ***
// *** Main functions are at the top to make the file more readable. ***
// *** From here on, helper functions and types are defined. ***
// *** ***
// ***************************************************************************
module std::compression::qoi @private;
// 8-bit opcodes
const OP_RGB = 0b11111110;
const OP_RGBA = 0b11111111;
// 2-bit opcodes
const OP_INDEX = 0b00;
const OP_DIFF = 0b01;
const OP_LUMA = 0b10;
const OP_RUN = 0b11;
struct Header @packed
{
uint be_magic; // magic bytes "qoif"
uint be_width; // image width in pixels (BE)
uint be_height; // image height in pixels (BE)
// informative fields
char channels; // 3 = RGB, 4 = RGB
char colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
}
const char[?] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1};
// inefficient, but it's only run once at a time
macro @enumcast($Type, raw)
{
foreach (value : $Type.values)
{
if (value.id == raw) return value;
}
return QOIError.INVALID_DATA?;
}
distinct Pixel = inline char[<4>];
macro char Pixel.hash(Pixel p)
{
return (p.r * 3 + p.g * 5 + p.b * 7 + p.a * 11) % 64;
}
struct OpRGB // No need to use @packed here, the alignment is 1 anyways.
{
char tag;
char red;
char green;
char blue;
}
struct OpRGBA @packed
{
char tag;
char red;
char green;
char blue;
char alpha;
}
bitstruct OpIndex : char
{
char tag : 6..7;
char index : 0..5;
}
bitstruct OpDiff : char
{
char tag : 6..7;
char diff_red : 4..5;
char diff_green : 2..3;
char diff_blue : 0..1;
}
bitstruct OpLuma : ushort @align(1)
{
char tag : 6..7;
char diff_green : 0..5;
char diff_red_minus_green : 12..15;
char diff_blue_minus_green : 8..11;
}
bitstruct OpRun : char
{
char tag : 6..7;
char run : 0..5;
}
// Macro used to locate chunks in data buffers.
// Can be used both for reading and writing.
macro @extract($Type, char[] data, uint* pos)
{
// slice data, then double cast
$Type* chunk = ($Type*)data[*pos : $Type.sizeof].ptr;
*pos += $Type.sizeof;
return chunk;
}

View File

@@ -2,6 +2,7 @@
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator; module std::core::mem::allocator;
import std::math;
struct ArenaAllocator (Allocator) struct ArenaAllocator (Allocator)
{ {
@@ -9,13 +10,14 @@ struct ArenaAllocator (Allocator)
usz used; usz used;
} }
/** <*
* Initialize a memory arena for use using the provided bytes. Initialize a memory arena for use using the provided bytes.
**/ *>
fn void ArenaAllocator.init(&self, char[] data) fn ArenaAllocator* ArenaAllocator.init(&self, char[] data)
{ {
self.data = data; self.data = data;
self.used = 0; self.used = 0;
return self;
} }
fn void ArenaAllocator.clear(&self) fn void ArenaAllocator.clear(&self)
@@ -26,12 +28,19 @@ fn void ArenaAllocator.clear(&self)
struct ArenaAllocatorHeader @local struct ArenaAllocatorHeader @local
{ {
usz size; usz size;
char[*] data; char[?] data;
} }
macro ArenaAllocator* wrap(char[] bytes)
{
return ArenaAllocator{}.init(bytes);
}
<*
@require ptr != null
*>
fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
{ {
if (!ptr) return;
assert((uptr)ptr >= (uptr)self.data.ptr, "Pointer originates from a different allocator."); assert((uptr)ptr >= (uptr)self.data.ptr, "Pointer originates from a different allocator.");
ArenaAllocatorHeader* header = ptr - ArenaAllocatorHeader.sizeof; ArenaAllocatorHeader* header = ptr - ArenaAllocatorHeader.sizeof;
// Reclaim memory if it's the last element. // Reclaim memory if it's the last element.
@@ -40,53 +49,40 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
self.used -= header.size + ArenaAllocatorHeader.sizeof; self.used -= header.size + ArenaAllocatorHeader.sizeof;
} }
} }
fn usz ArenaAllocator.mark(&self) @dynamic => self.used; fn usz ArenaAllocator.mark(&self) @dynamic => self.used;
fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark; fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark;
/** <*
* @require !alignment || math::is_power_of_2(alignment) @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 offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big` @require size > 0
* @require offset <= size && offset >= 0 *>
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
**/
fn void*! ArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{ {
if (!size) return null;
alignment = alignment_for_allocation(alignment); alignment = alignment_for_allocation(alignment);
usz total_len = self.data.len; usz total_len = self.data.len;
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?; if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
void* start_mem = self.data.ptr; void* start_mem = self.data.ptr;
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof + offset; void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof;
void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment); void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz end = (usz)(aligned_pointer_to_offset - self.data.ptr) + size - offset; usz end = (usz)(mem - self.data.ptr) + size;
if (end > total_len) return AllocationFailure.OUT_OF_MEMORY?; if (end > total_len) return AllocationFailure.OUT_OF_MEMORY?;
self.used = end; self.used = end;
void* mem = aligned_pointer_to_offset - offset;
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof; ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
header.size = size; header.size = size;
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT); if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem; return mem;
} }
/** <*
* @require !alignment || math::is_power_of_2(alignment) @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 offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big` @require old_pointer != null
* @require offset <= size && offset >= 0 @require size > 0
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset *>
**/ 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, usz offset) @dynamic
{ {
if (!size)
{
self.release(old_pointer, alignment > 0);
return null;
}
if (!old_pointer)
{
return self.acquire(size, true, alignment, offset);
}
alignment = alignment_for_allocation(alignment); alignment = alignment_for_allocation(alignment);
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator."); assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
usz total_len = self.data.len; usz total_len = self.data.len;
@@ -94,7 +90,7 @@ fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof; ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
usz old_size = header.size; usz old_size = header.size;
// Do last allocation and alignment match? // Do last allocation and alignment match?
if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer + offset, alignment)) if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
{ {
if (old_size >= size) if (old_size >= size)
{ {
@@ -110,7 +106,7 @@ fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
return old_pointer; return old_pointer;
} }
// Otherwise just allocate new memory. // Otherwise just allocate new memory.
void* mem = self.acquire(size, false, alignment, offset)!; void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem; return mem;
} }

View File

@@ -1,21 +1,22 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator; module std::core::mem::allocator;
import std::math;
struct DynamicArenaAllocator (Allocator) struct DynamicArenaAllocator (Allocator)
{ {
Allocator* backing_allocator; Allocator backing_allocator;
DynamicArenaPage* page; DynamicArenaPage* page;
DynamicArenaPage* unused_page; DynamicArenaPage* unused_page;
usz page_size; usz page_size;
} }
/** <*
* @param [&inout] allocator @param [&inout] allocator
* @require page_size >= 128 @require page_size >= 128
**/ *>
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator* allocator) fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator allocator)
{ {
self.page = null; self.page = null;
self.unused_page = null; self.unused_page = null;
@@ -29,14 +30,16 @@ fn void DynamicArenaAllocator.free(&self)
while (page) while (page)
{ {
DynamicArenaPage* next_page = page.prev_arena; DynamicArenaPage* next_page = page.prev_arena;
self.backing_allocator.free(page); allocator::free(self.backing_allocator, page.memory);
allocator::free(self.backing_allocator, page);
page = next_page; page = next_page;
} }
page = self.unused_page; page = self.unused_page;
while (page) while (page)
{ {
DynamicArenaPage* next_page = page.prev_arena; DynamicArenaPage* next_page = page.prev_arena;
self.backing_allocator.free(page); allocator::free(self.backing_allocator, page.memory);
allocator::free(self.backing_allocator, page);
page = next_page; page = next_page;
} }
self.page = null; self.page = null;
@@ -57,12 +60,12 @@ struct DynamicArenaChunk @local
usz size; usz size;
} }
/** <*
* @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 fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
{ {
if (!ptr) return;
DynamicArenaPage* current_page = self.page; DynamicArenaPage* current_page = self.page;
if (ptr == current_page.current_stack_ptr) if (ptr == current_page.current_stack_ptr)
{ {
@@ -71,20 +74,13 @@ fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
current_page.current_stack_ptr = null; current_page.current_stack_ptr = null;
} }
/** <*
* @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`
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic @require self.page != null `tried to realloc pointer on invalid allocator`
*>
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{ {
if (!size)
{
self.release(old_pointer, alignment > 0);
return null;
}
if (!old_pointer)
{
return self.acquire(size, true, alignment, offset);
}
DynamicArenaPage* current_page = self.page; DynamicArenaPage* current_page = self.page;
alignment = alignment_for_allocation(alignment); alignment = alignment_for_allocation(alignment);
usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX; usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
@@ -108,7 +104,7 @@ fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a
current_page.used += add_size; current_page.used += add_size;
return old_pointer; return old_pointer;
} }
void* new_mem = self.acquire(size, false, alignment, offset)!; void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT); mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
return new_mem; return new_mem;
} }
@@ -130,25 +126,25 @@ fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
self.page = page; self.page = page;
} }
/** <*
* @require math::is_power_of_2(alignment) @require math::is_power_of_2(alignment)
* @require size > 0 @require size > 0
*/ *>
fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment, usz offset) @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. // 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 + offset, alignment) - offset); 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) // Grab the page without alignment (we do it ourselves)
void* mem = self.backing_allocator.alloc_checked(page_size)!; void* mem = allocator::malloc_try(self.backing_allocator, page_size)!;
DynamicArenaPage*! page = self.backing_allocator.new(DynamicArenaPage); DynamicArenaPage*! page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
if (catch err = page) if (catch err = page)
{ {
self.backing_allocator.free(mem); allocator::free(self.backing_allocator, mem);
return err?; return err?;
} }
page.memory = mem; page.memory = mem;
void* mem_start = mem::aligned_pointer(mem + offset + DynamicArenaChunk.sizeof, alignment) - offset; void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment);
assert(mem_start + size < mem + page_size); assert(mem_start + size < mem + page_size);
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1; DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
chunk.size = size; chunk.size = size;
@@ -160,12 +156,12 @@ fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment, usz o
return mem_start; return mem_start;
} }
/** <*
* @require !alignment || math::is_power_of_2(alignment) @require size > 0 `acquire expects size > 0`
*/ @require !alignment || math::is_power_of_2(alignment)
fn void*! DynamicArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic *>
fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{ {
if (!size) return null;
alignment = alignment_for_allocation(alignment); alignment = alignment_for_allocation(alignment);
DynamicArenaPage* page = self.page; DynamicArenaPage* page = self.page;
void* ptr = {| void* ptr = {|
@@ -175,14 +171,14 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, bool clear, usz alignme
self.unused_page = page.prev_arena; self.unused_page = page.prev_arena;
page.prev_arena = null; page.prev_arena = null;
} }
if (!page) return self._alloc_new(size, alignment, offset); if (!page) return self._alloc_new(size, alignment);
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset; void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
usz new_used = start - page.memory + size; usz new_used = start - page.memory + size;
if ALLOCATE_NEW: (new_used > page.total) if ALLOCATE_NEW: (new_used > page.total)
{ {
if ((page = self.unused_page)) if ((page = self.unused_page))
{ {
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset; start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
new_used = start + size - page.memory; new_used = start + size - page.memory;
if (page.total >= new_used) if (page.total >= new_used)
{ {
@@ -192,7 +188,7 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, bool clear, usz alignme
break ALLOCATE_NEW; break ALLOCATE_NEW;
} }
} }
return self._alloc_new(size, alignment, offset); return self._alloc_new(size, alignment);
} }
page.used = new_used; page.used = new_used;
assert(start + size == page.memory + page.used); assert(start + size == page.memory + page.used);
@@ -201,6 +197,6 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, bool clear, usz alignme
chunk.size = size; chunk.size = size;
return mem; return mem;
|}!; |}!;
if (clear) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT); if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
return ptr; return ptr;
} }

View File

@@ -1,9 +1,9 @@
// Copyright (c) 2021-2023 Christoffer Lerno. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator; module std::core::mem::allocator;
import std::math;
struct SimpleHeapAllocator (Allocator) struct SimpleHeapAllocator (Allocator)
{ {
@@ -11,39 +11,29 @@ struct SimpleHeapAllocator (Allocator)
Header* free_list; Header* free_list;
} }
/** <*
* @require allocator "An underlying memory provider must be given" @require allocator != null "An underlying memory provider must be given"
* @require !self.free_list "The allocator may not be already initialized" @require !self.free_list "The allocator may not be already initialized"
**/ *>
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator) fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
{ {
self.alloc_fn = allocator; self.alloc_fn = allocator;
self.free_list = null; self.free_list = null;
} }
fn void*! SimpleHeapAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{ {
if (!size) return null; if (init_type == ZERO)
if (clear)
{ {
return alignment > 0 ? @aligned_calloc(self._calloc, size, alignment, offset) : self._calloc(size); return alignment > 0 ? @aligned_alloc(self._calloc, size, alignment) : self._calloc(size);
} }
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment, offset) : self._alloc(size); return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size);
} }
fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{ {
if (!size)
{
self.release(old_pointer, alignment > 0);
return null;
}
if (!old_pointer)
{
return self.acquire(size, true, alignment, offset);
}
return alignment > 0 return alignment > 0
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment, offset) ? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment)
: self._realloc(old_pointer, size); : self._realloc(old_pointer, size);
} }
@@ -59,9 +49,9 @@ fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dyn
} }
} }
/** <*
* @require old_pointer && bytes > 0 @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. // Find the block header.
@@ -108,7 +98,7 @@ fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
return current + 1; return current + 1;
case current.size > aligned_bytes: case current.size > aligned_bytes:
Header* unallocated = (Header*)((char*)current + aligned_bytes + Header.sizeof); Header* unallocated = (Header*)((char*)current + aligned_bytes + Header.sizeof);
unallocated.size = current.size - aligned_bytes; unallocated.size = current.size - aligned_bytes - Header.sizeof;
unallocated.next = current.next; unallocated.next = current.next;
if (current == self.free_list) if (current == self.free_list)
{ {

View File

@@ -1,49 +1,147 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator; module std::core::mem::allocator @if(env::LIBC);
import std::io;
import libc; import libc;
const LibcAllocator LIBC_ALLOCATOR = {}; const LibcAllocator LIBC_ALLOCATOR = {};
distinct 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");
module std::core::mem::allocator @if(env::POSIX);
import std::os;
import libc;
distinct LibcAllocator (Allocator) = uptr; fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
fn void*! LibcAllocator.acquire(&self, usz bytes, bool clear, usz alignment, usz offset) @dynamic
{ {
assert(alignment != 0 || offset == 0); if (init_type == ZERO)
if (clear)
{ {
void* data = alignment ? @aligned_calloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment, offset)!! : libc::calloc(bytes, 1); void* data @noinit;
return data ?: AllocationFailure.OUT_OF_MEMORY?; if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
} }
else else
{ {
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment, offset)!! : libc::malloc(bytes); void* data @noinit;
if (!data) return AllocationFailure.OUT_OF_MEMORY?; if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
$if env::TESTING: {
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
$endif }
return data; else
{
if (!(data = libc::malloc(bytes))) return AllocationFailure.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, usz offset) @dynamic fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{ {
assert(alignment != 0 || offset == 0); if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
if (!new_bytes) void* new_ptr;
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return AllocationFailure.OUT_OF_MEMORY?;
$switch
$case env::DARWIN:
usz old_usable_size = darwin::malloc_size(old_ptr);
$case env::LINUX:
usz old_usable_size = linux::malloc_usable_size(old_ptr);
$default:
usz old_usable_size = new_bytes;
$endswitch
usz copy_size = new_bytes < old_usable_size ? new_bytes : old_usable_size;
mem::copy(new_ptr, old_ptr, copy_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
libc::free(old_ptr);
return new_ptr;
}
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
libc::free(old_ptr);
}
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
{
if (init_type == ZERO)
{ {
self.release(old_ptr, alignment > 0); if (alignment > 0)
return null; {
} return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
if (!old_ptr) }
{ return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
return self.acquire(new_bytes, true, alignment, offset);
} }
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
if (!data) return AllocationFailure.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
{
if (alignment) if (alignment)
{ {
void* data = @aligned_realloc(fn void*(usz bytes) => libc::calloc(bytes, 1), libc::free, old_ptr, new_bytes, alignment, offset)!!; return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
}
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
}
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
if (aligned)
{
win32::_aligned_free(old_ptr);
return;
}
libc::free(old_ptr);
}
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
{
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?;
}
else
{
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
if (!data) return AllocationFailure.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
{
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 ?: AllocationFailure.OUT_OF_MEMORY?;
} }
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?; return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;

View File

@@ -2,7 +2,7 @@ module std::core::mem::allocator;
struct OnStackAllocator (Allocator) struct OnStackAllocator (Allocator)
{ {
Allocator* backing_allocator; Allocator backing_allocator;
char[] data; char[] data;
usz used; usz used;
OnStackAllocatorExtraChunk* chunk; OnStackAllocatorExtraChunk* chunk;
@@ -16,11 +16,12 @@ struct OnStackAllocatorExtraChunk @local
void* data; void* data;
} }
/** <*
* @param [&inout] allocator Initialize a memory arena for use using the provided bytes.
* Initialize a memory arena for use using the provided bytes.
**/ @param [&inout] allocator
fn void OnStackAllocator.init(&self, char[] data, Allocator* allocator) *>
fn void OnStackAllocator.init(&self, char[] data, Allocator allocator)
{ {
self.data = data; self.data = data;
self.backing_allocator = allocator; self.backing_allocator = allocator;
@@ -34,15 +35,15 @@ fn void OnStackAllocator.free(&self)
{ {
if (chunk.is_aligned) if (chunk.is_aligned)
{ {
self.backing_allocator.free_aligned(chunk.data); allocator::free_aligned(self.backing_allocator, chunk.data);
} }
else else
{ {
self.backing_allocator.free(chunk.data); allocator::free(self.backing_allocator, chunk.data);
} }
void* old = chunk; void* old = chunk;
chunk = chunk.prev; chunk = chunk.prev;
self.backing_allocator.free(old); allocator::free(self.backing_allocator, old);
} }
self.chunk = null; self.chunk = null;
self.used = 0; self.used = 0;
@@ -51,15 +52,17 @@ fn void OnStackAllocator.free(&self)
struct OnStackAllocatorHeader struct OnStackAllocatorHeader
{ {
usz size; usz size;
char[*] data; char[?] data;
} }
<*
@require old_pointer != null
*>
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{ {
if (!old_pointer) return;
if (allocation_in_stack_mem(self, old_pointer)) return; if (allocation_in_stack_mem(self, old_pointer)) return;
on_stack_allocator_remove_chunk(self, old_pointer); 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 fn bool allocation_in_stack_mem(OnStackAllocator* a, void* ptr) @local
@@ -76,7 +79,7 @@ fn void on_stack_allocator_remove_chunk(OnStackAllocator* a, void* ptr) @local
if (chunk.data == ptr) if (chunk.data == ptr)
{ {
*addr = chunk.prev; *addr = chunk.prev;
a.backing_allocator.free(chunk); allocator::free(a.backing_allocator, chunk);
return; return;
} }
addr = &chunk.prev; addr = &chunk.prev;
@@ -96,58 +99,51 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a
return null; return null;
} }
/** <*
* @require size > 0 @require size > 0
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big` @require old_pointer != null
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big` @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require offset <= size && offset >= 0 *>
* @require mem::aligned_offset(offset, OnStackAllocatorExtraChunk.alignof) == offset 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, usz offset) @dynamic
{ {
if (!allocation_in_stack_mem(self, old_pointer)) if (!allocation_in_stack_mem(self, old_pointer))
{ {
OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(self, old_pointer); OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(self, old_pointer);
assert(chunk, "Tried to realloc pointer not belonging to the allocator"); assert(chunk, "Tried to realloc pointer not belonging to the allocator");
return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment, offset)!; return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment)!;
} }
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof; OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
usz old_size = header.size; usz old_size = header.size;
void* mem = self.acquire(size, true, alignment, offset)!; void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem; return mem;
} }
/** <*
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big` @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big` @require size > 0
* @require offset <= size && offset >= 0 *>
* @require offset == 0 || alignment > 0 fn void*! OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
* @require mem::aligned_offset(offset, OnStackAllocatorHeader.alignof) == offset
**/
fn void*! OnStackAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{ {
if (size == 0) return null;
bool aligned = alignment > 0; bool aligned = alignment > 0;
alignment = alignment_for_allocation(alignment); alignment = alignment_for_allocation(alignment);
usz total_len = self.data.len; usz total_len = self.data.len;
void* start_mem = self.data.ptr; void* start_mem = self.data.ptr;
void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof + offset; void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof ;
void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment); void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz end = (usz)(aligned_pointer_to_offset - self.data.ptr) + size - offset; usz end = (usz)(mem - self.data.ptr) + size;
Allocator* backing_allocator = self.backing_allocator; Allocator backing_allocator = self.backing_allocator;
if (end > total_len) if (end > total_len)
{ {
OnStackAllocatorExtraChunk* chunk = backing_allocator.alloc_checked(OnStackAllocatorExtraChunk.sizeof)!; OnStackAllocatorExtraChunk* chunk = allocator::alloc_try(backing_allocator, OnStackAllocatorExtraChunk)!;
defer catch backing_allocator.free(chunk); defer catch allocator::free(backing_allocator, chunk);
defer try self.chunk = chunk; defer try self.chunk = chunk;
*chunk = { .prev = self.chunk, .is_aligned = aligned }; *chunk = { .prev = self.chunk, .is_aligned = aligned };
return chunk.data = backing_allocator.acquire(size, clear, aligned ? alignment : 0, offset)!; return chunk.data = backing_allocator.acquire(size, init_type, aligned ? alignment : 0)!;
} }
self.used = end; self.used = end;
void *mem = aligned_pointer_to_offset - offset;
OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader.sizeof; OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader.sizeof;
header.size = size; header.size = size;
return mem; return mem;

View File

@@ -1,19 +1,19 @@
module std::core::mem::allocator; module std::core::mem::allocator;
import std::io; import std::io, std::math;
struct TempAllocatorChunk @local struct TempAllocatorChunk @local
{ {
usz size; usz size;
char[*] data; char[?] data;
} }
struct TempAllocator (Allocator) struct TempAllocator (Allocator)
{ {
Allocator* backing_allocator; Allocator backing_allocator;
TempAllocatorPage* last_page; TempAllocatorPage* last_page;
usz used; usz used;
usz capacity; usz capacity;
char[*] data; char[?] data;
} }
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u; const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
@@ -26,18 +26,18 @@ struct TempAllocatorPage
usz mark; usz mark;
usz size; usz size;
usz ident; usz ident;
char[*] data; char[?] data;
} }
macro usz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED; macro usz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED; macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
/** <*
* @require size >= 16 @require size >= 16
**/ *>
fn TempAllocator*! new_temp(usz size, Allocator* allocator) fn TempAllocator*! new_temp_allocator(usz size, Allocator allocator)
{ {
TempAllocator* temp = allocator.alloc_checked(TempAllocator.sizeof + size)!; TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
temp.last_page = null; temp.last_page = null;
temp.backing_allocator = allocator; temp.backing_allocator = allocator;
temp.used = 0; temp.used = 0;
@@ -45,6 +45,13 @@ fn TempAllocator*! new_temp(usz size, Allocator* allocator)
return temp; return temp;
} }
fn void TempAllocator.destroy(&self)
{
self.reset(0);
if (self.last_page) (void)self._free_page(self.last_page);
allocator::free(self.backing_allocator, self);
}
fn usz TempAllocator.mark(&self) @dynamic => self.used; fn usz TempAllocator.mark(&self) @dynamic => self.used;
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
@@ -53,6 +60,7 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
if (old_pointer + old_size == &self.data[self.used]) if (old_pointer + old_size == &self.data[self.used])
{ {
self.used -= old_size; self.used -= old_size;
asan::poison_memory_region(&self.data[self.used], old_size);
} }
} }
fn void TempAllocator.reset(&self, usz mark) @dynamic fn void TempAllocator.reset(&self, usz mark) @dynamic
@@ -60,22 +68,35 @@ fn void TempAllocator.reset(&self, usz mark) @dynamic
TempAllocatorPage *last_page = self.last_page; TempAllocatorPage *last_page = self.last_page;
while (last_page && last_page.mark > mark) while (last_page && last_page.mark > mark)
{ {
self.used = last_page.mark;
TempAllocatorPage *to_free = last_page; TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page; last_page = last_page.prev_page;
self._free_page(to_free)!!; self._free_page(to_free)!!;
} }
self.last_page = last_page; 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; 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; void* mem = page.start;
if (page.is_aligned()) return self.backing_allocator.free_aligned(mem); return self.backing_allocator.release(mem, page.is_aligned());
return self.backing_allocator.free(mem);
} }
fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment, usz offset) @inline @local fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
{ {
// Then the actual start pointer: // Then the actual start pointer:
void* real_pointer = page.start; void* real_pointer = page.start;
@@ -90,53 +111,36 @@ fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size,
*pointer_to_prev = page.prev_page; *pointer_to_prev = page.prev_page;
usz page_size = page.pagesize(); usz page_size = page.pagesize();
// Clear on size > original size. // Clear on size > original size.
void* data = self.acquire(size, size > page_size, alignment, offset)!; void* data = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
if (page.is_aligned()) self.backing_allocator.release(real_pointer, page.is_aligned());
{
self.backing_allocator.free_aligned(real_pointer);
}
else
{
self.backing_allocator.free(real_pointer);
}
return data; return data;
} }
fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment, usz offset) @dynamic fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
{ {
if (!size)
{
self.release(pointer, alignment > 0);
return null;
}
if (!pointer)
{
return self.acquire(size, true, alignment, offset);
}
TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof; TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof;
if (chunk.size == (usz)-1) if (chunk.size == (usz)-1)
{ {
assert(self.last_page, "Realloc of non temp pointer"); assert(self.last_page, "Realloc of non temp pointer");
// First grab the page // First grab the page
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof; TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
return self._realloc_page(page, size, alignment, offset); return self._realloc_page(page, size, alignment);
} }
// TODO optimize last allocation TempAllocatorChunk* data = self.acquire(size, NO_ZERO, alignment)!;
TempAllocatorChunk* data = self.acquire(size, size > chunk.size, alignment, offset)!;
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return data; return data;
} }
/** <*
* @require !alignment || math::is_power_of_2(alignment) @require size > 0
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big` @require !alignment || math::is_power_of_2(alignment)
**/ @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic *>
fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{ {
if (!size) return null;
alignment = alignment_for_allocation(alignment); alignment = alignment_for_allocation(alignment);
void* start_mem = &self.data; void* start_mem = &self.data;
void* starting_ptr = start_mem + self.used; void* starting_ptr = start_mem + self.used;
@@ -144,17 +148,18 @@ fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz
void* mem = aligned_header_start + TempAllocatorChunk.sizeof; void* mem = aligned_header_start + TempAllocatorChunk.sizeof;
if (alignment > TempAllocatorChunk.alignof) if (alignment > TempAllocatorChunk.alignof)
{ {
mem = mem::aligned_pointer(mem + offset, alignment) - offset; mem = mem::aligned_pointer(mem, alignment);
} }
usz new_usage = (usz)(mem - start_mem) + size; usz new_usage = (usz)(mem - start_mem) + size;
// Arena alignment, simple! // Arena allocation, simple!
if (new_usage <= self.capacity) if (new_usage <= self.capacity)
{ {
asan::unpoison_memory_region(starting_ptr, new_usage - self.used);
TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof; TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof;
chunk_start.size = size; chunk_start.size = size;
self.used = new_usage; self.used = new_usage;
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT); if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem; return mem;
} }
@@ -162,19 +167,22 @@ fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz
TempAllocatorPage* page; TempAllocatorPage* page;
// We have something we need to align. // We have something we need to align.
if (alignment > mem::DEFAULT_MEM_ALIGNMENT || offset) if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{ {
// This is actually simpler, since it will create the offset for us. // This is actually simpler, since it will create the offset for us.
usz total_alloc_size = TempAllocatorPage.sizeof + size; usz total_alloc_size = mem::aligned_offset(TempAllocatorPage.sizeof + size, alignment);
if (clear) if (init_type == ZERO)
{ {
page = self.backing_allocator.calloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!; mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
} }
else else
{ {
page = self.backing_allocator.alloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!; mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
} }
page.start = page; void* start = mem;
mem += mem::aligned_offset(TempAllocatorPage.sizeof, alignment);
page = (TempAllocatorPage*)mem - 1;
page.start = start;
page.size = size | PAGE_IS_ALIGNED; page.size = size | PAGE_IS_ALIGNED;
} }
else else
@@ -182,7 +190,7 @@ fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz
// Here we might need to pad // Here we might need to pad
usz padded_header_size = mem::aligned_offset(TempAllocatorPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT); usz padded_header_size = mem::aligned_offset(TempAllocatorPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT);
usz total_alloc_size = padded_header_size + size; usz total_alloc_size = padded_header_size + size;
void* alloc = self.backing_allocator.acquire(total_alloc_size, clear, 0, 0)!; void* alloc = self.backing_allocator.acquire(total_alloc_size, init_type, 0)!;
// Find the page. // Find the page.
page = alloc + padded_header_size - TempAllocatorPage.sizeof; page = alloc + padded_header_size - TempAllocatorPage.sizeof;

View File

@@ -1,10 +1,9 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator; module std::core::mem::allocator;
import std::collections::map; import std::collections, std::io, std::os::backtrace;
import std::collections::list;
const MAX_BACKTRACE = 16; const MAX_BACKTRACE = 16;
struct Allocation struct Allocation
@@ -21,106 +20,90 @@ def AllocMap = HashMap(<uptr, Allocation>);
// is not compatible with allocators that uses mark() // is not compatible with allocators that uses mark()
struct TrackingAllocator (Allocator) struct TrackingAllocator (Allocator)
{ {
Allocator* inner_allocator; Allocator inner_allocator;
AllocMap map; AllocMap map;
usz mem_total; usz mem_total;
usz allocs_total; usz allocs_total;
} }
/** <*
* Initialize a tracking allocator to wrap (and track) another 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) fn void TrackingAllocator.init(&self, Allocator allocator)
{ {
*self = { .inner_allocator = allocator }; *self = { .inner_allocator = allocator };
self.map.init_new(.allocator = allocator); self.map.new_init(allocator: allocator);
} }
/** <*
* Free this tracking allocator. Free this tracking allocator.
**/ *>
fn void TrackingAllocator.free(&self) fn void TrackingAllocator.free(&self)
{ {
self.map.free(); self.map.free();
*self = {}; *self = {};
} }
/** <*
* @return "the total allocated memory not yet freed." @return "the total allocated memory not yet freed."
**/ *>
fn usz TrackingAllocator.allocated(&self) fn usz TrackingAllocator.allocated(&self) => @pool()
{ {
usz allocated = 0; usz allocated = 0;
@pool() foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
{
foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
};
return allocated; return allocated;
} }
/** <*
* @return "the total memory allocated (freed or not)." @return "the total memory allocated (freed or not)."
**/ *>
fn usz TrackingAllocator.total_allocated(&self) => self.mem_total; fn usz TrackingAllocator.total_allocated(&self) => self.mem_total;
/** <*
* @return "the total number of allocations (freed or not)." @return "the total number of allocations (freed or not)."
**/ *>
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total; fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator* allocator) fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
{ {
return self.map.value_tlist(); return self.map.value_tlist();
} }
/** <*
* @return "the number of non-freed allocations." @return "the number of non-freed allocations."
**/ *>
fn usz TrackingAllocator.allocation_count(&self) => self.map.count; fn usz TrackingAllocator.allocation_count(&self) => self.map.count;
fn void*! TrackingAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{ {
void* data = self.inner_allocator.acquire(size, clear, alignment, offset)!; void* data = self.inner_allocator.acquire(size, init_type, alignment)!;
self.allocs_total++; self.allocs_total++;
if (data) void*[MAX_BACKTRACE] bt;
{ backtrace::capture_current(&bt);
void*[MAX_BACKTRACE] bt; self.map.set((uptr)data, { data, size, bt });
backtrace::capture_current(&bt); self.mem_total += size;
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
self.allocs_total++;
}
return data; return data;
} }
fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{ {
void* data = self.inner_allocator.resize(old_pointer, size, alignment, offset)!; void* data = self.inner_allocator.resize(old_pointer, size, alignment)!;
if (old_pointer) self.map.remove((uptr)old_pointer);
{ void*[MAX_BACKTRACE] bt;
self.map.remove((uptr)old_pointer); backtrace::capture_current(&bt);
} self.map.set((uptr)data, { data, size, bt });
if (data) self.mem_total += size;
{ self.allocs_total++;
void*[MAX_BACKTRACE] bt;
backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
self.allocs_total++;
}
return data; return data;
} }
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
{ {
if (old_pointer) if (catch self.map.remove((uptr)old_pointer))
{ {
if (catch self.map.remove((uptr)old_pointer)) unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
{
assert(false, "Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
}
} }
self.inner_allocator.release(old_pointer, is_aligned); self.inner_allocator.release(old_pointer, is_aligned);
} }
@@ -130,101 +113,103 @@ fn void TrackingAllocator.clear(&self)
self.map.clear(); 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.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 total = 0;
usz entries = 0; usz entries = 0;
bool leaks = false; 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, "===============================")!;
} Allocation[] allocs = self.map.value_tlist();
else 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 ==================================")!; entries++;
io::fprintn(out, "Size in bytes Address Function ")!; total += allocation.size;
foreach (i, &allocation : allocs) io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
{
entries++;
total += allocation.size;
BacktraceList backtraces = {};
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
if (allocation.backtrace[3])
{
trace = backtrace::symbolize_backtrace(allocation.backtrace[3:1], mem::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, "===================================================================================")!;
} }
io::fprintn(out, "===============================")!;
} }
else else
{ {
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!; 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, "===================================================================================")!;
} }
io::fprintfn(out, "- Total currently allocated memory: %d", total)!; }
io::fprintfn(out, "- Total current allocations: %d", entries)!; else
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)!; io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
if (leaks) }
{ io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
io::fprintn(out)!; io::fprintfn(out, "- Total current allocations: %d", entries)!;
io::fprintn(out, "Full leak report:")!; io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
foreach (i, &allocation : allocs) io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
{ if (leaks)
if (!allocation.backtrace[3]) {
{ io::fprintn(out)!;
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!; io::fprintn(out, "Full leak report:")!;
continue; 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)
{
end = j;
break;
} }
BacktraceList backtraces = {}; }
usz end = MAX_BACKTRACE; BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], allocator::temp())!;
foreach (j, val : allocation.backtrace) io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
{ foreach (trace : list)
if (!val) {
{ if (trace.has_file())
end = j; {
break; io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
} continue;
} }
BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], mem::temp())!; if (trace.is_unknown())
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!; {
foreach (trace : list) io::fprintfn(out, " ??? (in unknown)");
{ continue;
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);
} }
} io::fprintfn(out, " %s (source unavailable)", trace.function);
} }
}; }
}
} }

View File

@@ -1,11 +1,12 @@
module std::core::array; module std::core::array;
import std::core::array::slice;
/** <*
* @param [in] array @param [in] array
* @param [in] element @param [in] element
* @return "the first index of the element" @return "the first index of the element"
* @return! SearchResult.MISSING @return! SearchResult.MISSING
**/ *>
macro index_of(array, element) macro index_of(array, element)
{ {
foreach (i, &e : array) foreach (i, &e : array)
@@ -15,12 +16,26 @@ macro index_of(array, element)
return SearchResult.MISSING?; return SearchResult.MISSING?;
} }
/** <*
* @param [in] array @require @typekind(array_ptr) == POINTER
* @param [in] element @require @typekind(*array_ptr) == VECTOR || @typekind(*array_ptr) == ARRAY
* @return "the last index of the element" @require @typekind((*array_ptr)[0]) == VECTOR || @typekind((*array_ptr)[0]) == ARRAY
* @return! SearchResult.MISSING *>
**/ macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
{
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 };
}
<*
@param [in] array
@param [in] element
@return "the last index of the element"
@return! SearchResult.MISSING
*>
macro rindex_of(array, element) macro rindex_of(array, element)
{ {
foreach_r (i, &e : array) foreach_r (i, &e : array)
@@ -30,21 +45,21 @@ macro rindex_of(array, element)
return SearchResult.MISSING?; return SearchResult.MISSING?;
} }
/** <*
* Concatenate two arrays or subarrays, returning a subarray containing the concatenation of them. Concatenate two arrays or slices, returning a slice containing the concatenation of them.
*
* @param [in] arr1 @param [in] arr1
* @param [in] arr2 @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) == SUBARRAY || @typekind(arr1) == ARRAY @require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
* @require @typekind(arr2) == SUBARRAY || @typekind(arr2) == 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 @ensure result.len == arr1.len + arr2.len
**/ *>
macro concat_new(arr1, arr2, Allocator* allocator = mem::heap()) macro concat(arr1, arr2, Allocator allocator) @nodiscard
{ {
var $Type = $typeof(arr1[0]); var $Type = $typeof(arr1[0]);
$Type[] result = allocator.new_array($Type, arr1.len + arr2.len); $Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len);
if (arr1.len > 0) if (arr1.len > 0)
{ {
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $Type.alignof, $Type.alignof); mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $Type.alignof, $Type.alignof);
@@ -55,16 +70,124 @@ macro concat_new(arr1, arr2, Allocator* allocator = mem::heap())
} }
return result; return result;
} }
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
/** @param [in] arr1
* Concatenate two arrays or subarrays, returning a subarray containing the concatenation of them, @param [in] arr2
* allocated using the temp allocator. @param [&inout] allocator "The allocator to use, default is the heap allocator"
* @require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
* @param [in] arr1 @require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
* @param [in] arr2 @require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
* @require @typekind(arr1) == SUBARRAY || @typekind(arr1) == ARRAY @ensure return.len == arr1.len + arr2.len
* @require @typekind(arr2) == SUBARRAY || @typekind(arr2) == ARRAY *>
* @require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type" macro concat_new(arr1, arr2, Allocator allocator = allocator::heap()) @nodiscard
* @ensure result.len == arr1.len + arr2.len {
**/ return concat(arr1, arr2, allocator);
macro tconcat(arr1, arr2) => concat(arr1, arr2, mem::temp()); }
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them,
allocated using the temp allocator.
@param [in] arr1
@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 return.len == arr1.len + arr2.len
*>
macro tconcat(arr1, arr2) @nodiscard => concat(arr1, arr2, allocator::temp());
module std::core::array::slice(<Type>);
struct Slice2d
{
Type* ptr;
usz inner_len;
usz ystart;
usz ylen;
usz xstart;
usz xlen;
}
fn usz Slice2d.len(&self) @operator(len)
{
return self.ylen;
}
fn usz Slice2d.count(&self)
{
return self.ylen * self.xlen;
}
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
{
foreach (y, line : *self)
{
foreach (x, val : line)
{
@body({ x, y }, val);
}
}
}
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
{
foreach (y, line : *self)
{
foreach (x, &val : line)
{
@body({ x, y }, val);
}
}
}
<*
@require idy >= 0 && idy < self.ylen
*>
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
*>
fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0)
{
if (xlen < 1) xlen = self.xlen + xlen;
if (ylen < 1) ylen = self.ylen + ylen;
return { self.ptr, self.inner_len, y + self.ystart, ylen, x + self.xstart, xlen };
}

View File

@@ -87,10 +87,10 @@ bitstruct UInt128LE : uint128 @littleendian
uint128 val : 0..127; uint128 val : 0..127;
} }
/** <*
* @require is_array_or_sub_of_char(bytes) "argument must be an array, a pointer to an array or a subarray of char" @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_bitorder($Type) "type must be a bitorder integer"
**/ *>
macro read(bytes, $Type) macro read(bytes, $Type)
{ {
char[] s; char[] s;
@@ -103,10 +103,10 @@ macro read(bytes, $Type)
return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val; return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
} }
/** <*
* @require is_arrayptr_or_sub_of_char(bytes) "argument must be a pointer to an array or a subarray of char" @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_bitorder($Type) "type must be a bitorder integer"
**/ *>
macro write(x, bytes, $Type) macro write(x, bytes, $Type)
{ {
char[] s; char[] s;
@@ -144,7 +144,7 @@ macro is_bitorder($Type)
$endswitch $endswitch
} }
macro bool is_array_or_sub_of_char(bytes) macro bool is_array_or_slice_of_char(bytes)
{ {
$switch (@typekind(bytes)) $switch (@typekind(bytes))
$case POINTER: $case POINTER:
@@ -154,7 +154,7 @@ macro bool is_array_or_sub_of_char(bytes)
return $Inner2.typeid == char.typeid; return $Inner2.typeid == char.typeid;
$endif $endif
$case ARRAY: $case ARRAY:
$case SUBARRAY: $case SLICE:
var $Inner = $typefrom($typeof(bytes).inner); var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid; return $Inner.typeid == char.typeid;
$default: $default:
@@ -162,7 +162,7 @@ macro bool is_array_or_sub_of_char(bytes)
$endswitch $endswitch
} }
macro bool is_arrayptr_or_sub_of_char(bytes) macro bool is_arrayptr_or_slice_of_char(bytes)
{ {
$switch (@typekind(bytes)) $switch (@typekind(bytes))
$case POINTER: $case POINTER:
@@ -171,7 +171,7 @@ macro bool is_arrayptr_or_sub_of_char(bytes)
var $Inner2 = $typefrom($Inner.inner); var $Inner2 = $typefrom($Inner.inner);
return $Inner2.typeid == char.typeid; return $Inner2.typeid == char.typeid;
$endif $endif
$case SUBARRAY: $case SLICE:
var $Inner = $typefrom($typeof(bytes).inner); var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid; return $Inner.typeid == char.typeid;
$default: $default:

View File

@@ -1,123 +1,132 @@
// Copyright (c) 2021-2022 Christoffer Lerno and contributors. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno and contributors. All rights reserved.
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::builtin; module std::core::builtin;
import libc; import libc, std::hash, std::io, std::os::backtrace;
import std::hash;
/** /*
* Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds. Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
**/ */
fault IteratorResult fault IteratorResult { NO_MORE_ELEMENT }
{
NO_MORE_ELEMENT
}
/** /*
* Use `SearchResult` when trying to return a value from some collection but the element is missing. Use `SearchResult` when trying to return a value from some collection but the element is missing.
**/ */
fault SearchResult fault SearchResult { MISSING }
{
MISSING
}
/** /*
* Use `CastResult` when an attempt at conversion fails. Use `CastResult` when an attempt at conversion fails.
**/ */
fault CastResult fault CastResult { TYPE_MISMATCH }
{
TYPE_MISMATCH
}
/**
* Stores a variable on the stack, then restores it at the end of the def VoidFn = fn void();
* macro scope.
* <*
* @param variable `the variable to store and restore` Stores a variable on the stack, then restores it at the end of the
**/ macro scope.
macro void @scope(&variable; @body) @builtin
@param #variable `the variable to store and restore`
@require values::@is_lvalue(#variable)
*>
macro void @scope(#variable; @body) @builtin
{ {
var temp = *variable; var temp = #variable;
defer *variable = temp; defer #variable = temp;
@body(); @body();
} }
/** <*
* Swap two variables 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; var temp = #a;
*a = *b; #a = #b;
*b = temp; #b = temp;
} }
/** <*
* Convert an `any` type to a type, returning an failure if there is a type mismatch. 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 v `the any to convert to the given type.`
* @param $Type `the type to convert to` @param $Type `the type to convert to`
* @return `The any.ptr converted to its type.` @return `The any.ptr converted to its type.`
* @ensure @typeis(return, $Type*) @ensure @typeis(return, $Type*)
* @return! CastResult.TYPE_MISMATCH @return! CastResult.TYPE_MISMATCH
**/ *>
macro anycast(any* v, $Type) @builtin macro anycast(any v, $Type) @builtin
{ {
if (v.type != $Type.typeid) return CastResult.TYPE_MISMATCH?; if (v.type != $Type.typeid) return CastResult.TYPE_MISMATCH?;
return ($Type*)v.ptr; 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) => @pool()
{ {
@pool() 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)
{ {
void*[256] buffer; if (i < backtraces_to_ignore) continue;
void*[] backtraces = backtrace::capture_current(&buffer); String inline_suffix = trace.is_inline ? " [inline]" : "";
backtraces_to_ignore++; if (trace.is_unknown())
BacktraceList! backtrace = backtrace::symbolize_backtrace(backtraces, mem::temp()); {
if (catch backtrace) return false; io::eprintfn(" in ???%s", inline_suffix);
if (backtrace.len() <= backtraces_to_ignore) return false; continue;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
{
if (i < backtraces_to_ignore) continue;
if (trace.is_unknown())
{
io::eprintn(" in ???");
continue;
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]", trace.function, trace.file, trace.line, trace.object_file);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]", trace.function, trace.object_file);
} }
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) fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
{ {
$if $defined(io::stderr): $if $defined(io::stderr):
if (!print_backtrace(message, 2)) if (!print_backtrace(message, 2))
{ {
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line); io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
return;
} }
$endif $endif
$$trap(); $$trap();
} }
macro void abort(String string = "Unrecoverable error reached", ...) @builtin @noreturn
{
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
$$trap();
}
bool in_panic @local = false;
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE) fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
{ {
if (in_panic)
{
io::eprintn("Panic inside of panic.");
return;
}
in_panic = true;
$if $defined(io::stderr): $if $defined(io::stderr):
io::eprint("\nERROR: '"); io::eprint("\nERROR: '");
io::eprint(message); io::eprint(message);
io::eprintfn("', in %s (%s:%d)", function, file, line); io::eprintfn("', in %s (%s:%d)", function, file, line);
$endif $endif
in_panic = false;
$$trap(); $$trap();
} }
@@ -127,36 +136,54 @@ PanicFn panic = &default_panic;
fn void panicf(String fmt, String file, String function, uint line, args...) fn void panicf(String fmt, String file, String function, uint line, args...)
{ {
@stack_mem(512; Allocator* allocator) if (in_panic)
{
io::eprint("Panic inside of panic: ");
io::eprintn(fmt);
return;
}
in_panic = true;
@stack_mem(512; Allocator allocator)
{ {
DString s; DString s;
s.init_new(.allocator = allocator); s.new_init(allocator: allocator);
s.appendf(fmt, ...args); s.appendf(fmt, ...args);
in_panic = false;
panic(s.str_view(), file, function, line); panic(s.str_view(), file, function, line);
}; };
} }
/** <*
* Marks the path as unreachable. This will panic in safe mode, and in fast will simply be assumed Marks the path as unreachable. This will panic in safe mode, and in fast will simply be assumed
* never happens. 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 macro void unreachable(String string = "Unreachable statement reached.", ...) @builtin @noreturn
{ {
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat()); $if env::COMPILER_SAFE_MODE:
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
$endif;
$$unreachable(); $$unreachable();
} }
/** <*
* Marks the path as unsupported, this is similar to unreachable. 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 macro void unsupported(String string = "Unsupported function invoked") @builtin @noreturn
{ {
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat()); panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
$$unreachable(); $$unreachable();
} }
<*
Unconditionally break into an attached debugger when reached.
*>
macro void breakpoint() @builtin
{
$$breakpoint();
}
macro any_make(void* ptr, typeid type) @builtin macro any_make(void* ptr, typeid type) @builtin
{ {
return $$any_make(ptr, type); return $$any_make(ptr, type);
@@ -172,13 +199,13 @@ macro any.as_inner(&self)
return $$any_make(self.ptr, self.type.inner); return $$any_make(self.ptr, self.type.inner);
} }
/** <*
* @param expr "the expression to cast" @param expr "the expression to cast"
* @param $Type "the type to cast to" @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) @ensure @typeis(return, $Type)
**/ *>
macro bitcast(expr, $Type) @builtin macro bitcast(expr, $Type) @builtin
{ {
$if $Type.alignof <= $alignof(expr): $if $Type.alignof <= $alignof(expr):
@@ -190,82 +217,100 @@ macro bitcast(expr, $Type) @builtin
$endif $endif
} }
/** <*
* @param $Type `The type of the enum` @param $Type `The type of the enum`
* @param [in] enum_name `The name of the enum to search for` @param [in] enum_name `The name of the enum to search for`
* @require $Type.kindof == ENUM `Only enums may be used` @require $Type.kindof == ENUM `Only enums may be used`
* @ensure @typeis(return, $Type) @ensure @typeis(return, $Type)
* @return! SearchResult.MISSING @return! SearchResult.MISSING
**/ *>
macro enum_by_name($Type, String enum_name) @builtin macro enum_by_name($Type, String enum_name) @builtin
{ {
typeid x = $Type.typeid; typeid x = $Type.typeid;
foreach (i, name : x.names) 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 SearchResult.MISSING?;
} }
/** <*
* Mark an expression as likely to be true @param $Type `The type of the enum`
* @require $Type.kindof == ENUM `Only enums may be used`
* @param #value "expression to be marked likely" @require $defined($Type.#value) `Expected '#value' to match an enum associated value`
* @param $probability "in the range 0 - 1" @require $assignable(value, $typeof($Type{}.#value)) `Expected the value to match the type of the associated value`
* @require $probability >= 0 && $probability <= 1.0 @ensure @typeis(return, $Type)
**/ @return! SearchResult.MISSING
*>
macro @enum_from_value($Type, #value, value) @builtin
{
usz elements = $Type.elements;
foreach (e : $Type.values)
{
if (e.#value == value) return e;
}
return SearchResult.MISSING?;
}
<*
Mark an expression as likely to be true
@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 macro bool @likely(bool #value, $probability = 1.0) @builtin
{ {
$switch $switch
$case env::BUILTIN_EXPECT_IS_DISABLED: $case env::BUILTIN_EXPECT_IS_DISABLED:
return #value; return #value;
$case $probability == 1.0: $case $probability == 1.0:
return $$expect(#value, true); return $$expect(#value, true);
$default: $default:
return $$expect_with_probability(#value, true, $probability); return $$expect_with_probability(#value, true, $probability);
$endswitch $endswitch
} }
/** <*
* Mark an expression as unlikely to be true Mark an expression as unlikely to be true
*
* @param #value "expression to be marked unlikely" @param #value "expression to be marked unlikely"
* @param $probability "in the range 0 - 1" @param $probability "in the range 0 - 1"
* @require $probability >= 0 && $probability <= 1.0 @require $probability >= 0 && $probability <= 1.0
**/ *>
macro bool @unlikely(bool #value, $probability = 1.0) @builtin macro bool @unlikely(bool #value, $probability = 1.0) @builtin
{ {
$switch $switch
$case env::BUILTIN_EXPECT_IS_DISABLED: $case env::BUILTIN_EXPECT_IS_DISABLED:
return #value; return #value;
$case $probability == 1.0: $case $probability == 1.0:
return $$expect(#value, false); return $$expect(#value, false);
$default: $default:
return $$expect_with_probability(#value, false, $probability); return $$expect_with_probability(#value, false, $probability);
$endswitch $endswitch
} }
/** <*
* @require values::@is_int(#value) || values::@is_bool(#value) @require values::@is_int(#value) || values::@is_bool(#value)
* @require $assignable(expected, $typeof(#value)) @require $assignable(expected, $typeof(#value))
* @require $probability >= 0 && $probability <= 1.0 @require $probability >= 0 && $probability <= 1.0
**/ *>
macro @expect(#value, expected, $probability = 1.0) @builtin macro @expect(#value, expected, $probability = 1.0) @builtin
{ {
$switch $switch
$case env::BUILTIN_EXPECT_IS_DISABLED: $case env::BUILTIN_EXPECT_IS_DISABLED:
return #value == expected; return #value == expected;
$case $probability == 1.0: $case $probability == 1.0:
return $$expect(#value, ($typeof(#value))expected); return $$expect(#value, ($typeof(#value))expected);
$default: $default:
return $$expect_with_probability(#value, expected, $probability); return $$expect_with_probability(#value, expected, $probability);
$endswitch $endswitch
} }
/** <*
* Locality for prefetch, levels 0 - 3, corresponding Locality for prefetch, levels 0 - 3, corresponding
* to "extremely local" to "no locality" to "extremely local" to "no locality"
**/ *>
enum PrefetchLocality enum PrefetchLocality
{ {
NO_LOCALITY, NO_LOCALITY,
@@ -274,13 +319,13 @@ enum PrefetchLocality
VERY_NEAR, VERY_NEAR,
} }
/** <*
* Prefetch a pointer. Prefetch a pointer.
* @param [in] ptr `Pointer to prefetch` @param [in] ptr `Pointer to prefetch`
* @param $locality `Locality ranging from none to extremely local` @param $locality `Locality ranging from none to extremely local`
* @param $write `Prefetch for write, otherwise prefetch for read.` @param $write `Prefetch for write, otherwise prefetch for read.`
**/ *>
macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
{ {
$if !env::BUILTIN_PREFETCH_IS_DISABLED: $if !env::BUILTIN_PREFETCH_IS_DISABLED:
@@ -290,51 +335,93 @@ macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write =
macro swizzle(v, ...) @builtin macro swizzle(v, ...) @builtin
{ {
return $$swizzle(v, $vasplat()); return $$swizzle(v, $vasplat);
} }
macro swizzle2(v, v2, ...) @builtin macro swizzle2(v, v2, ...) @builtin
{ {
return $$swizzle2(v, v2, $vasplat()); return $$swizzle2(v, v2, $vasplat);
} }
<*
Return the excuse in the Optional if it is Empty, otherwise
return a null fault.
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
*>
macro anyfault @catch(#expr) @builtin macro anyfault @catch(#expr) @builtin
{ {
if (catch f = #expr) return f; if (catch f = #expr) return f;
return anyfault {}; return anyfault {};
} }
<*
Check if an Optional expression holds a value or is empty, returning true
if it has a value.
@require @typekind(#expr) == OPTIONAL : `@ok expects an Optional value`
*>
macro bool @ok(#expr) @builtin macro bool @ok(#expr) @builtin
{ {
if (catch #expr) return false; if (catch #expr) return false;
return true; 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 uint int.hash(int i) => i; macro isz @str_find(String $string, String $needle) @builtin => $$str_find($string, $needle);
macro uint uint.hash(uint i) => i; macro String @str_upper(String $str) @builtin => $$str_upper($str);
macro uint short.hash(short s) => s; macro String @str_lower(String $str) @builtin => $$str_lower($str);
macro uint ushort.hash(ushort s) => s; macro uint @str_hash(String $str) @builtin => $$str_hash($str);
macro uint char.hash(char c) => c;
macro uint ichar.hash(ichar c) => c; macro @generic_hash_core(h, value)
macro uint long.hash(long i) => (uint)((i >> 32) ^ i); {
macro uint ulong.hash(ulong i) => (uint)((i >> 32) ^ i); h ^= (uint)value; // insert lowest 32 bits
macro uint int128.hash(int128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^ i); h *= 0x96f59e5b; // diffuse them up
macro uint uint128.hash(uint128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^ i); h ^= h >> 16; // diffuse them down
macro uint bool.hash(bool b) => (uint)b; return h;
macro uint typeid.hash(typeid t) => ((ulong)(uptr)t).hash(); }
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::encode(c); macro uint String.hash(String c) => (uint)fnv32a::encode(c);
macro uint char[].hash(char[] 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 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; const MAX_FRAMEADDRESS = 128;
/** <*
* @require n >= 0 @require n >= 0
**/ *>
macro void* get_frameaddress(int n) macro void* get_frameaddress(int n)
{ {
if (n > MAX_FRAMEADDRESS) return null; if (n > MAX_FRAMEADDRESS) return null;
@@ -473,9 +560,9 @@ macro void* get_frameaddress(int n)
} }
} }
/** <*
* @require n >= 0 @require n >= 0
**/ *>
macro void* get_returnaddress(int n) macro void* get_returnaddress(int n)
{ {
if (n > MAX_FRAMEADDRESS) return null; if (n > MAX_FRAMEADDRESS) return null;
@@ -615,7 +702,7 @@ macro void* get_returnaddress(int n)
} }
module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS); module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
import libc; import libc, std::io;
fn void sig_panic(String message) fn void sig_panic(String message)
{ {
@@ -663,7 +750,7 @@ fn void install_signal_handler(CInt signal, SignalFunction func) @local
} }
// Clean this up // Clean this up
fn void install_signal_handlers() @init(101) @local fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE)
{ {
install_signal_handler(libc::SIGBUS, &sig_bus_error); install_signal_handler(libc::SIGBUS, &sig_bus_error);
install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault); install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);

View File

@@ -1,11 +1,11 @@
// Copyright (c) 2021-2022 Christoffer Lerno and contributors. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno and contributors. All rights reserved.
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::builtin; module std::core::builtin;
/** <*
* @require types::is_comparable_value(a) && types::is_comparable_value(b) @require types::@comparable_value(a) && types::@comparable_value(b)
**/ *>
macro less(a, b) @builtin macro less(a, b) @builtin
{ {
$switch $switch
@@ -18,9 +18,9 @@ macro less(a, b) @builtin
$endswitch $endswitch
} }
/** <*
* @require types::is_comparable_value(a) && types::is_comparable_value(b) @require types::@comparable_value(a) && types::@comparable_value(b)
**/ *>
macro less_eq(a, b) @builtin macro less_eq(a, b) @builtin
{ {
$switch $switch
@@ -33,9 +33,9 @@ macro less_eq(a, b) @builtin
$endswitch $endswitch
} }
/** <*
* @require types::is_comparable_value(a) && types::is_comparable_value(b) @require types::@comparable_value(a) && types::@comparable_value(b)
**/ *>
macro greater(a, b) @builtin macro greater(a, b) @builtin
{ {
$switch $switch
@@ -48,9 +48,9 @@ macro greater(a, b) @builtin
$endswitch $endswitch
} }
/** <*
* @require types::is_comparable_value(a) && types::is_comparable_value(b) @require types::@comparable_value(a) && types::@comparable_value(b)
**/ *>
macro int compare_to(a, b) @builtin macro int compare_to(a, b) @builtin
{ {
$switch $switch
@@ -62,9 +62,9 @@ macro int compare_to(a, b) @builtin
return (int)(a > b) - (int)(a < b); return (int)(a > b) - (int)(a < b);
$endswitch $endswitch
} }
/** <*
* @require types::is_comparable_value(a) && types::is_comparable_value(b) @require types::@comparable_value(a) && types::@comparable_value(b)
**/ *>
macro greater_eq(a, b) @builtin macro greater_eq(a, b) @builtin
{ {
$switch $switch
@@ -77,9 +77,9 @@ macro greater_eq(a, b) @builtin
$endswitch $endswitch
} }
/** <*
* @require types::is_equatable_value(a) && types::is_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 macro bool equals(a, b) @builtin
{ {
$switch $switch
@@ -97,13 +97,13 @@ macro bool equals(a, b) @builtin
macro min(x, ...) @builtin macro min(x, ...) @builtin
{ {
$if $vacount == 1: $if $vacount == 1:
return less(x, $vaarg(0)) ? x : $vaarg(0); return less(x, $vaarg[0]) ? x : $vaarg[0];
$else $else
var result = x; var result = x;
$for (var $i = 0; $i < $vacount; $i++) $for (var $i = 0; $i < $vacount; $i++)
if (less($vaarg($i), result)) if (less($vaarg[$i], result))
{ {
result = $vaarg($i); result = $vaarg[$i];
} }
$endfor $endfor
return result; return result;
@@ -113,13 +113,13 @@ macro min(x, ...) @builtin
macro max(x, ...) @builtin macro max(x, ...) @builtin
{ {
$if $vacount == 1: $if $vacount == 1:
return greater(x, $vaarg(0)) ? x : $vaarg(0); return greater(x, $vaarg[0]) ? x : $vaarg[0];
$else $else
var result = x; var result = x;
$for (var $i = 0; $i < $vacount; $i++) $for (var $i = 0; $i < $vacount; $i++)
if (greater($vaarg($i), result)) if (greater($vaarg[$i], result))
{ {
result = $vaarg($i); result = $vaarg[$i];
} }
$endfor $endfor
return result; return result;

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::cinterop; module std::core::cinterop;
@@ -29,6 +29,12 @@ def CUChar = char;
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid); def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
enum CBool : char
{
FALSE,
TRUE
}
// Helper macros // Helper macros
macro typeid signed_int_from_bitsize(usz $bitsize) @private macro typeid signed_int_from_bitsize(usz $bitsize) @private
{ {

View File

@@ -9,10 +9,10 @@ const uint UTF16_SURROGATE_BITS @private = 10;
const uint UTF16_SURROGATE_LOW_VALUE @private = 0xDC00; const uint UTF16_SURROGATE_LOW_VALUE @private = 0xDC00;
const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800; const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
/** <*
* @param c `The utf32 codepoint to convert` @param c `The utf32 codepoint to convert`
* @param [out] output `the resulting buffer` @param [out] output `the resulting buffer`
**/ *>
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 UnicodeResult.CONVERSION_FAILED?;
@@ -45,12 +45,12 @@ fn usz! char32_to_utf8(Char32 c, char[] output)
} }
} }
/** <*
* Convert a code pointer into 1-2 UTF16 characters. Convert a code pointer into 1-2 UTF16 characters.
*
* @param c `The character to convert.` @param c `The character to convert.`
* @param [inout] output `the resulting UTF16 buffer to write to.` @param [inout] output `the resulting UTF16 buffer to write to.`
**/ *>
fn void char32_to_utf16_unsafe(Char32 c, Char16** output) fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
{ {
if (c < UTF16_SURROGATE_OFFSET) if (c < UTF16_SURROGATE_OFFSET)
@@ -66,13 +66,13 @@ fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
(*output)++[0] = (Char16)low; (*output)++[0] = (Char16)low;
} }
/** <*
* Convert 1-2 UTF16 data points into UTF8. Convert 1-2 UTF16 data points into UTF8.
*
* @param [in] ptr `The UTF16 data to convert.` @param [in] ptr `The UTF16 data to convert.`
* @param [inout] available `amount of UTF16 data available.` @param [inout] available `amount of UTF16 data available.`
* @param [inout] output `the resulting utf8 buffer to write to.` @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; Char16 high = *ptr;
@@ -100,22 +100,22 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
char32_to_utf8_unsafe(uc, output); char32_to_utf8_unsafe(uc, output);
*available = 2; *available = 2;
} }
/** <*
* @param c `The utf32 codepoint to convert` @param c `The utf32 codepoint to convert`
* @param [inout] output `the resulting buffer` @param [inout] output `the resulting buffer`
**/ *>
fn usz char32_to_utf8_unsafe(Char32 c, char** output) fn usz char32_to_utf8_unsafe(Char32 c, char** output)
{ {
switch switch
{ {
case c < 0x7f: case c <= 0x7f:
(*output)++[0] = (char)c; (*output)++[0] = (char)c;
return 1; return 1;
case c < 0x7ff: case c <= 0x7ff:
(*output)++[0] = (char)(0xC0 | c >> 6); (*output)++[0] = (char)(0xC0 | c >> 6);
(*output)++[0] = (char)(0x80 | (c & 0x3F)); (*output)++[0] = (char)(0x80 | (c & 0x3F));
return 2; return 2;
case c < 0xffff: case c <= 0xffff:
(*output)++[0] = (char)(0xE0 | c >> 12); (*output)++[0] = (char)(0xE0 | c >> 12);
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F)); (*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
(*output)++[0] = (char)(0x80 | (c & 0x3F)); (*output)++[0] = (char)(0x80 | (c & 0x3F));
@@ -129,11 +129,11 @@ fn usz char32_to_utf8_unsafe(Char32 c, char** output)
} }
} }
/** <*
* @param [in] ptr `pointer to the first character to parse` @param [in] ptr `pointer to the first character to parse`
* @param [inout] size `Set to max characters to read, set to characters read` @param [inout] size `Set to max characters to read, set to characters read`
* @return `the parsed 32 bit codepoint` @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; usz max_size = *size;
@@ -184,10 +184,10 @@ fn Char32! utf8_to_char32(char* ptr, usz* size)
return uc + c & 0x3F; 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` @return `the number of encoded code points`
**/ *>
fn usz utf8_codepoints(String utf8) fn usz utf8_codepoints(String utf8)
{ {
usz len = 0; usz len = 0;
@@ -198,11 +198,11 @@ fn usz utf8_codepoints(String utf8)
return len; return len;
} }
/** <*
* Calculate the UTF8 length required to encode an UTF32 array. 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` @return `the length of the resulting UTF8 array`
**/ *>
fn usz utf8len_for_utf32(Char32[] utf32) fn usz utf8len_for_utf32(Char32[] utf32)
{ {
usz len = 0; usz len = 0;
@@ -210,11 +210,11 @@ fn usz utf8len_for_utf32(Char32[] utf32)
{ {
switch (true) switch (true)
{ {
case uc < 0x7f: case uc <= 0x7f:
len++; len++;
case uc < 0x7ff: case uc <= 0x7ff:
len += 2; len += 2;
case uc < 0xffff: case uc <= 0xffff:
len += 3; len += 3;
default: default:
len += 4; len += 4;
@@ -223,11 +223,11 @@ fn usz utf8len_for_utf32(Char32[] utf32)
return len; return len;
} }
/** <*
* Calculate the UTF8 length required to encode an UTF16 array. 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` @return `the length of the resulting UTF8 array`
**/ *>
fn usz utf8len_for_utf16(Char16[] utf16) fn usz utf8len_for_utf16(Char16[] utf16)
{ {
usz len = 0; usz len = 0;
@@ -237,12 +237,12 @@ fn usz utf8len_for_utf16(Char16[] utf16)
Char16 c = utf16[i]; Char16 c = utf16[i];
if (c & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE) if (c & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
{ {
if (c < 0x7f) if (c <= 0x7f)
{ {
len++; len++;
continue; continue;
} }
if (c < 0x7ff) if (c <= 0x7ff)
{ {
len += 2; len += 2;
continue; continue;
@@ -255,11 +255,11 @@ fn usz utf8len_for_utf16(Char16[] utf16)
return len; return len;
} }
/** <*
* Calculate the UTF16 length required to encode a UTF8 array. 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` @return `the length of the resulting UTF16 array`
**/ *>
fn usz utf16len_for_utf8(String utf8) fn usz utf16len_for_utf8(String utf8)
{ {
usz len = utf8.len; usz len = utf8.len;
@@ -279,10 +279,10 @@ fn usz utf16len_for_utf8(String utf8)
return len16; return len16;
} }
/** <*
* @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.` @return `the required length of an UTF16 array to hold the UTF32 data.`
**/ *>
fn usz utf16len_for_utf32(Char32[] utf32) fn usz utf16len_for_utf32(Char32[] utf32)
{ {
usz len = utf32.len; usz len = utf32.len;
@@ -293,13 +293,13 @@ fn usz utf16len_for_utf32(Char32[] utf32)
return len; return len;
} }
/** <*
* Convert an UTF32 array to an UTF8 array. Convert an UTF32 array to an UTF8 array.
*
* @param [in] utf32 @param [in] utf32
* @param [out] utf8_buffer @param [out] utf8_buffer
* @return `the number of bytes written.` @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; char[] buffer = utf8_buffer;
@@ -313,13 +313,13 @@ fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
return utf8_buffer.len - buffer.len; return utf8_buffer.len - buffer.len;
} }
/** <*
* Convert an UTF8 array to an UTF32 array. Convert an UTF8 array to an UTF32 array.
*
* @param [in] utf8 @param [in] utf8
* @param [out] utf32_buffer @param [out] utf32_buffer
* @return `the number of Char32s written.` @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; usz len = utf8.len;
@@ -339,14 +339,14 @@ fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
return len32; return len32;
} }
/** <*
* Copy an array of UTF16 data into an UTF8 buffer without bounds Copy an array of UTF16 data into an UTF8 buffer without bounds
* checking. This will assume the buffer is sufficiently large to hold checking. This will assume the buffer is sufficiently large to hold
* the converted data. the converted data.
*
* @param [in] utf16 `The UTF16 array containing the data to convert.` @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 [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; usz len16 = utf16.len;
@@ -358,14 +358,14 @@ fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
} }
} }
/** <*
* Copy an array of UTF8 data into an UTF32 buffer without bounds Copy an array of UTF8 data into an UTF32 buffer without bounds
* checking. This will assume the buffer is sufficiently large to hold checking. This will assume the buffer is sufficiently large to hold
* the converted data. the converted data.
*
* @param [in] utf8 `The UTF8 buffer containing the data to convert.` @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 [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; usz len = utf8.len;
@@ -378,14 +378,14 @@ fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
} }
} }
/** <*
* Copy an array of UTF8 data into an UTF16 buffer without bounds Copy an array of UTF8 data into an UTF16 buffer without bounds
* checking. This will assume the buffer is sufficiently large to hold checking. This will assume the buffer is sufficiently large to hold
* the converted data. the converted data.
*
* @param [in] utf8 `The UTF8 buffer containing the data to convert.` @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 [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; usz len = utf8.len;
@@ -398,14 +398,14 @@ fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
} }
} }
/** <*
* Copy an array of UTF32 code points into an UTF8 buffer without bounds Copy an array of UTF32 code points into an UTF8 buffer without bounds
* checking. This will assume the buffer is sufficiently large to hold checking. This will assume the buffer is sufficiently large to hold
* the converted data. the converted data.
*
* @param [in] utf32 `The UTF32 buffer containing the data to convert.` @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 [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
**/ *>
fn void utf32to8_unsafe(Char32[] utf32, char* utf8_buffer) fn void utf32to8_unsafe(Char32[] utf32, char* utf8_buffer)
{ {
char* start = utf8_buffer; char* start = utf8_buffer;

View File

@@ -1,40 +1,41 @@
module std::core::dstring; module std::core::dstring;
import std::io; import std::io;
distinct DString (OutStream) = void*; distinct DString (OutStream) = DStringOpaque*;
distinct DStringOpaque = void;
const usz MIN_CAPACITY @private = 16; const usz MIN_CAPACITY @private = 16;
/** <*
* @require !self.data() "String already initialized" @require !self.data() "String already initialized"
**/ *>
fn DString DString.init_new(&self, usz capacity = MIN_CAPACITY, Allocator* allocator = mem::heap()) fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap())
{ {
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
StringData* data = allocator.new(StringData, .end_padding = capacity); StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
data.allocator = allocator; data.allocator = allocator;
data.len = 0; data.len = 0;
data.capacity = capacity; data.capacity = capacity;
return *self = (DString)data; return *self = (DString)data;
} }
/** <*
* @require !self.data() "String already initialized" @require !self.data() "String already initialized"
**/ *>
fn DString DString.init_temp(&self, usz capacity = MIN_CAPACITY) fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY)
{ {
self.init_new(capacity, mem::temp()) @inline; self.new_init(capacity, allocator::temp()) @inline;
return *self; return *self;
} }
fn DString new_with_capacity(usz capacity, Allocator* allocator = mem::heap()) fn DString new_with_capacity(usz capacity, Allocator allocator = allocator::heap())
{ {
return DString{}.init_new(capacity, allocator); return DString{}.new_init(capacity, allocator);
} }
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, mem::temp()) @inline; fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, allocator::temp()) @inline;
fn DString new(String c = "", Allocator* allocator = mem::heap()) fn DString new(String c = "", Allocator allocator = allocator::heap())
{ {
usz len = c.len; usz len = c.len;
StringData* data = (StringData*)new_with_capacity(len, allocator); StringData* data = (StringData*)new_with_capacity(len, allocator);
@@ -46,18 +47,68 @@ fn DString new(String c = "", Allocator* allocator = mem::heap())
return (DString)data; return (DString)data;
} }
fn DString temp_new(String s = "") => new(s, mem::temp()) @inline; fn DString temp_new(String s = "") => new(s, allocator::temp()) @inline;
fn DString DString.new_concat(self, DString b, Allocator* allocator = mem::heap())
fn void DString.replace_char(self, char ch, char replacement)
{
StringData* data = self.data();
foreach (&c : data.chars[:data.len])
{
if (*c == ch) *c = replacement;
}
}
fn void DString.replace(&self, String needle, String replacement)
{
StringData* data = self.data();
usz needle_len = needle.len;
if (!data || data.len < needle_len) return;
usz replace_len = replacement.len;
if (needle_len == 1 && replace_len == 1)
{
self.replace_char(needle[0], replacement[0]);
return;
}
@pool(data.allocator) {
String str = self.tcopy_str();
self.clear();
usz len = str.len;
usz match = 0;
foreach (i, c : str)
{
if (c == needle[match])
{
match++;
if (match == needle_len)
{
self.append_chars(replacement);
match = 0;
continue;
}
continue;
}
if (match > 0)
{
self.append_chars(str[i - match:match]);
match = 0;
}
self.append_char(c);
}
if (match > 0) self.append_chars(str[^match:match]);
};
}
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
{ {
DString string; DString string;
string.init_new(self.len() + b.len(), allocator); string.new_init(self.len() + b.len(), allocator);
string.append(self); string.append(self);
string.append(b); string.append(b);
return string; return string;
} }
fn DString DString.new_tconcat(self, DString b) => self.new_concat(b, mem::temp()); fn DString DString.temp_concat(self, DString b) => self.new_concat(b, allocator::temp());
fn ZString DString.zstr_view(&self) fn ZString DString.zstr_view(&self)
{ {
@@ -82,15 +133,15 @@ fn usz DString.capacity(self)
return self.data().capacity; return self.data().capacity;
} }
fn usz DString.len(&self) @dynamic fn usz DString.len(&self) @dynamic @operator(len)
{ {
if (!*self) return 0; if (!*self) return 0;
return self.data().len; return self.data().len;
} }
/** <*
* @require new_size <= self.len() @require new_size <= self.len()
*/ *>
fn void DString.chop(self, usz new_size) fn void DString.chop(self, usz new_size)
{ {
if (!self) return; if (!self) return;
@@ -104,19 +155,39 @@ fn String DString.str_view(self)
return (String)data.chars[:data.len]; return (String)data.chars[:data.len];
} }
fn void DString.append_utf32(&self, Char32[] chars) <*
@require index < self.len()
@require self.data() != null "Empty string"
*>
fn char DString.char_at(self, usz index) @operator([])
{
return self.data().chars[index];
}
<*
@require index < self.len()
@require self.data() != null "Empty string"
*>
fn char* DString.char_ref(&self, usz index) @operator(&[])
{
return &self.data().chars[index];
}
fn usz DString.append_utf32(&self, Char32[] chars)
{ {
self.reserve(chars.len); self.reserve(chars.len);
usz end = self.len();
foreach (Char32 c : chars) foreach (Char32 c : chars)
{ {
self.append_char32(c); self.append_char32(c);
} }
return self.data().len - end;
} }
/** <*
* @require index < self.len() @require index < self.len()
**/ *>
fn void DString.set(self, usz index, char c) fn void DString.set(self, usz index, char c) @operator([]=)
{ {
self.data().chars[index] = c; self.data().chars[index] = c;
} }
@@ -132,10 +203,10 @@ fn void DString.append_repeat(&self, char c, usz times)
} }
} }
/** <*
* @require c <= 0x10ffff @require c <= 0x10ffff
*/ *>
fn void DString.append_char32(&self, Char32 c) fn usz DString.append_char32(&self, Char32 c)
{ {
char[4] buffer @noinit; char[4] buffer @noinit;
char* p = &buffer; char* p = &buffer;
@@ -144,11 +215,12 @@ fn void DString.append_char32(&self, Char32 c)
StringData* data = self.data(); StringData* data = self.data();
data.chars[data.len:n] = buffer[:n]; data.chars[data.len:n] = buffer[:n];
data.len += n; data.len += n;
return n;
} }
fn DString DString.tcopy(&self) => self.copy(mem::temp()); fn DString DString.tcopy(&self) => self.copy(allocator::temp());
fn DString DString.copy(self, Allocator* allocator = null) fn DString DString.copy(self, Allocator allocator = null)
{ {
if (!self) if (!self)
{ {
@@ -156,32 +228,32 @@ fn DString DString.copy(self, Allocator* allocator = null)
return (DString)null; return (DString)null;
} }
StringData* data = self.data(); StringData* data = self.data();
if (!allocator) allocator = mem::heap(); if (!allocator) allocator = allocator::heap();
DString new_string = new_with_capacity(data.capacity, allocator); DString new_string = new_with_capacity(data.capacity, allocator);
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len); mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
return new_string; return new_string;
} }
fn ZString DString.copy_zstr(self, Allocator* allocator = mem::heap()) fn ZString DString.copy_zstr(self, Allocator allocator = allocator::heap())
{ {
usz str_len = self.len(); usz str_len = self.len();
if (!str_len) if (!str_len)
{ {
return (ZString)allocator.calloc(1); return (ZString)allocator::calloc(allocator, 1);
} }
char* zstr = allocator.alloc(str_len + 1); char* zstr = allocator::malloc(allocator, str_len + 1);
StringData* data = self.data(); StringData* data = self.data();
mem::copy(zstr, &data.chars, str_len); mem::copy(zstr, &data.chars, str_len);
zstr[str_len] = 0; zstr[str_len] = 0;
return (ZString)zstr; return (ZString)zstr;
} }
fn String DString.copy_str(self, Allocator* allocator = mem::heap()) fn String DString.copy_str(self, Allocator allocator = allocator::heap())
{ {
return (String)self.copy_zstr(allocator)[:self.len()]; return (String)self.copy_zstr(allocator)[:self.len()];
} }
fn String DString.tcopy_str(self) => self.copy_str(mem::temp()) @inline; fn String DString.tcopy_str(self) => self.copy_str(allocator::temp()) @inline;
fn bool DString.equals(self, DString other_string) fn bool DString.equals(self, DString other_string)
{ {
@@ -204,7 +276,7 @@ fn void DString.free(&self)
if (!*self) return; if (!*self) return;
StringData* data = self.data(); StringData* data = self.data();
if (!data) return; if (!data) return;
data.allocator.free(data); allocator::free(data.allocator, data);
*self = (DString)null; *self = (DString)null;
} }
@@ -240,9 +312,9 @@ fn void DString.append_chars(&self, String str)
data.len += other_len; data.len += other_len;
} }
fn Char32[] DString.copy_utf32(&self, Allocator* allocator = mem::heap()) fn Char32[] DString.copy_utf32(&self, Allocator allocator = allocator::heap())
{ {
return self.str_view().to_new_utf32(allocator) @inline!!; return self.str_view().to_utf32(allocator) @inline!!;
} }
fn void DString.append_string(&self, DString str) fn void DString.append_string(&self, DString str)
@@ -280,6 +352,37 @@ fn void DString.append_char(&self, char c)
data.chars[data.len++] = c; data.chars[data.len++] = c;
} }
<*
@require start < self.len()
@require end < self.len()
@require end >= start "End must be same or equal to the start"
*>
fn void DString.delete_range(&self, usz start, usz end)
{
self.delete(start, end - start + 1);
}
<*
@require start < self.len()
@require start + len <= self.len()
*>
fn void DString.delete(&self, usz start, usz len = 1)
{
if (!len) return;
StringData* data = self.data();
usz new_len = data.len - len;
if (new_len == 0)
{
data.len = 0;
return;
}
usz len_after = data.len - start - len;
if (len_after > 0)
{
data.chars[start:len_after] = data.chars[start + len:len_after];
}
data.len = new_len;
}
macro void DString.append(&self, value) macro void DString.append(&self, value)
{ {
@@ -306,7 +409,10 @@ macro void DString.append(&self, value)
$endswitch $endswitch
} }
fn void DString.insert_at(&self, usz index, String s) <*
@require index <= self.len()
*>
fn void DString.insert_chars_at(&self, usz index, String s)
{ {
if (s.len == 0) return; if (s.len == 0) return;
self.reserve(s.len); self.reserve(s.len);
@@ -338,23 +444,128 @@ fn void DString.insert_at(&self, usz index, String s)
} }
} }
<*
@require index <= self.len()
*>
fn void DString.insert_string_at(&self, usz index, DString str)
{
StringData* other = str.data();
if (!other) return;
self.insert_at(index, str.str_view());
}
<*
@require index <= self.len()
*>
fn void DString.insert_char_at(&self, usz index, char c)
{
self.reserve(1);
StringData* data = self.data();
char* start = &data.chars[index];
mem::move(start + 1, start, self.len() - index);
data.chars[index] = c;
data.len++;
}
<*
@require index <= self.len()
*>
fn usz DString.insert_char32_at(&self, usz index, Char32 c)
{
char[4] buffer @noinit;
char* p = &buffer;
usz n = conv::char32_to_utf8_unsafe(c, &p);
self.reserve(n);
StringData* data = self.data();
char* start = &data.chars[index];
mem::move(start + n, start, self.len() - index);
data.chars[index:n] = buffer[:n];
data.len += n;
return n;
}
<*
@require index <= self.len()
*>
fn usz DString.insert_utf32_at(&self, usz index, Char32[] chars)
{
usz n = conv::utf8len_for_utf32(chars);
self.reserve(n);
StringData* data = self.data();
char* start = &data.chars[index];
mem::move(start + n, start, self.len() - index);
char[4] buffer @noinit;
foreach(c : chars)
{
char* p = &buffer;
usz m = conv::char32_to_utf8_unsafe(c, &p);
data.chars[index:m] = buffer[:m];
index += m;
}
data.len += n;
return n;
}
macro void DString.insert_at(&self, usz index, value)
{
var $Type = $typeof(value);
$switch ($Type)
$case char:
$case ichar:
self.insert_char_at(index, value);
$case DString:
self.insert_string_at(index, value);
$case String:
self.insert_chars_at(index, value);
$case Char32:
self.insert_char32_at(index, value);
$default:
$switch
$case $defined((Char32)value):
self.insert_char32_at(index, (Char32)value);
$case $defined((String)value):
self.insert_chars_at(index, (String)value);
$default:
$error "Unsupported type for insert";
$endswitch
$endswitch
}
fn usz! DString.appendf(&self, String format, args...) @maydiscard fn usz! DString.appendf(&self, String format, args...) @maydiscard
{ {
Formatter formatter; if (!self.data()) self.new_init(format.len + 20);
formatter.init(&out_string_append_fn, self); @pool(self.data().allocator)
return formatter.vprintf(format, args); {
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
{ {
Formatter formatter; if (!self.data()) self.new_init(format.len + 20);
formatter.init(&out_string_append_fn, self); @pool(self.data().allocator)
usz len = formatter.vprintf(format, args)!; {
self.append('\n'); Formatter formatter;
return len + 1; formatter.init(&out_string_append_fn, self);
usz len = formatter.vprintf(format, args)!;
self.append('\n');
return len + 1;
};
} }
fn DString new_join(String[] s, String joiner, Allocator* allocator = mem::heap()) fn DString new_join(String[] s, String joiner, Allocator allocator = allocator::heap())
{ {
if (!s.len) return (DString)null; if (!s.len) return (DString)null;
usz total_size = joiner.len * s.len; usz total_size = joiner.len * s.len;
@@ -378,6 +589,19 @@ fn void! out_string_append_fn(void* data, char c) @private
s.append_char(c); s.append_char(c);
} }
fn void DString.reverse(self)
{
StringData *data = self.data();
if (!data) return;
isz mid = data.len / 2;
for (isz i = 0; i < mid; i++)
{
char temp = data.chars[i];
isz reverse_index = data.len - 1 - i;
data.chars[i] = data.chars[reverse_index];
data.chars[reverse_index] = temp;
}
}
fn StringData* DString.data(self) @inline @private fn StringData* DString.data(self) @inline @private
{ {
@@ -398,10 +622,10 @@ fn void DString.reserve(&self, usz addition)
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY; if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
while (new_capacity < len) new_capacity *= 2; while (new_capacity < len) new_capacity *= 2;
data.capacity = new_capacity; data.capacity = new_capacity;
*self = (DString)data.allocator.realloc(data, StringData.sizeof + new_capacity); *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) if (&reader.available)
{ {
@@ -433,8 +657,8 @@ fn usz! DString.read_from_stream(&self, InStream* reader)
struct StringData @private struct StringData @private
{ {
Allocator* allocator; Allocator allocator;
usz len; usz len;
usz capacity; usz capacity;
char[*] chars; char[?] chars;
} }

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::env; module std::core::env;
@@ -112,25 +112,33 @@ enum ArchType
WASM64, // WebAssembly with 64-bit pointers WASM64, // WebAssembly with 64-bit pointers
RSCRIPT32, // 32-bit RenderScript RSCRIPT32, // 32-bit RenderScript
RSCRIPT64, // 64-bit RenderScript RSCRIPT64, // 64-bit RenderScript
XTENSA, // Xtensa
} }
const OsType OS_TYPE = (OsType)$$OS_TYPE; const String COMPILER_BUILD_HASH = $$BUILD_HASH;
const ArchType ARCH_TYPE = (ArchType)$$ARCH_TYPE; const String COMPILER_BUILD_DATE = $$BUILD_DATE;
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 LIBC = $$COMPILER_LIBC_AVAILABLE;
const bool NO_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 BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED; const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED;
const bool F16_SUPPORT = $$PLATFORM_F16_SUPPORTED; const bool F16_SUPPORT = $$PLATFORM_F16_SUPPORTED;
const bool F128_SUPPORT = $$PLATFORM_F128_SUPPORTED; const bool F128_SUPPORT = $$PLATFORM_F128_SUPPORTED;
const REGISTER_SIZE = $$REGISTER_SIZE;
const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE; const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE;
const bool DEBUG_SYMBOLS = $$DEBUG_SYMBOLS; const bool DEBUG_SYMBOLS = $$DEBUG_SYMBOLS;
const bool BACKTRACE = $$BACKTRACE;
const usz LLVM_VERSION = $$LLVM_VERSION; const usz LLVM_VERSION = $$LLVM_VERSION;
const bool BENCHMARKING = $$BENCHMARKING; const bool BENCHMARKING = $$BENCHMARKING;
const bool TESTING = $$TESTING; 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 TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING);
const bool X86_64 = ARCH_TYPE == X86_64; const bool X86_64 = ARCH_TYPE == X86_64;
const bool X86 = ARCH_TYPE == X86;
const bool AARCH64 = ARCH_TYPE == AARCH64; const bool AARCH64 = ARCH_TYPE == AARCH64;
const bool NATIVE_STACKTRACE = LINUX || DARWIN || WIN32; const bool NATIVE_STACKTRACE = LINUX || DARWIN || WIN32;
const bool LINUX = LIBC && OS_TYPE == LINUX; const bool LINUX = LIBC && OS_TYPE == LINUX;
@@ -140,10 +148,15 @@ const bool POSIX = LIBC && os_is_posix();
const bool OPENBSD = LIBC && OS_TYPE == OPENBSD; const bool OPENBSD = LIBC && OS_TYPE == OPENBSD;
const bool FREEBSD = LIBC && OS_TYPE == FREEBSD; const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
const bool NETBSD = LIBC && OS_TYPE == NETBSD; 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 WASI = LIBC && OS_TYPE == WASI;
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64; 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;
macro bool os_is_darwin() macro bool os_is_darwin() @const
{ {
$switch (OS_TYPE) $switch (OS_TYPE)
$case IOS: $case IOS:
@@ -156,7 +169,7 @@ macro bool os_is_darwin()
$endswitch $endswitch
} }
macro bool os_is_posix() macro bool os_is_posix() @const
{ {
$switch (OS_TYPE) $switch (OS_TYPE)
$case IOS: $case IOS:

File diff suppressed because it is too large Load Diff

View File

@@ -10,88 +10,35 @@ struct TrackingEnv
uint line; uint line;
} }
enum AllocInitType
{
NO_ZERO,
ZERO
}
interface Allocator interface Allocator
{ {
fn void reset(usz mark) @optional; fn void reset(usz mark) @optional;
fn usz mark() @optional; fn usz mark() @optional;
fn void*! acquire(usz size, bool clear, usz alignment, usz offset); <*
fn void*! resize(void* ptr, usz new_size, usz alignment, usz offset); @require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require size > 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 ptr != null
@require new_size > 0
*>
fn void*! resize(void* ptr, usz new_size, usz alignment = 0);
<*
@require ptr != null
*>
fn void release(void* ptr, bool aligned); fn void release(void* ptr, bool aligned);
} }
struct AlignedBlock
{
usz len;
void* start;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment, usz offset)
{
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
void* data = #alloc_fn(header + bytes)!;
$else
void* data = #alloc_fn(header + bytes);
$endif
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
assert(mem > data);
AlignedBlock* desc = (AlignedBlock*)mem - 1;
*desc = { bytes, data };
return mem;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
macro void*! @aligned_calloc(#calloc_fn, usz bytes, usz alignment, usz offset)
{
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
$if @typekind(#calloc_fn(bytes)) == OPTIONAL:
void* data = #calloc_fn(header + bytes)!;
$else
void* data = #calloc_fn(header + bytes);
$endif
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
AlignedBlock* desc = (AlignedBlock*)mem - 1;
assert(mem > data);
*desc = { bytes, data };
return mem;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment, usz offset)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
void* new_data = @aligned_calloc(#calloc_fn, bytes, alignment, offset)!;
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
$if @typekind(#free_fn(data_start)) == OPTIONAL:
#free_fn(data_start)!;
$else
#free_fn(data_start);
$endif
return new_data;
}
macro void! @aligned_free(#free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
#free_fn(desc.start)!;
$else
#free_fn(desc.start);
$endif
}
def MemoryAllocFn = fn char[]!(usz); def MemoryAllocFn = fn char[]!(usz);
fault AllocationFailure fault AllocationFailure
@@ -102,123 +49,406 @@ fault AllocationFailure
fn usz alignment_for_allocation(usz alignment) @inline @private fn usz alignment_for_allocation(usz alignment) @inline @private
{ {
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? alignment = mem::DEFAULT_MEM_ALIGNMENT : alignment; return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
} }
// Allocator "functions" macro void* malloc(Allocator allocator, usz size) @nodiscard
macro void*! Allocator.alloc_checked(&self, usz size)
{ {
return malloc_try(allocator, size)!!;
}
macro void*! malloc_try(Allocator allocator, usz size) @nodiscard
{
if (!size) return null;
$if env::TESTING: $if env::TESTING:
char* data = self.acquire(size, false, 0, 0)!; char* data = allocator.acquire(size, NO_ZERO)!;
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT); mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
return data; return data;
$else $else
return self.acquire(size, false, 0, 0); return allocator.acquire(size, NO_ZERO);
$endif $endif
} }
macro void*! Allocator.calloc_checked(&self, usz size) macro void* calloc(Allocator allocator, usz size) @nodiscard
{ {
return self.acquire(size, true, 0, 0); return calloc_try(allocator, size)!!;
}
macro void*! Allocator.realloc_checked(&self, void* ptr, usz new_size)
{
return self.resize(ptr, new_size, 0, 0);
} }
macro Allocator.new_array(&self, $Type, usz size, usz end_padding = 0) macro void*! calloc_try(Allocator allocator, usz size) @nodiscard
{ {
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size]!!; if (!size) return null;
return allocator.acquire(size, ZERO);
} }
macro Allocator.new_array_checked(&self, $Type, usz size, usz end_padding = 0) macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard
{ {
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size]; return realloc_try(allocator, ptr, new_size)!!;
} }
macro Allocator.new_zero_array(&self, $Type, usz size, usz end_padding = 0) macro void*! realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
{ {
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size]!!; if (!new_size)
{
free(allocator, ptr);
return null;
}
if (!ptr) return allocator.acquire(new_size, NO_ZERO);
return allocator.resize(ptr, new_size);
} }
macro Allocator.new_zero_array_checked(&self, $Type, usz size, usz end_padding = 0) macro void free(Allocator allocator, void* ptr)
{
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size];
}
macro Allocator.new(&self, $Type, usz end_padding = 0) @nodiscard
{
return ($Type*)self.alloc_checked($Type.sizeof + end_padding)!!;
}
macro Allocator.new_checked(&self, $Type, usz end_padding = 0) @nodiscard
{
return ($Type*)self.alloc_checked($Type.sizeof + end_padding);
}
macro Allocator.new_clear(&self, $Type, usz end_padding = 0) @nodiscard
{
return ($Type*)self.calloc_checked($Type.sizeof + end_padding)!!;
}
macro Allocator.new_clear_checked(&self, $Type, usz end_padding = 0) @nodiscard
{
return ($Type*)self.calloc_checked($Type.sizeof + end_padding);
}
macro Allocator.clone(&self, value)
{
var x = self.alloc($typeof(value));
*x = value;
return x;
}
macro void* Allocator.alloc(&self, usz size) @nodiscard
{
return self.alloc_checked(size)!!;
}
macro void* Allocator.calloc(&self, usz size) @nodiscard
{
return self.acquire(size, true, 0, 0)!!;
}
macro void* Allocator.realloc(&self, void* ptr, usz new_size) @nodiscard
{
return self.resize(ptr, new_size, 0, 0)!!;
}
macro void*! Allocator.alloc_aligned(&self, usz size, usz alignment, usz offset = 0)
{ {
if (!ptr) return;
$if env::TESTING: $if env::TESTING:
char* data = self.acquire(size, false, alignment, offset)!; ((char*)ptr)[0] = 0xBA;
$endif
allocator.release(ptr, false);
}
macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
{
if (!size) return null;
$if env::TESTING:
char* data = allocator.acquire(size, NO_ZERO, alignment)!;
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT); mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
return data; return data;
$else $else
return self.acquire(size, false, alignment, offset); return allocator.acquire(size, NO_ZERO, alignment);
$endif $endif
} }
macro void*! Allocator.calloc_aligned(&self, usz size, usz alignment, usz offset = 0)
macro void*! calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
{ {
return self.acquire(size, true, alignment, offset); if (!size) return null;
} return allocator.acquire(size, ZERO, alignment);
macro void*! Allocator.realloc_aligned(&self, void* ptr, usz new_size, usz alignment = 0, usz offset = 0)
{
return self.resize(ptr, new_size, alignment, offset);
} }
macro void Allocator.free(&self, void* ptr) macro void*! realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
{ {
if (!new_size)
{
free_aligned(allocator, ptr);
return null;
}
if (!ptr)
{
return malloc_aligned(allocator, new_size, alignment);
}
return allocator.resize(ptr, new_size, alignment);
}
macro void free_aligned(Allocator allocator, void* ptr)
{
if (!ptr) return;
$if env::TESTING: $if env::TESTING:
if (ptr) ((char*)ptr)[0] = 0xBA; ((char*)ptr)[0] = 0xBA;
$endif $endif
self.release(ptr, false); allocator.release(ptr, aligned: true);
} }
macro void Allocator.free_aligned(&self, void* ptr)
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
@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(Allocator allocator, $Type, ...) @nodiscard
{ {
$if env::TESTING: $if $vacount == 0:
if (ptr) ((char*)ptr)[0] = 0xBA; return ($Type*)calloc(allocator, $Type.sizeof);
$else
$Type* val = malloc(allocator, $Type.sizeof);
*val = $vaexpr[0];
return val;
$endif $endif
self.release(ptr, true); }
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
@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_try(Allocator allocator, $Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc_try(allocator, $Type.sizeof);
$else
$Type* val = malloc_try(allocator, $Type.sizeof)!;
*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.
@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(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)!;
*val = $vaexpr[0];
return val;
$endif
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT
*>
macro new_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
{
return ($Type*)calloc_try(allocator, $Type.sizeof + padding);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro alloc(Allocator allocator, $Type) @nodiscard
{
return ($Type*)malloc(allocator, $Type.sizeof);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro alloc_try(Allocator allocator, $Type) @nodiscard
{
return ($Type*)malloc_try(allocator, $Type.sizeof);
}
<*
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.
*>
macro alloc_aligned(Allocator allocator, $Type) @nodiscard
{
return ($Type*)malloc_aligned(allocator, $Type.sizeof, $Type.alignof);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT
*>
macro alloc_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
{
return ($Type*)malloc_try(allocator, $Type.sizeof + padding);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
*>
macro new_array(Allocator allocator, $Type, usz elements) @nodiscard
{
return new_array_try(allocator, $Type, elements)!!;
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
*>
macro new_array_try(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)calloc_try(allocator, $Type.sizeof * elements))[:elements];
}
<*
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.
*>
macro new_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)calloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*>
macro alloc_array(Allocator allocator, $Type, usz elements) @nodiscard
{
return alloc_array_try(allocator, $Type, elements)!!;
}
<*
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.
*>
macro alloc_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)malloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*>
macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
}
macro clone(Allocator allocator, value) @nodiscard
{
return new(allocator, $typeof(value), value);
}
fn any clone_any(Allocator allocator, any value) @nodiscard
{
usz size = value.type.sizeof;
void* data = malloc(allocator, size);
mem::copy(data, value.ptr, size);
return any_make(data, value.type);
} }
<*
@require bytes > 0
@require alignment > 0
@require bytes <= isz.max
*>
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
{
if (alignment < void*.alignof) alignment = void*.alignof;
usz header = AlignedBlock.sizeof + alignment;
usz alignsize = bytes + header;
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
void* data = #alloc_fn(alignsize)!;
$else
void* data = #alloc_fn(alignsize);
$endif
void* mem = mem::aligned_pointer(data + AlignedBlock.sizeof, alignment);
AlignedBlock* desc = (AlignedBlock*)mem - 1;
assert(mem > data);
*desc = { bytes, data };
return mem;
}
struct AlignedBlock
{
usz len;
void* start;
}
macro void! @aligned_free(#free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
#free_fn(desc.start)!;
$else
#free_fn(desc.start);
$endif
}
<*
@require bytes > 0
@require alignment > 0
*>
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;
void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!;
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
$if @typekind(#free_fn(data_start)) == OPTIONAL:
#free_fn(data_start)!;
$else
#free_fn(data_start);
$endif
return new_data;
}
// All allocators
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;
macro Allocator base_allocator() @private
{
$if env::LIBC:
return &allocator::LIBC_ALLOCATOR;
$else
return &allocator::NULL_ALLOCATOR;
$endif
}
macro TempAllocator* create_default_sized_temp_allocator(Allocator allocator) @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'.");
$endswitch
}
macro Allocator heap() => thread_allocator;
macro TempAllocator* temp()
{
if (!thread_temp_allocator)
{
init_default_temp_allocators();
}
return thread_temp_allocator;
}
fn void init_default_temp_allocators() @private
{
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];
}
fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::LIBC)
{
destroy_temp_allocators();
}
<*
Call this to destroy any memory used by the temp allocators. This will invalidate all temp memory.
*>
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;
}
fn TempAllocator* temp_allocator_next() @private
{
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;
fn void*! NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
return AllocationFailure.OUT_OF_MEMORY?;
}
fn void*! NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
return AllocationFailure.OUT_OF_MEMORY?;
}
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
}

View File

@@ -0,0 +1,268 @@
module std::core::cpudetect @if(env::X86 || env::X86_64);
struct CpuId
{
uint eax, ebx, ecx, edx;
}
fn CpuId x86_cpuid(uint eax, uint ecx = 0)
{
int edx;
int ebx;
asm
{
movl $eax, eax;
movl $ecx, ecx;
cpuid;
movl eax, $eax;
movl ebx, $ebx;
movl ecx, $ecx;
movl edx, $edx;
}
return { eax, ebx, ecx, edx };
}
enum X86Feature
{
ADX,
AES,
AMX_AVX512,
AMX_FP8,
AMX_MOVRS,
AMX_TF32,
AMX_TRANSPOSE,
AMX_BF16,
AMX_COMPLEX,
AMX_FP16,
AMX_INT8,
AMX_TILE,
APXF,
AVX,
AVX10_1_256,
AVX10_1_512,
AVX10_2_256,
AVX10_2_512,
AVX2,
AVX5124FMAPS,
AVX5124VNNIW,
AVX512BF16,
AVX512BITALG,
AVX512BW,
AVX512CD,
AVX512DQ,
AVX512ER,
AVX512F,
AVX512FP16,
AVX512IFMA,
AVX512PF,
AVX512VBMI,
AVX512VBMI2,
AVX512VL,
AVX512VNNI,
AVX512VP2INTERSECT,
AVX512VPOPCNTDQ,
AVXIFMA,
AVXNECONVERT,
AVXVNNI,
AVXVNNIINT16,
AVXVNNIINT8,
BMI,
BMI2,
CLDEMOTE,
CLFLUSHOPT,
CLWB,
CLZERO,
CMOV,
CMPCCXADD,
CMPXCHG16B,
CX8,
ENQCMD,
F16C,
FMA,
FMA4,
FSGSBASE,
FXSR,
GFNI,
HRESET,
INVPCID,
KL,
LWP,
LZCNT,
MMX,
MOVBE,
MOVDIR64B,
MOVDIRI,
MOVRS,
MWAITX,
PCLMUL,
PCONFIG,
PKU,
POPCNT,
PREFETCHI,
PREFETCHWT1,
PRFCHW,
PTWRITE,
RAOINT,
RDPID,
RDPRU,
RDRND,
RDSEED,
RTM,
SAHF,
SERIALIZE,
SGX,
SHA,
SHA512,
SHSTK,
SM3,
SM4,
SSE,
SSE2,
SSE3,
SSE4_1,
SSE4_2,
SSE4_A,
SSSE3,
TBM,
TSXLDTRK,
UINTR,
USERMSR,
VAES,
VPCLMULQDQ,
WAITPKG,
WBNOINVD,
WIDEKL,
X87,
XOP,
XSAVE,
XSAVEC,
XSAVEOPT,
XSAVES,
}
uint128 x86_features;
fn void add_feature_if_bit(X86Feature feature, uint register, int bit)
{
if (register & 1U << bit) x86_features |= 1u128 << feature.ordinal;
}
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 {};
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);
add_feature_if_bit(AMX_COMPLEX, leaf7s1.edx, 8);
add_feature_if_bit(AMX_FP16, leaf7s1.eax, 21);
add_feature_if_bit(AMX_INT8, leaf7.edx, 25);
add_feature_if_bit(AMX_TILE, leaf7.edx, 24);
add_feature_if_bit(APXF, leaf7s1.edx, 21);
add_feature_if_bit(AVX, feat.ecx, 28);
add_feature_if_bit(AVX10_1_256, leaf7s1.edx, 19);
add_feature_if_bit(AVX10_1_512, leaf_24.ebx, 18);
add_feature_if_bit(AVX2, leaf7.ebx, 5);
add_feature_if_bit(AVX5124FMAPS, leaf7.edx, 3);
add_feature_if_bit(AVX5124VNNIW, leaf7.edx, 2);
add_feature_if_bit(AVX512BF16, leaf7s1.eax, 5);
add_feature_if_bit(AVX512BITALG, leaf7.ecx, 12);
add_feature_if_bit(AVX512BW, leaf7.ebx, 30);
add_feature_if_bit(AVX512CD, leaf7.ebx, 28);
add_feature_if_bit(AVX512DQ, leaf7.ebx, 17);
add_feature_if_bit(AVX512ER, leaf7.ebx, 27);
add_feature_if_bit(AVX512F, leaf7.ebx, 16);
add_feature_if_bit(AVX512FP16, leaf7.edx, 23);
add_feature_if_bit(AVX512IFMA, leaf7.ebx, 21);
add_feature_if_bit(AVX512PF, leaf7.ebx, 26);
add_feature_if_bit(AVX512VBMI, leaf7.ecx, 1);
add_feature_if_bit(AVX512VBMI2, leaf7.ecx, 6);
add_feature_if_bit(AVX512VL, leaf7.ebx, 31);
add_feature_if_bit(AVX512VNNI, leaf7.ecx, 11);
add_feature_if_bit(AVX512VP2INTERSECT, leaf7.edx, 8);
add_feature_if_bit(AVX512VPOPCNTDQ, leaf7.ecx, 14);
add_feature_if_bit(AVXIFMA, leaf7s1.eax, 23);
add_feature_if_bit(AVXNECONVERT, leaf7s1.edx, 5);
add_feature_if_bit(AVXVNNI, leaf7s1.eax, 4);
add_feature_if_bit(AVXVNNIINT16, leaf7s1.edx, 10);
add_feature_if_bit(AVXVNNIINT8, leaf7s1.edx, 4);
add_feature_if_bit(BMI, leaf7.ebx, 3);
add_feature_if_bit(BMI2, leaf7.ebx, 8);
add_feature_if_bit(CLDEMOTE, leaf7.ecx, 25);
add_feature_if_bit(CLFLUSHOPT, leaf7.ebx, 23);
add_feature_if_bit(CLWB, leaf7.ebx, 24);
add_feature_if_bit(CLZERO, ext8.ecx, 0);
add_feature_if_bit(CMOV, feat.edx, 15);
add_feature_if_bit(CMPCCXADD, leaf7s1.eax, 7);
add_feature_if_bit(CMPXCHG16B, feat.ecx, 12);
add_feature_if_bit(CX8, feat.edx, 8);
add_feature_if_bit(ENQCMD, leaf7.ecx, 29);
add_feature_if_bit(F16C, feat.ecx, 29);
add_feature_if_bit(FMA, feat.ecx, 12);
add_feature_if_bit(FMA4, ext1.ecx, 16);
add_feature_if_bit(FSGSBASE, leaf7.ebx, 0);
add_feature_if_bit(FXSR, feat.edx, 24);
add_feature_if_bit(GFNI, leaf7.ecx, 8);
add_feature_if_bit(HRESET, leaf7s1.eax, 22);
add_feature_if_bit(INVPCID, leaf7.ebx, 10);
add_feature_if_bit(KL, leaf7.ecx, 23);
add_feature_if_bit(LWP, ext1.ecx, 15);
add_feature_if_bit(LZCNT, ext1.ecx, 5);
add_feature_if_bit(MMX, feat.edx, 23);
add_feature_if_bit(MOVBE, feat.ecx, 22);
add_feature_if_bit(MOVDIR64B, leaf7.ecx, 28);
add_feature_if_bit(MOVDIRI, leaf7.ecx, 27);
add_feature_if_bit(MWAITX, ext1.ecx, 29);
add_feature_if_bit(PCLMUL, feat.ecx, 1);
add_feature_if_bit(PCONFIG, leaf7.edx, 18);
add_feature_if_bit(PKU, leaf7.ecx, 4);
add_feature_if_bit(POPCNT, feat.ecx, 23);
add_feature_if_bit(PREFETCHI, leaf7s1.edx, 14);
add_feature_if_bit(PREFETCHWT1, leaf7.ecx, 0);
add_feature_if_bit(PRFCHW, ext1.ecx, 8);
add_feature_if_bit(PTWRITE, leaf_14.ebx, 4);
add_feature_if_bit(RAOINT, leaf7s1.eax, 3);
add_feature_if_bit(RDPID, leaf7.ecx, 22);
add_feature_if_bit(RDPRU, ext8.ecx, 4);
add_feature_if_bit(RDRND, feat.ecx, 30);
add_feature_if_bit(RDSEED, leaf7.ebx, 18);
add_feature_if_bit(RTM, leaf7.ebx, 11);
add_feature_if_bit(SAHF, ext1.ecx, 0);
add_feature_if_bit(SERIALIZE, leaf7.edx, 14);
add_feature_if_bit(SGX, leaf7.ebx, 2);
add_feature_if_bit(SHA, leaf7.ebx, 29);
add_feature_if_bit(SHA512, leaf7s1.eax, 0);
add_feature_if_bit(SHSTK, leaf7.ecx, 7);
add_feature_if_bit(SM3, leaf7s1.eax, 1);
add_feature_if_bit(SM4, leaf7s1.eax, 2);
add_feature_if_bit(SSE, feat.edx, 25);
add_feature_if_bit(SSE2, feat.edx, 26);
add_feature_if_bit(SSE3, feat.ecx, 0);
add_feature_if_bit(SSE4_1, feat.ecx, 19);
add_feature_if_bit(SSE4_2, feat.ecx, 20);
add_feature_if_bit(SSE4_A, ext1.ecx, 6);
add_feature_if_bit(SSSE3, feat.ecx, 9);
add_feature_if_bit(TBM, ext1.ecx, 21);
add_feature_if_bit(TSXLDTRK, leaf7.edx, 16);
add_feature_if_bit(UINTR, leaf7.edx, 5);
add_feature_if_bit(USERMSR, leaf7s1.edx, 15);
add_feature_if_bit(VAES, leaf7.ecx, 9);
add_feature_if_bit(VPCLMULQDQ, leaf7.ecx, 10);
add_feature_if_bit(WAITPKG, leaf7.ecx, 5);
add_feature_if_bit(WBNOINVD, ext8.ecx, 9);
add_feature_if_bit(WIDEKL, leaf_19.ebx, 2);
add_feature_if_bit(X87, feat.edx, 0);
add_feature_if_bit(XOP, ext1.ecx, 11);
add_feature_if_bit(XSAVE, feat.ecx, 26);
add_feature_if_bit(XSAVEC, leaf_d.eax, 1);
add_feature_if_bit(XSAVEOPT, leaf_d.eax, 0);
add_feature_if_bit(XSAVES, leaf_d.eax, 3);
}

View File

@@ -0,0 +1,254 @@
module std::core::machoruntime @if(env::DARWIN) @private;
struct SegmentCommand64
{
uint cmd;
uint cmdsize;
char[16] segname;
ulong vmaddr;
ulong vmsize;
ulong fileoff;
ulong filesize;
uint maxprot;
uint initprot;
uint nsects;
uint flags;
}
struct LoadCommand
{
uint cmd;
uint cmdsize;
}
struct Section64
{
char[16] sectname;
char[16] segname;
ulong addr;
ulong size;
uint offset;
uint align;
uint reloff;
uint nreloc;
uint flags;
uint reserved1;
uint reserved2;
uint reserved3;
}
struct MachHeader
{
uint magic;
uint cputype;
uint cpusubtype;
uint filetype;
uint ncmds;
uint sizeofcmds;
uint flags;
}
struct MachHeader64
{
inline MachHeader header;
uint reserved;
}
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++)
{
if (a[i] != (*b)[i]) return false;
if (a[i] == '\0') return true;
}
return false;
}
fn SegmentCommand64*! find_segment(MachHeader* header, char* segname)
{
LoadCommand* command = (void*)header + MachHeader64.sizeof;
for (uint i = 0; i < header.ncmds; i++)
{
if (command.cmd == LC_SEGMENT_64)
{
SegmentCommand64* segment = (SegmentCommand64*)command;
if (name_cmp(segname, &segment.segname)) return segment;
}
command = (void*)command + command.cmdsize;
}
return MachoSearch.NOT_FOUND?;
}
fn Section64*! find_section(SegmentCommand64* command, char* sectname)
{
Section64* section = (void*)command + SegmentCommand64.sizeof;
for (uint i = 0; i < command.nsects; i++)
{
if (name_cmp(sectname, &section.sectname)) return section;
section++;
}
return MachoSearch.NOT_FOUND?;
}
macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type)
{
Section64*! section = find_section(find_segment(header, segname), sectname);
if (catch section)
{
return $Type[] {};
}
$Type* ptr = (void*)header + section.offset;
return ptr[:section.size / $Type.sizeof];
}
def DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide);
extern fn void _dyld_register_func_for_add_image(DyldCallback);
struct DlInfo
{
char* dli_fname;
void* dli_fbase;
char* dli_sname;
void* dli_saddr;
}
extern fn void printf(char*, ...);
extern fn int dladdr(MachHeader* mh, DlInfo* dlinfo);
extern fn void* realloc(void* ptr, usz size);
extern fn void* malloc(usz size);
extern fn void free(void* ptr);
def CallbackFn = fn void();
struct Callback
{
uint priority;
CallbackFn xtor;
Callback* next;
}
struct DynamicMethod
{
void* fn_ptr;
char* sel;
union
{
DynamicMethod* next;
TypeId* type;
}
}
enum StartupState
{
NOT_STARTED,
INIT,
RUN_CTORS,
READ_DYLIB,
RUN_DYLIB_CTORS,
RUN_DTORS,
SHUTDOWN
}
StartupState runtime_state = NOT_STARTED;
Callback* ctor_first;
Callback* dtor_first;
fn void runtime_startup() @public @export("__c3_runtime_startup")
{
if (runtime_state != NOT_STARTED) return;
runtime_state = INIT;
_dyld_register_func_for_add_image(&dl_reg_callback);
assert(runtime_state == INIT);
runtime_state = RUN_CTORS;
Callback* ctor = ctor_first;
while (ctor)
{
ctor.xtor();
ctor = ctor.next;
}
assert(runtime_state == RUN_CTORS);
runtime_state = READ_DYLIB;
ctor_first = null;
}
fn void runtime_finalize() @public @export("__c3_runtime_finalize")
{
if (runtime_state != READ_DYLIB) return;
runtime_state = RUN_DTORS;
Callback* dtor = dtor_first;
while (dtor)
{
dtor.xtor();
dtor = dtor.next;
}
assert(runtime_state == RUN_DTORS);
runtime_state = SHUTDOWN;
}
fn void append_xxlizer(Callback** ref, Callback* cb)
{
while (Callback* current = *ref, current)
{
if (current.priority > cb.priority)
{
cb.next = current;
break;
}
ref = &current.next;
}
*ref = cb;
}
struct TypeId
{
char type;
TypeId* parentof;
DynamicMethod* dtable;
usz sizeof;
TypeId* inner;
usz len;
typeid[?] additional;
}
fn void dl_reg_callback(MachHeader* mh, isz vmaddr_slide)
{
usz size = 0;
assert(runtime_state == INIT || runtime_state == READ_DYLIB, "State was %s", runtime_state);
foreach (&dm : find_segment_section_body(mh, "__DATA", "__c3_dynamic", DynamicMethod))
{
TypeId* type = dm.type;
dm.next = type.dtable;
type.dtable = dm;
DynamicMethod* m = dm;
while (m)
{
m = m.next;
}
}
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3dtor", Callback))
{
append_xxlizer(&dtor_first, cb);
}
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3ctor", Callback))
{
append_xxlizer(&ctor_first, cb);
}
if (runtime_state != READ_DYLIB) return;
runtime_state = RUN_DYLIB_CTORS;
Callback* ctor = ctor_first;
ctor_first = null;
while (ctor)
{
ctor.xtor();
ctor = ctor.next;
}
assert(runtime_state == RUN_DYLIB_CTORS);
runtime_state = READ_DYLIB;
}

View File

@@ -21,7 +21,7 @@ macro int @main_to_void_main(#m, int, char**)
macro String[] args_to_strings(int argc, char** argv) @private macro String[] args_to_strings(int argc, char** argv) @private
{ {
String[] list = mem::new_array(String, argc); String[] list = mem::alloc_array(String, argc);
for (int i = 0; i < argc; i++) for (int i = 0; i < argc; i++)
{ {
char* arg = argv[i]; char* arg = argv[i];
@@ -47,6 +47,13 @@ macro int @main_to_int_main_args(#m, int argc, char** argv)
return #m(list); 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) macro int @main_to_void_main_args(#m, int argc, char** argv)
{ {
String[] list = args_to_strings(argc, argv); String[] list = args_to_strings(argc, argv);
@@ -68,7 +75,7 @@ macro String[] win_command_line_to_strings(ushort* cmd_line) @private
macro String[] wargs_strings(int argc, Char16** argv) @private macro String[] wargs_strings(int argc, Char16** argv) @private
{ {
String[] list = mem::new_array(String, argc); String[] list = mem::alloc_array(String, argc);
for (int i = 0; i < argc; i++) for (int i = 0; i < argc; i++)
{ {
Char16* arg = argv[i]; Char16* arg = argv[i];
@@ -84,19 +91,19 @@ macro void release_wargs(String[] list) @private
free(list.ptr); free(list.ptr);
} }
macro int @win_to_err_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) macro int @win_to_err_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{ {
if (catch #m()) return 1; if (catch #m()) return 1;
return 0; return 0;
} }
macro int @win_to_int_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) => #m(); macro int @win_to_int_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd) => #m();
macro int @win_to_void_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) macro int @win_to_void_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{ {
#m(); #m();
return 0; return 0;
} }
macro int @win_to_err_main_args(#m, void* handle, Char16* cmd_line, int show_cmd) macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{ {
String[] args = win_command_line_to_strings(cmd_line); String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args); defer release_wargs(args);
@@ -104,14 +111,14 @@ macro int @win_to_err_main_args(#m, void* handle, Char16* cmd_line, int show_cmd
return 0; return 0;
} }
macro int @win_to_int_main_args(#m, void* handle, Char16* cmd_line, int show_cmd) macro int @win_to_int_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{ {
String[] args = win_command_line_to_strings(cmd_line); String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args); defer release_wargs(args);
return #m(args); return #m(args);
} }
macro int @win_to_void_main_args(#m, void* handle, Char16* cmd_line, int show_cmd) macro int @win_to_void_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{ {
String[] args = win_command_line_to_strings(cmd_line); String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args); defer release_wargs(args);
@@ -119,26 +126,26 @@ macro int @win_to_void_main_args(#m, void* handle, Char16* cmd_line, int show_cm
return 0; return 0;
} }
macro int @win_to_err_main(#m, void* handle, Char16* cmd_line, int show_cmd) macro int @win_to_err_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{ {
String[] args = win_command_line_to_strings(cmd_line); String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args); defer release_wargs(args);
if (catch #m(handle, args, show_cmd)) return 1; if (catch #m(handle, prev_handle, args, show_cmd)) return 1;
return 0; return 0;
} }
macro int @win_to_int_main(#m, void* handle, Char16* cmd_line, int show_cmd) macro int @win_to_int_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{ {
String[] args = win_command_line_to_strings(cmd_line); String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args); defer release_wargs(args);
return #m(handle, args, show_cmd); return #m(handle, prev_handle, args, show_cmd);
} }
macro int @win_to_void_main(#m, void* handle, Char16* cmd_line, int show_cmd) macro int @win_to_void_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{ {
String[] args = win_command_line_to_strings(cmd_line); String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args); defer release_wargs(args);
#m(handle, args, show_cmd); #m(handle, prev_handle, args, show_cmd);
return 0; return 0;
} }
@@ -157,6 +164,13 @@ macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
return #m(args); 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) macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
{ {
String[] args = wargs_strings(argc, argv); String[] args = wargs_strings(argc, argv);

View File

@@ -2,243 +2,26 @@
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::runtime; module std::core::runtime;
import libc; import libc, std::time, std::io, std::sort;
struct AnyStruct struct ReflectedParam (Printable) @if(!$defined(ReflectedParam))
{
String name;
typeid type;
}
struct AnyRaw
{ {
void* ptr; void* ptr;
typeid type; typeid type;
} }
struct SubArrayStruct struct SliceRaw
{ {
void* ptr; void* ptr;
usz len; usz len;
} }
def BenchmarkFn = fn void!();
struct BenchmarkUnit
{
String name;
BenchmarkFn func;
}
fn BenchmarkUnit[] benchmark_collection_create(Allocator* allocator = mem::heap())
{
BenchmarkFn[] fns = $$BENCHMARK_FNS;
String[] names = $$BENCHMARK_NAMES;
BenchmarkUnit[] benchmarks = allocator.new_array(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(mem::temp()));
};
}
def TestFn = fn void!();
struct TestUnit
{
String name;
TestFn func;
}
fn TestUnit[] test_collection_create(Allocator* allocator = mem::heap())
{
TestFn[] fns = $$TEST_FNS;
String[] names = $$TEST_NAMES;
TestUnit[] tests = allocator.new_array(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());
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(mem::temp()));
};
}
module std::core::runtime @if(WASM_NOLIBC); module std::core::runtime @if(WASM_NOLIBC);

View File

@@ -0,0 +1,174 @@
module std::core::runtime;
import libc, std::time, std::io, std::sort;
def BenchmarkFn = fn void!() @if($$OLD_TEST);
def BenchmarkFn = fn void() @if(!$$OLD_TEST);
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) @if($$OLD_TEST)
{
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 run_benchmarks(BenchmarkUnit[] benchmarks) @if(!$$OLD_TEST)
{
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(allocator::temp()));
}

View File

@@ -0,0 +1,322 @@
// 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 libc, std::time, std::io, std::sort;
import std::os::env;
def TestFn = fn void!() @if($$OLD_TEST);
def TestFn = fn void() @if(!$$OLD_TEST);
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;
String current_test_name;
TestFn setup_fn;
TestFn teardown_fn;
char* error_buffer;
usz error_buffer_capacity;
File fake_stdout;
File orig_stdout;
File orig_stderr;
}
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;
}
// 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::get_var_temp("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;
libc::longjmp(&test_context.buf, 1);
}
fn void mute_output() @local
{
if (!test_context.fake_stdout.file) return;
assert(!test_context.orig_stderr.file);
assert(!test_context.orig_stdout.file);
File* stdout = io::stdout();
File* stderr = io::stderr();
test_context.orig_stderr = *stderr;
test_context.orig_stdout = *stdout;
*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.fake_stdout.file)
{
return;
}
assert(test_context.orig_stderr.file);
assert(test_context.orig_stdout.file);
File* stdout = io::stdout();
File* stderr = io::stderr();
*stderr = test_context.orig_stderr;
*stdout = test_context.orig_stdout;
test_context.orig_stderr.file = null;
test_context.orig_stdout.file = null;
usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
if (has_error)
{
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;
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(),
};
for (int i = 1; i < args.len; i++)
{
switch (args[i])
{
case "breakpoint":
context.breakpoint_on_assert = true;
case "nosort":
sort_tests = false;
case "noansi":
context.has_ansi_codes = false;
case "useansi":
context.has_ansi_codes = true;
case "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):
test_context.fake_stdout.file = libc::tmpfile();
$endif
if (test_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);
io::printn(name);
name.clear();
foreach(unit : tests)
{
if (test_context.test_filter && !unit.name.contains(test_context.test_filter))
{
tests_skipped++;
continue;
}
test_context.setup_fn = null;
test_context.teardown_fn = null;
test_context.current_test_name = unit.name;
defer name.clear();
name.appendf("Testing %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
TrackingAllocator mem;
mem.init(allocator::heap());
if (libc::setjmp(&context.buf) == 0)
{
mute_output();
mem.clear();
mem::@scoped(&mem)
{
$if(!$$OLD_TEST):
unit.func();
$else
if (catch err = unit.func())
{
io::printf("[FAIL] Failed due to: %s", err);
continue;
}
$endif
};
unmute_output(false); // all good, discard output
if (mem.has_leaks())
{
io::print(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
io::printn(" LEAKS DETECTED!");
mem.print_report();
}
else
{
io::printfn(test_context.has_ansi_codes ? "[\e[0;32mPASS\e[0m]" : "[PASS]");
tests_passed++;
}
if (test_context.teardown_fn)
{
test_context.teardown_fn();
}
}
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: ",
test_context.has_ansi_codes ? (n_failed ? "\e[0;31m" : "\e[0;32m") : "",
n_failed ? "FAILED" : "PASSED",
test_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 (test_context.fake_stdout.file) libc::fclose(test_context.fake_stdout.file);
test_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(allocator::temp()));
}

View File

@@ -0,0 +1,127 @@
// Add this to your code to suppress leak detection or set other default options
// fn ZString __asan_default_options() @export("__asan_default_options") @if(env::ADDRESS_SANITIZER)
// {
// return "detect_leaks=0";
// }
// Add this to break on error
// asan::set_error_report_callback(fn (ZString err)
// {
// breakpoint();
// });
module std::core::sanitizer::asan;
def ErrorCallback = fn void (ZString);
<*
Marks a memory region ([addr, addr+size)) as unaddressable.
This memory must be previously allocated by your program. Instrumented
code is forbidden from accessing addresses in this region until it is
unpoisoned. This function is not guaranteed to poison the entire region -
it could poison only a subregion of [addr, addr+size) due to ASan
alignment restrictions.
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."
*>
macro poison_memory_region(void* addr, usz size)
{
$if env::ADDRESS_SANITIZER:
__asan_poison_memory_region(addr, size);
$endif
}
<*
Marks a memory region ([addr, addr+size)) as addressable.
This memory must be previously allocated by your program. Accessing
addresses in this region is allowed until this region is poisoned again.
This function could unpoison a super-region of [addr, addr+size) due
to ASan alignment restrictions.
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."
*>
macro unpoison_memory_region(void* addr, usz size)
{
$if env::ADDRESS_SANITIZER:
__asan_unpoison_memory_region(addr, size);
$endif
}
<*
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."
*>
macro bool address_is_poisoned(void* addr)
{
$if env::ADDRESS_SANITIZER:
return (bool)__asan_address_is_poisoned(addr);
$else
return false;
$endif
}
<*
Checks if a region is poisoned.
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."
@return "Address of first poisoned byte."
*>
macro void* region_is_poisoned(void* beg, usz size)
{
$if env::ADDRESS_SANITIZER:
return __asan_region_is_poisoned(beg, size);
$else
return null;
$endif
}
<*
Sets the callback function to be called during ASan error reporting.
*>
fn void set_error_report_callback(ErrorCallback callback)
{
$if env::ADDRESS_SANITIZER:
__asan_set_error_report_callback(callback);
$endif
}
module std::core::sanitizer::asan @if(env::ADDRESS_SANITIZER);
extern fn void __asan_poison_memory_region(void* addr, usz size);
extern fn void __asan_unpoison_memory_region(void* addr, usz size);
extern fn CInt __asan_address_is_poisoned(void* addr);
extern fn void* __asan_region_is_poisoned(void* beg, usz size);
extern fn void __asan_describe_address(void* addr);
extern fn CInt __asan_report_present();
extern fn void* __asan_get_report_pc();
extern fn void* __asan_get_report_bp();
extern fn void* __asan_get_report_sp();
extern fn void* __asan_get_report_address();
extern fn CInt __asan_get_report_access_type();
extern fn usz __asan_get_report_access_size();
extern fn ZString __asan_get_report_description();
extern fn ZString __asan_locate_address(void* addr, char* name, usz name_size, void** region_address, usz* region_size);
extern fn usz __asan_get_alloc_stack(void* addr, void** trace, usz size, CInt* thread_id);
extern fn usz __asan_get_free_stack(void* addr, void** trace, usz size, CInt* thread_id);
extern fn void __asan_get_shadow_mapping(usz* shadow_scale, usz* shadow_offset);
extern fn void __asan_set_error_report_callback(ErrorCallback callback);
extern fn void __asan_print_accumulated_stats();
extern fn void* __asan_get_current_fake_stack();
extern fn void* __asan_addr_is_in_fake_stack(void* fake_stack, void* addr, void** beg, void** end);
extern fn void __asan_handle_no_return();
extern fn CInt __asan_update_allocation_context(void* addr);

View File

@@ -0,0 +1,80 @@
module std::core::sanitizer;
macro void annotate_contiguous_container(void* beg, void* end, void* old_mid, void* new_mid)
{
$if env::ADDRESS_SANITIZER:
__sanitizer_annotate_contiguous_container(beg, end, old_mid, new_mid);
$endif
}
macro void annotate_double_ended_contiguous_container(void* storage_beg, void* storage_end, void* old_container_beg, void* old_container_end, void* new_container_beg, void* new_container_end)
{
$if env::ADDRESS_SANITIZER:
__sanitizer_annotate_double_ended_contiguous_container(storage_beg, storage_end, old_container_beg, old_container_end, new_container_beg, new_container_end);
$endif
}
macro void print_stack_trace()
{
$if env::ADDRESS_SANITIZER:
__sanitizer_print_stack_trace();
$endif
}
fn void set_death_callback(VoidFn callback)
{
$if env::ANY_SANITIZER:
__sanitizer_set_death_callback(callback);
$endif
}
module std::core::sanitizer @if (env::ANY_SANITIZER);
struct __Sanitizer_sandbox_arguments
{
CInt coverage_sandboxed;
iptr coverage_fd;
CUInt coverage_max_block_size;
}
extern fn void __sanitizer_set_report_path(ZString path);
extern fn void __sanitizer_set_report_fd(void* fd);
extern fn ZString __sanitizer_get_report_path();
extern fn void __sanitizer_sandbox_on_notify(__Sanitizer_sandbox_arguments* args);
extern fn void __sanitizer_report_error_summary(ZString error_summary);
extern fn ushort __sanitizer_unaligned_load16(void* p);
extern fn uint __sanitizer_unaligned_load32(void* p);
extern fn ulong __sanitizer_unaligned_load64(void* p);
extern fn void __sanitizer_unaligned_store16(void* p, ushort x);
extern fn void __sanitizer_unaligned_store32(void* p, uint x);
extern fn void __sanitizer_unaligned_store64(void* p, ulong x);
extern fn CInt __sanitizer_acquire_crash_state();
extern fn void __sanitizer_annotate_contiguous_container(void* beg, void* end, void* old_mid, void* new_mid);
extern fn void __sanitizer_annotate_double_ended_contiguous_container(void* storage_beg, void* storage_end,
void* old_container_beg, void* old_container_end,
void* new_container_beg, void* new_container_end);
extern fn CInt __sanitizer_verify_contiguous_container(void* beg, void* mid, void* end);
extern fn CInt __sanitizer_verify_double_ended_contiguous_container(
void* storage_beg, void* container_beg,
void* container_end, void* storage_end);
extern fn void* __sanitizer_contiguous_container_find_bad_address(void* beg, void* mid, void* end);
extern fn void* __sanitizer_double_ended_contiguous_container_find_bad_address(
void* storage_beg, void* container_beg,
void* container_end, void* storage_end);
extern fn void __sanitizer_print_stack_trace();
extern fn void __sanitizer_symbolize_pc(void* pc, ZString fmt, char* out_buf, usz out_buf_size);
extern fn void __sanitizer_symbolize_global(void* data_ptr, ZString fmt, char* out_buf, usz out_buf_size);
extern fn void __sanitizer_set_death_callback(VoidFn callback);
extern fn void __sanitizer_weak_hook_memcmp(void* called_pc, void* s1, void* s2, usz n, CInt result);
extern fn void __sanitizer_weak_hook_strncmp(void* called_pc, ZString s1, ZString s2, usz n, CInt result);
extern fn void __sanitizer_weak_hook_strncasecmp(void* called_pc, ZString s1, ZString s2, usz n, CInt result);
extern fn void __sanitizer_weak_hook_strcmp(void* called_pc, ZString s1, ZString s2, CInt result);
extern fn void __sanitizer_weak_hook_strcasecmp(void* called_pc, ZString s1, ZString s2, CInt result);
extern fn void __sanitizer_weak_hook_strstr(void* called_pc, ZString s1, ZString s2, char* result);
extern fn void __sanitizer_weak_hook_strcasestr(void* called_pc, ZString s1, ZString s2, char* result);
extern fn void __sanitizer_weak_hook_memmem(void* called_pc, void* s1, usz len1, void* s2, usz len2, void* result);
extern fn void __sanitizer_print_memory_profile(usz top_percent, usz max_number_of_contexts);
extern fn void __sanitizer_start_switch_fiber(void** fake_stack_save, void* bottom, usz size);
extern fn void __sanitizer_finish_switch_fiber(void* fake_stack_save, void** bottom_old, usz* size_old);
extern fn CInt __sanitizer_get_module_and_offset_for_pc(void* pc, char* module_path, usz module_path_len, void** pc_offset);

View File

@@ -0,0 +1,39 @@
module std::core::sanitizer::tsan;
distinct MutexFlags = inline CUInt;
const MutexFlags MUTEX_LINKER_INIT = 1 << 0;
const MutexFlags MUTEX_WRITE_REENTRANT = 1 << 1;
const MutexFlags MUTEX_READ_REENTRANT = 1 << 2;
const MutexFlags MUTEX_NOT_STATIC = 1 << 8;
const MutexFlags MUTEX_READ_LOCK = 1 << 3;
const MutexFlags MUTEX_TRY_LOCK = 1 << 4;
const MutexFlags MUTEX_TRY_LOCK_FAILED = 1 << 5;
const MutexFlags MUTEX_RECURSIVE_LOCK = 1 << 6;
const MutexFlags MUTEX_RECURSIVE_UNLOCK = 1 << 7;
const MutexFlags MUTEX_TRY_READ_LOCK = MUTEX_READ_LOCK | MUTEX_TRY_LOCK;
const MutexFlags MUTEX_TRY_READ_LOCK_FAILED = MUTEX_TRY_READ_LOCK | MUTEX_TRY_LOCK_FAILED;
macro void mutex_create(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_create(addr, flags); $endif }
macro void mutex_destroy(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_destroy(addr, flags); $endif }
macro void mutex_pre_lock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_lock(addr, flags); $endif }
macro void mutex_post_lock(void* addr, MutexFlags flags, CInt recursion) { $if env::THREAD_SANITIZER: __tsan_mutex_post_lock(addr, flags, recursion); $endif }
macro CInt mutex_pre_unlock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: return __tsan_mutex_pre_unlock(addr, flags); $else return 0; $endif }
macro void mutex_post_unlock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_unlock(addr, flags); $endif }
macro void mutex_pre_signal(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_signal(addr, flags); $endif }
macro void mutex_post_signal(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_signal(addr, flags); $endif }
macro void mutex_pre_divert(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_divert(addr, flags); $endif }
macro void mutex_post_divert(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_divert(addr, flags); $endif }
module std::core::sanitizer::tsan @if(env::THREAD_SANITIZER) @private;
extern fn void __tsan_mutex_create(void* addr, CUInt flags);
extern fn void __tsan_mutex_destroy(void* addr, CUInt flags);
extern fn void __tsan_mutex_pre_lock(void* addr, CUInt flags);
extern fn void __tsan_mutex_post_lock(void* addr, CUInt flags, CInt recursion);
extern fn CInt __tsan_mutex_pre_unlock(void* addr, CUInt flags);
extern fn void __tsan_mutex_post_unlock(void* addr, CUInt flags);
extern fn void __tsan_mutex_pre_signal(void* addr, CUInt flags);
extern fn void __tsan_mutex_post_signal(void* addr, CUInt flags);
extern fn void __tsan_mutex_pre_divert(void* addr, CUInt flags);
extern fn void __tsan_mutex_post_divert(void* addr, CUInt flags);

View File

@@ -1,6 +1,8 @@
module std::core::string; module std::core::string;
import std::ascii; import std::ascii;
import std::io;
distinct String @if(!$defined(String)) = inline char[];
distinct ZString = inline char*; distinct ZString = inline char*;
distinct WString = inline Char16*; distinct WString = inline Char16*;
def Char32 = uint; def Char32 = uint;
@@ -31,35 +33,83 @@ fault NumberConversion
FLOAT_OUT_OF_RANGE, FLOAT_OUT_OF_RANGE,
} }
macro String tformat(String fmt, ...)
<*
Return a temporary ZString created using the formatting function.
@param [in] fmt `The formatting string`
*>
fn ZString tformat_zstr(String fmt, args...)
{ {
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8); DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
str.appendf(fmt, $vasplat()); str.appendf(fmt, ...args);
return str.zstr_view();
}
<*
Return a new String created using the formatting function.
@param [inout] allocator `The allocator to use`
@param [in] fmt `The formatting string`
*>
fn String format(String fmt, args..., Allocator allocator) => @pool(allocator)
{
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
str.appendf(fmt, ...args);
return str.copy_str(allocator);
}
<*
Return a heap allocated String created using the formatting function.
@param [in] fmt `The formatting string`
*>
fn String new_format(String fmt, args..., Allocator allocator = null) => format(fmt, ...args, allocator: allocator ?: allocator::heap());
<*
Return a temporary String created using the formatting function.
@param [in] fmt `The formatting string`
*>
fn String tformat(String fmt, args...)
{
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
str.appendf(fmt, ...args);
return str.str_view(); return str.str_view();
} }
macro String new_format(String fmt, ..., Allocator* allocator = mem::heap()) <*
Return a new ZString created using the formatting function.
@param [in] fmt `The formatting string`
@param [inout] allocator `The allocator to use`
*>
fn ZString new_format_zstr(String fmt, args..., Allocator allocator = allocator::heap()) => @pool(allocator)
{ {
@pool(allocator) DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
{ str.appendf(fmt, ...args);
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8); return str.copy_zstr(allocator);
str.appendf(fmt, $vasplat());
return str.copy_str(allocator);
};
} }
<*
Check if a character is in a set.
@param c `the character to check`
@param [in] set `The formatting string`
@pure
@return `True if a character is in the set`
*>
macro bool char_in_set(char c, String set) macro bool char_in_set(char c, String set)
{ {
foreach (ch : set) if (ch == c) return true; foreach (ch : set) if (ch == c) return true;
return false; return false;
} }
fn String join_new(String[] s, String joiner, Allocator* allocator = mem::heap()) fn String join_new(String[] s, String joiner, Allocator allocator = allocator::heap())
{ {
if (!s) if (!s)
{ {
return (String)allocator.new_zero_array(char, 2)[:0]; return (String)allocator::new_array(allocator, char, 2)[:0];
} }
usz total_size = joiner.len * s.len; usz total_size = joiner.len * s.len;
@@ -80,25 +130,59 @@ fn String join_new(String[] s, String joiner, Allocator* allocator = mem::heap()
}; };
} }
/** <*
* @param [in] string Remove characters from the front and end of a string.
* @param [in] to_trim
**/ @param [in] string `The string to trim`
@param [in] to_trim `The set of characters to trim, defaults to whitespace`
@pure
@return `a substring of the string passed in`
*>
fn String String.trim(string, String to_trim = "\t\n\r ") fn String String.trim(string, String to_trim = "\t\n\r ")
{
return string.trim_left(to_trim).trim_right(to_trim);
}
<*
Remove characters from the front of a string.
@param [in] string `The string to trim`
@param [in] to_trim `The set of characters to trim, defaults to whitespace`
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_left(string, String to_trim = "\t\n\r ")
{ {
usz start = 0; usz start = 0;
usz len = string.len; usz len = string.len;
while (start < len && char_in_set(string[start], to_trim)) start++; while (start < len && char_in_set(string[start], to_trim)) start++;
if (start == len) return string[:0]; if (start == len) return string[:0];
usz end = len - 1; return string[start..];
while (end > start && char_in_set(string[end], to_trim)) end--;
return string[start..end];
} }
/** <*
* @param [in] string Remove characters from the end of a string.
* @param [in] needle
**/ @param [in] string `The string to trim`
@param [in] to_trim `The set of characters to trim, defaults to whitespace`
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_right(string, String to_trim = "\t\n\r ")
{
usz len = string.len;
while (len > 0 && char_in_set(string[len - 1], to_trim)) len--;
return string[:len];
}
<*
Check if the String starts with the needle.
@param [in] string
@param [in] needle
@pure
@return `'true' if the string starts with the needle`
*>
fn bool String.starts_with(string, String needle) fn bool String.starts_with(string, String needle)
{ {
if (needle.len > string.len) return false; if (needle.len > string.len) return false;
@@ -106,10 +190,14 @@ fn bool String.starts_with(string, String needle)
return string[:needle.len] == needle; return string[:needle.len] == needle;
} }
/** <*
* @param [in] string Check if the String ends with the needle.
* @param [in] needle
**/ @param [in] string
@param [in] needle
@pure
@return `'true' if the string ends with the needle`
*>
fn bool String.ends_with(string, String needle) fn bool String.ends_with(string, String needle)
{ {
if (needle.len > string.len) return false; if (needle.len > string.len) return false;
@@ -117,24 +205,28 @@ fn bool String.ends_with(string, String needle)
return string[^needle.len..] == needle; return string[^needle.len..] == needle;
} }
/** <*
* Strip the front of the string if the prefix exists. Strip the front of the string if the prefix exists.
*
* @param [in] string @param [in] string
* @param [in] needle @param [in] needle
**/ @pure
@return `the substring with the prefix removed`
*>
fn String String.strip(string, String needle) fn String String.strip(string, String needle)
{ {
if (!needle.len || !string.starts_with(needle)) return string; if (!needle.len || !string.starts_with(needle)) return string;
return string[needle.len..]; return string[needle.len..];
} }
/** <*
* Strip the end of the string if the suffix exists. Strip the end of the string if the suffix exists.
*
* @param [in] string @param [in] string
* @param [in] needle @param [in] needle
**/ @pure
@return `the substring with the suffix removed`
*>
fn String String.strip_end(string, String needle) fn String String.strip_end(string, String needle)
{ {
if (!needle.len || !string.ends_with(needle)) return string; if (!needle.len || !string.ends_with(needle)) return string;
@@ -142,22 +234,23 @@ fn String String.strip_end(string, String needle)
return string[:(string.len - needle.len)]; return string[:(string.len - needle.len)];
} }
<*
Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
/** @param [in] s
* Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" } @param [in] needle
* @param max "Max number of elements, 0 means no limit, defaults to 0"
* @param [in] s @param skip_empty "True to skip empty elements"
* @param [in] needle @param [&inout] allocator "The allocator to use for the String[]"
* @param [&inout] allocator "The allocator, defaults to the heap allocator"
* @param max "Max number of elements, 0 means no limit, defaults to 0" @require needle.len > 0 "The needle must be at least 1 character long"
* @require needle.len > 0 "The needle must be at least 1 character long" @ensure return.len > 0
* @ensure return.len > 0 *>
**/ fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = allocator::heap(), bool skip_empty = false)
fn String[] String.split(s, String needle, usz max = 0, Allocator* allocator = mem::heap())
{ {
usz capacity = 16; usz capacity = 16;
usz i = 0; usz i = 0;
String* holder = allocator.new_array(String, capacity); String* holder = allocator::alloc_array(allocator, String, capacity);
bool no_more = false; bool no_more = false;
while (!no_more) while (!no_more)
{ {
@@ -173,43 +266,113 @@ fn String[] String.split(s, String needle, usz max = 0, Allocator* allocator = m
res = s; res = s;
no_more = true; no_more = true;
} }
if (!res.len && skip_empty)
{
continue;
}
if (i == capacity) if (i == capacity)
{ {
capacity *= 2; capacity *= 2;
holder = allocator.realloc(holder, String.sizeof * capacity); holder = allocator::realloc(allocator, holder, String.sizeof * capacity);
} }
holder[i++] = res; holder[i++] = res;
} }
return holder[:i]; return holder[:i];
} }
/** <*
* This function is identical to String.split, but implicitly uses the Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }, using the heap allocator
* temporary allocator. to store the parts.
*
* @param [in] s @param [in] s
* @param [in] needle @param [in] needle
* @param max "Max number of elements, 0 means no limit, defaults to 0" @param max "Max number of elements, 0 means no limit, defaults to 0"
**/ @param skip_empty "True to skip empty elements"
fn String[] String.tsplit(s, String needle, usz max = 0) @require needle.len > 0 "The needle must be at least 1 character long"
@ensure return.len > 0
*>
fn String[] String.new_split(s, String needle, usz max = 0, bool skip_empty) => s.split(needle, max, allocator::heap(), skip_empty) @inline;
<*
This function is identical to String.split, but implicitly uses the
temporary allocator.
@param [in] s
@param [in] needle
@param max "Max number of elements, 0 means no limit, defaults to 0"
@param skip_empty "True to skip empty elements"
*>
fn String[] String.tsplit(s, String needle, usz max = 0, bool skip_empty = false) => s.split(needle, max, allocator::temp(), skip_empty) @inline;
fault SplitResult { BUFFER_EXCEEDED }
<*
Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
@param [in] s
@param [in] needle
@param [inout] buffer
@param max "Max number of elements, 0 means no limit, defaults to 0"
@require needle.len > 0 "The needle must be at least 1 character long"
@ensure return.len > 0
@return! SplitResult.BUFFER_EXCEEDED `If there are more elements than would fit the buffer`
*>
fn String[]! String.split_to_buffer(s, String needle, String[] buffer, usz max = 0, bool skip_empty = false)
{ {
return s.split(needle, max, mem::temp()) @inline; usz max_capacity = buffer.len;
usz i = 0;
bool no_more = false;
while (!no_more)
{
usz! index = i == max - 1 ? SearchResult.MISSING? : s.index_of(needle);
String res @noinit;
if (try index)
{
res = s[:index];
s = s[index + needle.len..];
}
else
{
res = s;
no_more = true;
}
if (!res.len && skip_empty)
{
continue;
}
if (i == max_capacity)
{
return SplitResult.BUFFER_EXCEEDED?;
}
buffer[i++] = res;
}
return buffer[:i];
} }
<*
Check if a substring is found in the string.
@param [in] s
@param [in] needle "The string to look for."
@pure
@return "true if the string contains the substring, false otherwise"
*>
fn bool String.contains(s, String needle) fn bool String.contains(s, String needle)
{ {
return @ok(s.index_of(needle)); return @ok(s.index_of(needle));
} }
/** <*
* Find the index of the first incidence of a string. Find the index of the first incidence of a string.
*
* @param [in] s @param [in] s
* @pure @param needle "The character to look for"
* @ensure return < s.len @pure
* @return "the index of the needle" @ensure return < s.len
* @return! SearchResult.MISSING "if the needle cannot be found" @return "the index of the needle"
**/ @return! SearchResult.MISSING "if the needle cannot be found"
*>
fn usz! String.index_of_char(s, char needle) fn usz! String.index_of_char(s, char needle)
{ {
foreach (i, c : s) foreach (i, c : s)
@@ -219,15 +382,61 @@ fn usz! String.index_of_char(s, char needle)
return SearchResult.MISSING?; return SearchResult.MISSING?;
} }
/** <*
* Find the index of the first incidence of a string. Find the index of the first incidence of a one of the chars.
*
* @param [in] s @param [in] s
* @pure @param [in] needle "The characters to look for"
* @ensure return < s.len @pure
* @return "the index of the needle" @ensure return < s.len
* @return! SearchResult.MISSING "if the needle cannot be found" @return "the index of the needle"
**/ @return! SearchResult.MISSING "if the needle cannot be found"
*>
fn usz! String.index_of_chars(String s, char[] needle)
{
foreach (i, c : s)
{
foreach (j, pin : needle)
{
if (c == pin) return i;
}
}
return SearchResult.MISSING?;
}
<*
Find the index of the first incidence of a character.
@param [in] s
@param needle "The character to look for"
@param start_index "The index to start with, may exceed max index."
@pure
@ensure return < s.len
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found starting from the start_index"
*>
fn usz! String.index_of_char_from(s, char needle, usz start_index)
{
usz len = s.len;
if (len <= start_index) return SearchResult.MISSING?;
for (usz i = start_index; i < len; i++)
{
if (s[i] == needle) return i;
}
return SearchResult.MISSING?;
}
<*
Find the index of the first incidence of a character starting from the end.
@param [in] s
@param needle "the character to find"
@pure
@ensure return < s.len
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found"
*>
fn usz! String.rindex_of_char(s, char needle) fn usz! String.rindex_of_char(s, char needle)
{ {
foreach_r (i, c : s) foreach_r (i, c : s)
@@ -237,17 +446,17 @@ fn usz! String.rindex_of_char(s, char needle)
return SearchResult.MISSING?; return SearchResult.MISSING?;
} }
/** <*
* Find the index of the first incidence of a string. Find the index of the first incidence of a string.
*
* @param [in] s @param [in] s
* @param [in] needle @param [in] needle
* @pure @pure
* @ensure return < s.len @ensure return < s.len
* @require needle.len > 0 : "The needle must be len 1 or more" @require needle.len > 0 : "The needle must be len 1 or more"
* @return "the index of the needle" @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found" @return! SearchResult.MISSING "if the needle cannot be found"
**/ *>
fn usz! String.index_of(s, String needle) fn usz! String.index_of(s, String needle)
{ {
usz needed = needle.len; usz needed = needle.len;
@@ -262,17 +471,17 @@ fn usz! String.index_of(s, String needle)
return SearchResult.MISSING?; return SearchResult.MISSING?;
} }
/** <*
* Find the index of the last incidence of a string. Find the index of the last incidence of a string.
*
* @param [in] s @param [in] s
* @param [in] needle @param [in] needle
* @pure @pure
* @ensure return < s.len @ensure return < s.len
* @require needle.len > 0 "The needle must be len 1 or more" @require needle.len > 0 "The needle must be len 1 or more"
* @return "the index of the needle" @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found" @return! SearchResult.MISSING "if the needle cannot be found"
**/ *>
fn usz! String.rindex_of(s, String needle) fn usz! String.rindex_of(s, String needle)
{ {
usz needed = needle.len; usz needed = needle.len;
@@ -312,19 +521,19 @@ fn usz ZString.len(str)
} }
fn ZString String.zstr_copy(s, Allocator* allocator = mem::heap()) fn ZString String.zstr_copy(s, Allocator allocator = allocator::heap())
{ {
usz len = s.len; usz len = s.len;
char* str = allocator.alloc(len + 1); char* str = allocator::malloc(allocator, len + 1);
mem::copy(str, s.ptr, len); mem::copy(str, s.ptr, len);
str[len] = 0; str[len] = 0;
return (ZString)str; return (ZString)str;
} }
fn String String.concat(s1, String s2, Allocator* allocator = mem::heap()) fn String String.concat(s1, String s2, Allocator allocator = allocator::heap())
{ {
usz full_len = s1.len + s2.len; usz full_len = s1.len + s2.len;
char* str = allocator.alloc(full_len + 1); char* str = allocator::malloc(allocator, full_len + 1);
usz s1_len = s1.len; usz s1_len = s1.len;
mem::copy(str, s1.ptr, s1_len); mem::copy(str, s1.ptr, s1_len);
mem::copy(str + s1_len, s2.ptr, s2.len); mem::copy(str + s1_len, s2.ptr, s2.len);
@@ -332,144 +541,174 @@ fn String String.concat(s1, String s2, Allocator* allocator = mem::heap())
return (String)str[:full_len]; return (String)str[:full_len];
} }
fn String String.tconcat(s1, String s2) => s1.concat(s2, mem::temp()); fn String String.tconcat(s1, String s2) => s1.concat(s2, allocator::temp());
fn ZString String.zstr_tcopy(s) => s.zstr_copy(mem::temp()) @inline; fn ZString String.zstr_tcopy(s) => s.zstr_copy(allocator::temp()) @inline;
fn String String.copy(s, Allocator* allocator = mem::heap()) <*
Copy this string, by duplicating the string, always adding a zero byte
sentinel, so that it safely can be converted to a ZString by a
cast.
*>
fn String String.copy(s, Allocator allocator = allocator::heap())
{ {
usz len = s.len; usz len = s.len;
char* str = allocator.alloc(len + 1); char* str = allocator::malloc(allocator, len + 1);
mem::copy(str, s.ptr, len); mem::copy(str, s.ptr, len);
str[len] = 0; str[len] = 0;
return (String)str[:len]; return (String)str[:len];
} }
fn void String.free(&s, Allocator* allocator = mem::heap()) fn void String.free(&s, Allocator allocator = allocator::heap())
{ {
if (!s.len) return; if (!s.ptr) return;
allocator.free(s.ptr); allocator::free(allocator, s.ptr);
*s = ""; *s = "";
} }
fn String String.tcopy(s) => s.copy(mem::temp()) @inline; fn String String.tcopy(s) => s.copy(allocator::temp()) @inline;
fn String ZString.copy(z, Allocator* allocator = mem::temp()) fn String ZString.copy(z, Allocator allocator = allocator::heap())
{ {
return z.str_view().copy(allocator) @inline; return z.str_view().copy(allocator) @inline;
} }
fn String ZString.tcopy(z) fn String ZString.tcopy(z)
{ {
return z.str_view().copy(mem::temp()) @inline; return z.str_view().copy(allocator::temp()) @inline;
} }
/** <*
* Convert an UTF-8 string to UTF-16 Convert an UTF-8 string to UTF-16
* @return "The UTF-16 string as a slice, allocated using the given allocator" @return "The UTF-16 string as a slice, allocated using the given allocator"
* @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence" @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
* @return! AllocationFailure "If allocation of the string fails" @return! AllocationFailure "If allocation of the string fails"
**/ *>
fn Char16[]! String.to_new_utf16(s, Allocator* allocator = mem::heap()) fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::heap())
{ {
usz len16 = conv::utf16len_for_utf8(s); usz len16 = conv::utf16len_for_utf8(s);
Char16* data = allocator.new_array_checked(Char16, len16 + 1)!; Char16* data = allocator::alloc_array_try(allocator, Char16, len16 + 1)!;
conv::utf8to16_unsafe(s, data)!; conv::utf8to16_unsafe(s, data)!;
data[len16] = 0; data[len16] = 0;
return data[:len16]; return data[:len16];
} }
/** <*
* Convert an UTF-8 string to UTF-16 Convert an UTF-8 string to UTF-16
* @return "The UTF-16 string as a slice, allocated using the given allocator" @return "The UTF-16 string as a slice, allocated using the given allocator"
* @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence" @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
* @return! AllocationFailure "If allocation of the string fails" @return! AllocationFailure "If allocation of the string fails"
**/ *>
fn Char16[]! String.to_temp_utf16(s) fn Char16[]! String.to_temp_utf16(s)
{ {
return s.to_new_utf16(mem::temp()); return s.to_new_utf16(allocator::temp());
} }
fn WString! String.to_new_wstring(s, Allocator* allocator = mem::heap()) fn WString! String.to_wstring(s, Allocator allocator)
{ {
return (WString)s.to_new_utf16(allocator).ptr; return (WString)s.to_new_utf16(allocator).ptr;
} }
fn WString! String.to_temp_wstring(s) fn WString! String.to_temp_wstring(s) => s.to_wstring(allocator::temp());
{ fn WString! String.to_new_wstring(s) => s.to_wstring(allocator::heap());
return (WString)s.to_temp_utf16().ptr;
}
fn Char32[]! String.to_new_utf32(s, Allocator* allocator = mem::heap()) fn Char32[]! String.to_utf32(s, Allocator allocator)
{ {
usz codepoints = conv::utf8_codepoints(s); usz codepoints = conv::utf8_codepoints(s);
Char32* data = allocator.new_array(Char32, codepoints + 1); Char32* data = allocator::alloc_array_try(allocator, Char32, codepoints + 1)!;
conv::utf8to32_unsafe(s, data)!; conv::utf8to32_unsafe(s, data)!;
data[codepoints] = 0; data[codepoints] = 0;
return data[:codepoints]; return data[:codepoints];
} }
fn Char32[]! String.to_temp_utf32(s) fn Char32[]! String.to_new_utf32(s) => s.to_utf32(allocator::heap()) @inline;
{ fn Char32[]! String.to_temp_utf32(s) => s.to_utf32(allocator::temp()) @inline;
return s.to_new_utf32(mem::temp());
}
<*
Convert a string to ASCII lower case.
@param [inout] s
@pure
*>
fn void String.convert_ascii_to_lower(s) fn void String.convert_ascii_to_lower(s)
{ {
foreach (&c : s) if (c.is_upper()) *c += 'a' - 'A'; foreach (&c : s) if (c.is_upper() @pure) *c += 'a' - 'A';
} }
fn String String.new_ascii_to_lower(s, Allocator* allocator = mem::heap()) fn String String.new_ascii_to_lower(s, Allocator allocator = allocator::heap())
{ {
String copy = s.copy(allocator); String copy = s.copy(allocator);
copy.convert_ascii_to_lower(); copy.convert_ascii_to_lower();
return copy; return copy;
} }
fn String String.temp_ascii_to_lower(s, Allocator* allocator = mem::heap()) fn String String.temp_ascii_to_lower(s)
{ {
return s.new_ascii_to_lower(mem::temp()); return s.new_ascii_to_lower(allocator::temp());
} }
<*
Convert a string to ASCII upper case.
@param [inout] s
@pure
*>
fn void String.convert_ascii_to_upper(s) fn void String.convert_ascii_to_upper(s)
{ {
foreach (&c : s) if (c.is_lower()) *c -= 'a' - 'A'; foreach (&c : s) if (c.is_lower() @pure) *c -= 'a' - 'A';
} }
fn String String.new_ascii_to_upper(s, Allocator* allocator = mem::heap()) <*
Returns a string converted to ASCII upper case.
@param [in] s
@param [inout] allocator
@return `a new String converted to ASCII upper case.`
*>
fn String String.new_ascii_to_upper(s, Allocator allocator = allocator::heap())
{ {
String copy = s.copy(allocator); String copy = s.copy(allocator);
copy.convert_ascii_to_upper(); copy.convert_ascii_to_upper();
return copy; return copy;
} }
fn String String.temp_ascii_to_upper(s) fn StringIterator String.iterator(s)
{ {
return s.new_ascii_to_upper(mem::temp()); return { s, 0 };
} }
fn String! new_from_utf32(Char32[] utf32, Allocator* allocator = mem::heap()) <*
@param [in] s
@return `a temporary String converted to ASCII upper case.`
*>
fn String String.temp_ascii_to_upper(s)
{
return s.new_ascii_to_upper(allocator::temp());
}
fn String! new_from_utf32(Char32[] utf32, Allocator allocator = allocator::heap())
{ {
usz len = conv::utf8len_for_utf32(utf32); usz len = conv::utf8len_for_utf32(utf32);
char* data = allocator.alloc_checked(len + 1)!; char* data = allocator::malloc_try(allocator, len + 1)!;
defer catch allocator.free(data); defer catch allocator::free(allocator, data);
conv::utf32to8_unsafe(utf32, data); conv::utf32to8_unsafe(utf32, data);
data[len] = 0; data[len] = 0;
return (String)data[:len]; return (String)data[:len];
} }
fn String! new_from_utf16(Char16[] utf16, Allocator* allocator = mem::heap()) fn String! new_from_utf16(Char16[] utf16, Allocator allocator = allocator::heap())
{ {
usz len = conv::utf8len_for_utf16(utf16); usz len = conv::utf8len_for_utf16(utf16);
char* data = allocator.alloc_checked(len + 1)!; char* data = allocator::malloc_try(allocator, len + 1)!;
defer catch allocator.free(data); defer catch allocator::free(allocator, data);
conv::utf16to8_unsafe(utf16, data)!; conv::utf16to8_unsafe(utf16, data)!;
data[len] = 0; data[len] = 0;
return (String)data[:len]; return (String)data[:len];
} }
fn String! new_from_wstring(WString wstring, Allocator* allocator = mem::heap()) fn String! new_from_wstring(WString wstring, Allocator allocator = allocator::heap())
{ {
usz utf16_len; usz utf16_len;
while (wstring[utf16_len] != 0) utf16_len++; while (wstring[utf16_len] != 0) utf16_len++;
@@ -477,8 +716,8 @@ fn String! new_from_wstring(WString wstring, Allocator* allocator = mem::heap())
return new_from_utf16(utf16, allocator); return new_from_utf16(utf16, allocator);
} }
fn String! temp_from_wstring(WString wstring) => new_from_wstring(wstring, mem::temp()) @inline; fn String! temp_from_wstring(WString wstring) => new_from_wstring(wstring, allocator::temp()) @inline;
fn String! temp_from_utf16(Char16[] utf16) => new_from_utf16(utf16, mem::temp()) @inline; fn String! temp_from_utf16(Char16[] utf16) => new_from_utf16(utf16, allocator::temp()) @inline;
fn usz String.utf8_codepoints(s) fn usz String.utf8_codepoints(s)
{ {
@@ -490,7 +729,11 @@ fn usz String.utf8_codepoints(s)
return len; return len;
} }
macro String.to_integer(string, $Type)
<*
@require (base <= 10 && base > 1) || base == 16 : "Unsupported base"
*>
macro String.to_integer(string, $Type, int base = 10)
{ {
usz len = string.len; usz len = string.len;
usz index = 0; usz index = 0;
@@ -510,8 +753,8 @@ macro String.to_integer(string, $Type)
break; break;
} }
if (len == index) return NumberConversion.MALFORMED_INTEGER?; if (len == index) return NumberConversion.MALFORMED_INTEGER?;
$Type base = 10; $Type base_used = ($Type)base;
if (string[index] == '0') if (string[index] == '0' && base == 10)
{ {
index++; index++;
if (index == len) return ($Type)0; if (index == len) return ($Type)0;
@@ -519,15 +762,15 @@ macro String.to_integer(string, $Type)
{ {
case 'x': case 'x':
case 'X': case 'X':
base = 16; base_used = 16;
index++; index++;
case 'b': case 'b':
case 'B': case 'B':
base = 2; base_used = 2;
index++; index++;
case 'o': case 'o':
case 'O': case 'O':
base = 8; base_used = 8;
index++; index++;
default: default:
break; break;
@@ -539,21 +782,21 @@ macro String.to_integer(string, $Type)
{ {
char c = {| char c = {|
char ch = string[index++]; char ch = string[index++];
if (base != 16 || ch < 'A') return (char)(ch - '0'); if (base_used != 16 || ch < 'A') return (char)(ch - '0');
if (ch <= 'F') return (char)(ch - 'A'); if (ch <= 'F') return (char)(ch - 'A' + 10);
if (ch < 'a') return NumberConversion.MALFORMED_INTEGER?; if (ch < 'a') return NumberConversion.MALFORMED_INTEGER?;
if (ch > 'f') return NumberConversion.MALFORMED_INTEGER?; if (ch > 'f') return NumberConversion.MALFORMED_INTEGER?;
return (char)(ch - 'a'); return (char)(ch - 'a' + 10);
|}!; |}!;
if (c >= base) return NumberConversion.MALFORMED_INTEGER?; if (c >= base_used) return NumberConversion.MALFORMED_INTEGER?;
value = {| value = {|
if (is_negative) if (is_negative)
{ {
$Type new_value = value * base - c; $Type new_value = value * base_used - c;
if (new_value > value) return NumberConversion.INTEGER_OVERFLOW?; if (new_value > value) return NumberConversion.INTEGER_OVERFLOW?;
return new_value; return new_value;
} }
$Type new_value = value * base + c; $Type new_value = value * base_used + c;
if (new_value < value) return NumberConversion.INTEGER_OVERFLOW?; if (new_value < value) return NumberConversion.INTEGER_OVERFLOW?;
return new_value; return new_value;
|}!; |}!;
@@ -561,17 +804,74 @@ macro String.to_integer(string, $Type)
return value; return value;
} }
fn int128! String.to_int128(s) => s.to_integer(int128); fn int128! String.to_int128(s, int base = 10) => s.to_integer(int128, base);
fn long! String.to_long(s) => s.to_integer(long); fn long! String.to_long(s, int base = 10) => s.to_integer(long, base);
fn int! String.to_int(s) => s.to_integer(int); fn int! String.to_int(s, int base = 10) => s.to_integer(int, base);
fn short! String.to_short(s) => s.to_integer(short); fn short! String.to_short(s, int base = 10) => s.to_integer(short, base);
fn ichar! String.to_ichar(s) => s.to_integer(ichar); fn ichar! String.to_ichar(s, int base = 10) => s.to_integer(ichar, base);
fn uint128! String.to_uint128(s) => s.to_integer(uint128); fn uint128! String.to_uint128(s, int base = 10) => s.to_integer(uint128, base);
fn ulong! String.to_ulong(s) => s.to_integer(ulong); fn ulong! String.to_ulong(s, int base = 10) => s.to_integer(ulong, base);
fn uint! String.to_uint(s) => s.to_integer(uint); fn uint! String.to_uint(s, int base = 10) => s.to_integer(uint, base);
fn ushort! String.to_ushort(s) => s.to_integer(ushort); fn ushort! String.to_ushort(s, int base = 10) => s.to_integer(ushort, base);
fn char! String.to_uchar(s) => s.to_integer(char); fn char! String.to_uchar(s, int base = 10) => s.to_integer(char, base);
fn double! String.to_double(s) => s.to_real(double); fn double! String.to_double(s) => s.to_real(double);
fn float! String.to_float(s) => s.to_real(float); fn float! String.to_float(s) => s.to_real(float);
fn Splitter String.splitter(self, String split)
{
return { .string = self, .split = split };
}
fn Splitter String.tokenize(self, String split)
{
return { .string = self, .split = split, .tokenize = true };
}
struct Splitter
{
String string;
String split;
usz current;
bool tokenize;
int last_index;
}
fn void Splitter.reset(&self)
{
self.current = 0;
}
fn String! Splitter.next(&self)
{
while (true)
{
usz len = self.string.len;
usz current = self.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
String remaining = self.string[current..];
usz! next = remaining.index_of(self.split);
if (try next)
{
self.current = current + next + self.split.len;
if (!next && self.tokenize) continue;
return remaining[:next];
}
self.current = len;
return remaining;
}
}
macro String new_struct_to_str(x, Allocator allocator = allocator::heap())
{
DString s;
@stack_mem(512; Allocator mem)
{
s.new_init(allocator: mem);
io::fprint(&s, x)!!;
return s.copy_str(allocator);
};
}
macro String temp_struct_to_str(x) => new_struct_to_str(x, allocator::temp());

View File

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

View File

@@ -31,16 +31,16 @@ const MASK = KMAX - 1;
const B1B_DIG = 2; const B1B_DIG = 2;
const uint[2] B1B_MAX = { 9007199, 254740991 }; const uint[2] B1B_MAX = { 9007199, 254740991 };
/** <*
* @require chars.len > 0 @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; uint[KMAX] x;
const uint[2] TH = B1B_MAX; const uint[2] TH = B1B_MAX;
int emax = - $emin - $bits + 3; int emax = - $emin - $bits + 3;
const int[*] P10S = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; const int[?] P10S = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
usz index; usz index;
bool got_digit = chars[0] == '0'; bool got_digit = chars[0] == '0';
bool got_rad; bool got_rad;
@@ -266,7 +266,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
y *= sign; y *= sign;
bool denormal; bool denormal;
/* Limit precision for denormal results */ // Limit precision for denormal results
uint bits = $bits; uint bits = $bits;
if (bits > math::DOUBLE_MANT_DIG + e2 - $emin) if (bits > math::DOUBLE_MANT_DIG + e2 - $emin)
{ {

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

@@ -0,0 +1,223 @@
<*
Unit test module
This module provides a toolset of macros for running unit test checks
Example:
```c3
module sample::m;
import std::io;
fault MathError
{
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.new_init(allocator: 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, anyfault 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

@@ -8,18 +8,24 @@ fault ConversionResult
VALUE_OUT_OF_RANGE, VALUE_OUT_OF_RANGE,
VALUE_OUT_OF_UNSIGNED_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"
macro any_to_int(any* v, $Type) @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; typeid any_type = v.type;
TypeKind kind = any_type.kindof; 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; bool is_mixed_signed = $Type.kindof != any_type.kindof;
$Type max = $Type.max; $Type max = $Type.max;
$Type min = $Type.min; $Type min = $Type.min;
@@ -108,10 +114,10 @@ fn bool TypeKind.is_int(kind) @inline
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT; return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
} }
macro bool is_subarray_convertable($Type) macro bool is_slice_convertable($Type)
{ {
$switch ($Type.kindof) $switch ($Type.kindof)
$case SUBARRAY: $case SLICE:
return true; return true;
$case POINTER: $case POINTER:
return $Type.inner.kindof == TypeKind.ARRAY; return $Type.inner.kindof == TypeKind.ARRAY;
@@ -120,10 +126,51 @@ macro bool is_subarray_convertable($Type)
$endswitch $endswitch
} }
macro bool is_bool($Type) => $Type.kindof == TypeKind.BOOL; macro bool is_bool($Type) @const => $Type.kindof == TypeKind.BOOL;
macro bool is_int($Type) => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT; macro bool is_int($Type) @const => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT;
macro bool is_intlike($Type) <*
@require is_numerical($Type) "Expected a numerical type"
*>
macro bool is_signed($Type) @const
{
$switch (inner_kind($Type))
$case SIGNED_INT:
$case FLOAT:
return true;
$case VECTOR:
return is_signed($typefrom($Type.inner));
$default:
return false;
$endswitch
}
<*
@require is_numerical($Type) "Expected a numerical type"
*>
macro bool is_unsigned($Type) @const
{
$switch (inner_kind($Type))
$case UNSIGNED_INT:
return true;
$case VECTOR:
return is_unsigned($typefrom($Type.inner));
$default:
return false;
$endswitch
}
macro bool is_indexable($Type) @const
{
return $defined($Type{}[0]);
}
macro bool is_ref_indexable($Type) @const
{
return $defined(&$Type{}[0]);
}
macro bool is_intlike($Type) @const
{ {
$switch ($Type.kindof) $switch ($Type.kindof)
$case SIGNED_INT: $case SIGNED_INT:
@@ -136,7 +183,7 @@ macro bool is_intlike($Type)
$endswitch $endswitch
} }
macro bool is_underlying_int($Type) macro bool is_underlying_int($Type) @const
{ {
$switch ($Type.kindof) $switch ($Type.kindof)
$case SIGNED_INT: $case SIGNED_INT:
@@ -149,9 +196,9 @@ macro bool is_underlying_int($Type)
$endswitch $endswitch
} }
macro bool is_float($Type) => $Type.kindof == TypeKind.FLOAT; macro bool is_float($Type) @const => $Type.kindof == TypeKind.FLOAT;
macro bool is_floatlike($Type) macro bool is_floatlike($Type) @const
{ {
$switch ($Type.kindof) $switch ($Type.kindof)
$case FLOAT: $case FLOAT:
@@ -163,40 +210,45 @@ macro bool is_floatlike($Type)
$endswitch $endswitch
} }
macro bool is_vector($Type) macro bool is_vector($Type) @const
{ {
return $Type.kindof == TypeKind.VECTOR; return $Type.kindof == TypeKind.VECTOR;
} }
macro TypeKind inner_kind($Type) macro typeid inner_type($Type) @const
{ {
$if $Type.kindof == TypeKind.DISTINCT: $if $Type.kindof == TypeKind.DISTINCT:
return inner_kind($typefrom($Type.inner)); return inner_type($typefrom($Type.inner));
$else $else
return $Type.kindof; return $Type.typeid;
$endif $endif
} }
macro bool is_same($TypeA, $TypeB) macro TypeKind inner_kind($Type) @const
{
return inner_type($Type).kindof;
}
macro bool is_same($TypeA, $TypeB) @const
{ {
return $TypeA.typeid == $TypeB.typeid; return $TypeA.typeid == $TypeB.typeid;
} }
macro bool @has_same(#a, #b, ...) macro bool @has_same(#a, #b, ...) @const
{ {
var $type_a = @typeid(#a); var $type_a = @typeid(#a);
$if $type_a != @typeid(#b): $if $type_a != @typeid(#b):
return false; return false;
$endif $endif
$for (var $i = 0; $i < $vacount; $i++) $for (var $i = 0; $i < $vacount; $i++)
$if @typeid($vaexpr($i)) != $type_a: $if @typeid($vaexpr[$i]) != $type_a:
return false; return false;
$endif $endif
$endfor $endfor
return true; return true;
} }
macro bool may_load_atomic($Type) macro bool may_load_atomic($Type) @const
{ {
$switch ($Type.kindof) $switch ($Type.kindof)
$case SIGNED_INT: $case SIGNED_INT:
@@ -211,12 +263,12 @@ macro bool may_load_atomic($Type)
$endswitch $endswitch
} }
macro lower_to_atomic_compatible_type($Type) macro lower_to_atomic_compatible_type($Type) @const
{ {
$switch ($Type.kindof) $switch ($Type.kindof)
$case SIGNED_INT: $case SIGNED_INT:
$case UNSIGNED_INT: $case UNSIGNED_INT:
return $Type.typeid; return $Type.typeid;
$case DISTINCT: $case DISTINCT:
return lower_to_atomic_compatible_type($Type.inner); return lower_to_atomic_compatible_type($Type.inner);
$case FLOAT: $case FLOAT:
@@ -237,10 +289,10 @@ macro lower_to_atomic_compatible_type($Type)
$endswitch $endswitch
} }
macro bool is_promotable_to_floatlike($Type) => types::is_floatlike($Type) || types::is_int($Type); macro bool is_promotable_to_floatlike($Type) @const => types::is_floatlike($Type) || types::is_int($Type);
macro bool is_promotable_to_float($Type) => types::is_float($Type) || types::is_int($Type); macro bool is_promotable_to_float($Type) @const => types::is_float($Type) || types::is_int($Type);
macro bool is_same_vector_type($Type1, $Type2) macro bool is_same_vector_type($Type1, $Type2) @const
{ {
$if $Type1.kindof != TypeKind.VECTOR: $if $Type1.kindof != TypeKind.VECTOR:
return $Type2.kindof != TypeKind.VECTOR; return $Type2.kindof != TypeKind.VECTOR;
@@ -249,7 +301,7 @@ macro bool is_same_vector_type($Type1, $Type2)
$endif $endif
} }
macro bool is_equatable_type($Type) macro bool is_equatable_type($Type) @const
{ {
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals): $if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):
return true; return true;
@@ -258,20 +310,34 @@ macro bool is_equatable_type($Type)
$endif $endif
} }
/** <*
* Checks if a type implements the copy protocol. Checks if a type implements the copy protocol.
**/ *>
macro bool implements_copy($Type) macro bool implements_copy($Type) @const
{ {
return $defined($Type.copy) && $defined($Type.free); return $defined($Type.copy) && $defined($Type.free);
} }
macro bool is_equatable_value(value) macro bool is_equatable_value(value) @deprecated
{ {
return is_equatable_type($typeof(value)); return is_equatable_type($typeof(value));
} }
macro bool is_comparable_value(value) macro bool @equatable_value(#value) @const
{
return is_equatable_type($typeof(#value));
}
macro bool @comparable_value(#value) @const
{
$if $defined(#value.less) || $defined(#value.compare_to):
return true;
$else
return $typeof(#value).is_ordered;
$endif
}
macro bool is_comparable_value(value) @deprecated
{ {
$if $defined(value.less) || $defined(value.compare_to): $if $defined(value.less) || $defined(value.compare_to):
return true; return true;
@@ -298,10 +364,11 @@ enum TypeKind : char
FUNC, FUNC,
OPTIONAL, OPTIONAL,
ARRAY, ARRAY,
SUBARRAY, SLICE,
VECTOR, VECTOR,
DISTINCT, DISTINCT,
POINTER, POINTER,
INTERFACE,
} }
struct TypeEnum struct TypeEnum

View File

@@ -1,21 +1,22 @@
module std::core::values; module std::core::values;
macro typeid @typeid(#value) @builtin => $typeof(#value).typeid; macro typeid @typeid(#value) @const @builtin => $typeof(#value).typeid;
macro TypeKind @typekind(#value) @builtin => $typeof(#value).kindof; macro TypeKind @typekind(#value) @const @builtin => $typeof(#value).kindof;
macro bool @typeis(#value, $Type) @builtin => $typeof(#value).typeid == $Type.typeid; macro bool @typeis(#value, $Type) @const @builtin => $typeof(#value).typeid == $Type.typeid;
/** <*
* Return true if two values have the same type before any conversions. Return true if two values have the same type before any conversions.
**/ *>
macro bool @is_same_type(#value1, #value2) => $typeof(#value1).typeid == $typeof(#value2).typeid; macro bool @is_same_type(#value1, #value2) @const => $typeof(#value1).typeid == $typeof(#value2).typeid;
macro bool @is_bool(#value) => types::is_bool($typeof(#value)); macro bool @is_bool(#value) @const => types::is_bool($typeof(#value));
macro bool @is_int(#value) => types::is_int($typeof(#value)); macro bool @is_int(#value) @const => types::is_int($typeof(#value));
macro bool @is_floatlike(#value) => types::is_floatlike($typeof(#value)); macro bool @is_floatlike(#value) @const => types::is_floatlike($typeof(#value));
macro bool @is_float(#value) => types::is_float($typeof(#value)); macro bool @is_float(#value) @const => types::is_float($typeof(#value));
macro bool @is_promotable_to_floatlike(#value) => types::is_promotable_to_floatlike($typeof(#value)); macro bool @is_promotable_to_floatlike(#value) @const => types::is_promotable_to_floatlike($typeof(#value));
macro bool @is_promotable_to_float(#value) => types::is_promotable_to_float($typeof(#value)); macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_float($typeof(#value));
macro bool @is_vector(#value) => types::is_vector($typeof(#value)); macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) => types::is_same_vector_type($typeof(#value1), $typeof(#value2)); macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) => $assignable(#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) macro promote_int(x)
{ {
@@ -26,5 +27,40 @@ macro promote_int(x)
$endif $endif
} }
macro TypeKind @inner_kind(#value) => types::inner_kind($typeof(#value)); <*
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
$case @is_vector(y) &&& $typeof(y).inner == float.typeid:
return (float)x;
$case $typeof(y).typeid == float.typeid:
return (float)x;
$default:
return (double)x;
$endswitch
$else
return x;
$endif
}
macro TypeKind @inner_kind(#value) @const => types::inner_kind($typeof(#value));

View File

@@ -1,2 +1,12 @@
module std::crypto; module std::crypto;
fn bool safe_compare(void* data1, void* data2, usz len)
{
char match = 0;
for (usz i = 0; i < len; i++)
{
match = match | (mem::@volatile_load(((char*)data1)[i]) ^ mem::@volatile_load(((char*)data2)[i]));
}
return match == 0;
}

12
lib/std/crypto/dh.c3 Normal file
View File

@@ -0,0 +1,12 @@
module std::crypto::dh;
import std::math::bigint;
fn BigInt generate_secret(BigInt p, BigInt x, BigInt y)
{
return y.mod_pow(x, p);
}
fn BigInt public_key(BigInt p, BigInt g, BigInt x)
{
return g.mod_pow(x, p);
}

View File

@@ -9,12 +9,12 @@ struct Rc4
char[256] state; char[256] state;
} }
/** <*
* Initialize the RC4 state. Initialize the RC4 state.
*
* @param [in] key "The key to use" @param [in] key "The key to use"
* @require key.len > 0 "The key must be at least 1 byte long" @require key.len > 0 "The key must be at least 1 byte long"
**/ *>
fn void Rc4.init(&self, char[] key) fn void Rc4.init(&self, char[] key)
{ {
// Init the state matrix // Init the state matrix
@@ -28,13 +28,25 @@ fn void Rc4.init(&self, char[] key)
self.j = 0; self.j = 0;
} }
/** <*
* Encrypt or decrypt a sequence of bytes. Run a single pass of en/decryption using a particular key.
* @param [in] key
* @param [in] in "The input" @param [inout] data
* @param [out] out "The output" *>
* @require in.len <= out.len "Output would overflow" fn void crypt(char[] key, char[] data)
**/ {
Rc4 rc4;
rc4.init(key);
rc4.crypt(data, 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"
*>
fn void Rc4.crypt(&self, char[] in, char[] out) fn void Rc4.crypt(&self, char[] in, char[] out)
{ {
uint i = self.i; uint i = self.i;
@@ -52,11 +64,11 @@ fn void Rc4.crypt(&self, char[] in, char[] out)
self.j = j; self.j = j;
} }
/** <*
* Clear the rc4 state. Clear the rc4 state.
*
* @param [&out] self "The RC4 State" @param [&out] self "The RC4 State"
**/ *>
fn void Rc4.destroy(&self) fn void Rc4.destroy(&self)
{ {
*self = {}; *self = {};

406
lib/std/encoding/base32.c3 Normal file
View File

@@ -0,0 +1,406 @@
module std::encoding::base32;
// This module implements base32 encoding according to RFC 4648
// (https://www.rfc-editor.org/rfc/rfc4648)
struct Base32Alphabet
{
char[32] encoding;
char[256] reverse;
}
const char NO_PAD = 0;
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"
@return "The encoded string."
*>
fn String! encode(char[] src, Allocator allocator, 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);
}
<*
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"
@return "The decoded data."
*>
fn char[]! decode(char[] src, Allocator allocator, 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);
<*
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"
@return "Length in bytes of the decoded data."
*>
fn usz decode_len(usz n, char padding)
{
if (padding) return (n / 8) * 5;
// no padding
usz trailing = n % 8;
return n / 8 * 5 + (trailing * 5 ) / 8;
}
<*
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"
@return "Length in bytes of the encoded data."
*>
fn usz encode_len(usz n, char padding)
{
// A character is encoded into 8 x 5-bit blocks.
if (padding) return (n + 4) / 5 * 8;
// no padding
usz trailing = n % 5;
return n / 5 * 8 + (trailing * 8 + 4) / 5;
}
<*
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"
@return "The resulting dst buffer"
@return! DecodingFailure
*>
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;
usz dn = decode_len(src.len, padding);
usz n;
char[8] buf;
while (src.len > 0 && dst.len > 0)
{
usz i @noinit;
// load 8 bytes into buffer
for (i = 0; i < 8; i++)
{
if (src.len == 0)
{
if (padding > 0) return DecodingFailure.INVALID_PADDING?;
break;
}
if (src[0] == padding) break;
buf[i] = alphabet.reverse[src[0]];
if (buf[i] == INVALID) return DecodingFailure.INVALID_CHARACTER?;
src = src[1..];
}
// extract 5-bytes from the buffer which contains 8 x 5 bit chunks
switch (i)
{
case 8:
// |66677777| dst[4]
// | 77777| buf[7]
// |666 | buf[6] << 5
dst[4] = buf[7] | buf[6] << 5;
n++;
nextcase 7;
case 7:
// |45555566| dst[3]
// | 66| buf[6] >> 3
// | 55555 | buf[5] << 2
// |4 | buf[4] << 7
dst[3] = buf[6] >> 3 | buf[5] << 2 | buf[4] << 7;
n++;
nextcase 5;
case 5:
// |33334444| dst[2]
// | 4444| buf[4] >> 1
// |3333 | buf[3] << 4
dst[2] = buf[4] >> 1 | buf[3] << 4;
n++;
nextcase 4;
case 4:
// |11222223| dst[1]
// | 3| buf[3] >> 4
// | 22222 | buf[2] << 1
// |11 | buf[1] << 6
dst[1] = buf[3] >> 4 | buf[2] << 1 | buf[1] << 6;
n++;
nextcase 2;
case 2:
// |00000111| dst[0]
// | 111| buf[1] >> 2
// |00000 | buf[0] << 3
dst[0] = buf[1] >> 2 | buf[0] << 3;
n++;
default:
return DecodingFailure.INVALID_CHARACTER?;
}
if (dst.len < 5) break;
dst = dst[5..];
}
return dst_ptr[:n];
}
<*
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"
@return "The encoded size."
*>
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return (String)dst[:0];
char* dst_ptr = dst;
usz n = (src.len / 5) * 5;
usz dn = encode_len(src.len, padding);
uint msb, lsb;
for (usz i = 0; i < n; i += 5)
{
// to fit 40 bits we need two 32-bit uints
msb = (uint)src[i] << 24 | (uint)src[i+1] << 16
| (uint)src[i+2] << 8 | (uint)src[i+3];
lsb = msb << 8 | (uint)src[i+4];
// now slice them into 5-bit chunks and translate to the
// alphabet.
dst[0] = alphabet.encoding[(msb >> 27) & MASK];
dst[1] = alphabet.encoding[(msb >> 22) & MASK];
dst[2] = alphabet.encoding[(msb >> 17) & MASK];
dst[3] = alphabet.encoding[(msb >> 12) & MASK];
dst[4] = alphabet.encoding[(msb >> 7) & MASK];
dst[5] = alphabet.encoding[(msb >> 2) & MASK];
dst[6] = alphabet.encoding[(lsb >> 5) & MASK];
dst[7] = alphabet.encoding[lsb & MASK];
dst = dst[8..];
}
usz trailing = src.len - n;
if (trailing == 0) return (String)dst_ptr[:dn];
msb = 0;
switch (trailing)
{
case 4:
msb |= (uint)src[n+3];
lsb = msb << 8;
dst[6] = alphabet.encoding[(lsb >> 5) & MASK];
dst[5] = alphabet.encoding[(msb >> 2) & MASK];
nextcase 3;
case 3:
msb |= (uint)src[n+2] << 8;
dst[4] = alphabet.encoding[(msb >> 7) & MASK];
nextcase 2;
case 2:
msb |= (uint)src[n+1] << 16;
dst[3] = alphabet.encoding[(msb >> 12) & MASK];
dst[2] = alphabet.encoding[(msb >> 17) & MASK];
nextcase 1;
case 1:
msb |= (uint)src[n] << 24;
dst[1] = alphabet.encoding[(msb >> 22) & MASK];
dst[0] = alphabet.encoding[(msb >> 27) & MASK];
}
// add the padding
if (padding > 0)
{
for (usz i = (trailing * 8 / 5) + 1; i < 8; i++)
{
dst[i] = padding;
}
}
return (String)dst_ptr[:dn];
}
const uint MASK @private = 0b11111;
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];
// Standard base32 Alphabet
const Alphabet STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
// Extended Hex Alphabet
const Alphabet HEX_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
const Base32Alphabet STANDARD = {
.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
.reverse = x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffff1a1b1c1d1e1fffffffffffffffff
ff000102030405060708090a0b0c0d0e0f10111213141516171819ffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
};
const Base32Alphabet HEX = {
.encoding = "0123456789ABCDEFGHIJKLMNOPQRSTUV",
.reverse = x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff00010203040506070809ffffffffffff
ff0a0b0c0d0e0f101112131415161718191a1b1c1d1e1fffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
};

View File

@@ -5,14 +5,260 @@ import std::core::bitorder;
// Specifically this section: // Specifically this section:
// https://www.rfc-editor.org/rfc/rfc4648#section-4 // https://www.rfc-editor.org/rfc/rfc4648#section-4
const char NO_PAD = 0;
const char DEFAULT_PAD = '=';
struct Base64Alphabet
{
char[64] encoding;
char[256] reverse;
}
const Base64Alphabet STANDARD = {
.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
.reverse =
x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffff3effffff3f3435363738393a3b3c3dffffffffffff
ff000102030405060708090a0b0c0d0e0f10111213141516171819ffffffffff
ff1a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233ffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
};
const Base64Alphabet URL = {
.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
.reverse =
x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffff3effff3435363738393a3b3c3dffffffffffff
ff000102030405060708090a0b0c0d0e0f10111213141516171819ffffffff3f
ff1a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233ffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
};
const STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
fn String encode(char[] src, Allocator allocator, 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)
{
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);
<*
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"
@return "The size of the input once encoded."
*>
fn usz encode_len(usz n, char padding)
{
if (padding) return (n + 2) / 3 * 4;
usz trailing = n % 3;
return n / 3 * 4 + (trailing * 4 + 2) / 3;
}
<*
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"
@return "The size of the input once decoded."
@return! DecodingFailure.INVALID_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?;
// source size is multiple of 4
return dn;
}
if (trailing == 1) return DecodingFailure.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"
@return "The encoded size."
@return! Base64Error.DESTINATION_TOO_SMALL
*>
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return (String)dst[:0];
usz dn = encode_len(src.len, padding);
char* dst_ptr = dst;
assert(dst.len >= dn);
usz trailing = src.len % 3;
char[] src3 = src[:^trailing];
while (src3.len > 0)
{
uint group = (uint)src3[0] << 16 | (uint)src3[1] << 8 | (uint)src3[2];
dst[0] = alphabet.encoding[group >> 18 & MASK];
dst[1] = alphabet.encoding[group >> 12 & MASK];
dst[2] = alphabet.encoding[group >> 6 & MASK];
dst[3] = alphabet.encoding[group & MASK];
dst = dst[4..];
src3 = src3[3..];
}
// Encode the remaining bytes according to:
// https://www.rfc-editor.org/rfc/rfc4648#section-3.5
switch (trailing)
{
case 1:
uint group = (uint)src[^1] << 16;
dst[0] = alphabet.encoding[group >> 18 & MASK];
dst[1] = alphabet.encoding[group >> 12 & MASK];
if (padding > 0)
{
dst[2] = padding;
dst[3] = padding;
}
case 2:
uint group = (uint)src[^2] << 16 | (uint)src[^1] << 8;
dst[0] = alphabet.encoding[group >> 18 & MASK];
dst[1] = alphabet.encoding[group >> 12 & MASK];
dst[2] = alphabet.encoding[group >> 6 & MASK];
if (padding > 0)
{
dst[3] = padding;
}
case 0:
break;
default:
unreachable();
}
return (String)dst_ptr[:dn];
}
<*
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"
@return "The decoded data."
@return! DecodingFailure
*>
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)!;
assert(dst.len >= dn);
usz trailing = src.len % 4;
char* dst_ptr = dst;
char[] src4 = src;
switch
{
case !padding:
src4 = src[:^trailing];
default:
// If there is padding, keep the last 4 bytes for later.
// NB. src.len >= 4 as decode_len passed
trailing = 4;
if (src[^1] == padding) src4 = src[:^4];
}
while (src4.len > 0)
{
char c0 = alphabet.reverse[src4[0]];
char c1 = alphabet.reverse[src4[1]];
char c2 = alphabet.reverse[src4[2]];
char c3 = alphabet.reverse[src4[3]];
switch (0xFF)
{
case c0:
case c1:
case c2:
case c3:
return DecodingFailure.INVALID_CHARACTER?;
}
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6 | (uint)c3;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
dst[2] = (char)group;
dst = dst[3..];
src4 = src4[4..];
}
if (trailing == 0) return dst_ptr[:dn];
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 (!padding)
{
switch (src.len)
{
case 2:
uint group = (uint)c0 << 18 | (uint)c1 << 12;
dst[0] = (char)(group >> 16);
case 3:
char c2 = alphabet.reverse[src[2]];
if (c2 == 0xFF) return DecodingFailure.INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
}
}
else
{
// Valid paddings are:
// 2: xx==
// 1: xxx=
switch (padding)
{
case src[2]:
if (src[3] != padding) return DecodingFailure.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?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
dn -= 1;
}
}
return dst_ptr[:dn];
}
const MASK @private = 0b111111; const MASK @private = 0b111111;
struct Base64Encoder struct Base64Encoder @deprecated
{ {
int padding; char padding;
String alphabet; String alphabet;
} }
@@ -25,243 +271,109 @@ fault Base64Error
INVALID_CHARACTER, INVALID_CHARACTER,
} }
/** <*
* @param alphabet "The alphabet used for encoding." @param alphabet "The alphabet used for encoding."
* @param padding "Set to a negative value to disable padding." @param padding "Set to a negative value to disable padding."
* @require alphabet.len == 64 @require alphabet.len == 64
* @require padding < 256 @require padding < 256
* @return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET @return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
**/ *>
fn void! Base64Encoder.init(&self, String alphabet, int padding = '=') fn Base64Encoder*! Base64Encoder.init(&self, String alphabet, int padding = '=')
{ {
check_alphabet(alphabet, padding)!; check_alphabet(alphabet, padding)!;
*self = { .padding = padding, .alphabet = alphabet }; *self = { .padding = padding < 0 ? 0 : (char)padding, .alphabet = alphabet };
return self;
} }
/** <*
* Calculate the size of the encoded data. 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." @return "The size of the input once encoded."
**/ *>
fn usz Base64Encoder.encode_len(&self, usz n) fn usz Base64Encoder.encode_len(&self, usz n)
{ {
if (self.padding >= 0) return (n + 2) / 3 * 4; return encode_len(n, self.padding);
usz trailing = n % 3;
return n / 3 * 4 + (trailing * 4 + 2) / 3;
} }
/** <*
* Encode the content of src into dst, which must be properly sized. Encode the content of src into dst, which must be properly sized.
* @param src "The input to be encoded." @param src "The input to be encoded."
* @param dst "The encoded input." @param dst "The encoded input."
* @return "The encoded size." @return "The encoded size."
* @return! Base64Error.DESTINATION_TOO_SMALL @return! Base64Error.DESTINATION_TOO_SMALL
**/ *>
fn usz! Base64Encoder.encode(&self, char[] src, char[] dst) fn usz! Base64Encoder.encode(&self, char[] src, char[] dst)
{ {
if (src.len == 0) return 0; if (src.len == 0) return 0;
usz dn = self.encode_len(src.len); usz dn = self.encode_len(src.len);
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?; if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
usz trailing = src.len % 3; Base64Alphabet a = { .encoding = self.alphabet[:64] };
char[] src3 = src[:^trailing]; return encode_buffer(src, dst, self.padding, &a).len;
while (src3.len > 0)
{
uint group = (uint)src3[0] << 16 | (uint)src3[1] << 8 | (uint)src3[2];
dst[0] = self.alphabet[group >> 18 & MASK];
dst[1] = self.alphabet[group >> 12 & MASK];
dst[2] = self.alphabet[group >> 6 & MASK];
dst[3] = self.alphabet[group & MASK];
dst = dst[4..];
src3 = src3[3..];
}
// Encode the remaining bytes according to:
// https://www.rfc-editor.org/rfc/rfc4648#section-3.5
switch (trailing)
{
case 1:
uint group = (uint)src[^1] << 16;
dst[0] = self.alphabet[group >> 18 & MASK];
dst[1] = self.alphabet[group >> 12 & MASK];
if (self.padding >= 0)
{
char pad = (char)self.padding;
dst[2] = pad;
dst[3] = pad;
}
case 2:
uint group = (uint)src[^2] << 16 | (uint)src[^1] << 8;
dst[0] = self.alphabet[group >> 18 & MASK];
dst[1] = self.alphabet[group >> 12 & MASK];
dst[2] = self.alphabet[group >> 6 & MASK];
if (self.padding >= 0)
{
char pad = (char)self.padding;
dst[3] = pad;
}
}
return dn;
} }
struct Base64Decoder struct Base64Decoder @deprecated
{ {
int padding; char padding;
String alphabet; Base64Alphabet encoding;
char[256] reverse; bool init_done;
char invalid;
} }
/** import std;
* @param alphabet "The alphabet used for encoding." <*
* @param padding "Set to a negative value to disable padding." @param alphabet "The alphabet used for encoding."
* @require alphabet.len == 64 @param padding "Set to a negative value to disable padding."
* @require padding < 256 @require alphabet.len == 64
* @return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET @require padding < 256
**/ @return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
*>
fn void! Base64Decoder.init(&self, String alphabet, int padding = '=') fn void! Base64Decoder.init(&self, String alphabet, int padding = '=')
{ {
self.init_done = true;
check_alphabet(alphabet, padding)!; check_alphabet(alphabet, padding)!;
*self = { .padding = padding, .alphabet = alphabet }; *self = { .padding = padding < 0 ? 0 : (char)padding, .encoding.encoding = alphabet[:64] };
self.encoding.reverse[..] = 0xFF;
bool[256] checked;
foreach (i, c : alphabet) foreach (i, c : alphabet)
{ {
checked[c] = true; self.encoding.reverse[c] = (char)i;
self.reverse[c] = (char)i;
}
if (padding < 0)
{
self.invalid = 255;
return;
}
// Find a character for invalid neither in the alphabet nor equal to the padding.
char pad = (char)padding;
foreach (i, ok : checked)
{
if (!ok && (char)i != pad)
{
self.invalid = (char)i;
break;
}
} }
} }
/** <*
* Calculate the size of the decoded data. 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." @return "The size of the input once decoded."
* @return! Base64Error.INVALID_PADDING @return! Base64Error.INVALID_PADDING
**/ *>
fn usz! Base64Decoder.decode_len(&self, usz n) fn usz! Base64Decoder.decode_len(&self, usz n)
{ {
usz dn = n / 4 * 3; return decode_len(n, self.padding) ?? Base64Error.INVALID_PADDING?;
usz trailing = n % 4;
if (self.padding >= 0)
{
if (trailing != 0) return Base64Error.INVALID_PADDING?;
// source size is multiple of 4
}
else
{
if (trailing == 1) return Base64Error.INVALID_PADDING?;
dn += trailing * 3 / 4;
}
return dn;
} }
/** <*
* Decode the content of src into dst, which must be properly sized. Decode the content of src into dst, which must be properly sized.
* @param src "The input to be decoded." @param src "The input to be decoded."
* @param dst "The decoded input." @param dst "The decoded input."
* @return "The decoded size." @return "The decoded size."
* @return! Base64Error.DESTINATION_TOO_SMALL, Base64Error.INVALID_PADDING, Base64Error.INVALID_CHARACTER @return! Base64Error.DESTINATION_TOO_SMALL, Base64Error.INVALID_PADDING, Base64Error.INVALID_CHARACTER
**/ *>
fn usz! Base64Decoder.decode(&self, char[] src, char[] dst) fn usz! Base64Decoder.decode(&self, char[] src, char[] dst)
{ {
if (src.len == 0) return 0; if (src.len == 0) return 0;
usz dn = self.decode_len(src.len)!; usz dn = self.decode_len(src.len)!;
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?; if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
char[]! decoded = decode_buffer(src, dst, self.padding, &self.encoding);
usz trailing = src.len % 4; if (catch err = decoded)
char[] src4 = src;
switch
{ {
case self.padding < 0: case DecodingFailure.INVALID_PADDING:
src4 = src[:^trailing]; return Base64Error.INVALID_PADDING?;
case DecodingFailure.INVALID_CHARACTER:
return Base64Error.INVALID_CHARACTER?;
default: default:
// If there is padding, keep the last 4 bytes for later. return err?;
// NB. src.len >= 4 as decode_len passed
trailing = 4;
char pad = (char)self.padding;
if (src[^1] == pad) src4 = src[:^4];
} }
while (src4.len > 0) return decoded.len;
{
char c0 = self.reverse[src4[0]];
char c1 = self.reverse[src4[1]];
char c2 = self.reverse[src4[2]];
char c3 = self.reverse[src4[3]];
switch (self.invalid)
{
case c0:
case c1:
case c2:
case c3:
return Base64Error.INVALID_CHARACTER?;
}
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6 | (uint)c3;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
dst[2] = (char)group;
dst = dst[3..];
src4 = src4[4..];
}
if (trailing == 0) return dn;
src = src[^trailing..];
char c0 = self.reverse[src[0]];
char c1 = self.reverse[src[1]];
if (c0 == self.invalid || c1 == self.invalid) return Base64Error.INVALID_PADDING?;
if (self.padding < 0)
{
switch (src.len)
{
case 2:
uint group = (uint)c0 << 18 | (uint)c1 << 12;
dst[0] = (char)(group >> 16);
case 3:
char c2 = self.reverse[src[2]];
if (c2 == self.invalid) return Base64Error.INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
}
}
else
{
// Valid paddings are:
// 2: xx==
// 1: xxx=
char pad = (char)self.padding;
switch (pad)
{
case src[2]:
if (src[3] != pad) return Base64Error.INVALID_PADDING?;
uint group = (uint)c0 << 18 | (uint)c1 << 12;
dst[0] = (char)(group >> 16);
dn -= 2;
case src[3]:
char c2 = self.reverse[src[2]];
if (c2 == self.invalid) return Base64Error.INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
dn -= 1;
}
}
return dn;
} }
// Make sure that all bytes in the alphabet are unique and // Make sure that all bytes in the alphabet are unique and
@@ -285,4 +397,5 @@ fn void! check_alphabet(String alphabet, int padding) @local
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?; if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
checked[c] = true; checked[c] = true;
} }
} }

View File

@@ -1,64 +1,95 @@
module std::encoding::csv; module std::encoding::csv;
import std::io; import std::io;
struct CsvReader struct CsvReader
{ {
InStream* stream; InStream stream;
String separator; String separator;
} }
fn void CsvReader.init(&self, InStream* stream, String separator = ",") struct CsvRow (Printable)
{
String[] list;
String row;
Allocator allocator;
}
fn usz! CsvRow.to_format(&self, Formatter* f) @dynamic
{
return f.printf("%s", self.list);
}
fn usz CsvRow.len(&self) @operator(len)
{
return self.list.len;
}
<*
@require col < self.list.len
*>
fn String CsvRow.get_col(&self, usz col) @operator([])
{
return self.list[col];
}
fn void CsvReader.init(&self, InStream stream, String separator = ",")
{ {
self.stream = stream; self.stream = stream;
self.separator = separator; self.separator = separator;
} }
fn String[]! CsvReader.read_new_row(self, Allocator* allocator = mem::heap()) fn CsvRow! CsvReader.read_new_row(self)
{ {
return self.read_new_row_with_allocator(mem::temp()) @inline; return self.read_row(allocator::heap()) @inline;
} }
fn String[]! CsvReader.read_new_row_with_allocator(self, Allocator* allocator = mem::heap()) <*
@param [&inout] allocator
*>
fn CsvRow! CsvReader.read_row(self, Allocator allocator)
{ {
@pool(allocator) String row = io::readline(self.stream, allocator: allocator)!;
{ defer catch allocator::free(allocator, row);
return io::treadline(self.stream).split(self.separator, .allocator = allocator); String[] list = row.split(self.separator, allocator: allocator);
}; return { list, row, allocator };
} }
fn String[]! CsvReader.read_temp_row(self) fn CsvRow! CsvReader.read_temp_row(self)
{ {
return self.read_new_row_with_allocator(mem::temp()) @inline; return self.read_row(allocator::temp()) @inline;
} }
fn void! CsvReader.skip_row(self) @maydiscard <*
@require self.allocator != null `Row already freed`
*>
fn void CsvRow.free(&self)
{ {
@pool() allocator::free(self.allocator, self.list);
{ allocator::free(self.allocator, self.row);
(void)io::treadline(self.stream); self.allocator = null;
};
} }
macro CsvReader.@each_row(self, int rows = int.max; @body(String[] row)) fn void! CsvReader.skip_row(self) @maydiscard => @pool()
{ {
InputStream* stream = self.stream; (void)io::treadline(self.stream);
}
macro void! CsvReader.@each_row(self, int rows = int.max; @body(String[] row)) @maydiscard
{
InStream stream = self.stream;
String sep = self.separator; String sep = self.separator;
while (rows--) while (rows--)
{ {
@stack_mem(512; Allocator* mem) @stack_mem(512; Allocator mem)
{ {
String[] parts; String! s = io::readline(stream, mem);
@pool() if (catch err = s)
{ {
String! s = stream.treadline(); if (err == IoError.EOF) return;
if (catch err = s) return err?;
{ }
if (err == IoError.EOF) return; @body(s.split(sep, allocator: mem));
return err?;
}
parts = s.split(sep, .allocator = mem);
};
@body(parts);
}; };
} }
} }

View File

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

109
lib/std/encoding/hex.c3 Normal file
View File

@@ -0,0 +1,109 @@
module std::encoding::hex;
import std::encoding @norecurse;
// The implementation is based on https://www.rfc-editor.org/rfc/rfc4648
fn String encode_buffer(char[] code, char[] buffer)
{
return (String)buffer[:encode_bytes(code, buffer)];
}
fn char[]! decode_buffer(char[] code, char[] buffer)
{
return buffer[:decode_bytes(code, buffer)!];
}
fn String encode(char[] code, Allocator allocator)
{
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)
{
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());
<*
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 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."
@return "The encoded size."
@require dst.len >= encode_len(src.len) "Destination array is not large enough"
*>
fn usz encode_bytes(char[] src, char[] dst)
{
usz j = 0;
foreach (v : src)
{
dst[j] = HEXALPHABET[v >> 4];
dst[j + 1] = HEXALPHABET[v & 0x0f];
j = j + 2;
}
return src.len * 2;
}
<*
Calculate the size of the decoded data.
@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;
<*
Decodes src into bytes. Returns the actual number of bytes written to dst.
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
*>
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?;
dst[i] = (a << 4) | b;
i++;
}
return i;
}
const char[?] HEXALPHABET @private = "0123456789abcdef";
const char[?] HEXREVERSE @private =
x`ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
00010203040506070809ffffffffffff
ff0a0b0c0d0e0fffffffffffffffffff
ffffffffffffffffffffffffffffffff
ff0a0b0c0d0e0fffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff`;

View File

@@ -15,11 +15,31 @@ fault JsonParsingError
INVALID_NUMBER, INVALID_NUMBER,
} }
fn Object*! parse(InStream* s, Allocator* allocator = mem::heap()) fn Object*! parse_string(String s, Allocator allocator = allocator::heap())
{ {
JsonContext context = { .last_string = dstring::new_with_capacity(64, allocator), .stream = s, .allocator = allocator }; return parse(ByteReader{}.init(s), allocator);
defer context.last_string.free(); }
return parse_any(&context);
fn Object*! temp_parse_string(String s)
{
return parse(ByteReader{}.init(s), allocator::temp());
}
fn Object*! parse(InStream s, Allocator allocator = allocator::heap())
{
@stack_mem(512; Allocator mem)
{
JsonContext context = { .last_string = dstring::new_with_capacity(64, mem), .stream = s, .allocator = allocator };
@pool(allocator)
{
return parse_any(&context);
};
};
}
fn Object*! temp_parse(InStream s)
{
return parse(s, allocator::temp());
} }
// -- Implementation follows -- // -- Implementation follows --
@@ -44,8 +64,8 @@ enum JsonTokenType @local
struct JsonContext @local struct JsonContext @local
{ {
uint line; uint line;
InStream* stream; InStream stream;
Allocator* allocator; Allocator allocator;
JsonTokenType token; JsonTokenType token;
DString last_string; DString last_string;
double last_number; double last_number;
@@ -84,9 +104,9 @@ fn Object*! parse_any(JsonContext* context) @local
fn JsonTokenType! lex_number(JsonContext *context, char c) @local fn JsonTokenType! lex_number(JsonContext *context, char c) @local
{ {
@pool() @stack_mem(256; Allocator mem)
{ {
DString t = dstring::temp_with_capacity(32); DString t = dstring::new_with_capacity(32, allocator: mem);
bool negate = c == '-'; bool negate = c == '-';
if (negate) if (negate)
{ {
@@ -134,32 +154,34 @@ fn JsonTokenType! lex_number(JsonContext *context, char c) @local
fn Object*! parse_map(JsonContext* context) @local fn Object*! parse_map(JsonContext* context) @local
{ {
Object* map = object::new_obj(context.allocator); Object* map = object::new_obj(context.allocator);
JsonTokenType token = advance(context)!;
defer catch map.free(); defer catch map.free();
JsonTokenType token = advance(context)!;
DString temp_key = dstring::new_with_capacity(32, context.allocator); @stack_mem(256; Allocator mem)
defer temp_key.free();
while (token != JsonTokenType.RBRACE)
{ {
if (token != JsonTokenType.STRING) return JsonParsingError.UNEXPECTED_CHARACTER?; DString temp_key = dstring::new_with_capacity(32, mem);
DString string = context.last_string; while (token != JsonTokenType.RBRACE)
if (map.has_key(string.str_view())) return JsonParsingError.DUPLICATE_MEMBERS?;
// Copy the key to our temp holder. We do this to work around the issue
// if the temp allocator should be used as the default allocator.
temp_key.clear();
temp_key.append(string);
parse_expected(context, COLON)!;
Object* element = parse_any(context)!;
map.set(temp_key.str_view(), element);
token = advance(context)!;
if (token == JsonTokenType.COMMA)
{ {
if (token != JsonTokenType.STRING) return JsonParsingError.UNEXPECTED_CHARACTER?;
DString string = context.last_string;
if (map.has_key(string.str_view())) return JsonParsingError.DUPLICATE_MEMBERS?;
// Copy the key to our temp holder, since our
// last_string may be used in parse_any
temp_key.clear();
temp_key.append(string);
parse_expected(context, COLON)!;
Object* element = parse_any(context)!;
map.set(temp_key.str_view(), element);
token = advance(context)!; token = advance(context)!;
continue; if (token == JsonTokenType.COMMA)
{
token = advance(context)!;
continue;
}
if (token != JsonTokenType.RBRACE) return JsonParsingError.UNEXPECTED_CHARACTER?;
} }
if (token != JsonTokenType.RBRACE) return JsonParsingError.UNEXPECTED_CHARACTER?; return map;
} };
return map;
} }
fn Object*! parse_array(JsonContext* context) @local fn Object*! parse_array(JsonContext* context) @local
@@ -170,7 +192,7 @@ fn Object*! parse_array(JsonContext* context) @local
while (token != JsonTokenType.RBRACKET) while (token != JsonTokenType.RBRACKET)
{ {
Object* element = parse_from_token(context, token)!; Object* element = parse_from_token(context, token)!;
list.append(element); list.push(element);
token = advance(context)!; token = advance(context)!;
if (token == JsonTokenType.COMMA) if (token == JsonTokenType.COMMA)
{ {
@@ -364,6 +386,7 @@ fn JsonTokenType! lex_string(JsonContext* context)
default: default:
return JsonParsingError.INVALID_ESCAPE_SEQUENCE?; return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
} }
context.last_string.append(c);
} }
return STRING; return STRING;
} }

View File

@@ -8,7 +8,7 @@ distinct Fnv32a = uint;
const FNV32A_START @private = 0x811c9dc5; const FNV32A_START @private = 0x811c9dc5;
const FNV32A_MUL @private = 0x01000193; const FNV32A_MUL @private = 0x01000193;
macro void @update(uint* &h, char x) @private => *h = (*h * FNV32A_MUL) ^ x; macro void update(h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV32A_MUL;
fn void Fnv32a.init(&self) fn void Fnv32a.init(&self)
{ {
@@ -17,17 +17,17 @@ fn void Fnv32a.init(&self)
fn void Fnv32a.update(&self, char[] data) fn void Fnv32a.update(&self, char[] data)
{ {
uint h = (uint)*self; Fnv32a h = *self;
foreach (char x : data) foreach (char x : data)
{ {
@update(h, x); update(&h, x);
} }
*self = (Fnv32a)h; *self = h;
} }
macro void Fnv32a.update_char(&self, char c) macro void Fnv32a.update_char(&self, char c)
{ {
@update(*self, x); update(self, c);
} }
fn uint encode(char[] data) fn uint encode(char[] data)
@@ -35,7 +35,7 @@ fn uint encode(char[] data)
uint h = FNV32A_START; uint h = FNV32A_START;
foreach (char x : data) foreach (char x : data)
{ {
@update(h, x); update(&h, x);
} }
return h; return h;
} }

View File

@@ -8,7 +8,7 @@ distinct Fnv64a = ulong;
const FNV64A_START @private = 0xcbf29ce484222325; const FNV64A_START @private = 0xcbf29ce484222325;
const FNV64A_MUL @private = 0x00000100000001b3; const FNV64A_MUL @private = 0x00000100000001b3;
macro void @update(ulong* &h, char x) @private => *h = (*h * FNV64A_MUL) ^ x; macro void update(h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV64A_MUL;
fn void Fnv64a.init(&self) fn void Fnv64a.init(&self)
{ {
@@ -17,17 +17,17 @@ fn void Fnv64a.init(&self)
fn void Fnv64a.update(&self, char[] data) fn void Fnv64a.update(&self, char[] data)
{ {
ulong h = (ulong)*self; Fnv64a h = *self;
foreach (char x : data) foreach (char x : data)
{ {
@update(h, x); update(&h, x);
} }
*self = (Fnv64a)h; *self = h;
} }
macro void Fnv64a.update_char(&self, char c) macro void Fnv64a.update_char(&self, char c)
{ {
@update(*self, x); update(self, c);
} }
fn ulong encode(char[] data) fn ulong encode(char[] data)
@@ -35,7 +35,7 @@ fn ulong encode(char[] data)
ulong h = FNV64A_START; ulong h = FNV64A_START;
foreach (char x : data) foreach (char x : data)
{ {
@update(h, x); update(&h, x);
} }
return h; return h;
} }

107
lib/std/hash/hmac.c3 Normal file
View File

@@ -0,0 +1,107 @@
module std::hash::hmac(<HashAlg, HASH_BYTES, BLOCK_BYTES>);
import std::crypto;
struct Hmac
{
HashAlg a, b;
}
fn char[HASH_BYTES] hash(char[] key, char[] message)
{
Hmac hmac @noinit;
hmac.init(key);
hmac.update(message);
return hmac.final();
}
<*
@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)
{
usz l = output.len / HASH_BYTES;
usz r = output.len % HASH_BYTES;
Hmac hmac;
hmac.init(pw);
char[] dst_curr = output;
for (usz i = 1; i <= l; i++)
{
@derive(&hmac, salt, iterations, i, dst_curr[:HASH_BYTES]);
dst_curr = dst_curr[HASH_BYTES..];
}
if (r > 0)
{
char[HASH_BYTES] tmp;
@derive(&hmac, salt, iterations, l + 1, &tmp);
dst_curr[..] = tmp[:dst_curr.len];
mem::zero_volatile(&tmp);
}
}
fn void Hmac.init(&self, char[] key)
{
char[BLOCK_BYTES] buffer;
if (key.len > BLOCK_BYTES)
{
self.a.init();
self.a.update(key);
buffer[:HASH_BYTES] = self.a.final()[..];
}
else
{
buffer[:key.len] = key[..];
}
foreach (&b : buffer) *b ^= IPAD;
self.a.init();
self.a.update(&buffer);
foreach (&b : buffer) *b ^= IPAD ^ OPAD;
self.b.init();
self.b.update(&buffer);
mem::zero_volatile(&buffer);
}
fn void Hmac.update(&self, char[] data)
{
self.a.update(data);
}
fn char[HASH_BYTES] Hmac.final(&self)
{
self.b.update(&&self.a.final());
return self.b.final();
}
const IPAD @private = 0x36;
const OPAD @private = 0x5C;
macro @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[] out)
{
assert(out.len == HASH_BYTES);
char[HASH_BYTES] tmp @noinit;
defer mem::zero_volatile(&tmp);
Hmac hmac = *hmac_start;
hmac.update(salt);
UIntBE be = { (uint)index };
hmac.update(&&bitcast(be, char[4]));
tmp = hmac.final();
out[..] = tmp;
for (int it = 1; it < iterations; it++)
{
hmac = *hmac_start;
hmac.update(&tmp);
tmp = hmac.final();
foreach (i, v : tmp)
{
out[i] ^= v;
}
}
}

225
lib/std/hash/md5.c3 Normal file
View File

@@ -0,0 +1,225 @@
module std::hash::md5;
import std::hash::hmac;
import std::bits;
const BLOCK_BYTES = 64;
const HASH_BYTES = 16;
struct Md5
{
uint lo, hi;
uint a, b, c, d;
char[64] buffer;
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>);
fn char[HASH_BYTES] hash(char[] data)
{
Md5 md5;
md5.init();
md5.update(data);
return md5.final();
}
fn void Md5.init(&self)
{
self.a = 0x67452301;
self.b = 0xefcdab89;
self.c = 0x98badcfe;
self.d = 0x10325476;
self.lo = 0;
self.hi = 0;
}
fn void Md5.update(&ctx, char[] data)
{
uint saved_lo = ctx.lo;
if ((ctx.lo = (saved_lo + data.len) & 0x1fffffff) < saved_lo) ctx.hi++;
ctx.hi += data.len >> 29;
usz used = (usz)saved_lo & 0x3f;
if (used)
{
usz available = 64 - used;
if (data.len < available)
{
ctx.buffer[used:data.len] = data[..];
return;
}
ctx.buffer[used:available] = data[:available];
data = data[available..];
body(ctx, &ctx.buffer, 64);
}
if (data.len >= 64)
{
data = body(ctx, data, data.len & ~(usz)0x3f)[:data.len & 0x3f];
}
ctx.buffer[:data.len] = data[..];
}
fn char[HASH_BYTES] Md5.final(&ctx)
{
usz used = (usz)ctx.lo & 0x3f;
ctx.buffer[used++] = 0x80;
usz available = 64 - used;
if (available < 8)
{
ctx.buffer[used:available] = 0;
body(ctx, &ctx.buffer, 64);
used = 0;
available = 64;
}
ctx.buffer[used:available - 8] = 0;
ctx.lo <<= 3;
ctx.buffer[56:4] = bitcast(ctx.lo, char[4])[..];
ctx.buffer[60:4] = bitcast(ctx.hi, char[4])[..];
body(ctx, &ctx.buffer, 64);
char[16] res @noinit;
res[0:4] = bitcast(ctx.a, char[4]);
res[4:4] = bitcast(ctx.b, char[4]);
res[8:4] = bitcast(ctx.c, char[4]);
res[12:4] = bitcast(ctx.d, char[4]);
*ctx = {};
return res;
}
module std::hash::md5 @private;
// Implementation
macro @f(x, y, z) => z ^ (x & (y ^ z));
macro @g(x, y, z) => y ^ (z & (x ^ y));
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)
{
*a += #f(b, c, d) + @unaligned_load(*(uint *)&ptr[n * 4], 2) + t;
*a = (*a << s) | ((*a & 0xffffffff) >> (32 - s));
*a += b;
}
fn char* body(Md5* ctx, void* data, usz size)
{
char* ptr;
uint a, b, c, d;
uint saved_a, saved_b, saved_c, saved_d;
ptr = data;
a = ctx.a;
b = ctx.b;
c = ctx.c;
d = ctx.d;
do
{
saved_a = a;
saved_b = b;
saved_c = c;
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);
/* 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);
/* 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) ;
/* 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) ;
a += saved_a;
b += saved_b;
c += saved_c;
d += saved_d;
ptr += 64;
} while (size -= 64);
ctx.a = a;
ctx.b = b;
ctx.c = c;
ctx.d = d;
return ptr;
}

View File

@@ -5,13 +5,29 @@
// Implementation was off Steve Reid's SHA-1 C implementation // Implementation was off Steve Reid's SHA-1 C implementation
module std::hash::sha1; module std::hash::sha1;
import std::hash::hmac;
import std::bits; import std::bits;
const BLOCK_BYTES = 64;
const HASH_BYTES = 20;
struct Sha1 struct Sha1
{ {
uint[5] state; uint[5] state;
uint[2] count; uint[2] count;
char[64] buffer; 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>);
fn char[HASH_BYTES] hash(char[] data)
{
Sha1 sha1 @noinit;
sha1.init();
sha1.update(data);
return sha1.final();
} }
fn void Sha1.init(&self) fn void Sha1.init(&self)
@@ -28,10 +44,10 @@ fn void Sha1.init(&self)
}; };
} }
/** <*
* @param [in] data @param [in] data
* @require data.len <= uint.max @require data.len <= uint.max
**/ *>
fn void Sha1.update(&self, char[] data) fn void Sha1.update(&self, char[] data)
{ {
uint j = self.count[0]; uint j = self.count[0];
@@ -55,7 +71,7 @@ fn void Sha1.update(&self, char[] data)
} }
fn char[20] Sha1.final(&self) fn char[HASH_BYTES] Sha1.final(&self)
{ {
char[8] finalcount; char[8] finalcount;
for (uint i = 0; i < 8; i++) for (uint i = 0; i < 8; i++)
@@ -69,31 +85,31 @@ fn char[20] Sha1.final(&self)
} }
self.update(&finalcount); self.update(&finalcount);
char[20] digest; char[HASH_BYTES] digest;
for (uint i = 0; i < 20; i++) for (uint i = 0; i < HASH_BYTES; i++)
{ {
digest[i] = (char)((self.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF); digest[i] = (char)((self.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF);
} }
// Clear mem // Clear mem
mem::clear(self, Sha1.sizeof); *self = {};
finalcount = {}; finalcount = {};
return digest; return digest;
} }
union Long16 @local union Long16 @local
{ {
char[64] c; char[BLOCK_BYTES] c;
uint[16] l; 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] 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)); ^ 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: $if env::BIG_ENDIAN:
return block.l[i]; return block.l[i];
@@ -103,139 +119,139 @@ macro @blk0(&block, i) @local
$endif $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; 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); *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; 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); *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; 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); *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; 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); *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; 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); *wref = w.rotl(30);
} }
/** <*
* @param [&inout] state @param [&inout] state
* @param [&in] buffer @param [&in] buffer
**/ *>
fn void sha1_transform(uint* state, char* buffer) @local fn void sha1_transform(uint[5]* state, char* buffer) @local
{ {
Long16 block; Long16 block;
block.c[..] = buffer[:64]; block.c[..] = buffer[:64];
uint a = state[0]; uint a = (*state)[0];
uint b = state[1]; uint b = (*state)[1];
uint c = state[2]; uint c = (*state)[2];
uint d = state[3]; uint d = (*state)[3];
uint e = state[4]; uint e = (*state)[4];
@r0(block, a, b, c, d, e, 0); r0(&block, a, &b, c, d, &e, 0);
@r0(block, e, a, b, c, d, 1); r0(&block, e, &a, b, c, &d, 1);
@r0(block, d, e, a, b, c, 2); r0(&block, d, &e, a, b, &c, 2);
@r0(block, c, d, e, a, b, 3); r0(&block, c, &d, e, a, &b, 3);
@r0(block, b, c, d, e, a, 4); r0(&block, b, &c, d, e, &a, 4);
@r0(block, a, b, c, d, e, 5); r0(&block, a, &b, c, d, &e, 5);
@r0(block, e, a, b, c, d, 6); r0(&block, e, &a, b, c, &d, 6);
@r0(block, d, e, a, b, c, 7); r0(&block, d, &e, a, b, &c, 7);
@r0(block, c, d, e, a, b, 8); r0(&block, c, &d, e, a, &b, 8);
@r0(block, b, c, d, e, a, 9); r0(&block, b, &c, d, e, &a, 9);
@r0(block, a, b, c, d, e, 10); r0(&block, a, &b, c, d, &e, 10);
@r0(block, e, a, b, c, d, 11); r0(&block, e, &a, b, c, &d, 11);
@r0(block, d, e, a, b, c, 12); r0(&block, d, &e, a, b, &c, 12);
@r0(block, c, d, e, a, b, 13); r0(&block, c, &d, e, a, &b, 13);
@r0(block, b, c, d, e, a, 14); r0(&block, b, &c, d, e, &a, 14);
@r0(block, a, b, c, d, e, 15); r0(&block, a, &b, c, d, &e, 15);
@r1(block, e, a, b, c, d, 16); r1(&block, e, &a, b, c, &d, 16);
@r1(block, d, e, a, b, c, 17); r1(&block, d, &e, a, b, &c, 17);
@r1(block, c, d, e, a, b, 18); r1(&block, c, &d, e, a, &b, 18);
@r1(block, b, c, d, e, a, 19); r1(&block, b, &c, d, e, &a, 19);
@r2(block, a, b, c, d, e, 20); r2(&block, a, &b, c, d, &e, 20);
@r2(block, e, a, b, c, d, 21); r2(&block, e, &a, b, c, &d, 21);
@r2(block, d, e, a, b, c, 22); r2(&block, d, &e, a, b, &c, 22);
@r2(block, c, d, e, a, b, 23); r2(&block, c, &d, e, a, &b, 23);
@r2(block, b, c, d, e, a, 24); r2(&block, b, &c, d, e, &a, 24);
@r2(block, a, b, c, d, e, 25); r2(&block, a, &b, c, d, &e, 25);
@r2(block, e, a, b, c, d, 26); r2(&block, e, &a, b, c, &d, 26);
@r2(block, d, e, a, b, c, 27); r2(&block, d, &e, a, b, &c, 27);
@r2(block, c, d, e, a, b, 28); r2(&block, c, &d, e, a, &b, 28);
@r2(block, b, c, d, e, a, 29); r2(&block, b, &c, d, e, &a, 29);
@r2(block, a, b, c, d, e, 30); r2(&block, a, &b, c, d, &e, 30);
@r2(block, e, a, b, c, d, 31); r2(&block, e, &a, b, c, &d, 31);
@r2(block, d, e, a, b, c, 32); r2(&block, d, &e, a, b, &c, 32);
@r2(block, c, d, e, a, b, 33); r2(&block, c, &d, e, a, &b, 33);
@r2(block, b, c, d, e, a, 34); r2(&block, b, &c, d, e, &a, 34);
@r2(block, a, b, c, d, e, 35); r2(&block, a, &b, c, d, &e, 35);
@r2(block, e, a, b, c, d, 36); r2(&block, e, &a, b, c, &d, 36);
@r2(block, d, e, a, b, c, 37); r2(&block, d, &e, a, b, &c, 37);
@r2(block, c, d, e, a, b, 38); r2(&block, c, &d, e, a, &b, 38);
@r2(block, b, c, d, e, a, 39); r2(&block, b, &c, d, e, &a, 39);
@r3(block, a, b, c, d, e, 40); r3(&block, a, &b, c, d, &e, 40);
@r3(block, e, a, b, c, d, 41); r3(&block, e, &a, b, c, &d, 41);
@r3(block, d, e, a, b, c, 42); r3(&block, d, &e, a, b, &c, 42);
@r3(block, c, d, e, a, b, 43); r3(&block, c, &d, e, a, &b, 43);
@r3(block, b, c, d, e, a, 44); r3(&block, b, &c, d, e, &a, 44);
@r3(block, a, b, c, d, e, 45); r3(&block, a, &b, c, d, &e, 45);
@r3(block, e, a, b, c, d, 46); r3(&block, e, &a, b, c, &d, 46);
@r3(block, d, e, a, b, c, 47); r3(&block, d, &e, a, b, &c, 47);
@r3(block, c, d, e, a, b, 48); r3(&block, c, &d, e, a, &b, 48);
@r3(block, b, c, d, e, a, 49); r3(&block, b, &c, d, e, &a, 49);
@r3(block, a, b, c, d, e, 50); r3(&block, a, &b, c, d, &e, 50);
@r3(block, e, a, b, c, d, 51); r3(&block, e, &a, b, c, &d, 51);
@r3(block, d, e, a, b, c, 52); r3(&block, d, &e, a, b, &c, 52);
@r3(block, c, d, e, a, b, 53); r3(&block, c, &d, e, a, &b, 53);
@r3(block, b, c, d, e, a, 54); r3(&block, b, &c, d, e, &a, 54);
@r3(block, a, b, c, d, e, 55); r3(&block, a, &b, c, d, &e, 55);
@r3(block, e, a, b, c, d, 56); r3(&block, e, &a, b, c, &d, 56);
@r3(block, d, e, a, b, c, 57); r3(&block, d, &e, a, b, &c, 57);
@r3(block, c, d, e, a, b, 58); r3(&block, c, &d, e, a, &b, 58);
@r3(block, b, c, d, e, a, 59); r3(&block, b, &c, d, e, &a, 59);
@r4(block, a, b, c, d, e, 60); r4(&block, a, &b, c, d, &e, 60);
@r4(block, e, a, b, c, d, 61); r4(&block, e, &a, b, c, &d, 61);
@r4(block, d, e, a, b, c, 62); r4(&block, d, &e, a, b, &c, 62);
@r4(block, c, d, e, a, b, 63); r4(&block, c, &d, e, a, &b, 63);
@r4(block, b, c, d, e, a, 64); r4(&block, b, &c, d, e, &a, 64);
@r4(block, a, b, c, d, e, 65); r4(&block, a, &b, c, d, &e, 65);
@r4(block, e, a, b, c, d, 66); r4(&block, e, &a, b, c, &d, 66);
@r4(block, d, e, a, b, c, 67); r4(&block, d, &e, a, b, &c, 67);
@r4(block, c, d, e, a, b, 68); r4(&block, c, &d, e, a, &b, 68);
@r4(block, b, c, d, e, a, 69); r4(&block, b, &c, d, e, &a, 69);
@r4(block, a, b, c, d, e, 70); r4(&block, a, &b, c, d, &e, 70);
@r4(block, e, a, b, c, d, 71); r4(&block, e, &a, b, c, &d, 71);
@r4(block, d, e, a, b, c, 72); r4(&block, d, &e, a, b, &c, 72);
@r4(block, c, d, e, a, b, 73); r4(&block, c, &d, e, a, &b, 73);
@r4(block, b, c, d, e, a, 74); r4(&block, b, &c, d, e, &a, 74);
@r4(block, a, b, c, d, e, 75); r4(&block, a, &b, c, d, &e, 75);
@r4(block, e, a, b, c, d, 76); r4(&block, e, &a, b, c, &d, 76);
@r4(block, d, e, a, b, c, 77); r4(&block, d, &e, a, b, &c, 77);
@r4(block, c, d, e, a, b, 78); r4(&block, c, &d, e, a, &b, 78);
@r4(block, b, c, d, e, a, 79); r4(&block, b, &c, d, e, &a, 79);
state[0] += a; (*state)[0] += a;
state[1] += b; (*state)[1] += b;
state[2] += c; (*state)[2] += c;
state[3] += d; (*state)[3] += d;
state[4] += e; (*state)[4] += e;
a = b = c = d = e = 0; a = b = c = d = e = 0;
buffer[:64] = 0; block = {};
} }

176
lib/std/hash/sha256.c3 Normal file
View File

@@ -0,0 +1,176 @@
module std::hash::sha256;
import std::hash::hmac;
const BLOCK_SIZE = 64;
const HASH_SIZE = 32;
const uint[64] K @local = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
// Right rotate function
macro uint @rotr(uint x, uint n) @local => (((x) >> (n)) | ((x) << (32 - (n))));
// SHA-256 functions
macro uint @ch(uint x, uint y, uint z) @local => (x & y) ^ (~x & z);
macro uint @maj(uint x, uint y, uint z) @local => (x & y) ^ (x & z) ^ (y & z);
macro uint @_sigma0(uint x) @local => @rotr(x, 2) ^ @rotr(x, 13) ^ @rotr(x, 22);
macro uint @_sigma1(uint x) @local => @rotr(x, 6) ^ @rotr(x, 11) ^ @rotr(x, 25);
macro uint @sigma0(uint x) @local => @rotr(x, 7) ^ @rotr(x, 18) ^ (x >> 3);
macro uint @sigma1(uint x) @local => @rotr(x, 17) ^ @rotr(x, 19) ^ (x >> 10);
struct Sha256
{
uint[8] state;
ulong bitcount;
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>);
fn char[HASH_SIZE] hash(char[] data)
{
Sha256 sha256 @noinit;
sha256.init();
sha256.update(data);
return sha256.final();
}
fn void Sha256.init(&self)
{
// Sha256 initialization constants
*self = {
.state = {
0x6A09E667,
0xBB67AE85,
0x3C6EF372,
0xA54FF53A,
0x510E527F,
0x9B05688C,
0x1F83D9AB,
0x5BE0CD19
}
};
}
<*
@param [in] data
@require data.len <= uint.max
*>
fn void Sha256.update(&self, char[] data) {
uint i = 0;
uint len = data.len;
uint buffer_pos = (uint)(self.bitcount / 8) % BLOCK_SIZE;
self.bitcount += (ulong)(len * 8);
while (len--) {
self.buffer[buffer_pos++] = data[i++];
if (buffer_pos == BLOCK_SIZE) {
sha256_transform(&self.state, &self.buffer);
buffer_pos = 0; // Reset buffer position
}
}
}
fn char[HASH_SIZE] Sha256.final(&self) {
char[HASH_SIZE] hash;
ulong i = (self.bitcount / 8) % BLOCK_SIZE;
// Append 0x80 to the buffer
self.buffer[i++] = 0x80;
// Pad the buffer with zeros
if (i > BLOCK_SIZE - 8) {
while (i < BLOCK_SIZE) {
self.buffer[i++] = 0x00;
}
sha256_transform(&self.state, &self.buffer);
i = 0; // Reset buffer index after transformation
}
while (i < BLOCK_SIZE - 8) {
self.buffer[i++] = 0x00;
}
// Append the bitcount in big-endian format
for (int j = 0; j < 8; ++j) {
self.buffer[BLOCK_SIZE - 8 + j] = (char)((self.bitcount >> (56 - j * 8)) & 0xFF);
}
sha256_transform(&self.state, &self.buffer);
// Convert state to the final hash
for (i = 0; i < 8; ++i) {
hash[i * 4] = (char)((self.state[i] >> 24) & 0xFF);
hash[i * 4 + 1] = (char)((self.state[i] >> 16) & 0xFF);
hash[i * 4 + 2] = (char)((self.state[i] >> 8) & 0xFF);
hash[i * 4 + 3] = (char)(self.state[i] & 0xFF);
}
return hash;
}
<*
@param [&inout] state
@param [&in] buffer
*>
fn void sha256_transform(uint* state, char* buffer) @local {
uint a, b, c, d, e, f, g, h, t1, t2;
uint[64] m;
int i;
// Prepare the message schedule
for (i = 0; i < 16; ++i) {
m[i] = ((uint)buffer[i * 4] << 24) | ((uint)buffer[i * 4 + 1] << 16) |
((uint)buffer[i * 4 + 2] << 8) | ((uint)buffer[i * 4 + 3]); // Ensure values are cast to uint for correct shifts
}
for (i = 16; i < 64; ++i) {
m[i] = @sigma1(m[i - 2]) + m[i - 7] + @sigma0(m[i - 15]) + m[i - 16];
}
// Initialize working variables
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
f = state[5];
g = state[6];
h = state[7];
// Perform the main SHA-256 compression function
for (i = 0; i < 64; ++i) {
t1 = h + @_sigma1(e) + @ch(e, f, g) + K[i] + m[i];
t2 = @_sigma0(a) + @maj(a, b, c);
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
// Update the state
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
state[5] += f;
state[6] += g;
state[7] += h;
a = b = c = d = e = f = g = h = t1 = t2 = i = 0;
m[:64] = buffer[:64] = 0;
}

View File

@@ -2,12 +2,12 @@ module std::io;
struct BitReader struct BitReader
{ {
InStream* reader; InStream reader;
uint bits; uint bits;
uint len; uint len;
} }
fn void BitReader.init(&self, InStream* byte_reader) fn void BitReader.init(&self, InStream byte_reader)
{ {
*self = { .reader = byte_reader }; *self = { .reader = byte_reader };
} }
@@ -17,10 +17,10 @@ fn void BitReader.clear(&self) @inline
self.len = 0; self.len = 0;
} }
/** <*
* @require nbits <= 8 @require nbits <= 8
* @require self.len + nbits <= uint.sizeof * 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; uint bits = self.bits;
@@ -40,12 +40,16 @@ fn char! BitReader.read_bits(&self, uint nbits)
struct BitWriter struct BitWriter
{ {
OutStream* writer; OutStream writer;
uint bits; uint bits;
uint len; uint len;
} }
fn void BitWriter.init(&self, OutStream* byte_writer) // 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 }; *self = { .writer = byte_writer };
} }
@@ -53,7 +57,9 @@ fn void BitWriter.init(&self, OutStream* byte_writer)
fn void! BitWriter.flush(&self) fn void! BitWriter.flush(&self)
{ {
if (self.len == 0) return; 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; uint n = (self.len + 7) / 8;
char[4] buffer; char[4] buffer;
bitorder::write(bits, &buffer, UIntBE); bitorder::write(bits, &buffer, UIntBE);
@@ -61,25 +67,28 @@ fn void! BitWriter.flush(&self)
self.len = 0; self.len = 0;
} }
/** <*
* @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; if (nbits == 0) return;
uint n = self.len + nbits; while (self.len + nbits > WRITER_BITS)
uint to_write = n / 8;
uint left = n % 8;
if (to_write > 0)
{ {
ulong lbits; uint to_push = WRITER_BITS - self.len;
if (self.len > 0) lbits = (ulong)self.bits << (64 - self.len); uint bits_to_push = (bits >> (nbits - to_push)) & ((1 << to_push) - 1);
lbits |= (ulong)(bits >> left) << (64 - (n - left));
char[8] buffer; self.bits <<= to_push;
bitorder::write(lbits, &buffer, ULongBE); self.bits |= bits_to_push;
io::write_all(self.writer, buffer[:to_write])!; self.len += to_push;
nbits -= to_push;
self.flush()!;
} }
self.bits <<= left;
self.bits |= bits & ((1 << left) - 1); if (nbits == 0) return;
self.len = left;
self.bits <<= nbits;
self.bits |= bits & ((1 << nbits) - 1);
self.len += nbits;
} }

View File

@@ -7,7 +7,7 @@ struct File (InStream, OutStream)
} }
module std::io::file; module std::io::file;
import libc; import libc, std::io::path, std::io::os;
fn File! open(String filename, String mode) fn File! open(String filename, String mode)
{ {
@@ -37,17 +37,17 @@ fn usz! get_size(String path)
fn void! delete(String filename) => os::native_remove(filename) @inline; fn void! delete(String filename) => os::native_remove(filename) @inline;
/** <*
* @require self.file != null @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)!; self.file = os::native_freopen(self.file, filename, mode)!;
} }
/** <*
* @require self.file != null @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)!; os::native_fseek(self.file, offset, seek_mode)!;
@@ -57,9 +57,9 @@ fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
/* /*
Implement later Implement later
/** <*
* @require self.file == null @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() @pool()
@@ -71,17 +71,17 @@ fn void! File.memopen(File* file, char[] data, String mode)
*/ */
/** <*
* @require self.file != null @require self.file != null
*/ *>
fn void! File.write_byte(&self, char c) @dynamic fn void! File.write_byte(&self, char c) @dynamic
{ {
if (!libc::fputc(c, self.file)) return IoError.EOF?; return os::native_fputc(c, self.file);
} }
/** <*
* @param [&inout] self @param [&inout] self
*/ *>
fn void! File.close(&self) @inline @dynamic fn void! File.close(&self) @inline @dynamic
{ {
if (self.file && libc::fclose(self.file)) if (self.file && libc::fclose(self.file))
@@ -105,31 +105,40 @@ fn void! File.close(&self) @inline @dynamic
self.file = null; self.file = null;
} }
/** <*
* @require self.file @require self.file != null
*/ *>
fn bool File.eof(&self) @inline fn bool File.eof(&self) @inline
{ {
return libc::feof(self.file) != 0; return libc::feof(self.file) != 0;
} }
/** <*
* @param [in] buffer @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); return os::native_fread(self.file, buffer);
} }
/** <*
* @param [out] 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); return os::native_fwrite(self.file, buffer);
} }
fn Fd File.fd(self) @if(env::LIBC)
{
return libc::fileno(self.file);
}
fn bool File.isatty(self) @if(env::LIBC)
{
return libc::isatty(self.fd()) > 0;
}
fn char! File.read_byte(&self) @dynamic fn char! File.read_byte(&self) @dynamic
{ {
@@ -138,9 +147,64 @@ fn char! File.read_byte(&self) @dynamic
return (char)c; return (char)c;
} }
/** <*
* @require self.file `File must be initialized` Load up to buffer.len characters. Returns IoError.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"
*>
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?;
file.seek(0, SET)!;
usz read = 0;
while (read < len)
{
read += file.read(buffer[read:len - read])!;
}
return buffer[:len];
}
fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
{
File file = open(filename, "rb")!;
defer (void)file.close();
usz len = file.seek(0, END)!;
file.seek(0, SET)!;
char* data = allocator::malloc_try(allocator, len)!;
defer catch allocator::free(allocator, data);
usz read = 0;
while (read < len)
{
read += file.read(data[read:len - read])!;
}
return data[:len];
}
fn char[]! load_temp(String filename)
{
return load_new(filename, allocator::temp());
}
fn void! save(String filename, char[] data)
{
File file = open(filename, "wb")!;
defer (void)file.close();
while (data.len)
{
usz written = file.write(data)!;
data = data[written..];
}
}
<*
@require self.file != null `File must be initialized`
*>
fn void! File.flush(&self) @dynamic fn void! File.flush(&self) @dynamic
{ {
libc::fflush(self.file); libc::fflush(self.file);

View File

@@ -6,7 +6,8 @@ const int PRINTF_NTOA_BUFFER_SIZE = 256;
interface Printable interface Printable
{ {
fn String to_new_string(Allocator *allocator) @optional; 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 usz! to_format(Formatter* formatter) @optional;
} }
@@ -14,23 +15,53 @@ fault PrintFault
{ {
BUFFER_EXCEEDED, BUFFER_EXCEEDED,
INTERNAL_BUFFER_EXCEEDED, INTERNAL_BUFFER_EXCEEDED,
INVALID_FORMAT_STRING, INVALID_FORMAT,
MISSING_ARG, NOT_ENOUGH_ARGUMENTS,
INVALID_ARGUMENT_TYPE, INVALID_ARGUMENT,
}
fault FormattingFault
{
UNTERMINATED_FORMAT,
MISSING_ARG,
INVALID_WIDTH_ARG,
INVALID_FORMAT_TYPE,
} }
def OutputFn = fn void!(void* buffer, char c); def OutputFn = fn void!(void* buffer, char c);
def FloatType = double; def 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);
}
<*
Introspect a struct and print it to a formatter
@require @typekind(value) == STRUCT `This macro is only valid on macros`
*>
macro usz! struct_to_format(value, Formatter* f, bool $force_dump)
{
var $Type = $typeof(value);
usz total = f.print("{ ")!;
$foreach ($i, $member : $Type.membersof)
$if $i > 0:
total += f.print(", ")!;
$endif
$if $member.nameof != "":
total += f.printf("%s: ", $member.nameof)!;
$endif
$if ($force_dump &&& $member.typeid.kindof == STRUCT) |||
is_struct_with_default_print($typefrom($member.typeid)):
total += struct_to_format($member.get(value), f, $force_dump)!;
$else
total += f.printf("%s", $member.get(value))!;
$endif
$endforeach
return total + f.print(" }");
}
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...)
{ {
@@ -47,6 +78,7 @@ struct Formatter
uint width; uint width;
uint prec; uint prec;
usz idx; usz idx;
anyfault first_fault;
} }
} }
@@ -68,11 +100,16 @@ fn void Formatter.init(&self, OutputFn out_fn, void* data = null)
fn usz! Formatter.out(&self, char c) @private fn usz! Formatter.out(&self, char c) @private
{ {
self.out_fn(self.data, c)!; if (catch err = self.out_fn(self.data, c))
{
if (self.first_fault) return self.first_fault?;
self.first_fault = err;
return err?;
}
return 1; return 1;
} }
fn usz! Formatter.print_with_function(&self, Printable* arg) fn usz! Formatter.print_with_function(&self, Printable arg)
{ {
if (&arg.to_format) if (&arg.to_format)
{ {
@@ -85,9 +122,10 @@ fn usz! Formatter.print_with_function(&self, Printable* arg)
self.width = old_width; self.width = old_width;
self.prec = old_prec; self.prec = old_prec;
} }
if (!arg) return self.out_substr("(null)");
return arg.to_format(self); return arg.to_format(self);
} }
if (&arg.to_new_string) if (&arg.to_string)
{ {
PrintFlags old = self.flags; PrintFlags old = self.flags;
uint old_width = self.width; uint old_width = self.width;
@@ -98,16 +136,17 @@ fn usz! Formatter.print_with_function(&self, Printable* arg)
self.width = old_width; self.width = old_width;
self.prec = old_prec; self.prec = old_prec;
} }
@pool() if (!arg) return self.out_substr("(null)");
@stack_mem(1024; Allocator mem)
{ {
return self.out_substr(arg.to_new_string(mem::temp())); return self.out_substr(arg.to_string(mem));
}; };
} }
return SearchResult.MISSING?; return SearchResult.MISSING?;
} }
fn usz! Formatter.out_str(&self, any* arg) @private fn usz! Formatter.out_str(&self, any arg) @private
{ {
switch (arg.type.kindof) switch (arg.type.kindof)
{ {
@@ -118,8 +157,9 @@ fn usz! Formatter.out_str(&self, any* arg) @private
case ANYFAULT: case ANYFAULT:
case FAULT: case FAULT:
return self.out_substr((*(anyfault*)arg.ptr).nameof); return self.out_substr((*(anyfault*)arg.ptr).nameof);
case INTERFACE:
case ANY: case ANY:
return self.out_str(*(any**)arg); return self.out_str(*(any*)arg);
case OPTIONAL: case OPTIONAL:
unreachable(); unreachable();
case SIGNED_INT: case SIGNED_INT:
@@ -133,7 +173,7 @@ fn usz! Formatter.out_str(&self, any* arg) @private
} }
self.flags = {}; self.flags = {};
self.width = 0; self.width = 0;
return self.ntoa_any(arg, 10); return self.ntoa_any(arg, 10) ?? self.out_substr("<INVALID>");
case FLOAT: case FLOAT:
PrintFlags flags = self.flags; PrintFlags flags = self.flags;
uint width = self.width; uint width = self.width;
@@ -144,27 +184,18 @@ fn usz! Formatter.out_str(&self, any* arg) @private
} }
self.flags = {}; self.flags = {};
self.width = 0; self.width = 0;
return self.ftoa(float_from_any(arg)!!); return self.ftoa(float_from_any(arg)) ?? self.out_substr("ERR");
case BOOL: case BOOL:
return self.out_substr(*(bool*)arg.ptr ? "true" : "false"); return self.out_substr(*(bool*)arg.ptr ? "true" : "false");
default: default:
} }
usz! n = self.print_with_function((Printable*)arg); usz! n = self.print_with_function((Printable)arg);
if (catch err = n) if (try n) return n;
{ if (@catch(n) != SearchResult.MISSING) n!;
case SearchResult.MISSING:
break;
default:
return err?;
}
else
{
return n;
}
switch (arg.type.kindof) switch (arg.type.kindof)
{ {
case ENUM: 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); assert(i < arg.type.names.len, "Illegal enum value found, numerical value was %d.", i);
return self.out_substr(arg.type.names[i]); return self.out_substr(arg.type.names[i]);
case STRUCT: case STRUCT:
@@ -176,16 +207,29 @@ fn usz! Formatter.out_str(&self, any* arg) @private
case FUNC: case FUNC:
return self.out_substr("<function>"); return self.out_substr("<function>");
case DISTINCT: case DISTINCT:
if (arg.type == String.typeid)
{
return self.out_substr(*(String*)arg);
}
if (arg.type == ZString.typeid) if (arg.type == ZString.typeid)
{ {
return self.out_substr(((ZString*)arg).str_view()); return self.out_substr(*(ZString*)arg ? ((ZString*)arg).str_view() : "(null)");
} }
if (arg.type == DString.typeid) if (arg.type == DString.typeid)
{ {
return self.out_substr(((DString*)arg).str_view()); return self.out_substr(*(DString*)arg ? ((DString*)arg).str_view() : "(null)");
} }
return self.out_str(arg.as_inner()); return self.out_str(arg.as_inner());
case POINTER: case POINTER:
typeid inner = arg.type.inner;
void** pointer = arg.ptr;
if (arg.type.inner != void.typeid)
{
any deref = any_make(*pointer, inner);
n = self.print_with_function((Printable)deref);
if (try n) return n;
if (@catch(n) != SearchResult.MISSING) n!;
}
PrintFlags flags = self.flags; PrintFlags flags = self.flags;
uint width = self.width; uint width = self.width;
defer defer
@@ -193,11 +237,10 @@ fn usz! Formatter.out_str(&self, any* arg) @private
self.flags = flags; self.flags = flags;
self.width = width; self.width = width;
} }
self.flags = {};
self.width = 0; self.width = 0;
return self.ntoa_any(arg, 16); return self.out_substr("0x")! + self.ntoa_any(arg, 16);
case ARRAY: case ARRAY:
// this is SomeType[*] so grab the "SomeType" // this is SomeType[?] so grab the "SomeType"
PrintFlags flags = self.flags; PrintFlags flags = self.flags;
uint width = self.width; uint width = self.width;
defer defer
@@ -231,7 +274,7 @@ fn usz! Formatter.out_str(&self, any* arg) @private
} }
self.flags = {}; self.flags = {};
self.width = 0; self.width = 0;
// this is SomeType[*] so grab the "SomeType" // this is SomeType[?] so grab the "SomeType"
typeid inner = arg.type.inner; typeid inner = arg.type.inner;
usz size = inner.sizeof; usz size = inner.sizeof;
usz vlen = arg.type.len; usz vlen = arg.type.len;
@@ -246,13 +289,9 @@ fn usz! Formatter.out_str(&self, any* arg) @private
} }
len += self.out_substr(">]")!; len += self.out_substr(">]")!;
return len; return len;
case SUBARRAY: case SLICE:
// this is SomeType[] so grab the "SomeType" // this is SomeType[] so grab the "SomeType"
typeid inner = arg.type.inner; typeid inner = arg.type.inner;
if (inner == char.typeid)
{
return self.out_substr(*(String*)arg);
}
if (inner == void.typeid) inner = char.typeid; if (inner == void.typeid) inner = char.typeid;
PrintFlags flags = self.flags; PrintFlags flags = self.flags;
uint width = self.width; uint width = self.width;
@@ -277,6 +316,9 @@ fn usz! Formatter.out_str(&self, any* arg) @private
} }
len += self.out(']')!; len += self.out(']')!;
return len; return len;
case ANY:
case INTERFACE:
unreachable("Already handled");
default: default:
} }
return self.out_substr("Invalid type"); return self.out_substr("Invalid type");
@@ -289,10 +331,31 @@ fn void! out_null_fn(void* data @unused, char c @unused) @private
{ {
} }
macro usz! @report_fault(Formatter* f, $fault)
fn usz! Formatter.vprintf(&self, String format, any*[] anys)
{ {
(void)f.out_substr($fault);
return PrintFault.INVALID_FORMAT?;
}
macro usz! @wrap_bad(Formatter* f, #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?;
}
return len;
}
fn usz! Formatter.vprintf(&self, String format, any[] anys)
{
self.first_fault = {};
if (!self.out_fn) if (!self.out_fn)
{ {
// use null output function // use null output function
@@ -312,7 +375,7 @@ fn usz! Formatter.vprintf(&self, String format, any*[] anys)
continue; continue;
} }
i++; i++;
if (i >= format_len) return PrintFault.INVALID_FORMAT_STRING?; if (i >= format_len) return @report_fault(self, "%ERR");
c = format[i]; c = format[i];
if (c == '%') if (c == '%')
{ {
@@ -332,11 +395,12 @@ fn usz! Formatter.vprintf(&self, String format, any*[] anys)
case '#': self.flags.hash = true; case '#': self.flags.hash = true;
default: break FLAG_EVAL; default: break FLAG_EVAL;
} }
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING?; if (++i >= format_len) return @report_fault(self, "%ERR");
c = format[i]; c = format[i];
} }
// evaluate width field // 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]; c = format[i];
if (w < 0) if (w < 0)
{ {
@@ -349,16 +413,22 @@ fn usz! Formatter.vprintf(&self, String format, any*[] anys)
if (c == '.') if (c == '.')
{ {
self.flags.precision = true; self.flags.precision = true;
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING?; 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; self.prec = prec < 0 ? 0 : prec;
c = format[i]; c = format[i];
} }
// evaluate specifier // evaluate specifier
uint base = 0; uint base = 0;
if (variant_index >= anys.len) return PrintFault.MISSING_ARG?; if (variant_index >= anys.len)
any* current = anys[variant_index++]; {
self.first_err(PrintFault.NOT_ENOUGH_ARGUMENTS);
total_len += self.out_substr("<MISSING>")!;
continue;
}
any current = anys[variant_index++];
switch (c) switch (c)
{ {
case 'd': case 'd':
@@ -383,29 +453,62 @@ fn usz! Formatter.vprintf(&self, String format, any*[] anys)
self.flags.uppercase = true; self.flags.uppercase = true;
nextcase; nextcase;
case 'a': case 'a':
total_len += self.atoa(float_from_any(current)!!)!; total_len += @wrap_bad(self, self.atoa(float_from_any(current)))!;
continue; continue;
case 'F' : case 'F' :
self.flags.uppercase = true; self.flags.uppercase = true;
nextcase; nextcase;
case 'f': case 'f':
total_len += self.ftoa(float_from_any(current)!!)!; total_len += @wrap_bad(self, self.ftoa(float_from_any(current)))!;
continue; continue;
case 'E': case 'E':
self.flags.uppercase = true; self.flags.uppercase = true;
nextcase; nextcase;
case 'e': case 'e':
total_len += self.etoa(float_from_any(current)!!)!; total_len += @wrap_bad(self, self.etoa(float_from_any(current)))!;
continue; continue;
case 'G': case 'G':
self.flags.uppercase = true; self.flags.uppercase = true;
nextcase; nextcase;
case 'g': case 'g':
total_len += self.gtoa(float_from_any(current)!!)!; total_len += @wrap_bad(self, self.gtoa(float_from_any(current)))!;
continue; continue;
case 'c': case 'c':
total_len += self.out_char(current)!; total_len += self.out_char(current)!;
continue; continue;
case 'H':
self.flags.uppercase = true;
nextcase;
case 'h':
char[] out @noinit;
switch (current)
{
case char[]:
out = *current;
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': case 's':
if (self.flags.left) if (self.flags.left)
{ {
@@ -414,11 +517,14 @@ fn usz! Formatter.vprintf(&self, String format, any*[] anys)
total_len += self.pad(' ', self.width, len)!; total_len += self.pad(' ', self.width, len)!;
continue; continue;
} }
OutputFn out_fn = self.out_fn; if (self.width)
self.out_fn = (OutputFn)&out_null_fn; {
usz len = self.out_str(current)!; OutputFn out_fn = self.out_fn;
self.out_fn = out_fn; self.out_fn = (OutputFn)&out_null_fn;
total_len += self.pad(' ', self.width, len)!; usz len = self.out_str(current)!;
self.out_fn = out_fn;
total_len += self.pad(' ', self.width, len)!;
}
total_len += self.out_str(current)!; total_len += self.out_str(current)!;
continue; continue;
case 'p': case 'p':
@@ -426,7 +532,9 @@ fn usz! Formatter.vprintf(&self, String format, any*[] anys)
self.flags.hash = true; self.flags.hash = true;
base = 16; base = 16;
default: default:
return PrintFault.INVALID_FORMAT_STRING?; self.first_err(PrintFault.INVALID_FORMAT);
total_len += self.out_substr("<BAD FORMAT>")!;
continue;
} }
if (base != 10) if (base != 10)
{ {
@@ -437,14 +545,13 @@ fn usz! Formatter.vprintf(&self, String format, any*[] anys)
if (self.flags.precision) self.flags.zeropad = false; if (self.flags.precision) self.flags.zeropad = false;
bool is_neg; bool is_neg;
uint128 v = int_from_any(current, &is_neg)!!; total_len += @wrap_bad(self, self.ntoa(int_from_any(current, &is_neg), is_neg, base))!;
total_len += self.ntoa(v, is_neg, base)!;
} }
// termination // termination
// out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); // out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
// return written chars without terminating \0 // return written chars without terminating \0
if (self.first_fault) return self.first_fault?;
return total_len; return total_len;
} }
@@ -458,4 +565,4 @@ fn usz! Formatter.print(&self, String str)
} }
foreach (c : str) self.out(c)!; foreach (c : str) self.out(c)!;
return self.idx; return self.idx;
} }

View File

@@ -1,15 +1,44 @@
module std::io; module std::io;
import std::math;
const char[16] XDIGITS_H = "0123456789ABCDEF"; const char[16] XDIGITS_H = "0123456789ABCDEF";
const char[16] XDIGITS_L = "0123456789abcdef"; const char[16] XDIGITS_L = "0123456789abcdef";
fault FormattingFault
{
BAD_FORMAT
}
fn usz! print_hex_chars(Formatter* f, char[] out, bool uppercase) @inline
{
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)
{
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; if (!self.flags.left) return 0;
return self.pad(' ', self.width, len); 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) switch (arg.type.kindof)
{ {
@@ -20,6 +49,7 @@ fn uint128! int_from_any(any* arg, bool *is_neg) @private
case TypeKind.ENUM: case TypeKind.ENUM:
return int_from_any(arg.as_inner(), is_neg); return int_from_any(arg.as_inner(), is_neg);
default: default:
break;
} }
*is_neg = false; *is_neg = false;
switch (arg) switch (arg)
@@ -58,18 +88,15 @@ fn uint128! int_from_any(any* arg, bool *is_neg) @private
double d = *arg; double d = *arg;
return (uint128)((*is_neg = d < 0) ? -d : d); return (uint128)((*is_neg = d < 0) ? -d : d);
default: default:
return PrintFault.INVALID_ARGUMENT_TYPE?; return FormattingFault.BAD_FORMAT?;
} }
} }
fn FloatType! float_from_any(any* arg) @private fn FloatType! float_from_any(any arg) @private
{ {
$if env::F128_SUPPORT: $if env::F128_SUPPORT:
if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr); if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr);
$endif $endif
$if env::F16_SUPPORT:
if (arg.type == float16.typeid) return *((float16*)arg.ptr);
$endif
if (arg.type.kindof == TypeKind.DISTINCT) if (arg.type.kindof == TypeKind.DISTINCT)
{ {
return float_from_any(arg.as_inner()); return float_from_any(arg.as_inner());
@@ -103,19 +130,19 @@ fn FloatType! float_from_any(any* arg) @private
case double: case double:
return (FloatType)*arg; return (FloatType)*arg;
default: default:
return PrintFault.INVALID_ARGUMENT_TYPE?; return FormattingFault.BAD_FORMAT?;
} }
} }
/** <*
* Read a simple integer value, typically for formatting. Read a simple integer value, typically for formatting.
*
* @param [inout] len_ptr "the length remaining." @param [inout] len_ptr "the length remaining."
* @param [in] buf "the buf to read from." @param [in] buf "the buf to read from."
* @param maxlen "the maximum len that can be read." @param maxlen "the maximum len that can be read."
* @return "The result of the atoi." @return "The result of the atoi."
**/ *>
fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
{ {
uint i = 0; uint i = 0;
@@ -203,8 +230,7 @@ fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
// Add padding // Add padding
if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!; if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
String s = self.flags.uppercase ? "INF" : "inf"; String s = self.flags.uppercase ? "INF" : "inf";
if (y != y) s = self.flags.uppercase ? "NAN" : "nan"; if (math::is_nan(y)) s = self.flags.uppercase ? "NAN" : "nan";
len += s.len;
if (pl) len += self.out(is_neg ? '-' : '+')!; if (pl) len += self.out(is_neg ? '-' : '+')!;
len += self.out_chars(s)!; len += self.out_chars(s)!;
if (self.flags.left) len += self.pad(' ', self.width, 3 + pl)!; if (self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
@@ -587,15 +613,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; bool is_neg;
uint128 val = int_from_any(arg, &is_neg)!!; return self.ntoa(int_from_any(arg, &is_neg)!!, is_neg, base) @inline;
return self.ntoa(val, 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; usz len = 1;
uint l = 1; uint l = 1;
// pre padding // pre padding
@@ -630,40 +659,31 @@ fn usz! Formatter.out_reverse(&self, char[] buf) @private
usz buffer_start_idx = self.idx; usz buffer_start_idx = self.idx;
usz len = buf.len; usz len = buf.len;
// pad spaces up to given width // pad spaces up to given width
if (!self.flags.zeropad) if (!self.flags.zeropad && !self.flags.left)
{ {
n += self.adjust(len)!; n += self.pad(' ', self.width, len)!;
} }
// reverse string // reverse string
while (len) n += self.out(buf[--len])!; while (len) n += self.out(buf[--len])!;
// append pad spaces up to given width // append pad spaces up to given width
n += self.adjust(self.idx - buffer_start_idx)!; n += self.adjust(n)!;
return n; return n;
} }
fn void! printf_advance_format(usz format_len, usz *index_ptr) @inline @private
{
usz val = ++(*index_ptr);
if (val >= format_len) return FormattingFault.UNTERMINATED_FORMAT?;
}
fn any*! next_any(any** args_ptr, usz args_len, usz* arg_index_ptr) @inline @private
{
if (*arg_index_ptr >= args_len) return FormattingFault.MISSING_ARG?;
return args_ptr[(*arg_index_ptr)++];
}
fn int! printf_parse_format_field( fn int! printf_parse_format_field(
any** args_ptr, usz args_len, usz* args_index_ptr, any* args_ptr, usz args_len, usz* args_index_ptr,
char* format_ptr, usz format_len, usz* index_ptr) @inline @private char* format_ptr, usz format_len, usz* index_ptr) @inline @private
{ {
char c = format_ptr[*index_ptr]; char c = format_ptr[*index_ptr];
if (c.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr); if (c.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr);
if (c != '*') return 0; if (c != '*') return 0;
printf_advance_format(format_len, index_ptr)!; usz len = ++(*index_ptr);
any* val = next_any(args_ptr, args_len, args_index_ptr)!; if (len >= format_len) return FormattingFault.BAD_FORMAT?;
if (!val.type.kindof.is_int()) return FormattingFault.INVALID_WIDTH_ARG?; if (*args_index_ptr >= args_len) return FormattingFault.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); uint! intval = types::any_to_int(val, int);
return intval ?? FormattingFault.INVALID_WIDTH_ARG?; return intval ?? FormattingFault.BAD_FORMAT?;
} }

View File

@@ -45,13 +45,19 @@ fault IoError
} }
/** <*
* @param stream Read from a stream (default is stdin) to the next "\n"
* @require @is_instream(stream) or to the end of the stream, whatever comes first.
**/ "\r" will be filtered from the String.
macro String! readline(stream = io::stdin(), Allocator* allocator = mem::heap())
@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())
{ {
bool $is_stream = @typeid(stream) == InStream*.typeid; bool $is_stream = @typeis(stream, InStream);
$if $is_stream: $if $is_stream:
$typeof(&stream.read_byte) func = &stream.read_byte; $typeof(&stream.read_byte) func = &stream.read_byte;
char val = func((void*)stream)!; char val = func((void*)stream)!;
@@ -59,63 +65,98 @@ macro String! readline(stream = io::stdin(), Allocator* allocator = mem::heap()
char val = stream.read_byte()!; char val = stream.read_byte()!;
$endif $endif
if (val == '\n') return ""; if (val == '\n') return "";
@pool(allocator) @pool(allocator)
{ {
DString str = dstring::temp_with_capacity(256); DString str = dstring::temp_with_capacity(256);
if (val != '\r') str.append(val); if (val != '\r') str.append(val);
while (1) while (1)
{ {
$if $is_stream: $if $is_stream:
char! c = func((void*)stream); char! c = func((void*)stream);
$else $else
char! c = stream.read_byte(); char! c = stream.read_byte();
$endif $endif
if (catch err = c) if (catch err = c)
{ {
if (err == IoError.EOF) break; if (err == IoError.EOF) break;
return err?; return err?;
} }
if (c == '\r') continue; if (c == '\r') continue;
if (c == '\n') break; if (c == '\n') break;
str.append_char(c); str.append_char(c);
} }
return str.copy_str(allocator); return str.copy_str(allocator);
}; };
} }
macro String! treadline(stream = io::stdin()) => readline(stream, mem::temp()) @inline; <*
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_outstream(out) "The output must implement OutStream" @require @is_instream(stream) `The stream must implement InStream.`
*/ @return `The temporary string containing the data read.`
*>
macro String! treadline(stream = io::stdin())
{
return readline(stream, allocator::temp()) @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.`
@return `the number of bytes printed.`
*>
macro usz! fprint(out, x) macro usz! fprint(out, x)
{ {
var $Type = $typeof(x); var $Type = $typeof(x);
$switch ($Type) $switch ($Type)
$case String: $case String: return out.write(x);
return out.write(x); $case ZString: return out.write(x.str_view());
$case ZString: $case DString: return out.write(x.str_view());
return out.write(x.str_view()); $default:
$case DString: $if $assignable(x, String):
return out.write(x.str_view()); return out.write((String)x);
$default: $else
$if $assignable(x, String): $if is_struct_with_default_print($Type):
return out.write((String)x); Formatter formatter;
$else formatter.init(&out_putstream_fn, &&(OutStream)out);
return fprintf(out, "%s", x); return struct_to_format(x, &formatter, false);
$endif $else
return fprintf(out, "%s", x);
$endif
$endif
$endswitch $endswitch
} }
fn usz! fprintf(OutStream* out, String format, args...) <*
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`
@return `the number of characters printed`
*>
fn usz! fprintf(OutStream out, String format, args...)
{ {
Formatter formatter; Formatter formatter;
formatter.init(&out_putstream_fn, &out); formatter.init(&out_putstream_fn, &out);
return formatter.vprintf(format, args); return formatter.vprintf(format, args);
} }
fn usz! fprintfn(OutStream* out, String format, args...) @maydiscard <*
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`
@return `the number of characters printed`
*>
fn usz! fprintfn(OutStream out, String format, args...) @maydiscard
{ {
Formatter formatter; Formatter formatter;
formatter.init(&out_putstream_fn, &out); formatter.init(&out_putstream_fn, &out);
@@ -125,15 +166,15 @@ fn usz! fprintfn(OutStream* out, String format, args...) @maydiscard
return len + 1; return len + 1;
} }
/** <*
* @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)!; usz len = fprint(out, x)!;
out.write_byte('\n')!; out.write_byte('\n')!;
$switch $switch
$case @typeid(out) == OutStream*.typeid: $case @typeid(out) == OutStream.typeid:
if (&out.flush) out.flush()!; if (&out.flush) out.flush()!;
$case $defined(out.flush): $case $defined(out.flush):
out.flush()!; out.flush()!;
@@ -141,21 +182,37 @@ macro usz! fprintn(out, x = "")
return len + 1; return len + 1;
} }
<*
Print any value to stdout.
*>
macro void print(x) macro void print(x)
{ {
(void)fprint(io::stdout(), x); (void)fprint(io::stdout(), x);
} }
<*
Print any value to stdout, appending an '\n after.
@param x "The value to print"
*>
macro void printn(x = "") macro void printn(x = "")
{ {
(void)fprintn(io::stdout(), x); (void)fprintn(io::stdout(), x);
} }
<*
Print any value to stderr.
*>
macro void eprint(x) macro void eprint(x)
{ {
(void)fprint(io::stderr(), x); (void)fprint(io::stderr(), x);
} }
<*
Print any value to stderr, appending an '\n after.
@param x "The value to print"
*>
macro void eprintn(x) macro void eprintn(x)
{ {
(void)fprintn(io::stderr(), x); (void)fprintn(io::stderr(), x);
@@ -164,15 +221,36 @@ 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; OutStream* stream = data;
return (*stream).write_byte(c); 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
} }
<*
Prints using a 'printf'-style formatting string.
To print integer numbers, use "%d" or "%x"/"%X,
the latter gives the hexadecimal representation.
All types can be printed using "%s" which gives
the default representation of the value.
To create a custom output for a type, implement
the Printable interface.
@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...) @maydiscard
{ {
Formatter formatter; Formatter formatter;
@@ -180,36 +258,65 @@ fn usz! printf(String format, args...) @maydiscard
return formatter.vprintf(format, args); return formatter.vprintf(format, args);
} }
<*
Prints using a 'printf'-style formatting string,
appending '\n' at the end. See `printf`.
@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...) @maydiscard
{ {
Formatter formatter; Formatter formatter;
formatter.init(&out_putchar_fn); formatter.init(&out_putchar_fn);
usz len = formatter.vprintf(format, args)!; usz! len = formatter.vprintf(format, args);
putchar('\n'); out_putchar_fn(null, '\n')!;
io::stdout().flush()!; io::stdout().flush()!;
return len + 1; return len + 1;
} }
<*
Prints using a 'printf'-style formatting string
to stderr.
@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; Formatter formatter;
OutStream* stream = stderr(); OutStream stream = stderr();
formatter.init(&out_putstream_fn, &stream); formatter.init(&out_putstream_fn, &stream);
return formatter.vprintf(format, args); return formatter.vprintf(format, args);
} }
<*
Prints using a 'printf'-style formatting string,
to stderr appending '\n' at the end. See `printf`.
@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; Formatter formatter;
OutStream* stream = stderr(); OutStream stream = stderr();
formatter.init(&out_putstream_fn, &stream); formatter.init(&out_putstream_fn, &stream);
usz len = formatter.vprintf(format, args)! + 1; usz! len = formatter.vprintf(format, args);
stderr().write_byte('\n')!; stderr().write_byte('\n')!;
stderr().flush()!; stderr().flush()!;
return len; return len + 1;
} }
<*
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`
@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; Formatter formatter;
@@ -219,6 +326,7 @@ fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
return buffer[:data.written]; return buffer[:data.written];
} }
// 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; BufferData *buffer_data = data;
@@ -226,22 +334,30 @@ fn void! out_buffer_fn(void *data, char c) @private
buffer_data.buffer[buffer_data.written++] = c; buffer_data.buffer[buffer_data.written++] = c;
} }
// Used for buffer printing
struct BufferData @private struct BufferData @private
{ {
char[] buffer; char[] buffer;
usz written; usz written;
} }
// Only available with LIBC
module std::io @if (env::LIBC); module std::io @if (env::LIBC);
import libc; import libc;
<*
Libc `putchar`, prints a single character to stdout.
*>
fn void putchar(char c) @inline fn void putchar(char c) @inline
{ {
libc::putchar(c); libc::putchar(c);
} }
<*
Get standard out.
@return `stdout as a File`
*>
fn File* stdout() fn File* stdout()
{ {
static File file; static File file;
@@ -249,6 +365,11 @@ fn File* stdout()
return &file; return &file;
} }
<*
Get standard err.
@return `stderr as a File`
*>
fn File* stderr() fn File* stderr()
{ {
static File file; static File file;
@@ -256,6 +377,11 @@ fn File* stderr()
return &file; return &file;
} }
<*
Get standard in.
@return `stdin as a File`
*>
fn File* stdin() fn File* stdin()
{ {
static File file; static File file;
@@ -271,7 +397,7 @@ File stderr_file;
fn void putchar(char c) @inline fn void putchar(char c) @inline
{ {
(void)stdout_file.putc(c); (void)stdout_file.write_byte(c);
} }
fn File* stdout() fn File* stdout()
@@ -288,3 +414,4 @@ fn File* stdin()
{ {
return &stdin_file; return &stdin_file;
} }

View File

@@ -1,5 +1,5 @@
module std::io::os; module std::io::os;
import libc; import std::io::path, libc, std::os;
macro void! native_chdir(Path path) macro void! native_chdir(Path path)
{ {

View File

@@ -1,66 +1,57 @@
module std::io::os @if(env::LIBC); module std::io::os @if(env::LIBC);
import libc; import libc;
/** <*
* @require mode.len > 0 @require mode.len > 0
* @require filename.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())!;
$if env::WIN32: $else
void* file = libc::_wfopen(filename.to_temp_wstring(), mode.to_temp_wstring())!; void* file = libc::fopen(filename.zstr_tcopy(), mode.zstr_tcopy());
$else $endif
void* file = libc::fopen(filename.zstr_tcopy(), mode.zstr_tcopy()); return file ?: file_open_errno()?;
$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: switch (libc::errno())
CInt result = libc::_wremove(filename.to_temp_wstring())!;
$else
CInt result = libc::remove(filename.zstr_tcopy());
$endif
if (result)
{ {
switch (libc::errno()) case errno::ENOENT:
{ return IoError.FILE_NOT_FOUND?;
case errno::ENOENT: case errno::EACCES:
return IoError.FILE_NOT_FOUND?; default:
case errno::EACCES: return IoError.FILE_CANNOT_DELETE?;
default:
return IoError.FILE_CANNOT_DELETE?;
}
} }
}; }
} }
/** <*
* @require mode.len > 0 @require mode.len > 0
* @require filename.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)!;
$if env::WIN32: $else
file = libc::_wfreopen(filename.to_temp_wstring(), mode.to_temp_wstring(), file)!; file = libc::freopen(filename.zstr_tcopy(), mode.zstr_tcopy(), file);
$else $endif
file = libc::freopen(filename.zstr_tcopy(), mode.zstr_tcopy(), file); return file ?: file_open_errno()?;
$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()?;
} }
@@ -75,6 +66,11 @@ fn usz! native_fwrite(CFile file, char[] buffer) @inline
return libc::fwrite(buffer.ptr, 1, buffer.len, file); return libc::fwrite(buffer.ptr, 1, buffer.len, file);
} }
fn void! native_fputc(CInt c, CFile stream) @inline
{
if (libc::fputc(c, stream) == libc::EOF) return IoError.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); return libc::fread(buffer.ptr, 1, buffer.len, file);

View File

@@ -9,6 +9,7 @@ def FtellFn = fn usz!(void*);
def FwriteFn = fn usz!(void*, char[] buffer); def FwriteFn = fn usz!(void*, char[] buffer);
def FreadFn = fn usz!(void*, char[] buffer); def FreadFn = fn usz!(void*, char[] buffer);
def RemoveFn = fn void!(String); def RemoveFn = fn void!(String);
def FputcFn = fn void!(int, void*);
FopenFn native_fopen_fn @weak @if(!$defined(native_fopen_fn)); FopenFn native_fopen_fn @weak @if(!$defined(native_fopen_fn));
FcloseFn native_fclose_fn @weak @if(!$defined(native_fclose_fn)); FcloseFn native_fclose_fn @weak @if(!$defined(native_fclose_fn));
@@ -18,32 +19,33 @@ FtellFn native_ftell_fn @weak @if(!$defined(native_ftell_fn));
FwriteFn native_fwrite_fn @weak @if(!$defined(native_fwrite_fn)); FwriteFn native_fwrite_fn @weak @if(!$defined(native_fwrite_fn));
FreadFn native_fread_fn @weak @if(!$defined(native_fread_fn)); FreadFn native_fread_fn @weak @if(!$defined(native_fread_fn));
RemoveFn native_remove_fn @weak @if(!$defined(native_remove_fn)); RemoveFn native_remove_fn @weak @if(!$defined(native_remove_fn));
FputcFn native_fputc_fn @weak @if(!$defined(native_fputc_fn));
/** <*
* @require mode.len > 0 @require mode.len > 0
* @require filename.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); if (native_fopen_fn) return native_fopen_fn(filename, mode);
return IoError.UNSUPPORTED_OPERATION?; return IoError.UNSUPPORTED_OPERATION?;
} }
/** <*
* Delete a file. Delete a file.
*
* @require filename.len > 0 @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); if (native_remove_fn) return native_remove_fn(filename);
return IoError.UNSUPPORTED_OPERATION?; return IoError.UNSUPPORTED_OPERATION?;
} }
/** <*
* @require mode.len > 0 @require mode.len > 0
* @require filename.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); if (native_freopen_fn) return native_freopen_fn(file, filename, mode);
@@ -73,3 +75,9 @@ fn usz! native_fread(CFile file, char[] buffer) @inline
if (native_fread_fn) return native_fread_fn(file, buffer); if (native_fread_fn) return native_fread_fn(file, buffer);
return IoError.UNSUPPORTED_OPERATION?; return IoError.UNSUPPORTED_OPERATION?;
} }
fn void! native_fputc(CInt c, CFile stream) @inline
{
if (native_fputc_fn) return native_fputc_fn(c, stream);
return IoError.UNSUPPORTED_OPERATION?;
}

View File

@@ -1,56 +1,50 @@
module std::io::os; module std::io::os;
import libc; import libc, std::os, std::io;
fn void! native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX) 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: switch (libc::errno())
int res = libc::stat(path.zstr_tcopy(), stat);
$else
unreachable("Stat unimplemented");
int res = 0;
$endif
if (res != 0)
{ {
switch (libc::errno()) case errno::EBADF:
{ return IoError.FILE_NOT_VALID?;
case errno::EBADF: case errno::EFAULT:
return IoError.FILE_NOT_VALID?; unreachable("Invalid stat");
case errno::EFAULT: case errno::EIO:
unreachable("Invalid stat"); return IoError.GENERAL_ERROR?;
case errno::EIO: case errno::EACCES:
return IoError.GENERAL_ERROR?; return IoError.NO_PERMISSION?;
case errno::EACCES: case errno::ELOOP:
return IoError.NO_PERMISSION?; return IoError.NO_PERMISSION?;
case errno::ELOOP: case errno::ENAMETOOLONG:
return IoError.NO_PERMISSION?; return IoError.NAME_TOO_LONG?;
case errno::ENAMETOOLONG: case errno::ENOENT:
return IoError.NAME_TOO_LONG?; return IoError.FILE_NOT_FOUND?;
case errno::ENOENT: case errno::ENOTDIR:
return IoError.FILE_NOT_FOUND?; return IoError.FILE_NOT_DIR?;
case errno::ENOTDIR: case errno::EOVERFLOW:
return IoError.FILE_NOT_DIR?; return IoError.GENERAL_ERROR?;
case errno::EOVERFLOW: default:
return IoError.GENERAL_ERROR?; return IoError.UNKNOWN_ERROR?;
default:
return IoError.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_FILE_ATTRIBUTE_DATA data; Win32_LARGE_INTEGER size;
win32::getFileAttributesExW(path.to_temp_wstring()!, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data); size.lowPart = data.nFileSizeLow;
Win32_LARGE_INTEGER size; size.highPart = data.nFileSizeHigh;
size.lowPart = data.nFileSizeLow; return (usz)size.quadPart;
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)
@@ -71,6 +65,9 @@ fn bool native_file_or_dir_exists(String path)
{ {
$switch $switch
$case env::DARWIN: $case env::DARWIN:
$case env::FREEBSD:
$case env::NETBSD:
$case env::OPENBSD:
$case env::LINUX: $case env::LINUX:
Stat stat; Stat stat;
return @ok(native_stat(&stat, path)); return @ok(native_stat(&stat, path));
@@ -93,9 +90,12 @@ fn bool native_is_file(String path)
{ {
$switch $switch
$case env::DARWIN: $case env::DARWIN:
$case env::FREEBSD:
$case env::NETBSD:
$case env::OPENBSD:
$case env::LINUX: $case env::LINUX:
Stat stat; Stat stat;
return @ok(native_stat(&stat, path)) && stat.st_mode & libc::S_IFREG; return @ok(native_stat(&stat, path)) && libc_S_ISTYPE(stat.st_mode, libc::S_IFREG);
$default: $default:
File! f = file::open(path, "r"); File! f = file::open(path, "r");
defer (void)f.close(); defer (void)f.close();
@@ -105,9 +105,9 @@ fn bool native_is_file(String path)
fn bool native_is_dir(String path) fn bool native_is_dir(String path)
{ {
$if env::DARWIN || env::LINUX: $if env::DARWIN || env::LINUX || env::BSD_FAMILY:
Stat stat; Stat stat;
return @ok(native_stat(&stat, path)) && stat.st_mode & libc::S_IFDIR; return @ok(native_stat(&stat, path)) && libc_S_ISTYPE(stat.st_mode, libc::S_IFDIR);
$else $else
return native_file_or_dir_exists(path) && !native_is_file(path); return native_file_or_dir_exists(path) && !native_is_file(path);
$endif $endif

View File

@@ -1,7 +1,7 @@
module std::io::os; module std::io::os;
import libc; import libc, std::os;
macro String! getcwd(Allocator* allocator = mem::heap()) macro String! getcwd(Allocator allocator = allocator::heap())
{ {
$switch $switch
$case env::WIN32: $case env::WIN32:

View File

@@ -1,9 +1,10 @@
module std::io::file::os @if(env::POSIX); 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; PathList list;
list.init_new(.allocator = allocator); list.new_init(allocator: allocator);
DIRPtr directory = posix::opendir(dir.str_view() ? dir.as_zstr() : (ZString)"."); DIRPtr directory = posix::opendir(dir.str_view() ? dir.as_zstr() : (ZString)".");
defer if (directory) posix::closedir(directory); 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) ? IoError.CANNOT_READ_DIR : IoError.FILE_NOT_DIR)?;
@@ -15,17 +16,18 @@ fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Al
if (entry.d_type == posix::DT_LNK && no_symlinks) continue; if (entry.d_type == posix::DT_LNK && no_symlinks) continue;
if (entry.d_type == posix::DT_DIR && no_dirs) continue; if (entry.d_type == posix::DT_DIR && no_dirs) continue;
Path path = path::new(name, allocator)!!; Path path = path::new(name, allocator)!!;
list.append(path); list.push(path);
} }
return list; return list;
} }
module std::io::os @if(env::WIN32); 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; PathList list;
list.init_new(.allocator = allocator); list.new_init(allocator: allocator);
@pool(allocator) @pool(allocator)
{ {
@@ -41,7 +43,7 @@ fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Al
{ {
String filename = string::temp_from_wstring((WString)&find_data.cFileName)!; String filename = string::temp_from_wstring((WString)&find_data.cFileName)!;
if (filename == ".." || filename == ".") continue; if (filename == ".." || filename == ".") continue;
list.append(path::new(filename, allocator)!); list.push(path::new(filename, allocator)!);
}; };
} while (win32::findNextFileW(find, &find_data)); } while (win32::findNextFileW(find, &find_data));
return list; return list;

View File

@@ -1,9 +1,9 @@
module std::io::file::os @if(env::POSIX); module std::io::os @if(env::POSIX);
import libc; 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()); DIRPtr directory = posix::opendir(dir.as_zstr());
@@ -16,7 +16,7 @@ fn void! native_rmtree(Path dir)
{ {
String name = ((ZString)&entry.name).str_view(); String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue; if (!name || name == "." || name == "..") continue;
Path new_path = dir.tappend(name)!; Path new_path = dir.temp_append(name)!;
if (entry.d_type == posix::DT_DIR) if (entry.d_type == posix::DT_DIR)
{ {
native_rmtree(new_path)!; native_rmtree(new_path)!;
@@ -33,6 +33,7 @@ fn void! native_rmtree(Path dir)
} }
module std::io::os @if(env::WIN32); 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)
{ {
@@ -46,9 +47,9 @@ fn void! native_rmtree(Path path)
{ {
@pool() @pool()
{ {
String filename = string::new_from_wstring((WString)&find_data.cFileName, mem::temp())!; String filename = string::new_from_wstring((WString)&find_data.cFileName, allocator::temp())!;
if (filename == "." || filename == "..") continue; if (filename == "." || filename == "..") continue;
Path file_path = path.tappend(filename)!; Path file_path = path.temp_append(filename)!;
if (find_data.dwFileAttributes & win32::FILE_ATTRIBUTE_DIRECTORY) if (find_data.dwFileAttributes & win32::FILE_ATTRIBUTE_DIRECTORY)
{ {
native_rmtree(file_path)!; native_rmtree(file_path)!;

View File

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

View File

@@ -1,5 +1,6 @@
module std::io::path; module std::io::path;
import std::collections::list; 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_PATH_ENV = env::WIN32 ? PathEnv.WIN32 : PathEnv.POSIX;
const char PREFERRED_SEPARATOR_WIN32 = '\\'; const char PREFERRED_SEPARATOR_WIN32 = '\\';
@@ -14,7 +15,9 @@ fault PathResult
NO_PARENT, NO_PARENT,
} }
struct Path (Printable) def Path = PathImp;
struct PathImp (Printable)
{ {
String path_string; String path_string;
PathEnv env; PathEnv env;
@@ -26,11 +29,16 @@ enum PathEnv
POSIX POSIX
} }
fn Path! getcwd(Allocator* allocator = mem::heap()) fn Path! new_cwd(Allocator allocator = allocator::heap()) => @pool(allocator)
{
return new(os::getcwd(allocator::temp()), allocator);
}
fn Path! getcwd(Allocator allocator = allocator::heap()) @deprecated("Use new_cwd()")
{ {
@pool(allocator) @pool(allocator)
{ {
return new(os::getcwd(mem::temp()), allocator); return new(os::getcwd(allocator::temp()), allocator);
}; };
} }
@@ -38,9 +46,10 @@ 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 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 bool exists(Path path) => os::native_file_or_dir_exists(path.str_view());
fn Path! tgetcwd() => getcwd(mem::temp()) @inline; 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 void! chdir(Path path) => os::native_chdir(path) @inline;
fn Path! temp_directory(Allocator* allocator = mem::heap()) => os::native_temp_directory(allocator); 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 void! delete(Path path) => os::native_remove(path.str_view()) @inline;
macro bool is_separator(char c, PathEnv path_env = DEFAULT_PATH_ENV) macro bool is_separator(char c, PathEnv path_env = DEFAULT_PATH_ENV)
@@ -58,7 +67,17 @@ macro bool is_win32_separator(char c)
return c == '/' || c == '\\'; return c == '/' || c == '\\';
} }
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator* allocator = mem::heap()) 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())
{ {
$if $defined(os::native_ls): $if $defined(os::native_ls):
return os::native_ls(dir, no_dirs, no_symlinks, mask, allocator); return os::native_ls(dir, no_dirs, no_symlinks, mask, allocator);
@@ -74,6 +93,13 @@ enum MkdirPermissions
USER_AND_ADMIN USER_AND_ADMIN
} }
<*
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`
*>
fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL) fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
{ {
if (!path.path_string.len) return PathResult.INVALID_PATH?; if (!path.path_string.len) return PathResult.INVALID_PATH?;
@@ -89,12 +115,22 @@ fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions =
return os::native_mkdir(path, permissions); return os::native_mkdir(path, permissions);
} }
<*
Tries to delete directory, which must be empty.
@param path `The path to delete`
@return `true if there was a directory to delete, false otherwise`
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
fn bool! rmdir(Path path) fn bool! rmdir(Path path)
{ {
if (!path.path_string.len) return PathResult.INVALID_PATH?; if (!path.path_string.len) return PathResult.INVALID_PATH?;
return os::native_rmdir(path); return os::native_rmdir(path);
} }
<*
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 PathResult.INVALID_PATH?;
@@ -105,30 +141,37 @@ fn void! rmtree(Path path)
$endif $endif
} }
fn Path! new(String path, Allocator* allocator = mem::heap(), PathEnv path_env = DEFAULT_PATH_ENV) <*
Creates a new path.
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
fn Path! new(String path, Allocator allocator = allocator::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
{ {
return { normalize(path.copy(allocator), path_env), path_env }; return { normalize(path.copy(allocator), path_env), path_env };
} }
<*
Creates a new path using the temp allocator.
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
fn Path! temp_new(String path, PathEnv path_env = DEFAULT_PATH_ENV) fn Path! temp_new(String path, PathEnv path_env = DEFAULT_PATH_ENV)
{ {
return new(path, mem::temp(), path_env); return new(path, allocator::temp(), path_env);
} }
fn Path! new_win32_wstring(WString path, Allocator* allocator = mem::heap()) fn Path! new_win32_wstring(WString path, Allocator allocator = allocator::heap()) => @pool(allocator)
{ {
@pool(allocator) return path::new(string::temp_from_wstring(path)!, allocator: allocator);
{
return path::new(string::temp_from_wstring(path)!, .allocator = allocator);
};
} }
fn Path! new_windows(String path, Allocator* allocator = mem::heap()) fn Path! new_windows(String path, Allocator allocator = allocator::heap())
{ {
return new(path, allocator, WIN32); return new(path, allocator, WIN32);
} }
fn Path! new_posix(String path, Allocator* allocator = mem::heap()) fn Path! new_posix(String path, Allocator allocator = allocator::heap())
{ {
return new(path, allocator, POSIX); return new(path, allocator, POSIX);
} }
@@ -138,12 +181,17 @@ fn bool Path.equals(self, Path p2)
return self.env == p2.env && self.path_string == p2.path_string; 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(...)")
* Append the string to the current path. {
* return self.new_append(filename, allocator) @inline;
* @param [in] filename }
**/
fn Path! Path.append(self, String filename, Allocator* allocator = mem::heap()) <*
Append the string to the current path.
@param [in] filename
*>
fn Path! Path.new_append(self, String filename, Allocator allocator = allocator::heap())
{ {
if (!self.path_string.len) return new(filename, allocator, self.env)!; if (!self.path_string.len) return new(filename, allocator, self.env)!;
assert(!is_separator(self.path_string[^1], self.env)); assert(!is_separator(self.path_string[^1], self.env));
@@ -158,7 +206,9 @@ fn Path! Path.append(self, String filename, Allocator* allocator = mem::heap())
}; };
} }
fn Path! Path.tappend(self, String filename) => self.append(filename, mem::temp()); fn Path! Path.temp_append(self, String filename) => self.new_append(filename, allocator::temp());
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 Path.start_of_base_name(self) @local
{ {
@@ -166,7 +216,19 @@ fn usz Path.start_of_base_name(self) @local
if (!path_str.len) return 0; if (!path_str.len) return 0;
if (self.env == PathEnv.WIN32) if (self.env == PathEnv.WIN32)
{ {
return path_str.rindex_of_char('\\') + 1 ?? volume_name_len(path_str, self.env)!!; 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)!!;
} }
return path_str.rindex_of_char('/') + 1 ?? 0; return path_str.rindex_of_char('/') + 1 ?? 0;
} }
@@ -176,28 +238,44 @@ fn bool! Path.is_absolute(self)
String path_str = self.str_view(); String path_str = self.str_view();
if (!path_str.len) return false; if (!path_str.len) return false;
usz path_start = volume_name_len(path_str, self.env)!; usz path_start = volume_name_len(path_str, self.env)!;
if (path_start > 0 && path_str[0] == '\\') return true;
return path_start < path_str.len && is_separator(path_str[path_start], self.env); return path_start < path_str.len && is_separator(path_str[path_start], self.env);
} }
fn Path! Path.absolute(self, Allocator* allocator = mem::heap()) fn Path! Path.absolute(self, Allocator allocator = allocator::heap()) @deprecated("Use path.new_absolute()")
{
return self.new_absolute(allocator) @inline;
}
<*
@require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
*>
fn Path! Path.new_absolute(self, Allocator allocator = allocator::heap())
{ {
String path_str = self.str_view(); String path_str = self.str_view();
if (!path_str.len) path_str = "."; if (!path_str.len) return PathResult.INVALID_PATH?;
if (self.is_absolute()!) return new(path_str, allocator, self.env);
if (path_str == ".") if (path_str == ".")
{ {
String cwd = os::getcwd(mem::temp())!; @pool(allocator)
return new(cwd, allocator, self.env); {
String cwd = os::getcwd(allocator::temp())!;
return new(cwd, allocator, self.env);
};
} }
switch (self.env) $if DEFAULT_PATH_ENV == WIN32:
{ @pool(allocator)
case WIN32: {
usz path_start = volume_name_len(path_str, self.env)!; const usz BUFFER_LEN = 4096;
if (path_start > 0) return self; WString buffer = (WString)mem::temp_alloc_array(Char16, BUFFER_LEN);
case POSIX: buffer = win32::_wfullpath(buffer, path_str.to_temp_wstring()!, BUFFER_LEN);
if (path_str[0] == PREFERRED_SEPARATOR_POSIX) return self; if (!buffer) return PathResult.INVALID_PATH?;
} return { string::new_from_wstring(buffer, allocator), WIN32 };
String cwd = os::getcwd(mem::temp())!; };
return Path{ cwd, self.env }.append(path_str, allocator)!; $else
String cwd = os::getcwd(allocator::temp())!;
return Path { cwd, self.env }.new_append(path_str, allocator)!;
$endif
} }
fn String Path.basename(self) fn String Path.basename(self)
@@ -208,16 +286,40 @@ fn String Path.basename(self)
return path_str[basename_start..]; return path_str[basename_start..];
} }
fn String Path.dirname(self) fn String Path.dirname(self)
{ {
usz basename_start = self.start_of_base_name(); usz basename_start = self.start_of_base_name();
String path_str = self.path_string; String path_str = self.path_string;
if (basename_start == 0) return ""; if (basename_start == 0) return ".";
usz start = volume_name_len(path_str, self.env)!!; usz start = volume_name_len(path_str, self.env)!!;
if (basename_start <= start + 1) return path_str[:basename_start]; if (basename_start <= start + 1)
{
if (self.env == WIN32 && basename_start > start && path_str[0..1] == `\\`)
{
return path_str[:basename_start - 1];
}
return path_str[:basename_start];
}
return path_str[:basename_start - 1]; 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 '.')`
@require extension.len > 0 : `The extension cannot be empty`
@return `true if the extension matches`
*>
fn bool Path.has_extension(self, String extension)
{
String basename = self.basename();
if (basename.len <= extension.len) return false;
if (basename[^extension.len + 1] != '.') return false;
return basename[^extension.len..] == extension;
}
fn String! Path.extension(self) fn String! Path.extension(self)
{ {
String basename = self.basename(); String basename = self.basename();
@@ -248,13 +350,20 @@ fn usz! volume_name_len(String path, PathEnv path_env) @local
while (count < len && path[count] == '\\') count++; while (count < len && path[count] == '\\') count++;
// Not 2 => folded paths // Not 2 => folded paths
if (count != 2) return 0; if (count != 2) return 0;
// Check that we have a name followed by '/' // Check that we have a name followed by '\'
isz base_found = 0;
for (usz i = 2; i < len; i++) for (usz i = 2; i < len; i++)
{ {
char c = path[i]; char c = path[i];
if (is_win32_separator(c)) return i; if (is_win32_separator(c))
{
if (base_found) return i;
base_found = i;
continue;
}
if (is_reserved_win32_path_char(c)) return PathResult.INVALID_PATH?; if (is_reserved_win32_path_char(c)) return PathResult.INVALID_PATH?;
} }
if (base_found > 0 && base_found + 1 < len) return len;
return PathResult.INVALID_PATH?; return PathResult.INVALID_PATH?;
case 'A'..'Z': case 'A'..'Z':
case 'a'..'z': case 'a'..'z':
@@ -264,6 +373,13 @@ fn usz! volume_name_len(String path, PathEnv path_env) @local
} }
} }
<*
Get the path of the parent. This does not allocate, but returns a slice
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`
*>
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 PathResult.NO_PARENT?;
@@ -279,8 +395,12 @@ fn Path! Path.parent(self)
fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV) fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
{ {
if (!path_str.len) return ""; if (!path_str.len) return path_str;
usz path_start = volume_name_len(path_str, path_env)!; usz path_start = volume_name_len(path_str, path_env)!;
if (path_start > 0 && path_env == PathEnv.WIN32)
{
for (usz i = 0; i < path_start; i++) if (path_str[i] == '/') path_str[i] = '\\';
}
usz path_len = path_str.len; usz path_len = path_str.len;
if (path_start == path_len) return path_str; if (path_start == path_len) return path_str;
char path_separator = path_env == PathEnv.WIN32 ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX; char path_separator = path_env == PathEnv.WIN32 ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX;
@@ -384,7 +504,13 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
len++; len++;
} }
if (len > path_start + 1 && is_separator(path_str[len - 1], path_env)) len--; if (len > path_start + 1 && is_separator(path_str[len - 1], path_env)) len--;
path_str.ptr[len] = 0; if (path_str.len > len) path_str.ptr[len] = 0;
// Empty path after normalization -> "."
if (!len)
{
path_str[0] = '.';
return path_str[:1];
}
return path_str[:len]; return path_str[:len];
} }
@@ -395,6 +521,7 @@ fn String Path.root_directory(self)
String path_str = self.str_view(); String path_str = self.str_view();
usz len = path_str.len; usz len = path_str.len;
if (!len) return ""; if (!len) return "";
if (path_str == ".") return ".";
if (self.env == PathEnv.WIN32) if (self.env == PathEnv.WIN32)
{ {
usz root_len = volume_name_len(path_str, self.env)!!; usz root_len = volume_name_len(path_str, self.env)!!;
@@ -414,21 +541,22 @@ fn String Path.root_directory(self)
def PathWalker = fn bool! (Path, bool is_dir, void*); def PathWalker = fn bool! (Path, bool is_dir, void*);
/* <*
* Walk the path recursively. PathWalker is run on every file and Walk the path recursively. PathWalker is run on every file and
* directory found. Return true to abort the walk. directory found. Return true to abort the walk.
*/ @require self.env == DEFAULT_PATH_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; const PATH_MAX = 512;
@stack_mem(PATH_MAX; Allocator* allocator) @stack_mem(PATH_MAX; Allocator allocator)
{ {
Path abs = self.absolute(allocator)!; Path abs = self.new_absolute(allocator)!;
PathList files = ls(abs, .allocator = allocator)!; PathList files = new_ls(abs, allocator: allocator)!;
foreach (f : files) foreach (f : files)
{ {
if (f.str_view() == "." || f.str_view() == "..") continue; if (f.str_view() == "." || f.str_view() == "..") continue;
f = abs.append(f.str_view(), allocator)!; f = abs.new_append(f.str_view(), allocator)!;
bool is_directory = is_dir(f); bool is_directory = is_dir(f);
if (w(f, is_directory, data)!) return true; if (w(f, is_directory, data)!) return true;
if (is_directory && f.walk(w, data)!) return true; if (is_directory && f.walk(w, data)!) return true;
@@ -448,6 +576,11 @@ fn bool Path.has_suffix(self, String str)
return self.str_view().ends_with(str); return self.str_view().ends_with(str);
} }
fn void Path.free_with_allocator(self, Allocator allocator)
{
allocator::free(allocator, self.path_string.ptr);
}
fn void Path.free(self) fn void Path.free(self)
{ {
free(self.path_string.ptr); free(self.path_string.ptr);
@@ -459,7 +592,7 @@ fn usz! Path.to_format(&self, Formatter* formatter) @dynamic
return formatter.print(self.str_view()); return formatter.print(self.str_view());
} }
fn String Path.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic fn String Path.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{ {
return self.str_view().copy(allocator); return self.str_view().copy(allocator);
} }

View File

@@ -1,4 +1,5 @@
module std::io; module std::io;
import std::math;
interface InStream interface InStream
{ {
@@ -8,7 +9,7 @@ interface InStream
fn usz! available() @optional; fn usz! available() @optional;
fn usz! read(char[] buffer); fn usz! read(char[] buffer);
fn char! read_byte(); fn char! read_byte();
fn usz! write_to(OutStream* out) @optional; fn usz! write_to(OutStream out) @optional;
fn void! pushback_byte() @optional; fn void! pushback_byte() @optional;
} }
@@ -20,10 +21,10 @@ interface OutStream
fn void! flush() @optional; fn void! flush() @optional;
fn usz! write(char[] bytes); fn usz! write(char[] bytes);
fn void! write_byte(char c); fn void! write_byte(char c);
fn usz! read_to(InStream* in) @optional; 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.available) return s.available();
if (&s.seek) if (&s.seek)
@@ -38,36 +39,36 @@ fn usz! available(InStream* s)
macro bool @is_instream(#expr) macro bool @is_instream(#expr)
{ {
return $assignable(#expr, InStream*); return $assignable(#expr, InStream);
} }
macro bool @is_outstream(#expr) macro bool @is_outstream(#expr)
{ {
return $assignable(#expr, OutStream*); return $assignable(#expr, OutStream);
} }
/** <*
* @param [&out] ref @param [&out] ref
* @require @is_instream(stream) @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]); 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) @require @is_outstream(stream)
* @ensure return == ref.type.sizeof @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]); return write_all(stream, ((char*)ref)[:ref.type.sizeof]);
} }
/** <*
* @require @is_instream(stream) @require @is_instream(stream)
*/ *>
macro usz! read_all(stream, char[] buffer) macro usz! read_all(stream, char[] buffer)
{ {
if (buffer.len == 0) return 0; if (buffer.len == 0) return 0;
@@ -76,9 +77,25 @@ macro usz! read_all(stream, char[] buffer)
return n; return n;
} }
/** <*
* @require @is_outstream(stream) @require @is_instream(stream)
*/ *>
macro char[]! read_new_fully(stream, Allocator allocator = allocator::heap())
{
usz len = available(stream)!;
char* data = allocator::malloc_try(allocator, len)!;
defer catch allocator::free(allocator, data);
usz read = 0;
while (read < len)
{
read += stream.read(data[read:len - read])!;
}
return data[:len];
}
<*
@require @is_outstream(stream)
*>
macro usz! write_all(stream, char[] buffer) macro usz! write_all(stream, char[] buffer)
{ {
if (buffer.len == 0) return 0; if (buffer.len == 0) return 0;
@@ -87,7 +104,12 @@ macro usz! write_all(stream, char[] buffer)
return n; return n;
} }
macro usz! @read_using_read_byte(&s, char[] buffer) macro usz! @read_using_read_byte(&s, char[] buffer) @deprecated
{
return read_using_read_byte(*s, buffer);
}
macro usz! read_using_read_byte(s, char[] buffer)
{ {
usz len = 0; usz len = 0;
foreach (&cptr : buffer) foreach (&cptr : buffer)
@@ -104,17 +126,26 @@ macro usz! @read_using_read_byte(&s, char[] buffer)
return len; return len;
} }
macro void! @write_byte_using_write(&s, char c) macro void! write_byte_using_write(s, char c)
{ {
char[1] buff = { c }; char[1] buff = { c };
(*s).write(&buff)!; s.write(&buff)!;
} }
macro void! @write_byte_using_write(&s, char c) @deprecated
{
return write_byte_using_write(*s, c);
}
macro char! @read_byte_using_read(&s) macro char! @read_byte_using_read(&s) @deprecated
{
return read_byte_using_read(*s);
}
macro char! read_byte_using_read(s)
{ {
char[1] buffer; char[1] buffer;
usz read = (*s).read(&buffer)!; usz read = s.read(&buffer)!;
if (read != 1) return IoError.EOF?; if (read != 1) return IoError.EOF?;
return buffer[0]; return buffer[0];
} }
@@ -122,40 +153,44 @@ macro char! @read_byte_using_read(&s)
def ReadByteFn = fn char!(); def 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)!; foreach (c : bytes) s.write_byte(self, c)!;
return bytes.len; return bytes.len;
} }
macro void! @pushback_using_seek(&s) macro usz! @write_using_write_byte(&s, char[] bytes) @deprecated
{
return write_using_write_byte(*s, bytes);
}
macro void! pushback_using_seek(s)
{ {
s.seek(-1, CURSOR)!; s.seek(-1, CURSOR)!;
} }
fn usz! copy_to(InStream* in, OutStream* dst, char[] buffer = {}) macro void! @pushback_using_seek(&s) @deprecated
{
s.seek(-1, CURSOR)!;
}
fn usz! copy_to(InStream in, OutStream dst, char[] buffer = {})
{ {
if (buffer.len) return copy_through_buffer(in, dst, buffer); if (buffer.len) return copy_through_buffer(in, dst, buffer);
if (&in.write_to) return in.write_to(dst); if (&in.write_to) return in.write_to(dst);
if (&dst.read_to) return dst.read_to(in); if (&dst.read_to) return dst.read_to(in);
$switch (env::MEMORY_ENV) $switch (env::MEMORY_ENV)
$case NORMAL: $case NORMAL:
@pool() return copy_through_buffer(in, dst, &&char[4096]{});
{
return copy_through_buffer(in, dst, mem::temp_array(char, 4096));
};
$case SMALL: $case SMALL:
@pool() return copy_through_buffer(in, dst, &&char[1024]{});
{
return copy_through_buffer(in, dst, mem::temp_array(char, 1024));
};
$case TINY: $case TINY:
$case NONE: $case NONE:
return copy_through_buffer(in, dst, &&(char[256]{})); return copy_through_buffer(in, dst, &&(char[256]{}));
$endswitch $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; usz total_copied;
while (true) while (true)
@@ -173,12 +208,12 @@ macro usz! copy_through_buffer(InStream *in, OutStream* dst, char[] buffer) @loc
} }
} }
const char[*] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 }; const char[?] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 };
/** <*
* @require @is_instream(stream) @require @is_instream(stream)
* @require @typekind(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int() @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); var $Type = $typefrom($typeof(x_ptr).inner);
@@ -212,10 +247,10 @@ macro usz! read_varint(stream, x_ptr)
} }
return MathError.OVERFLOW?; return MathError.OVERFLOW?;
} }
/** <*
* @require @is_outstream(stream) @require @is_outstream(stream)
* @require @typekind(x).is_int() @require @typekind(x).is_int()
**/ *>
macro usz! write_varint(stream, x) macro usz! write_varint(stream, x)
{ {
var $Type = $typeof(x); var $Type = $typeof(x);
@@ -230,4 +265,205 @@ macro usz! write_varint(stream, x)
} }
buffer[i] = (char)x; buffer[i] = (char)x;
return write_all(stream, buffer[:i + 1]); return write_all(stream, buffer[:i + 1]);
}
<*
@require @is_instream(stream)
*>
macro ushort! read_be_ushort(stream)
{
char hi_byte = stream.read_byte()!;
char lo_byte = stream.read_byte()!;
return (ushort)(hi_byte << 8 | lo_byte);
}
<*
@require @is_instream(stream)
*>
macro short! read_be_short(stream)
{
return read_be_ushort(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_short(stream, ushort s)
{
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_instream(stream)
*>
macro uint! read_be_uint(stream)
{
uint val = stream.read_byte()! << 24;
val += stream.read_byte()! << 16;
val += stream.read_byte()! << 8;
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro int! read_be_int(stream)
{
return read_be_uint(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_int(stream, uint s)
{
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_instream(stream)
*>
macro ulong! read_be_ulong(stream)
{
ulong val = (ulong)stream.read_byte()! << 56;
val += (ulong)stream.read_byte()! << 48;
val += (ulong)stream.read_byte()! << 40;
val += (ulong)stream.read_byte()! << 32;
val += (ulong)stream.read_byte()! << 24;
val += (ulong)stream.read_byte()! << 16;
val += (ulong)stream.read_byte()! << 8;
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro long! read_be_long(stream)
{
return read_be_ulong(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_long(stream, ulong s)
{
stream.write_byte((char)(s >> 56))!;
stream.write_byte((char)(s >> 48))!;
stream.write_byte((char)(s >> 40))!;
stream.write_byte((char)(s >> 32))!;
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_instream(stream)
*>
macro uint128! read_be_uint128(stream)
{
uint128 val = (uint128)stream.read_byte()! << 120;
val += (uint128)stream.read_byte()! << 112;
val += (uint128)stream.read_byte()! << 104;
val += (uint128)stream.read_byte()! << 96;
val += (uint128)stream.read_byte()! << 88;
val += (uint128)stream.read_byte()! << 80;
val += (uint128)stream.read_byte()! << 72;
val += (uint128)stream.read_byte()! << 64;
val += (uint128)stream.read_byte()! << 56;
val += (uint128)stream.read_byte()! << 48;
val += (uint128)stream.read_byte()! << 40;
val += (uint128)stream.read_byte()! << 32;
val += (uint128)stream.read_byte()! << 24;
val += (uint128)stream.read_byte()! << 16;
val += (uint128)stream.read_byte()! << 8;
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro int128! read_be_int128(stream)
{
return read_be_uint128(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_int128(stream, uint128 s)
{
stream.write_byte((char)(s >> 120))!;
stream.write_byte((char)(s >> 112))!;
stream.write_byte((char)(s >> 104))!;
stream.write_byte((char)(s >> 96))!;
stream.write_byte((char)(s >> 88))!;
stream.write_byte((char)(s >> 80))!;
stream.write_byte((char)(s >> 72))!;
stream.write_byte((char)(s >> 64))!;
stream.write_byte((char)(s >> 56))!;
stream.write_byte((char)(s >> 48))!;
stream.write_byte((char)(s >> 40))!;
stream.write_byte((char)(s >> 32))!;
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_outstream(stream)
@require data.len < 256 "Data exceeded 255"
*>
macro usz! write_tiny_bytearray(stream, char[] data)
{
stream.write_byte((char)data.len)!;
return stream.write(data) + 1;
}
<*
@require @is_instream(stream)
*>
macro char[]! read_tiny_bytearray(stream, Allocator allocator)
{
int len = stream.read_byte()!;
if (!len) return {};
char[] data = allocator::alloc_array(allocator, char, len);
io::read_all(stream, data)!;
return data;
}
<*
@require @is_outstream(stream)
@require data.len < 0x1000 "Data exceeded 65535"
*>
macro usz! write_short_bytearray(stream, char[] data)
{
io::write_be_short(stream, (ushort)data.len)!;
return stream.write(data) + 2;
}
<*
@require @is_instream(stream)
*>
macro char[]! read_short_bytearray(stream, Allocator allocator)
{
int len = io::read_be_ushort(stream)!;
if (!len) return {};
char[] data = allocator::alloc_array(allocator, char, len);
io::read_all(stream, data)!;
return data;
}
<*
Wrap bytes for reading using io functions.
*>
fn ByteReader wrap_bytes(char[] bytes)
{
return { bytes, 0 };
} }

View File

@@ -2,19 +2,19 @@ module std::io;
struct ReadBuffer (InStream) struct ReadBuffer (InStream)
{ {
InStream* wrapped_stream; InStream wrapped_stream;
char[] bytes; char[] bytes;
usz read_idx; usz read_idx;
usz write_idx; usz write_idx;
} }
/** <*
* Buffer reads from a stream. Buffer reads from a stream.
* @param [inout] self @param [inout] self
* @require bytes.len > 0 @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) fn ReadBuffer* ReadBuffer.init(&self, InStream wrapped_stream, char[] bytes)
{ {
*self = { .wrapped_stream = wrapped_stream, .bytes = bytes }; *self = { .wrapped_stream = wrapped_stream, .bytes = bytes };
return self; return self;
@@ -63,18 +63,18 @@ fn void! ReadBuffer.refill(&self) @local @inline
struct WriteBuffer (OutStream) struct WriteBuffer (OutStream)
{ {
OutStream* wrapped_stream; OutStream wrapped_stream;
char[] bytes; char[] bytes;
usz index; usz index;
} }
/** <*
* Buffer writes to a stream. Call `flush` when done writing to the buffer. Buffer writes to a stream. Call `flush` when done writing to the buffer.
* @param [inout] self @param [inout] self
* @require bytes.len > 0 "Non-empty buffer required" @require bytes.len > 0 "Non-empty buffer required"
* @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 WriteBuffer* WriteBuffer.init(&self, OutStream* wrapped_stream, char[] bytes) fn WriteBuffer* WriteBuffer.init(&self, OutStream wrapped_stream, char[] bytes)
{ {
*self = { .wrapped_stream = wrapped_stream, .bytes = bytes }; *self = { .wrapped_stream = wrapped_stream, .bytes = bytes };
return self; return self;
@@ -121,13 +121,16 @@ fn usz! WriteBuffer.write(&self, char[] bytes) @dynamic
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; usz n = self.bytes.len - self.index;
if (n == 0) self.write_pending()!; if (n == 0)
self.bytes[0] = c; {
self.index = 1; self.write_pending()!;
}
self.bytes[self.index] = c;
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])!; self.index -= self.wrapped_stream.write(self.bytes[:self.index])!;
if (self.index != 0) return IoError.INCOMPLETE_WRITE?; if (self.index != 0) return IoError.INCOMPLETE_WRITE?;
} }

View File

@@ -3,7 +3,7 @@ import std::math;
struct ByteBuffer (InStream, OutStream) struct ByteBuffer (InStream, OutStream)
{ {
Allocator* allocator; Allocator allocator;
usz max_read; usz max_read;
char[] bytes; char[] bytes;
usz read_idx; usz read_idx;
@@ -11,29 +11,29 @@ struct ByteBuffer (InStream, OutStream)
bool has_last; bool has_last;
} }
/** <*
* ByteBuffer provides a streamable read/write buffer. ByteBuffer provides a streamable read/write buffer.
* max_read defines how many bytes might be kept before its internal buffer is shrinked. 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.init_new(&self, usz max_read, usz initial_capacity = 16, Allocator* allocator = mem::heap()) fn ByteBuffer* ByteBuffer.new_init(&self, usz max_read, usz initial_capacity = 16, Allocator allocator = allocator::heap())
{ {
*self = { .allocator = allocator, .max_read = max_read }; *self = { .allocator = allocator, .max_read = max_read };
initial_capacity = max(initial_capacity, 16); initial_capacity = max(initial_capacity, 16);
self.grow(initial_capacity)!; self.grow(initial_capacity);
return self; return self;
} }
fn ByteBuffer*! ByteBuffer.init_temp(&self, usz max_read, usz initial_capacity = 16) fn ByteBuffer* ByteBuffer.temp_init(&self, usz max_read, usz initial_capacity = 16)
{ {
return self.init_new(max_read, initial_capacity, mem::temp()); return self.new_init(max_read, initial_capacity, allocator::temp());
} }
/** <*
* @require buf.len > 0 @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 }; *self = { .max_read = buf.len, .bytes = buf };
return self; return self;
@@ -41,14 +41,14 @@ fn ByteBuffer*! ByteBuffer.init_with_buffer(&self, char[] buf)
fn void ByteBuffer.free(&self) fn void ByteBuffer.free(&self)
{ {
if (self.allocator) self.allocator.free(self.bytes); if (self.allocator) allocator::free(self.allocator, self.bytes);
*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; 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.bytes[self.write_idx:bytes.len] = bytes[..];
self.write_idx += bytes.len; self.write_idx += bytes.len;
return bytes.len; return bytes.len;
@@ -57,7 +57,7 @@ fn usz! ByteBuffer.write(&self, char[] bytes) @dynamic
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; 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.bytes[self.write_idx] = c;
self.write_idx++; self.write_idx++;
} }
@@ -93,9 +93,9 @@ fn char! ByteBuffer.read_byte(&self) @dynamic
return c; return c;
} }
/* <*
* Only the last byte of a successful read can be pushed back. 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 IoError.EOF?;
@@ -128,10 +128,10 @@ fn usz! ByteBuffer.available(&self) @inline @dynamic
return self.write_idx - self.read_idx; 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); n = math::next_power_of_2(n);
char* p = self.allocator.realloc_aligned(self.bytes, n, .alignment = char.alignof)!; char* p = allocator::realloc(self.allocator, self.bytes, n);
self.bytes = p[:n]; self.bytes = p[:n];
} }

View File

@@ -53,7 +53,7 @@ fn usz! ByteReader.seek(&self, isz offset, Seek seek) @dynamic
return 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; if (self.index >= self.bytes.len) return 0;
usz written = writer.write(self.bytes[self.index..])!; usz written = writer.write(self.bytes[self.index..])!;

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