Compare commits

..

222 Commits

Author SHA1 Message Date
Christoffer Lerno
fe6817f90d Update frontpage version. 2025-10-03 18:42:32 +02:00
Christoffer Lerno
98a72007f8 Releasenotes fixup 2025-10-03 18:41:11 +02:00
Christoffer Lerno
87c1e09a7a Compiler segfault when splatting variable that does not exist in untyped vaarg macro #2509 2025-10-03 14:08:19 +02:00
Christoffer Lerno
e0fbe31f00 Update release versions 2025-10-02 21:27:35 +02:00
Christoffer Lerno
7d6c844b99 Dead code analysis with labelled if did not work properly. 2025-10-02 21:24:05 +02:00
Christoffer Lerno
a03446a26d - Fix lambda-in-macro visibility, where lambdas would sometimes not correctly link if used through a macro. 2025-10-01 21:05:49 +02:00
Christoffer Lerno
a7e77fec78 $for int $a = 1; $a < 2; $a++ would not parse. 2025-10-01 14:53:52 +02:00
Christoffer Lerno
05c3fa1afd Update CI 2025-10-01 09:43:20 +02:00
Christoffer Lerno
30c8435669 Remove prerelease 2025-10-01 09:35:38 +02:00
Christoffer Lerno
94497c968b - Prevent foo.bar = {} when bar is a flexible array member. #2497
- Fix several issues relating to multi-level inference like `int[*][*]` #2505
2025-09-30 23:43:20 +02:00
Christoffer Lerno
281d4af464 Update contact information for CoC 2025-09-29 13:57:01 +02:00
Christoffer Lerno
cb2d0e798e Prevent foo.bar = {} when bar is a flexible array member. 2025-09-29 01:59:38 +02:00
Christoffer Lerno
da67cd4eb0 Assert when the binary doesn't get created and --run-once is used. #2502 2025-09-29 00:16:26 +02:00
Christoffer Lerno
7d06ca6d35 Crash during codegen when taking the typeid of an empty enum with associated values. 2025-09-29 00:01:20 +02:00
Christoffer Lerno
6d45450130 Renaming, and further refactoring. 2025-09-28 15:33:17 +02:00
Christoffer Lerno
27bbeaf79c Remove use of CheckType to simplify expression handling. 2025-09-28 11:41:32 +02:00
Christoffer Lerno
3af5a537da Cleanup around expressions. 2025-09-27 16:37:56 +02:00
Christoffer Lerno
6287e8dfbf Restore some out checking. 2025-09-26 21:19:38 +02:00
konimarti
1f49a5448e Add AES algorithm (#2496)
* crypto: add AES algorithm

* Some updates to the API

* Silence test.

* Fixed stdlib tests

* Some cleanup. Comments. Make internal methods functions.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-09-26 16:59:30 +02:00
m0tholith
ece4a2b6fb Make StderrLogger print file and line if FULL_LOG (#2500)
* Make StderrLogger print file and line if `FULL_LOG`

* Avoid inlining a lot of code by using a macro wrapper. Fix test.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-09-26 16:58:29 +02:00
Christoffer Lerno
e68bd0c57f Update test. 2025-09-25 14:48:00 +02:00
Christoffer Lerno
eaeafb7299 Issue not correctly aborting compilation on recursive generics. 2025-09-25 14:48:00 +02:00
Christoffer Lerno
44d736a537 Add +++= operator. 2025-09-25 14:48:00 +02:00
Christoffer Lerno
122dbb3668 Compiler assert with typed macro vaargs accessing a macro passed as vaarg #2498 2025-09-25 14:13:06 +02:00
Christoffer Lerno
c2abbe2e2f Loosen generic resolution. 2025-09-24 00:12:58 +02:00
Christoffer Lerno
3ccabd625c Renaming 2025-09-22 23:08:11 +02:00
Christoffer Lerno
cfe6534c15 Add shebang test. 2025-09-20 22:55:08 +02:00
Christoffer Lerno
f5090eb158 Support #! as a comment on the first line only. 2025-09-20 21:10:19 +02:00
Christoffer Lerno
d3db91536c Incorrect nameof on nested struct names. #2492 2025-09-20 15:00:44 +02:00
Arnaud Moura
9c42919e5a Install scripts (#2393)
* Add scripts to install c3
* Install script support debian binaries
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-09-19 22:24:05 +02:00
Book-reader
a6d33ec4af Update stdlib to use struct member docs from #2427 and other small changes (#2473)
* Doc comment improvements

* update `compression/qoi.c3` to use const enums

* revert sweeping doc comment changes that impacted readability for now

* Some tweaks.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-09-19 18:41:32 +02:00
Christoffer Lerno
b03ae8bb17 Alias and distinct types didn't check the underlying type wasn't compile time or optional. 2025-09-19 18:05:29 +02:00
Christoffer Lerno
59fd777198 Add exec timings to -vv output #2490 2025-09-19 17:20:56 +02:00
Christoffer Lerno
d8286fa2a5 Add 'loop-vectorize', 'slp-vectorize', 'unroll-loops' and 'merge-functions' optimization flags #2491. 2025-09-19 13:34:06 +02:00
Christoffer Lerno
3345e70c63 Comparing slices and arrays of user-defined types that implement == operator now works #2486. 2025-09-19 11:21:29 +02:00
BWindey
12eea4a98d [STDLIB] Add macro return types (#2487)
* add return types to macros where applicable
* std::time::clock::now() -> clock::now()
2025-09-18 14:06:58 +02:00
Christoffer Lerno
fdc20dc642 Taking .ordinal from an enum passed by pointer and then taking the address of this result would return the enum, not int. 2025-09-18 14:04:49 +02:00
Christoffer Lerno
c5e3a1b2da - Compiler segfault for invalid e-mails in project.json. #2488
- `env::PROJECT_VERSION` now returns the version in project.json.
2025-09-18 10:58:15 +02:00
Christoffer Lerno
35270fb0bf Fix unnecessary namespacing. 2025-09-18 00:16:08 +02:00
Christoffer Lerno
d782dad149 Fix compile time format check when the formatting string is a constant slice. 2025-09-17 14:31:00 +02:00
Christoffer Lerno
92aefb15f8 Generic inference (#2475)
* Change generic symbol resolution.
* Infer generic parameters lhs -> rhs: `List{int} x = list::NOHEAP`.
* Regression: Compiler segfault when assigning struct literal with too few members #2483
2025-09-16 18:05:21 +02:00
Christoffer Lerno
8342ac80d3 $alignof, $offsetof and $nameof can now be used in $defined. 2025-09-16 15:36:37 +02:00
Christoffer Lerno
c71444e7a0 Compile time switch over type would not correctly compare function pointer types. 2025-09-16 14:10:28 +02:00
Christian Brendlin
06e10bb69f Add Gentoo installation instructions to README
Added Gentoo installation instructions for c3c.
2025-09-15 14:38:23 +02:00
Velikiy Kirill
fabd96552f Implement write_to_stdin() in std::os::process (#2482)
* SubProcess: Add write_to_stdin

* SubProcess: Change unit test for windows support
2025-09-15 13:41:35 +02:00
m0tholith
2e99ae5ab9 For c3c run and friends, pass SIGINT to child process on Linux (#2480) 2025-09-13 18:51:33 +02:00
Christoffer Lerno
8fea6ee8ab Compiler segfault when modifying variable using an inline assembly block inside defer #2450. 2025-09-12 20:01:28 +02:00
Christoffer Lerno
e6b10ee00c Stack object size limit error on a static object. #2476 2025-09-12 17:11:25 +02:00
Christoffer Lerno
6aff6d66de Fix missing bitstruct member <* *> compatibility. 2025-09-10 23:39:04 +02:00
Christoffer Lerno
8035991ac3 ?? with void results on both sides cause a compiler crash #2472 2025-09-10 10:47:14 +02:00
Jonathan Nilsson
c0bd14cee7 Find cl and set INCLUDE env var outside of a visual studio command promt (#2467)
* Added search for cl

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-09-09 22:38:40 +02:00
Christoffer Lerno
3ba0beee96 Support for SysV for CVaList 2025-09-09 13:41:26 +02:00
Christoffer Lerno
8e6535f13c Fix of last checkin 2025-09-09 01:06:18 +02:00
Christoffer Lerno
0d8f9520e9 CVaList support on MacOS aarch64. 2025-09-09 01:05:20 +02:00
Christoffer Lerno
3caaf0a3e8 Compiler hang with unaligned load-store pair. #2470 2025-09-09 00:07:45 +02:00
Christoffer Lerno
a2206f1bcd int val = some_int + some_distinct_inline_int errors that int cannot be cast to DistinctInt #2468 2025-09-08 10:21:47 +02:00
Christoffer Lerno
7b5277d52c Any register allowed in X86_64 inline asm address. #2463 2025-09-07 00:03:49 +02:00
Christoffer Lerno
9f55a74d2e Remove use of find_len and len_from_list. Rename lenof to lengthof 2025-09-06 18:35:03 +02:00
Christoffer Lerno
3eb8f68ded - Add lenof() compile time function #2439
- Fix release notes
2025-09-06 18:17:17 +02:00
Christoffer Lerno
bd9bc118db Allow doc comments on individual struct members, faultdefs and enum values #2427. 2025-09-06 16:18:33 +02:00
Christoffer Lerno
95375a2591 Overloading &[] should be enough for foreach. #2466 2025-09-06 15:18:26 +02:00
Christoffer Lerno
b7115e9c70 Correctly silence "unsupported architecture" warning with --quiet #2465 2025-09-06 12:09:31 +02:00
Zack Puhl
078d9dc0b7 Add LinkedList Operators and Update Tests (#2438)
* Add LinkedList Operators and Update Tests
* add linkedlist printing and `@new` macros (single-line init and pool-capable)
* add linkedlist node and reg iterator; comparisons w/ ==
* Fix benchmarks. Drop random access to the linked list using []. Only return a direct array view.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-09-06 11:57:21 +02:00
Book-reader
79c0c8e082 Build linux binaries for releases with proper curl support (#2464)
* Build linux binaries with proper curl support

* Move vendor-fetch tests to a more appropriate location
2025-09-06 11:00:07 +02:00
Christoffer Lerno
69b3263a00 - Added path::home_directory, path::documents_directory, path::videos_directory, path::pictures_directory, path::desktop_directory, path::screenshots_directory,
`path::public_share_directory`, `path::templates_directory`, `path::saved_games_directory`, `path::music_directory`, `path::downloads_directory`.
  Fix codegen bug in expressions like `foo(x()) ?? io::EOF?` causing irregular crashes.
2025-09-06 02:27:10 +02:00
dimapaloskin
cbd415881b Support .m files in expand_csources 2025-09-06 01:48:20 +02:00
LowByteFox
6dbd81a6f9 add ability for TinyCC to compile c3c (#2459)
* add ability for TinyCC to compile c3c

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-09-06 01:47:48 +02:00
Christoffer Lerno
e605a21fd3 Revert "Revert 0.7.6 code for 0.7.5 re-release"
This reverts commit d1349c9cfb.
2025-09-05 23:30:35 +02:00
Christoffer Lerno
d1349c9cfb Revert 0.7.6 code for 0.7.5 re-release 2025-09-05 18:42:54 +02:00
Christoffer Lerno
c375aef9a3 Updates to grammar 2025-09-04 20:18:48 +02:00
Book-reader
3c1f692d49 Make enum Cc inline so it can index Termios.c_cc (#2448) 2025-09-04 11:41:06 +02:00
Christoffer Lerno
29e20ee1be - Inlining location when accessing #foo symbols.
- Improve inlined-at when checking generic code.
2025-09-04 11:39:48 +02:00
niedlich
cf14787552 Typo fixes (#2457)
* fix typos in comments and strings
* fix typos in symbols (and some comments/strings)
* fix typos in releasenotes.md

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-09-04 01:07:07 +02:00
Book-reader
10241df23c Add generic InterfaceList type for storing values that implement a specific interface (#2433)
* Add generic InterfaceList type
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-09-03 22:58:27 +02:00
Christoffer Lerno
8795ffc4f1 Returning pointer to index of slice stored in a struct from method taking self incorrectly detected as returning pointer to local variable #2455. 2025-09-03 01:02:25 +02:00
Christoffer Lerno
e25812a071 $defined(x[0] = val) causes an error instead of returning false when a type does not have []= defined #2454 2025-09-03 00:08:43 +02:00
Christoffer Lerno
14a929588a Confusing error message when type has [] overloaded but not []= #2453 2025-09-02 23:56:15 +02:00
Christoffer Lerno
02d1486af9 - Compiler assert with var x @noinit = 0 #2452 2025-09-02 23:39:00 +02:00
Christoffer Lerno
bab317282c Update version to 0.7.6 2025-09-01 16:21:48 +02:00
Christoffer Lerno
a3a6319bcf Fix tests 2025-09-01 13:32:40 +02:00
Christoffer Lerno
17dfbb377e Fixed test. 2025-09-01 13:05:14 +02:00
Christoffer Lerno
ff39f14dd1 $defined(Foo x = $vaexpr[0]) works correctly. 2025-09-01 12:09:14 +02:00
Christoffer Lerno
af4309b286 $defined returns an error when assigning a struct initializer with an incorrect type #2449 2025-09-01 10:44:19 +02:00
Christoffer Lerno
176fb47c23 Update to 0.7.5 release candidate. 2025-09-01 02:11:08 +02:00
Christoffer Lerno
3a69c9f1fe Fix test. 2025-09-01 01:18:17 +02:00
Christoffer Lerno
944cc00d34 Fix regression on splat. 2025-09-01 01:14:39 +02:00
Christoffer Lerno
a751177a3e Const enum methods are not being recognized. #2445 2025-08-31 23:56:48 +02:00
Christoffer Lerno
d291a40f69 Compiler hangs on == overload if other is generic #2443
Fix missing end of line when encountering errors in project creation.
2025-08-31 23:40:55 +02:00
Christoffer Lerno
cb006dd715 - Compiler module-scope pointer to slice with offset, causes assert. #2446 2025-08-31 23:18:27 +02:00
Christoffer Lerno
c7f09f2879 Disambiguate types when they have the same name and need cast between each other. 2025-08-31 15:16:52 +02:00
Christoffer Lerno
c0387221af Fix regression where files were added more than once. #2442 2025-08-31 12:07:10 +02:00
Christoffer Lerno
0c7c5fbd7b Update MAP_ANONYMOUS for Linux 2025-08-31 00:27:01 +02:00
Christoffer Lerno
fafcf3d0a9 Enum inference, like Foo x = $eval("A"), now works correctly for $eval. 2025-08-30 15:24:11 +02:00
Christoffer Lerno
b757f1447b Fix incorrect priority filtering. Move ??? to features. 2025-08-30 13:22:10 +02:00
LowByteFox
bc3d9d761f fix buffer overflow when deallocating an object and implement asan poisoning for unused memory 2025-08-30 11:31:35 +02:00
Christoffer Lerno
10bc68fb39 Fixed regression with optional argument macros and lambdas. 2025-08-30 01:00:24 +02:00
Christoffer Lerno
de8aed9d96 Enable $defined((void)#hash) 2025-08-29 23:24:32 +02:00
Christoffer Lerno
1080303768 Using ... to expand elements. 2025-08-29 16:30:28 +02:00
Christoffer Lerno
0503e15e31 Fix error message for $case after $switch 2025-08-29 14:42:07 +02:00
Christoffer Lerno
ca2fabc9f9 - $defined(#hash) will not check the internal expression, just that #hash exists.
- Added optional macro arguments using `macro foo(int x = ...)` which can be checked using `$defined(x)`.
- Supplemental `roundeven` has a normal implementation.
2025-08-29 11:23:39 +02:00
Christoffer Lerno
0178a44b3c Add some initial ability to compile for a limited set of backends because of narrow-minded LLVM maintainers. 640 kb is good enough for anybody. 2025-08-28 20:38:14 +02:00
Christoffer Lerno
8f3cb9c6e9 Add more comments to mem functions. 2025-08-28 18:23:01 +02:00
Christoffer Lerno
c339278ff7 String.bformat has reduced overhead. 2025-08-28 12:12:22 +02:00
Christoffer Lerno
47316dac59 Add compile time ternary $val ??? <expr> : <expr>. 2025-08-28 01:56:05 +02:00
Christoffer Lerno
90d3f429aa - @test/@benchmark on module would attach to interface and regular methods. 2025-08-28 00:28:32 +02:00
Christoffer Lerno
239d249f01 - Added $kindof compile time function.
- Deprecated `@typekind` macro in favour of `$kindof`.
- Deprecated `@typeis` macro in favour of `$typeof(#foo) == int`.
2025-08-27 20:38:12 +02:00
Christoffer Lerno
7312c10b9e - @is_const is deprecated in favour of directly using $defined.
- `@is_lvalue(#value)` is deprecated in favour of directly using `$defined`.
2025-08-27 18:21:55 +02:00
Christoffer Lerno
3c6e6f1965 Make log and exp no-strip. 2025-08-27 14:41:19 +02:00
Christoffer Lerno
28b9be64ee Update error message for missing body after if/for/etc #2289. 2025-08-27 12:37:01 +02:00
Christoffer Lerno
d2cae909e1 A file with an inferred module may not contain additional other modules. 2025-08-27 11:42:53 +02:00
Christoffer Lerno
e194081e21 Fix test. 2025-08-27 10:45:59 +02:00
Christoffer Lerno
04cc34f12e Fix correct ? after optional function name when reporting type errors. 2025-08-27 09:33:25 +02:00
Christoffer Lerno
f7143c1852 Fix tests, reenable LLVM 22 2025-08-26 20:55:58 +02:00
Christoffer Lerno
1781e97f02 Update jumptable codegen. 2025-08-26 20:34:44 +02:00
Christoffer Lerno
c17cb7d0ca Fix alignment on jump table. 2025-08-26 20:09:04 +02:00
Christoffer Lerno
21343baa75 Update for LLVM 21 (#2435)
Support LLVM 21
2025-08-26 17:29:39 +02:00
Christoffer Lerno
58c59361ea - Add linklib-dir to c3l-libraries to place their linked libraries in. Defaults to linked-libs
- If the `os-arch` linked library doesn't exist, try with `os` for c3l libs.
2025-08-26 15:21:45 +02:00
Christoffer Lerno
cb17cfff7d Deprecation of @assignable_to 2025-08-26 13:21:42 +02:00
Christoffer Lerno
1634217fc4 Grabbing (missing) methods on function pointers would cause crash #2434. 2025-08-25 16:39:17 +02:00
Christoffer Lerno
bc9b0900a5 - Inlining a const as an lvalue would take the wrong path and corrupt the expression node. 2025-08-25 15:26:47 +02:00
Christian Brendlin
f43a7540c5 clarify directory creation error messages (#2388)
* Improve error messages for project creation
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-25 14:56:59 +02:00
Zack Puhl
410a25f334 Add array @reduce, @filter, @any, @all, & @indices_of (#2419)
* wip: array reduce and any/all

* wip: continue adding more functional-like array module functions

* wip: documentation and other fixes

* finish unit tests, require INDEX variable in lambdas, ready for first review

* don't worry about iterating by ref (it wasn't passing it that way anyhow)

* update release notes again

* simplify, remove "summary operators" and related tests

* Use more $defined, remove "has_operator". Fix regression in `$defined(var $f = 123)`

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-25 14:47:08 +02:00
Zack Puhl
35c04cdc36 Add form-feed and vertical tab to trim defaults (#2407)
* Add form-feed and vertical tab to` trim` defaults

* add some initial string-based benchmarking

* update to non-const string

* do not account for mem times in bench

* misc bench fixes to repair reporting times; improve trim tests

* ok last one for real..remove (void) casts

* finally, swap to more efficient default whitespace order in `trim`
2025-08-25 14:23:14 +02:00
Christoffer Lerno
3e641ab82b Properly add "inlined at" for generic instantiation errors #2382. 2025-08-25 13:31:56 +02:00
Christoffer Lerno
7972397c65 Incorrect name on wincrt, likely a regression. 2025-08-24 13:54:52 +02:00
Christoffer Lerno
9bf933ae31 - has_tagof on tagged lambdas returns false #2432 2025-08-23 23:41:32 +02:00
Christoffer Lerno
a69ee59b82 Fix tests. 2025-08-23 22:49:33 +02:00
Christoffer Lerno
961aa0ef61 Struct and typedef subtypes inherit dynamic functions. 2025-08-23 22:31:29 +02:00
Christoffer Lerno
48318c3ad4 Fixed tests. 2025-08-23 19:43:01 +02:00
Christoffer Lerno
a004cd3d03 Lambdas on the top level were not exported by default. #2428 2025-08-23 19:40:18 +02:00
Christoffer Lerno
768ce6092d @tag was not allowed to repeat. 2025-08-23 18:42:57 +02:00
Christoffer Lerno
e4e499edd2 Allow $defined take declarations: $defined(int x = y)
Taking the address of a label would cause a crash. #2430
2025-08-23 12:00:17 +02:00
Christoffer Lerno
f36e9fea48 Types converts to typeid implicitly. 2025-08-22 00:26:18 +02:00
Christoffer Lerno
d5eec296a0 Some cleanup. 2025-08-21 17:06:06 +02:00
Christoffer Lerno
e2e2ca1d7f Add @safeinfer to allow var to be used locally. 2025-08-21 12:39:08 +02:00
Christoffer Lerno
6ab7198f2f - Added AsciiCharset for matching ascii characters quickly.
- Added `String.trim_charset`.
2025-08-21 02:57:17 +02:00
Christoffer Lerno
2e1f7c95ce Cleanup. 2025-08-21 01:46:41 +02:00
Christoffer Lerno
a2ef63f5b6 Error if a stack allocated variable is too big (configurable with --max-stack-object-size). 2025-08-21 00:33:56 +02:00
vssukharev
b7c9a4e2e9 Several fixes for the compiler (#2422)
* Update .gitignore for nix

* Fix build failure when compiling with -Werror=maybe-uninitialized

* Fix test failure formatting

* Add separateDebugInfo to c3c Nix build
2025-08-20 21:47:22 +02:00
Christoffer Lerno
28ffb864a3 Deprecated PollSubscribes and PollEvents in favour of PollSubscribe and PollEvent and made them const enums. 2025-08-19 12:20:17 +02:00
Book-reader
18b4ce4e7d fix Socket.get_option calling setsockopt instead of getsockopt (#2421)
* fix Socket.get_option

* `Socket.get_option` didn't properly call `getsockopt`, and `getsockopt` had an invalid signature.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-19 11:36:18 +02:00
Christoffer Lerno
551ce34b9b - foo[x][y] = b now interpreted as (*&foo[x])[y] = b which allows overloads to do chained [] accesses. 2025-08-19 01:57:51 +02:00
Christoffer Lerno
de09a19a48 - Incorrect type checking when &[] and [] return optional values.
- Failed to find subscript overloading on optional values.
- Added `&[]` overload to HashMap.
2025-08-19 00:41:40 +02:00
Manu Linares
ba55946c9a Fixes slicing with negative value error message - llvm_codegen_expr.c (#2410)
* Fixes slicing with negative value error message - llvm_codegen_expr.c

Fixes printing negative value

Example:
```
fn void main()
{
    int[100] arr;
    usz a = 35;
    usz b = 36;
    arr[b:a - b];
}
```

output should be:
ERROR: 'Negative value (-1) given for slice length.'

* Update releasenotes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-18 23:12:03 +02:00
Christian Brendlin
33ab18033a #2391: Improve missing $endif error message (#2413)
* Improve error message missing

* remove temp comment

* Update releasenotes.md

* Use sema_note to print the "$if" line

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-18 23:09:07 +02:00
Christian Brendlin
96127d4ff3 Fix crash when exporting functions with const enums #2384 (#2414)
* headers: handle const enums in exported signatures as base type

* update releasenotes.md

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-18 22:49:42 +02:00
Alessandro Mauri
43163fe2a0 Fix out of bounds access when stdout is empty 2025-08-18 22:31:46 +02:00
Christoffer Lerno
a52b30c951 Update releasenotes for fix, which was the previous commit for fixing "Compiler assert when calling unassigned CT functions #2418". Fail :( 2025-08-18 21:29:36 +02:00
Christoffer Lerno
7d6a864d56 Miscompilation of do-while when the while starts with a branch #2394. Also: change do-while to make the lowering follow the execution. 2025-08-18 21:28:19 +02:00
Christoffer Lerno
eeab73df4e Miscompilation of do-while when the while starts with a branch #2394. Also: change do-while to make the lowering follow the execution. 2025-08-18 21:04:52 +02:00
Christoffer Lerno
db45abdfc7 Update releasenotes for #2398 fix. 2025-08-18 13:09:24 +02:00
vssukharev
cb32441533 Temporary fix for #2398 (#2415)
* Temporary fix for #2398

* Update sema_liveness.c

Remove comment

---------

Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
2025-08-18 13:06:23 +02:00
Christoffer Lerno
643aa47e99 Compiler segfault with struct containing list of structs with an inline member #2416 2025-08-18 12:19:14 +02:00
Christoffer Lerno
5e1bf75621 Updated release notes 2025-08-18 12:03:05 +02:00
Christoffer Lerno
7c8e3dd4fd Fix max module name to 31 chars and the entire module path to 63 characters. 2025-08-18 12:02:00 +02:00
Zack Puhl
ad02fad167 Add Freestanding OS Types (#2399)
* Add Freestanding OS Types to `env::`
2025-08-16 19:47:03 +02:00
Sander van den Bosch
261184b5c1 Add HashSet and String methods (#2386)
* Add `String.contains_char` using `String.index_of_char` and `HashSet.values` together with `HashSet.tvalues`

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-16 16:57:31 +02:00
Manu Linares
d07da2804e Update sema_decls.c
Fix compilation error

```
[ 48%] Building C object CMakeFiles/c3c.dir/src/compiler/sema_passes.c.o
/home/mb/.cache/yay/c3c-git/src/c3c/src/compiler/sema_decls.c: In function ‘sema_analyse_operator_method’:
/home/mb/.cache/yay/c3c-git/src/c3c/src/compiler/sema_decls.c:2312:25: error: suggest parentheses around assignment used as truth value [-Werror=parentheses]
 2312 |                         is_wildcard = method->func_decl.is_wildcard_overload = true;
      |                         ^~~~~~~~~~~
```
2025-08-16 16:47:59 +02:00
Zack Puhl
702b63ddb7 Add array::zip and Related Macros (#2370)
* zip / zip_into
* Deprecate `add_array` in favour of `push_all` on lists.
* Add support for generic lists for zip.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-16 13:30:24 +02:00
konimarti
e35dbd29fb string: use correct allocator in replace (#2405)
`replace` accepts an Allocator but uses `mem` instead.
2025-08-16 03:41:59 +02:00
Christoffer Lerno
b52ab886d2 Compiler segfault on global slice initialization with null[:0] #2404. 2025-08-15 20:10:28 +02:00
Christoffer Lerno
4b95d6be4c New method resolution. 2025-08-15 19:48:08 +02:00
Christoffer Lerno
34b0b6f8f9 Fix for bug when @format encountered * in some cases. 2025-08-15 08:26:43 +02:00
Christoffer Lerno
4fea202e6d Added libloaderapi to std::os::win32 2025-08-14 22:11:20 +02:00
Zack Puhl
8cfdb76869 Add benchmarkrun and *.wasm to .gitignore (#2400)
* Add `benchmarkrun` and `*.wasm` to .gitignore
* add .s assembly files too
2025-08-14 20:49:18 +02:00
Christoffer Lerno
858f8d2405 Updating license docs further. 2025-08-14 20:42:07 +02:00
Christoffer Lerno
67aa18c1aa Clarifying license. 2025-08-14 20:40:30 +02:00
Christoffer Lerno
31b15c775e Slicing a constant array with designated initialization would not update the indexes. 2025-08-14 20:27:23 +02:00
Zack Puhl
bf7e7e2397 Fix Benchmark Progress Printing (#2401) 2025-08-14 20:24:37 +02:00
Zack Puhl
eb8fb8871f Fix $$str_hash to use a5hash like String.hash() (#2403)
* Fix `$$str_hash` to use `a5hash` like `String.hash()`
2025-08-14 20:24:01 +02:00
Christoffer Lerno
85dc9c45ab - Deprecate @compact use for comparison. Old behaviour is enabled using --use-old-compact-eq.
- Switch available for types implementing `@operator(==)`.
- `Type.is_eq` is now true for types with `==` overload.
- Functions being tested for overload are now always checked before test.
- Compile time indexing at compile time in a $typeof was no considered compile time.
2025-08-14 15:53:35 +02:00
Book-reader
076ef187cb improve & fix libc termios bindings (#2372)
* improve & fix libc termios bindings
2025-08-13 17:24:41 +02:00
Zack Puhl
c8d39251a9 Add compile-time @min and @max (#2378)
* Add compile-time `@min` and `@max`
2025-08-13 17:23:24 +02:00
Christoffer Lerno
f5e6b697b8 Make @try maydiscard. 2025-08-13 13:31:29 +02:00
Christoffer Lerno
e8e88c1920 Merge branch 'dev' 2025-08-13 13:30:47 +02:00
Christoffer Lerno
82a58e1c66 Update libc memcpy. 2025-08-13 13:29:56 +02:00
Velikiy Kirill
8e8d0436ad Add epoll bindings to std::os::linux + misc (#2350)
* Add epoll to std::os::linux + misc

* Fix import in linux.c3

* epoll: Add unit tests

* epoll: Fix imports in unit tests

* epoll: Add libc import
2025-08-13 02:41:09 +02:00
Christoffer Lerno
db99de9717 Improve codegen for stack allocated large non-zero arrays. 2025-08-12 17:01:39 +02:00
tonis2
fd9fbe26a1 JSON parser trailing issue fix 2025-08-12 16:19:41 +02:00
Christoffer Lerno
582453cb45 Assert triggered when trying to slice a struct. 2025-08-12 00:01:55 +02:00
Zack Puhl
b73a44ec7d Add/Fix OpenBSD native_cpus (#2387)
* Add OpenBSD `native_cpus`

* update release notes

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-11 23:10:09 +02:00
Christoffer Lerno
be98a01ed8 types::has_equals fails with assert for bitstructs #2377 2025-08-11 20:53:11 +02:00
Christoffer Lerno
625a6d987d Refactoring 2025-08-11 20:53:11 +02:00
vssukharev
2597f6217e Several fixes for Nix build (#2389)
* Fixed a bug for build via nix, when c3c-checks and c3c-debug didn't have a git hash

* Got rid of python3 requirement for checks in nix as it's no longer needed

* Now c3c properly displays it's build date in nix build

* Add more platforms for checks in nix build

* Add installation and compilation on Nix to README
2025-08-11 19:21:41 +02:00
sipekdan
0470f3be8e Fix typo in bf16 suffix parsing logic 2025-08-11 19:20:19 +02:00
Christoffer Lerno
1d25197bfd Bitstructs no longer overloadable with bitops. #2374 2025-08-06 14:51:37 +02:00
Zack Puhl
aae873c044 Add bitsizeof to Builtins (#2376)
* Add `bitsizeof` to Builtins

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-06 13:55:39 +02:00
Zack Puhl
6471728ee5 Add @clz CT macro (#2367)
* Add `@clz` CT macro
2025-08-06 13:42:52 +02:00
Christoffer Lerno
29bae1fbd6 Updated like the PR #2375 2025-08-06 11:28:36 +02:00
Christoffer Lerno
9c770f360e Formatter did not properly handle "null" for any, and null for empty faults. #2375 2025-08-06 11:23:56 +02:00
Christoffer Lerno
3b6d68ef21 Compiler segfault when using bitwise not on number literal cast to bitstruct #2373. 2025-08-06 00:55:56 +02:00
Christoffer Lerno
24c03f9800 Fixed bug generating $c += 1 when $c was derived from a pointer but behind a cast. 2025-08-06 00:44:22 +02:00
Christoffer Lerno
ed61b51489 Adding MultiLogger 2025-08-05 18:49:41 +02:00
Christoffer Lerno
0205ee8688 Added the std::core::log for logging. 2025-08-05 18:30:46 +02:00
Christoffer Lerno
abd3585c44 Parsing difference between "0x00." and "0X00." literals #2371 2025-08-05 13:09:53 +02:00
Christoffer Lerno
aa910a1c44 Compiler assert when using generic parameters list without any parameters. #2369 2025-08-05 12:50:36 +02:00
Christoffer Lerno
00b88a8027 Compiler assertion when defining a function with return type untyped_list #2368. In general, improve error message when ct types are return types. 2025-08-05 04:11:54 +02:00
Christoffer Lerno
229fdd6193 Detect recursive creation of generics #2366. 2025-08-05 02:55:32 +02:00
Christoffer Lerno
5292e08cd6 Remove lambda code that should never happen. 2025-08-04 22:13:37 +02:00
Christoffer Lerno
90990ed2f3 Support partial const enums despite use-old-enums enabled. 2025-08-04 17:06:17 +02:00
Christoffer Lerno
c99284103d Fix issue with naked functions #2365 2025-08-04 15:51:49 +02:00
sudokit
b463358add Added dl_iterate_phdr 2025-08-04 14:56:51 +02:00
LowByteFox
0e10b71cbf Stdlib: SingleSizeObjectPool implementation (#2360)
* implement working single size object pool
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-04 14:54:26 +02:00
Christoffer Lerno
cb2d8133e0 Fixed: regression in comments for @deprecated and @pure. 2025-08-04 13:25:30 +02:00
Christoffer Lerno
f2d27229d2 Bug causing a compiler error when parsing a broken lambda inside of an expression. 2025-08-04 12:25:19 +02:00
Zack Puhl
604661b12c Increase Primitive Type Hash Speeds (#2329)
* simplify and add much faster hash functions in key locations
* add benchmark runtime @start and @end macros for better control
* update benchmark reporting and hashmap tests

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-04 11:50:17 +02:00
hamkoroke
440df8415e Support memory mapped files and add File.map (#2321)
* Support memory mapped files and add File.map

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-03 23:26:52 +02:00
Christoffer Lerno
c31c423386 Reduce allocated Vmem for the compiler on 32 bit machines. 2025-08-03 22:58:51 +02:00
Velikiy Kirill
8358af2240 Add LinkedBlockingQueue (#2328)
* Add LinkedBlockingQueue

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-03 22:47:21 +02:00
Christoffer Lerno
4625b457fb Add additional checks for phi to prevent miscompilation. 2025-08-03 17:46:03 +02:00
Christoffer Lerno
151a28a92a Codegen error in if (try x = (false ? io::EOF? : 1)), i.e. using if-try with a CT known value. 2025-08-03 16:28:11 +02:00
Christoffer Lerno
9fe6c77d28 Codegen error in if (try x = (true ? io::EOF? : 1)), i.e. using if-try with a known Empty. 2025-08-03 13:33:53 +02:00
waveproc
1c4f7a4b61 change cmake build instructions in README.md (#2300)
* change cmake build instructions in README.md


---------

Co-authored-by: Your Name <you@example.com>
2025-08-03 00:11:57 +02:00
Velikiy Kirill
f23bbb342c Update release notes to include HashSet and Linked containers 2025-08-02 23:19:55 +02:00
Zack Puhl
91b866c967 Immediately skip empty tests (#2352) 2025-08-02 23:17:34 +02:00
Zack Puhl
483fe62750 OpenBSD Sockets (#2353)
* native file testing for BSD
* basic OpenBSD socket port
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-02 23:15:53 +02:00
Zack Puhl
2a47cc2ca9 Pair and Triple Compare w/ Unit Tests (#2359)
* Pair and Triple Compare w/ Unit Tests
* scope creep myself by adding date-time eq op
* make Pair and Triple printable
* Update releasenotes. Restrict equals on tuples to when underlying type supports `==`. Remove unnecessary Time.eq.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-02 23:11:27 +02:00
Zack Puhl
e707190539 Add @intlog2 for Floored CT log-base2 (#2355)
* Add `@intlog2` for Floored CT log-base2
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-02 22:53:36 +02:00
Christoffer Lerno
cb62554a26 Fix version. 2025-08-02 16:57:39 +02:00
Christoffer Lerno
9c58db99af With avx512, passing a 512 bit vector in a union would be lowered incorrectly, causing an assert. #2362 2025-08-02 16:56:43 +02:00
Christoffer Lerno
90c339ebdb List.remove_at would incorrectly trigger ASAN. 2025-08-02 16:56:43 +02:00
Christoffer Lerno
e3a8a3ec02 Support alias foo = module std::io module aliasing. 2025-08-02 16:56:43 +02:00
380 changed files with 16416 additions and 5379 deletions

View File

@@ -8,11 +8,11 @@ on:
env:
LLVM_RELEASE_VERSION_WINDOWS: 18
LLVM_RELEASE_VERSION_MAC: 17
LLVM_RELEASE_VERSION_LINUX: 17
LLVM_RELEASE_VERSION_MAC: 18
LLVM_RELEASE_VERSION_LINUX: 19
LLVM_RELEASE_VERSION_OPENBSD: 19
LLVM_RELEASE_VERSION_UBUNTU22: 17
LLVM_DEV_VERSION: 21
LLVM_RELEASE_VERSION_UBUNTU22: 19
LLVM_DEV_VERSION: 22
jobs:
build-msvc:
@@ -88,16 +88,16 @@ jobs:
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55 --print-linking examples\raylib\raylib_snake.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55 --print-linking examples\raylib\raylib_tetris.c3
- name: run compiler tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ..\build\${{ matrix.build_type }}\c3c.exe test_suite/
- name: Compile run unit tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1 -D SLOW_TESTS
- name: run compiler tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ..\build\${{ matrix.build_type }}\c3c.exe test_suite/ --no-terminal
- name: Test python script
run: |
py msvc_build_libraries.py --accept-license
@@ -230,13 +230,14 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20]
llvm_version: [17, 18, 19, 20, 21, 22]
steps:
- uses: actions/checkout@v4
- name: Install common deps
run: |
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
sudo apt-get update
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl libcurl4-openssl-dev
- name: Install Clang ${{matrix.llvm_version}}
run: |
@@ -381,6 +382,10 @@ jobs:
./build/c3c init myproject
ls myproject
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib55
- name: run compiler tests
run: |
cd test
@@ -416,7 +421,8 @@ jobs:
- uses: actions/checkout@v4
- name: Install common deps
run: |
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
sudo apt-get update
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl libcurl4-openssl-dev
- name: Install Clang ${{matrix.llvm_version}}
run: |
@@ -460,6 +466,7 @@ jobs:
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
- name: Compile and run some examples
run: |
cd resources
@@ -506,6 +513,10 @@ jobs:
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib55
- name: run compiler tests
run: |
cd test

10
.gitignore vendored
View File

@@ -1,5 +1,7 @@
# Prerequisites
*.d
testrun
benchmarkrun
# Object files
*.o
@@ -7,6 +9,8 @@
*.obj
*.elf
*.ll
*.wasm
*.s
# Linker output
*.ilk
@@ -76,8 +80,10 @@ TAGS
/.cache/
/compile_commands.json
# 'nix build' resulting symlink
# Nix
result
/.envrc
/.direnv/
# macOS
.DS_Store
@@ -85,4 +91,4 @@ result
# tests
/test/tmp/*
/test/testrun
/test/test_suite_runner
/test/test_suite_runner

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.20)
set(C3_LLVM_MIN_VERSION 17)
set(C3_LLVM_MAX_VERSION 21)
set(C3_LLVM_MAX_VERSION 22)
set(C3_LLVM_DEFAULT_VERSION 19)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
@@ -70,15 +70,16 @@ else()
endif()
# Options
set(C3_LINK_DYNAMIC OFF CACHE BOOL "Link dynamically with LLVM/LLD libs")
set(C3_WITH_LLVM ON CACHE BOOL "Build with LLVM")
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
set(C3_USE_MIMALLOC OFF CACHE BOOL "Use built-in mimalloc")
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
set(C3_USE_TB OFF CACHE BOOL "Use TB")
set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory")
set(C3_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation")
set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
set(C3_LINK_DYNAMIC OFF CACHE BOOL "Link dynamically with LLVM/LLD libs")
set(C3_WITH_LLVM ON CACHE BOOL "Build with LLVM")
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
set(C3_USE_MIMALLOC OFF CACHE BOOL "Use built-in mimalloc")
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
set(C3_USE_TB OFF CACHE BOOL "Use TB")
set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory")
set(C3_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation")
set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
set(TCC_LIB_PATH "/usr/lib/tcc/libtcc1.a" CACHE STRING "Use custom libtcc1.a path")
set(C3_OPTIONS
C3_LINK_DYNAMIC
@@ -390,6 +391,7 @@ add_executable(c3c
src/utils/unzipper.c
src/compiler/c_codegen.c
src/compiler/decltable.c
src/compiler/methodtable.c
src/compiler/mac_support.c
src/compiler/windows_support.c
src/compiler/codegen_asm.c
@@ -574,6 +576,17 @@ else()
target_link_options(c3c PRIVATE -pthread)
endif()
if(CMAKE_C_COMPILER_ID STREQUAL "TinyCC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-eh-frame-hdr -z noexecstack")
# Link the static tcc runtime archive if it exists
if(EXISTS "${TCC_LIB_PATH}")
target_link_libraries(c3c "${TCC_LIB_PATH}")
else()
message(FATAL_ERROR "TCC runtime not found at ${TCC_LIB_PATH}; Ensure the path is correct.")
endif()
endif()
install(TARGETS c3c DESTINATION bin)
install(DIRECTORY lib/ DESTINATION lib/c3)

View File

@@ -59,7 +59,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at . All
reported by contacting the project team at info@c3-lang.org. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.

179
LICENSE
View File

@@ -1,165 +1,20 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (c) 2022-2025 Christoffer Lernö and contributors
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

165
LICENSE_SRC Normal file
View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@@ -1,20 +0,0 @@
Copyright (c) 2022 Christoffer Lernö and contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

158
README.md
View File

@@ -142,7 +142,7 @@ fn void main()
### Current status
The current stable version of the compiler is **version 0.7.4**.
The current stable version of the compiler is **version 0.7.6**.
The upcoming 0.7.x releases will focus on expanding the standard library,
fixing bugs and improving compile time analysis.
@@ -209,12 +209,43 @@ This installs the latest prerelease build, as opposed to the latest released ver
3. If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
4. Run `c3c.exe`.
#### Installing on Windows with the install script
Open a PowerShell terminal (you may need to run it as an administrator) and run the following command:
```bash
iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex
```
The script will inform you once the installation is successful and add the `~/.c3` directory to your PATH, which will allow you to run the c3c command from any location.
You can choose another version with option `C3_VERSION`.
For example, you can force the installation of the 0.7.4 version:
```bash
$env:C3_VERSION='0.7.4'; powershell -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex"
```
If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
#### Installing on Debian with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux-debug.tar.gz))
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on Debian with the install script
Open a terminal and run the following command:
```bash
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | bash
```
The C3 compiler will be installed, and the script will also update your ~/.bashrc to include `~/.c3` in your PATH, allowing you to invoke the c3c command from anywhere. You might need to restart your terminal or source your shell for the changes to take effect.
You can choose another version with option `C3_VERSION`.
For example, you can force the installation of the 0.7.4 version:
```bash
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | C3_VERSION=0.7.4 bash
```
#### Installing on Ubuntu with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20-debug.tar.gz))
@@ -264,6 +295,60 @@ cd c3c-git
makepkg -si
```
#### Installing via Nix
You can access `c3c` via [flake.nix](./flake.nix), which will contain the latest commit of the compiler. To add `c3c` to your `flake.nix`, do the following:
```nix
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
c3c.url = "github:c3lang/c3c";
# Those are desired if you don't want to copy extra nixpkgs
c3c.inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import inputs.nixpkgs { inherit system; };
c3c = inputs.c3c.packages.${system}.c3c;
in
{
devShells.default = pkgs.mkShell {
buildInputs = [
pkgs.c3c
];
};
}
);
}
```
### Installing on Gentoo
`c3c` is available in the [Gentoo GURU overlay](https://wiki.gentoo.org/wiki/Project:GURU).
Enable and sync the GURU repository (if not already done):
```sh
sudo eselect repository enable guru
sudo emaint sync -r guru
```
Install `c3c` with:
```sh
sudo emerge -av dev-lang/c3c
```
* The compiler binary is installed to `/usr/bin/c3c`.
* The standard library is installed to `/usr/lib/c3`.
For Gentoo-specific issues, please use the [Gentoo Bugzilla](https://bugs.gentoo.org/) (Product: *GURU*).
#### Building via Docker
You can build `c3c` using an Ubuntu container. By default, the script will build through Ubuntu 22.04. You can specify the version by passing the `UBUNTU_VERSION` environment variable.
@@ -276,14 +361,15 @@ See the `build-with-docker.sh` script for more information on other configurable
#### Installing on OS X using Homebrew
2. Install CMake: `brew install cmake`
3. Install LLVM 17+: `brew install llvm`
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
5. Enter the C3C directory `cd c3c`.
6. Create a build directory `mkdir build`
7. Change directory to the build directory `cd build`
8. Set up CMake build for debug: `cmake ..`
9. Build: `cmake --build .`
1. Install [Homebrew](https://brew.sh/)
2. Install LLVM 17+: `brew install llvm`
3. Install lld: `brew install lld`
4. Install CMake: `brew install cmake`
5. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
6. Enter the C3C directory `cd c3c`.
7. Set up CMake build for debug: `cmake -B build -S .`
8. Build: `cmake --build build`
9. Change directory to the build directory `cd build`
#### Installing on Windows using Scoop
@@ -347,13 +433,12 @@ You should now have a `c3c` executable in `build-debug\Debug`.
#### 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.
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`
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`. If you're using Ubuntu 25.04, also install `libpolly-20-dev`.
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build`
6. Change directory to the build directory `cd build`
7. Set up CMake build: `cmake ..`
8. Build: `cmake --build .`
5. Set up CMake build: `cmake -B build -S .`
6. Build: `cmake --build build`
7. Change directory to the build directory `cd build`
You should now have a `c3c` executable.
@@ -366,13 +451,12 @@ You can try it out by running some sample code: `./c3c compile ../resources/exam
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 .`
4. Create the CMake build cache: `cmake -B build -S .`
5. Build: `cmake --build build`
6. Enter the build directory: `cd 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 .`
For a system-wide installation, run the following as root: `cmake --install .`
#### Compiling on Fedora
@@ -382,15 +466,15 @@ For a sytem-wide installation, run the following as root: `cmake --install .`
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 .`
5. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake -B build -S . -DC3_LINK_DYNAMIC=1`
6. Build the project: `cmake --build build`
7. Enter the build directory: `cd 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 Arch Linux
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm`
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm libxml2`
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: `git clone https://github.com/c3lang/c3c.git --depth=1`
3. Enter the C3C directory: `cd c3c`
@@ -400,23 +484,30 @@ cmake -B build \
-D C3_LINK_DYNAMIC=ON \
-D CMAKE_BUILD_TYPE=Release
```
5. Build the project: `make -C build`.
5. Build the project: `cmake --build build`.
After compilation, the `c3c` binary will be located in the `build` directory. You can test it by compiling an example: `./build/c3c compile resources/examples/ls.c3`.
6. To install the compiler globally: `sudo cmake --install build`
#### Compiling on NixOS
1. Enter nix shell, by typing `nix develop` in root directory
2. Configure cmake via `cmake . -Bbuild $=C3_CMAKE_FLAGS`. Note: passing `C3_CMAKE_FLAGS` is needed in due to generate `compile_commands.json` and find missing libs.
4. Build it `cmake --build build`
5. Test it out: `./build/c3c -V`
6. If you use `clangd` lsp server for your editor, it is recommended to make a symbolic link to `compile_command.json` in the root: `ln -s ./build/compile_commands.json compile_commands.json`
#### Compiling on other Linux / Unix variants
1. Install CMake.
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`
4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build`
6. Change directory to the build directory `cd build`
7. Set up CMake build for debug: `cmake ..`. At this point you may need to manually
provide the link path to the LLVM CMake directories, e.g. `cmake -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/ ..`
8. Build: `cmake --build .`
5. Set up CMake build for debug: `cmake -B build -S .`. At this point you may need to manually
provide the link path to the LLVM CMake directories, e.g. `cmake -B build -S . -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/`
6. Build: `cmake --build build`
7. Change directory to the build directory `cd build`
*A note on compiling for Linux/Unix/MacOS: to be able to fetch vendor libraries
libcurl is needed. The CMake script should detect it if it is available. Note that
@@ -424,8 +515,13 @@ this functionality is non-essential and it is perfectly fine to user the compile
#### Licensing
The C3 compiler is licensed under LGPL 3.0, the standard library itself is
MIT licensed.
Unless specified otherwise, the code in this repository is MIT licensed.
The exception is the compiler source code (the source code under `src`),
which is licensed under LGPL 3.0.
This means you are free to use all parts of standard library,
tests, benchmarks, grammar, examples and so on under the MIT license, including
using those libraries and tests if your build your own C3 compiler.
#### Editor plugins

View File

@@ -0,0 +1,219 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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.
//
// Some benchmark test ideas are sourced from this article on C++ hashmap benchmarking:
// https://martin.ankerl.com/2022/08/27/hashmap-bench-01/
//
module hashmap_benchmarks;
import std::collections::map;
import std::math::random;
const DEFAULT_ITERATIONS = 16384;
Lcg64Random rand;
HashMap { int, int } modifying_numbers_random;
fn void bench_setup() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(DEFAULT_ITERATIONS);
// TODO: Cannot take the address of a @benchmark function. If we could, we could pass &insert_erase as a fn ptr and use the $qnameof CT eval internally.
set_benchmark_func_iterations($qnameof(insert_erase), 32);
set_benchmark_func_iterations($qnameof(random_access), 1024);
random::seed(&rand, 0x4528_21e6_38d0_1377);
for (usz i = 0; i < 1_000; ++i) modifying_numbers_random.set(rand.next_int(), rand.next_int());
}
// ==============================================================================================
module hashmap_benchmarks @benchmark;
import std::collections::map;
import std::math::random;
import std::encoding::base64;
fn void generic_hash_speeds()
{
(char){}.hash();
(char[<100>]){}.hash();
(char[100]){}.hash();
(ichar){}.hash();
(ichar[<100>]){}.hash();
(ichar[100]){}.hash();
(short){}.hash();
(short[<100>]){}.hash();
(short[100]){}.hash();
(ushort){}.hash();
(ushort[<100>]){}.hash();
(ushort[100]){}.hash();
(int){}.hash();
(int[<100>]){}.hash();
(int[100]){}.hash();
(uint){}.hash();
(uint[<100>]){}.hash();
(uint[100]){}.hash();
(long){}.hash();
(long[<20>]){}.hash();
(long[100]){}.hash();
(ulong){}.hash();
(ulong[<20>]){}.hash();
(ulong[100]){}.hash();
(int128){}.hash();
(int128[<20>]){}.hash();
(int128[100]){}.hash();
(uint128){}.hash();
(uint128[<20>]){}.hash();
(uint128[100]){}.hash();
(bool){}.hash();
(bool[<100>]){}.hash();
(bool[100]){}.hash();
String x = "abc";
char[] y = "abc";
assert(x.hash() == y.hash());
String z1 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
char[] z2 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
assert(z1.hash() == z2.hash());
assert(int.typeid.hash());
}
fn void hash_speeds_of_many_random_values() => @pool()
{
var $arrsz = 10_000;
uint fake_checksum;
char[] chars = allocator::new_array(tmem, char, $arrsz)[:$arrsz];
foreach (&v : chars) *v = (char)random::next(&rand, uint.max);
ushort[] shorts = allocator::new_array(tmem, ushort, $arrsz)[:$arrsz];
foreach (&v : shorts) *v = (ushort)random::next(&rand, uint.max);
uint[] ints = allocator::new_array(tmem, uint, $arrsz)[:$arrsz];
foreach (&v : ints) *v = random::next(&rand, uint.max);
ulong[] longs = allocator::new_array(tmem, ulong, $arrsz)[:$arrsz];
foreach (&v : longs) *v = (ulong)random::next(&rand, uint.max);
uint128[] vwideints = allocator::new_array(tmem, uint128, $arrsz)[:$arrsz];
foreach (&v : vwideints) *v = (uint128)random::next(&rand, uint.max);
char[48][] zstrs = allocator::new_array(tmem, char[48], $arrsz)[:$arrsz];
String[] strs = mem::temp_array(String, $arrsz);
foreach (x, &v : zstrs)
{
foreach (&c : (*v)[:random::next(&rand, 48)]) *c = (char)random::next(&rand, char.max);
strs[x] = ((ZString)&v[0]).str_view();
}
runtime::@start_benchmark();
foreach (v : chars) fake_checksum += v.hash();
foreach (v : shorts) fake_checksum += v.hash();
foreach (v : ints) fake_checksum += v.hash();
foreach (v : longs) fake_checksum += v.hash();
foreach (v : vwideints) fake_checksum += v.hash();
foreach (v : strs) fake_checksum += v.hash();
runtime::@end_benchmark();
}
fn void modifying_numbers_init_from_map() => @pool()
{
HashMap { int, int } v;
v.tinit_from_map(&modifying_numbers_random);
v.free();
}
fn void insert_erase() => @pool()
{
uint iters = 1_000_000;
HashMap { int, int } v;
v.tinit();
runtime::@start_benchmark();
for (int i = 0; i < iters; ++i) v[i] = i;
for (int i = 0; i < iters; ++i) v.remove(i);
runtime::@end_benchmark();
v.free();
}
fn void random_access() => @pool()
{
HashMap { int, int } v;
v.tinit();
uint bound = 10_000;
usz pseudo_checksum = 0;
for (uint i = 0; i < bound; ++i) v[i] = i;
runtime::@start_benchmark();
for (uint i = 0; i < 1_000_000; ++i) pseudo_checksum += (v[i.hash() % bound] ?? 0);
runtime::@end_benchmark();
v.free();
}
fn void random_access_erase() => @pool()
{
HashMap { int, int } v;
v.tinit();
uint bound = 10_000;
for (uint i = 0; i < bound; ++i) v[i] = i;
runtime::@start_benchmark();
for (uint i = 0; i < bound; ++i)
{
v[i.hash() % bound] = i; // supplant an entry
v.remove(random::next(&rand, bound)); // remove a random entry
}
runtime::@end_benchmark();
v.free();
}
fn void random_access_string_keys() => @pool()
{
HashMap { String, ulong } v;
v.tinit();
usz pseudo_checksum = 0;
String[] saved = mem::temp_array(String, 5_000);
for (usz i = 0; i < saved.len; ++i)
{
ulong hash = i.hash();
String b64key = base64::tencode(@as_char_view(hash));
v[b64key] = hash;
if (i < saved.len) saved[i] = b64key;
}
runtime::@start_benchmark();
for (usz i = 0; i < saved.len; ++i)
{
pseudo_checksum += v[ saved[random::next(&rand, saved.len)] ]!! % 512;
}
runtime::@end_benchmark();
v.free();
}

View File

@@ -0,0 +1,38 @@
module linkedlist_benchmarks;
import std::collections::linkedlist;
LinkedList{int} long_list;
const HAY = 2;
const NEEDLE = 1000;
fn void bench_setup() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(4096);
int[*] haystack = { [0..999] = HAY };
long_list = linkedlist::@new{int}(mem, haystack[..]);
long_list.push(NEEDLE);
long_list.push_all(haystack[..]);
}
// ==============================================================================================
module linkedlist_benchmarks @benchmark;
String die_str = "Failed to find the value `1`. Is something broken?";
fn void foreach_iterator()
{
foreach (v : long_list.array_view()) if (v == NEEDLE) return;
runtime::@kill_benchmark(die_str);
}
fn void foreach_r_iterator()
{
foreach_r (v : long_list.array_view()) if (v == NEEDLE) return;
runtime::@kill_benchmark(die_str);
}

View File

@@ -0,0 +1,46 @@
module string_trim_wars;
const String WHITESPACE_TARGET = " \n\t\r\f\va \tbcde\v\f\r\t\n ";
const String WHITESPACE_NUMERIC_TARGET = " 25290 0969 99a \tbcde12332 34 43 0000";
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(64);
set_benchmark_max_iterations(1 << 24);
}
macro void trim_bench($trim_str, String $target = WHITESPACE_TARGET) => @pool()
{
String s1;
String s2 = $target.tcopy();
runtime::@start_benchmark();
$switch:
$case $typeof($trim_str) == String:
s1 = s2.trim($trim_str);
$case $typeof($trim_str) == AsciiCharset:
s1 = s2.trim_charset($trim_str);
$default: $error "Unable to determine the right String `trim` operation to use.";
$endswitch
@volatile_load(s1);
runtime::@end_benchmark();
}
module string_trim_wars @benchmark;
fn void trim_control() => trim_bench(" "); // only spaces
fn void trim_whitespace_default() => trim_bench("\t\n\r "); // default set
fn void trim_whitespace_default_ordered() => trim_bench(" \n\t\r"); // default \w set, but ordered by expected freq
fn void trim_whitespace_bad() => trim_bench("\f\v\n\t\r "); // bad-perf ordering, all \w
fn void trim_whitespace_ordered_extended() => trim_bench(" \n\t\r\f\v"); // proposed ordering, all \w
fn void trim_charset_whitespace() => trim_bench(ascii::WHITESPACE_SET); // use charset, all \w
fn void trim_many() => trim_bench(" \n\t\r\f\v0123456789", WHITESPACE_NUMERIC_TARGET); // ordered, all \w + num
fn void trim_charset_many() => trim_bench(ascii::WHITESPACE_SET | ascii::NUMBER_SET, WHITESPACE_NUMERIC_TARGET); // set, all \w + num

View File

@@ -0,0 +1,31 @@
module std::crypto::aes_bench;
import std::crypto::aes;
fn void init() @init
{
set_benchmark_warmup_iterations(5);
set_benchmark_max_iterations(10_000);
}
AesType aes = AES256;
char[] key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4";
char[] text = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710";
char[] cipher = x"601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6";
char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
fn void bench_ctr_xcrypt() @benchmark
{
char[64] out;
Aes ctx;
// encrypt
ctx.init_with_iv(aes, CTR, key, iv);
ctx.encrypt_buffer(text, &out);
// decrypt
ctx.init_with_iv(aes, CTR, key, iv);
ctx.decrypt_buffer(cipher, &out);
}

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
: ${DOCKER:=docker}
: ${IMAGE:="c3c-builder"}
@@ -41,4 +41,4 @@ exec $DOCKER run -i --rm \
-DCMAKE_DLLTOOL=llvm-dlltool-$LLVM_VERSION \
-DC3_LLVM_VERSION=auto && \
cmake --build build && \
cp -r build/c3c build/lib bin"
cp -r build/c3c build/lib bin"

View File

@@ -6,7 +6,7 @@ ENV LLVM_DEV_VERSION=20
ARG CMAKE_VERSION=3.20
RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ && \
RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ libcurl4-openssl-dev && \
wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-$CMAKE_VERSION-linux-x86_64.sh && \
mkdir -p /opt/cmake && \
sh cmake-${CMAKE_VERSION}-linux-x86_64.sh --prefix=/opt/cmake --skip-license && \
@@ -46,4 +46,4 @@ RUN groupadd -g 1337 c3c && \
USER c3c
ENV PATH="/opt/cmake/bin:${PATH}"
WORKDIR /home/c3c
WORKDIR /home/c3c

View File

@@ -6,29 +6,27 @@
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem
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";
}
);
c3cBuild = set: pkgs.callPackage ./nix/default.nix (set // {
rev = self.rev or "unknown";
});
in {
packages = {
default = self.packages.${system}.c3c;
c3c = call {};
c3c = c3cBuild {};
c3c-checks = pkgs.callPackage ./nix/default.nix {
c3c-checks = c3cBuild {
checks = true;
};
c3c-debug = pkgs.callPackage ./nix/default.nix {
c3c-debug = c3cBuild {
debug = true;
};
c3c-debug-checks = pkgs.callPackage ./nix/default.nix {
c3c-debug-checks = c3cBuild {
debug = true;
checks = true;
};

189
install/install.ps1 Normal file
View File

@@ -0,0 +1,189 @@
<#
.SYNOPSIS
C3 install script.
.DESCRIPTION
This script installs C3 on Windows from the command line.
.PARAMETER C3Version
Specifies the version of C3 to install.
Default is 'latest'. Can also be set via environment variable 'C3_VERSION'.
.PARAMETER C3Home
Specifies C3's installation directory.
Default is '$Env:USERPROFILE\.c3'. Can also be set via environment variable 'C3_HOME'.
.PARAMETER NoPathUpdate
If specified, the script will not modify the PATH environment variable.
.PARAMETER C3Repourl
Specifies the repository URL of C3.
Default is 'https://github.com/c3lang/c3c'. Can also be set via environment variable 'C3_REPOURL'.
.LINK
https://c3-lang.org/
.LINK
https://github.com/c3lang/c3c
#>
# Script parameters with defaults
param (
[string] $C3Version = 'latest',
[string] $C3Home = "$Env:USERPROFILE\.c3",
[switch] $NoPathUpdate,
[string] $C3Repourl = 'https://github.com/c3lang/c3c'
)
# Enable strict mode for better error handling
Set-StrictMode -Version Latest
# Function to broadcast environment variable changes to Windows system
function Publish-Env {
# Add P/Invoke type if it does not exist
if (-not ("Win32.NativeMethods" -as [Type])) {
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageTimeout(
IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
"@
}
# Constants for broadcasting environment changes
$HWND_BROADCAST = [IntPtr] 0xffff
$WM_SETTINGCHANGE = 0x1a
$result = [UIntPtr]::Zero
# Broadcast the message to all windows
[Win32.Nativemethods]::SendMessageTimeout($HWND_BROADCAST,
$WM_SETTINGCHANGE,
[UIntPtr]::Zero,
"Environment",
2,
5000,
[ref] $result
) | Out-Null
}
# Function to write or update an environment variable in the registry
function Write-Env {
param(
[String] $name,
[String] $val,
[Switch] $global
)
# Determine the registry key based on scope (user or system)
$RegisterKey = if ($global) {
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
} else {
Get-Item -Path 'HKCU:'
}
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment', $true)
# If value is null, delete the variable
if ($null -eq $val) {
$EnvRegisterKey.DeleteValue($name)
} else {
# Determine the correct registry value type
$RegistryValueKind = if ($val.Contains('%')) {
[Microsoft.Win32.RegistryValueKind]::ExpandString
} elseif ($EnvRegisterKey.GetValue($name)) {
$EnvRegisterKey.GetValueKind($name)
} else {
[Microsoft.Win32.RegistryValueKind]::String
}
$EnvRegisterKey.SetValue($name, $val, $RegistryValueKind)
}
# Broadcast the change to the system
Publish-Env
}
# Function to get an environment variable from the registry
function Get-Env {
param(
[String] $name,
[Switch] $global
)
# Determine registry key based on scope
$RegisterKey = if ($global) {
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
} else {
Get-Item -Path 'HKCU:'
}
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment')
$RegistryValueOption = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
# Retrieve the value without expanding environment variables
$EnvRegisterKey.GetValue($name, $null, $RegistryValueOption)
}
# Override defaults if environment variables exist
if ($Env:C3_VERSION) { $C3Version = $Env:C3_VERSION }
if ($Env:C3_HOME) { $C3Home = $Env:C3_HOME }
if ($Env:C3_NO_PATH_UPDATE) { $NoPathUpdate = $true }
if ($Env:C3_REPOURL) { $C3Repourl = $Env:C3_REPOURL -replace '/$', '' }
# Set binary name
$BINARY = "c3-windows"
# Determine the download URL based on version
if ($C3Version -eq 'latest') {
$DOWNLOAD_URL = "$C3Repourl/releases/latest/download/$BINARY.zip"
} else {
# Ensure version starts with 'v'
$C3Version = "v" + ($C3Version -replace '^v', '')
$DOWNLOAD_URL = "$C3Repourl/releases/download/$C3Version/$BINARY.zip"
}
$BinDir = $C3Home
Write-Host "This script will automatically download and install C3 ($C3Version) for you."
Write-Host "Getting it from this url: $DOWNLOAD_URL"
Write-Host "The binary will be installed into '$BinDir'"
# Create temporary file for download
$TEMP_FILE = [System.IO.Path]::GetTempFileName()
try {
# Download the binary
Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $TEMP_FILE
# Remove previous installation if it exists
if (Test-Path -Path $BinDir) {
Remove-Item -Path $BinDir -Recurse -Force | Out-Null
}
# Rename temp file to .zip
$ZIP_FILE = $TEMP_FILE + ".zip"
Rename-Item -Path $TEMP_FILE -NewName $ZIP_FILE
# Extract downloaded zip
Expand-Archive -Path $ZIP_FILE -DestinationPath $Env:USERPROFILE -Force
# Rename extracted folder to target installation directory
Rename-Item -Path "$Env:USERPROFILE/c3-windows-Release" -NewName $BinDir
} catch {
Write-Host "Error: '$DOWNLOAD_URL' is not available or failed to download"
exit 1
} finally {
# Cleanup temporary zip file
Remove-Item -Path $ZIP_FILE
}
# Update PATH environment variable if requested
if (!$NoPathUpdate) {
$PATH = Get-Env 'PATH'
if ($PATH -notlike "*$BinDir*") {
Write-Output "Adding $BinDir to PATH"
# Persist PATH for future sessions
Write-Env -name 'PATH' -val "$BinDir;$PATH"
# Update PATH for current session
$Env:PATH = "$BinDir;$PATH"
Write-Output "You may need to restart your shell"
} else {
Write-Output "$BinDir is already in PATH"
}
} else {
Write-Output "You may need to update your PATH manually to use c3"
}

137
install/install.sh Normal file
View File

@@ -0,0 +1,137 @@
#!/usr/bin/env bash
set -euo pipefail # Exit on error, unset variables, and fail pipelines on any error
__wrap__() {
# Version of C3 to install (default: latest)
VERSION="${C3_VERSION:-latest}"
# Installation directory (default: ~/.c3)
C3_HOME="${C3_HOME:-$HOME/.c3}"
# Expand '~' if present
C3_HOME="${C3_HOME/#\~/$HOME}"
BIN_DIR="$C3_HOME"
# C3 compiler repository URL
REPO="c3lang/c3c"
REPOURL="${C3_REPOURL:-https://github.com/$REPO}"
detect_platform() {
# Detects the operating system
local os_type
os_type="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$os_type" in
darwin) # macOS
echo "macos"
;;
msys*|mingw*|cygwin*) # Windows (Git Bash / MSYS / Cygwin)
IS_MSYS=true
echo "windows"
;;
*)
echo $os_type
;;
esac
}
# Determine platform string
PLATFORM="$(detect_platform)"
# File extension for the archive (ZIP for Windows, TAR.GZ for others)
EXT=".tar.gz"
BINARY="c3-${PLATFORM}"
if [[ "${IS_MSYS:-false}" == true ]]; then
EXT=".zip"
fi
# Determine the download URL (latest release or specific version)
if [[ "$VERSION" == "latest" ]]; then
URL="${REPOURL%/}/releases/latest/download/${BINARY}${EXT}"
else
URL="${REPOURL%/}/releases/download/v${VERSION#v}/${BINARY}${EXT}"
fi
# Temporary file for the downloaded archive
TEMP_FILE="$(mktemp "${TMPDIR:-/tmp}/.C3_install.XXXXXXXX")"
trap 'rm -f "$TEMP_FILE"' EXIT # Ensure temp file is deleted on exit
download_file() {
# Download the archive using curl or wget
# Check that the curl version is not 8.8.0, which is broken for --write-out
# https://github.com/curl/curl/issues/13845
if command -v curl >/dev/null && [[ "$(curl --version | awk 'NR==1{print $2}')" != "8.8.0" ]]; then
curl -SL "$URL" -o "$TEMP_FILE"
elif command -v wget >/dev/null; then
wget -O "$TEMP_FILE" "$URL"
else
echo "Error: curl or wget is required." >&2
exit 1
fi
}
echo "Downloading C3 ($VERSION) from $URL..."
download_file
# Remove existing installation and extract the new one
rm -rf "$BIN_DIR"
if [[ "$EXT" == ".zip" ]]; then
unzip "$TEMP_FILE" -d "$HOME"
else
tar -xzf "$TEMP_FILE" -C "$HOME"
fi
# Move extracted folder to installation directory
mv "$HOME/c3" "$BIN_DIR"
chmod +x "$BIN_DIR/c3c" # Ensure compiler binary is executable
echo "✅ Installation completed in $BIN_DIR"
# Update PATH unless suppressed by environment variable
if [ -n "${C3_NO_PATH_UPDATE:-}" ]; then
echo "No path update because C3_NO_PATH_UPDATE is set"
else
update_shell() {
FILE="$1"
LINE="$2"
# Create shell config file if missing
if [ ! -f "$FILE" ]; then
touch "$FILE"
fi
# Add the PATH line if not already present
if ! grep -Fxq "$LINE" "$FILE"; then
echo "Updating '${FILE}'"
echo "$LINE" >>"$FILE"
echo "Please restart or source your shell."
fi
}
# Detect the current shell and add C3 to its PATH
case "$(basename "${SHELL-}")" in
bash)
# Default to bashrc as that is used in non login shells instead of the profile.
LINE="export PATH=\"${BIN_DIR}:\$PATH\""
update_shell ~/.bashrc "$LINE"
;;
fish)
LINE="fish_add_path ${BIN_DIR}"
update_shell ~/.config/fish/config.fish "$LINE"
;;
zsh)
LINE="export PATH=\"${BIN_DIR}:\$PATH\""
update_shell ~/.zshrc "$LINE"
;;
tcsh)
LINE="set path = ( ${BIN_DIR} \$path )"
update_shell ~/.tcshrc "$LINE"
;;
'')
echo "warn: Could not detect shell type." >&2
echo " Please permanently add '${BIN_DIR}' to your \$PATH to enable the 'c3c' command." >&2
;;
*)
echo "warn: Could not update shell $(basename "$SHELL")" >&2
echo " Please permanently add '${BIN_DIR}' to your \$PATH to enable the 'c3c' command." >&2
;;
esac
fi
}
__wrap__

View File

@@ -58,7 +58,7 @@ fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired,
nextcase;
$endif
default:
unreachable("Unsuported size (%d) for atomic_compare_exchange", size);
unreachable("Unsupported size (%d) for atomic_compare_exchange", size);
}
return 0;
}
}

View File

@@ -2,10 +2,10 @@
// 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;
import std::collections::interfacelist;
alias AnyPredicate = fn bool(any value);
alias AnyTest = fn bool(any type, any context);
alias AnyPredicate = InterfacePredicate {any};
alias AnyTest = InterfaceTest {any};
<*
The AnyList contains a heterogenous set of types. Anything placed in the
@@ -18,282 +18,7 @@ alias AnyTest = fn bool(any type, any context);
If we're not doing pop, then things are easier, since we can just hand over
the existing any.
*>
struct AnyList (Printable)
{
usz size;
usz capacity;
Allocator allocator;
any* entries;
}
<*
Initialize the list. If not initialized then it will use the temp allocator
when something is pushed to it.
@param [&inout] allocator : "The allocator to use"
@param initial_capacity : "The initial capacity to reserve, defaults to 16"
*>
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.tinit(&self, usz initial_capacity = 16)
{
return self.init(tmem, initial_capacity) @inline;
}
fn bool AnyList.is_initialized(&self) @inline => self.allocator != null;
<*
Push an element on the list by cloning it.
*>
macro void AnyList.push(&self, element)
{
if (!self.allocator) self.allocator = tmem;
self._append(allocator::clone(self.allocator, 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.
@param $Type : "The type we assume the value has"
@return "The last value as the type given"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
macro AnyList.pop(&self, $Type)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return *anycast(self.entries[--self.size], $Type);
}
<*
Copy the last value, pop it and return the copy of it.
@param [&inout] allocator : "The allocator to use for copying"
@return "A copy of the last value if it exists"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.copy_pop(&self, Allocator allocator)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return allocator::clone_any(allocator, self.entries[--self.size]);
}
<*
Copy the last value, pop it and return the copy of it.
@return "A temp copy of the last value if it exists"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.tcopy_pop(&self) => self.copy_pop(tmem);
<*
Pop the last value. It must later be released using `list.free_element()`.
@return "The last value if it exists"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.pop_retained(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
<*
Remove all elements in the list.
*>
fn void AnyList.clear(&self)
{
for (usz i = 0; i < self.size; i++)
{
self.free_element(self.entries[i]);
}
self.size = 0;
}
<*
Pop a value who's type is known. If the type is incorrect, this
will still pop the element.
@param $Type : "The type we assume the value has"
@return "The first value as the type given"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
macro AnyList.pop_first(&self, $Type)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return *anycast(self.entries[0], $Type);
}
<*
Pop the first value. It must later be released using `list.free_element()`.
@return "The first value if it exists"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.pop_first_retained(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
<*
Copy the first value, pop it and return the copy of it.
@param [&inout] allocator : "The allocator to use for copying"
@return "A copy of the first value if it exists"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.copy_pop_first(&self, Allocator allocator)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
defer self.remove_at(0);
return allocator::clone_any(allocator, self.entries[0]);
}
<*
Copy the first value, pop it and return the temp copy of it.
@return "A temp copy of the first value if it exists"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.tcopy_pop_first(&self) => self.copy_pop_first(tmem);
<*
Remove the element at the particular index.
@param index : "The index of the element to remove"
@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];
}
<*
Add all the elements in another AnyList.
@param [&in] other_list : "The list to add"
*>
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 order of the elements in the 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);
}
}
<*
Return a view of the data as a slice.
@return "The slice view"
*>
fn any[] AnyList.array_view(&self)
{
return self.entries[:self.size];
}
<*
Push an element to the front of the list.
@param value : "The value to push to the list"
*>
macro void AnyList.push_front(&self, value)
{
self.insert_at(0, value);
}
<*
Insert an element at a particular index.
@param index : "the index where the element should be inserted"
@param type : "the value to insert"
@require index <= self.size : "The index is out of bounds"
*>
macro void AnyList.insert_at(&self, usz index, type)
{
if (index == self.size)
{
self.push(type);
return;
}
any value = allocator::copy(self.allocator, type);
self._insert_at(self, index, value);
}
<*
Remove the last element in the list. The list may not be empty.
@require self.size > 0 : "The list was already empty"
*>
fn void AnyList.remove_last(&self)
{
self.free_element(self.entries[--self.size]);
}
<*
Remove the first element in the list, the list may not be empty.
@require self.size > 0
*>
fn void AnyList.remove_first(&self)
{
self.remove_at(0);
}
typedef AnyList = inline InterfaceList {any};
<*
Return the first element by value, assuming it is the given type.
@@ -313,10 +38,7 @@ macro AnyList.first(&self, $Type)
@return "The first element"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.first_any(&self) @inline
{
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
}
fn any? AnyList.first_any(&self) @inline => InterfaceList {any}.first(self);
<*
Return the last element by value, assuming it is the given type.
@@ -336,29 +58,36 @@ macro AnyList.last(&self, $Type)
@return "The last element"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.last_any(&self) @inline
fn any? AnyList.last_any(&self) @inline => InterfaceList {any}.last(self);
<*
Pop a value who's type is known. If the type is incorrect, this
will still pop the element.
@param $Type : "The type we assume the value has"
@return "The last value as the type given"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
macro AnyList.pop(&self, $Type)
{
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return *anycast(self.entries[--self.size], $Type);
}
<*
Return whether the list is empty.
Pop a value who's type is known. If the type is incorrect, this
will still pop the element.
@return "True if the list is empty"
@param $Type : "The type we assume the value has"
@return "The first value as the type given"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
fn bool AnyList.is_empty(&self) @inline
macro AnyList.pop_first(&self, $Type)
{
return !self.size;
}
<*
Return the length of the list.
@return "The number of elements in the list"
*>
fn usz AnyList.len(&self) @operator(len) @inline
{
return self.size;
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return *anycast(self.entries[0], $Type);
}
<*
@@ -383,222 +112,11 @@ macro AnyList.get(&self, usz index, $Type)
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
@require index < self.size : "Index out of range"
*>
fn any AnyList.get_any(&self, usz index) @inline @operator([])
{
return self.entries[index];
}
fn any AnyList.get_any(&self, usz index) @inline @operator([]) => InterfaceList {any}.get(self, index);
<*
Completely free and clear a list.
Return the length of the list.
@return "The number of elements in the list"
*>
fn void AnyList.free(&self)
{
if (!self.allocator) return;
self.clear();
allocator::free(self.allocator, self.entries);
self.capacity = 0;
self.entries = null;
}
<*
Swap two elements in a list.
@param i : "Index of one of the elements"
@param j : "Index of the other element"
@require i < self.size : "The first index is out of range"
@require j < self.size : "The second index is out of range"
*>
fn void AnyList.swap(&self, usz i, usz j)
{
any temp = self.entries[i];
self.entries[i] = self.entries[j];
self.entries[j] = temp;
}
<*
Print the list to a formatter.
*>
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;
}
}
<*
Remove any elements matching the predicate.
@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);
}
<*
Retain the elements matching the predicate.
@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);
}
<*
Remove any elements matching the predicate.
@param filter : "The function to determine if it should be removed or not"
@param context : "The context to the function"
@return "the number of deleted elements"
*>
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
{
return self._remove_using_test(filter, false, context);
}
<*
Retain any elements matching the predicate.
@param selection : "The function to determine if it should be retained or not"
@param context : "The context to the function"
@return "the number of deleted elements"
*>
fn usz AnyList.retain_using_test(&self, AnyTest selection, any context)
{
return self._remove_using_test(selection, true, context);
}
<*
Reserve memory so that at least the `min_capacity` exists.
@param min_capacity : "The min capacity to hold"
*>
fn void AnyList.reserve(&self, usz min_capacity)
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = tmem;
min_capacity = math::next_power_of_2(min_capacity);
self.entries = allocator::realloc(self.allocator, self.entries, any.sizeof * min_capacity);
self.capacity = min_capacity;
}
<*
Set the element at any index.
@param index : "The index where to set the value."
@param value : "The value to set"
@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);
}
// -- private
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);
}
fn void AnyList._append(&self, any element) @local
{
self.ensure_capacity();
self.entries[self.size++] = element;
}
<*
@require index < self.size
*>
fn void AnyList._insert_at(&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;
}
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;
}
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.len(&self) @operator(len) @inline => InterfaceList {any}.len(self);

View File

@@ -136,6 +136,7 @@ fn void BitSet.unset(&self, usz i)
@param i : "The index of the bit"
@require i < SIZE : "Index was out of range"
@pure
*>
fn bool BitSet.get(&self, usz i) @operator([]) @inline
{
@@ -144,6 +145,11 @@ fn bool BitSet.get(&self, usz i) @operator([]) @inline
return self.data[q] & (1 << r) != 0;
}
<*
Return the number of bits.
@pure
*>
fn usz BitSet.len(&self) @operator(len) @inline
{
return SZ * BITS;

View File

@@ -11,7 +11,7 @@ alias ElementPredicate = fn bool(Type *type);
alias ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
struct ElasticArray (Printable)
{
@@ -121,7 +121,24 @@ fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
@param [in] array
*>
fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
fn usz ElasticArray.add_array_to_limit(&self, Type[] array) @deprecated("Use push_all_to_limit")
{
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 as many values from this array as possible, returning the
number of elements that didn't fit.
@param [in] array
*>
fn usz ElasticArray.push_all_to_limit(&self, Type[] array)
{
if (!array.len) return 0;
foreach (i, &value : array)
@@ -139,7 +156,7 @@ fn usz ElasticArray.add_array_to_limit(&self, Type[] 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)
fn void ElasticArray.add_array(&self, Type[] array) @deprecated("Use push_all")
{
if (!array.len) return;
foreach (&value : array)
@@ -148,6 +165,21 @@ fn void ElasticArray.add_array(&self, Type[] array)
}
}
<*
Add the values of an array to this list.
@param [in] array
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
@ensure self.size >= array.len
*>
fn void ElasticArray.push_all(&self, Type[] array)
{
if (!array.len) return;
foreach (&value : array)
{
self.entries[self.size++] = *value;
}
}
<*
@@ -426,4 +458,4 @@ fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER)
fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER)
{
return list_common::list_compact(self);
}
}

View File

@@ -30,8 +30,10 @@ struct HashMap (Printable)
{
Entry*[] table;
Allocator allocator;
uint count; // Number of elements
uint threshold; // Resize limit
<* Last inserted LinkedEntry *>
uint count;
<* Resize limit *>
uint threshold;
float load_factor;
}
@@ -182,6 +184,24 @@ fn Value*? HashMap.get_ref(&map, Key key)
return NOT_FOUND?;
}
fn Value* HashMap.get_or_create_ref(&map, Key key) @operator(&[])
{
uint hash = rehash(key.hash());
if (map.count)
{
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;
}
}
map.set(key, {});
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;
}
unreachable();
}
fn Entry*? HashMap.get_entry(&map, Key key)
{
if (!map.count) return NOT_FOUND?;
@@ -194,8 +214,9 @@ fn Entry*? HashMap.get_entry(&map, Key key)
}
<*
Get the value or update and
@require @assignable_to(#expr, Value)
Get the value or set it to the value
@require $defined(Value val = #expr)
*>
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
{
@@ -229,15 +250,15 @@ fn bool HashMap.has_key(&map, Key key)
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
switch (map.allocator.ptr)
{
case &dummy:
map.init(mem);
case null:
map.tinit();
default:
break;
}
switch (map.allocator.ptr)
{
case &dummy:
map.init(mem);
case null:
map.tinit();
default:
break;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
@@ -332,10 +353,7 @@ macro HashMap.@each_entry(map; @body(entry))
}
}
fn Value[] HashMap.tvalues(&map)
{
return map.values(tmem) @inline;
}
fn Value[] HashMap.tvalues(&self) => self.values(tmem) @inline;
fn Value[] HashMap.values(&self, Allocator allocator)
{
@@ -421,7 +439,7 @@ fn usz? HashMap.to_format(&self, Formatter* f) @dynamic
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s: %s", entry.key, entry.value)!;
};
};
return len + f.print(" }");
}
@@ -589,4 +607,4 @@ macro uint index_for(uint hash, uint capacity) @private
return hash & (capacity - 1);
}
int dummy @local;
int dummy @local;

View File

@@ -14,19 +14,21 @@ const Allocator SET_HEAP_ALLOCATOR = (Allocator)&dummy;
<* Copy the ONHEAP allocator to initialize to a set that is heap allocated *>
const HashSet ONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
struct Entry
struct Entry
{
uint hash;
Value value;
Entry* next;
}
struct HashSet (Printable)
struct HashSet (Printable)
{
Entry*[] table;
Allocator allocator;
usz count; // Number of elements
usz threshold; // Resize limit
<* Number of elements *>
usz count;
<* Resize limit *>
usz threshold;
float load_factor;
}
@@ -39,7 +41,7 @@ fn int HashSet.len(&self) @operator(len) => (int) self.count;
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
@@ -285,7 +287,7 @@ fn usz HashSet.remove_all_from(&set, HashSet* other)
<*
Free all memory allocated by the hash set.
*>
fn void HashSet.free(&set)
fn void HashSet.free(&set)
{
if (!set.is_initialized()) return;
set.clear();
@@ -329,7 +331,23 @@ fn void HashSet.reserve(&set, usz capacity)
}
}
fn Value[] HashSet.tvalues(&self) => self.values(tmem) @inline;
fn Value[] HashSet.values(&self, Allocator allocator)
{
if (!self.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, self.count);
usz index = 0;
foreach (Entry* entry : self.table)
{
while (entry)
{
list[index++] = entry.value;
entry = entry.next;
}
}
return list;
}
// --- Set Operations ---

View File

@@ -0,0 +1,539 @@
// Copyright (c) 2024-2025 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@require Type.kindof == INTERFACE || Type.kindof == ANY : "The kind of an interfacelist must be an interface or `any`"
*>
module std::collections::interfacelist {Type};
import std::io,std::math;
alias InterfacePredicate = fn bool(Type value);
alias InterfaceTest = fn bool(Type type, Type context);
<*
The InterfaceList contains a heterogenous set of types implementing an interface. anything placed in the
list will shallowly copied in order to be stored as the interface. This means
that the list will copy and free its elements.
However, because we're getting interface values back when we pop, those operations
need to take an allocator, as we can only copy then pop then return the copy.
If we're not doing pop, then things are easier, since we can just hand over
the existing value.
*>
struct InterfaceList (Printable)
{
usz size;
usz capacity;
Allocator allocator;
Type* entries;
}
<*
Initialize the list. If not initialized then it will use the temp allocator
when something is pushed to it.
@param [&inout] allocator : "The allocator to use"
@param initial_capacity : "The initial capacity to reserve, defaults to 16"
*>
fn InterfaceList* InterfaceList.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, Type, 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 InterfaceList* InterfaceList.tinit(&self, usz initial_capacity = 16)
{
return self.init(tmem, initial_capacity) @inline;
}
fn bool InterfaceList.is_initialized(&self) @inline => self.allocator != null;
<*
Push an element on the list by cloning it.
@require $defined(Type t = &element) : "Element must implement the interface"
*>
macro void InterfaceList.push(&self, element)
{
if (!self.allocator) self.allocator = tmem;
self._append(allocator::clone(self.allocator, element));
}
<*
Free a retained element removed using *_retained.
*>
fn void InterfaceList.free_element(&self, Type element) @inline
{
allocator::free(self.allocator, element.ptr);
}
<*
Copy the last value, pop it and return the copy of it.
@param [&inout] allocator : "The allocator to use for copying"
@return "A copy of the last value if it exists"
@return? NO_MORE_ELEMENT
*>
fn Type? InterfaceList.copy_pop(&self, Allocator allocator)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return (Type)allocator::clone_any(allocator, self.entries[--self.size]);
}
<*
Copy the last value, pop it and return the copy of it.
@return "A temp copy of the last value if it exists"
@return? NO_MORE_ELEMENT
*>
fn Type? InterfaceList.tcopy_pop(&self) => self.copy_pop(tmem);
<*
Pop the last value. It must later be released using `list.free_element()`.
@return "The last value if it exists"
@return? NO_MORE_ELEMENT
*>
fn Type? InterfaceList.pop_retained(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
<*
Remove all elements in the list.
*>
fn void InterfaceList.clear(&self)
{
for (usz i = 0; i < self.size; i++)
{
self.free_element(self.entries[i]);
}
self.size = 0;
}
<*
Pop the first value. It must later be released using `list.free_element()`.
@return "The first value if it exists"
@return? NO_MORE_ELEMENT
*>
fn Type? InterfaceList.pop_first_retained(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
<*
Copy the first value, pop it and return the copy of it.
@param [&inout] allocator : "The allocator to use for copying"
@return "A copy of the first value if it exists"
@return? NO_MORE_ELEMENT
*>
fn Type? InterfaceList.copy_pop_first(&self, Allocator allocator)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
defer self.remove_at(0);
return (Type)allocator::clone_any(allocator, self.entries[0]);
}
<*
Copy the first value, pop it and return the temp copy of it.
@return "A temp copy of the first value if it exists"
@return? NO_MORE_ELEMENT
*>
fn Type? InterfaceList.tcopy_pop_first(&self) => self.copy_pop_first(tmem);
<*
Remove the element at the particular index.
@param index : "The index of the element to remove"
@require index < self.size
*>
fn void InterfaceList.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];
}
<*
Add all the elements in another InterfaceList.
@param [&in] other_list : "The list to add"
*>
fn void InterfaceList.add_all(&self, InterfaceList* other_list)
{
if (!other_list.size) return;
self.reserve(other_list.size);
foreach (value : other_list)
{
self.entries[self.size++] = (Type)allocator::clone_any(self.allocator, value);
}
}
<*
Reverse the order of the elements in the list.
*>
fn void InterfaceList.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);
}
}
<*
Return a view of the data as a slice.
@return "The slice view"
*>
fn Type[] InterfaceList.array_view(&self)
{
return self.entries[:self.size];
}
<*
Push an element to the front of the list.
@param value : "The value to push to the list"
@require $defined(Type t = &value) : "Value must implement the interface"
*>
macro void InterfaceList.push_front(&self, value)
{
self.insert_at(0, value);
}
<*
Insert an element at a particular index.
@param index : "the index where the element should be inserted"
@param type : "the value to insert"
@require index <= self.size : "The index is out of bounds"
@require $defined(Type t = &type) : "Type must implement the interface"
*>
macro void InterfaceList.insert_at(&self, usz index, type)
{
if (index == self.size)
{
self.push(type);
return;
}
Type value = allocator::clone(self.allocator, type);
self._insert_at(self, index, value);
}
<*
Remove the last element in the list. The list may not be empty.
@require self.size > 0 : "The list was already empty"
*>
fn void InterfaceList.remove_last(&self)
{
self.free_element(self.entries[--self.size]);
}
<*
Remove the first element in the list, the list may not be empty.
@require self.size > 0
*>
fn void InterfaceList.remove_first(&self)
{
self.remove_at(0);
}
<*
Return the first element
@return "The first element"
@return? NO_MORE_ELEMENT
*>
fn Type? InterfaceList.first(&self) @inline
{
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
}
<*
Return the last element
@return "The last element"
@return? NO_MORE_ELEMENT
*>
fn Type? InterfaceList.last(&self) @inline
{
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?;
}
<*
Return whether the list is empty.
@return "True if the list is empty"
*>
fn bool InterfaceList.is_empty(&self) @inline
{
return !self.size;
}
<*
Return the length of the list.
@return "The number of elements in the list"
*>
fn usz InterfaceList.len(&self) @operator(len) @inline
{
return self.size;
}
<*
Return an element in the list.
@param index : "The index of the element to retrieve"
@return "The element at the index"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
@require index < self.size : "Index out of range"
*>
fn Type InterfaceList.get(&self, usz index) @inline @operator([])
{
return self.entries[index];
}
<*
Completely free and clear a list.
*>
fn void InterfaceList.free(&self)
{
if (!self.allocator) return;
self.clear();
allocator::free(self.allocator, self.entries);
self.capacity = 0;
self.entries = null;
}
<*
Swap two elements in a list.
@param i : "Index of one of the elements"
@param j : "Index of the other element"
@require i < self.size : "The first index is out of range"
@require j < self.size : "The second index is out of range"
*>
fn void InterfaceList.swap(&self, usz i, usz j)
{
Type temp = self.entries[i];
self.entries[i] = self.entries[j];
self.entries[j] = temp;
}
<*
Print the list to a formatter.
*>
fn usz? InterfaceList.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;
}
}
<*
Remove Type elements matching the predicate.
@param filter : "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
fn usz InterfaceList.remove_if(&self, InterfacePredicate filter)
{
return self._remove_if(filter, false);
}
<*
Retain the elements matching the predicate.
@param selection : "The function to determine if it should be kept or not"
@return "the number of deleted elements"
*>
fn usz InterfaceList.retain_if(&self, InterfacePredicate selection)
{
return self._remove_if(selection, true);
}
<*
Remove Type elements matching the predicate.
@param filter : "The function to determine if it should be removed or not"
@param context : "The context to the function"
@return "the number of deleted elements"
*>
fn usz InterfaceList.remove_using_test(&self, InterfaceTest filter, Type context)
{
return self._remove_using_test(filter, false, context);
}
<*
Retain Type elements matching the predicate.
@param selection : "The function to determine if it should be retained or not"
@param context : "The context to the function"
@return "the number of deleted elements"
*>
fn usz InterfaceList.retain_using_test(&self, InterfaceTest selection, Type context)
{
return self._remove_using_test(selection, true, context);
}
<*
Reserve memory so that at least the `min_capacity` exists.
@param min_capacity : "The min capacity to hold"
*>
fn void InterfaceList.reserve(&self, usz min_capacity)
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = tmem;
min_capacity = math::next_power_of_2(min_capacity);
self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity);
self.capacity = min_capacity;
}
<*
Set the element at Type index.
@param index : "The index where to set the value."
@param value : "The value to set"
@require index <= self.size : "Index out of range"
@require $defined(Type t = &value) : "Value must implement the interface"
*>
macro void InterfaceList.set(&self, usz index, value)
{
if (index == self.size)
{
self.push(value);
return;
}
self.free_element(self.entries[index]);
self.entries[index] = allocator::clone(self.allocator, value);
}
// -- private
fn void InterfaceList.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);
}
fn void InterfaceList._append(&self, Type element) @local
{
self.ensure_capacity();
self.entries[self.size++] = element;
}
<*
@require index < self.size
*>
fn void InterfaceList._insert_at(&self, usz index, Type 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;
}
macro usz InterfaceList._remove_using_test(&self, InterfaceTest 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;
}
macro usz InterfaceList._remove_if(&self, InterfacePredicate 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;
}

View File

@@ -0,0 +1,333 @@
module std::collections::blockingqueue { Value };
import std::thread, std::time;
const INITIAL_CAPACITY = 16;
struct QueueEntry
{
Value value;
<* Next in queue order *>
QueueEntry* next;
<* Previous in queue order *>
QueueEntry* prev;
}
struct LinkedBlockingQueue
{
<* First element in queue *>
QueueEntry* head;
<* Last element in queue *>
QueueEntry* tail;
<* Current number of elements *>
usz count;
<* Maximum capacity (0 for unbounded) *>
usz capacity;
Mutex lock;
ConditionVariable not_empty;
ConditionVariable not_full;
Allocator allocator;
}
<*
@param [&inout] allocator : "The allocator to use"
@param capacity : "Maximum capacity (0 for unbounded)"
@require !self.is_initialized() : "Queue was already initialized"
*>
fn LinkedBlockingQueue* LinkedBlockingQueue.init(&self, Allocator allocator, usz capacity = 0)
{
self.allocator = allocator;
self.capacity = capacity;
self.count = 0;
self.head = null;
self.tail = null;
self.lock.init()!!;
self.not_empty.init()!!;
self.not_full.init()!!;
return self;
}
fn LinkedBlockingQueue* LinkedBlockingQueue.tinit(&self, usz capacity = 0)
{
return self.init(tmem, capacity) @inline;
}
<*
@require self.is_initialized() : "Queue must be initialized"
*>
fn void LinkedBlockingQueue.free(&self)
{
self.lock.@in_lock()
{
// Free all remaining entries
QueueEntry* entry = self.head;
while (entry != null)
{
QueueEntry* next = entry.next;
allocator::free(self.allocator, entry);
entry = next;
}
};
(void)self.lock.destroy();
(void)self.not_empty.destroy();
(void)self.not_full.destroy();
}
fn void LinkedBlockingQueue.link_entry(&self, QueueEntry* entry) @private
{
entry.next = null;
entry.prev = self.tail;
if (self.tail == null)
{
// First element in queue
self.head = entry;
}
else
{
// Append to tail
self.tail.next = entry;
}
self.tail = entry;
self.count++;
}
fn QueueEntry* LinkedBlockingQueue.unlink_head(&self) @private
{
if (self.head == null) return null;
QueueEntry* entry = self.head;
self.head = entry.next;
if (self.head != null)
{
self.head.prev = null;
}
else
{
// Queue is now empty
self.tail = null;
}
self.count--;
return entry;
}
<*
@param value : "Value to add to the queue"
@require self.is_initialized() : "Queue must be initialized"
*>
fn void LinkedBlockingQueue.push(&self, Value value)
{
self.lock.@in_lock()
{
while (self.capacity > 0 && self.count >= self.capacity)
{
self.not_full.wait(&self.lock)!!;
}
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
.value = value,
.next = null,
.prev = null
});
self.link_entry(entry);
// Signal that queue is no longer empty
self.not_empty.signal()!!;
};
}
<*
Get a value from the queue, blocking if there is no element in the queue.
@require self.is_initialized() : "Queue must be initialized"
@return "The removed value"
*>
fn Value LinkedBlockingQueue.poll(&self)
{
self.lock.@in_lock()
{
while (self.count == 0)
{
self.not_empty.wait(&self.lock)!!;
}
QueueEntry* entry = self.unlink_head();
Value value = entry.value;
allocator::free(self.allocator, entry);
if (self.capacity > 0)
{
self.not_full.signal()!!;
}
return value;
};
}
<*
Pop an element from the queue, fail is it is empty.
@require self.is_initialized() : "Queue must be initialized"
@return "The removed value"
@return? NO_MORE_ELEMENT : "If the queue is empty"
*>
fn Value? LinkedBlockingQueue.pop(&self)
{
self.lock.@in_lock()
{
if (self.count == 0) return NO_MORE_ELEMENT?;
QueueEntry* entry = self.unlink_head();
Value value = entry.value;
allocator::free(self.allocator, entry);
if (self.capacity > 0)
{
self.not_full.signal()!!;
}
return value;
};
}
<*
Poll with a timeout.
@param timeout : "Timeout in microseconds"
@require self.is_initialized() : "Queue must be initialized"
@return "The removed value or null if timeout occurred"
@return? NO_MORE_ELEMENT : "If we reached the timeout"
*>
fn Value? LinkedBlockingQueue.poll_timeout(&self, Duration timeout)
{
self.lock.@in_lock()
{
// Use while loop to handle spurious wakeups
if (!self.count)
{
Time start = time::now();
Time end = start + timeout;
while (!self.count)
{
if (end <= time::now()) break;
if (catch self.not_empty.wait_until(&self.lock, end)) break;
}
if (!self.count) return NO_MORE_ELEMENT?;
}
QueueEntry* entry = self.unlink_head();
Value value = entry.value;
allocator::free(self.allocator, entry);
// Must signal not_full after removing an item
if (self.capacity > 0)
{
self.not_full.signal()!!;
}
return value;
};
}
<*
@require self.is_initialized() : "Queue must be initialized"
@return "Current size of the queue"
*>
fn usz LinkedBlockingQueue.size(&self)
{
self.lock.@in_lock()
{
return self.count;
};
}
<*
@require self.is_initialized() : "Queue must be initialized"
@return "True if queue is empty"
*>
fn bool LinkedBlockingQueue.is_empty(&self)
{
self.lock.@in_lock()
{
return self.count == 0;
};
}
<*
Try to push, return CAPACITY_EXCEEDED if the queue is full.
@param value : "Value to add to the queue"
@require self.is_initialized() : "Queue must be initialized"
@return? CAPACITY_EXCEEDED : "If the queue is full"
*>
fn void? LinkedBlockingQueue.try_push(&self, Value value)
{
self.lock.@in_lock()
{
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED?;
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
.value = value,
.next = null,
.prev = null
});
self.link_entry(entry);
self.not_empty.signal()!!;
};
}
<*
Try to push, return CAPACITY_EXCEEDED if the queue is still full after timeout is reached.
@param value : "Value to add to the queue"
@param timeout : "Timeout in microseconds"
@require self.is_initialized() : "Queue must be initialized"
@return? CAPACITY_EXCEEDED : "If the queue is full"
*>
fn void? LinkedBlockingQueue.push_timeout(&self, Value value, Duration timeout)
{
self.lock.@in_lock()
{
if (self.capacity > 0 && self.count >= self.capacity)
{
Time start = time::now();
Time end = start + timeout;
while (self.capacity > 0 && self.count >= self.capacity)
{
if (end <= time::now()) break;
if (catch self.not_empty.wait_until(&self.lock, end)) break;
}
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED?;
}
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
.value = value,
.next = null,
.prev = null
});
self.link_entry(entry);
self.not_empty.signal()!!;
};
}
<*
@require self.is_initialized() : "Queue must be initialized"
@return "The head value or NO_MORE_ELEMENT? if queue is empty"
*>
fn Value? LinkedBlockingQueue.peek(&self)
{
self.lock.@in_lock()
{
return (self.head != null) ? self.head.value : NO_MORE_ELEMENT?;
};
}
<*
@return "True if queue is initialized"
*>
fn bool LinkedBlockingQueue.is_initialized(&self)
{
return self.allocator && self.lock.initialized;
}

View File

@@ -15,9 +15,12 @@ struct LinkedEntry
uint hash;
Key key;
Value value;
LinkedEntry* next; // For bucket chain
LinkedEntry* before; // Previous in insertion order
LinkedEntry* after; // Next in insertion order
<* For bucket chain *>
LinkedEntry* next;
<* Previous in insertion order *>
LinkedEntry* before;
<* Next in insertion order *>
LinkedEntry* after;
}
struct LinkedHashMap (Printable)
@@ -27,8 +30,10 @@ struct LinkedHashMap (Printable)
usz count;
usz threshold;
float load_factor;
LinkedEntry* head; // First inserted LinkedEntry
LinkedEntry* tail; // Last inserted LinkedEntry
<* First inserted LinkedEntry *>
LinkedEntry* head;
<* Last inserted LinkedEntry *>
LinkedEntry* tail;
}
@@ -188,8 +193,9 @@ fn LinkedEntry*? LinkedHashMap.get_entry(&map, Key key)
}
<*
Get the value or update and
@require @assignable_to(#expr, Value)
Get the value or set it to the value
@require $defined(Value val = #expr)
*>
macro Value LinkedHashMap.@get_or_set(&map, Key key, Value #expr)
{
@@ -248,7 +254,7 @@ fn void? LinkedHashMap.remove(&map, Key key) @maydiscard
fn void LinkedHashMap.clear(&map)
{
if (!map.count) return;
LinkedEntry* entry = map.head;
while (entry)
{
@@ -256,12 +262,12 @@ fn void LinkedHashMap.clear(&map)
map.free_entry(entry);
entry = next;
}
foreach (LinkedEntry** &bucket : map.table)
{
*bucket = null;
}
map.count = 0;
map.head = null;
map.tail = null;
@@ -283,10 +289,10 @@ fn Key[] LinkedHashMap.tkeys(&self)
fn Key[] LinkedHashMap.keys(&self, Allocator allocator)
{
if (!self.count) return {};
Key[] list = allocator::alloc_array(allocator, Key, self.count);
usz index = 0;
LinkedEntry* entry = self.head;
while (entry)
{
@@ -337,7 +343,7 @@ fn Value[] LinkedHashMap.values(&self, Allocator allocator)
fn bool LinkedHashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
{
if (!map.count) return false;
LinkedEntry* entry = map.head;
while (entry)
{
@@ -395,7 +401,7 @@ fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint buck
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, {
.hash = hash,
.key = key,
@@ -404,10 +410,10 @@ fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint buck
.before = map.tail,
.after = null
});
// Update bucket chain
map.table[bucket_index] = entry;
// Update linked list
if (map.tail)
{
@@ -419,7 +425,7 @@ fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint buck
map.head = entry;
}
map.tail = entry;
if (map.count++ >= map.threshold)
{
map.resize(map.table.len * 2);
@@ -430,28 +436,28 @@ fn void LinkedHashMap.resize(&map, uint new_capacity) @private
{
LinkedEntry*[] old_table = map.table;
uint old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
map.threshold = uint.max;
return;
}
LinkedEntry*[] new_table = allocator::new_array(map.allocator, LinkedEntry*, new_capacity);
map.table = new_table;
map.threshold = (uint)(new_capacity * map.load_factor);
// Rehash all entries - linked list order remains unchanged
foreach (uint i, LinkedEntry *e : old_table)
{
if (!e) continue;
// Split the bucket chain into two chains based on new bit
LinkedEntry* lo_head = null;
LinkedEntry* lo_tail = null;
LinkedEntry* hi_head = null;
LinkedEntry* hi_tail = null;
do
{
LinkedEntry* next = e.next;
@@ -483,7 +489,7 @@ fn void LinkedHashMap.resize(&map, uint new_capacity) @private
e = next;
}
while (e);
if (lo_tail)
{
lo_tail.next = null;
@@ -495,7 +501,7 @@ fn void LinkedHashMap.resize(&map, uint new_capacity) @private
new_table[i + old_capacity] = hi_head;
}
}
map.free_internal(old_table.ptr);
}
@@ -561,12 +567,12 @@ fn void LinkedHashMap.free_internal(&map, void* ptr) @inline @private
fn bool LinkedHashMap.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);
LinkedEntry* prev = null;
LinkedEntry* e = map.table[i];
while (e)
{
if (e.hash == hash && equals(key, e.key))
@@ -579,7 +585,7 @@ fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
{
map.table[i] = e.next;
}
if (e.before)
{
e.before.after = e.after;
@@ -588,7 +594,7 @@ fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
{
map.head = e.after;
}
if (e.after)
{
e.after.before = e.before;
@@ -597,7 +603,7 @@ fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
{
map.tail = e.before;
}
map.count--;
map.free_entry(e);
return true;

View File

@@ -11,20 +11,27 @@ struct LinkedEntry
{
uint hash;
Value value;
LinkedEntry* next; // For bucket chain
LinkedEntry* before; // Previous in insertion order
LinkedEntry* after; // Next in insertion order
<* For bucket chain *>
LinkedEntry* next;
<* Previous in insertion order *>
LinkedEntry* before;
<* Next in insertion order *>
LinkedEntry* after;
}
struct LinkedHashSet (Printable)
{
LinkedEntry*[] table;
Allocator allocator;
usz count; // Number of elements
usz threshold; // Resize limit
<* Number of elements *>
usz count;
<* Resize limit *>
usz threshold;
float load_factor;
LinkedEntry* head; // First inserted LinkedEntry
LinkedEntry* tail; // Last inserted LinkedEntry
<* Resize limit *>
LinkedEntry* head;
<* First inserted LinkedEntry *>
LinkedEntry* tail;
}
fn int LinkedHashSet.len(&self) @operator(len) => (int) self.count;
@@ -43,7 +50,7 @@ fn LinkedHashSet* LinkedHashSet.init(&self, Allocator allocator, usz capacity =
self.threshold = (usz)(capacity * load_factor);
self.load_factor = load_factor;
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
self.head = null;
self.tail = null;
return self;
@@ -304,7 +311,7 @@ fn void LinkedHashSet.free(&set)
fn void LinkedHashSet.clear(&set)
{
if (!set.count) return;
LinkedEntry* entry = set.head;
while (entry)
{
@@ -312,12 +319,12 @@ fn void LinkedHashSet.clear(&set)
set.free_entry(entry);
entry = next;
}
foreach (LinkedEntry** &bucket : set.table)
{
*bucket = null;
}
set.count = 0;
set.head = null;
set.tail = null;
@@ -363,16 +370,16 @@ fn LinkedHashSet LinkedHashSet.intersection(&self, Allocator allocator, LinkedHa
{
LinkedHashSet result;
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
// Iterate through the smaller set for efficiency
LinkedHashSet* smaller = self.count <= other.count ? self : other;
LinkedHashSet* larger = self.count > other.count ? self : other;
smaller.@each(;Value value)
smaller.@each(;Value value)
{
if (larger.contains(value)) result.add(value);
};
return result;
}
@@ -435,7 +442,7 @@ fn bool LinkedHashSet.is_subset(&self, LinkedHashSet* other)
{
if (self.count == 0) return true;
if (self.count > other.count) return false;
self.@each(; Value value) {
if (!other.contains(value)) return false;
};
@@ -453,10 +460,10 @@ fn void LinkedHashSet.add_entry(&set, uint hash, Value value, uint bucket_index)
.before = set.tail,
.after = null
});
// Update bucket chain
set.table[bucket_index] = entry;
// Update linked list
if (set.tail)
{
@@ -468,7 +475,7 @@ fn void LinkedHashSet.add_entry(&set, uint hash, Value value, uint bucket_index)
set.head = entry;
}
set.tail = entry;
if (set.count++ >= set.threshold)
{
set.resize(set.table.len * 2);
@@ -479,28 +486,28 @@ fn void LinkedHashSet.resize(&set, usz new_capacity) @private
{
LinkedEntry*[] old_table = set.table;
usz old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
set.threshold = uint.max;
return;
}
LinkedEntry*[] new_table = allocator::new_array(set.allocator, LinkedEntry*, new_capacity);
set.table = new_table;
set.threshold = (uint)(new_capacity * set.load_factor);
// Rehash all entries - linked list order remains unchanged
foreach (uint i, LinkedEntry *e : old_table)
{
if (!e) continue;
// Split the bucket chain into two chains based on new bit
LinkedEntry* lo_head = null;
LinkedEntry* lo_tail = null;
LinkedEntry* hi_head = null;
LinkedEntry* hi_tail = null;
do
{
LinkedEntry* next = e.next;
@@ -532,7 +539,7 @@ fn void LinkedHashSet.resize(&set, usz new_capacity) @private
e = next;
}
while (e);
if (lo_tail)
{
lo_tail.next = null;
@@ -544,7 +551,7 @@ fn void LinkedHashSet.resize(&set, usz new_capacity) @private
new_table[i + old_capacity] = hi_head;
}
}
set.free_internal(old_table.ptr);
}
@@ -601,16 +608,16 @@ fn void LinkedHashSet.free_internal(&set, void* ptr) @inline @private
fn void LinkedHashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
{
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
.hash = hash,
.value = value,
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
.hash = hash,
.value = value,
.next = set.table[bucket_index],
.before = set.tail,
.after = null
});
set.table[bucket_index] = entry;
// Update linked list
if (set.tail)
{
@@ -622,19 +629,19 @@ fn void LinkedHashSet.create_entry(&set, uint hash, Value value, int bucket_inde
set.head = entry;
}
set.tail = entry;
set.count++;
}
fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
{
if (!set.count) return false;
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
LinkedEntry* prev = null;
LinkedEntry* e = set.table[i];
while (e)
{
if (e.hash == hash && equals(value, e.value))
@@ -647,7 +654,7 @@ fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
{
set.table[i] = e.next;
}
if (e.before)
{
e.before.after = e.after;
@@ -656,7 +663,7 @@ fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
{
set.head = e.after;
}
if (e.after)
{
e.after.before = e.before;
@@ -665,7 +672,7 @@ fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
{
set.tail = e.before;
}
set.count--;
set.free_entry(e);
return true;
@@ -715,9 +722,9 @@ fn bool LinkedHashSetIterator.has_next(&self)
return self.current && self.current.after != null;
}
fn usz LinkedHashSetIterator.len(&self) @operator(len)
fn usz LinkedHashSetIterator.len(&self) @operator(len)
{
return self.set.count;
}
int dummy @local;
int dummy @local;

View File

@@ -2,13 +2,14 @@
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::linkedlist{Type};
import std::io;
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
struct Node @private
struct Node
{
Node *next;
Node *prev;
Node* next;
Node* prev;
Type value;
}
@@ -16,8 +17,31 @@ struct LinkedList
{
Allocator allocator;
usz size;
Node *_first;
Node *_last;
Node* _first;
Node* _last;
}
fn usz? LinkedList.to_format(&self, Formatter* f) @dynamic
{
usz len = f.print("{ ")!;
for (Node* node = self._first; node != null; node.next)
{
len += f.printf(node.next ? "%s, " : "s", node.value)!;
}
return len + f.print(" }");
}
macro LinkedList @new(Allocator allocator, Type[] #default_values = {})
{
LinkedList new_list;
new_list.init(allocator);
new_list.push_all(#default_values);
return new_list;
}
macro LinkedList @tnew(Type[] #default_values = {})
{
return @new(tmem, #default_values);
}
<*
@@ -68,6 +92,11 @@ fn void LinkedList.push_front(&self, Type value)
self.size++;
}
fn void LinkedList.push_front_all(&self, Type[] value)
{
foreach_r (v : value) self.push_front(v);
}
fn void LinkedList.push(&self, Type value)
{
Node *last = self._last;
@@ -85,6 +114,11 @@ fn void LinkedList.push(&self, Type value)
self.size++;
}
fn void LinkedList.push_all(&self, Type[] value)
{
foreach (v : value) self.push(v);
}
fn Type? LinkedList.peek(&self) => self.first() @inline;
fn Type? LinkedList.peek_last(&self) => self.last() @inline;
@@ -133,6 +167,7 @@ macro Node* LinkedList.node_at_index(&self, usz index)
while (index--) node = node.next;
return node;
}
<*
@require index < self.size
*>
@@ -141,6 +176,14 @@ fn Type LinkedList.get(&self, usz index)
return self.node_at_index(index).value;
}
<*
@require index < self.size
*>
fn Type* LinkedList.get_ref(&self, usz index)
{
return &self.node_at_index(index).value;
}
<*
@require index < self.size
*>
@@ -149,6 +192,26 @@ fn void LinkedList.set(&self, usz index, Type element)
self.node_at_index(index).value = element;
}
fn usz? LinkedList.index_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
for (Node* node = self._first, usz i = 0; node != null; node = node.next, ++i)
{
if (node.value == t) return i;
}
return NOT_FOUND?;
}
fn usz? LinkedList.rindex_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
for (Node* node = self._last, usz i = self.size - 1; node != null; node = node.prev, --i)
{
if (node.value == t) return i;
if (i == 0) break;
}
return NOT_FOUND?;
}
<*
@require index < self.size
*>
@@ -334,3 +397,69 @@ fn void LinkedList.unlink(&self, Node* x) @private
self.free_node(x);
self.size--;
}
macro bool LinkedList.eq(&self, other) @operator(==) @if(ELEMENT_IS_EQUATABLE)
{
Node* node1 = self._first;
Node* node2 = other._first;
while (true)
{
if (!node1) return node2 == null;
if (!node2) return false;
if (node1.value != node2.value) return false;
node1 = node1.next;
node2 = node2.next;
}
return true;
}
fn LinkedListArrayView LinkedList.array_view(&self)
{
return { .list = self, .current_node = self._first };
}
struct LinkedListArrayView
{
LinkedList* list;
Node* current_node;
usz current_index;
}
fn usz LinkedListArrayView.len(&self) @operator(len) => self.list.size;
<*
@require index < self.list.size
*>
fn Type LinkedListArrayView.get(&self, usz index) @operator([])
{
return *self.get_ref(index);
}
<*
@require index < self.list.size
*>
fn Type* LinkedListArrayView.get_ref(&self, usz index) @operator(&[])
{
if (index == self.list.size - 1)
{
self.current_node = self.list._last;
self.current_index = index;
}
while (self.current_index != index)
{
switch
{
case index < self.current_index: // reverse iteration
self.current_node = self.current_node.prev;
self.current_index--;
case index > self.current_index:
self.current_node = self.current_node.next;
self.current_index++;
}
}
return &self.current_node.value;
}

View File

@@ -13,7 +13,7 @@ const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR };
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
struct List (Printable)
{
@@ -57,7 +57,7 @@ fn List* List.tinit(&self, usz initial_capacity = 16)
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
{
self.init(allocator, values.len) @inline;
self.add_array(values) @inline;
self.push_all(values) @inline;
return self;
}
@@ -70,7 +70,7 @@ fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
fn List* List.tinit_with_array(&self, Type[] values)
{
self.tinit(values.len) @inline;
self.add_array(values) @inline;
self.push_all(values) @inline;
return self;
}
@@ -137,9 +137,10 @@ fn Type? List.pop_first(&self)
*>
fn void List.remove_at(&self, usz index)
{
self.set_size(self.size - 1);
if (!self.size || index == self.size) return;
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
usz new_size = self.size - 1;
defer self.set_size(new_size);
if (!new_size || index == new_size) return;
self.entries[index .. new_size - 1] = self.entries[index + 1 .. new_size];
}
fn void List.add_all(&self, List* other_list)
@@ -198,7 +199,21 @@ fn Type[] List.array_view(&self)
@param [in] array
@ensure self.size >= array.len
*>
fn void List.add_array(&self, Type[] array)
fn void List.add_array(&self, Type[] array) @deprecated("Use push_all")
{
if (!array.len) return;
self.reserve(array.len);
usz index = self.set_size(self.size + array.len);
self.entries[index : array.len] = array[..];
}
<*
Add the values of an array to this list.
@param [in] array
@ensure self.size >= array.len
*>
fn void List.push_all(&self, Type[] array)
{
if (!array.len) return;
self.reserve(array.len);

View File

@@ -185,7 +185,7 @@ fn void Object.set_object(&self, String key, Object* new_object) @private
<*
@require self.allocator != null : "This object is not properly initialized, was it really created using 'new'"
@require !@typeis(value, void*) ||| value == null : "void pointers cannot be stored in an object"
@require $typeof(value) != void* ||| value == null : "void pointers cannot be stored in an object"
*>
macro Object* Object.object_from_value(&self, value) @private
{
@@ -203,7 +203,7 @@ macro Object* Object.object_from_value(&self, value) @private
return value;
$case $Type.typeid == void*.typeid:
return &NULL_OBJECT;
$case @assignable_to(value, String):
$case $defined(String x = value):
return new_string(value, self.allocator);
$default:
$error "Unsupported object type.";

View File

@@ -1,16 +1,22 @@
module std::collections::pair{Type1, Type2};
import std::io;
struct Pair
struct Pair (Printable)
{
Type1 first;
Type2 second;
}
fn usz? Pair.to_format(&self, Formatter* f) @dynamic
{
return f.printf("{ %s, %s }", self.first, self.second);
}
<*
@param [&out] a
@param [&out] b
@require @assignable_to(self.first, $typeof(*a)) : "You cannot assign the first value to a"
@require @assignable_to(self.second, $typeof(*b)) : "You cannot assign the second value to b"
@require $defined(*a = self.first) : "You cannot assign the first value to a"
@require $defined(*b = self.second) : "You cannot assign the second value to b"
*>
macro void Pair.unpack(&self, a, b)
{
@@ -18,22 +24,34 @@ macro void Pair.unpack(&self, a, b)
*b = self.second;
}
module std::collections::triple{Type1, Type2, Type3};
fn bool Pair.equal(self, Pair other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2))
{
return self.first == other.first && self.second == other.second;
}
struct Triple
module std::collections::triple{Type1, Type2, Type3};
import std::io;
struct Triple (Printable)
{
Type1 first;
Type2 second;
Type3 third;
}
fn usz? Triple.to_format(&self, Formatter* f) @dynamic
{
return f.printf("{ %s, %s, %s }", self.first, self.second, self.third);
}
<*
@param [&out] a
@param [&out] b
@param [&out] c
@require @assignable_to(self.first, $typeof(*a)) : "You cannot assign the first value to a"
@require @assignable_to(self.second, $typeof(*b)) : "You cannot assign the second value to b"
@require @assignable_to(self.third, $typeof(*c)) : "You cannot assign the second value to c"
@require $defined(*a = self.first) : "You cannot assign the first value to a"
@require $defined(*b = self.second) : "You cannot assign the second value to b"
@require $defined(*c = self.third) : "You cannot assign the second value to c"
*>
macro void Triple.unpack(&self, a, b, c)
{
@@ -42,6 +60,12 @@ macro void Triple.unpack(&self, a, b, c)
*c = self.third;
}
fn bool Triple.equal(self, Triple other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2) &&& types::has_equals(Type3))
{
return self.first == other.first && self.second == other.second && self.third == other.third;
}
module std::collections::tuple{Type1, Type2};
struct Tuple @deprecated("Use 'Pair' instead")

View File

@@ -7,10 +7,12 @@ const uint PIXELS_MAX = 400000000;
Purely informative. It will be saved to the file header,
but does not affect how chunks are en-/decoded.
*>
enum QOIColorspace : char (char id)
enum QOIColorspace : const char
{
SRGB = 0, // sRGB with linear alpha
LINEAR = 1 // all channels linear
<* sRGB with linear alpha *>
SRGB = 0,
<* all channels linear *>
LINEAR = 1
}
<*
@@ -19,7 +21,7 @@ enum QOIColorspace : char (char id)
AUTO can be used when decoding to automatically determine
the channels from the file's header.
*>
enum QOIChannels : char (char id)
enum QOIChannels : const inline char
{
AUTO = 0,
RGB = 3,
@@ -132,7 +134,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
// check input data size
uint image_size = pixels * desc.channels.id;
uint image_size = pixels * desc.channels;
if (image_size != input.len) return INVALID_DATA?;
// allocate memory for encoded data (output)
@@ -146,13 +148,13 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
.be_magic = bswap('qoif'),
.be_width = bswap(desc.width),
.be_height = bswap(desc.height),
.channels = desc.channels.id,
.colorspace = desc.colorspace.id
.channels = desc.channels,
.colorspace = desc.colorspace
};
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
uint loc_end = image_size - desc.channels; // End of image data
char run_length = 0; // Length of the current run
Pixel[64] palette; // Zero-initialized by default
@@ -163,7 +165,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
ichar[<3>] luma; // ...and luma
// write chunks
for (loc = 0; loc < image_size; loc += desc.channels.id)
for (loc = 0; loc < image_size; loc += desc.channels)
{
// set previous pixel
prev = p;
@@ -292,8 +294,8 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c
// 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
desc.channels = header.channels;
desc.colorspace = header.colorspace;
if (desc.channels == AUTO) return INVALID_DATA?; // Channels must be specified in the header
// check width and height
@@ -314,11 +316,11 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c
if (channels == AUTO) channels = desc.channels;
// allocate memory for image data
usz image_size = (usz)pixels * channels.id;
usz image_size = (usz)pixels * channels;
char[] image = allocator::alloc_array(allocator, char, image_size);
defer catch allocator::free(allocator, image);
for (loc = 0; loc < image_size; loc += channels.id)
for (loc = 0; loc < image_size; loc += channels)
{
// get chunk tag
tag = data[pos];
@@ -391,31 +393,22 @@ 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)
<* magic bytes "qoif" *>
uint be_magic;
<* image width in pixels (BE) *>
uint be_width;
<* image height in pixels (BE) *>
uint be_height;
// informative fields
char channels; // 3 = RGB, 4 = RGB
char colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
<* 3 = RGB, 4 = RGB *>
QOIChannels channels;
<* 0 = sRGB with linear alpha, 1 = all channels linear *>
QOIColorspace colorspace;
}
const char[*] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1};
// inefficient, but it's only run once at a time
<*
@return? INVALID_DATA
*>
macro @enumcast($Type, raw)
{
foreach (value : $Type.values)
{
if (value.id == raw) return value;
}
return INVALID_DATA?;
}
typedef Pixel = inline char[<4>];
macro char Pixel.hash(Pixel p)
{

View File

@@ -13,7 +13,7 @@ import std::math;
The advantage over the BackedArenaAllocator, is that when allocating beyond the first "page", it will
retain the characteristics of an arena allocator (allocating a large piece of memory then handing off
memory from that memory), wheras the BackedArenaAllocator will have heap allocator characteristics.
memory from that memory), whereas the BackedArenaAllocator will have heap allocator characteristics.
*>
struct DynamicArenaAllocator (Allocator)
{

View File

@@ -19,7 +19,7 @@ alias AllocMap = HashMap { uptr, Allocation };
// It tracks allocations using a hash map but
// is not compatible with allocators that uses mark()
//
// It is also embarassingly single-threaded, so
// It is also embarrassingly single-threaded, so
// do not use it to track allocations that cross threads.
struct TrackingAllocator (Allocator)
@@ -216,4 +216,4 @@ fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
}
}
}
}
}

View File

@@ -22,9 +22,12 @@ struct Vmem (Allocator)
bitstruct VmemOptions : int
{
bool shrink_on_reset; // Release memory on reset
bool protect_unused_pages; // Protect unused pages on reset
bool scratch_released_data; // Overwrite released data with 0xAA
<* Release memory on reset *>
bool shrink_on_reset;
<* Protect unused pages on reset *>
bool protect_unused_pages;
<* Overwrite released data with 0xAA *>
bool scratch_released_data;
}
<*
@@ -38,11 +41,11 @@ bitstruct VmemOptions : int
fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOptions options = { true, true, env::COMPILER_SAFE_MODE }, usz min_size = 0)
{
static usz page_size = 0;
if (!page_size) page_size = mem::os_pagesize();
if (page_size < reserve_page_size) page_size = reserve_page_size;
preferred_size = mem::aligned_offset(preferred_size, page_size);
if (!min_size) min_size = max(preferred_size / 1024, 1);
VirtualMemory? memory = mem::OUT_OF_MEMORY?;
if (!page_size) page_size = mem::os_pagesize();
if (page_size < reserve_page_size) page_size = reserve_page_size;
preferred_size = mem::aligned_offset(preferred_size, page_size);
if (!min_size) min_size = max(preferred_size / 1024, 1);
VirtualMemory? memory = mem::OUT_OF_MEMORY?;
while (preferred_size >= min_size)
{
memory = vm::virtual_alloc(preferred_size, PROTECTED);

View File

@@ -1,13 +1,13 @@
module std::core::array;
import std::core::array::slice;
import std::collections::pair, std::io;
<*
Returns true if the array contains at least one element, else false
@param [in] array
@param [in] element
@require @typekind(array) == SLICE || @typekind(array) == ARRAY
@require @typeis(array[0], $typeof(element)) : "array and element must have the same type"
@require $kindof(array) == SLICE || $kindof(array) == ARRAY
@require @typematch(array[0], element) : "array and element must have the same type"
*>
macro bool contains(array, element)
{
@@ -15,18 +15,21 @@ macro bool contains(array, element)
{
if (*item == element) return true;
}
return false;
return false;
}
<*
Return the first index of element found in the array, searching from the start.
@param [in] array
@param [in] element
@require $kindof(array) == SLICE || $kindof(array) == ARRAY
@require @typematch(array[0], element) : "array and element must have the same type"
@return "the first index of the element"
@return? NOT_FOUND
*>
macro index_of(array, element)
macro usz? index_of(array, element)
{
foreach (i, &e : array)
{
@@ -35,6 +38,7 @@ macro index_of(array, element)
return NOT_FOUND?;
}
<*
Slice a 2d array and create a Slice2d from it.
@@ -44,9 +48,9 @@ macro index_of(array, element)
@param xlen : "The length of the slice in x, defaults to the length of the array"
@param ylen : "The length of the slice in y, defaults to the length of the array"
@return "A Slice2d from the array"
@require @typekind(array_ptr) == POINTER
@require @typekind(*array_ptr) == VECTOR || @typekind(*array_ptr) == ARRAY
@require @typekind((*array_ptr)[0]) == VECTOR || @typekind((*array_ptr)[0]) == ARRAY
@require $kindof(array_ptr) == POINTER
@require $kindof(*array_ptr) == VECTOR || $kindof(*array_ptr) == ARRAY
@require $kindof((*array_ptr)[0]) == VECTOR || $kindof((*array_ptr)[0]) == ARRAY
*>
macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
{
@@ -59,13 +63,13 @@ macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
<*
Return the first index of element found in the array, searching in reverse from the end.
@param [in] array
@param [in] element
@return "the last index of the element"
@return? NOT_FOUND
*>
macro rindex_of(array, element)
macro usz? rindex_of(array, element)
{
foreach_r (i, &e : array)
{
@@ -74,15 +78,16 @@ macro rindex_of(array, element)
return NOT_FOUND?;
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
@param [in] arr1
@param [in] arr2
@param [&inout] allocator : "The allocator to use, default is the heap allocator"
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
@require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len
*>
macro concat(Allocator allocator, arr1, arr2) @nodiscard
@@ -99,15 +104,450 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
}
return result;
}
<*
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"
@require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type"
@ensure return.len == arr1.len + arr2.len
*>
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
<*
Apply a reduction/folding operation to an iterable type. This walks along the input array
and applies an `#operation` to each value, returning it to the `identity` (or "accumulator")
base value.
For example:
```c3
int[] my_slice = { 1, 8, 12 };
int folded = array::@reduce(my_slice, 2, fn (i, e) => i * e);
assert(folded == (2 * 1 * 8 * 12));
```
Notice how the given `identity` value started the multiplication chain at 2. When enumerating
`my_slice`, each element is accumulated onto the `identity` value with each sequential iteration.
```
i = 2; // identity value
i *= 1; // my_slice[0]
i *= 8; // my_slice[1]
i *= 12; // my_slice[2]
```
@param [in] array
@param identity
@param #operation : "The reduction/folding lambda function or function pointer to apply."
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@reduce_fn(array, identity)) $func = #operation) : "Invalid lambda or function pointer type"
*>
macro @reduce(array, identity, #operation)
{
$typefrom(@reduce_fn(array, identity)) $func = #operation;
foreach (index, element : array) identity = $func(identity, element, index);
return identity;
}
<*
Apply a summation operator (+) to an identity value across a span of array elements
and return the final accumulated result.
@pure
@param [in] array
@param identity_value : "The base accumulator value to use for the sum"
@require @is_valid_list(array) : "Expected a valid list"
@require $defined(array[0] + array[0]) : "Array element type must implement the '+' operator"
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
*>
macro @sum(array, identity_value = 0)
{
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc + e);
}
<*
Apply a product operator (*) to an identity value across a span of array elements
and return the final accumulated result.
@pure
@param [in] array
@param identity_value : "The base accumulator value to use for the product"
@require @is_valid_list(array) : "Expected a valid list"
@require $defined(array[0] * array[0]) : "Array element type must implement the '*' operator"
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
*>
macro @product(array, identity_value = 1)
{
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc * e);
}
<*
Applies a given predicate function to each element of an array and returns a new
array of `usz` values, each element representing an index within the original array
where the predicate returned `true`.
The `.len` value of the returned array can also be used to quickly identify how many
input array elements matched the predicate.
For example:
```c3
int[] arr = { 0, 20, 4, 30 };
int[] matched_indices = array::@indices_of(mem, arr, fn (u, a) => a > 10);
```
The `matched_indices` variable should contain a dynamically-allocated array of `[1, 3]`,
and thus its count indicates that 2 of the 4 elements matched the predicate condition.
@param [&inout] allocator
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro usz[] @indices_of(Allocator allocator, array, #predicate)
{
usz[] results = allocator::new_array(allocator, usz, lengthof(array));
usz matches;
$typefrom(@predicate_fn(array)) $predicate = #predicate;
foreach (index, element : array)
{
if ($predicate(element, index)) results[matches++] = index;
}
return results[:matches];
}
<*
Array `@indices_of` using the temp allocator.
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro usz[] @tindices_of(array, #predicate)
{
return @indices_of(tmem, array, #predicate);
}
<*
Applies a predicate function to each element of an input array and returns a new array
containing shallow copies of _only_ the elements for which the predicate function returned
a `true` value.
For example:
```c3
int[] my_arr = { 1, 2, 4, 10, 11, 45 };
int[] evens = array::@filter(mem, my_arr, fn (e, u) => !(e % 2));
assert(evens == (int[]){2, 4, 10 });
```
@param [&inout] allocator
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro @filter(Allocator allocator, array, #predicate) @nodiscard
{
var $InnerType = $typeof(array[0]);
usz[] matched_indices = @indices_of(allocator, array, #predicate);
defer allocator::free(allocator, matched_indices.ptr); // can free this upon leaving this call
if (!matched_indices.len) return ($InnerType[]){};
$InnerType[] result = allocator::new_array(allocator, $InnerType, matched_indices.len);
foreach (i, index : matched_indices) result[i] = array[index];
return result;
}
<*
Array `@filter` using the temp allocator.
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro @tfilter(array, #predicate) @nodiscard
{
return @filter(tmem, array, #predicate);
}
<*
Returns `true` if _any_ element of the input array returns `true` when
the `#predicate` function is applied.
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro bool @any(array, #predicate)
{
$typefrom(@predicate_fn(array)) $predicate = #predicate;
foreach (index, element : array) if ($predicate(element, index)) return true;
return false;
}
<*
Returns `true` if _all_ elements of the input array return `true` when
the `#predicate` function is applied.
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro bool @all(array, #predicate)
{
$typefrom(@predicate_fn(array)) $predicate = #predicate;
foreach (index, element : array) if (!$predicate(element, index)) return false;
return true;
}
<*
Zip together two separate arrays/slices into a single array of Pairs or return values. Values will
be collected up to the length of the shorter array if `fill_with` is left undefined; otherwise, they
will be collected up to the length of the LONGER array, with missing values in the shorter array being
assigned to the value of `fill_with`. Return array elements do not have to be of the same type.
For example:
```c3
uint[] chosen_session_ids = server::get_random_sessions(instance)[:128];
String[200] refreshed_session_keys = prng::new_keys_batch();
Pair { uint, String }[] sessions_meta = array::zip(mem, chosen_session_ids, refreshed_session_keys);
// The resulting Pair{}[] slice is then length of the shortest of the two arrays, so 128.
foreach (i, &sess : sessions:meta) {
// distribute new session keys to associated instance IDs
}
```
Or:
```c3
String[] client_names = server::online_usernames(instance);
uint128[] session_ids = server::user_keys();
// in this example, we 'know' ahead of time that 'session_ids' can only ever be SHORTER
// than 'client_names', but never longer, because it's possible new users have logged
// in without getting whatever this 'session ID' is delegated to them.
Pair { String, uint128 }[] zipped = array::tzip(client_names, session_ids, fill_with: uint128.max);
server::refresh_session_keys_by_pair(zipped)!;
```
### When an `operation` is supplied...
Apply an operation to each element of two slices or arrays and return the results of
each operation into a newly allocated array.
This essentially combines Iterable1 with Iterable2 using the `operation` functor.
See the functional `zipWith` construct, which has a more appropriate name than, e.g., `map`;
a la: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#v:zipWith
Similar to "normal" `zip`, this macro pads the shorter input array with a given `fill_with`, or
an empty value if one isn't supplied. This `fill_with` is supplied to the `operation` functor
_BEFORE_ calculating its result while zipping.
For example: a functor of `fn char (char a, char b) => a + b` with a `fill_with` of 7,
where the `left` array is the shorter iterable, will put 7 into that lambda in each place
where `left` is being filled in during the zip operation.
@param [&inout] allocator : "The allocator to use; default is the heap allocator."
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
@require @is_valid_operation(left, right, ...#operation) : "The operator must take two parameters matching the elements of the left and right side"
@require @is_valid_fill(left, right, ...fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
*>
macro @zip(Allocator allocator, left, right, #operation = ..., fill_with = ...) @nodiscard
{
var $LeftType = $typeof(left[0]);
var $RightType = $typeof(right[0]);
var $Type = Pair { $LeftType, $RightType };
bool $is_op = $defined(#operation);
$if $is_op:
$Type = $typeof(#operation).returns;
$endif
usz left_len = lengthof(left);
usz right_len = lengthof(right);
$LeftType left_fill;
$RightType right_fill;
usz result_len = min(left_len, right_len);
$if $defined(fill_with):
switch
{
case left_len > right_len:
$if !$defined(($RightType)fill_with):
unreachable();
$else
right_fill = ($RightType)fill_with;
result_len = left_len;
$endif
case left_len < right_len:
$if !$defined(($LeftType)fill_with):
unreachable();
$else
left_fill = ($LeftType)fill_with;
result_len = right_len;
$endif
}
$endif
if (result_len == 0) return ($Type[]){};
$Type[] result = allocator::alloc_array(allocator, $Type, result_len);
foreach (idx, &item : result)
{
$if $is_op:
var $LambdaType = $typeof(fn $Type ($LeftType a, $RightType b) => ($Type){});
$LambdaType $operation = ($LambdaType)#operation;
$LeftType lval = idx >= left_len ? left_fill : left[idx];
$RightType rval = idx >= right_len ? right_fill : right[idx];
*item = $operation(lval, rval);
$else
*item = {
idx >= left_len ? left_fill : left[idx],
idx >= right_len ? right_fill : right[idx]
};
$endif
}
return result;
}
<*
Array 'zip' using the temp allocator.
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
@require @is_valid_operation(left, right, ...#operation) : "The operator must take two parameters matching the elements of the left and right side"
@require @is_valid_fill(left, right, ...fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
*>
macro @tzip(left, right, #operation = ..., fill_with = ...) @nodiscard
{
return @zip(tmem, left, right, #operation: ...#operation, fill_with: ...fill_with);
}
<*
Apply an operation to each element of two slices or arrays and store the results of
each operation into the 'left' value.
This is useful because no memory allocations are required in order to perform the operation.
A good example of using this might be using algorithmic transformations on data in-place:
```
char[] partial_cipher = get_next_plaintext_block();
array::@zip_into(
partial_cipher[ENCRYPT_OFFSET:BASE_KEY.len],
BASE_KEY,
fn char (char a, char b) => a ^ (b * 5) % 37
);
```
This parameterizes the lambda function with left (`partial_cipher`) and right (`BASE_KEY`) slice
elements and stores the end result in-place within the left slice. This is in contrast to a
regular `zip_with` which will create a cloned final result and return it.
@param [inout] left : `Slice to store results of applied functor/operation.`
@param [in] right : `Slice to apply in the functor/operation.`
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
@require @is_valid_list(left) : "Expected a valid list"
@require @is_valid_list(right) : "Expected a valid list"
@require lengthof(right) >= lengthof(left) : `Right side length must be >= the destination (left) side length; consider using a sub-array of data for the assignment.`
@require $defined($typefrom(@zip_into_fn(left, right)) x = #operation) : "The functor must use the same types as the `left` and `right` inputs, and return a value of the `left` type."
*>
macro @zip_into(left, right, #operation)
{
$typefrom(@zip_into_fn(left, right)) $operation = #operation;
foreach (i, &v : left) *v = $operation(left[i], right[i]);
}
// --- helper functions
module std::core::array @private;
macro typeid @predicate_fn(#array) @const
{
return $typeof(fn bool ($typeof(#array[0]) a, usz index = 0) => true).typeid;
}
macro typeid @reduce_fn(#array, #identity) @const
{
return @typeid(fn $typeof(#identity) ($typeof(#identity) i, $typeof(#array[0]) a, usz index = 0) => i);
}
macro typeid @zip_into_fn(#left, #right) @const
{
return @typeid(fn $typeof(#left[0]) ($typeof(#left[0]) l, $typeof(#right[0]) r) => l);
}
macro bool @is_valid_operation(#left, #right, #operation = ...) @const
{
$switch:
$case !$defined(#operation):
return true;
$case $kindof(#operation) != FUNC:
return false;
$default:
return $defined(#operation(#left[0], #right[0]));
$endswitch
}
macro bool @is_valid_list(#expr) @const
{
return $defined(#expr[0], lengthof(#expr));
}
macro bool @is_valid_fill(left, right, fill_with = ...)
{
$if !$defined(fill_with):
return true;
$else
usz left_len = lengthof(left);
usz right_len = lengthof(right);
if (left_len == right_len) return true;
return left_len > right_len ? $defined(($typeof(right[0]))fill_with) : $defined(($typeof(left[0]))fill_with);
$endif
}

View File

@@ -112,3 +112,27 @@ const char[256] HEX_VALUE = {
const char[256] TO_UPPER @private = { ['a'..'z'] = 'a' - 'A' };
const char[256] TO_LOWER @private = { ['A'..'Z'] = 'a' - 'A' };
typedef AsciiCharset = uint128;
macro AsciiCharset @create_set(String $string) @const
{
AsciiCharset $set;
$foreach $c : $string:
$set |= 1ULL << $c;
$endforeach
return $set;
}
fn AsciiCharset create_set(String string)
{
AsciiCharset set;
foreach (c : string) set |= (AsciiCharset)1ULL << c;
return set;
}
macro bool AsciiCharset.contains(set, char c) => !!(c < 128) & !!(set & (AsciiCharset)(1ULL << c));
const AsciiCharset WHITESPACE_SET = @create_set("\t\n\v\f\r ");
const AsciiCharset NUMBER_SET = @create_set("0123456789");

View File

@@ -94,7 +94,7 @@ bitstruct UInt128LE : uint128 @littleendian
macro read(bytes, $Type)
{
char[] s;
$switch @typekind(bytes):
$switch $kindof(bytes):
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
@@ -110,7 +110,7 @@ macro read(bytes, $Type)
macro write(x, bytes, $Type)
{
char[] s;
$switch @typekind(bytes):
$switch $kindof(bytes):
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
@@ -119,7 +119,7 @@ macro write(x, bytes, $Type)
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
}
macro is_bitorder($Type)
macro bool is_bitorder($Type)
{
$switch $Type:
$case UShortLE:
@@ -181,4 +181,4 @@ macro bool @is_arrayptr_or_slice_of_char(#bytes) @const
$default:
return false;
$endswitch
}
}

View File

@@ -7,7 +7,7 @@ import libc, std::hash, std::io, std::os::backtrace;
<*
EMPTY_MACRO_SLOT is a value used for implementing optional arguments for macros in an efficient
way. It relies on the fact that distinct types are not implicitly convertable.
way. It relies on the fact that distinct types are not implicitly convertible.
You can use `@is_empty_macro_slot()` and `@is_valid_macro_slot()` to figure out whether
the argument has been used or not.
@@ -18,17 +18,17 @@ import libc, std::hash, std::io, std::os::backtrace;
macro foo(a, #b = EMPTY_MACRO_SLOT)
{
$if @is_valid_macro_slot(#b):
return invoke_foo2(a, #b);
$else
return invoke_foo1(a);
$endif
return invoke_foo2(a, #b);
$else
return invoke_foo1(a);
$endif
}
*>
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
typedef EmptySlot = void*;
macro @is_empty_macro_slot(#arg) @const @builtin => @typeis(#arg, EmptySlot);
macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
macro bool @is_empty_macro_slot(#arg) @const @builtin => $typeof(#arg) == EmptySlot;
macro bool @is_valid_macro_slot(#arg) @const @builtin => $typeof(#arg) != EmptySlot;
<*
Returns a random value at compile time.
@@ -39,20 +39,28 @@ macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
macro @rnd() @const @builtin => $$rnd();
/*
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
Use `NO_MORE_ELEMENT` when reading the end of an iterator, or accessing a result out of bounds.
*/
faultdef NO_MORE_ELEMENT @builtin;
/*
Use `SearchResult` when trying to return a value from some collection but the element is missing.
Use `NOT_FOUND` when trying to return a value from some collection but the element is missing.
*/
faultdef NOT_FOUND @builtin;
/*
Use `CastResult` when an attempt at conversion fails.
Use `TYPE_MISMATCH` when an attempt at conversion fails.
*/
faultdef TYPE_MISMATCH @builtin;
/*
Use `CAPACITY_EXCEEDED` when trying to add to a bounded list or similar.
*/
faultdef CAPACITY_EXCEEDED @builtin;
/*
Use `NOT_IMPLEMENTED` when something is conditionally available.
*/
faultdef NOT_IMPLEMENTED @builtin;
alias VoidFn = fn void();
@@ -61,7 +69,7 @@ alias VoidFn = fn void();
macro scope.
@param #variable : `the variable to store and restore`
@require values::@is_lvalue(#variable)
@require $defined(#variable = #variable) : `Expected an actual variable`
*>
macro void @scope(#variable; @body) @builtin
{
@@ -81,13 +89,17 @@ macro void @swap(#a, #b) @builtin
#b = temp;
}
macro usz bitsizeof($Type) @builtin @const => $Type.sizeof * 8u;
macro usz @bitsizeof(#expr) @builtin @const => $sizeof(#expr) * 8u;
<*
Convert an `any` type to a type, returning an failure if there is a type mismatch.
@param v : `the any to convert to the given type.`
@param $Type : `the type to convert to`
@return `The any.ptr converted to its type.`
@ensure @typeis(return, $Type*)
@ensure $typeof(return) == $Type*
@return? TYPE_MISMATCH
*>
macro anycast(any v, $Type) @builtin
@@ -96,7 +108,7 @@ macro anycast(any v, $Type) @builtin
return ($Type*)v.ptr;
}
macro bool @assignable_to(#foo, $Type) @const @builtin => $defined(*&&($Type){} = #foo);
macro bool @assignable_to(#foo, $Type) @const @builtin @deprecated("use '$defined($Type x = #foo)'") => $defined(*&&($Type){} = #foo);
macro @addr(#val) @builtin
{
@@ -112,12 +124,12 @@ macro typeid @typeid(#value) @const @builtin
return $typeof(#value).typeid;
}
macro TypeKind @typekind(#value) @const @builtin
macro TypeKind @typekind(#value) @const @builtin @deprecated("Use `$kindof(#value)`.")
{
return $typeof(#value).kindof;
return $kindof(#value);
}
macro bool @typeis(#value, $Type) @const @builtin
macro bool @typeis(#value, $Type) @const @builtin @deprecated("Use `$typeof(#value) == $Type` instead.")
{
return $typeof(#value).typeid == $Type.typeid;
}
@@ -273,7 +285,7 @@ macro any.as_inner(&self)
@param $Type : "the type to cast to"
@require $sizeof(expr) == $Type.sizeof : "Cannot bitcast between types of different size."
@ensure @typeis(return, $Type)
@ensure $typeof(return) == $Type*
*>
macro bitcast(expr, $Type) @builtin
{
@@ -290,7 +302,7 @@ macro bitcast(expr, $Type) @builtin
@param $Type : `The type of the enum`
@param [in] enum_name : `The name of the enum to search for`
@require $Type.kindof == ENUM : `Only enums may be used`
@ensure @typeis(return, $Type)
@ensure $typeof(return) == $Type*
@return? NOT_FOUND
*>
macro enum_by_name($Type, String enum_name) @builtin
@@ -307,8 +319,8 @@ macro enum_by_name($Type, String enum_name) @builtin
@param $Type : `The type of the enum`
@require $Type.kindof == ENUM : `Only enums may be used`
@require $defined($Type.#value) : `Expected '#value' to match an enum associated value`
@require @assignable_to(value, $typeof(($Type){}.#value)) : `Expected the value to match the type of the associated value`
@ensure @typeis(return, $Type)
@require $defined($typeof(($Type){}.#value) v = value) : `Expected the value to match the type of the associated value`
@ensure $typeof(return) == $Type*
@return? NOT_FOUND
*>
macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.lookup_field and Enum.lookup")
@@ -360,7 +372,7 @@ macro bool @unlikely(bool #value, $probability = 1.0) @builtin
<*
@require values::@is_int(#value) || values::@is_bool(#value)
@require @assignable_to(expected, $typeof(#value))
@require $defined($typeof(#value) v = expected)
@require $probability >= 0 && $probability <= 1.0
*>
macro @expect(#value, expected, $probability = 1.0) @builtin
@@ -411,11 +423,38 @@ macro swizzle2(v, v2, ...) @builtin
return $$swizzle2(v, v2, $vasplat);
}
<*
Returns the count of leading zero bits from an integer at compile-time.
@require types::is_int($typeof($value)) : "Input value must be an integer"
@require $sizeof($value) * 8 <= 128 : "Input value must be 128 bits wide or lower"
*>
macro uint @clz($value) @builtin @const
{
$if $value == 0:
return $sizeof($value) * 8; // it's all leading zeroes
$endif
usz $n = 0;
uint128 $x = (uint128)$value;
$if $x <= 0x0000_0000_0000_0000_FFFF_FFFF_FFFF_FFFF: $n += 64; $x <<= 64; $endif
$if $x <= 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 32; $x <<= 32; $endif
$if $x <= 0x0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 16; $x <<= 16; $endif
$if $x <= 0x00FF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 8; $x <<= 8; $endif
$if $x <= 0x0FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 4; $x <<= 4; $endif
$if $x <= 0x3FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 2; $x <<= 2; $endif
$if $x <= 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 1; $endif
return $n % ($sizeof($value) * 8); // mod by the bitsize of the input value to go back from uint128 -> it's-type
}
<*
Return the excuse in the Optional if it is Empty, otherwise
return a null fault.
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
@require $kindof(#expr) == OPTIONAL : `@catch expects an Optional value`
*>
macro fault @catch(#expr) @builtin
{
@@ -427,7 +466,7 @@ macro fault @catch(#expr) @builtin
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`
@require $kindof(#expr) == OPTIONAL : `@ok expects an Optional value`
*>
macro bool @ok(#expr) @builtin
{
@@ -441,9 +480,9 @@ macro bool @ok(#expr) @builtin
@require $defined(#v = #v) : "#v must be a variable"
@require $defined(#expr!) : "Expected an optional expression"
@require @assignable_to(#expr!!, $typeof(#v)) : `Type of #expr must be an optional of #v's type`
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
*>
macro void? @try(#v, #expr) @builtin
macro void? @try(#v, #expr) @builtin @maydiscard
{
var res = #expr;
if (catch err = res) return err?;
@@ -459,10 +498,10 @@ macro void? @try(#v, #expr) @builtin
while (true)
{
char[] data;
// Read until end of file
if (@try_catch(data, load_line(), io::EOF)) break;
.. use data ..
char[] data;
// Read until end of file
if (@try_catch(data, load_line(), io::EOF)) break;
.. use data ..
}
In this example we read until we reach an EOF, which is expected. However, if we encounter some other
@@ -470,19 +509,19 @@ macro void? @try(#v, #expr) @builtin
while (true)
{
char[]? data;
data = load_line();
if (catch err = data)
{
if (err = io::EOF) break;
return err?
}
.. use data ..
char[]? data;
data = load_line();
if (catch err = data)
{
if (err = io::EOF) break;
return err?
}
.. use data ..
}
@require $defined(#v = #v) : "#v must be a variable"
@require $defined(#expr!) : "Expected an optional expression"
@require @assignable_to(#expr!!, $typeof(#v)) : `Type of #expr must be an optional of #v's type`
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
@return "True if it was the expected fault, false if the variable was assigned, otherwise returns an optional."
*>
macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin
@@ -517,7 +556,7 @@ macro @generic_hash_core(h, value)
return h;
}
macro @generic_hash(value)
macro uint @generic_hash(value)
{
uint h = @generic_hash_core((uint)0x3efd4391, value);
$for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4:
@@ -565,24 +604,36 @@ macro uint ichar[<*>].hash(self) => hash_vec(self);
macro uint bool[<*>].hash(self) => hash_vec(self);
macro uint typeid.hash(typeid t) => @generic_hash(((ulong)(uptr)t));
macro uint String.hash(String c) => (uint)fnv32a::hash(c);
macro uint char[].hash(char[] c) => (uint)fnv32a::hash(c);
macro uint String.hash(String c) => (uint)a5hash::hash(c);
macro uint char[].hash(char[] c) => (uint)a5hash::hash(c);
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
<*
@require @typekind(array_ptr) == POINTER &&& @typekind(*array_ptr) == ARRAY
@require $kindof(array_ptr) == POINTER &&& $kindof(*array_ptr) == ARRAY
*>
macro uint hash_array(array_ptr) @local
{
return (uint)fnv32a::hash(((char*)array_ptr)[:$sizeof(*array_ptr)]);
var $len = $sizeof(*array_ptr);
$if $len > 16:
return (uint)komi::hash(((char*)array_ptr)[:$len]);
$else
return (uint)wyhash2::hash(((char*)array_ptr)[:$len]);
$endif
}
<*
@require @typekind(vec) == VECTOR
@require $kindof(vec) == VECTOR
*>
macro uint hash_vec(vec) @local
{
return (uint)fnv32a::hash(((char*)&&vec)[:$sizeof(vec.len * $typeof(vec).inner.sizeof)]);
var $len = $sizeof(vec.len * $typeof(vec).inner.sizeof);
$if $len > 16:
return (uint)komi::hash(((char*)&&vec)[:$len]);
$else
return (uint)wyhash2::hash(((char*)&&vec)[:$len]);
$endif
}
const MAX_FRAMEADDRESS = 128;

View File

@@ -6,7 +6,7 @@ module std::core::builtin;
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
macro less(a, b) @builtin
macro bool less(a, b) @builtin
{
$switch:
$case $defined(a.less):
@@ -21,7 +21,7 @@ macro less(a, b) @builtin
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
macro less_eq(a, b) @builtin
macro bool less_eq(a, b) @builtin
{
$switch:
$case $defined(a.less):
@@ -36,7 +36,7 @@ macro less_eq(a, b) @builtin
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
macro greater(a, b) @builtin
macro bool greater(a, b) @builtin
{
$switch:
$case $defined(a.less):
@@ -65,7 +65,7 @@ macro int compare_to(a, b) @builtin
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
macro greater_eq(a, b) @builtin
macro bool greater_eq(a, b) @builtin
{
$switch:
$case $defined(a.less):
@@ -126,3 +126,36 @@ macro max(x, ...) @builtin
$endif
}
<*
@require types::is_numerical($typeof($a))
*>
macro @max($a, ...) @builtin @const
{
$if $vacount == 1:
return $a > $vaconst[0] ? $a : $vaconst[0];
$else
var $result = $a;
$for var $x = 0; $x < $vacount; ++$x:
$if $vaconst[$x] > $result: $result = $vaconst[$x]; $endif
$endfor
return $result;
$endif
}
<*
@require types::is_numerical($typeof($a))
*>
macro @min($a, ...) @builtin @const
{
$if $vacount == 1:
return $a < $vaconst[0] ? $a : $vaconst[0];
$else
var $result = $a;
$for var $x = 0; $x < $vacount; ++$x:
$if $vaconst[$x] < $result: $result = $vaconst[$x]; $endif
$endfor
return $result;
$endif
}

View File

@@ -2,6 +2,8 @@
// 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::cinterop;
import std::core::env;
const C_INT_SIZE = $$C_INT_SIZE;
const C_LONG_SIZE = $$C_LONG_SIZE;
@@ -59,3 +61,57 @@ macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
$default: $error("Invalid bitsize");
$endswitch
}
const USE_STACK_VALIST = env::ARCH_32_BIT || env::WIN32 || (env::DARWIN && env::AARCH64);
module std::core::cinterop @if(USE_STACK_VALIST);
typedef CVaList = void*;
macro CVaList.next(&self, $Type)
{
void *ptr = mem::aligned_pointer((void*)*self, max($Type.alignof, 8));
defer *self = (CVaList)(ptr + 1);
return *($Type*)ptr;
}
module std::core::cinterop @if(env::X86_64 && !env::WIN32);
struct CVaListData
{
uint gp_offset;
uint fp_offset;
void *overflow_arg_area;
void *reg_save_area;
}
typedef CVaList = CVaListData*;
macro CVaList.next(self, $Type)
{
CVaListData* data = (CVaListData*)self;
$switch:
$case $Type.kindof == FLOAT ||| ($Type.kindof == VECTOR && $Type.sizeof <= 16):
var $LoadType = $Type.sizeof < 8 ? double : $Type;
if (data.fp_offset < 6 * 8 + 8 * 16 )
{
defer data.fp_offset += (uint)mem::aligned_offset($Type.sizeof, 16);
return ($Type)*($LoadType*)(data.reg_save_area + data.fp_offset);
}
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
defer data.overflow_arg_area = ptr + $Type.sizeof;
return ($Type)*($LoadType*)ptr;
$case $Type.kindof == SIGNED_INT || $Type.kindof == UNSIGNED_INT:
var $LoadType = $Type.sizeof < 4 ? int : $Type;
if (data.gp_offset < 6 * 8 && $Type.sizeof <= 8)
{
defer data.gp_offset += (uint)mem::aligned_offset($Type.sizeof, 8);
return ($Type)*($LoadType*)(data.reg_save_area + data.gp_offset);
}
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
defer data.overflow_arg_area = ptr + $Type.sizeof;
return ($Type)*($LoadType*)ptr;
$default:
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
defer data.overflow_arg_area = ptr + $Type.sizeof;
return *($Type*)ptr;
$endswitch
}

View File

@@ -154,7 +154,12 @@ const bool NETBSD = LIBC && OS_TYPE == NETBSD;
const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD;
const bool WASI = LIBC && OS_TYPE == WASI;
const bool ANDROID = LIBC && OS_TYPE == ANDROID;
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
const bool WASM_NOLIBC @builtin @deprecated("Use 'FREESTANDING_WASM' instead") = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
const bool FREESTANDING_PE32 = NO_LIBC && OS_TYPE == WIN32;
const bool FREESTANDING_MACHO = NO_LIBC && OS_TYPE == MACOS;
const bool FREESTANDING_ELF = NO_LIBC && !env::FREESTANDING_PE32 && !env::FREESTANDING_MACHO && !env::FREESTANDING_WASM;
const bool FREESTANDING_WASM = NO_LIBC && (ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64);
const bool FREESTANDING = env::FREESTANDING_PE32 || env::FREESTANDING_MACHO || env::FREESTANDING_ELF || env::FREESTANDING_WASM;
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
@@ -201,5 +206,6 @@ macro bool os_is_posix() @const
}
const String[] AUTHORS = $$AUTHORS;
const String[] AUTHOR_EMAILS = $$AUTHOR_EMAILS;
const String PROJECT_VERSION = $$PROJECT_VERSION;
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);

239
lib/std/core/logging.c3 Normal file
View File

@@ -0,0 +1,239 @@
module std::core::log;
import std::io, std::thread, std::time, std::math::random;
const FULL_LOG = env::COMPILER_SAFE_MODE || $feature(FULL_LOG);
typedef LogCategory = inline char;
typedef LogTag = char[12];
const LogCategory CATEGORY_APPLICATION = 0;
const LogCategory CATEGORY_SYSTEM = 1;
const LogCategory CATEGORY_KERNEL = 2;
const LogCategory CATEGORY_AUDIO = 3;
const LogCategory CATEGORY_VIDEO = 4;
const LogCategory CATEGORY_RENDER = 5;
const LogCategory CATEGORY_INPUT = 6;
const LogCategory CATEGORY_NETWORK = 7;
const LogCategory CATEGORY_SOCKET = 8;
const LogCategory CATEGORY_SECURITY = 9;
const LogCategory CATEGORY_TEST = 10;
const LogCategory CATEGORY_ERROR = 11;
const LogCategory CATEGORY_ASSERT = 12;
const LogCategory CATEGORY_CRASH = 13;
const LogCategory CATEGORY_STATS = 14;
const LogCategory CATEGORY_CUSTOM_START = 100;
tlocal LogCategory default_category = CATEGORY_APPLICATION;
tlocal LogTag current_tag;
enum LogPriority : int
{
VERBOSE,
DEBUG,
INFO,
WARN,
ERROR,
CRITICAL,
}
interface Logger
{
fn void log(LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
}
macro void verbose(String fmt, ..., LogCategory category = default_category) => call_log(VERBOSE, category, fmt, $vasplat);
macro void debug(String fmt, ..., LogCategory category = default_category) => call_log(DEBUG, category, fmt, $vasplat);
macro void info(String fmt, ..., LogCategory category = default_category) => call_log(INFO, category, fmt, $vasplat);
macro void warn(String fmt, ..., LogCategory category = default_category) => call_log(WARN, category, fmt, $vasplat);
macro void error(String fmt, ..., LogCategory category = default_category) => call_log(ERROR, category, fmt, $vasplat);
macro void critical(String fmt, ..., LogCategory category = default_category) => call_log(CRITICAL, category, fmt, $vasplat);
macro void @category_scope(LogCategory new_category; @body)
{
LogCategory old = default_category;
default_category = new_category;
defer default_category = old;
@body();
}
<*
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
*>
macro void @tag_scope(String tag_prefix = ""; @body)
{
LogTag old = current_tag;
push_tag(tag_prefix);
defer current_tag = old;
@body();
}
<*
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
*>
macro void push_tag(String tag_prefix = "")
{
current_tag = create_tag(tag_prefix);
}
<*
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
*>
fn LogTag create_tag(String tag_prefix)
{
LogTag tag @noinit;
int start = 0;
foreach (int i, c : tag_prefix)
{
if (c == 0) break;
tag[start++] = c;
}
if (start > 0) tag[start++] = '_';
for (int i = start; i < tag.len; i++)
{
tag[i] = (char)rand_in_range('a', 'z');
}
return tag;
}
fn void set_priority_for_category(LogCategory category, LogPriority new_priority)
{
@atomic_store(config_priorities[category], new_priority, UNORDERED);
}
fn LogPriority get_priority_for_category(LogCategory category)
{
return @atomic_load(config_priorities[category], UNORDERED);
}
fn void set_priority_all(LogPriority new_priority)
{
for (int i = 0; i < config_priorities.len; i++)
{
@atomic_store(config_priorities[i], new_priority, UNORDERED);
}
}
fn void set_logger(Logger logger)
{
init();
if (!logger_mutex.is_initialized())
{
current_logger = logger;
current_logfn = &logger.log;
return;
}
logger_mutex.@in_lock()
{
current_logger = logger;
current_logfn = &logger.log;
};
}
macro void init()
{
log_init.call(fn () => (void)logger_mutex.init());
}
macro void call_log(LogPriority prio, LogCategory category, String fmt, args...)
{
$if FULL_LOG:
call_log_internal(prio, category, $$FILE, $$FUNC, $$LINE, fmt, args);
$else
call_log_internal(prio, category, "", "", 0, fmt, args);
$endif
}
fn void call_log_internal(LogPriority prio, LogCategory category, String file, String func, int line, String fmt, any[] args)
{
LogPriority priority = mem::@atomic_load(config_priorities[category], UNORDERED);
if (priority > prio) return;
init();
bool locked = logger_mutex.is_initialized() && @ok(logger_mutex.lock());
Logger logger = current_logger;
LogFn logfn = current_logfn;
defer if (locked) (void)logger_mutex.unlock();
logfn(logger.ptr, prio, category, current_tag, file, func, line, fmt, args);
}
fn String? get_category_name(LogCategory category)
{
String val = category_names[category];
return val ?: NOT_FOUND?;
}
fn void set_category_name(LogCategory category, String name)
{
category_names[category] = name;
}
struct NullLogger (Logger)
{
void* dummy;
}
fn void NullLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
{}
struct MultiLogger (Logger)
{
Logger[] loggers;
}
fn void MultiLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
{
foreach (logger : self.loggers)
{
logger.log(priority, category, tag, file, function, line, fmt, args);
}
}
module std::core::log @private;
import std::io, std::thread, std::time;
struct StderrLogger (Logger) @if(env::LIBC)
{
void* dummy;
}
fn void StderrLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic @if(env::LIBC)
{
@stack_mem(256 + 64; Allocator mem)
{
DString str;
str.init(mem, 256);
str.appendf(fmt, ...args);
TzDateTime time = datetime::now().to_local();
$if FULL_LOG:
io::eprintfn("[%02d:%02d:%02d:%04d] %s:%d [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), file, line, priority, str);
$else
io::eprintfn("[%02d:%02d:%02d:%04d] [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), priority, str);
$endif
};
}
alias LogFn = fn void(void*, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
LogFn current_logfn = env::LIBC ??? (LogFn)&StderrLogger.log : (LogFn)&NullLogger.log;
OnceFlag log_init;
Mutex logger_mutex;
Logger current_logger = env::LIBC ??? &stderr_logger : &null_logger;
StderrLogger stderr_logger @if (env::LIBC);
NullLogger null_logger;
LogPriority[256] config_priorities = { [0..255] = ERROR, [CATEGORY_APPLICATION] = INFO, [CATEGORY_TEST] = VERBOSE, [CATEGORY_ASSERT] = WARN};
String[256] category_names = {
[CATEGORY_APPLICATION] = "APP",
[CATEGORY_SYSTEM] = "SYSTEM",
[CATEGORY_KERNEL] = "KERNEL",
[CATEGORY_AUDIO] = "AUDIO",
[CATEGORY_VIDEO] = "VIDEO",
[CATEGORY_RENDER] = "RENDER",
[CATEGORY_INPUT] = "INPUT",
[CATEGORY_NETWORK] = "NETWORD",
[CATEGORY_SOCKET] = "SOCKET",
[CATEGORY_SECURITY] = "SECURITY",
[CATEGORY_TEST] = "TEST",
[CATEGORY_ERROR] = "ERROR",
[CATEGORY_ASSERT] = "ASSERT",
[CATEGORY_CRASH] = "CRASH",
[CATEGORY_STATS] = "STATS"
};

View File

@@ -20,6 +20,9 @@ macro bool @constant_is_power_of_2($x) @const @private
return $x != 0 && ($x & ($x - 1)) == 0;
}
<*
@return "The os page size."
*>
fn usz os_pagesize()
{
$switch:
@@ -44,8 +47,8 @@ fn usz os_pagesize()
@param ptr : "The pointer address to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@require @assignable_to(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $defined(*ptr = passthru) : "Pointer and passthru must match"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
@@ -63,12 +66,13 @@ macro masked_load(ptr, bool[<*>] mask, passthru)
@param passthru : "The value to use for non masked values"
@param $alignment : "The alignment to assume for the pointer"
@require @assignable_to(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $defined(*ptr = passthru) : "Pointer and passthru must match"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
@ensure $typeof(return) == $typeof(*ptr)
*>
macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
{
@@ -82,9 +86,9 @@ macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require @assignable_to(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
@require $defined(*ptrvec[0] = passthru[0]) : "Pointer and passthru must match"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@@ -104,9 +108,9 @@ macro gather(ptrvec, bool[<*>] mask, passthru)
@param passthru : "The value to use for non masked values"
@param $alignment : "The alignment to assume for the pointers"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require @assignable_to(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
@require $defined(*ptrvec[0] = passthru[0]) : "Pointer and passthru must match"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -126,8 +130,8 @@ macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
@param value : "The value to store masked"
@param mask : "The mask for the store"
@require @assignable_to(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $defined(*ptr = value) : "Pointer and value must match"
@require $kindof(value) == VECTOR : "Expected value to be a vector"
@require value.len == mask.len : "Mask and value must have the same length"
*>
macro masked_store(ptr, value, bool[<*>] mask)
@@ -141,8 +145,8 @@ macro masked_store(ptr, value, bool[<*>] mask)
@param mask : "The mask for the store"
@param $alignment : "The alignment of the pointer"
@require @assignable_to(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $defined(*ptr = value) : "Pointer and value must match"
@require $kindof(value) == VECTOR : "Expected value to be a vector"
@require value.len == mask.len : "Mask and value must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -156,9 +160,9 @@ macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
@param ptrvec : "The vector pointer containing the addresses to store to."
@param value : "The value to store masked"
@param mask : "The mask for the store"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require @assignable_to(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(value) == VECTOR : "Expected value to be a vector"
@require $defined(*ptrvec[0] = value[0]) : "Pointer and value must match"
@require value.len == mask.len : "Mask and value must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@@ -174,9 +178,9 @@ macro scatter(ptrvec, value, bool[<*>] mask)
@param mask : "The mask for the store"
@param $alignment : "The alignment of the load"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require @assignable_to(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(value) == VECTOR : "Expected value to be a vector"
@require $defined(*ptrvec[0] = value[0]) : "Pointer and value must match"
@require value.len == mask.len : "Mask and value must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -238,6 +242,9 @@ macro @volatile_store(#x, value) @builtin
return $$volatile_store(&#x, ($typeof(#x))value);
}
<*
All possible atomic orderings
*>
enum AtomicOrdering : int
{
NOT_ATOMIC, // Not atomic
@@ -433,7 +440,7 @@ macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $i
@require values::@inner_kind(b) == TypeKind.SLICE || values::@inner_kind(b) == TypeKind.POINTER
@require values::@inner_kind(a) != TypeKind.SLICE || len == -1
@require values::@inner_kind(a) != TypeKind.POINTER || len > -1
@require values::@assign_to(a, b) && values::@assign_to(b, a)
@require $defined(a = b, b = a)
*>
macro bool equals(a, b, isz len = -1, usz $align = 0)
{
@@ -646,7 +653,7 @@ macro void @pool(usz reserve = 0; @body) @builtin
@body();
}
module std::core::mem @if(WASM_NOLIBC);
module std::core::mem @if(env::FREESTANDING_WASM);
import std::core::mem::allocator @public;
SimpleHeapAllocator wasm_allocator @private;
extern int __heap_base;
@@ -684,6 +691,12 @@ macro @clone(value) @builtin @nodiscard
return allocator::clone(mem, value);
}
<*
@param value : "The value to clone"
@return "A pointer to the cloned value"
*>
macro @clone_slice(value) @builtin @nodiscard => allocator::clone_slice(mem, value);
<*
@param value : "The value to clone"
@return "A pointer to the cloned value, which must be released using free_aligned"
@@ -706,6 +719,12 @@ macro @tclone(value) @builtin @nodiscard
$endif
}
<*
@param value : "The value to clone"
@return "A pointer to the cloned value"
*>
macro @tclone_slice(value) @builtin @nodiscard => allocator::clone_slice(tmem, value);
fn void* malloc(usz size) @builtin @inline @nodiscard
{
return allocator::malloc(mem, size);
@@ -727,56 +746,69 @@ fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@param $Type : "The type to allocate"
@param #init : "The optional initializer"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
@return "A pointer to data of type $Type."
*>
macro new($Type, ...) @nodiscard
macro new($Type, #init = ...) @nodiscard @safemacro
{
$if $vacount == 0:
return ($Type*)calloc($Type.sizeof);
$else
$if $defined(#init):
$Type* val = malloc($Type.sizeof);
*val = $vaexpr[0];
*val = #init;
return val;
$else
return ($Type*)calloc($Type.sizeof);
$endif
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
@param $Type : "The type to allocate"
@param padding : "The padding to add after the allocation"
@param #init : "The optional initializer"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to data of type $Type."
*>
macro new_with_padding($Type, usz padding, ...) @nodiscard
macro new_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
{
$if $vacount == 0:
return ($Type*)calloc($Type.sizeof + padding);
$else
$if $defined(#init):
$Type* val = malloc($Type.sizeof + padding);
*val = $vaexpr[0];
*val = #init;
return val;
$else
return ($Type*)calloc($Type.sizeof + padding);
$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_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@param $Type : "The type to allocate"
@param #init : "The optional initializer"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to data of type $Type with the proper alignment"
*>
macro new_aligned($Type, ...) @nodiscard
macro new_aligned($Type, #init = ...) @nodiscard @safemacro
{
$if $vacount == 0:
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
$else
$if $defined(#init):
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
*val = $vaexpr[0];
*val = #init;
return val;
$else
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
$endif
}
<*
@param $Type : "The type to allocate"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
@return "A pointer to uninitialized data for the type $Type"
*>
macro alloc($Type) @nodiscard
{
@@ -784,7 +816,11 @@ macro alloc($Type) @nodiscard
}
<*
@param $Type : "The type to allocate"
@param padding : "The padding to add after the allocation"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
@return "A pointer to uninitialized data for the type $Type"
*>
macro alloc_with_padding($Type, usz padding) @nodiscard
{
@@ -792,8 +828,12 @@ macro alloc_with_padding($Type, usz padding) @nodiscard
}
<*
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.
@param $Type : "The type to allocate"
@return "A pointer to uninitialized data for the type $Type with the proper alignment"
*>
macro alloc_aligned($Type) @nodiscard
{
@@ -801,32 +841,39 @@ macro alloc_aligned($Type) @nodiscard
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@param $Type : "The type to allocate"
@param #init : "The optional initializer"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to temporary data of type $Type."
*>
macro tnew($Type, ...) @nodiscard
macro tnew($Type, #init = ...) @nodiscard @safemacro
{
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
$else
$if $defined(#init):
$Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
*val = $vaexpr[0];
*val = #init;
return val;
$else
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
$endif
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@param $Type : "The type to allocate"
@param padding : "The padding to add after the allocation"
@param #init : "The optional initializer"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to temporary data of type $Type with added padding at the end."
*>
macro temp_with_padding($Type, usz padding, ...) @nodiscard
macro temp_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
{
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
$else
$if $defined(#init):
$Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline;
*val = $vaexpr[0];
*val = #init;
return val;
$else
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
$endif
}

View File

@@ -65,7 +65,7 @@ alias MemoryAllocFn = fn char[]?(usz);
fn usz alignment_for_allocation(usz alignment) @inline @private
fn usz alignment_for_allocation(usz alignment) @inline
{
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
}
@@ -167,7 +167,7 @@ macro void free_aligned(Allocator allocator, 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_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
*>
macro new(Allocator allocator, $Type, ...) @nodiscard
{
@@ -183,7 +183,7 @@ macro new(Allocator allocator, $Type, ...) @nodiscard
<*
@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_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
*>
macro new_try(Allocator allocator, $Type, ...) @nodiscard
{
@@ -200,7 +200,7 @@ macro new_try(Allocator allocator, $Type, ...) @nodiscard
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_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
*>
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
{
@@ -317,6 +317,23 @@ macro clone(Allocator allocator, value) @nodiscard
return new(allocator, $typeof(value), value);
}
<*
@param [&inout] allocator : "The allocator used to clone"
@param slice : "The slice to clone"
@return "A pointer to the cloned slice"
@require $kindof(slice) == SLICE || $kindof(slice) == ARRAY
*>
macro clone_slice(Allocator allocator, slice) @nodiscard
{
var $Type = $typeof(slice[0]);
$Type[] new_arr = new_array(allocator, $Type, slice.len);
mem::copy(new_arr.ptr, &slice[0], slice.len * $Type.sizeof);
return new_arr;
}
<*
Clone overaligned values. Must be released using free_aligned.
@@ -348,7 +365,7 @@ 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:
$if $kindof(#alloc_fn(bytes)) == OPTIONAL:
void* data = #alloc_fn(alignsize)!;
$else
void* data = #alloc_fn(alignsize);
@@ -369,7 +386,7 @@ struct AlignedBlock
macro void? @aligned_free(#free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
$if $kindof(#free_fn(desc.start)) == OPTIONAL:
#free_fn(desc.start)!;
$else
#free_fn(desc.start);
@@ -386,7 +403,7 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
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:
$if $kindof(#free_fn(data_start)) == OPTIONAL:
#free_fn(data_start)!;
$else
#free_fn(data_start);
@@ -494,7 +511,7 @@ macro Allocator temp() @deprecated("Use 'tmem' instead")
alias tmem @builtin = current_temp;
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::WASM_NOLIBC)
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::FREESTANDING_WASM)
{
auto_create_temp = true;
}

252
lib/std/core/mem_mempool.c3 Normal file
View File

@@ -0,0 +1,252 @@
module std::core::mem::mempool;
import std::core::mem, std::core::mem::allocator, std::math;
import std::core::sanitizer::asan;
const INITIAL_CAPACITY = 0;
struct FixedBlockPoolNode
{
void* buffer;
FixedBlockPoolNode *next;
usz capacity;
}
struct FixedBlockPoolEntry
{
void *previous;
}
<*
Fixed blocks pool pre-allocating blocks backed by an Allocator which are then reserved for the user,
blocks deallocated by the user are later re-used by future blocks allocations
`grow_capacity` can be changed in order to affect how many blocks will be allocated by next pool allocation,
it has to be greater than 0
`allocated` number of allocated blocks
`used` number of used blocks by the user
*>
struct FixedBlockPool
{
Allocator allocator;
FixedBlockPoolNode head;
FixedBlockPoolNode *tail;
void *next_free;
void *freelist;
usz block_size;
usz grow_capacity;
usz allocated;
usz page_size;
usz alignment;
usz used;
bool initialized;
}
<*
Initialize an block pool
@param [in] allocator : "The allocator to use"
@param block_size : "The block size to use"
@param capacity : "The amount of blocks to be pre-allocated"
@param alignment : "The alignment of the buffer"
@require !alignment || math::is_power_of_2(alignment)
@require !self.initialized : "The block pool must not be initialized"
@require block_size > 0 : "Block size must be non zero"
@require calculate_actual_capacity(capacity, block_size) * block_size >= block_size
: "Total memory would overflow"
*>
macro FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_size, usz capacity = INITIAL_CAPACITY, usz alignment = 0)
{
self.allocator = allocator;
self.tail = &self.head;
self.head.next = null;
self.block_size = math::max(block_size, FixedBlockPoolEntry.sizeof);
capacity = calculate_actual_capacity(capacity, self.block_size);
self.alignment = allocator::alignment_for_allocation(alignment);
self.page_size = capacity * self.block_size;
assert(self.page_size >= self.block_size, "Total memory would overflow %d %d", block_size, capacity);
self.head.buffer = self.allocate_page();
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
asan::poison_memory_region(self.head.buffer, self.page_size);
$endif
self.head.capacity = capacity;
self.next_free = self.head.buffer;
self.freelist = null;
self.grow_capacity = capacity;
self.initialized = true;
self.allocated = capacity;
self.used = 0;
return self;
}
<*
Initialize an block pool
@param [in] allocator : "The allocator to use"
@param $Type : "The type used for setting the block size"
@param capacity : "The amount of blocks to be pre-allocated"
@require !self.initialized : "The block pool must not be initialized"
*>
macro FixedBlockPool* FixedBlockPool.init_for_type(&self, Allocator allocator, $Type, usz capacity = INITIAL_CAPACITY)
{
return self.init(allocator, $Type.sizeof, capacity, $Type.alignof);
}
<*
Initialize an block pool using Temporary allocator
@param $Type : "The type used for setting the block size"
@param capacity : "The amount of blocks to be pre-allocated"
@require !self.initialized : "The block pool must not be initialized"
*>
macro FixedBlockPool* FixedBlockPool.tinit_for_type(&self, $Type, usz capacity = INITIAL_CAPACITY) => self.init_for_type(tmem, $Type, capacity);
<*
Initialize an block pool using Temporary allocator
@param block_size : "The block size to use"
@param capacity : "The amount of blocks to be pre-allocated"
@require !self.initialized : "The block pool must not be initialized"
*>
macro FixedBlockPool* FixedBlockPool.tinit(&self, usz block_size, usz capacity = INITIAL_CAPACITY) => self.init(tmem, block_size, capacity);
<*
Free up the entire block pool
@require self.initialized : "The block pool must be initialized"
*>
fn void FixedBlockPool.free(&self)
{
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
asan::unpoison_memory_region(self.head.buffer, self.page_size);
$endif
self.free_page(self.head.buffer);
FixedBlockPoolNode* iter = self.head.next;
while (iter)
{
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
asan::unpoison_memory_region(iter.buffer, self.page_size);
$endif
self.free_page(iter.buffer);
FixedBlockPoolNode* current = iter;
iter = iter.next;
allocator::free(self.allocator, current);
}
self.initialized = false;
self.allocated = 0;
self.used = 0;
}
<*
Allocate an block on the block pool, re-uses previously deallocated blocks
@require self.initialized : "The block pool must be initialized"
*>
fn void* FixedBlockPool.alloc(&self)
{
defer self.used++;
if (self.freelist)
{
FixedBlockPoolEntry* entry = self.freelist;
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
asan::unpoison_memory_region(entry, self.block_size);
$endif
self.freelist = entry.previous;
mem::clear(entry, self.block_size);
return entry;
}
void* end = self.tail.buffer + (self.tail.capacity * self.block_size);
if (self.next_free >= end) self.new_node();
void* ptr = self.next_free;
self.next_free += self.block_size;
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
asan::unpoison_memory_region(ptr, self.block_size);
$endif
return ptr;
}
<*
Deallocate a block from the block pool
@require self.initialized : "The block pool must be initialized"
@require self.check_ptr(ptr) : "The pointer should be part of the pool"
*>
fn void FixedBlockPool.dealloc(&self, void* ptr)
{
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
mem::set(ptr, 0xAA, self.block_size);
$endif
FixedBlockPoolEntry* entry = ptr;
entry.previous = self.freelist;
self.freelist = entry;
self.used--;
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
asan::poison_memory_region(ptr, self.block_size);
$endif
}
<*
@require self.initialized : "The block pool must be initialized"
*>
fn bool FixedBlockPool.check_ptr(&self, void *ptr) @local
{
FixedBlockPoolNode* iter = &self.head;
while (iter)
{
void* end = iter.buffer + (iter.capacity * self.block_size);
if (ptr >= iter.buffer && ptr < end) return true;
iter = iter.next;
}
return false;
}
<*
@require self.grow_capacity > 0 : "How many blocks will it store"
*>
fn void FixedBlockPool.new_node(&self) @local
{
FixedBlockPoolNode* node = allocator::new(self.allocator, FixedBlockPoolNode);
node.buffer = self.allocate_page();
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
asan::poison_memory_region(node.buffer, self.page_size);
$endif
node.capacity = self.grow_capacity;
self.tail.next = node;
self.tail = node;
self.next_free = node.buffer;
self.allocated += node.capacity;
}
macro void* FixedBlockPool.allocate_page(&self) @private
{
return self.alignment > mem::DEFAULT_MEM_ALIGNMENT
? allocator::calloc_aligned(self.allocator, self.page_size, self.alignment)!!
: allocator::calloc(self.allocator, self.page_size);
}
macro void FixedBlockPool.free_page(&self, void* page) @private
{
if (self.alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
allocator::free_aligned(self.allocator, page);
}
else
{
allocator::free(self.allocator, page);
}
}
macro usz calculate_actual_capacity(usz capacity, usz block_size) @private
{
// Assume some overhead
if (capacity) return capacity;
capacity = (mem::os_pagesize() - 128) / block_size;
return capacity ?: 1;
}

View File

@@ -2,7 +2,7 @@
The VM module holds code for working with virtual memory on supported platforms (currently Win32 and Posix)
*>
module std::core::mem::vm;
import std::os::win32, std::os::posix, libc;
import std::io, std::os::win32, std::os::posix, libc;
<*
VirtualMemory is an abstraction for working with an allocated virtual memory area. It will invoke vm:: functions
@@ -219,6 +219,36 @@ fn void? decommit(void* ptr, usz len, bool block = true)
$endswitch
}
<*
Map a portion of an already-opened file into memory.
@param fd : "File descriptor"
@param size : "Number of bytes to map, will be rounded up to page size"
@param offset : "Byte offset in file, must be page size aligned"
@param access : "The initial access permissions"
@param shared : "if True then MAP_SHARED else MAP_PRIVATE"
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
@return "Pointer to the mapped region"
*>
fn void*? mmap_file(Fd fd, usz size, usz offset = 0, VirtualMemoryAccess access = READ, bool shared = false) @if (env::POSIX)
{
CInt flags = shared ? posix::MAP_SHARED : posix::MAP_PRIVATE;
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), flags, fd, offset);
if (ptr != posix::MAP_FAILED) return ptr;
switch (libc::errno())
{
case errno::ENOMEM: return mem::OUT_OF_MEMORY?;
case errno::EOVERFLOW: return RANGE_OVERFLOW?;
case errno::EPERM: return ACCESS_DENIED?;
case errno::EINVAL: return INVALID_ARGS?;
case errno::EACCES: return io::NO_PERMISSION?;
case errno::EBADF: return io::FILE_NOT_VALID?;
case errno::EAGAIN: return io::WOULD_BLOCK?;
case errno::ENXIO: return io::FILE_NOT_FOUND?;
default: return UNKNOWN_ERROR?;
}
}
<*
Create a VirtualMemory using

View File

@@ -6,7 +6,7 @@
then free the pointer and the atomic variable assuming that they are allocated using the Allocator in the Ref.
@require !$defined(Type.dealloc) ||| $defined(Type.dealloc(&&(Type){})) : "'dealloc' must only take a pointer to the underlying type"
@require !$defined(Type.dealloc) ||| @typeis((Type){}.dealloc(), void) : "'dealloc' must return 'void'"
@require !$defined(Type.dealloc) ||| $typeof((Type){}.dealloc()) == void : "'dealloc' must return 'void'"
*>
module std::core::mem::ref { Type };
import std::thread, std::atomic;
@@ -21,7 +21,7 @@ fn Ref wrap(Type* ptr, Allocator allocator = mem)
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| @assignable_to($vaexpr[0], Type) : "The first argument must be an initializer for the type"
@require $vacount == 0 ||| $defined(Type a = $vaexpr[0]) : "The first argument must be an initializer for the type"
*>
macro Ref new(..., Allocator allocator = mem)
{
@@ -99,7 +99,7 @@ struct RefCounted
}
<*
@require @assignable_to(refcounted, RefCounted*) : "Expected a ref counted value"
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
*>
macro retain(refcounted)
{
@@ -112,8 +112,8 @@ macro retain(refcounted)
}
<*
@require @assignable_to(refcounted, RefCounted*) : "Expected a ref counted value"
@require !$defined(refcounted.dealloc()) ||| @typeis(refcounted.dealloc(), void)
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
@require !$defined(refcounted.dealloc()) ||| $typeof(refcounted.dealloc()) == void
: "Expected refcounted type to have a valid dealloc"
*>
macro void release(refcounted)

View File

@@ -39,7 +39,7 @@ macro @enum_lookup_new($Type, $name, value)
}
module std::core::runtime @if(WASM_NOLIBC);
module std::core::runtime @if(env::FREESTANDING_WASM);
extern fn void __wasm_call_ctors();
fn void wasm_initialize() @extern("_initialize") @wasm

View File

@@ -1,7 +1,9 @@
module std::core::runtime;
import libc, std::time, std::io, std::sort;
import libc, std::time, std::io, std::sort, std::math, std::collections::map;
alias BenchmarkFn = fn void();
alias BenchmarkFn = fn void ();
HashMap { String, uint } bench_fn_iters @local;
struct BenchmarkUnit
{
@@ -17,6 +19,7 @@ fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator)
foreach (i, benchmark : fns)
{
benchmarks[i] = { names[i], fns[i] };
if (!bench_fn_iters.has_key(names[i])) bench_fn_iters[names[i]] = benchmark_max_iterations;
}
return benchmarks;
}
@@ -36,6 +39,49 @@ fn void set_benchmark_max_iterations(uint value) @builtin
{
assert(value > 0);
benchmark_max_iterations = value;
foreach (k : bench_fn_iters.key_iter()) bench_fn_iters[k] = value;
}
fn void set_benchmark_func_iterations(String func, uint value) @builtin
{
assert(value > 0);
bench_fn_iters[func] = value;
}
Clock benchmark_clock @local;
NanoDuration benchmark_nano_seconds @local;
long cycle_start @local;
long cycle_stop @local;
DString benchmark_log @local;
bool benchmark_warming @local;
uint this_iteration @local;
bool benchmark_stop @local;
macro void @start_benchmark()
{
benchmark_clock = clock::now();
cycle_start = $$sysclock();
}
macro void @end_benchmark()
{
benchmark_nano_seconds = benchmark_clock.mark();
cycle_stop = $$sysclock();
}
macro void @kill_benchmark(String format, ...)
{
@log_benchmark(format, $vasplat);
benchmark_stop = true;
}
macro void @log_benchmark(msg, args...) => @pool()
{
if (benchmark_warming) return;
benchmark_log.appendf("%s [%d]: ", $$FUNC, this_iteration);
benchmark_log.appendfn(msg, ...args);
}
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
@@ -58,36 +104,69 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
name.clear();
long sys_clock_started;
long sys_clock_finished;
long sys_clocks;
Clock clock;
foreach(unit : benchmarks)
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());
benchmark_warming = true;
for (uint i = 0; i < benchmark_warmup_iterations; i++)
{
unit.func() @inline;
}
benchmark_warming = false;
clock = std::time::clock::now();
sys_clock_started = $$sysclock();
NanoDuration running_timer;
long total_clocks;
for (uint i = 0; i < benchmark_max_iterations; i++)
uint current_benchmark_iterations = bench_fn_iters[unit.name] ?? benchmark_max_iterations;
char[] perc_str = { [0..19] = ' ', [20] = 0 };
int perc = 0;
uint print_step = current_benchmark_iterations / 100;
for (this_iteration = 0; this_iteration < current_benchmark_iterations; ++this_iteration, benchmark_nano_seconds = {})
{
if (0 == this_iteration % print_step) // only print right about when the % will update
{
perc_str[0..(uint)math::floor((this_iteration / (float)current_benchmark_iterations) * 20)] = '#';
perc = (uint)math::ceil(100 * (this_iteration / (float)current_benchmark_iterations));
io::printf("\r%s [%s] %d / %d (%d%%)", name.str_view(), (ZString)perc_str, this_iteration, current_benchmark_iterations, perc);
io::stdout().flush()!!;
}
@start_benchmark(); // can be overridden by calls inside the unit's func
unit.func() @inline;
if (benchmark_stop) return false;
if (benchmark_nano_seconds == (NanoDuration){}) @end_benchmark(); // only mark when it wasn't already by the unit.func
total_clocks += cycle_stop - cycle_start;
running_timer += benchmark_nano_seconds;
}
sys_clock_finished = $$sysclock();
NanoDuration nano_seconds = clock.mark();
sys_clocks = sys_clock_finished - sys_clock_started;
float clock_cycles = (float)total_clocks / current_benchmark_iterations;
float measurement = (float)running_timer / current_benchmark_iterations;
String[] units = { "nanoseconds", "microseconds", "milliseconds", "seconds" };
io::printfn("[COMPLETE] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
float adjusted_measurement = measurement;
while (adjusted_measurement > 1_000) adjusted_measurement /= 1_000;
float adjusted_runtime_total = (float)running_timer;
while (adjusted_runtime_total > 1_000) adjusted_runtime_total /= 1_000;
io::printf("\r%s ", name.str_view());
io::printfn(
"[COMPLETE] %.2f %s, %.2f CPU clocks, %d iterations (runtime %.2f %s)",
adjusted_measurement,
units[math::min(3, (int)math::floor(math::log(measurement, 1_000)))],
clock_cycles,
current_benchmark_iterations,
adjusted_runtime_total,
units[math::min(3, (int)math::floor(math::log((float)running_timer, 1_000)))],
);
}
io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : "");
@@ -96,5 +175,12 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
fn bool default_benchmark_runner(String[] args) => @pool()
{
benchmark_log.init(mem);
defer
{
if (benchmark_log.len()) io::printfn("\n---------- BENCHMARK LOG ----------\n%s\n", benchmark_log.str_view());
benchmark_log.free();
}
return run_benchmarks(benchmark_collection_create(tmem));
}

View File

@@ -14,9 +14,9 @@ TestContext* test_context @private;
struct TestContext
{
JmpBuf buf;
// Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add'
<* 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
<* Triggers debugger breakpoint when assert or test:: checks failed *>
bool breakpoint_on_assert;
// internal state
@@ -140,7 +140,6 @@ fn void unmute_output(bool has_error) @local
usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
if (has_error)
{
io::printf("\nTesting %s ", test_context.current_test_name);
io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
}
@@ -171,6 +170,11 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
usz max_name;
bool sort_tests = true;
bool check_leaks = true;
if (!tests.len)
{
io::printn("There are no test units to run.");
return true; // no tests == technically a pass
}
foreach (&unit : tests)
{
if (max_name < unit.name.len) max_name = unit.name.len;
@@ -305,7 +309,7 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
}
mem.free();
}
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count > 1 ? "s" : "");
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: ",

View File

@@ -29,11 +29,11 @@ alias ErrorCallback = fn void (ZString);
@param addr : "Start of memory region."
@param size : "Size of memory region."
*>
macro poison_memory_region(void* addr, usz size)
macro void poison_memory_region(void* addr, usz size)
{
$if env::ADDRESS_SANITIZER:
$if env::ADDRESS_SANITIZER:
__asan_poison_memory_region(addr, size);
$endif
$endif
}
<*
@@ -50,11 +50,11 @@ macro poison_memory_region(void* addr, usz size)
@param addr : "Start of memory region."
@param size : "Size of memory region."
*>
macro unpoison_memory_region(void* addr, usz size)
macro void unpoison_memory_region(void* addr, usz size)
{
$if env::ADDRESS_SANITIZER:
$if env::ADDRESS_SANITIZER:
__asan_unpoison_memory_region(addr, size);
$endif
$endif
}
<*

View File

@@ -1,6 +1,5 @@
module std::core::string;
import std::io;
import std::core::mem::allocator;
import std::io, std::ascii;
typedef String @if(!$defined(String)) = inline char[];
@@ -110,16 +109,25 @@ fn String format(Allocator allocator, String fmt, args...) @format(1) => @pool()
}
<*
Return a new String created using the formatting function.
Return a new String created using the formatting function, the resulting string must fit the buffer.
@param [inout] buffer : `The buffer to use`
@param [in] fmt : `The formatting string`
*>
fn String bformat(char[] buffer, String fmt, args...) @format(1)
{
DString str = dstring::new_with_capacity(allocator::wrap(buffer), fmt.len + args.len * 8);
str.appendf(fmt, ...args);
return str.str_view();
Formatter f;
OutputFn format_fn = fn void?(void* buf, char c) {
char[]* buffer_ref = buf;
char[] buffer = *buffer_ref;
if (buffer.len == 0) return io::BUFFER_EXCEEDED?;
buffer[0] = c;
*buffer_ref = buffer[1..];
};
char[] buffer_copy = buffer;
f.init(format_fn, &buffer_copy);
usz len = f.vprintf(fmt, args)!!;
return (String)buffer[:len];
}
<*
@@ -138,7 +146,7 @@ fn String tformat(String fmt, args...) @format(0)
Check if a character is in a set.
@param c : `the character to check`
@param [in] set : `The formatting string`
@param [in] set : `String containing the characters`
@pure
@return `True if a character is in the set`
*>
@@ -187,7 +195,7 @@ fn String String.replace(self, Allocator allocator, String needle, String new_st
@pool()
{
String[] split = self.tsplit(needle);
return dstring::join(tmem, split, new_str).copy_str(mem);
return dstring::join(tmem, split, new_str).copy_str(allocator);
};
}
@@ -214,11 +222,28 @@ fn String String.treplace(self, String needle, String new_str)
@pure
@return `a substring of the string passed in`
*>
fn String String.trim(self, String to_trim = "\t\n\r ")
fn String String.trim(self, String to_trim = " \n\t\r\f\v")
{
return self.trim_left(to_trim).trim_right(to_trim);
}
<*
Remove characters from the front and end of a string.
@param [in] self : `The string to trim`
@param to_trim : `The set of characters to trim, defaults to whitespace`
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_charset(self, AsciiCharset to_trim = ascii::WHITESPACE_SET)
{
usz start = 0;
usz len = self.len;
while (start < len && to_trim.contains(self[start])) start++;
while (len > start && to_trim.contains(self[len - 1])) len--;
return self[start..len - 1];
}
<*
Remove characters from the front of a string.
@@ -227,7 +252,7 @@ fn String String.trim(self, String to_trim = "\t\n\r ")
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_left(self, String to_trim = "\t\n\r ")
fn String String.trim_left(self, String to_trim = " \n\t\r\f\v")
{
usz start = 0;
usz len = self.len;
@@ -244,7 +269,7 @@ fn String String.trim_left(self, String to_trim = "\t\n\r ")
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_right(self, String to_trim = "\t\n\r ")
fn String String.trim_right(self, String to_trim = " \n\t\r\f\v")
{
usz len = self.len;
while (len > 0 && char_in_set(self[len - 1], to_trim)) len--;
@@ -427,6 +452,19 @@ fn bool String.contains(s, String substr)
return @ok(s.index_of(substr));
}
<*
Check if a character is found in the string.
@param [in] s
@param character : "The character to look for."
@pure
@return "true if the string contains the character, false otherwise"
*>
fn bool String.contains_char(s, char character)
{
return @ok(s.index_of_char(character));
}
<*
Check how many non-overlapping instances of a substring there is.

View File

@@ -115,7 +115,9 @@ fn bool TypeKind.is_int(kind) @inline
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
}
macro bool is_slice_convertable($Type)
macro bool is_slice_convertable($Type) @deprecated("Use is_slice_convertible") => is_slice_convertible($Type);
macro bool is_slice_convertible($Type)
{
$switch $Type.kindof:
$case SLICE:
@@ -339,6 +341,8 @@ macro bool is_same_vector_type($Type1, $Type2) @const
$endif
}
macro bool has_equals($Type) @const => $defined(($Type){} == ($Type){});
macro bool is_equatable_type($Type) @const
{
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):

View File

@@ -2,10 +2,11 @@ module std::core::values;
import std::core::types;
macro bool @typematch(#value1, #value2) @builtin @const => $typeof(#value1) == $typeof(#value2);
<*
Return true if two values have the same type before any conversions.
*>
macro bool @is_same_type(#value1, #value2) @const => $typeof(#value1).typeid == $typeof(#value2).typeid;
macro bool @is_same_type(#value1, #value2) @const @deprecated("Use @typematch") => $typeof(#value1).typeid == $typeof(#value2).typeid;
macro bool @is_bool(#value) @const => types::is_bool($typeof(#value));
macro bool @is_int(#value) @const => types::is_int($typeof(#value));
macro bool @is_flat_intlike(#value) @const => types::is_flat_intlike($typeof(#value));
@@ -15,12 +16,11 @@ macro bool @is_promotable_to_floatlike(#value) @const => types::is_promotable_to
macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_float($typeof(#value));
macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) @const => @assignable_to(#value1, $typeof(#value2));
macro bool @is_lvalue(#value) => $defined(#value = #value);
macro bool @is_const(#foo) @const @builtin
macro bool @assign_to(#value1, #value2) @const @deprecated("use '$defined(#value1 = #value2)'") => @assignable_to(#value1, $typeof(#value2));
macro bool @is_lvalue(#value) @deprecated("use '$defined(#value = #value)'")=> $defined(#value = #value);
macro bool @is_const(#foo) @const @builtin @deprecated("use '$defined(var $v = expr)'")
{
var $v;
return $defined($v = #foo);
return $defined(var $v = #foo);
}
macro promote_int(x)
@@ -43,7 +43,7 @@ macro promote_int(x)
@param #value_2
@returns `The selected value.`
*>
macro @select(bool $bool, #value_1, #value_2) @builtin
macro @select(bool $bool, #value_1, #value_2) @builtin @deprecated("Use '$bool ? #value_1 : #value_2' instead.")
{
$if $bool:
return #value_1;
@@ -51,6 +51,7 @@ macro @select(bool $bool, #value_1, #value_2) @builtin
return #value_2;
$endif
}
macro promote_int_same(x, y)
{
$if @is_int(x):

650
lib/std/crypto/aes.c3 Normal file
View File

@@ -0,0 +1,650 @@
<*
This is an implementation of the AES algorithm with the ECB, CTR and CBC
modes. The key size can be chosen among AES128, AES192, AES256.
Ported from github.com/kokke/tiny-aes-c by Koni Marti.
The implementation is verified against the test vectors from the National
Institute of Standards and Technology Special Publication 800-38A 2001 ED.
Data length must be evenly divisible by 16 bytes (len % 16 == 0) unless CTR is
used. You should pad the end of the string with zeros or use PKCS7 if this is not the case.
For AES192/256 the key size is proportionally larger.
The following example demonstrates the AES encryption of a plaintext string
with an AES 128-bit key:
```
module app;
import std::crypto::aes, std::io;
fn void main()
{
char[] key = x"2b7e151628aed2a6abf7158809cf4f3c";
char[] text = x"6bc1bee22e409f96e93d7e117393172a";
char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
Aes aes;
aes.init(AES128, key, iv);
defer aes.destroy();
char[] cipher = aes.encrypt(mem, text);
defer free(cipher);
assert(cipher == x"874d6191b620e3261bef6864990db6ce");
}
```
*>
module std::crypto::aes;
<* Block length in bytes. AES is 128-bit blocks only. *>
const BLOCKLEN = 16;
<* Number of columns of a AES state. *>
const COLNUM = 4;
<*
Block modes:
ECB - Electronic Code Book (Not recommended, indata be 16 byte multiple)
CBC - Cipher Block Chaining (Indata be 16 byte multiple)
CTR - Counter Mode (Recommended, data may be any size)
*>
enum BlockMode
{
ECB,
CBC,
CTR,
}
<* AES type: 128, 192 or 256 bits *>
enum AesType : (AesKey key)
{
AES128 = { 128, 16, 176, 4, 10 },
AES192 = { 192, 24, 208, 6, 12 },
AES256 = { 256, 32, 240, 8, 14 }
}
struct AesKey
{
<* Size of key in bits *>
usz key_size;
<* Size of key in bytes *>
int key_len;
<* Size of the expanded round_key *>
int key_exp_size; // expected size of round_key
<* Number of 32 bit words in key *>
usz nk;
<* Number of rounds in the cipher *>
usz nr;
}
struct Aes
{
<* The type, AES128, AES192 or AES256 *>
AesKey type;
<* Block mode: ECB, CBC or CTR *>
BlockMode mode;
<* Initialization Vector *>
char[BLOCKLEN] iv;
<* Internal key state *>
char[256] round_key;
<* Internal state *>
AesState state;
}
alias AesState = char[COLNUM][COLNUM];
<*
Initializes the AES crypto. The initialization vector should be securely random for each encryption
to mitigate things like replay attacks.
@param type : "The type or AES: 128, 192 or 256 bits"
@param [in] key : "The key to use, should be the same bit size as the type, so 16, 24 or 32 bytes"
@param iv : "The initialization vector"
@param mode : "The block mode: EBC, CBC, CTR. Defaults to CTR"
@require key.len == type.key.key_len : "Key does not match expected length."
*>
fn Aes* Aes.init(&self, AesType type, char[] key, char[BLOCKLEN] iv, BlockMode mode = CTR)
{
*self = { .type = type.key, .mode = mode, .iv = iv };
key_expansion(type, key, &self.round_key);
return self;
}
<*
Completely erases data stored in the context.
*>
fn void Aes.destroy(&self)
{
*self = {};
}
<*
Check if the length is valid using the given block mode. It has to be a multiple of 16 bytes unless CTR is used.
*>
macro bool is_valid_encryption_len(BlockMode mode, usz len)
{
switch (mode)
{
case CTR:
return true;
case ECB:
case CBC:
return len % BLOCKLEN == 0;
}
}
<*
@param [in] in : "Plaintext input."
@param [out] out : "Cipher output."
@require is_valid_encryption_len(self.mode, in.len) : "The input must be a multiple of 16 unless CTR is used"
@require out.len >= in.len : "Out buffer must be sufficiently large to hold the data"
*>
fn void Aes.encrypt_buffer(&self, char[] in, char[] out)
{
switch (self.mode)
{
case CTR: ctr_xcrypt_buffer(self, in, out);
case ECB: ecb_encrypt_buffer(self, in, out);
case CBC: cbc_encrypt_buffer(self, in, out);
}
}
<*
@param [in] in : "Cipher input."
@param [out] out : "Plaintext output."
@require is_valid_encryption_len(self.mode, in.len) : "The encrypted data must be a multiple of 16 unless CTR is used"
@require out.len >= in.len : "Out buffer must be sufficiently large to hold the data"
*>
fn void Aes.decrypt_buffer(&self, char[] in, char[] out)
{
switch (self.mode)
{
case ECB: ecb_decrypt_buffer(self, in, out);
case CBC: cbc_decrypt_buffer(self, in, out);
case CTR: ctr_xcrypt_buffer(self, in, out);
}
}
<*
Encrypt the data, allocating memory for the encrypted data.
@param [in] in : "Plaintext input."
@param [&inout] allocator : "The allocator to use for the output"
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
*>
fn char[] Aes.encrypt(&self, Allocator allocator, char[] in)
{
char[] out = allocator::alloc_array(allocator, char, in.len);
self.encrypt_buffer(in, out) @inline;
return out;
}
<*
Encrypt the data, allocating temp memory for the encrypted data.
@param [in] in : "Plaintext input."
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
*>
fn char[] Aes.tencrypt(&self, char[] in)
{
return self.encrypt(tmem, in);
}
<*
Decrypt the data, allocating memory for the decrypted data.
@param [in] in : "Encrypted input."
@param [&inout] allocator : "The allocator to use for the output"
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
*>
fn char[] Aes.decrypt(&self, Allocator allocator, char[] in)
{
char[] out = allocator::alloc_array(allocator, char, in.len);
self.decrypt_buffer(in, out) @inline;
return out;
}
<*
Decrypt the data, allocating temp memory for the decrypted data.
@param [in] in : "Encrypted input."
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
*>
fn char[] Aes.tdecrypt(&self, char[] in)
{
return self.decrypt(tmem, in);
}
module std::crypto::aes @private;
<*
@param [&inout] aes : "AES context."
@param [in] in : "Plaintext input."
@param [out] out : "Cipher output."
*>
fn void ecb_encrypt_block(Aes *aes, char[BLOCKLEN]* in, char[BLOCKLEN]* out)
{
for (usz i = 0; i < 4; i++)
{
for (usz j = 0; j < 4; j++)
{
aes.state[i][j] = (*in)[i * 4 + j];
}
}
aes_cipher(aes, &aes.round_key);
for (usz i = 0; i < 4; i++)
{
for (usz j = 0; j < 4; j++)
{
(*out)[i * 4 + j] = aes.state[i][j];
}
}
}
<*
@param [&inout] aes : "AES context."
@param [in] in : "Cipher input."
@param [out] out : "Plaintext output."
*>
fn void ecb_decrypt_block(Aes *aes, char[BLOCKLEN]* in, char[BLOCKLEN]* out)
{
for (usz i = 0; i < 4; i++)
{
for (usz j = 0; j < 4; j++)
{
aes.state[i][j] = (*in)[i * 4 + j];
}
}
inv_cipher(aes, &aes.round_key);
for (usz i = 0; i < 4; i++)
{
for (usz j = 0; j < 4; j++)
{
(*out)[i * 4 + j] = aes.state[i][j];
}
}
}
<*
@param [&inout] aes : "AES context."
@param [in] in : "Cipher input."
@param [out] out : "Plaintext output."
@require out.len >= in.len : "out must be at least as large as buf"
*>
fn void ecb_decrypt_buffer(Aes *aes, char[] in, char[] out)
{
usz len = in.len;
for (usz i = 0; i < len; i += 4)
{
ecb_decrypt_block(aes, in[:BLOCKLEN], out[:BLOCKLEN]) @inline;
}
}
<*
@param [&inout] aes : "AES context."
@param [in] in : "Plaintext input."
@param [out] out : "Cipher output."
*>
fn void ecb_encrypt_buffer(Aes *aes, char[] in, char[] out)
{
usz len = in.len;
for (usz i = 0; i < len; i += BLOCKLEN)
{
ecb_encrypt_block(aes, in[i:BLOCKLEN], out[i:BLOCKLEN]) @inline;
}
}
fn void xor_with_iv(char[] buf, char[BLOCKLEN]* iv) @local
{
foreach (i, b : *iv)
{
buf[i] ^= b;
}
}
<*
@param [&inout] aes : "AES context."
@param [in] in : "Plaintext input."
@param [out] out : "Cipher output."
*>
fn void cbc_encrypt_buffer(Aes *aes, char[] in, char[] out)
{
char[] iv = aes.iv[..];
usz len = in.len;
char[BLOCKLEN] tmp;
char[BLOCKLEN] tmp2;
for (usz i = 0; i < len; i += BLOCKLEN)
{
tmp[:BLOCKLEN] = in[i:BLOCKLEN];
xor_with_iv(&tmp, iv);
ecb_encrypt_block(aes, &tmp, &tmp2);
out[i:BLOCKLEN] = tmp2[..];
iv = tmp2[..];
}
}
<*
@param [&inout] aes : "AES context."
@param [in] in : "Cipher input."
@param [out] out : "Plaintext output."
*>
fn void cbc_decrypt_buffer(Aes *aes, char[] in, char[] out)
{
char[BLOCKLEN] tmp;
usz len = in.len;
for (usz i = 0; i < len; i += BLOCKLEN)
{
ecb_decrypt_block(aes, in[i:BLOCKLEN], &tmp);
xor_with_iv(&tmp, aes.iv[..]);
aes.iv[:BLOCKLEN] = in[i:BLOCKLEN];
out[i:BLOCKLEN] = tmp[..];
}
}
<*
@param [&inout] aes : "AES context."
@param [in] in : "Plaintext/cipher input."
@param [out] out : "Cipher/plaintext output."
*>
fn void ctr_xcrypt_buffer(Aes *aes, char[] in, char[] out)
{
char[BLOCKLEN] buffer @noinit;
usz len = in.len;
for (int bi = BLOCKLEN, usz i = 0; i < len; i++)
{
if (bi == BLOCKLEN)
{
buffer = aes.iv;
ecb_encrypt_block(aes, &buffer, &buffer);
for LOOP: (bi = (BLOCKLEN - 1); bi >= 0; bi--)
{
if (aes.iv[bi] == 255)
{
aes.iv[bi] = 0;
continue;
}
aes.iv[bi]++;
break LOOP;
}
bi = 0;
}
out[i] = in[i] ^ buffer[bi];
bi++;
}
}
macro char get_sbox_value(num) => SBOX[num];
macro char get_sbox_invert(num) => RSBOX[num];
const char[256] SBOX =
x`637c777bf26b6fc53001672bfed7ab76
ca82c97dfa5947f0add4a2af9ca472c0
b7fd9326363ff7cc34a5e5f171d83115
04c723c31896059a071280e2eb27b275
09832c1a1b6e5aa0523bd6b329e32f84
53d100ed20fcb15b6acbbe394a4c58cf
d0efaafb434d338545f9027f503c9fa8
51a3408f929d38f5bcb6da2110fff3d2
cd0c13ec5f974417c4a77e3d645d1973
60814fdc222a908846eeb814de5e0bdb
e0323a0a4906245cc2d3ac629195e479
e7c8376d8dd54ea96c56f4ea657aae08
ba78252e1ca6b4c6e8dd741f4bbd8b8a
703eb5664803f60e613557b986c11d9e
e1f8981169d98e949b1e87e9ce5528df
8ca1890dbfe6426841992d0fb054bb16`;
const char[256] RSBOX =
x`52096ad53036a538bf40a39e81f3d7fb
7ce339829b2fff87348e4344c4dee9cb
547b9432a6c2233dee4c950b42fac34e
082ea16628d924b2765ba2496d8bd125
72f8f66486689816d4a45ccc5d65b692
6c704850fdedb9da5e154657a78d9d84
90d8ab008cbcd30af7e45805b8b34506
d02c1e8fca3f0f02c1afbd0301138a6b
3a9111414f67dcea97f2cfcef0b4e673
96ac7422e7ad3585e2f937e81c75df6e
47f11a711d29c5896fb7620eaa18be1b
fc563e4bc6d279209adbc0fe78cd5af4
1fdda8338807c731b11210592780ec5f
60517fa919b54a0d2de57a9f93c99cef
a0e03b4dae2af5b0c8ebbb3c83539961
172b047eba77d626e169146355210c7d`;
const char[11] RCON = x`8d01020408102040801b36`;
fn void add_round_key(Aes* aes, usz round, char[] round_key)
{
usz i, j;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
aes.state[i][j] ^= round_key[(round * COLNUM * 4) + (i * COLNUM) + j];
}
}
}
fn void sub_bytes(Aes* aes)
{
for (usz i = 0; i < 4; i++)
{
for (usz j = 0; j < 4; j++)
{
aes.state[j][i] = get_sbox_value(aes.state[j][i]);
}
}
}
fn void shift_rows(Aes* aes)
{
char temp;
temp = aes.state[0][1];
aes.state[0][1] = aes.state[1][1];
aes.state[1][1] = aes.state[2][1];
aes.state[2][1] = aes.state[3][1];
aes.state[3][1] = temp;
temp = aes.state[0][2];
aes.state[0][2] = aes.state[2][2];
aes.state[2][2] = temp;
temp = aes.state[1][2];
aes.state[1][2] = aes.state[3][2];
aes.state[3][2] = temp;
temp = aes.state[0][3];
aes.state[0][3] = aes.state[3][3];
aes.state[3][3] = aes.state[2][3];
aes.state[2][3] = aes.state[1][3];
aes.state[1][3] = temp;
}
fn char xtime(char x) @local
{
return ((x << 1) ^ (((x >> 7) & 1) * 0x1b));
}
fn void mix_columns(Aes* aes)
{
for (usz i = 0; i < 4; i++)
{
char t = aes.state[i][0];
char tmp = aes.state[i][0] ^ aes.state[i][1] ^ aes.state[i][2] ^ aes.state[i][3];
char tm = aes.state[i][0] ^ aes.state[i][1];
tm = xtime(tm);
aes.state[i][0] ^= tm ^ tmp;
tm = aes.state[i][1] ^ aes.state[i][2];
tm = xtime(tm);
aes.state[i][1] ^= tm ^ tmp;
tm = aes.state[i][2] ^ aes.state[i][3];
tm = xtime(tm);
aes.state[i][2] ^= tm ^ tmp;
tm = aes.state[i][3] ^ t;
tm = xtime(tm);
aes.state[i][3] ^= tm ^ tmp;
}
}
fn char multiply(char x, char y) @local
{
return (((y & 1) * x) ^
(((y>>1) & 1) * xtime(x)) ^
(((y>>2) & 1) * xtime(xtime(x))) ^
(((y>>3) & 1) * xtime(xtime(xtime(x)))) ^
(((y>>4) & 1) * xtime(xtime(xtime(xtime(x))))));
}
fn void inv_mix_columns(Aes* aes)
{
for (int i = 0; i < 4; i++)
{
char a = aes.state[i][0];
char b = aes.state[i][1];
char c = aes.state[i][2];
char d = aes.state[i][3];
aes.state[i][0] = multiply(a, 0x0e) ^ multiply(b, 0x0b) ^ multiply(c, 0x0d) ^ multiply(d, 0x09);
aes.state[i][1] = multiply(a, 0x09) ^ multiply(b, 0x0e) ^ multiply(c, 0x0b) ^ multiply(d, 0x0d);
aes.state[i][2] = multiply(a, 0x0d) ^ multiply(b, 0x09) ^ multiply(c, 0x0e) ^ multiply(d, 0x0b);
aes.state[i][3] = multiply(a, 0x0b) ^ multiply(b, 0x0d) ^ multiply(c, 0x09) ^ multiply(d, 0x0e);
}
}
fn void inv_sub_bytes(Aes* aes)
{
for (usz i = 0; i < 4; i++)
{
for (usz j = 0; j < 4; j++)
{
aes.state[j][i] = get_sbox_invert(aes.state[j][i]);
}
}
}
fn void inv_shift_rows(Aes* aes)
{
char temp;
temp = aes.state[3][1];
aes.state[3][1] = aes.state[2][1];
aes.state[2][1] = aes.state[1][1];
aes.state[1][1] = aes.state[0][1];
aes.state[0][1] = temp;
temp = aes.state[0][2];
aes.state[0][2] = aes.state[2][2];
aes.state[2][2] = temp;
temp = aes.state[1][2];
aes.state[1][2] = aes.state[3][2];
aes.state[3][2] = temp;
temp = aes.state[0][3];
aes.state[0][3] = aes.state[1][3];
aes.state[1][3] = aes.state[2][3];
aes.state[2][3] = aes.state[3][3];
aes.state[3][3] = temp;
}
fn void aes_cipher(Aes* aes, char[] round_key)
{
usz round = 0;
add_round_key(aes, 0, round_key);
for LOOP: (round = 1;; round++)
{
sub_bytes(aes);
shift_rows(aes);
if (round == aes.type.nr) break LOOP;
mix_columns(aes);
add_round_key(aes, round, round_key);
}
add_round_key(aes, aes.type.nr, round_key);
}
fn void inv_cipher(Aes* aes, char[] round_key)
{
add_round_key(aes, aes.type.nr, round_key);
for (usz round = aes.type.nr - 1; ; round--)
{
inv_shift_rows(aes);
inv_sub_bytes(aes);
add_round_key(aes, round, round_key);
if (!round) return;
inv_mix_columns(aes);
}
}
<*¨
@param type : "The AES variant to expant the key for"
@param [inout] round_key : "Key to expand into"
@param [in] key : "The key to expand"
@require key.len == type.key.key_len : "Key does not match expected length."
*>
fn void key_expansion(AesType type, char[] key, char[] round_key) @private
{
usz nk = type.key.nk;
for (usz i = 0; i < nk; i++)
{
round_key[(i * 4) + 0] = key[(i * 4) + 0];
round_key[(i * 4) + 1] = key[(i * 4) + 1];
round_key[(i * 4) + 2] = key[(i * 4) + 2];
round_key[(i * 4) + 3] = key[(i * 4) + 3];
}
for (usz i = nk; i < COLNUM * (type.key.nr + 1); i++)
{
usz k = (i - 1) * 4;
char[4] tempa @noinit;
tempa[0] = round_key[k + 0];
tempa[1] = round_key[k + 1];
tempa[2] = round_key[k + 2];
tempa[3] = round_key[k + 3];
if (i % nk == 0)
{
// rotword
char tmp = tempa[0];
tempa[0] = tempa[1];
tempa[1] = tempa[2];
tempa[2] = tempa[3];
tempa[3] = tmp;
// subword
tempa[0] = get_sbox_value(tempa[0]);
tempa[1] = get_sbox_value(tempa[1]);
tempa[2] = get_sbox_value(tempa[2]);
tempa[3] = get_sbox_value(tempa[3]);
tempa[0] = tempa[0] ^ RCON[i / nk];
}
if (type.key.key_size == 256)
{
if (i % nk == 4)
{
// subword
tempa[0] = get_sbox_value(tempa[0]);
tempa[1] = get_sbox_value(tempa[1]);
tempa[2] = get_sbox_value(tempa[2]);
tempa[3] = get_sbox_value(tempa[3]);
}
}
usz j = i * 4;
k = (i - nk) * 4;
round_key[j + 0] = round_key[k + 0] ^ tempa[0];
round_key[j + 1] = round_key[k + 1] ^ tempa[1];
round_key[j + 2] = round_key[k + 2] ^ tempa[2];
round_key[j + 3] = round_key[k + 3] ^ tempa[3];
}
}

View File

@@ -0,0 +1,87 @@
// Experimental implementation
module std::crypto::aes128;
import std::crypto::aes;
fn char[] encrypt(Allocator allocator, char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
{
Aes aes @noinit;
aes.init(AES128, key, iv, CTR);
defer aes.destroy();
return aes.encrypt(allocator, data);
}
fn char[] tencrypt(char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
{
return encrypt(tmem, key, iv, data);
}
fn char[] decrypt(Allocator allocator, char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
{
Aes aes @noinit;
aes.init(AES128, key, iv, CTR);
defer aes.destroy();
return aes.decrypt(allocator, data);
}
fn char[] tdecrypt(char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
{
return decrypt(tmem, key, iv, data);
}
module std::crypto::aes192;
import std::crypto::aes;
fn char[] encrypt(Allocator allocator, char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
{
Aes aes @noinit;
aes.init(AES192, key, iv, CTR);
defer aes.destroy();
return aes.encrypt(allocator, data);
}
fn char[] tencrypt(char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
{
return encrypt(tmem, key, iv, data);
}
fn char[] decrypt(Allocator allocator, char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
{
Aes aes @noinit;
aes.init(AES192, key, iv, CTR);
defer aes.destroy();
return aes.decrypt(allocator, data);
}
fn char[] tdecrypt(char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
{
return decrypt(tmem, key, iv, data);
}
module std::crypto::aes256;
import std::crypto::aes;
fn char[] encrypt(Allocator allocator, char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
{
Aes aes @noinit;
aes.init(AES256, key, iv, CTR);
defer aes.destroy();
return aes.encrypt(allocator, data);
}
fn char[] tencrypt(char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
{
return encrypt(tmem, key, iv, data);
}
fn char[] decrypt(Allocator allocator, char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
{
Aes aes @noinit;
aes.init(AES256, key, iv, CTR);
defer aes.destroy();
return aes.decrypt(allocator, data);
}
fn char[] tdecrypt(char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
{
return decrypt(tmem, key, iv, data);
}

View File

@@ -9,4 +9,3 @@ fn bool safe_compare(void* data1, void* data2, usz len)
}
return match == 0;
}

View File

@@ -212,7 +212,8 @@ fn F25519Int pack(Point* p)
struct Unpacking
{
Point point;
char on_curve; // Non-zero if true.
<* Non-zero if true. *>
char on_curve;
}
<*
@@ -365,7 +366,7 @@ fn void F25519Int.normalize(&s)
{
s.reduce_carry((*s)[^1] >> 7);
// Substract p
// Subtract p
F25519Int sub @noinit;
ushort c = 19;
foreach (i, v : (*s)[:^1])
@@ -399,7 +400,7 @@ fn char eq(F25519Int* a, F25519Int* b)
}
<*
Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
Constant-time conditional selection. Result is undefined if condition is neither 0 nor 1.
@param [&in] zero : "selected if condition is 0"
@param [&in] one : "selected if condition is 1"
@@ -441,7 +442,7 @@ fn F25519Int F25519Int.add(&s, F25519Int* n) @operator(+)
macro F25519Int F25519Int.@sub(&s, F25519Int #n) @operator(-) => s.sub(@addr(#n));
<*
Substraction.
Subtraction.
@param [&in] s
@param [&in] n
@@ -638,7 +639,7 @@ fn FBaseInt from_bytes(char[] bytes)
}
<*
Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
Constant-time conditional selection. Result is undefined if condition is neither 0 nor 1.
@param [&in] zero : "selected if condition is 0"
@param [&in] one : "selected if condition is 1"
@@ -676,7 +677,7 @@ fn FBaseInt FBaseInt.add(&s, FBaseInt* n) @operator(+)
}
<*
Substraction if RHS is less than LHS else identity.
Subtraction if RHS is less than LHS else identity.
@param [&in] s
@param [&in] n

View File

@@ -26,15 +26,7 @@ fn Object*? parse(Allocator allocator, InStream s)
JsonContext context = { .last_string = dstring::new_with_capacity(smem, 64), .stream = s, .allocator = allocator };
@pool()
{
Object* o = parse_any(&context)!;
defer catch o.free();
while (char c = read_next(&context)!, c != 0)
{
if (c.is_space()) continue;
return UNEXPECTED_CHARACTER?;
}
if (!@catch(context.stream.read_byte())) return UNEXPECTED_CHARACTER?;
return o;
return parse_any(&context)!;
};
};
}

View File

@@ -33,7 +33,7 @@
module std::hash::a5hash;
macro @a5mul(#u, #v, #lo, #hi) @local
macro void @a5mul(#u, #v, #lo, #hi) @local
{
uint128 imd = (uint128)#u * (uint128)#v;
#lo = (ulong)imd;

View File

@@ -83,7 +83,7 @@ fn char[HASH_BYTES] Hmac.final(&self)
const IPAD @private = 0x36;
const OPAD @private = 0x5C;
macro @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[] out)
macro void @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[] out)
{
assert(out.len == HASH_BYTES);
char[HASH_BYTES] tmp @noinit;
@@ -104,4 +104,4 @@ macro @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[]
out[i] ^= v;
}
}
}
}

View File

@@ -33,7 +33,7 @@
module std::hash::komi;
macro @komimul(#u, #v, #lo, #hi) @local
macro void @komimul(#u, #v, #lo, #hi) @local
{
uint128 imd = (uint128)#u * (uint128)#v;
#lo = (ulong)imd;

View File

@@ -106,7 +106,7 @@ macro @h(x, y, z) => (x ^ y) ^ z;
macro @h2(x, y, z) => x ^ (y ^ z);
macro @i(x, y, z) => y ^ (x | ~z);
macro @step(#f, a, b, c, d, ptr, n, t, s)
macro void @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));

View File

@@ -103,13 +103,13 @@ union Long16 @local
uint[16] l;
}
macro blk(Long16* block, i) @local
macro uint blk(Long16* block, i) @local
{
return (block.l[i & 15] = (block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15]
^ block.l[(i + 2) & 15] ^ block.l[i & 15]).rotl(1));
}
macro blk0(Long16* block, i) @local
macro uint blk0(Long16* block, i) @local
{
$if env::BIG_ENDIAN:
return block.l[i];
@@ -119,35 +119,35 @@ macro blk0(Long16* block, i) @local
$endif
}
macro r0(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro void r0(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + v.rotl(5);
*wref = w.rotl(30);
}
macro r1(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro void r1(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += ((w & (x ^ y)) ^ y) + blk(block, i) + 0x5A827999 + v.rotl(5);
*wref = w.rotl(30);
}
macro r2(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro void r2(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += (w ^ x ^ y) + blk(block, i) + 0x6ED9EBA1 + v.rotl(5);
*wref = w.rotl(30);
}
macro r3(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro void r3(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += (((w | x) & y) | (w & x)) + blk(block, i) + 0x8F1BBCDC + v.rotl(5);
*wref = w.rotl(30);
}
macro r4(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro void r4(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
var w = *wref;
*z += (w ^ x ^ y) + blk(block, i) + 0xCA62C1D6 + v.rotl(5);
@@ -254,4 +254,4 @@ fn void sha1_transform(uint[5]* state, char* buffer) @local
(*state)[4] += e;
a = b = c = d = e = 0;
block = {};
}
}

View File

@@ -1,52 +1,52 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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.
//
// SipHash is a secure pseudorandom function (PRF) which digests a 128-bit key
// and a variable-length message to produce a 64- or 128-bit hash value.
//
// SipHash can be employed in numerous useful ways and structures, e.g.:
// - Hash Tables
// - Message Authentication Codes
// - Denial of Service (hash flooding) resistance
// - Bloom filters
// - Keyed runtime identifier derivation
//
// Read more: https://en.wikipedia.org/wiki/SipHash
//
//
// COMMON HASH VARIANTS.
// These two forms of SipHash (24 and 48) are the most widely
// used by many implementations.
// These provide typical 64-bit hash results.
// -- Best for performance-critical applications.
<*
Best for performance-critical applications.
See std::hash::siphash for more information.
*>
module std::hash::siphash24;
import std::hash::siphash;
alias SipHash24 = SipHash { ulong, 2, 4 };
alias hash = siphash::hash { ulong, 2, 4 };
// -- Best for conservative security applications.
<*
Best for security-focused applications.
See std::hash::siphash for more information.
*>
module std::hash::siphash48;
import std::hash::siphash;
alias SipHash48 = SipHash { ulong, 4, 8 };
alias hash = siphash::hash { ulong, 4, 8 };
// Exact same as above, but for 128-bit outputs. Algorithm internally changes slightly.
<* Exact same as siphash24, but for 128-bit outputs. Algorithm internally changes slightly. *>
module std::hash::siphash24_128;
import std::hash::siphash;
alias SipHash24_128 = SipHash { uint128, 2, 4 };
alias hash = siphash::hash { uint128, 2, 4 };
<* Exact same as siphash48, but for 128-bit outputs. Algorithm internally changes slightly. *>
module std::hash::siphash48_128;
import std::hash::siphash;
alias SipHash48_128 = SipHash { uint128, 4, 8 };
alias hash = siphash::hash { uint128, 4, 8 };
<*
@require OutType.typeid == uint128.typeid || OutType.typeid == ulong.typeid : "Module OutType must be either uint128 or ulong."
SipHash is a secure pseudorandom function (PRF) which digests a 128-bit key
and a variable-length message to produce a 64- or 128-bit hash value.
SipHash can be employed in numerous useful ways and structures, e.g.:
- Hash Tables
- Message Authentication Codes
- Denial of Service (hash flooding) resistance
- Bloom filters
- Keyed runtime identifier derivation
Read more: https://en.wikipedia.org/wiki/SipHash
@require OutType.typeid == uint128.typeid || OutType.typeid == ulong.typeid : "Module OutType must be either uint128 or ulong."
*>
module std::hash::siphash { OutType, BLOCK_ROUNDS, FINALIZE_ROUNDS };

92
lib/std/io/file_mmap.c3 Normal file
View File

@@ -0,0 +1,92 @@
module std::io::file::mmap @if(env::LIBC &&& env::POSIX);
import std::core::mem::vm, std::io::file;
struct FileMmap
{
File file;
VirtualMemory vm;
usz offset;
usz len;
}
<*
Provides a slice of bytes to the expected mapped range discarding the extra bytes due to misaligment of offset and/or size.
@return "Slice of the mapped range where the first byte matches the file's byte at the offset specified to File::file_mmap()"
*>
fn char[] FileMmap.bytes(&self)
{
return self.vm.ptr[self.offset:self.len];
}
<*
Destroys the underlying VirtualMemory object ie. calls munmap()"
*>
fn void? FileMmap.destroy(&self) @maydiscard
{
fault err1 = @catch(self.file.close());
fault err2 = @catch(self.vm.destroy());
if (err1) return err1?;
if (err2) return err2?;
}
module std::io::file @if(env::LIBC &&& env::POSIX);
<*
Maps a region of an already-opened file into memory
@param file : "Already opened file created on the caller scope"
@param offset : "Byte offset in file, will be rounded down to page size"
@param len : "Size in bytes to map starting from offset, will be rounded up to page size"
@return? mem::OUT_OF_MEMORY, vm::ACCESS_DENIED, vm::RANGE_OVERFLOW, vm::INVALID_ARGS, vm::UNKNOWN_ERROR, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
@return "Memory mapped region. Must be released with FileMmap.destroy(). Provided File will not be closed"
*>
fn FileMmap? mmap_file(File file, usz offset = 0, usz len = 0, VirtualMemoryAccess access = READ, bool shared = false)
{
if (len == 0)
{
usz cur = file.seek(0, CURSOR)!;
defer file.seek(cur, SET)!!;
usz file_size = file.seek(0, END)!;
len = file_size - offset;
}
// get the page size
usz page_size = vm::aligned_alloc_size(0);
// align the offset specified by the user (might be not aligned)
usz page_offset = offset & (page_size - 1);
usz map_offset = offset - page_offset;
// adjust map length (both the region start and the region end might be not aligned)
usz map_len = len + page_offset; // when region start not aligned
map_len = vm::aligned_alloc_size(map_len); // when region end not aligned
void* ptr = vm::mmap_file(file.fd(), map_len, map_offset, access, shared)!;
// FileMmap does not own the supplied file
return {{}, {ptr, map_len, access}, page_offset, len};
}
<*
Maps a region of the given file into memory
@param filename : "File path"
@param mode : "File opening mode"
@param offset : "Byte offset in file, will be rounded down to page size"
@param len : "Size in bytes to map starting from offset, will be rounded up to page size"
@return? mem::OUT_OF_MEMORY, vm::ACCESS_DENIED, vm::RANGE_OVERFLOW, vm::INVALID_ARGS, vm::UNKNOWN_ERROR, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
@return "Memory mapped region. Must be released with FileMmap.destroy()"
*>
fn FileMmap? mmap_open(String filename, String mode, usz offset = 0, usz len = 0, VirtualMemoryAccess access = READ, bool shared = false)
{
File file = open(filename, mode)!;
defer catch (void)file.close();
FileMmap mm = mmap_file(file, offset, len, access, shared)!;
// FileMmap owns the file and it will close it on destroy()
mm.file = file;
return mm;
}

View File

@@ -27,7 +27,7 @@ macro bool is_struct_with_default_print($Type)
<*
Introspect a struct and print it to a formatter
@require @typekind(value) == STRUCT || @typekind(value) == BITSTRUCT : `This macro is only valid on macros`
@require $kindof(value) == STRUCT || $kindof(value) == BITSTRUCT : `This macro is only valid on macros`
*>
macro usz? struct_to_format(value, Formatter* f, bool $force_dump)
{
@@ -146,10 +146,14 @@ fn usz? Formatter.out_str(&self, any arg) @private
case VOID:
return self.out_substr("void");
case FAULT:
return self.out_substr((*(fault*)arg.ptr).nameof);
fault f = *(fault*)arg.ptr;
return self.out_substr(f ? f.nameof : "(empty-fault)");
case INTERFACE:
any a = *(any*)arg;
return a ? self.out_str(a) : self.out_substr("(empty-interface)");
case ANY:
return self.out_str(*(any*)arg);
any a = *(any*)arg;
return a ? self.out_str(a) : self.out_substr("(empty-any)");
case OPTIONAL:
unreachable();
case SIGNED_INT:

View File

@@ -22,7 +22,7 @@ fn usz? print_hex_chars(Formatter* f, char[] out, bool uppercase) @inline
return len;
}
macro Formatter.first_err(&self, fault f)
macro fault Formatter.first_err(&self, fault f)
{
if (self.first_fault) return self.first_fault;
self.first_fault = f;

View File

@@ -35,6 +35,7 @@ faultdef
NO_PERMISSION,
OUT_OF_SPACE,
OVERFLOW,
PATH_COULD_NOT_BE_FOUND,
READ_ONLY,
SYMLINK_FAILED,
TOO_MANY_DESCRIPTORS,
@@ -50,7 +51,7 @@ faultdef
"\r" will be filtered from the String.
@param stream : `The stream to read from.`
@require !($defined(&stream) &&& @is_instream(&stream)) : "The value for 'stream' should have been passed as a pointer and not as a value, please add '&'."
@require @is_not_instream_if_ptr(stream) : "The value for 'stream' should have been passed as a pointer and not as a value, please add '&'."
@require @is_instream(stream) : `Make sure that the stream is actually an InStream.`
@param [inout] allocator : `the allocator to use.`
@return `The string containing the data read.`
@@ -76,7 +77,7 @@ macro String? readline(Allocator allocator, stream = io::stdin())
on the temporary allocator and does not need to be freed.
@param stream : `The stream to read from.`
@require !($defined(&stream) &&& @is_instream(&stream)) : "The value for 'stream' should have been passed as a pointer and not as a value, please add '&'."
@require @is_not_instream_if_ptr(stream) : "The value for 'stream' should have been passed as a pointer and not as a value, please add '&'."
@require @is_instream(stream) : `The stream must implement InStream.`
@return `The temporary string containing the data read.`
*>
@@ -90,22 +91,22 @@ macro String? treadline(stream = io::stdin())
@param out_stream : `The stream to write to`
@param in_stream : `The stream to read from.`
@require !($defined(&in_stream) &&& @is_instream(&in_stream)) : "The value for 'in_stream' should have been passed as a pointer and not as a value, please add '&'."
@require !($defined(&out_stream) &&& @is_outstream(&out_stream)) : "The value for 'out_stream' should have been passed as a pointer and not as a value, please add '&'."
@require @is_not_instream_if_ptr(in_stream) : "The value for 'in_stream' should have been passed as a pointer and not as a value, please add '&'."
@require @is_not_outstream_if_ptr(out_stream) : "The value for 'out_stream' should have been passed as a pointer and not as a value, please add '&'."
@require @is_instream(in_stream) : `The in_stream must implement InStream.`
@require @is_outstream(out_stream) : `The out_stream must implement OutStream.`
@return `The number of bytes written`
*>
macro usz? readline_to_stream(out_stream, in_stream = io::stdin())
{
bool $is_stream = @typeis(in_stream, InStream);
bool $is_stream = $typeof(in_stream) == InStream;
$if $is_stream:
var func = &in_stream.read_byte;
char val = func((void*)in_stream)!;
$else
char val = in_stream.read_byte()!;
$endif
bool $is_out_stream = @typeis(out_stream, OutStream);
bool $is_out_stream = $typeof(out_stream) == OutStream;
$if $is_out_stream:
var out_func = &out_stream.write_byte;
$endif
@@ -160,7 +161,7 @@ macro usz? fprint(out, x)
$case ZString: return out.write(x.str_view());
$case DString: return out.write(x.str_view());
$default:
$if @assignable_to(x, String):
$if $defined(String a = x):
return out.write((String)x);
$else
$if is_struct_with_default_print($Type):
@@ -215,7 +216,7 @@ macro usz? fprintn(out, x = "")
usz len = fprint(out, x)!;
out.write_byte('\n')!;
$switch:
$case @typeid(out) == OutStream.typeid:
$case $typeof(out) == OutStream:
if (&out.flush) out.flush()!;
$case $defined(out.flush):
out.flush()!;
@@ -254,7 +255,7 @@ macro void eprint(x)
@param x : "The value to print"
*>
macro void eprintn(x)
macro void eprintn(x = "")
{
(void)fprintn(io::stderr(), x);
}

View File

@@ -1,5 +1,112 @@
module std::io::os;
enum NativeSystemDir
{
DESKTOP,
DOCUMENTS,
VIDEOS,
MUSIC,
DOWNLOADS,
PICTURES,
TEMPLATES,
PUBLIC_SHARE,
SAVED_GAMES,
SCREENSHOTS
}
module std::io::os @if(env::LIBC);
import std::io::path, std::os;
import std::io, std::os;
fn String? win32_get_known_folder_temp(Win32_REFKNOWNFOLDERID rfid) @private @if(env::WIN32)
{
Win32_PWSTR path;
Win32_HRESULT res = win32::shGetKnownFolderPath(rfid, 0x00008000 /* KF_FLAG_CREATE */, null, &path);
if (res) return io::PATH_COULD_NOT_BE_FOUND?;
return string::from_wstring(tmem, (WString)path);
}
fn Path? native_home_directory(Allocator allocator) => @pool()
{
$switch env::OS_TYPE:
$case IOS:
$case MACOS:
$case TVOS:
$case WATCHOS:
$case FREEBSD:
$case KFREEBSD:
$case LINUX:
$case NETBSD:
$case OPENBSD:
$case HAIKU:
return path::new(allocator, env::tget_var("HOME")) ?? io::PATH_COULD_NOT_BE_FOUND?;
$case WIN32:
return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_PROFILE));
$default:
return io::PATH_COULD_NOT_BE_FOUND?;
$endswitch
}
fn Path? native_user_directory(Allocator allocator, NativeSystemDir dir) => @pool()
{
$switch env::OS_TYPE:
$case FREEBSD:
$case KFREEBSD:
$case LINUX:
$case NETBSD:
$case OPENBSD:
$case HAIKU:
switch (dir)
{
case DESKTOP: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "DESKTOP"));
case DOWNLOADS: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "DOWNLOAD"));
case DOCUMENTS: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "DOCUMENTS"));
case MUSIC: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "MUSIC"));
case VIDEOS: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "VIDEOS"));
case PICTURES: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "PICTURES"));
case PUBLIC_SHARE: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "PUBLICSHARE"));
case TEMPLATES: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "TEMPLATES"));
case SAVED_GAMES:
case SCREENSHOTS: nextcase;
default: return io::PATH_COULD_NOT_BE_FOUND?;
}
$case IOS:
$case MACOS:
$case WATCHOS:
$case TVOS:
switch (dir)
{
case DESKTOP: return path::new(allocator, darwin::find_first_directory_temp(DESKTOP, USER));
case DOWNLOADS: return path::new(allocator, darwin::find_first_directory_temp(DOWNLOADS, USER));
case DOCUMENTS: return path::new(allocator, darwin::find_first_directory_temp(DOCUMENT, USER));
case MUSIC: return path::new(allocator, darwin::find_first_directory_temp(MUSIC, USER));
case VIDEOS: return path::new(allocator, darwin::find_first_directory_temp(MOVIES, USER));
case PICTURES: return path::new(allocator, darwin::find_first_directory_temp(PICTURES, USER));
case PUBLIC_SHARE: return path::new(allocator, darwin::find_first_directory_temp(SHARED_PUBLIC, USER));
case SAVED_GAMES:
case SCREENSHOTS:
case TEMPLATES: nextcase;
default: return io::PATH_COULD_NOT_BE_FOUND?;
}
$case WIN32:
switch (dir)
{
case DOWNLOADS: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_DOWNLOADS));
case DOCUMENTS: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_DOCUMENTS));
case DESKTOP: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_DESKTOP));
case MUSIC: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_MUSIC));
case VIDEOS: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_VIDEOS));
case PICTURES: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_PICTURES));
case SAVED_GAMES: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_SAVED_GAMES));
case SCREENSHOTS: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_SCREENSHOTS));
case TEMPLATES: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_TEMPLATES));
case PUBLIC_SHARE: nextcase;
default: return io::PATH_COULD_NOT_BE_FOUND?;
}
$default:
return io::PATH_COULD_NOT_BE_FOUND?;
$endswitch
}
fn Path? native_temp_directory(Allocator allocator) @if(!env::WIN32)
{
@@ -23,7 +130,6 @@ fn Path? native_temp_directory(Allocator allocator) @if(env::WIN32) => @pool()
module std::io::os @if(env::NO_LIBC);
import std::io::path;
macro Path? native_temp_directory(Allocator allocator)
{
return io::UNSUPPORTED_OPERATION?;
}
macro Path? native_home_directory(Allocator allocator) => io::PATH_COULD_NOT_BE_FOUND?;
macro Path? native_temp_directory(Allocator allocator) => io::PATH_COULD_NOT_BE_FOUND?;
fn Path? native_user_directory(Allocator allocator, NativeSystemDir dir) => io::PATH_COULD_NOT_BE_FOUND?;

View File

@@ -45,7 +45,7 @@ fn Path? tcwd() => cwd(tmem) @inline;
*>
macro void? chdir(path)
{
$if @typeis(path, String):
$if $typeof(path) == String:
@pool()
{
return os::native_chdir(temp(path));
@@ -57,9 +57,21 @@ macro void? chdir(path)
fn Path? temp_directory(Allocator allocator) => os::native_temp_directory(allocator);
fn Path? home_directory(Allocator allocator) => os::native_home_directory(allocator);
fn Path? desktop_directory(Allocator allocator) => os::native_user_directory(allocator, DESKTOP);
fn Path? videos_directory(Allocator allocator) => os::native_user_directory(allocator, VIDEOS);
fn Path? music_directory(Allocator allocator) => os::native_user_directory(allocator, MUSIC);
fn Path? documents_directory(Allocator allocator) => os::native_user_directory(allocator, DOCUMENTS);
fn Path? screenshots_directory(Allocator allocator) => os::native_user_directory(allocator, SCREENSHOTS);
fn Path? saved_games_directory(Allocator allocator) => os::native_user_directory(allocator, SAVED_GAMES);
fn Path? downloads_directory(Allocator allocator) => os::native_user_directory(allocator, DOWNLOADS);
fn Path? pictures_directory(Allocator allocator) => os::native_user_directory(allocator, PICTURES);
fn Path? templates_directory(Allocator allocator) => os::native_user_directory(allocator, TEMPLATES);
fn Path? public_share_directory(Allocator allocator) => os::native_user_directory(allocator, PUBLIC_SHARE);
fn void? delete(Path path) => os::native_remove(path.str_view()) @inline;
macro bool @is_pathlike(#path) => @typeis(#path, String) || @typeis(#path, Path);
macro bool @is_pathlike(#path) @const => $typeof(#path) == String ||| $typeof(#path) == Path;
macro bool is_separator(char c, PathEnv path_env = DEFAULT_ENV)
{
@@ -95,7 +107,7 @@ enum MkdirPermissions
*>
macro bool? mkdir(path, bool recursive = false, MkdirPermissions permissions = NORMAL)
{
$if @typeis(path, String):
$if $typeof(path) == String:
@pool() { return _mkdir(temp(path), recursive, permissions); };
$else
return _mkdir(path, recursive, permissions);
@@ -113,7 +125,7 @@ macro bool? mkdir(path, bool recursive = false, MkdirPermissions permissions = N
*>
macro bool? rmdir(path)
{
$if @typeis(path, String):
$if $typeof(path) == String:
@pool() { return _rmdir(temp(path)); };
$else
return _mkdir(path);

View File

@@ -1,5 +1,7 @@
module std::io;
import std::math;
import std::core::env;
interface InStream
{
@@ -37,14 +39,24 @@ fn usz? available(InStream s)
return 0;
}
macro bool @is_instream(#expr)
macro bool @is_instream(#expr) @const
{
return @assignable_to(#expr, InStream);
return $defined(InStream i = #expr);
}
macro bool @is_outstream(#expr)
macro bool @is_not_instream_if_ptr(#expr) @const
{
return @assignable_to(#expr, OutStream);
return !$defined(&#expr) ||| !@is_instream(&#expr);
}
macro bool @is_outstream(#expr) @const
{
return $defined(OutStream s = #expr);
}
macro bool @is_not_outstream_if_ptr(#expr) @const
{
return !$defined(&#expr) ||| !@is_outstream(&#expr);
}
<*
@@ -187,7 +199,7 @@ const char[*] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 };
<*
@require @is_instream(stream)
@require @typekind(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
@require $kindof(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
*>
macro usz? read_varint(stream, x_ptr)
{
@@ -222,7 +234,7 @@ macro usz? read_varint(stream, x_ptr)
}
<*
@require @is_outstream(stream)
@require @typekind(x).is_int()
@require $kindof(x).is_int()
*>
macro usz? write_varint(stream, x)
{
@@ -250,6 +262,16 @@ macro ushort? read_be_ushort(stream)
return (ushort)(hi_byte << 8 | lo_byte);
}
<*
@require @is_instream(stream)
*>
macro ushort? read_le_ushort(stream)
{
char lo_byte = stream.read_byte()!;
char hi_byte = stream.read_byte()!;
return (ushort)(hi_byte << 8 | lo_byte);
}
<*
@require @is_instream(stream)
*>
@@ -258,6 +280,14 @@ macro short? read_be_short(stream)
return read_be_ushort(stream);
}
<*
@require @is_instream(stream)
*>
macro short? read_le_short(stream)
{
return read_le_ushort(stream);
}
<*
@require @is_outstream(stream)
*>
@@ -267,6 +297,15 @@ macro void? write_be_short(stream, ushort s)
stream.write_byte((char)s)!;
}
<*
@require @is_outstream(stream)
*>
macro void? write_le_short(stream, ushort s)
{
stream.write_byte((char)s)!;
stream.write_byte((char)(s >> 8))!;
}
<*
@require @is_instream(stream)
*>
@@ -278,6 +317,17 @@ macro uint? read_be_uint(stream)
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro uint? read_le_uint(stream)
{
uint val = stream.read_byte()!;
val += stream.read_byte()! << 8;
val += stream.read_byte()! << 16;
return val + stream.read_byte()! << 24;
}
<*
@require @is_instream(stream)
*>
@@ -286,6 +336,14 @@ macro int? read_be_int(stream)
return read_be_uint(stream);
}
<*
@require @is_instream(stream)
*>
macro int? read_le_int(stream)
{
return read_le_uint(stream);
}
<*
@require @is_outstream(stream)
*>
@@ -297,6 +355,17 @@ macro void? write_be_int(stream, uint s)
stream.write_byte((char)s)!;
}
<*
@require @is_outstream(stream)
*>
macro void? write_le_int(stream, uint s)
{
stream.write_byte((char)s)!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 24))!;
}
<*
@require @is_instream(stream)
*>
@@ -312,6 +381,21 @@ macro ulong? read_be_ulong(stream)
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro ulong? read_le_ulong(stream)
{
ulong val = (ulong)stream.read_byte()!;
val += (ulong)stream.read_byte()! << 8;
val += (ulong)stream.read_byte()! << 16;
val += (ulong)stream.read_byte()! << 24;
val += (ulong)stream.read_byte()! << 32;
val += (ulong)stream.read_byte()! << 40;
val += (ulong)stream.read_byte()! << 48;
return val + (ulong)stream.read_byte()! << 56;
}
<*
@require @is_instream(stream)
*>
@@ -320,6 +404,14 @@ macro long? read_be_long(stream)
return read_be_ulong(stream);
}
<*
@require @is_instream(stream)
*>
macro long? read_le_long(stream)
{
return read_le_ulong(stream);
}
<*
@require @is_outstream(stream)
*>
@@ -335,6 +427,21 @@ macro void? write_be_long(stream, ulong s)
stream.write_byte((char)s)!;
}
<*
@require @is_outstream(stream)
*>
macro void? write_le_long(stream, ulong s)
{
stream.write_byte((char)s)!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 32))!;
stream.write_byte((char)(s >> 40))!;
stream.write_byte((char)(s >> 48))!;
stream.write_byte((char)(s >> 56))!;
}
<*
@require @is_instream(stream)
*>
@@ -358,6 +465,29 @@ macro uint128? read_be_uint128(stream)
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro uint128? read_le_uint128(stream)
{
uint128 val = stream.read_byte()!;
val += (uint128)stream.read_byte()! << 8;
val += (uint128)stream.read_byte()! << 16;
val += (uint128)stream.read_byte()! << 24;
val += (uint128)stream.read_byte()! << 32;
val += (uint128)stream.read_byte()! << 40;
val += (uint128)stream.read_byte()! << 48;
val += (uint128)stream.read_byte()! << 56;
val += (uint128)stream.read_byte()! << 64;
val += (uint128)stream.read_byte()! << 72;
val += (uint128)stream.read_byte()! << 80;
val += (uint128)stream.read_byte()! << 88;
val += (uint128)stream.read_byte()! << 96;
val += (uint128)stream.read_byte()! << 104;
val += (uint128)stream.read_byte()! << 112;
return val + (uint128)stream.read_byte()! << 120;
}
<*
@require @is_instream(stream)
*>
@@ -366,6 +496,14 @@ macro int128? read_be_int128(stream)
return read_be_uint128(stream);
}
<*
@require @is_instream(stream)
*>
macro int128? read_le_int128(stream)
{
return read_le_uint128(stream);
}
<*
@require @is_outstream(stream)
*>
@@ -389,6 +527,30 @@ macro void? write_be_int128(stream, uint128 s)
stream.write_byte((char)s)!;
}
<*
@require @is_outstream(stream)
*>
macro void? write_le_int128(stream, uint128 s)
{
stream.write_byte((char)s)!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 32))!;
stream.write_byte((char)(s >> 40))!;
stream.write_byte((char)(s >> 48))!;
stream.write_byte((char)(s >> 56))!;
stream.write_byte((char)(s >> 64))!;
stream.write_byte((char)(s >> 72))!;
stream.write_byte((char)(s >> 80))!;
stream.write_byte((char)(s >> 88))!;
stream.write_byte((char)(s >> 96))!;
stream.write_byte((char)(s >> 104))!;
stream.write_byte((char)(s >> 112))!;
stream.write_byte((char)(s >> 120))!;
}
<*
@require @is_outstream(stream)
@require data.len < 256 : "Data exceeded 255"
@@ -433,6 +595,34 @@ macro char[]? read_short_bytearray(stream, Allocator allocator)
return data;
}
<*
@require @is_instream(stream)
*>
macro void? skip(stream, usz bytes)
{
if (!bytes) return;
$switch:
$case !$defined(stream.seek):
for (usz i = 0; i < bytes; i++)
{
stream.read()!;
}
return;
$case $typeof(stream) == InStream:
if (!&stream.seek)
{
for (usz i = 0; i < bytes; i++)
{
stream.read()!;
}
return;
}
stream.seek(bytes, CURSOR)!;
$default:
stream.seek(bytes, CURSOR)!;
$endswitch
}
<*
Wrap bytes for reading using io functions.
*>

View File

@@ -13,7 +13,7 @@ struct ByteBuffer (InStream, OutStream)
<*
ByteBuffer provides a streamable read/write buffer.
max_read defines how many bytes might be kept before its internal buffer is shrinked.
max_read defines how many bytes might be kept before its internal buffer is shrunk.
@require self.bytes.len == 0 : "Buffer already initialized."
*>
fn ByteBuffer* ByteBuffer.init(&self, Allocator allocator, usz max_read, usz initial_capacity = 16)
@@ -135,7 +135,7 @@ fn void ByteBuffer.grow(&self, usz n)
self.bytes = p[:n];
}
macro ByteBuffer.shrink(&self)
macro void ByteBuffer.shrink(&self)
{
if (self.read_idx >= self.max_read)
{
@@ -145,4 +145,4 @@ macro ByteBuffer.shrink(&self)
self.write_idx = 1 + readable;
self.read_idx = 1;
}
}
}

View File

@@ -259,6 +259,8 @@ macro CFile stdout() { return (CFile*)(uptr)STDOUT_FD; }
macro CFile stderr() { return (CFile*)(uptr)STDERR_FD; }
module libc @if(!env::LIBC);
import std::core::mem;
fn void longjmp(JmpBuf* buffer, CInt value) @weak @extern("longjmp") @nostrip
{
@@ -288,22 +290,9 @@ fn void* realloc(void* ptr, usz size) @weak @extern("realloc") @nostrip
unreachable("realloc unavailable");
}
fn void* memcpy(void* dest, void* src, usz n) @weak @extern("memcpy") @nostrip
{
for (usz i = 0; i < n; i++) ((char*)dest)[i] = ((char*)src)[i];
return dest;
}
fn void* memmove(void* dest, void* src, usz n) @weak @extern("memmove") @nostrip
{
return memcpy(dest, src, n) @inline;
}
fn void* memset(void* dest, CInt value, usz n) @weak @extern("memset") @nostrip
{
for (usz i = 0; i < n; i++) ((char*)dest)[i] = (char)value;
return dest;
}
alias memcpy = mem::__memcpy;
alias memmove = mem::__memcpy;
alias memset = mem::__memset;
fn int fseek(CFile stream, SeekIndex offset, int whence) @weak @extern("fseek") @nostrip
{
@@ -418,17 +407,28 @@ alias SeekIndex = CLong;
struct Tm
{
CInt tm_sec; // seconds after the minute [0-60]
CInt tm_min; // minutes after the hour [0-59]
CInt tm_hour; // hours since midnight [0-23]
CInt tm_mday; // day of the month [1-31]
CInt tm_mon; // months since January [0-11]
CInt tm_year; // years since 1900
CInt tm_wday; // days since Sunday [0-6]
CInt tm_yday; // days since January 1 [0-365]
CInt tm_isdst; // Daylight Savings Time flag
TimeOffset tm_gmtoff @if(!env::WIN32); /* offset from UTC in seconds */
char* tm_zone @if(!env::WIN32); /* timezone abbreviation */
<* seconds after the minute [0-60] *>
CInt tm_sec;
<* minutes after the hour [0-59] *>
CInt tm_min;
<* hours since midnight [0-23] *>
CInt tm_hour;
<* day of the month [1-31] *>
CInt tm_mday;
<* months since January [0-11] *>
CInt tm_mon;
<* years since 1900 *>
CInt tm_year;
<* days since Sunday [0-6] *>
CInt tm_wday;
<* days since January 1 [0-365] *>
CInt tm_yday;
<* Daylight Savings Time flag *>
CInt tm_isdst;
<* offset from UTC in seconds *>
TimeOffset tm_gmtoff @if(!env::WIN32);
<* timezone abbreviation *>
char* tm_zone @if(!env::WIN32);
CInt tm_nsec @if(env::WASI);
}
@@ -444,12 +444,12 @@ alias Clock_t @if(env::WIN32) = int;
alias Clock_t @if(!env::WIN32) = CLong;
alias TimeOffset @if(env::WASI) = int;
alias TimeOffset @if(!env::WASI) = CLong ;
alias TimeOffset @if(!env::WASI) = CLong;
const int TIME_UTC = 1;
// This is a best-effort aproximation, but the C standard does not enforce
// This is a best-effort approximation, but the C standard does not enforce
// that this is a compile-time standard.
const CLOCKS_PER_SEC @if(env::WIN32) = 1000;
const CLOCKS_PER_SEC @if(!env::WIN32) = 1000000;

View File

@@ -17,17 +17,28 @@ struct Stat
Gid_t st_gid;
Dev_t st_rdev;
TimeSpec st_atimespec; // time of last access
TimeSpec st_mtimespec; // time of last data modification
TimeSpec st_ctimespec; // time of last status change
TimeSpec st_birthtimespec; // time of file creation(birth)
Off_t st_size; // file size, in bytes
Blkcnt_t st_blocks; // blocks allocated for file
Blksize_t st_blocksize; // optimal blocksize for I/O
uint st_flags; // user defined flags for file
uint st_gen; // file generation number
int st_lspare; // RESERVED
long[2] st_qspare; // RESERVED
<* time of last access *>
TimeSpec st_atimespec;
<* time of last data modification *>
TimeSpec st_mtimespec;
<* time of last status change *>
TimeSpec st_ctimespec;
<* time of file creation(birth) *>
TimeSpec st_birthtimespec;
<* file size, in bytes *>
Off_t st_size;
<* blocks allocated for file *>
Blkcnt_t st_blocks;
<* optimal blocksize for I/O *>
Blksize_t st_blocksize;
<* user defined flags for file *>
uint st_flags;
<* file generation number *>
uint st_gen;
<* RESERVED *>
int st_lspare;
<* RESERVED *>
long[2] st_qspare;
}
extern fn int stat(ZString str, Stat* stat) @extern("stat64");

View File

@@ -56,5 +56,5 @@ struct Stat @if(!env::X86_64)
extern fn CInt stat(ZString path, Stat* stat);
extern fn CInt get_nprocs();
extern fn CInt get_nprocs_conf();
extern fn CInt sysctl(CInt *name, CUInt namelen, void *oldp, usz *oldlenp, void *newp, usz newlen);

View File

@@ -64,116 +64,194 @@ extern fn CInt sigaction(CInt signum, Sigaction *action, Sigaction *oldaction);
module libc::termios @if(env::LIBC &&& env::POSIX);
typedef Cc = char;
typedef Speed = CUInt;
typedef Tcflags = CUInt;
typedef Tcactions = CInt;
bitstruct Tc_iflags : CUInt
{
bool ignbrk;
bool brkint;
bool ignpar;
bool parmrk;
bool inpck;
bool istrip;
bool inlcr;
bool igncr;
bool icrnl;
bool iuclc;
bool ixon;
bool ixany;
bool ixoff;
bool imaxbel;
bool iutf8;
}
const Tcactions TCOOFF = 0;
const Tcactions TCOON = 1;
const Tcactions TCIOFF = 2;
const Tcactions TCION = 3;
const Tcactions TCIFLUSH = 0;
const Tcactions TCOFLUSH = 1;
const Tcactions TCIOFLUSH = 2;
const Tcactions TCSANOW = 0;
const Tcactions TCSADRAIN = 1;
const Tcactions TCSAFLUSH = 2;
const Speed B0 = 0000000;
const Speed B50 = 0000001;
const Speed B75 = 0000002;
const Speed B110 = 0000003;
const Speed B134 = 0000004;
const Speed B150 = 0000005;
const Speed B200 = 0000006;
const Speed B300 = 0000007;
const Speed B600 = 0000010;
const Speed B1200 = 0000011;
const Speed B1800 = 0000012;
const Speed B2400 = 0000013;
const Speed B4800 = 0000014;
const Speed B9600 = 0000015;
const Speed B19200 = 0000016;
const Speed B38400 = 0000017;
const Speed B57600 = 0010001;
const Speed B115200 = 0010002;
const Speed B230400 = 0010003;
const Speed B460800 = 0010004;
const Speed B500000 = 0010005;
const Speed B576000 = 0010006;
const Speed B921600 = 0010007;
const Speed B1000000 = 0010010;
const Speed B1152000 = 0010011;
const Speed B1500000 = 0010012;
const Speed B2000000 = 0010013;
const Speed B2500000 = 0010014;
const Speed B3000000 = 0010015;
const Speed B3500000 = 0010016;
const Speed B4000000 = 0010017;
const Speed MAX_BAUD = B4000000;
const Tcflags VINTR = 0;
const Tcflags VQUIT = 1;
const Tcflags VERASE = 2;
const Tcflags VKILL = 3;
const Tcflags VEOF = 4;
const Tcflags VTIME = 5;
const Tcflags VMIN = 6;
const Tcflags VSWTC = 7;
const Tcflags VSTART = 8;
const Tcflags VSTOP = 9;
const Tcflags VSUSP = 10;
const Tcflags VEOL = 11;
const Tcflags VREPRINT = 12;
const Tcflags VDISCARD = 13;
const Tcflags VWERASE = 14;
const Tcflags VLNEXT = 15;
const Tcflags VEOL2 = 16;
const Tcflags ISIG = 0000001;
const Tcflags ICANON = 0000002;
const Tcflags ECHO = 0000010;
const Tcflags ECHOE = 0000020;
const Tcflags ECHOK = 0000040;
const Tcflags ECHONL = 0000100;
const Tcflags NOFLSH = 0000200;
const Tcflags TOSTOP = 0000400;
const Tcflags IEXTEN = 0100000;
const Tcflags CSIZE = 0000060;
const Tcflags CS5 = 0000000;
const Tcflags CS6 = 0000020;
const Tcflags CS7 = 0000040;
const Tcflags CS8 = 0000060;
const Tcflags CSTOPB = 0000100;
const Tcflags CREAD = 0000200;
const Tcflags PARENB = 0000400;
const Tcflags PARODD = 0001000;
const Tcflags HUPCL = 0002000;
const Tcflags CLOCAL = 0004000;
const Tcflags OPOST = 0000001;
const Tcflags OLCUC = 0000002;
const Tcflags ONLCR = 0000004;
const Tcflags OCRNL = 0000010;
const Tcflags ONOCR = 0000020;
const Tcflags ONLRET = 0000040;
const Tcflags OFILL = 0000100;
const Tcflags OFDEL = 0000200;
const Tcflags VTDLY = 0040000;
const Tcflags VT0 = 0000000;
const Tcflags VT1 = 0040000;
const Tcflags IGNBRK = 0000001;
const Tcflags BRKINT = 0000002;
const Tcflags IGNPAR = 0000004;
const Tcflags PARMRK = 0000010;
const Tcflags INPCK = 0000020;
const Tcflags ISTRIP = 0000040;
const Tcflags INLCR = 0000100;
const Tcflags IGNCR = 0000200;
const Tcflags ICRNL = 0000400;
const Tcflags IUCLC = 0001000;
const Tcflags IXON = 0002000;
const Tcflags IXANY = 0004000;
const Tcflags IXOFF = 0010000;
const Tcflags IMAXBEL = 0020000;
const Tcflags IUTF8 = 0040000;
bitstruct Tc_oflags : CUInt
{
bool opost : 0;
bool olcuc : 1;
bool onlcr : 2;
bool ocrnl : 3;
bool onocr : 4;
bool onlret : 5;
bool ofill : 6;
bool ofdel : 7;
T_nldly nldly : 8..8;
T_crdly crdly : 9..10;
T_tabdly tabdly : 11..12;
T_bsdly bsdly : 13..13;
T_vtdly vtdly : 14..14;
T_ffdly ffdly : 15..15;
}
bitstruct Tc_cflags : CUInt
{
T_csize csize : 4..5;
bool cstopb : 6;
bool cread : 7;
bool parenb : 8;
bool parodd : 9;
bool hupcl : 10;
bool clocal : 11;
bool addrb : 29;
}
bitstruct Tc_lflags : CUInt
{
bool isig : 0;
bool icanon : 1;
bool xcase : 2;
bool echo : 3;
bool echoe : 4;
bool echok : 5;
bool echonl : 6;
bool noflsh : 7;
bool tostop : 8;
bool echoctl : 9;
bool echoprt : 10;
bool echoke : 11;
bool flusho : 12;
bool pendin : 14;
bool iexten : 15;
bool extproc : 16;
}
enum T_nldly : const char
{
NL0 = 0b0,
NL1 = 0b1,
}
enum T_crdly : const char
{
CR0 = 0b00,
CR1 = 0b01,
CR2 = 0b10,
CR3 = 0b11,
}
enum T_tabdly : const char
{
TAB0 = 0b00,
TAB1 = 0b01,
TAB2 = 0b10,
TAB3 = 0b11,
XTABS = TAB3,
}
enum T_bsdly : const char
{
BS0 = 0b0,
BS1 = 0b1,
}
enum T_ffdly : const char
{
FF0 = 0b0,
FF1 = 0b1,
}
enum T_vtdly : const char
{
VT0 = 0b0,
VT1 = 0b1,
}
enum T_csize : const char
{
CS5 = 0b00,
CS6 = 0b01,
CS7 = 0b10,
CS8 = 0b11,
}
enum Speed : const CUInt
{
B0 = 0o0000000,
B50 = 0o0000001,
B75 = 0o0000002,
B110 = 0o0000003,
B134 = 0o0000004,
B150 = 0o0000005,
B200 = 0o0000006,
B300 = 0o0000007,
B600 = 0o0000010,
B1200 = 0o0000011,
B1800 = 0o0000012,
B2400 = 0o0000013,
B4800 = 0o0000014,
B9600 = 0o0000015,
B19200 = 0o0000016,
B38400 = 0o0000017,
B57600 = 0o0010001,
B115200 = 0o0010002,
B230400 = 0o0010003,
B460800 = 0o0010004,
B500000 = 0o0010005,
B576000 = 0o0010006,
B921600 = 0o0010007,
B1000000 = 0o0010010,
B1152000 = 0o0010011,
B1500000 = 0o0010012,
B2000000 = 0o0010013,
B2500000 = 0o0010014,
B3000000 = 0o0010015,
B3500000 = 0o0010016,
B4000000 = 0o0010017,
MAX_BAUD = B4000000,
}
enum Cc : const inline char
{
VINTR = 0,
VQUIT = 1,
VERASE = 2,
VKILL = 3,
VEOF = 4,
VTIME = 5,
VMIN = 6,
VSWTC = 7,
VSTART = 8,
VSTOP = 9,
VSUSP = 10,
VEOL = 11,
VREPRINT = 12,
VDISCARD = 13,
VWERASE = 14,
VLNEXT = 15,
VEOL2 = 16,
}
enum Tcactions : const CInt
{
TCOOFF = 0,
TCOON = 1,
TCIOFF = 2,
TCION = 3,
TCIFLUSH = 0,
TCOFLUSH = 1,
TCIOFLUSH = 2,
TCSANOW = 0,
TCSADRAIN = 1,
TCSAFLUSH = 2,
}
extern fn CInt tcgetattr(Fd fd, Termios* self);
extern fn CInt tcsetattr(Fd fd, Tcactions optional_actions, Termios* self);
@@ -189,13 +267,119 @@ extern fn CInt cfsetispeed(Termios* self, Speed speed);
const CInt NCCS = 32;
struct Termios
{
Tcflags c_iflag;
Tcflags c_oflag;
Tcflags c_cflag;
Tcflags c_lflag;
Tc_iflags c_iflag;
Tc_oflags c_oflag;
Tc_cflags c_cflag;
Tc_lflags c_lflag;
Cc c_line;
Cc[NCCS] c_cc;
Speed c_ispeed;
Speed c_ospeed;
}
}
const Tcactions TCOOFF @deprecated = 0;
const Tcactions TCOON @deprecated = 1;
const Tcactions TCIOFF @deprecated = 2;
const Tcactions TCION @deprecated = 3;
const Tcactions TCIFLUSH @deprecated = 0;
const Tcactions TCOFLUSH @deprecated = 1;
const Tcactions TCIOFLUSH @deprecated = 2;
const Tcactions TCSANOW @deprecated = 0;
const Tcactions TCSADRAIN @deprecated = 1;
const Tcactions TCSAFLUSH @deprecated = 2;
const Speed B0 @deprecated = 0o0000000;
const Speed B50 @deprecated = 0o0000001;
const Speed B75 @deprecated = 0o0000002;
const Speed B110 @deprecated = 0o0000003;
const Speed B134 @deprecated = 0o0000004;
const Speed B150 @deprecated = 0o0000005;
const Speed B200 @deprecated = 0o0000006;
const Speed B300 @deprecated = 0o0000007;
const Speed B600 @deprecated = 0o0000010;
const Speed B1200 @deprecated = 0o0000011;
const Speed B1800 @deprecated = 0o0000012;
const Speed B2400 @deprecated = 0o0000013;
const Speed B4800 @deprecated = 0o0000014;
const Speed B9600 @deprecated = 0o0000015;
const Speed B19200 @deprecated = 0o0000016;
const Speed B38400 @deprecated = 0o0000017;
const Speed B57600 @deprecated = 0o0010001;
const Speed B115200 @deprecated = 0o0010002;
const Speed B230400 @deprecated = 0o0010003;
const Speed B460800 @deprecated = 0o0010004;
const Speed B500000 @deprecated = 0o0010005;
const Speed B576000 @deprecated = 0o0010006;
const Speed B921600 @deprecated = 0o0010007;
const Speed B1000000 @deprecated = 0o0010010;
const Speed B1152000 @deprecated = 0o0010011;
const Speed B1500000 @deprecated = 0o0010012;
const Speed B2000000 @deprecated = 0o0010013;
const Speed B2500000 @deprecated = 0o0010014;
const Speed B3000000 @deprecated = 0o0010015;
const Speed B3500000 @deprecated = 0o0010016;
const Speed B4000000 @deprecated = 0o0010017;
const Speed MAX_BAUD @deprecated = B4000000;
const Cc VINTR @deprecated = 0;
const Cc VQUIT @deprecated = 1;
const Cc VERASE @deprecated = 2;
const Cc VKILL @deprecated = 3;
const Cc VEOF @deprecated = 4;
const Cc VTIME @deprecated = 5;
const Cc VMIN @deprecated = 6;
const Cc VSWTC @deprecated = 7;
const Cc VSTART @deprecated = 8;
const Cc VSTOP @deprecated = 9;
const Cc VSUSP @deprecated = 10;
const Cc VEOL @deprecated = 11;
const Cc VREPRINT @deprecated = 12;
const Cc VDISCARD @deprecated = 13;
const Cc VWERASE @deprecated = 14;
const Cc VLNEXT @deprecated = 15;
const Cc VEOL2 @deprecated = 16;
const Tc_lflags ISIG @deprecated = {.isig};
const Tc_lflags ICANON @deprecated = {.icanon};
const Tc_lflags ECHO @deprecated = {.echo};
const Tc_lflags ECHOE @deprecated = {.echoe};
const Tc_lflags ECHOK @deprecated = {.echok};
const Tc_lflags ECHONL @deprecated = {.echonl};
const Tc_lflags NOFLSH @deprecated = {.noflsh};
const Tc_lflags TOSTOP @deprecated = {.tostop};
const Tc_lflags IEXTEN @deprecated = {.iexten};
const Tc_cflags CSIZE @deprecated = {.csize = CS8};
const Tc_cflags CS5 @deprecated = {.csize = CS5};
const Tc_cflags CS6 @deprecated = {.csize = CS6};
const Tc_cflags CS7 @deprecated = {.csize = CS7};
const Tc_cflags CS8 @deprecated = {.csize = CS8};
const Tc_cflags CSTOPB @deprecated = {.cstopb};
const Tc_cflags CREAD @deprecated = {.cread};
const Tc_cflags PARENB @deprecated = {.parenb};
const Tc_cflags PARODD @deprecated = {.parodd};
const Tc_cflags HUPCL @deprecated = {.hupcl};
const Tc_cflags CLOCAL @deprecated = {.clocal};
const Tc_oflags OPOST @deprecated = {.opost};
const Tc_oflags OLCUC @deprecated = {.olcuc};
const Tc_oflags ONLCR @deprecated = {.onlcr};
const Tc_oflags OCRNL @deprecated = {.ocrnl};
const Tc_oflags ONOCR @deprecated = {.onocr};
const Tc_oflags ONLRET @deprecated = {.onlret};
const Tc_oflags OFILL @deprecated = {.ofill};
const Tc_oflags OFDEL @deprecated = {.ofdel};
const Tc_oflags VTDLY @deprecated = {.vtdly = VT1};
const Tc_oflags VT0 @deprecated = {.vtdly = VT0};
const Tc_oflags VT1 @deprecated = {.vtdly = VT1};
const Tc_iflags IGNBRK @deprecated = {.ignbrk};
const Tc_iflags BRKINT @deprecated = {.brkint};
const Tc_iflags IGNPAR @deprecated = {.ignpar};
const Tc_iflags PARMRK @deprecated = {.parmrk};
const Tc_iflags INPCK @deprecated = {.inpck};
const Tc_iflags ISTRIP @deprecated = {.istrip};
const Tc_iflags INLCR @deprecated = {.inlcr};
const Tc_iflags IGNCR @deprecated = {.igncr};
const Tc_iflags ICRNL @deprecated = {.icrnl};
const Tc_iflags IUCLC @deprecated = {.iuclc};
const Tc_iflags IXON @deprecated = {.ixon};
const Tc_iflags IXANY @deprecated = {.ixany};
const Tc_iflags IXOFF @deprecated = {.ixoff};
const Tc_iflags IMAXBEL @deprecated = {.imaxbel};
const Tc_iflags IUTF8 @deprecated = {.iutf8};

View File

@@ -1,15 +1,23 @@
module libc::termios @if(env::LIBC &&& env::POSIX);
fn int sendBreak(Fd fd, int duration) => tcsendbreak(fd, duration);
fn int send_break(Fd fd, int duration) => tcsendbreak(fd, duration);
fn int drain(Fd fd) => tcdrain(fd);
fn int flush(Fd fd, int queue_selector) => tcflush(fd, queue_selector);
fn int flow(Fd fd, int action) => tcflow(fd, action);
fn Speed Termios.getOSpeed(Termios* self) => cfgetospeed(self);
fn Speed Termios.getISpeed(Termios* self) => cfgetispeed(self);
fn int Termios.setOSpeed(Termios* self, Speed speed) => cfsetospeed(self, speed);
fn int Termios.setISpeed(Termios* self, Speed speed) => cfsetispeed(self, speed);
fn int Termios.getAttr(Termios* self, Fd fd) => tcgetattr(fd, self);
fn int Termios.setAttr(Termios* self, Fd fd, Tcactions optional_actions) => tcsetattr(fd, optional_actions, self);
fn Speed Termios.get_ospeed(&self) => cfgetospeed(self);
fn Speed Termios.get_ispeed(&self) => cfgetispeed(self);
fn int Termios.set_ospeed(&self, Speed speed) => cfsetospeed(self, speed);
fn int Termios.set_ispeed(&self, Speed speed) => cfsetispeed(self, speed);
fn int Termios.get_attr(&self, Fd fd) => tcgetattr(fd, self);
fn int Termios.set_attr(&self, Fd fd, Tcactions optional_actions) => tcsetattr(fd, optional_actions, self);
fn int sendBreak(Fd fd, int duration) @deprecated => send_break(fd, duration);
fn Speed Termios.getOSpeed(&self) @deprecated => self.get_ospeed();
fn Speed Termios.getISpeed(&self) @deprecated => self.get_ispeed();
fn int Termios.setOSpeed(&self, Speed speed) @deprecated => self.set_ospeed(speed);
fn int Termios.setISpeed(&self, Speed speed) @deprecated => self.set_ispeed(speed);
fn int Termios.getAttr(&self, Fd fd) @deprecated => self.get_attr(fd);
fn int Termios.setAttr(&self, Fd fd, Tcactions optional_actions) @deprecated => self.set_attr(fd, optional_actions);
module libc::termios @if(!env::LIBC ||| !env::POSIX);

View File

@@ -2,8 +2,8 @@ module std::math;
// Complex number aliases.
alias Complexf = Complex {float};
alias Complex = Complex {double};
alias Complexf = ComplexNumber {float};
alias Complex = ComplexNumber {double};
alias COMPLEX_IDENTITY @builtin = complex::IDENTITY {double};
alias COMPLEXF_IDENTITY @builtin = complex::IDENTITY {float};
alias IMAGINARY @builtin @deprecated("Use I") = complex::IMAGINARY { double };
@@ -19,7 +19,7 @@ alias I_F @builtin = complex::IMAGINARY { float };
module std::math::complex {Real};
import std::io;
union Complex (Printable)
union ComplexNumber (Printable)
{
struct
{
@@ -28,39 +28,39 @@ union Complex (Printable)
Real[<2>] v;
}
const Complex IDENTITY = { 1, 0 };
const Complex IMAGINARY = { 0, 1 };
const ComplexNumber IDENTITY = { 1, 0 };
const ComplexNumber IMAGINARY = { 0, 1 };
macro Complex Complex.add(self, Complex b) @operator(+) => { .v = self.v + b.v };
macro Complex Complex.add_this(&self, Complex b) @operator(+=) => { .v = self.v += b.v };
macro Complex Complex.add_real(self, Real r) @operator_s(+) => { .v = self.v + (Real[<2>]) { r, 0 } };
macro Complex Complex.add_each(self, Real b) => { .v = self.v + b };
macro Complex Complex.sub(self, Complex b) @operator(-) => { .v = self.v - b.v };
macro Complex Complex.sub_this(&self, Complex b) @operator(-=) => { .v = self.v -= b.v };
macro Complex Complex.sub_real(self, Real r) @operator(-) => { .v = self.v - (Real[<2>]) { r, 0 } };
macro Complex Complex.sub_real_inverse(self, Real r) @operator_r(-) => { .v = (Real[<2>]) { r, 0 } - self.v };
macro Complex Complex.sub_each(self, Real b) => { .v = self.v - b };
macro Complex Complex.scale(self, Real r) @operator_s(*) => { .v = self.v * r };
macro Complex Complex.mul(self, Complex b)@operator(*) => { self.r * b.r - self.c * b.c, self.r * b.c + b.r * self.c };
macro Complex Complex.div_real(self, Real r) @operator(/) => { .v = self.v / r };
macro Complex Complex.div_real_inverse(Complex c, Real r) @operator_r(/) => ((Complex) { .r = r }).div(c);
macro Complex Complex.div(self, Complex b) @operator(/)
macro ComplexNumber ComplexNumber.add(self, ComplexNumber b) @operator(+) => { .v = self.v + b.v };
macro ComplexNumber ComplexNumber.add_this(&self, ComplexNumber b) @operator(+=) => { .v = self.v += b.v };
macro ComplexNumber ComplexNumber.add_real(self, Real r) @operator_s(+) => { .v = self.v + (Real[<2>]) { r, 0 } };
macro ComplexNumber ComplexNumber.add_each(self, Real b) => { .v = self.v + b };
macro ComplexNumber ComplexNumber.sub(self, ComplexNumber b) @operator(-) => { .v = self.v - b.v };
macro ComplexNumber ComplexNumber.sub_this(&self, ComplexNumber b) @operator(-=) => { .v = self.v -= b.v };
macro ComplexNumber ComplexNumber.sub_real(self, Real r) @operator(-) => { .v = self.v - (Real[<2>]) { r, 0 } };
macro ComplexNumber ComplexNumber.sub_real_inverse(self, Real r) @operator_r(-) => { .v = (Real[<2>]) { r, 0 } - self.v };
macro ComplexNumber ComplexNumber.sub_each(self, Real b) => { .v = self.v - b };
macro ComplexNumber ComplexNumber.scale(self, Real r) @operator_s(*) => { .v = self.v * r };
macro ComplexNumber ComplexNumber.mul(self, ComplexNumber b)@operator(*) => { self.r * b.r - self.c * b.c, self.r * b.c + b.r * self.c };
macro ComplexNumber ComplexNumber.div_real(self, Real r) @operator(/) => { .v = self.v / r };
macro ComplexNumber ComplexNumber.div_real_inverse(ComplexNumber c, Real r) @operator_r(/) => ((ComplexNumber) { .r = r }).div(c);
macro ComplexNumber ComplexNumber.div(self, ComplexNumber b) @operator(/)
{
Real div = b.v.dot(b.v);
return { (self.r * b.r + self.c * b.c) / div, (self.c * b.r - self.r * b.c) / div };
}
macro Complex Complex.inverse(self)
macro ComplexNumber ComplexNumber.inverse(self)
{
Real sqr = self.v.dot(self.v);
return { self.r / sqr, -self.c / sqr };
}
macro Complex Complex.conjugate(self) => { .r = self.r, .c = -self.c };
macro Complex Complex.negate(self) @operator(-) => { .v = -self.v };
macro bool Complex.equals(self, Complex b) @operator(==) => self.v == b.v;
macro bool Complex.equals_real(self, Real r) @operator_s(==) => self.v == { r, 0 };
macro bool Complex.not_equals(self, Complex b) @operator(!=) => self.v != b.v;
macro ComplexNumber ComplexNumber.conjugate(self) => { .r = self.r, .c = -self.c };
macro ComplexNumber ComplexNumber.negate(self) @operator(-) => { .v = -self.v };
macro bool ComplexNumber.equals(self, ComplexNumber b) @operator(==) => self.v == b.v;
macro bool ComplexNumber.equals_real(self, Real r) @operator_s(==) => self.v == { r, 0 };
macro bool ComplexNumber.not_equals(self, ComplexNumber b) @operator(!=) => self.v != b.v;
fn usz? Complex.to_format(&self, Formatter* f) @dynamic
fn usz? ComplexNumber.to_format(&self, Formatter* f) @dynamic
{
return f.printf("%g%+gi", self.r, self.c);
}

View File

@@ -82,7 +82,7 @@ macro abs(x) => $$abs(x);
@require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value"
@require values::@is_int(y) || values::@is_float(y) : "Expected an integer or floating point value"
*>
macro is_approx(x, y, eps)
macro bool is_approx(x, y, eps)
{
if (x == y) return true;
if (is_nan(x) || is_nan(y)) return false;
@@ -93,7 +93,7 @@ macro is_approx(x, y, eps)
@require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value"
@require values::@is_int(y) || values::@is_float(y) : "Expected an integer or floating point value"
*>
macro is_approx_rel(x, y, eps)
macro bool is_approx_rel(x, y, eps)
{
if (x == y) return true;
if (is_nan(x) || is_nan(y)) return false;
@@ -101,7 +101,7 @@ macro is_approx_rel(x, y, eps)
}
<*
@require values::@is_int(x) : `The input must be an integer`
@require $kindof(x).is_int() : `The input must be an integer`
*>
macro sign(x)
{
@@ -119,7 +119,7 @@ macro sign(x)
*>
macro atan2(x, y)
{
$if @typeis(x, float) && @typeis(y, float):
$if $typeof(x) == float &&& $typeof(y) == float:
return _atan2f(x, y);
$else
return _atan2(x, y);
@@ -128,16 +128,16 @@ macro atan2(x, y)
<*
@require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value"
@require @typekind(sinp) == POINTER : "Expected sinp to be a pointer"
@require values::@is_same_type(sinp, cosp) : "Expected sinp and cosp to have the same type"
@require @assignable_to(x, $typeof(*sinp)) : "Expected x and sinp/cosp to have the same type"
@require $kindof(sinp) == POINTER : "Expected sinp to be a pointer"
@require @typematch(sinp, cosp) : "Expected sinp and cosp to have the same type"
@require $defined(*sinp = x) : "Expected x and *sinp/*cosp to have the same type"
*>
macro sincos_ref(x, sinp, cosp)
macro void sincos_ref(x, sinp, cosp)
{
$if @typeis(sinp, float*.typeid):
return _sincosf(x, sinp, cosp);
$if $typeof(sinp) == float*:
_sincosf(x, sinp, cosp);
$else
return _sincos(x, sinp, cosp);
_sincos(x, sinp, cosp);
$endif
}
@@ -149,7 +149,7 @@ macro sincos_ref(x, sinp, cosp)
*>
macro sincos(x)
{
$if @typeis(x, float):
$if $typeof(x) == float:
float[<2>] v @noinit;
_sincosf(x, &v[0], &v[1]);
$else
@@ -164,7 +164,7 @@ macro sincos(x)
*>
macro atan(x)
{
$if @typeis(x, float):
$if $typeof(x) == float:
return _atanf(x);
$else
return _atan(x);
@@ -176,7 +176,7 @@ macro atan(x)
*>
macro atanh(x)
{
$if @typeis(x, float):
$if $typeof(x) == float:
return _atanhf(x);
$else
return _atanh(x);
@@ -188,7 +188,7 @@ macro atanh(x)
*>
macro acos(x)
{
$if @typeis(x, float):
$if $typeof(x) == float:
return _acosf(x);
$else
return _acos(x);
@@ -200,7 +200,7 @@ macro acos(x)
*>
macro acosh(x)
{
$if @typeis(x, float):
$if $typeof(x) == float:
return _acoshf(x);
$else
return _acosh(x);
@@ -212,7 +212,7 @@ macro acosh(x)
*>
macro asin(x)
{
$if @typeis(x, float):
$if $typeof(x) == float:
return _asinf(x);
$else
return _asin(x);
@@ -224,7 +224,7 @@ macro asin(x)
*>
macro asinh(x)
{
$if @typeis(x, float):
$if $typeof(x) == float:
return _asinhf(x);
$else
return _asinh(x);
@@ -239,7 +239,7 @@ macro ceil(x) => $$ceil(x);
<*
Ceil for compile time evaluation.
@require @typeis($input, double) || @typeis($input, float) : "Only float and double may be used"
@require $kindof($input) == FLOAT : "Only float and double may be used"
*>
macro @ceil($input) @const => $$ceil($input);
@@ -252,8 +252,8 @@ macro @ceil($input) @const => $$ceil($input);
@return "lower if x < lower, upper if x > upper, otherwise return x."
@require types::is_numerical($typeof(x)) : `The input must be a numerical value or numerical vector`
@require values::@assign_to(lower, x) : `The lower bound must be convertable to the value type.`
@require values::@assign_to(upper, x) : `The upper bound must be convertable to the value type.`
@require $defined(x = lower) : `The lower bound must be convertible to the value type.`
@require $defined(x = upper) : `The upper bound must be convertible to the value type.`
*>
macro clamp(x, lower, upper) => $$max(($typeof(x))lower, $$min(x, ($typeof(x))upper));
@@ -344,6 +344,23 @@ macro log(x, base)
*>
macro log2(x) => $$log2(values::promote_int(x));
<*
@require values::@is_int($x) : `The input value must be an integer.`
@require $x >= 0 : `The input value must be a positive integer.`
@return `A floored base-2 log of an input integer value.`
*>
macro @intlog2($x)
{
$if $x <= 1:
return 0;
$endif
$typeof($x) $z = 0;
$for var $y = $x; $y > 0; $y >>= 1, ++$z: $endfor
return $z - 1;
}
<*
@require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector`
*>
@@ -351,7 +368,7 @@ macro log10(x) => $$log10(values::promote_int(x));
<*
@require types::is_numerical($typeof(x)) : `The input must be a floating point value or float vector`
@require types::is_same($typeof(x), $typeof(y)) : `The input types must be equal`
@require @typematch(x, y) : `The input types must be equal`
*>
macro max(x, y, ...)
{
@@ -396,7 +413,7 @@ macro nearbyint(x) => $$nearbyint(x);
<*
@require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector`
@require @assignable_to(exp, $typeof(values::promote_int(x))) || values::@is_int(exp) : `The input must be an integer, castable to the type of x`
@require $defined($typeof(values::promote_int(x)) v = exp) || values::@is_int(exp) : `The input must be an integer, castable to the type of x`
*>
macro pow(x, exp)
{
@@ -523,7 +540,7 @@ macro bool is_finite(x)
<*
@require values::@is_promotable_to_float(x) : `The input must be a float`
*>
macro is_nan(x)
macro bool is_nan(x)
{
$switch $typeof(x):
$case float:
@@ -537,7 +554,7 @@ macro is_nan(x)
<*
@require values::@is_promotable_to_float(x) : `The input must be a float`
*>
macro is_inf(x)
macro bool is_inf(x)
{
$switch $typeof(x):
$case float:
@@ -578,7 +595,7 @@ macro normalize(x) @private
@param then_value : "The vector to get elements from where the mask is 'true'"
@param else_value : "The vector to get elements from where the mask is 'false'"
@require values::@is_vector(then_value) && values::@is_vector(else_value) : "'Then' and 'else' must be vectors."
@require values::@is_same_type(then_value, else_value) : "'Then' and 'else' vectors must be of the same type."
@require @typematch(then_value, else_value) : "'Then' and 'else' vectors must be of the same type."
@require then_value.len == mask.len : "Mask and selected vectors must be of the same width."
@return "a vector of the same type as then/else"
@@ -1118,27 +1135,27 @@ macro overflow_mul_helper(x, y) @local
<*
@param [&out] out : "Where the result of the addition is stored"
@return "Whether the addition resulted in an integer overflow"
@require values::@is_same_type(a, b) : "a and b must be the same type"
@require @typematch(a, b) : "a and b must be the same type"
@require values::@is_flat_intlike(a) &&& values::@is_flat_intlike(b) : "a and b must both be integer or integer vector based"
@require $defined(*out) &&& values::@is_same_type(*out, a) : "out must be a pointer of the same type as a and b"
@require $defined(*out) &&& @typematch(*out, a) : "out must be a pointer of the same type as a and b"
*>
macro bool overflow_add(a, b, out) => $$overflow_add(a, b, out);
<*
@param [&out] out : "Where the result of the subtraction is stored"
@return "Whether the subtraction resulted in an integer overflow"
@require values::@is_same_type(a, b) : "a and b must be the same type"
@require @typematch(a, b) : "a and b must be the same type"
@require values::@is_flat_intlike(a) &&& values::@is_flat_intlike(b) : "a and b must both be integer or integer vector based"
@require $defined(*out) &&& values::@is_same_type(*out, a) : "out must be a pointer of the same type as a and b"
@require $defined(*out) &&& @typematch(*out, a) : "out must be a pointer of the same type as a and b"
*>
macro bool overflow_sub(a, b, out) => $$overflow_sub(a, b, out);
<*
@param [&out] out : "Where the result of the multiplication is stored"
@return "Whether the multiplication resulted in an integer overflow"
@require values::@is_same_type(a, b) : "a and b must be the same type"
@require @typematch(a, b) : "a and b must be the same type"
@require values::@is_flat_intlike(a) &&& values::@is_flat_intlike(b) : "a and b must both be integer or integer vector based"
@require $defined(*out) &&& values::@is_same_type(*out, a) : "out must be a pointer of the same type as a and b"
@require $defined(*out) &&& @typematch(*out, a) : "out must be a pointer of the same type as a and b"
*>
macro bool overflow_mul(a, b, out) => $$overflow_mul(a, b, out);

View File

@@ -17,7 +17,7 @@ const float EXPF_P2 = -2.7777778450e-03f;
const float EXPF_P3 = 6.6137559770e-05f;
const float EXPF_P4 = -1.6533901999e-06f;
fn double exp(double x) @extern("exp")
fn double exp(double x) @extern("exp") @nostrip @weak
{
if (x != x) return x;
if (x == double.inf) return double.inf;
@@ -38,7 +38,7 @@ fn double exp(double x) @extern("exp")
return ldexp(exp_r, (int)k);
}
fn float expf(float x) @extern("expf")
fn float expf(float x) @extern("expf") @nostrip @weak
{
if (x != x) return x;
if (x == float.inf) return float.inf;

View File

@@ -19,7 +19,7 @@ const float LOGF_L4 = 2.4279078841e-01f;
const double SQRT2 = 1.41421356237309504880;
const float SQRT2F = 1.41421356237309504880f;
fn double log(double x) @extern("log")
fn double log(double x) @extern("log") @nostrip @weak
{
if (x != x) return x;
if (x < 0.0) return double.nan;
@@ -50,7 +50,7 @@ fn double log(double x) @extern("log")
return k * LOG_LN2_HI - ((hfsq - (s * (hfsq + r) + k * LOG_LN2_LO)) - f);
}
fn float logf(float x) @extern("logf")
fn float logf(float x) @extern("logf") @nostrip @weak
{
if (x != x) return x;
if (x < 0.0f) return float.nan;

View File

@@ -55,7 +55,8 @@ struct Exp2Data @private
double shift;
double negln2hiN;
double negln2loN;
double[4] poly; // Last four coefficients.
<* Last four coefficients. *>
double[4] poly;
double exp2_shift;
double[EXP2_POLY_ORDER] exp2_poly;
ulong[2 * EXP_DATA_WIDTH] tab;
@@ -253,4 +254,4 @@ macro force_eval_add(x, v)
{
$typeof(x) temp @noinit;
@volatile_store(temp, x + v);
}
}

View File

@@ -31,7 +31,8 @@ fn float _roundf(float x) @extern("roundf") @weak @nostrip
uint u = bitcast(x, uint);
int e = (u >> 23) & 0xff;
if (e >= 0x7f + 23) return x;
if (u >> 31) x = -x;
bool signed = u >> 31 != 0;
if (signed) x = -x;
if (e < 0x7f - 1)
{
force_eval_add(x, TOINTF);
@@ -47,7 +48,7 @@ fn float _roundf(float x) @extern("roundf") @weak @nostrip
default:
y = y + x;
}
if (u >> 31) y = -y;
if (signed) y = -y;
return y;
}

View File

@@ -2,8 +2,8 @@ module std::math;
// Predefined quaternion aliases.
alias Quaternionf = Quaternion {float};
alias Quaternion = Quaternion {double};
alias Quaternionf = QuaternionNumber {float};
alias Quaternion = QuaternionNumber {double};
alias QUATERNION_IDENTITY @builtin = quaternion::IDENTITY {double};
alias QUATERNIONF_IDENTITY @builtin = quaternion::IDENTITY {float};
@@ -15,7 +15,7 @@ alias QUATERNIONF_IDENTITY @builtin = quaternion::IDENTITY {float};
module std::math::quaternion {Real};
import std::math::vector;
union Quaternion
union QuaternionNumber
{
struct
{
@@ -24,22 +24,22 @@ union Quaternion
Real[<4>] v;
}
const Quaternion IDENTITY = { 0, 0, 0, 1 };
const QuaternionNumber IDENTITY = { 0, 0, 0, 1 };
macro Quaternion Quaternion.add(self, Quaternion b) @operator(+) => { .v = self.v + b.v };
macro Quaternion Quaternion.add_each(self, Real b) => { .v = self.v + b };
macro Quaternion Quaternion.sub(self, Quaternion b) @operator(-) => { .v = self.v - b.v };
macro Quaternion Quaternion.negate(self) @operator(-) => { .v = -self.v };
macro Quaternion Quaternion.sub_each(self, Real b) => { .v = self.v - b };
macro Quaternion Quaternion.scale(self, Real s) @operator_s(*) => { .v = self.v * s };
macro Quaternion Quaternion.normalize(self) => { .v = self.v.normalize() };
macro Real Quaternion.length(self) => self.v.length();
macro Quaternion Quaternion.lerp(self, Quaternion q2, Real amount) => { .v = self.v.lerp(q2.v, amount) };
macro Matrix4f Quaternion.to_matrixf(&self) => into_matrix(self, Matrix4f);
macro Matrix4 Quaternion.to_matrix(&self) => into_matrix(self, Matrix4);
fn Quaternion Quaternion.nlerp(self, Quaternion q2, Real amount) => { .v = self.v.lerp(q2.v, amount).normalize() };
macro QuaternionNumber QuaternionNumber.add(self, QuaternionNumber b) @operator(+) => { .v = self.v + b.v };
macro QuaternionNumber QuaternionNumber.add_each(self, Real b) => { .v = self.v + b };
macro QuaternionNumber QuaternionNumber.sub(self, QuaternionNumber b) @operator(-) => { .v = self.v - b.v };
macro QuaternionNumber QuaternionNumber.negate(self) @operator(-) => { .v = -self.v };
macro QuaternionNumber QuaternionNumber.sub_each(self, Real b) => { .v = self.v - b };
macro QuaternionNumber QuaternionNumber.scale(self, Real s) @operator_s(*) => { .v = self.v * s };
macro QuaternionNumber QuaternionNumber.normalize(self) => { .v = self.v.normalize() };
macro Real QuaternionNumber.length(self) => self.v.length();
macro QuaternionNumber QuaternionNumber.lerp(self, QuaternionNumber q2, Real amount) => { .v = self.v.lerp(q2.v, amount) };
macro Matrix4f QuaternionNumber.to_matrixf(&self) => into_matrix(self, Matrix4f);
macro Matrix4 QuaternionNumber.to_matrix(&self) => into_matrix(self, Matrix4);
fn QuaternionNumber QuaternionNumber.nlerp(self, QuaternionNumber q2, Real amount) => { .v = self.v.lerp(q2.v, amount).normalize() };
fn Quaternion Quaternion.invert(self)
fn QuaternionNumber QuaternionNumber.invert(self)
{
Real length_sq = self.v.dot(self.v);
if (length_sq <= 0) return self;
@@ -47,9 +47,9 @@ fn Quaternion Quaternion.invert(self)
return { self.v[0] * -inv_length, self.v[1] * -inv_length, self.v[2] * -inv_length, self.v[3] * inv_length };
}
fn Quaternion Quaternion.slerp(self, Quaternion q2, Real amount)
fn QuaternionNumber QuaternionNumber.slerp(self, QuaternionNumber q2, Real amount)
{
Quaternion result = {};
QuaternionNumber result = {};
Real[<4>] q2v = q2.v;
Real cos_half_theta = self.v.dot(q2v);
@@ -76,7 +76,7 @@ fn Quaternion Quaternion.slerp(self, Quaternion q2, Real amount)
return { .v = q1v * ratio_a + q2v * ratio_b };
}
fn Quaternion Quaternion.mul(self, Quaternion b) @operator(*)
fn QuaternionNumber QuaternionNumber.mul(self, QuaternionNumber b) @operator(*)
{
return { self.i * b.l + self.l * b.i + self.j * b.k - self.k * b.j,
self.j * b.l + self.l * b.j + self.k * b.i - self.i * b.k,
@@ -84,9 +84,9 @@ fn Quaternion Quaternion.mul(self, Quaternion b) @operator(*)
self.l * b.l - self.i * b.i - self.j * self.j - self.k * self.k };
}
macro into_matrix(Quaternion* q, $Type) @private
macro into_matrix(QuaternionNumber* q, $Type) @private
{
Quaternion rotation = q.normalize();
QuaternionNumber rotation = q.normalize();
var x = rotation.i;
var y = rotation.j;
var z = rotation.k;

View File

@@ -133,14 +133,14 @@ macro double next_double(random)
}
// True if the value is a Random.
macro bool is_random(random) => @assignable_to(random, Random);
macro bool is_random(random) => $defined(Random r = random);
macro uint128 @long_to_int128(#function) => (uint128)#function << 64 + #function;
macro ulong @int_to_long(#function) => (ulong)#function << 32 + #function;
macro uint @short_to_int(#function) => (uint)#function << 16 + #function;
macro ushort @char_to_short(#function) => (ushort)#function << 8 + #function;
macro @random_value_to_bytes(#function, char[] bytes)
macro void @random_value_to_bytes(#function, char[] bytes)
{
var $byte_size = $sizeof(#function());
usz len = bytes.len;
@@ -174,7 +174,7 @@ interface Random
}
macro init_default_random() @private
macro void init_default_random() @private
{
if (!default_random_initialized)
{

View File

@@ -1,5 +1,5 @@
module std::math::random;
import std::hash::fnv32a, std::time;
import std::hash::a5hash, std::time;
const ODD_PHI64 @local = 0x9e3779b97f4a7c15;
const MUL_MCG64 @local = 0xf1357aea2e62a9c5;
@@ -69,12 +69,11 @@ fn void seeder(char[] input, char[] out_buffer)
macro uint hash(value) @local
{
return fnv32a::hash(&&bitcast(value, char[$sizeof(value)]));
return (uint)a5hash::hash(&&bitcast(value, char[$sizeof(value)]));
}
fn char[8 * 4] entropy() @if(!env::WASM_NOLIBC)
fn char[8 * 4] entropy() @if(!env::FREESTANDING_WASM)
{
void* addr = malloc(1);
free(addr);
static uint random_int;
@@ -92,7 +91,7 @@ fn char[8 * 4] entropy() @if(!env::WASM_NOLIBC)
return bitcast(entropy_data, char[8 * 4]);
}
fn char[8 * 4] entropy() @if(env::WASM_NOLIBC)
fn char[8 * 4] entropy() @if(env::FREESTANDING_WASM)
{
static uint random_int;
random_int += 0xedf19156;

View File

@@ -1,15 +1,78 @@
module std::math::math_rt;
fn float __roundevenf(float f) @extern("roundevenf") @weak @nostrip
const double TOINT = 1 / math::DOUBLE_EPSILON;
const float TOINTF = (float)(1 / math::FLOAT_EPSILON);
macro force_eval_add(x, v)
{
// Slow implementation
return math::round(f / 2) * 2;
$typeof(x) temp @noinit;
@volatile_store(temp, x + v);
}
fn double __roundeven(double d) @extern("roundeven") @weak @nostrip
fn double __roundeven(double x) @extern("roundeven") @weak @nostrip
{
// Slow implementation
return math::round(d / 2) * 2;
ulong u = bitcast(x, ulong);
int e = (int)((u >> 52) & 0x7ff);
if (e >= 0x3ff + 52) return x;
bool signed = u >> 63 != 0;
if (signed) x = -x;
if (e < 0x3ff - 1)
{
/* raise inexact if x!=0 */
force_eval_add(x, TOINT);
return 0 * x;
}
double y = (x + TOINT) - TOINT - x;
switch
{
case y > 0.5:
y = y + x - 1;
case y < -0.5:
y = y + x + 1;
case y == 0.5 || y == -0.5:
if (u & 1)
{
y = x + (y > 0 ? y + 1 : y - 1);
break;
}
nextcase;
default:
y = y + x;
}
if (signed) y = -y;
return y;
}
fn float __roundevenf(float x) @extern("roundevenf") @weak @nostrip
{
uint u = bitcast(x, uint);
int e = (u >> 23) & 0xff;
if (e >= 0x7f + 23) return x;
bool signed = u >> 31 != 0;
if (signed) x = -x;
if (e < 0x7f - 1)
{
force_eval_add(x, TOINTF);
return 0 * x;
}
float y = (x + TOINTF) - TOINTF - x;
switch
{
case y > 0.5f:
y = y + x - 1;
case y < -0.5f:
y = y + x + 1;
case y == 0.5f || y == -0.5f:
if (u & 1)
{
y = x + (y > 0.0f ? y + 1.0f : y - 1.0f);
break;
}
nextcase;
default:
y = y + x;
}
if (signed) y = -y;
return y;
}
fn double __powidf2(double a, int b) @extern("__powidf2") @weak @nostrip

View File

@@ -3,25 +3,25 @@
module std::math::vector;
import std::math;
macro double[<*>].sq_magnitude(self) => self.dot(self);
macro float[<*>].sq_magnitude(self) => self.dot(self);
macro double double[<*>].sq_magnitude(self) => self.dot(self);
macro float float[<*>].sq_magnitude(self) => self.dot(self);
macro double[<*>].distance_sq(self, double[<*>] v2) => (self - v2).sq_magnitude();
macro float[<*>].distance_sq(self, float[<*>] v2) => (self - v2).sq_magnitude();
macro double double[<*>].distance_sq(self, double[<*>] v2) => (self - v2).sq_magnitude();
macro float float[<*>].distance_sq(self, float[<*>] v2) => (self - v2).sq_magnitude();
macro float[<2>].transform(self, Matrix4f mat) => transform2(self, mat);
macro float[<2>].rotate(self, float angle) => rotate(self, angle);
macro float[<2>].angle(self, float[<2>] v2) => math::atan2(v2.y, v2.x) - math::atan2(self.y, self.x);
macro float[<2>] float[<2>].transform(self, Matrix4f mat) => transform2(self, mat);
macro float[<2>] float[<2>].rotate(self, float angle) => rotate(self, angle);
macro float[<2>] float[<2>].angle(self, float[<2>] v2) => math::atan2(v2.y, v2.x) - math::atan2(self.y, self.x);
macro double[<2>].transform(self, Matrix4 mat) => transform2(self, mat);
macro double[<2>].rotate(self, double angle) => rotate(self, angle);
macro double[<2>].angle(self, double[<2>] v2) => math::atan2(v2.y, v2.x) - math::atan2(self.y, self.x);
macro double[<2>] double[<2>].transform(self, Matrix4 mat) => transform2(self, mat);
macro double[<2>] double[<2>].rotate(self, double angle) => rotate(self, angle);
macro double[<2>] double[<2>].angle(self, double[<2>] v2) => math::atan2(v2.y, v2.x) - math::atan2(self.y, self.x);
macro float[<*>].clamp_mag(self, float min, float max) => clamp_magnitude(self, min, max);
macro double[<*>].clamp_mag(self, double min, double max) => clamp_magnitude(self, min, max);
macro float[<*>] float[<*>].clamp_mag(self, float min, float max) => clamp_magnitude(self, min, max);
macro double[<*>] double[<*>].clamp_mag(self, double min, double max) => clamp_magnitude(self, min, max);
macro float[<*>].towards(self, float[<*>] target, float max_distance) => towards(self, target, max_distance);
macro double[<*>].towards(self, double[<*>] target, double max_distance) => towards(self, target, max_distance);
macro float[<*>] float[<*>].towards(self, float[<*>] target, float max_distance) => towards(self, target, max_distance);
macro double[<*>] double[<*>].towards(self, double[<*>] target, double max_distance) => towards(self, target, max_distance);
fn float[<3>] float[<3>].cross(self, float[<3>] v2) => cross3(self, v2);
fn double[<3>] double[<3>].cross(self, double[<3>] v2) => cross3(self, v2);

View File

@@ -1,5 +1,5 @@
module std::net::os;
const bool SUPPORTS_INET = env::LIBC && (env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID);
const bool SUPPORTS_INET = env::LIBC && (env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID || env::OPENBSD);
typedef AIFamily = CInt;
typedef AIProtocol = CInt;
@@ -23,7 +23,7 @@ struct AddrInfo
ZString ai_canonname;
SockAddrPtr ai_addr;
}
struct @if(env::LINUX)
struct @if(env::LINUX || env::OPENBSD)
{
SockAddrPtr ai_addr;
ZString ai_canonname;
@@ -53,12 +53,41 @@ const AIFamily AF_APPLETALK = PLATFORM_AF_APPLETALK;
const O_NONBLOCK = PLATFORM_O_NONBLOCK;
extern fn CInt getaddrinfo(ZString nodename, ZString servname, AddrInfo* hints, AddrInfo** res) @if(SUPPORTS_INET);
extern fn void freeaddrinfo(AddrInfo* res) @if(SUPPORTS_INET);
extern fn CInt setsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t optlen) @if(SUPPORTS_INET);
extern fn CInt getsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t optlen) @if(SUPPORTS_INET);
module std::net::os @if(!env::LIBC || !(env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID));
<*
The getaddrinfo() function is used to get a list of IP addresses and port numbers for host hostname and service servname.
@param [in] nodename
@param [in] servname
@param [in] hints
@param [out] res
@require (void*)nodename || (void*)servname : "One the names must be non-null"
*>
extern fn CInt getaddrinfo(ZString nodename, ZString servname, AddrInfo* hints, AddrInfo** res) @if(SUPPORTS_INET);
<*
freeaddrinfo() frees an AddrInfo created by getaddrinfo.
@param [&in] res
*>
extern fn void freeaddrinfo(AddrInfo* res) @if(SUPPORTS_INET);
<*
Set options on a socket.
@param [out] optval
*>
extern fn CInt setsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t optlen) @if(SUPPORTS_INET);
<*
Get options on a socket
@param [in] optval
@param [inout] optlen
*>
extern fn CInt getsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t* optlen) @if(SUPPORTS_INET);
module std::net::os @if(!env::LIBC || !(env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID || env::OPENBSD));
const AIFamily PLATFORM_AF_INET6 = 0;
const AIFamily PLATFORM_AF_IPX = 0;

View File

@@ -67,7 +67,7 @@ const int SO_WANTMORE = 0x4000; // Apple: Give hint when more data re
const int SO_WANTOOBFLAG = 0x8000; // Apple: Want OOB in MSG_FLAG on receive
const int SO_SNDBUF = 0x1001; // Send buffer size
const int SO_RCVBUF = 0x1002; // Recieve buffer size
const int SO_RCVBUF = 0x1002; // Receive buffer size
const int SO_SNDLOWAT = 0x1003; // Send low-water mark
const int SO_RCVLOWAT = 0x1004; // Receive low-water mark
const int SO_SNDTIMEO = 0x1005; // Send timeout
@@ -94,4 +94,4 @@ const CShort POLLATTRIB = 0x0400; // file attributes may have changed
const CShort POLLNLINK = 0x0800; // (un)link/rename may have happened
const CShort POLLWRITE = 0x1000; // file's contents may have changed
const CInt MSG_PEEK = 0x0002;
const CInt MSG_PEEK = 0x0002;

109
lib/std/net/os/openbsd.c3 Normal file
View File

@@ -0,0 +1,109 @@
module std::net::os @if(env::OPENBSD);
import libc;
// for most of this, see OpenBSD /usr/include/sys/socket.h
const AIFlags AI_EXT = 0x8;
const AIFlags AI_NUMERICSERV = 0x10;
const AIFlags AI_FQDN = 0x20;
const AIFlags AI_ADDRCONFIG = 0x40;
const AIFamily PLATFORM_AF_LOCAL = AF_UNIX; // draft POSIX compatibility
const AIFamily PLATFORM_AF_IMPLINK = 3; // arpanet imp addresses
const AIFamily PLATFORM_AF_PUP = 4; // pup protocols: e.g. BSP
const AIFamily PLATFORM_AF_CHAOS = 5; // mit CHAOS protocols
const AIFamily PLATFORM_AF_NS = 6; // XEROX NS protocols
const AIFamily PLATFORM_AF_ISO = 7; // ISO protocols
const AIFamily PLATFORM_AF_OSI = PLATFORM_AF_ISO;
const AIFamily PLATFORM_AF_ECMA = 8; // european computer manufacturers
const AIFamily PLATFORM_AF_DATAKIT = 9; // datakit protocols
const AIFamily PLATFORM_AF_CCITT = 10; // CCITT protocols, X.25 etc
const AIFamily PLATFORM_AF_SNA = 11; // IBM SNA
const AIFamily PLATFORM_AF_DECNET = 12; // DECnet
const AIFamily PLATFORM_AF_DLI = 13; // DEC Direct data link interface
const AIFamily PLATFORM_AF_LAT = 14; // LAT
const AIFamily PLATFORM_AF_HYLINK = 15; // NSC Hyperchannel
const AIFamily PLATFORM_AF_APPLETALK = 16; // Apple Talk
const AIFamily PLATFORM_AF_ROUTE = 17; // Internal Routing Protocol
const AIFamily PLATFORM_AF_LINK = 18; // Link layer interface
const AIFamily PLATFORM_PSEUDO_AF_XTP = 19; // eXpress Transfer Protocol (no AF)
const AIFamily PLATFORM_AF_COIP = 20; // connection-oriented IP, aka ST II
const AIFamily PLATFORM_AF_CNT = 21; // Computer Network Technology
const AIFamily PLATFORM_PSEUDO_AF_RTIP = 22; // Help Identify RTIP packets
const AIFamily PLATFORM_AF_IPX = 23; // Novell Internet Protocol
const AIFamily PLATFORM_AF_INET6 = 24; // IPv6
const AIFamily PLATFORM_PSEUDO_AF_PIP = 25; // Help Identify PIP packets
const AIFamily PLATFORM_AF_ISDN = 26; // Integrated Services Digital Network*/
const AIFamily PLATFORM_AF_E164 = PLATFORM_AF_ISDN; // CCITT E.164 recommendation
const AIFamily PLATFORM_AF_NATM = 27; // native ATM access
const AIFamily PLATFORM_AF_ENCAP = 28;
const AIFamily PLATFORM_AF_SIP = 29; // Simple Internet Protocol
const AIFamily PLATFORM_AF_KEY = 30;
const AIFamily PLATFORM_PSEUDO_AF_HDRCMPLT = 31; // Used by BPF to not rewrite headers in interface output routine
const AIFamily PLATFORM_AF_BLUETOOTH = 32; // Bluetooth
const AIFamily PLATFORM_AF_MPLS = 33; // MPLS
const AIFamily PLATFORM_PSEUDO_AF_PFLOW = 34; // pflow
const AIFamily PLATFORM_PSEUDO_AF_PIPEX = 35; // PIPEX
const AIFamily PLATFORM_AF_FRAME = 36; // frame (Ethernet) sockets
const AIFamily PLATFORM_AF_MAX = 37;
// see: https://github.com/openbsd/src/blob/1ca4dc792538d17b4c2bd71550f68070505fd7b9/sys/sys/socket.h#L157
const int SOL_SOCKET = 0xFFFF;
const int SO_DEBUG = 0x0001; // turn on debugging info recording
const int SO_ACCEPTCONN = 0x0002; // socket has had listen()
const int SO_REUSEADDR = 0x0004; // allow local address reuse
const int SO_KEEPALIVE = 0x0008; // keep connections alive
const int SO_DONTROUTE = 0x0010; // just use interface addresses
const int SO_BROADCAST = 0x0020; // permit sending of broadcast msgs
const int SO_USELOOPBACK = 0x0040; // bypass hardware when possible
const int SO_LINGER = 0x0080; // linger on close if data present
const int SO_OOBINLINE = 0x0100; // leave received OOB data in line
const int SO_REUSEPORT = 0x0200; // allow local address & port reuse
const int SO_TIMESTAMP = 0x0800; // timestamp received dgram traffic
const int SO_BINDANY = 0x1000; // allow bind to any address
const int SO_ZEROIZE = 0x2000; // zero out all mbufs sent over socket
// additional
const int SO_SNDBUF = 0x1001; // send buffer size
const int SO_RCVBUF = 0x1002; // receive buffer size
const int SO_SNDLOWAT = 0x1003; // send low-water mark
const int SO_RCVLOWAT = 0x1004; // receive low-water mark
const int SO_SNDTIMEO = 0x1005; // send timeout
const int SO_RCVTIMEO = 0x1006; // receive timeout
const int SO_ERROR = 0x1007; // get error status and clear
const int SO_TYPE = 0x1008; // get socket type
const int SO_NETPROC = 0x1020; // multiplex; network processing
const int SO_RTABLE = 0x1021; // routing table to be used
const int SO_PEERCRED = 0x1022; // get connect-time credentials
const int SO_SPLICE = 0x1023; // splice data to other socket
const int SO_DOMAIN = 0x1024; // get socket domain
const int SO_PROTOCOL = 0x1025; // get socket protocol
// POLLIN through POLLNVAL are predefined by lib/std/net/os/posix.c3
const CUShort POLLRDNORM = 0x0040;
const CUShort POLLNORM = POLLRDNORM;
const CUShort POLLWRNORM = POLLOUT;
const CUShort POLLRDBAND = 0x0080;
const CUShort POLLWRBAND = 0x0100;
const CInt MSG_OOB = 0x1; // process out-of-band data
const CInt MSG_PEEK = 0x2; // peek at incoming message
const CInt MSG_DONTROUTE = 0x4; // send without using routing tables
const CInt MSG_EOR = 0x8; // data completes record
const CInt MSG_TRUNC = 0x10; // data discarded before delivery
const CInt MSG_CTRUNC = 0x20; // control data lost before delivery
const CInt MSG_WAITALL = 0x40; // wait for full request or error
const CInt MSG_DONTWAIT = 0x80; // this message should be nonblocking
const CInt MSG_BCAST = 0x100; // this message rec'd as broadcast
const CInt MSG_MCAST = 0x200; // this message rec'd as multicast
const CInt MSG_NOSIGNAL = 0x400; // do not send SIGPIPE
const CInt MSG_CMSG_CLOEXEC = 0x800; // set FD_CLOEXEC on received fds
const CInt MSG_WAITFORONE = 0x1000; // nonblocking but wait for one msg
// socket creation options
const SOCK_CLOEXEC = 0x8000; // set FD_CLOEXEC
const SOCK_NONBLOCK = 0x4000; // set O_NONBLOCK
const SOCK_NONBLOCK_INHERIT = 0x2000; // inherit O_NONBLOCK from listener
const SOCK_DNS = 0x1000; // set SS_DNS
const PLATFORM_O_NONBLOCK = SOCK_NONBLOCK;

View File

@@ -25,31 +25,54 @@ macro void @loop_over_ai(AddrInfo* ai; @body(NativeSocket fd, AddrInfo* ai))
const Duration POLL_FOREVER = (Duration)-1;
typedef PollSubscribes = ushort;
typedef PollEvents = ushort;
enum PollSubscribe : const ushort
{
ANY_READ = os::POLLIN,
PRIO_READ = os::POLLPRI,
OOB_READ = os::POLLRDBAND,
READ = os::POLLRDNORM,
ANY_WRITE = os::POLLOUT,
OOB_WRITE = os::POLLWRBAND,
WRITE = os::POLLWRNORM,
}
const PollSubscribes SUBSCRIBE_ANY_READ = os::POLLIN;
const PollSubscribes SUBSCRIBE_PRIO_READ = os::POLLPRI;
const PollSubscribes SUBSCRIBE_OOB_READ = os::POLLRDBAND;
const PollSubscribes SUBSCRIBE_READ = os::POLLRDNORM;
const PollSubscribes SUBSCRIBE_ANY_WRITE = os::POLLOUT;
const PollSubscribes SUBSCRIBE_OOB_WRITE = os::POLLWRBAND;
const PollSubscribes SUBSCRIBE_WRITE = os::POLLWRNORM;
const PollSubscribe SUBSCRIBE_ANY_READ = (PollSubscribe)os::POLLIN;
const PollSubscribe SUBSCRIBE_PRIO_READ = (PollSubscribe)os::POLLPRI;
const PollSubscribe SUBSCRIBE_OOB_READ = (PollSubscribe)os::POLLRDBAND;
const PollSubscribe SUBSCRIBE_READ = (PollSubscribe)os::POLLRDNORM;
const PollSubscribe SUBSCRIBE_ANY_WRITE = (PollSubscribe)os::POLLOUT;
const PollSubscribe SUBSCRIBE_OOB_WRITE = (PollSubscribe)os::POLLWRBAND;
const PollSubscribe SUBSCRIBE_WRITE = (PollSubscribe)os::POLLWRNORM;
const PollEvents POLL_EVENT_READ_PRIO = os::POLLPRI;
const PollEvents POLL_EVENT_READ_OOB = os::POLLRDBAND;
const PollEvents POLL_EVENT_READ = os::POLLRDNORM;
const PollEvents POLL_EVENT_WRITE_OOB = os::POLLWRBAND;
const PollEvents POLL_EVENT_WRITE = os::POLLWRNORM;
const PollEvents POLL_EVENT_DISCONNECT = os::POLLHUP;
const PollEvents POLL_EVENT_ERROR = os::POLLERR;
const PollEvents POLL_EVENT_INVALID = os::POLLNVAL;
enum PollEvent : const ushort
{
READ_PRIO = os::POLLPRI,
READ_OOB = os::POLLRDBAND,
READ = os::POLLRDNORM,
WRITE_OOB = os::POLLWRBAND,
WRITE = os::POLLWRNORM,
DISCONNECT = os::POLLHUP,
ERROR = os::POLLERR,
INVALID = os::POLLNVAL,
}
const PollEvent POLL_EVENT_READ_PRIO = (PollEvent)os::POLLPRI;
const PollEvent POLL_EVENT_READ_OOB = (PollEvent)os::POLLRDBAND;
const PollEvent POLL_EVENT_READ = (PollEvent)os::POLLRDNORM;
const PollEvent POLL_EVENT_WRITE_OOB = (PollEvent)os::POLLWRBAND;
const PollEvent POLL_EVENT_WRITE = (PollEvent)os::POLLWRNORM;
const PollEvent POLL_EVENT_DISCONNECT = (PollEvent)os::POLLHUP;
const PollEvent POLL_EVENT_ERROR = (PollEvent)os::POLLERR;
const PollEvent POLL_EVENT_INVALID = (PollEvent)os::POLLNVAL;
alias PollSubscribes @deprecated("Use PollSubscribe") = PollSubscribe;
alias PollEvents @deprecated("Use PollEvent") = PollEvent;
struct Poll
{
NativeSocket socket;
PollSubscribes events;
PollEvents revents;
PollSubscribe events;
PollEvent revents;
}
<*
@@ -116,7 +139,8 @@ fn void? Socket.set_option(&self, SocketOption option, bool value)
fn bool? Socket.get_option(&self, SocketOption option)
{
CInt flag;
int errcode = os::setsockopt(self.sock, os::SOL_SOCKET, option.value, &flag, CInt.sizeof);
Socklen_t socklen = CInt.sizeof;
int errcode = os::getsockopt(self.sock, os::SOL_SOCKET, option.value, &flag, &socklen);
if (errcode != 0) return SOCKOPT_FAILED?;
return (bool)flag;
}
@@ -169,9 +193,9 @@ fn usz? Socket.peek(&self, char[] bytes) @dynamic
enum SocketShutdownHow : (CInt native_value)
{
RECEIVE = @select(env::WIN32, libc::SD_RECEIVE, libc::SHUT_RD),
SEND = @select(env::WIN32, libc::SD_SEND, libc::SHUT_WR),
BOTH = @select(env::WIN32, libc::SD_BOTH, libc::SHUT_RDWR),
RECEIVE = env::WIN32 ??? libc::SD_RECEIVE : libc::SHUT_RD,
SEND = env::WIN32 ??? libc::SD_SEND : libc::SHUT_WR,
BOTH = env::WIN32 ??? libc::SD_BOTH : libc::SHUT_RDWR,
}
fn void? Socket.shutdown(&self, SocketShutdownHow how)

View File

@@ -1,7 +1,7 @@
module std::net @if(os::SUPPORTS_INET);
import std::time, libc, std::os;
macro apply_sockoptions(sockfd, options) @private
macro void? apply_sockoptions(sockfd, options) @private
{
Socket sock = { .sock = sockfd };
foreach (o : options) sock.set_option(o, true)!;
@@ -96,7 +96,7 @@ fn Socket? connect_async_from_addrinfo(AddrInfo* addrinfo, SocketOption[] option
return os::socket_error()?;
}
macro @network_loop_over_ai(network, host, port; @body(fd, ai)) @private
macro void @network_loop_over_ai(network, host, port; @body(fd, ai)) @private
{
AddrInfo* ai = network.addrinfo(host, port)!;
AddrInfo* first = ai;

141
lib/std/os/linux/epoll.c3 Normal file
View File

@@ -0,0 +1,141 @@
module std::os::linux @if(env::LINUX);
import libc;
// https://github.com/bminor/glibc/blob/master/sysdeps/unix/sysv/linux/sys/epoll.h
const uint EPOLLIN = EpollEvents.EPOLLIN;
const uint EPOLLPRI = EpollEvents.EPOLLPRI;
const uint EPOLLOUT = EpollEvents.EPOLLOUT;
const uint EPOLLRDNORM = EpollEvents.EPOLLRDNORM;
const uint EPOLLRDBAND = EpollEvents.EPOLLRDBAND;
const uint EPOLLWRNORM = EpollEvents.EPOLLWRNORM;
const uint EPOLLWRBAND = EpollEvents.EPOLLWRBAND;
const uint EPOLLMSG = EpollEvents.EPOLLMSG;
const uint EPOLLERR = EpollEvents.EPOLLERR;
const uint EPOLLHUP = EpollEvents.EPOLLHUP;
const uint EPOLLRDHUP = EpollEvents.EPOLLRDHUP;
const uint EPOLLEXCLUSIVE = EpollEvents.EPOLLEXCLUSIVE;
const uint EPOLLWAKEUP = EpollEvents.EPOLLWAKEUP;
const uint EPOLLONESHOT = EpollEvents.EPOLLONESHOT;
const uint EPOLLET = EpollEvents.EPOLLET;
enum EpollEvents: const inline uint
{
EPOLLIN = 0x001,
EPOLLPRI = 0x002,
EPOLLOUT = 0x004,
EPOLLRDNORM = 0x040,
EPOLLRDBAND = 0x080,
EPOLLWRNORM = 0x100,
EPOLLWRBAND = 0x200,
EPOLLMSG = 0x400,
EPOLLERR = 0x008,
EPOLLHUP = 0x010,
EPOLLRDHUP = 0x2000,
EPOLLEXCLUSIVE = 1u << 28,
EPOLLWAKEUP = 1u << 29,
EPOLLONESHOT = 1u << 30,
EPOLLET = 1u << 31
}
/* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */
const uint EPOLL_CTL_ADD = 1; /* Add a file descriptor to the interface. */
const uint EPOLL_CTL_DEL = 2; /* Remove a file descriptor from the interface. */
const uint EPOLL_CTL_MOD = 3; /* Change file descriptor epoll_event structure. */
union EpollData
{
void* ptr;
int fd;
uint u32;
ulong u64;
}
struct EpollEvent @packed
{
<* Epoll events *>
uint events;
<* User data variable *>
EpollData data;
}
struct EpollParams
{
uint busy_poll_usecs;
ushort busy_poll_budget;
char prefer_busy_poll;
<* pad the struct to a multiple of 64bits *>
char __pad;
}
// https://github.com/MatthiasWM/mosrun/blob/master/scratchpad.txt#L330-L348
/*
* Ioctl's have the command encoded in the lower word,
* and the size of any in or out parameters in the upper
* word. The high 2 bits of the upper word are used
* to encode the in/out status of the parameter; for now
* we restrict parameters to at most 256 bytes (disklabels are 216 bytes).
*/
const IOCPARM_MASK = 0xff; /* parameters must be < 256 bytes */
const IOC_VOID = 0x20000000; /* no parameters */
const IOC_OUT = 0x40000000; /* copy out parameters */
const IOC_IN = 0x80000000; /* copy in parameters */
const IOC_INOUT = (IOC_IN|IOC_OUT);
macro ulong @ioctl_IO ($x,$y) @const => (IOC_VOID | (($x)<<8)|y);
macro ulong @ioctl_IOR ($x,$y,$Type) @const => (IOC_OUT |(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y);
macro ulong @ioctl_IOW ($x,$y,$Type) @const => (IOC_IN |(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y);
/* this should be _IORW, but stdio got there first */
macro ulong @ioctl_IOWR ($x,$y,$Type) @const => (IOC_INOUT|(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y);
macro ulong @ioctl_ION ($x,$y,$n) @const => (IOC_INOUT|((($n)&IOCPARM_MASK)<<16)|(($x)<<8)|y);
// https://github.com/bminor/glibc/blob/master/sysdeps/unix/sysv/linux/sys/epoll.h
const EPOLL_IOC_TYPE = 0x8A;
const EPIOCSPARAMS = @ioctl_IOW(EPOLL_IOC_TYPE, 0x01, EpollParams);
const EPIOCGPARAMS = @ioctl_IOR(EPOLL_IOC_TYPE, 0x02, EpollParams);
<*
* Creates an epoll instance. Returns an fd for the new instance.
* The "size" parameter is a hint specifying the number of file
* descriptors to be associated with the new instance.
* The fd returned by epoll_create() should be closed with close().
*>
extern fn int epoll_create(int);
<*
* Same as epoll_create but with an FLAGS parameter.
* The unused SIZE parameter has been dropped.
*>
extern fn int epoll_create1(int);
<*
* Manipulate an epoll instance "epfd". Returns 0 in case of success,
* -1 in case of error ( the "errno" variable will contain the
* specific error code ) The "op" parameter is one of the EPOLL_CTL_*
* constants defined above. The "fd" parameter is the target of the
* operation. The "event" parameter describes which events the caller
* is interested in and any associated user data.
*>
extern fn int epoll_ctl (int, int, int, EpollEvent*);
<*
* Wait for events on an epoll instance "epfd". Returns the number of
* triggered events returned in "events" buffer. Or -1 in case of
* error with the "errno" variable set to the specific error code. The
* "events" parameter is a buffer that will contain triggered
* events. The "maxevents" is the maximum number of events to be
* returned ( usually size of "events" ). The "timeout" parameter
* specifies the maximum wait time in milliseconds (-1 == infinite).
*>
extern fn int epoll_wait (int, EpollEvent*, int, int);
<*
* Same as epoll_wait, but the thread's signal mask is temporarily
* and atomically replaced with the one provided as parameter.
*>
extern fn int epoll_pwait (int, EpollEvent*, int, int, Sigset_t*);
<*
* Same as epoll_pwait, but the timeout as a timespec.
*>
extern fn int epoll_pwait2 (int, EpollEvent*, int, TimeSpec*, Sigset_t*);

View File

@@ -1,5 +1,34 @@
module std::os::linux @if(env::LINUX);
import libc, std::os, std::io, std::collections::list;
import libc, std::os, std::io, std::collections::list, std::net::os;
// https://man7.org/linux/man-pages/man3/inet_ntop.3.html
extern fn char** inet_ntop(int, void*, char*, Socklen_t);
// https://linux.die.net/man/3/ntohs
<*
* The htonl() function converts the unsigned integer hostlong from host byte order to network byte order.
*>
extern fn uint htonl(uint hostlong);
<*
* The htons() function converts the unsigned short integer hostshort from host byte order to network byte order.
*>
extern fn ushort htons(ushort hostshort);
<*
* The ntohl() function converts the unsigned integer netlong from network byte order to host byte order.
*>
extern fn uint ntohl(uint netlong);
<*
* The ntohs() function converts the unsigned short integer netshort from network byte order to host byte order.
*>
extern fn ushort ntohs(ushort netshort);
// https://man7.org/linux/man-pages/man3/bzero.3.html
<*
* The bzero() function erases the data in the n bytes of the memory
* starting at the location pointed to by s, by writing zeros (bytes
* containing '\0') to that area.
*>
extern fn void bzero(char*, usz);
extern fn isz readlink(ZString path, char* buf, usz bufsize);
@@ -89,6 +118,35 @@ struct Linux_Dl_info
void* dli_saddr; /* Address of nearest symbol */
}
alias Dl_iterate_phdr_callback64 = fn CInt(Linux_dl_phdr_info_64*, usz, void*);
alias Dl_iterate_phdr_callback32 = fn CInt(Linux_dl_phdr_info_32*, usz, void*);
extern fn CInt dl_iterate_phdr64(Dl_iterate_phdr_callback64 callback, void* data);
extern fn CInt dl_iterate_phdr32(Dl_iterate_phdr_callback32 callback, void* data);
struct Linux_dl_phdr_info_64
{
Elf64_Addr dlpi_addr;
ZString dlpi_name;
Elf64_Phdr* dlpi_phdr;
Elf64_Half dlpi_phnum;
ulong dlpi_adds;
ulong dlpi_subs;
usz dlpi_tsl_modid;
void* dlpi_tls_data;
}
struct Linux_dl_phdr_info_32
{
Elf32_Addr dlpi_addr;
ZString dlpi_name;
Elf32_Phdr* dlpi_phdr;
Elf32_Half dlpi_phnum;
ulong dlpi_adds;
ulong dlpi_subs;
usz dlpi_tsl_modid;
void* dlpi_tls_data;
}
fn ulong? elf_module_image_base(String path) @local
{
File file = file::open(path, "rb")!;

View File

@@ -1,11 +1,18 @@
module std::os::macos::cf @if(env::DARWIN) @link(env::DARWIN, "CoreFoundation.framework");
typedef CFArrayRef = void*;
typedef CFArray = inline CFType;
alias CFArrayRef = CFArray*;
typedef CFArrayCallBacksRef = void*;
typedef CFMutableArrayRef = void*;
typedef CFMutableArray = inline CFArray;
typedef CFMutableArrayRef = CFMutableArray*;
extern fn CFIndex CFArray.getCount(&self) @extern("CFArrayGetCount");
extern fn void* CFArray.getValueAtIndex(&self, CFIndex i) @extern("CFArrayGetValueAtIndex");
extern fn CFArrayRef macos_CFArrayCreate(CFAllocatorRef allocator, void** values, CFIndex num_values, CFArrayCallBacksRef callBacks) @extern("CFArrayCreate") @builtin;
extern fn CFArrayRef macos_CFArrayCopy(CFAllocatorRef allocator, CFArrayRef array) @extern("CFArrayCopy") @builtin;
extern fn CFIndex macos_CFArrayGetCount(CFArrayRef array) @extern("CFArrayGetCount") @builtin;
extern fn void macos_CFArrayAppendArray(CFMutableArrayRef theArray, CFArrayRef otherArray, CFRange otherRange) @extern("CFArrayAppendArray") @builtin;
extern fn void CFMutableArray.appendArray(&self, CFArrayRef otherArray, CFRange otherRange) @extern("CFArrayAppendArray");
extern fn void CFMutableArray.appendValue(&self, void *value) @extern("CFArrayAppendValue");
extern fn CFMutableArrayRef macos_CFArrayCreateMutable(CFAllocatorRef allocator, CFIndex capacity, CFArrayCallBacksRef callBacks) @extern("CFArrayCreateMutable") @builtin;
extern fn void macos_CFArrayAppendValue(CFMutableArrayRef theArray, void *value) @extern("CFArrayAppendValue") @builtin;

View File

@@ -1,13 +1,42 @@
module std::os::macos::cf @if(env::DARWIN) @link(env::DARWIN, "CoreFoundation.framework");
typedef CFTypeRef = void*;
typedef CFType = void;
typedef CFTypeRef = CFType*;
alias CFIndex = isz;
typedef CFString = inline CFType;
alias CFStringRef = CFString*;
struct CFRange
{
CFIndex location;
CFIndex length;
}
extern fn CFTypeRef macos_CFRetain(CFTypeRef cf) @extern("CFRetain") @builtin;
extern fn void macos_CFRelease(CFTypeRef cf) @extern("CFRelease") @builtin;
extern fn ZString CFString.getCStringPtr(&self, CFStringEncoding encoding) @extern("CFStringGetCStringPtr");
extern fn ZString CFString.getCString(&self, char* buffer, usz len, CFStringEncoding encoding) @extern("CFStringGetCString");
extern fn CFTypeRef CFType.retain(&self) @extern("CFRetain");
extern fn void CFType.release(&self) @extern("CFRelease");
extern fn CFIndex CFType.getRetainCount(&self) @extern("CFGetRetainCount");
enum CFStringEncoding : const uint
{
INVALID_ID = 0xffffffffU,
MAC_ROMAN = 0,
WINDOWS_LATIN_1 = 0x0500,
ISO_LATIM_1 = 0x0201,
NEXT_STEP_LATIN = 0x0B01,
ASCII = 0x0600,
UNICODE = 0x0100,
UTF8 = 0x08000100,
NON_LOSSY_ASCII = 0x0BFF,
UTF16 = 0x0100,
UTF16BE = 0x10000100,
UTF16LE = 0x14000100,
UTF32 = 0x0c000100,
UTF32BE = 0x18000100,
UTF32LE = 0x1c000100
}

View File

@@ -1,2 +1,60 @@
module std::os::darwin @if(env::DARWIN);
module std::os::darwin @if(env::DARWIN) @link("Foundation.framework");
import std::os::macos::cf, std::os::macos::objc, std::io;
enum NSSearchPathDomainMask : const NSUInteger
{
USER = 1,
LOCAL = 2,
NETWORK = 4,
SYSTEM = 8,
ALL = 0x0ffff
}
enum NSSearchPathDirectory : const NSUInteger
{
APPLICATION = 1,
DEMO_APPLICATION,
DEVELOPER_APPLICATION,
ADMIN_APPLICATION,
LIBRARY,
DEVELOPER,
USER,
DOCUMENTATION,
DOCUMENT,
CORE_SERVICE,
AUTOSAVED_INFORMATION,
DESKTOP = 12,
CACHES = 13,
APPLICATION_SUPPORT = 14,
DOWNLOADS = 15,
INPUT_METHODS = 16,
MOVIES = 17,
MUSIC = 18,
PICTURES = 19,
PRINTER_DESCRIPTION = 20,
SHARED_PUBLIC = 21,
PREFERENCE_PANES = 22,
APPLICATION_SCRIPTS = 23,
ITEM_REPLACEMENT = 99,
ALL_APPLICATIONS = 100,
ALL_LIBRARIES = 101,
TRASH = 102,
}
// real signature in Foundation
extern fn CFArrayRef nsSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, bool expandTilde) @extern("NSSearchPathForDirectoriesInDomains");
fn String? find_first_directory_temp(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask)
{
objc::@autoreleasepool()
{
CFArrayRef arr = nsSearchPathForDirectoriesInDomains(directory, domainMask, true);
if (!arr.getCount()) return io::PATH_COULD_NOT_BE_FOUND?;
CFStringRef str = (CFStringRef)arr.getValueAtIndex(0);
char* buffer = tmalloc(2048);
if (!str.getCString(buffer, 2048, UTF8)) return io::PATH_COULD_NOT_BE_FOUND?;
return ((ZString)buffer).str_view();
};
}

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