Compare commits

..

163 Commits

Author SHA1 Message Date
Christoffer Lerno
6e348d1e71 Update compiler version 2025-06-02 10:12:04 +02:00
Christoffer Lerno
d697b910ba Removed the naive check for compile time modification, which fixes #1997 but regresses in detection. 2025-06-01 23:50:13 +02:00
Christoffer Lerno
4d848f1707 Incorrect ensure on String.split. 2025-06-01 20:28:32 +02:00
Christoffer Lerno
6377f0573d Typo 2025-06-01 20:06:34 +02:00
Christoffer Lerno
c3d2b2824c Bug using #foo arguments with $defined #2173 2025-05-31 17:35:29 +02:00
Christoffer Lerno
18e408ead4 Fix example. 2025-05-30 19:22:17 +02:00
Christoffer Lerno
08c63108a1 Release candidate 0.7.2 2025-05-30 19:13:19 +02:00
Christoffer Lerno
da25a411f9 Generic faults is disallowed. 2025-05-30 19:12:26 +02:00
Christian Brendlin
e685414829 Fix #1718: Add --header-output option to specify header file directory (#2161)
* Fix #1718: Add --header-output option to specify header file directory

- Add header_out field to BuildOptions struct
- Add header_file_dir field to BuildTarget struct
- Add --header-output command line option parsing with help text
- Modify header_gen() to use configured output directory instead of hardcoded root
- Add default behavior to use build directory when no custom path specified
- Add directory creation for header output paths
- Resolves issue where generated C headers were always output to root directory

* Fix directory creation timing for header output

- Move header output directory creation before header_gen() call
- Ensures custom header output directories are created before files are written
- Fixes issue where --header-output would fail if directory doesn't exist

* Fix style

* Fix Style

* Add to releasenotes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-05-30 10:19:46 +02:00
BWindey
ae5a74bc41 [Feat] add quiet to project.json settings (#2166)
* Add quiet to BuildTarget struct and set default to false

* Link BuildTarget.quiet with BuildOptions.verbosity_level (like --quiet flag)

* Parse quiet from project.json, and sort the entries alphabetically

* Add changes to release-notes

* Only set options.verbosity_level if it wasn't set on the commandline

* Formatting

* Added small fix.
2025-05-30 10:16:14 +02:00
Christoffer Lerno
76374d31c4 Spelling 2025-05-30 10:08:04 +02:00
Christoffer Lerno
ffd7a5e483 Fix incorrect percentage 2025-05-30 01:47:05 +02:00
BWindey
d143ec227c Libc ioctl extern function (#2162)
* Add barebones extern ioctl() binding in libc
* Update release notes
2025-05-29 23:00:35 +02:00
Christoffer Lerno
f2703508f2 Fixed test. 2025-05-29 21:14:50 +02:00
Christoffer Lerno
bb96dc931e Add deprecation for @param foo "abc". 2025-05-29 00:45:11 +02:00
Christoffer Lerno
a5a2b00ec8 Too strict project view #2163. 2025-05-28 22:44:00 +02:00
Christoffer Lerno
00f1206f3c Compiler didn't check foreach over flexible array member, and folding a flexible array member was allowed #2164. 2025-05-28 22:21:06 +02:00
Christoffer Lerno
349d9ef3cf Allow recursive generic modules. 2025-05-28 15:39:35 +02:00
Christoffer Lerno
9f30b56e13 Deprecate f32, f64 and f128 suffixes. 2025-05-28 13:01:49 +02:00
Christoffer Lerno
83d6b35afe Add d as floating point suffix for double types. 2025-05-28 12:02:24 +02:00
Christoffer Lerno
f4b9f375e0 Add run-dir to project.json 2025-05-27 23:22:32 +02:00
Christoffer Lerno
be3f9007c9 Check pointer/slice/etc on [out] and & params. #2156. 2025-05-27 23:03:43 +02:00
Christian Brendlin
b665e2cbe5 change releasenotes entry to reflect to correct issue (#2159)
I changed the reference #2138 to point to the issue #2012 instead of the pull request.
2025-05-27 14:15:36 +02:00
Christoffer Lerno
0ed68f94cf Update matrix pass. 2025-05-27 13:59:12 +02:00
Christoffer Lerno
966e8107f8 Add $$matrix_mul and $$matrix_transpose builtins. 2025-05-27 00:50:21 +02:00
Book-reader
61a4dcc807 add macro wrappers for $$overflow_add, $$overflow_sub, and $$overflow_mul builtins 2025-05-26 20:58:16 +02:00
Christoffer Lerno
52541a03eb @jump now included in --list-attributes #2155. 2025-05-26 16:18:08 +02:00
Christoffer Lerno
972c84b65b for with incorrect var declaration caused crash #2154. 2025-05-26 15:56:51 +02:00
Christoffer Lerno
f668b96cc9 $$sat_mul was missing. 2025-05-26 12:23:19 +02:00
Christoffer Lerno
9461873b4c Distinct types could not be used with tagof #2152 2025-05-26 00:57:20 +02:00
Christoffer Lerno
8d563eba7a Implicit casting from struct to interface failure for inheriting interfaces #2151. Fix second bug in #2148 2025-05-24 17:10:11 +02:00
Christoffer Lerno
fe98225f0a Remove superfluous cleanup parameter in os::exit and os::fastexit. 2025-05-23 22:04:54 +02:00
Christoffer Lerno
bae3e59217 Add missing @noreturn to os::exit 2025-05-23 21:41:33 +02:00
Christoffer Lerno
b5ddc36d7f Limit vector max size, default is 4096 bits, but may be increased using --max-vector-size. 2025-05-23 21:40:14 +02:00
Christoffer Lerno
c2c0ecded8 - --path does not interact correctly with relative path arguments #2149. 2025-05-23 19:17:04 +02:00
Christoffer Lerno
9d5b31dad5 Missing error on default values for body with default arguments #2148. 2025-05-23 18:57:21 +02:00
Christoffer Lerno
6c0e94cad9 Fix indent 2025-05-23 16:45:57 +02:00
Christian Brendlin
84aee6a25b Feature: Add inherit_stdio Option for SubProcess (#2138)
* add inherit_stdio option
2025-05-22 11:06:23 +02:00
Matthew Brush
71a765c66e Update CODESTYLE.md
Fix a couple typos and wording.
2025-05-22 11:03:50 +02:00
Gregory Oakes
5c3b637cf6 Add Maybe.equals when inner type is equatable. 2025-05-22 00:06:11 +02:00
Christoffer Lerno
bd1de1e7dc &&& was accidentally available as a valid prefix operator. 2025-05-21 23:36:33 +02:00
Christoffer Lerno
3cd2267b0a Update error message. 2025-05-20 23:00:31 +02:00
Christoffer Lerno
7fcc91edc8 Improve error message when encountering recursively defined structs. #2146 2025-05-19 21:36:47 +02:00
Christoffer Lerno
9052f07c19 Empty default case in @jump switch does not fallthrough #2147. 2025-05-19 21:18:23 +02:00
Christoffer Lerno
c7f0d54328 Designated const initializers with {} would overwrite the parent field. 2025-05-18 23:40:52 +02:00
Christoffer Lerno
498803e9ba Error when using named argument on trailing macro body expansion #2139. 2025-05-17 23:50:15 +02:00
Christoffer Lerno
082457c5fb Incorrect parsing of call attributes #2144. 2025-05-17 22:10:03 +02:00
Christoffer Lerno
23897bc9a4 - Incorrect parsing of ad hoc generic types, like Foo{int}**** #2140.
- $define did not correctly handle generic types #2140.
2025-05-17 21:14:10 +02:00
Christoffer Lerno
8ada2a70d9 Using a non-const as the end range for a bitstruct would trigger an assert. 2025-05-17 18:55:58 +02:00
mr6r4y
a91330b7d1 Fix typo causing segmentation fault 2025-05-17 15:41:49 +02:00
Christoffer Lerno
2f3954a7d9 Deprecate SomeFn.params 2025-05-16 21:57:18 +02:00
Christoffer Lerno
b7ae5dce8b Deprecate MyEnum.elements. 2025-05-16 16:12:37 +02:00
Christoffer Lerno
91db6ceeda Defining an enum like ABC = { 1 2 } was accidentally allowed. 2025-05-16 09:56:08 +02:00
Christoffer Lerno
fc2f718d9e Update error message. 2025-05-15 23:34:01 +02:00
Christoffer Lerno
64ef3fc756 Some folding was missing in binary op compile time resolution #2135. 2025-05-15 16:04:55 +02:00
Christoffer Lerno
93dd432b62 Improve error message when using keywords as functions/macros/variables #2133. 2025-05-15 15:27:14 +02:00
Christoffer Lerno
6c822e5aa3 Add math::@ceil() compile time ceil function. #2134 2025-05-15 12:46:46 +02:00
Christoffer Lerno
8c741c617c Variable aliases of aliases would not resolve correctly. #2131
Variable aliases could not be assigned to.
2025-05-15 09:36:16 +02:00
Christoffer Lerno
b83e57b952 Added @rnd() compile time random function (using the $$rnd() builtin). #2078 2025-05-15 00:51:33 +02:00
Christoffer Lerno
24ebe975d8 Allow the right hand side of ||| and &&& be runtime values. 2025-05-14 23:40:36 +02:00
Christoffer Lerno
511ae0da00 Contract on trying to use Object without initializing it. 2025-05-14 23:22:34 +02:00
Christoffer Lerno
36eb650228 Correctly error on @attrdef Foo = ;. 2025-05-14 12:15:48 +02:00
DragonFriend
50b4d7aa35 Add replace and treplace to String (#2127)
* Add replace and treplace functions to String
2025-05-14 11:00:20 +02:00
Christoffer Lerno
abe4727c3a Deprecate uXX and iXX bit suffixes.
Add experimental LL / ULL suffixes for int128 and uint128 literals.
2025-05-13 23:48:59 +02:00
Christoffer Lerno
c528f53d58 - attrdef with any invalid name causes compiler assert #2128. 2025-05-12 01:41:19 +02:00
Christoffer Lerno
83955ea5b5 Add --run-dir, to specify directory for running executable using compile-run and run #2121. 2025-05-12 01:24:51 +02:00
Christoffer Lerno
fc5c70a628 Update links. 2025-05-11 22:46:43 +02:00
Christoffer Lerno
5287640140 Fix link 2025-05-11 22:38:50 +02:00
Christoffer Lerno
634438eb82 Cleanup. 2025-05-08 21:05:43 +02:00
Christoffer Lerno
164c901ae6 More comments on the allocators. 2025-05-07 12:52:19 +02:00
Christoffer Lerno
54e70cae0f Add DateTime + Duration overloads. 2025-05-07 10:49:30 +02:00
Lucian Feroiu
30ec200492 Add support for default Homebrew-installed LLD (#2119) 2025-05-06 22:38:48 +02:00
Christoffer Lerno
584a8a2e60 - Fix regression in Time diff due to operator overloading #2124
- Add `Duration * Int` and `Clock - Clock` overload.
2025-05-06 22:33:39 +02:00
Christoffer Lerno
3f07d1c7b8 Fix No index OOB check for [:^n] #2123 2025-05-06 16:53:14 +02:00
Christoffer Lerno
125436d23e Better default assert messages when no message is specified #2122 2025-05-05 00:01:36 +02:00
Christoffer Lerno
900365c25e Fix stringify for compound initializers #2120. 2025-05-04 15:31:55 +02:00
Christoffer Lerno
d313afa487 Add String.count to count the number of instances of a string. 2025-05-02 21:48:04 +02:00
Christoffer Lerno
a411f20762 Assert when a macro with compile time value is discarded, e.g. foo(); where foo() returns an untyped list. #2117 2025-05-02 21:16:56 +02:00
Christoffer Lerno
8a0907cb70 Add String.tokenize_all to replace the now deprecated String.splitter 2025-05-02 20:51:15 +02:00
Christoffer Lerno
8a09b2e5f7 std::ascii moved into std::core::ascii. Old _m variants are deprecated, as is uint methods. 2025-05-02 18:06:28 +02:00
Christoffer Lerno
bfccc303d1 Added comments. 2025-05-02 13:36:46 +02:00
Christoffer Lerno
0d3299f267 Added String.quick_ztr and String.is_zstr 2025-05-02 13:22:13 +02:00
Christoffer Lerno
11bb8b49da - Assert triggered when casting from int[2] to uint[2] #2115 2025-05-01 18:23:48 +02:00
Christoffer Lerno
f0d2b0eff0 Update links in example code 2025-05-01 02:14:15 +02:00
Christoffer Lerno
005cc08118 0.7.2 bump 2025-04-30 18:03:00 +02:00
Christoffer Lerno
c5494a23ce Update readme 2025-04-30 16:13:59 +02:00
Christoffer Lerno
5dcc67aa1b Release candidate 0.7.1 2025-04-30 14:37:14 +02:00
Christian Buttner
335f53fb64 Rework Win32 mutex, condition variable and once flag (#2111)
* Rework Win32 mutex, condition variable and once flag.
2025-04-29 22:50:01 +02:00
Christoffer Lerno
3636898ac0 Fixed enum regression after 0.7.0 enum change. 2025-04-29 11:53:32 +02:00
Christoffer Lerno
5ba24e05d0 Typo 2025-04-27 14:22:51 +02:00
Christoffer Lerno
0ada5504af Add a file about contributing. 2025-04-27 14:01:22 +02:00
Christoffer Lerno
8ac02a28cc Error message for casting generic to incompatible type does not work properly with nested generics #1953 2025-04-27 00:40:43 +02:00
Christoffer Lerno
246957b8bd Minor refactoring 2025-04-26 23:32:52 +02:00
Christian Buttner
0595270d9a Fix mem::copy_inline compile. 2025-04-25 16:04:41 +02:00
Christoffer Lerno
8b86d1461d "Length mismatch between slices" when there is none #2110 2025-04-25 16:03:36 +02:00
Christoffer Lerno
0129308bf3 c3c build picks first target rather than the first executable #2105. 2025-04-25 15:44:34 +02:00
Christoffer Lerno
05094b4f47 @ensure should be allowed to read "out" variables. #2107 2025-04-25 15:36:07 +02:00
Christoffer Lerno
9a59cd164d Fixed regression slice copy #2106 2025-04-25 15:14:00 +02:00
Christoffer Lerno
3eecaf9e29 Compiler crash when passing an untyped list as an argument to assert #2108. 2025-04-25 15:02:23 +02:00
Christoffer Lerno
8b47673524 Added Enum.lookup and Enum.lookup_field. 2025-04-25 14:44:00 +02:00
Christoffer Lerno
8b29e4780d The %s would not properly print function pointers. 2025-04-25 11:26:28 +02:00
niedlich
fd2a81afb1 Improved CMake integration of LLVM (#2103)
Added the paths for the CMake config files for LLVM 19 and higher to ``CMAKE_PREFIX_PATH``,
so that they will also be searched automatically.
2025-04-23 11:41:26 +02:00
Christoffer Lerno
a0d4df2272 Update README.md
Add showcase link.
2025-04-23 00:52:46 +02:00
Christoffer Lerno
e39c7cae8d Comparing a distinct type with an enum with an inline distinct type failed unexpectedly 2025-04-22 21:51:37 +02:00
Christoffer Lerno
8a2907806b Fixes to tclone and other temp allocations with overaligned data. 2025-04-20 21:30:36 +02:00
Christoffer Lerno
f778e75757 Added missing @clone_aligned and add checks to @tclone 2025-04-20 18:31:52 +02:00
hyperpastel
6ab7953706 Patch false maybe-uninitialized warning 2025-04-20 16:59:52 +02:00
Christoffer Lerno
42e4370994 Fix conditional in slice assign check. 2025-04-18 22:14:33 +02:00
Christoffer Lerno
9a1fdbbca0 Add missing build_options.c commit. 2025-04-18 19:50:04 +02:00
joshringuk@gmail.com
434a0e8e4b array contains 2025-04-18 18:15:01 +02:00
Christoffer Lerno
946c167bf1 Improve error for default args #2096. Deprecated old inference with slice copy. Copying must now ensure a slicing operator at the end of the right hand side: foo[1..2] = bar[..] rather than the old foo[1..2] = bar. The old behaviour can be mostly retained with --use-old-slice-copy). 2025-04-18 17:19:04 +02:00
Christoffer Lerno
ba10c8953d @ensure was not included when the function doesn't return a value #2098. 2025-04-17 20:26:21 +02:00
Christoffer Lerno
72d7813c20 @if was ignored on attrdef, regression 0.7 #2093 2025-04-17 19:07:48 +02:00
Christoffer Lerno
1083de1f81 - Fix broken enum inline -> bool conversions #2094. 2025-04-17 19:00:04 +02:00
Christoffer Lerno
b4b6cba301 - Improved error messages on Foo { 3, abc } #2099. 2025-04-17 18:42:25 +02:00
Christoffer Lerno
3244898610 - @if now does implicit conversion to bool like $if. #2086 2025-04-16 23:49:12 +02:00
Christoffer Lerno
6454856fdb String str = "" is now guaranteed to be null terminated. #2083 2025-04-16 23:19:28 +02:00
Christoffer Lerno
5cf48ad730 Add Ubuntu 22 2025-04-16 20:25:42 +02:00
Boris Barbulovski
b5d0739de0 Add env::ANDROID to std.* 2025-04-16 17:47:49 +02:00
AlexCodesApps
f6e130ad3c Type mismatch fix (#2081)
* Fixed type mismatch in static function 'match_argopt' in file 'src/build/build_options.c',
 where false was returned from the function which has a return type of 'const char *'.
2025-04-16 17:46:54 +02:00
Simone Raimondi
f9e62b80ea Fix for build (#2082) 2025-04-16 17:45:39 +02:00
Christoffer Lerno
debbae594c Remove more Ubuntu 20 2025-04-16 17:11:53 +02:00
Christoffer Lerno
37ffd92f7b - Bug with slice acces as inline struct member #2088. 2025-04-16 17:02:22 +02:00
Christoffer Lerno
a44e932806 ABI bug on x64 Linux / MacOS when passing a union containing a struct of 3 floats. #2087 2025-04-16 15:58:14 +02:00
Christoffer Lerno
668175851b Improved error message #2084 2025-04-16 15:25:05 +02:00
Christoffer Lerno
e7c9ec0938 Added comments. 2025-04-16 01:32:01 +02:00
Christoffer Lerno
d6fa9cd50b Added some comments about the tracking allocator. 2025-04-15 16:03:12 +02:00
Christoffer Lerno
41e173d255 Added some comment about the temp allocator. 2025-04-15 15:53:11 +02:00
Christoffer Lerno
fde2bb2a7e Support @if on locals. 2025-04-15 13:20:10 +02:00
Christoffer Lerno
0a9bb2e8e0 Fix to simple a += b overload fallback. Renaming and reordering in the stdlib. 2025-04-15 12:01:58 +02:00
Christoffer Lerno
b64dcde21d Make aliases able to use @deprecated. Prefer math::I and math::I_F for math::IMAGINARY and math::IMAGINARYF the latter is deprecated. Combination of += and [] overloads now properly handled in most cases. 2025-04-14 20:51:01 +02:00
Christoffer Lerno
eade5fa57a Fix Windows sincos. 2025-04-14 03:36:03 +02:00
Christoffer Lerno
f85198e3ee Added += and related as overloads. Updated tests and docs. Slice2 extracted to its own file. 2025-04-14 00:55:46 +02:00
Christoffer Lerno
dca805bd8a Added tests to sincos. Correctly detect multiple overloads of the same type. Fix regression quaternion overload. Remove "1." style. 2025-04-13 15:46:27 +02:00
Christoffer Lerno
3888fcb182 - Add @operator_r and @operator_s attributes. 2025-04-13 13:43:03 +02:00
Christoffer Lerno
de73265d28 Fix operator overload struct placement. 2025-04-11 21:27:59 +02:00
Christoffer Lerno
cb895754c8 Size to store overload increas for msvc. 2025-04-11 21:17:42 +02:00
Christoffer Lerno
6e42bfef3b Typo 2025-04-11 21:04:46 +02:00
Christoffer Lerno
01357ef6d7 Check before hitting assert. 2025-04-11 21:00:30 +02:00
Christoffer Lerno
89d205258e Fix test. 2025-04-11 19:11:49 +02:00
Christoffer Lerno
0f2d425297 Operator overloading for + - * / % & | ^ << >> ~ == != 2025-04-11 18:46:22 +02:00
Christoffer Lerno
28fc03c376 Do not user finalizer with wasm 2025-04-08 00:03:35 +02:00
Christoffer Lerno
1290906d66 Setup temp allocator by default on Wasm 2025-04-07 23:35:38 +02:00
Christoffer Lerno
25d416aca1 Regression with invalid setup of the WASM temp allocator. 2025-04-07 21:58:21 +02:00
Christoffer Lerno
8cce7f6836 Incorrect rounding at compile time going from double to int. 2025-04-07 02:36:04 +02:00
Christoffer Lerno
4c26adb376 Improved error message when narrowing isn't allowed. 2025-04-07 01:12:23 +02:00
Christoffer Lerno
94b8330ac5 Function @require checks are added to the caller in safe mode. #186 2025-04-06 15:28:10 +02:00
Christoffer Lerno
3cb5df5639 0.7 fixes. Improving the yacc grammar. 2025-04-04 18:14:16 +02:00
Christoffer Lerno
ded5fde2d5 Fix test, fix type name. 2025-04-04 13:48:07 +02:00
Christoffer Lerno
65fb977e89 Clearer errors when using a &ref parameter with type. 2025-04-04 13:21:53 +02:00
Christoffer Lerno
e3f3b6f5f1 Better errors trying to convert an enum to an int and vice versa. Trying to cast an enum to int and back caused the compiler to crash. 2025-04-04 02:38:51 +02:00
Boris Barbulovski
ab4ed9472a Copy paste typo. 2025-04-04 02:29:25 +02:00
Christoffer Lerno
1668999f90 Better errors on some common casting mistakes (pointer->slice, String->ZString, deref pointer->array) #2064. 2025-04-04 00:12:52 +02:00
Christoffer Lerno
e828d9a05a Fix stdlib naming. 2025-04-03 01:47:52 +02:00
Avaxar
47447dc069 Glob crt1.o on Linux depending on architecture 2025-04-03 00:59:51 +02:00
Christoffer Lerno
39a59c929f Add dummy deprecated key. 2025-04-03 00:59:24 +02:00
Christoffer Lerno
f355738dda Project refactoring. Remove deprecated properties. 2025-04-03 00:56:17 +02:00
Christoffer Lerno
87e254e4b1 Added vector hash to release notes and change raylib dependency 2025-03-31 16:42:24 +02:00
Sander van den Bosch
561a683230 Added .hash() functions for vectors (#2043)
* Added .hash() functions for vectors
* Update test to a non-zero sized vector
* Changed vector hash functions to hash the underlying bytes in a char slice, the same approch is used for arrays
* Added test for hashed
* Updated formatting to be consistant with C3 code style
* Formatting, use "self"

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-03-31 16:33:48 +02:00
Alec Larson
63e5aa58c5 Create project_schema.json
By setting the `$schema` field of your `project.json` file to a URL leading to this JSON schema, your IDE may be able to provide auto-completion.
2025-03-31 16:09:52 +02:00
Christoffer Lerno
2be3071bdb 0.7.1 dev 2025-03-31 01:36:58 +02:00
Christoffer Lerno
d3e81b193a Update CI 2025-03-31 01:34:01 +02:00
Christoffer Lerno
586d191585 Fix in stdlib and update readme. 2025-03-30 23:11:29 +02:00
273 changed files with 8609 additions and 3132 deletions

View File

@@ -10,7 +10,7 @@ env:
LLVM_RELEASE_VERSION_WINDOWS: 18
LLVM_RELEASE_VERSION_MAC: 17
LLVM_RELEASE_VERSION_LINUX: 17
LLVM_RELEASE_VERSION_UBUNTU20: 17
LLVM_RELEASE_VERSION_UBUNTU22: 17
LLVM_DEV_VERSION: 21
jobs:
@@ -77,15 +77,15 @@ jobs:
- name: Vendor-fetch
run: |
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55_v7
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55
- name: Try raylib5
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55_v7
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55_v7 --print-linking examples\raylib\raylib_arkanoid.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55_v7 --print-linking examples\raylib\raylib_snake.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55_v7 --print-linking examples\raylib\raylib_tetris.c3
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55 --print-linking examples\raylib\raylib_arkanoid.c3
..\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: Compile run unit tests
run: |
@@ -158,7 +158,7 @@ jobs:
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib55_v7
./build/c3c vendor-fetch raylib55
- name: Build testproject lib
run: |
@@ -401,8 +401,8 @@ jobs:
name: c3-linux-${{matrix.build_type}}
path: c3-linux-${{matrix.build_type}}.tar.gz
build-linux-ubuntu20:
runs-on: ubuntu-20.04
build-linux-ubuntu22:
runs-on: ubuntu-22.04
strategy:
# Don't abort runners if a single one fails
fail-fast: false
@@ -507,7 +507,7 @@ jobs:
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU22
run: |
mkdir c3
cp -r lib c3
@@ -515,15 +515,15 @@ jobs:
cp releasenotes.md c3
cp msvc_build_libraries.py c3
cp build/c3c c3
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz c3
tar czf c3-ubuntu-22-${{matrix.build_type}}.tar.gz c3
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU22
uses: actions/upload-artifact@v4
with:
name: c3-ubuntu-20-${{matrix.build_type}}
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
name: c3-ubuntu-22-${{matrix.build_type}}
path: c3-ubuntu-22-${{matrix.build_type}}.tar.gz
build-with-docker:
runs-on: ubuntu-22.04
strategy:
@@ -642,7 +642,7 @@ jobs:
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib55_v7
./build/c3c vendor-fetch raylib55
- name: Compile and run some examples
run: |
@@ -749,7 +749,7 @@ jobs:
release:
runs-on: ubuntu-22.04
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu20]
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu22]
if: github.ref == 'refs/heads/master'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -769,19 +769,19 @@ jobs:
- run: zip -r c3-windows-debug.zip c3-windows-Debug
- run: mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux-Release/c3-linux.tar.gz
- run: mv c3-linux-Debug/c3-linux-Debug.tar.gz c3-linux-Debug/c3-linux-debug.tar.gz
- run: mv c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
- run: mv c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
- run: mv c3-ubuntu-22-Release/c3-ubuntu-22-Release.tar.gz c3-ubuntu-22-Release/c3-ubuntu-22.tar.gz
- run: mv c3-ubuntu-22-Debug/c3-ubuntu-22-Debug.tar.gz c3-ubuntu-22-Debug/c3-ubuntu-22-debug.tar.gz
- run: mv c3-macos-Release/c3-macos-Release.zip c3-macos-Release/c3-macos.zip
- run: mv c3-macos-Debug/c3-macos-Debug.zip c3-macos-Debug/c3-macos-debug.zip
- run: gh release delete latest-0.7.0-prerelease --cleanup-tag -y || true
- run: echo "RELEASE_NAME=latest-0.7.0-rc-$(date +'%Y%m%d-%H%M')" >> $GITHUB_ENV
- run: gh release delete latest-prerelease --cleanup-tag -y || true
- run: echo "RELEASE_NAME=latest-prerelease-$(date +'%Y%m%d-%H%M')" >> $GITHUB_ENV
- id: create_release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: latest-0.7.0-prerelease
tag_name: latest-prerelease
name: ${{ env.RELEASE_NAME }}
draft: false
prerelease: true
@@ -790,7 +790,7 @@ jobs:
c3-windows-debug.zip
c3-linux-Release/c3-linux.tar.gz
c3-linux-Debug/c3-linux-debug.tar.gz
c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
c3-ubuntu-22-Release/c3-ubuntu-22.tar.gz
c3-ubuntu-22-Debug/c3-ubuntu-22-debug.tar.gz
c3-macos-Release/c3-macos.zip
c3-macos-Debug/c3-macos-debug.zip

View File

@@ -144,6 +144,20 @@ if(C3_WITH_LLVM)
find_package(LLVM REQUIRED CONFIG)
find_package(LLD REQUIRED CONFIG)
else()
# Add paths for LLVM CMake files of version 19 and higher as they follow a new installation
# layout and are now in /usr/lib/llvm/*/lib/cmake/llvm/ rather than /usr/lib/cmake/llvm/
#
# Because of CMAKE_FIND_PACKAGE_SORT_ORDER CMAKE_FIND_PACKAGE_SORT_DIRECTION,
# the newest version will always be found first.
message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}")
if (DEFINED LLVM_DIR)
message(STATUS "Looking for LLVM CMake files in user-specified directory ${LLVM_DIR}")
else()
file (GLOB LLVM_CMAKE_PATHS "/usr/lib/llvm/*/lib/cmake/llvm/")
list (APPEND CMAKE_PREFIX_PATH ${LLVM_CMAKE_PATHS} "/usr/lib/")
message(STATUS "No LLVM_DIR specified, searching default directories ${CMAKE_PREFIX_PATH}")
endif()
if (NOT C3_LLVM_VERSION STREQUAL "auto")
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
else()
@@ -151,6 +165,10 @@ if(C3_WITH_LLVM)
endif()
endif()
if (EXISTS /opt/homebrew/lib)
list(APPEND LLVM_LIBRARY_DIRS /opt/homebrew/lib)
endif()
if (EXISTS /usr/lib)
# Some systems (such as Alpine Linux) seem to put some of the relevant
# LLVM files in /usr/lib, but this doesn't seem to be included in the

View File

@@ -151,8 +151,9 @@ one with related functions when working on temporary strings.
# C3 Standard library style guide.
When contributing to the standard librairy please to your best to follow the following style requirements
as to ensure a consistent style in the stdlib and also make accepting PRs more quickly.
When contributing to the standard library please try your best to adhere to the
following style requirements to ensure a consistent style in the stdlib and to
facilitate accepting PRs more quickly.
### Braces are placed on the next line
@@ -256,4 +257,4 @@ argument.
## Add tests to your changes
If you add or fix things, then there should always be tests in `test/unit/stdlib` to verify
the functionality.
the functionality.

70
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,70 @@
# How to contribute to C3
The C3 project consists of
1. The C3 language itself.
2. The C3 compiler, called c3c.
3. The C3 standard library
4. Various tools, such as the editor plugins
## 1. How to contribute to the C3 language
The C3 language is essentially the language specification. You can contribute to the language by:
1. Filing enhancement requests for changes to the language.
2. Offering feedback on existing features, on Discord or by filing issues.
3. Help working on the language specification.
4. Help working on the grammar.
## 2. How to contribute to the C3 compiler
The C3 compiler consists for the compiler itself + test suites for testing the compiler.
You can contribute by:
1. File bugs (by far the most important thing).
2. Suggest improved diagnostics / error messages.
3. Refactoring existing code (needs deep understanding of the compiler).
4. Add support for more architectures.
5. Add support for more backends.
## 3. How to contribute to the standard library
The standard library is the library itself + test suites for testing the standard library.
You can contribute by:
1. Filing bugs on the standard library.
2. Write additional unit tests.
3. Suggest new functionality by filing an issue.
4. Work on stdlib additions.
5. Fix bugs in the stdlib
6. Maintain a section of the standard library
### How to work on small stdlib additions
If there is just a matter of adding a function or two to an existing module, a pull request
is sufficient. However, please make sure that:
1. It follows the guidelines for the code to ensure a uniform experience (naming standard, indentation, braces etc).
2. Add a line in the release notes about the change.
3. Make sure it has unit tests.
### How to work on non-trivial additions to the stdlib
Regardless whether an addition is approved for inclusion or not, it needs to incubate:
1. First implement it standalone, showing that its working well and has a solid design. This has the advantage of people being able to contribute or even create competing implementations
2. Once it is considered finished it can be proposed for inclusion.
This will greatly help improving the quality of additions.
Note that any new addition needs a full set of unit tests before being included into the standard library.
### Maintain a part of the standard library
A single maintainer is insufficient for a standard library, instead we need one or more maintainer
for each module. The maintainer(s) will review pull requests and actively work on making the module
pristine with the highest possible quality.
## 4. How to contribute to various tools
In general, file a pull request. Depending on who maintains it, rules may differ.

View File

@@ -8,16 +8,18 @@ for programmers who like C.
Precompiled binaries for the following operating systems are available:
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
![vkQuake](https://github.com/c3lang/c3c/blob/master/resources/images/vkQuake.png?raw=true)
Thanks to full ABI compatibility with C, it's possible to mix C and C3 in the same project with no effort. As a demonstration, vkQuake was compiled with a small portion of the code converted to C3 and compiled with the c3c compiler. (The fork can be found at https://github.com/c3lang/vkQuake)
Thanks to full ABI compatibility with C, it's possible to mix C and C3 in the same project with no effort. As a demonstration, vkQuake was compiled with a small portion of the code converted to C3 and compiled with the c3c compiler. (The aging fork can be found at https://github.com/c3lang/vkQuake)
A non-curated list of user written projects and other resources can be found [here](https://github.com/c3lang/c3-showcase).
### Design Principles
- Procedural "get things done"-type of language.
@@ -33,7 +35,7 @@ whole new language.
### Example code
The following code shows [generic modules](https://c3-lang.org/references/docs/generics/) (more examples can be found at https://c3-lang.org/references/docs/examples/).
The following code shows [generic modules](https://c3-lang.org/generic-programming/generics/) (more examples can be found at https://c3-lang.org/language-overview/examples/).
```cpp
module stack {Type};
@@ -124,6 +126,7 @@ fn void main()
- New semantic macro system
- Module based name spacing
- Slices
- Operator overloading
- Compile time reflection
- Enhanced compile time execution
- Generics based on generic modules
@@ -138,9 +141,10 @@ fn void main()
### Current status
The current stable version of the compiler is **version 0.6.8**.
The current stable version of the compiler is **version 0.7.2**.
The the next version is 0.7.0 which will be a breaking release.
The upcoming 0.7.x releases will focus on expanding the standard library,
fixing bugs and improving compile time analysis.
Follow the issues [here](https://github.com/c3lang/c3c/issues).
If you have suggestions on how to improve the language, either [file an issue](https://github.com/c3lang/c3c/issues)
@@ -193,29 +197,31 @@ More platforms will be supported in the future.
### Installing
This installs the latest prerelease build, as opposed to the latest released version.
#### Installing on Windows with precompiled binaries
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-windows-debug.zip))
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows-debug.zip))
2. Unzip exe and standard lib.
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 Debian with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-linux-debug.tar.gz))
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 Ubuntu with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20-debug.tar.gz))
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))
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on MacOS with precompiled binaries
1. Make sure you have XCode with command line tools installed.
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-macos-debug.zip))
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos-debug.zip))
3. Unzip executable and standard lib.
4. Run `./c3c`.
@@ -406,4 +412,4 @@ A special thank you to sponsors [Caleb-o](https://github.com/Caleb-o) and [devda
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=c3lang/c3c&type=Date)](https://www.star-history.com/#c3lang/c3c&Date)
[![Star History Chart](https://api.star-history.com/svg?repos=c3lang/c3c&type=Date)](https://www.star-history.com/#c3lang/c3c&Date)

View File

@@ -1,77 +1,42 @@
<* This module is scheduled for removal, use std::core::ascii *>
module std::ascii;
macro bool in_range_m(c, start, len) => (uint)(c - start) < len;
macro bool is_lower_m(c) => in_range_m(c, 0x61, 26);
macro bool is_upper_m(c) => in_range_m(c, 0x41, 26);
macro bool is_digit_m(c) => in_range_m(c, 0x30, 10);
macro bool is_lower_m(c) => in_range_m(c, 0x61, 26);
macro bool is_upper_m(c) => in_range_m(c, 0x41, 26);
macro bool is_digit_m(c) => in_range_m(c, 0x30, 10);
macro bool is_bdigit_m(c) => in_range_m(c, 0x30, 2);
macro bool is_odigit_m(c) => in_range_m(c, 0x30, 8);
macro bool is_xdigit_m(c) => in_range_m(c | 32, 0x61, 6) || is_digit_m(c);
macro bool is_alpha_m(c) => in_range_m(c | 32, 0x61, 26);
macro bool is_print_m(c) => in_range_m(c, 0x20, 95);
macro bool is_graph_m(c) => in_range_m(c, 0x21, 94);
macro bool is_space_m(c) => in_range_m(c, 0x9, 5) || c == 0x20;
macro bool is_alnum_m(c) => is_alpha_m(c) || is_digit_m(c);
macro bool is_punct_m(c) => !is_alnum_m(c) && is_graph_m(c);
macro bool is_blank_m(c) => c == 0x20 || c == 0x9;
macro bool is_cntrl_m(c) => c < 0x20 || c == 0x7f;
macro bool is_alpha_m(c) => in_range_m(c | 32, 0x61, 26);
macro bool is_print_m(c) => in_range_m(c, 0x20, 95);
macro bool is_graph_m(c) => in_range_m(c, 0x21, 94);
macro bool is_space_m(c) => in_range_m(c, 0x9, 5) || c == 0x20;
macro bool is_alnum_m(c) => is_alpha_m(c) || is_digit_m(c);
macro bool is_punct_m(c) => !is_alnum_m(c) && is_graph_m(c);
macro bool is_blank_m(c) => c == 0x20 || c == 0x9;
macro bool is_cntrl_m(c) => c < 0x20 || c == 0x7f;
macro to_lower_m(c) => is_upper_m(c) ? c + 0x20 : c;
macro to_upper_m(c) => is_lower_m(c) ? c - 0x20 : c;
fn bool in_range(char c, char start, char len) => in_range_m(c, start, len);
fn bool is_lower(char c) => is_lower_m(c);
fn bool is_upper(char c) => is_upper_m(c);
fn bool is_digit(char c) => is_digit_m(c);
fn bool is_bdigit(char c) => is_bdigit_m(c);
fn bool is_odigit(char c) => is_odigit_m(c);
fn bool is_xdigit(char c) => is_xdigit_m(c);
fn bool is_alpha(char c) => is_alpha_m(c);
fn bool is_print(char c) => is_print_m(c);
fn bool is_graph(char c) => is_graph_m(c);
fn bool is_space(char c) => is_space_m(c);
fn bool is_alnum(char c) => is_alnum_m(c);
fn bool is_punct(char c) => is_punct_m(c);
fn bool is_blank(char c) => is_blank_m(c);
fn bool is_cntrl(char c) => is_cntrl_m(c);
fn char to_lower(char c) => (char)to_lower_m(c);
fn char to_upper(char c) => (char)to_upper_m(c);
fn bool char.in_range(char c, char start, char len) => in_range_m(c, start, len);
fn bool char.is_lower(char c) => is_lower_m(c);
fn bool char.is_upper(char c) => is_upper_m(c);
fn bool char.is_digit(char c) => is_digit_m(c);
fn bool char.is_bdigit(char c) => is_bdigit_m(c);
fn bool char.is_odigit(char c) => is_odigit_m(c);
fn bool char.is_xdigit(char c) => is_xdigit_m(c);
fn bool char.is_alpha(char c) => is_alpha_m(c);
fn bool char.is_print(char c) => is_print_m(c);
fn bool char.is_graph(char c) => is_graph_m(c);
fn bool char.is_space(char c) => is_space_m(c);
fn bool char.is_alnum(char c) => is_alnum_m(c);
fn bool char.is_punct(char c) => is_punct_m(c);
fn bool char.is_blank(char c) => is_blank_m(c);
fn bool char.is_cntrl(char c) => is_cntrl_m(c);
fn char char.to_lower(char c) => (char)to_lower_m(c);
fn char char.to_upper(char c) => (char)to_upper_m(c);
<*
@require c.is_xdigit()
*>
fn char char.from_hex(char c) => c.is_digit() ? c - '0' : 10 + (c | 0x20) - 'a';
fn bool uint.in_range(uint c, uint start, uint len) => in_range_m(c, start, len);
fn bool uint.is_lower(uint c) => is_lower_m(c);
fn bool uint.is_upper(uint c) => is_upper_m(c);
fn bool uint.is_digit(uint c) => is_digit_m(c);
fn bool uint.is_bdigit(uint c) => is_bdigit_m(c);
fn bool uint.is_odigit(uint c) => is_odigit_m(c);
fn bool uint.is_xdigit(uint c) => is_xdigit_m(c);
fn bool uint.is_alpha(uint c) => is_alpha_m(c);
fn bool uint.is_print(uint c) => is_print_m(c);
fn bool uint.is_graph(uint c) => is_graph_m(c);
fn bool uint.is_space(uint c) => is_space_m(c);
fn bool uint.is_alnum(uint c) => is_alnum_m(c);
fn bool uint.is_punct(uint c) => is_punct_m(c);
fn bool uint.is_blank(uint c) => is_blank_m(c);
fn bool uint.is_cntrl(uint c) => is_cntrl_m(c);
fn uint uint.to_lower(uint c) => (uint)to_lower_m(c);
fn uint uint.to_upper(uint c) => (uint)to_upper_m(c);
fn bool uint.is_lower(uint c) @deprecated => is_lower_m(c);
fn bool uint.is_upper(uint c) @deprecated => is_upper_m(c);
fn bool uint.is_digit(uint c) @deprecated => is_digit_m(c);
fn bool uint.is_bdigit(uint c) @deprecated => is_bdigit_m(c);
fn bool uint.is_odigit(uint c) @deprecated => is_odigit_m(c);
fn bool uint.is_xdigit(uint c) @deprecated => is_xdigit_m(c);
fn bool uint.is_alpha(uint c) @deprecated => is_alpha_m(c);
fn bool uint.is_print(uint c) @deprecated => is_print_m(c);
fn bool uint.is_graph(uint c) @deprecated => is_graph_m(c);
fn bool uint.is_space(uint c) @deprecated => is_space_m(c);
fn bool uint.is_alnum(uint c) @deprecated => is_alnum_m(c);
fn bool uint.is_punct(uint c) @deprecated => is_punct_m(c);
fn bool uint.is_blank(uint c) @deprecated => is_blank_m(c);
fn bool uint.is_cntrl(uint c) @deprecated => is_cntrl_m(c);
fn uint uint.to_lower(uint c) @deprecated => (uint)to_lower_m(c);
fn uint uint.to_upper(uint c) @deprecated => (uint)to_upper_m(c);

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
// Copyright (c) 2023-2025 Eduardo José Gómez Hernández. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::atomic::types{Type};
@@ -183,7 +183,7 @@ macro bool is_native_atomic_type($Type)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be added to ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -203,7 +203,7 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be subtracted from ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -223,7 +223,7 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be multiplied with ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -263,7 +263,7 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to divide ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -303,7 +303,7 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise or with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -320,7 +320,7 @@ macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise xor with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -337,7 +337,7 @@ macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise and with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -354,7 +354,7 @@ macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to shift ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -396,7 +396,7 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to shift ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -438,7 +438,7 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@@ -466,7 +466,7 @@ macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [&inout] ptr : "the variable or dereferenced pointer to the data."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"

View File

@@ -7,6 +7,17 @@ import std::io,std::math;
alias AnyPredicate = fn bool(any value);
alias AnyTest = fn bool(any type, any context);
<*
The AnyList contains a heterogenous set of types. Anything placed in the
list will shallowly copied in order to be stored as an `any`. This means
that the list will copy and free its elements.
However, because we're getting `any` 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 any.
*>
struct AnyList (Printable)
{
usz size;
@@ -17,8 +28,11 @@ struct AnyList (Printable)
<*
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"
@param initial_capacity : "The initial capacity to reserve, defaults to 16"
*>
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
{
@@ -49,6 +63,361 @@ fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16)
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);
}
<*
Return the first element by value, assuming it is the given type.
@param $Type : "The type of the first element"
@return "The first element"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
macro AnyList.first(&self, $Type)
{
return *anycast(self.first_any(), $Type);
}
<*
Return the first element
@return "The first element"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.first_any(&self) @inline
{
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
}
<*
Return the last element by value, assuming it is the given type.
@param $Type : "The type of the last element"
@return "The last element"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
macro AnyList.last(&self, $Type)
{
return *anycast(self.last_any(), $Type);
}
<*
Return the last element
@return "The last element"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.last_any(&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 AnyList.is_empty(&self) @inline
{
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;
}
<*
Return an element in the list by value, assuming it is the given type.
@param index : "The index of the element to retrieve"
@param $Type : "The type of the element"
@return "The element at the index"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
@require index < self.size : "Index out of range"
*>
macro AnyList.get(&self, usz index, $Type)
{
return *anycast(self.entries[index], $Type);
}
<*
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 any AnyList.get_any(&self, usz index) @inline @operator([])
{
return self.entries[index];
}
<*
Completely free and clear a 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)
@@ -70,265 +439,8 @@ fn usz? AnyList.to_format(&self, Formatter* formatter) @dynamic
}
<*
Push an element on the list by cloning it.
*>
macro void AnyList.push(&self, element)
{
if (!self.allocator) self.allocator = tmem;
self.append_internal(allocator::clone(self.allocator, element));
}
Remove any elements matching the predicate.
fn void AnyList.append_internal(&self, any element) @local
{
self.ensure_capacity();
self.entries[self.size++] = element;
}
<*
Free a retained element removed using *_retained.
*>
fn void AnyList.free_element(&self, any element) @inline
{
allocator::free(self.allocator, element.ptr);
}
<*
Pop a value who's type is known. If the type is incorrect, this
will still pop the element.
@return? 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);
}
<*
Pop the last value and allocate the copy using the given allocator.
@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]);
}
<*
Pop the last value and allocate the copy using the temp allocator
@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? NO_MORE_ELEMENT
*>
fn any? AnyList.pop_retained(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
fn void AnyList.clear(&self)
{
for (usz i = 0; i < self.size; i++)
{
self.free_element(self.entries[i]);
}
self.size = 0;
}
<*
Same as pop() but pops the first value instead.
*>
macro AnyList.pop_first(&self, $Type)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return *anycast(self.entries[0], $Type);
}
<*
Same as pop_retained() but pops the first value instead.
*>
fn any? AnyList.pop_first_retained(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
<*
Same as copy_pop() but pops the first value instead.
*>
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]);
}
<*
Same as temp_pop() but pops the first value instead.
*>
fn any? AnyList.tcopy_pop_first(&self) => self.copy_pop_first(tmem);
<*
@require index < self.size
*>
fn void AnyList.remove_at(&self, usz index)
{
if (!--self.size || index == self.size) return;
self.free_element(self.entries[index]);
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
}
fn void AnyList.add_all(&self, AnyList* other_list)
{
if (!other_list.size) return;
self.reserve(other_list.size);
foreach (value : other_list)
{
self.entries[self.size++] = allocator::clone_any(self.allocator, value);
}
}
<*
Reverse the elements in a list.
*>
fn void AnyList.reverse(&self)
{
if (self.size < 2) return;
usz half = self.size / 2U;
usz end = self.size - 1;
for (usz i = 0; i < half; i++)
{
self.swap(i, end - i);
}
}
fn any[] AnyList.array_view(&self)
{
return self.entries[:self.size];
}
<*
Push an element to the front of the list.
*>
macro void AnyList.push_front(&self, type)
{
self.insert_at(0, type);
}
<*
@require index < self.size
*>
macro void AnyList.insert_at(&self, usz index, type) @local
{
any value = allocator::copy(self.allocator, type);
self.insert_at_internal(self, index, value);
}
<*
@require index < self.size
*>
fn void AnyList.insert_at_internal(&self, usz index, any value) @local
{
self.ensure_capacity();
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.size++;
self.entries[index] = value;
}
<*
@require self.size > 0
*>
fn void AnyList.remove_last(&self)
{
self.free_element(self.entries[--self.size]);
}
<*
@require self.size > 0
*>
fn void AnyList.remove_first(&self)
{
self.remove_at(0);
}
macro AnyList.first(&self, $Type)
{
return *anycast(self.first_any(), $Type);
}
fn any? AnyList.first_any(&self) @inline
{
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
}
macro AnyList.last(&self, $Type)
{
return *anycast(self.last_any(), $Type);
}
fn any? AnyList.last_any(&self) @inline
{
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?;
}
fn bool AnyList.is_empty(&self) @inline
{
return !self.size;
}
fn usz AnyList.len(&self) @operator(len) @inline
{
return self.size;
}
<*
@require index < self.size : "Index out of range"
*>
macro AnyList.get(&self, usz index, $Type)
{
return *anycast(self.entries[index], $Type);
}
<*
@require index < self.size : "Index out of range"
*>
fn any AnyList.get_any(&self, usz index) @inline
{
return self.entries[index];
}
fn void AnyList.free(&self)
{
if (!self.allocator) return;
self.clear();
allocator::free(self.allocator, self.entries);
self.capacity = 0;
self.entries = null;
}
fn void AnyList.swap(&self, usz i, usz j)
{
any temp = self.entries[i];
self.entries[i] = self.entries[j];
self.entries[j] = temp;
}
<*
@param filter : "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
@@ -338,6 +450,8 @@ fn usz AnyList.remove_if(&self, AnyPredicate filter)
}
<*
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"
*>
@@ -346,40 +460,95 @@ fn usz AnyList.retain_if(&self, AnyPredicate selection)
return self._remove_if(selection, true);
}
macro usz AnyList._remove_if(&self, AnyPredicate filter, bool $invert) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && filter(&self.entries[i - 1])) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$endif
}
return size - self.size;
}
<*
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);
}
fn usz AnyList.retain_using_test(&self, AnyTest filter, any 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(filter, true, 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
@@ -408,45 +577,28 @@ macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @
return size - self.size;
}
<*
Reserve at least min_capacity
*>
fn void AnyList.reserve(&self, usz min_capacity)
macro usz AnyList._remove_if(&self, AnyPredicate filter, bool $invert) @local
{
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;
}
macro any AnyList.@item_at(&self, usz index) @operator([])
{
return self.entries[index];
}
<*
@require index <= self.size : "Index out of range"
*>
macro void AnyList.set(&self, usz index, value)
{
if (index == self.size)
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
self.push(value);
return;
// 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
}
self.free_element(self.entries[index]);
self.entries[index] = allocator::copy(self.allocator, value);
}
fn void AnyList.ensure_capacity(&self, usz added = 1) @inline @private
{
usz new_size = self.size + added;
if (self.capacity >= new_size) return;
assert(new_size < usz.max / 2U);
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
while (new_capacity < new_size) new_capacity *= 2U;
self.reserve(new_capacity);
return size - self.size;
}

View File

@@ -1,18 +1,22 @@
// Copyright (c) 2023-2025 C3 team. 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 SIZE > 0
@require SIZE > 0 : "The size of the bitset in bits must be at least 1"
*>
module std::collections::bitset{SIZE};
module std::collections::bitset {SIZE};
alias Type = uint;
const BITS = Type.sizeof * 8;
const BITS = uint.sizeof * 8;
const SZ = (SIZE + BITS - 1) / BITS;
struct BitSet
{
Type[SZ] data;
uint[SZ] data;
}
<*
@return "The number of bits set"
*>
fn usz BitSet.cardinality(&self)
{
usz n;
@@ -24,7 +28,11 @@ fn usz BitSet.cardinality(&self)
}
<*
@require i < SIZE
Set a bit in the bitset.
@param i : "The index to set"
@require i < SIZE : "Index was out of range"
*>
fn void BitSet.set(&self, usz i)
{
@@ -34,7 +42,86 @@ fn void BitSet.set(&self, usz i)
}
<*
@require i < SIZE
Perform xor over all bits, mutating itself
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
macro BitSet BitSet.xor_self(&self, BitSet set) @operator(^=)
{
foreach (i, &x : self.data) *x ^= set.data[i];
return *self;
}
<*
Perform xor over all bits, returning a new bit set.
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
fn BitSet BitSet.xor(&self, BitSet set) @operator(^)
{
BitSet new_set @noinit;
foreach (i, x : self.data) new_set.data[i] = x ^ set.data[i];
return new_set;
}
<*
Perform or over all bits, returning a new bit set.
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
fn BitSet BitSet.or(&self, BitSet set) @operator(|)
{
BitSet new_set @noinit;
foreach (i, x : self.data) new_set.data[i] = x | set.data[i];
return new_set;
}
<*
Perform or over all bits, mutating itself
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
macro BitSet BitSet.or_self(&self, BitSet set) @operator(|=)
{
foreach (i, &x : self.data) *x |= set.data[i];
return *self;
}
<*
Perform & over all bits, returning a new bit set.
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
fn BitSet BitSet.and(&self, BitSet set) @operator(&)
{
BitSet new_set @noinit;
foreach (i, x : self.data) new_set.data[i] = x & set.data[i];
return new_set;
}
<*
Perform & over all bits, mutating itself.
@param set : "The bit set to xor with"
@return "The resulting bit set"
*>
macro BitSet BitSet.and_self(&self, BitSet set) @operator(&=)
{
foreach (i, &x : self.data) *x &= set.data[i];
return *self;
}
<*
Unset (clear) a bit in the bitset.
@param i : "The index to set"
@require i < SIZE : "Index was out of range"
*>
fn void BitSet.unset(&self, usz i)
{
@@ -44,7 +131,11 @@ fn void BitSet.unset(&self, usz i)
}
<*
@require i < SIZE
Get a particular bit in the bitset
@param i : "The index of the bit"
@require i < SIZE : "Index was out of range"
*>
fn bool BitSet.get(&self, usz i) @operator([]) @inline
{
@@ -59,7 +150,12 @@ fn usz BitSet.len(&self) @operator(len) @inline
}
<*
@require i < SIZE
Change a particular bit in the bitset
@param i : "The index of the bit"
@param value : "The value to set the bit to"
@require i < SIZE : "Index was out of range"
*>
fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
{

View File

@@ -3,9 +3,10 @@
*>
module std::collections::enummap{Enum, ValueType};
import std::io;
struct EnumMap (Printable)
{
ValueType[Enum.len] values;
ValueType[Enum.values.len] values;
}
fn void EnumMap.init(&self, ValueType init_value)

View File

@@ -8,9 +8,10 @@
module std::collections::enumset{Enum};
import std::io;
alias EnumSetType @private = $typefrom(type_for_enum_elements(Enum.elements));
const ENUM_COUNT @private = Enum.values.len;
alias EnumSetType @private = $typefrom(type_for_enum_elements(ENUM_COUNT));
const IS_CHAR_ARRAY = Enum.elements > 128;
const IS_CHAR_ARRAY = ENUM_COUNT > 128;
typedef EnumSet (Printable) = EnumSetType;
fn void EnumSet.add(&self, Enum v)

View File

@@ -34,3 +34,12 @@ macro Type? Maybe.get(self)
{
return self.has_value ? self.value : NOT_FOUND?;
}
fn bool Maybe.equals(self, Maybe other) @operator(==) @if(types::is_equatable_type(Type))
{
if (self.has_value)
{
return other.has_value && equals(self.value, other.value);
}
return !other.has_value;
}

View File

@@ -186,6 +186,10 @@ fn void Object.set_object(&self, String key, Object* new_object) @private
self.map.set(key, new_object);
}
<*
@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"
*>
macro Object* Object.object_from_value(&self, value) @private
{
var $Type = $typeof(value);
@@ -201,7 +205,6 @@ macro Object* Object.object_from_value(&self, value) @private
$case $Type.typeid == Object*.typeid:
return value;
$case $Type.typeid == void*.typeid:
if (value != null) return TYPE_MISMATCH?;
return &NULL_OBJECT;
$case $assignable(value, String):
return new_string(value, self.allocator);

View File

@@ -248,7 +248,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
}
// write end of stream
output[pos:END_OF_STREAM.len] = END_OF_STREAM;
output[pos:END_OF_STREAM.len] = END_OF_STREAM[..];
pos += END_OF_STREAM.len;
return output[:pos];
@@ -364,7 +364,7 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c
}
// draw the pixel
if (channels == RGBA) { image[loc:4] = p.rgba; } else { image[loc:3] = p.rgb; }
if (channels == RGBA) { image[loc:4] = p.rgba[..]; } else { image[loc:3] = p.rgb[..]; }
}
return image;

View File

@@ -1,9 +1,13 @@
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
// Copyright (c) 2023-2025 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator;
import std::math;
// The arena allocator allocates up to its maximum data
// and then fails to allocate more, returning out of memory.
// It supports mark and reset to mark.
struct ArenaAllocator (Allocator)
{
char[] data;
@@ -12,6 +16,8 @@ struct ArenaAllocator (Allocator)
<*
Initialize a memory arena for use using the provided bytes.
@param [inout] data : "The memory to use for the arena."
*>
fn ArenaAllocator* ArenaAllocator.init(&self, char[] data)
{
@@ -20,23 +26,44 @@ fn ArenaAllocator* ArenaAllocator.init(&self, char[] data)
return self;
}
<*
Reset the usage completely.
*>
fn void ArenaAllocator.clear(&self)
{
self.used = 0;
}
struct ArenaAllocatorHeader @local
{
usz size;
char[*] data;
}
<*
Given some memory, create an arena allocator on the stack for it.
@param [inout] bytes : `The bytes to use, may be empty.`
@return `An arena allocator using the bytes`
*>
macro ArenaAllocator* wrap(char[] bytes)
{
return (ArenaAllocator){}.init(bytes);
}
<*
"Mark" the current state of the arena allocator by returning the use count.
@return `The value to pass to 'reset' in order to reset to the current use.`
*>
fn usz ArenaAllocator.mark(&self) => self.used;
<*
Reset to a previous mark.
@param mark : `The previous mark.`
@require mark <= self.used : "Invalid mark - out of range"
*>
fn void ArenaAllocator.reset(&self, usz mark) => self.used = mark;
<*
Implements the Allocator interface method.
@require ptr != null
*>
fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
@@ -50,10 +77,10 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
}
}
fn usz ArenaAllocator.mark(&self) => self.used;
fn void ArenaAllocator.reset(&self, usz mark) => self.used = mark;
<*
Implements the Allocator interface method.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require size > 0
@@ -77,6 +104,8 @@ fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz a
}
<*
Implements the Allocator interface method.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require old_pointer != null
@@ -111,4 +140,12 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
}
// Internal data
struct ArenaAllocatorHeader @local
{
usz size;
char[*] data;
}

View File

@@ -1,12 +1,15 @@
module std::core::mem::allocator;
import std::io, std::math;
struct AllocChunk @local
{
usz size;
char[*] data;
}
<*
The backed arena allocator provides an allocator that will allocate from a pre-allocated chunk of memory
provided by it's backing allocator. The allocator supports mark / reset operations, so it can be used
as a stack (push-pop) allocator. If the initial memory is used up, it will fall back to regular allocations,
that will be safely freed on `reset`.
While this allocator is similar to the dynamic arena, it supports multiple "save points", which the dynamic arena
doesn't.
*>
struct BackedArenaAllocator (Allocator)
{
Allocator backing_allocator;
@@ -16,6 +19,12 @@ struct BackedArenaAllocator (Allocator)
char[*] data;
}
struct AllocChunk @local
{
usz size;
char[*] data;
}
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
struct ExtraPage @local

View File

@@ -4,6 +4,17 @@
module std::core::mem::allocator;
import std::math;
<*
The dynamic arena allocator is an arena allocator that can grow by adding additional arena "pages".
It only supports reset, at which point all pages except the first one is released to the backing
allocator.
If you want multiple save points, use the BackedArenaAllocator instead.
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.
*>
struct DynamicArenaAllocator (Allocator)
{
Allocator backing_allocator;

View File

@@ -5,6 +5,13 @@
module std::core::mem::allocator;
import std::math;
<*
The SimpleHeapAllocator implements a simple heap allocator on top of an allocator function.
It uses the given allocator function to allocate memory from some source, but never frees it.
This allocator is intended to be used in environments where there isn't any native libc malloc,
and it has to be emulated from a memory region, or wrapping linear memory as is the case for plain WASM.
*>
struct SimpleHeapAllocator (Allocator)
{
MemoryAllocFn alloc_fn;

View File

@@ -1,13 +1,15 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator @if(env::LIBC);
import std::io;
import libc;
const LibcAllocator LIBC_ALLOCATOR = {};
<*
The LibcAllocator is a wrapper around malloc to conform to the Allocator interface.
*>
typedef LibcAllocator (Allocator, Printable) = uptr;
const LibcAllocator LIBC_ALLOCATOR = {};
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
fn usz? LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");

View File

@@ -1,5 +1,14 @@
module std::core::mem::allocator;
<*
The OnStackAllocator is similar to the ArenaAllocator: it allocates from a chunk of memory
given to it.
The difference is that when it runs out of memory it will go directly to its backing allocator
rather than failing.
It is utilized by the @stack_mem macro as an alternative to the temp allocator.
*>
struct OnStackAllocator (Allocator)
{
Allocator backing_allocator;
@@ -8,7 +17,6 @@ struct OnStackAllocator (Allocator)
OnStackAllocatorExtraChunk* chunk;
}
struct OnStackAllocatorExtraChunk @local
{
bool is_aligned;

View File

@@ -2,12 +2,32 @@ module std::core::mem::allocator;
import std::io, std::math;
import std::core::sanitizer::asan;
struct TempAllocatorChunk @local
{
usz size;
char[*] data;
}
// This implements the temp allocator.
// The temp allocator is a specialized allocator only intended for use where
// the allocation is strictly stack-like.
//
// It is *not* thread-safe: you cannot safely use the
// temp allocator on a thread and pass it to another thread.
//
// Fundamentally the temp allocator is a thread local arena allocator
// but the stack-like behaviour puts additional constraints to it.
//
// It works great for allocating temporary data in a scope then dropping
// that data, however you should not be storing temporary data in globals
// or locals that have a lifetime outside of the current temp allocator scope.
//
// Furthermore, note that the temp allocator is bounded, with additional
// allocations on top of that causing heap allocations. Such heap allocations
// will be cleaned up but is undesirable from a performance standpoint.
//
// If you want customizable behaviour similar to the temp allocator, consider
// the ArenaAllocator, BackedArenaAllocator or the DynamicArenaAllocator.
//
// Experimenting with the temp allocator to make it work outside of its
// standard usage patterns will invariably end in tears and frustrated
// hair pulling.
//
// Use one of the ArenaAllocators instead.
struct TempAllocator (Allocator)
{
@@ -21,8 +41,13 @@ struct TempAllocator (Allocator)
char[*] data;
}
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
struct TempAllocatorChunk @local
{
usz size;
char[*] data;
}
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
struct TempAllocatorPage
{
@@ -82,6 +107,9 @@ fn TempAllocator*? TempAllocator.derive_allocator(&self, usz min_size, usz buffe
return temp;
}
<*
Reset the entire temp allocator, which will merge all the children into it.
*>
fn void TempAllocator.reset(&self)
{
TempAllocator* child = self.derived;

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
@@ -18,6 +18,10 @@ alias AllocMap = HashMap { uptr, Allocation };
// A simple tracking allocator.
// It tracks allocations using a hash map but
// is not compatible with allocators that uses mark()
//
// It is also embarassingly single-threaded, so
// do not use it to track allocations that cross threads.
struct TrackingAllocator (Allocator)
{
Allocator inner_allocator;

View File

@@ -2,6 +2,25 @@ module std::core::array;
import std::core::array::slice;
<*
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"
*>
macro bool contains(array, element)
{
foreach (&item : array)
{
if (*item == element) return true;
}
return false;
}
<*
Return the first index of element found in the array, searching from the start.
@param [in] array
@param [in] element
@return "the first index of the element"
@@ -17,6 +36,14 @@ macro index_of(array, element)
}
<*
Slice a 2d array and create a Slice2d from it.
@param array_ptr : "the pointer to create a slice from"
@param x : "The starting position of the slice x, optional"
@param y : "The starting position of the slice y, optional"
@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
@@ -26,11 +53,13 @@ macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
if (xlen < 1) xlen = $typeof((*array_ptr)[0]).len + xlen;
if (ylen < 1) ylen = $typeof((*array_ptr)).len + ylen;
var $ElementType = $typeof((*array_ptr)[0][0]);
return Slice2d{$ElementType} { ($ElementType*)array_ptr, $typeof((*array_ptr)[0]).len, y, ylen, x, xlen };
return (Slice2d{$ElementType}) { ($ElementType*)array_ptr, $typeof((*array_ptr)[0]).len, y, ylen, x, xlen };
}
<*
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"
@@ -81,97 +110,4 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
@require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
@ensure return.len == arr1.len + arr2.len
*>
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
module std::core::array::slice{Type};
struct Slice2d
{
Type* ptr;
usz inner_len;
usz ystart;
usz ylen;
usz xstart;
usz xlen;
}
fn usz Slice2d.len(&self) @operator(len)
{
return self.ylen;
}
fn usz Slice2d.count(&self)
{
return self.ylen * self.xlen;
}
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
{
foreach (y, line : *self)
{
foreach (x, val : line)
{
@body({ x, y }, val);
}
}
}
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
{
foreach (y, line : *self)
{
foreach (x, &val : line)
{
@body({ x, y }, val);
}
}
}
<*
@require idy >= 0 && idy < self.ylen
*>
macro Type[] Slice2d.get_row(self, usz idy) @operator([])
{
return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen];
}
macro Type Slice2d.get_coord(self, usz[<2>] coord)
{
return *self.get_coord_ref(coord);
}
macro Type Slice2d.get_xy(self, x, y)
{
return *self.get_xy_ref(x, y);
}
macro Type* Slice2d.get_xy_ref(self, x, y)
{
return self.ptr + self.inner_len * (y + self.ystart) + self.xstart + x;
}
macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord)
{
return self.get_xy_ref(coord.x, coord.y);
}
macro void Slice2d.set_coord(self, usz[<2>] coord, Type value)
{
*self.get_coord_ref(coord) = value;
}
macro void Slice2d.set_xy(self, x, y, Type value)
{
*self.get_xy_ref(x, y) = value;
}
<*
@require y >= 0 && y < self.ylen
@require x >= 0 && x < self.xlen
*>
fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0)
{
if (xlen < 1) xlen = self.xlen + xlen;
if (ylen < 1) ylen = self.ylen + ylen;
return { self.ptr, self.inner_len, y + self.ystart, ylen, x + self.xstart, xlen };
}
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);

114
lib/std/core/ascii.c3 Normal file
View File

@@ -0,0 +1,114 @@
<*
This module contains utils for handling ASCII characters. They only operate on
characters corresponding to 0-127.
*>
module std::core::ascii;
macro bool @is_lower(c) => ASCII_LOOKUP[c].lower; // Is a-z
macro bool @is_upper(c) => ASCII_LOOKUP[c].upper; // Is A-Z
macro bool @is_digit(c) => ASCII_LOOKUP[c].digit; // Is 0-9
macro bool @is_bdigit(c) => ASCII_LOOKUP[c].bin_digit; // Is 0-1
macro bool @is_odigit(c) => ASCII_LOOKUP[c].oct_digit; // Is 0-7
macro bool @is_xdigit(c) => ASCII_LOOKUP[c].hex_digit; // Is 0-9 or a-f or A-F
macro bool @is_alpha(c) => ASCII_LOOKUP[c].alpha; // Is a-z or A-Z
macro bool @is_print(c) => ASCII_LOOKUP[c].printable; // Is a printable character (space or higher and < 127
macro bool @is_graph(c) => ASCII_LOOKUP[c].graph; // Does it show any graphics (printable but not space)
macro bool @is_space(c) => ASCII_LOOKUP[c].space; // Is it a space character: space, tab, linefeed etc
macro bool @is_alnum(c) => ASCII_LOOKUP[c].alphanum; // Is it alpha or digit
macro bool @is_punct(c) => ASCII_LOOKUP[c].punct; // Is it "graph" but not digit or letter
macro bool @is_blank(c) => ASCII_LOOKUP[c].blank; // Is it a blank space: space or tab
macro bool @is_cntrl(c) => ASCII_LOOKUP[c].control; // Is it a control character: before space or 127
macro char @to_lower(c) => c + TO_LOWER[c]; // Convert A-Z to a-z if found
macro char @to_upper(c) => c - TO_UPPER[c]; // Convert a-z to A-Z if found
fn bool is_lower(char c) => @is_lower(c); // Is a-z
fn bool is_upper(char c) => @is_upper(c); // Is A-Z
fn bool is_digit(char c) => @is_digit(c); // Is 0-9
fn bool is_bdigit(char c) => @is_bdigit(c); // Is 0-1
fn bool is_odigit(char c) => @is_odigit(c); // Is 0-7
fn bool is_xdigit(char c) => @is_xdigit(c); // Is 0-9 or a-f or A-F
fn bool is_alpha(char c) => @is_alpha(c); // Is a-z or A-Z
fn bool is_print(char c) => @is_print(c); // Is a printable character (space or higher and < 127
fn bool is_graph(char c) => @is_graph(c); // Does it show any graphics (printable but not space)
fn bool is_space(char c) => @is_space(c); // Is it a space character: space, tab, linefeed etc
fn bool is_alnum(char c) => @is_alnum(c); // Is it alpha or digit
fn bool is_punct(char c) => @is_punct(c); // Is it "graph" but not digit or letter
fn bool is_blank(char c) => @is_blank(c); // Is it a blank space: space or tab
fn bool is_cntrl(char c) => @is_cntrl(c); // Is it a control character: before space or 127
fn char to_lower(char c) => @to_lower(c); // Convert A-Z to a-z if found
fn char to_upper(char c) => @to_upper(c); // Convert a-z to A-Z if found
// The following methods are macro methods for the same functions
macro bool char.is_lower(char c) => @is_lower(c);
macro bool char.is_upper(char c) => @is_upper(c);
macro bool char.is_digit(char c) => @is_digit(c);
macro bool char.is_bdigit(char c) => @is_bdigit(c);
macro bool char.is_odigit(char c) => @is_odigit(c);
macro bool char.is_xdigit(char c) => @is_xdigit(c);
macro bool char.is_alpha(char c) => @is_alpha(c);
macro bool char.is_print(char c) => @is_print(c);
macro bool char.is_graph(char c) => @is_graph(c);
macro bool char.is_space(char c) => @is_space(c);
macro bool char.is_alnum(char c) => @is_alnum(c);
macro bool char.is_punct(char c) => @is_punct(c);
macro bool char.is_blank(char c) => @is_blank(c);
macro bool char.is_cntrl(char c) => @is_cntrl(c);
macro char char.to_lower(char c) => @to_lower(c);
macro char char.to_upper(char c) => @to_upper(c);
<*
Convert a-f/A-F/0-9 to the appropriate hex value.
@require c.is_xdigit()
@ensure return >= 0 && return <= 15
*>
macro char char.from_hex(char c) => HEX_VALUE[c];
<*
Bitstruct containing the different properties of a character
*>
bitstruct CharType : ushort @private
{
bool lower;
bool upper;
bool digit;
bool bin_digit;
bool hex_digit;
bool oct_digit;
bool alpha;
bool alphanum;
bool space;
bool printable;
bool blank;
bool punct;
bool control;
bool graph;
}
const CharType[256] ASCII_LOOKUP @private = {
[0..31] = { .control },
[9..13] = { .control, .space },
['\t'] = { .control, .space, .blank },
[' '] = { .space, .printable, .blank },
[33..126] = { .printable, .graph, .punct },
['0'..'9'] = { .printable, .graph, .alphanum, .hex_digit, .digit },
['2'..'7'] = { .printable, .graph, .alphanum, .hex_digit, .digit, .oct_digit },
['0'..'1'] = { .printable, .graph, .alphanum, .hex_digit, .digit, .oct_digit, .bin_digit },
['A'..'Z'] = { .printable, .graph, .alphanum, .alpha, .upper },
['A'..'F'] = { .printable, .graph, .alphanum, .alpha, .upper, .hex_digit },
['a'..'z'] = { .printable, .graph, .alphanum, .alpha, .lower },
['a'..'f'] = { .printable, .graph, .alphanum, .alpha, .lower, .hex_digit },
[127] = { .control },
};
const char[256] HEX_VALUE = {
['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4,
['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9,
['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14,
['F'] = 15, ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13,
['e'] = 14, ['f'] = 15
};
const char[256] TO_UPPER @private = { ['a'..'z'] = 'a' - 'A' };
const char[256] TO_LOWER @private = { ['A'..'Z'] = 'a' - 'A' };

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2023 Christoffer Lerno and contributors. All rights reserved.
// Copyright (c) 2023-2025 Christoffer Lerno and contributors. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::bitorder;

View File

@@ -30,6 +30,14 @@ 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);
<*
Returns a random value at compile time.
@ensure return >= 0.0 && return < 1.0
@return "A compile time random"
*>
macro @rnd() @const @builtin => $$rnd();
/*
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
*/
@@ -278,7 +286,6 @@ macro enum_by_name($Type, String enum_name) @builtin
*>
macro @enum_from_value($Type, #value, value) @builtin
{
usz elements = $Type.elements;
foreach (e : $Type.values)
{
if (e.#value == value) return e;
@@ -431,22 +438,63 @@ macro @generic_hash(value)
return h;
}
macro uint int.hash(int i) => @generic_hash(i);
macro uint uint.hash(uint i) => @generic_hash(i);
macro uint short.hash(short s) => @generic_hash(s);
macro uint ushort.hash(ushort s) => @generic_hash(s);
macro uint char.hash(char c) => @generic_hash(c);
macro uint ichar.hash(ichar c) => @generic_hash(c);
macro uint long.hash(long i) => @generic_hash(i);
macro uint ulong.hash(ulong i) => @generic_hash(i);
macro uint int128.hash(int128 i) => @generic_hash(i);
macro uint uint128.hash(uint128 i) => @generic_hash(i);
macro uint bool.hash(bool b) => @generic_hash(b);
macro uint int128.hash(self) => @generic_hash(self);
macro uint uint128.hash(self) => @generic_hash(self);
macro uint long.hash(self) => @generic_hash(self);
macro uint ulong.hash(self) => @generic_hash(self);
macro uint int.hash(self) => @generic_hash(self);
macro uint uint.hash(self) => @generic_hash(self);
macro uint short.hash(self) => @generic_hash(self);
macro uint ushort.hash(self) => @generic_hash(self);
macro uint ichar.hash(self) => @generic_hash(self);
macro uint char.hash(self) => @generic_hash(self);
macro uint bool.hash(self) => @generic_hash(self);
macro uint int128[*].hash(&self) => hash_array(self);
macro uint uint128[*].hash(&self) => hash_array(self);
macro uint long[*].hash(&self) => hash_array(self);
macro uint ulong[*].hash(&self) => hash_array(self);
macro uint int[*].hash(&self) => hash_array(self);
macro uint uint[*].hash(&self) => hash_array(self);
macro uint short[*].hash(&self) => hash_array(self);
macro uint ushort[*].hash(&self) => hash_array(self);
macro uint char[*].hash(&self) => hash_array(self);
macro uint ichar[*].hash(&self) => hash_array(self);
macro uint bool[*].hash(&self) => hash_array(self);
macro uint int128[<*>].hash(self) => hash_vec(self);
macro uint uint128[<*>].hash(self) => hash_vec(self);
macro uint long[<*>].hash(self) => hash_vec(self);
macro uint ulong[<*>].hash(self) => hash_vec(self);
macro uint int[<*>].hash(self) => hash_vec(self);
macro uint uint[<*>].hash(self) => hash_vec(self);
macro uint short[<*>].hash(self) => hash_vec(self);
macro uint ushort[<*>].hash(self) => hash_vec(self);
macro uint char[<*>].hash(self) => hash_vec(self);
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 void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
<*
@require @typekind(array_ptr) == POINTER &&& @typekind(*array_ptr) == ARRAY
*>
macro uint hash_array(array_ptr) @local
{
return (uint)fnv32a::hash(((char*)array_ptr)[:$sizeof(*array_ptr)]);
}
<*
@require @typekind(vec) == VECTOR
*>
macro uint hash_vec(vec) @local
{
return (uint)fnv32a::hash(((char*)&&vec)[:$sizeof(vec.len * $typeof(vec).inner.sizeof)]);
}
const MAX_FRAMEADDRESS = 128;
<*
@@ -731,7 +779,7 @@ macro void* get_returnaddress(int n)
}
}
module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
module std::core::builtin @if((env::LINUX || env::ANDROID || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
import libc, std::io;
fn void sig_panic(String message)

View File

@@ -1,12 +1,20 @@
module std::core::dstring;
import std::io;
<*
The DString offers a dynamic string builder.
*>
typedef DString (OutStream) = DStringOpaque*;
typedef DStringOpaque = void;
const usz MIN_CAPACITY @private = 16;
<*
Initialize the DString with a particular allocator.
@param [&inout] allocator : "The allocator to use"
@param capacity : "Starting capacity, defaults to MIN_CAPACITY and cannot be smaller"
@return "Return the DString itself"
@require !self.data() : "String already initialized"
*>
fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY)
@@ -20,6 +28,11 @@ fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY)
}
<*
Initialize the DString with the temp allocator. Note that if the dstring is never
initialized, this is the allocator it will default to.
@param capacity : "Starting capacity, defaults to MIN_CAPACITY and cannot be smaller"
@return "Return the DString itself"
@require !self.data() : "String already initialized"
*>
fn DString DString.tinit(&self, usz capacity = MIN_CAPACITY)
@@ -99,7 +112,7 @@ fn void DString.replace(&self, String needle, String replacement)
};
}
fn DString DString.concat(self, Allocator allocator, DString b)
fn DString DString.concat(self, Allocator allocator, DString b) @nodiscard
{
DString string;
string.init(allocator, self.len() + b.len());
@@ -220,7 +233,7 @@ fn usz DString.append_char32(&self, Char32 c)
fn DString DString.tcopy(&self) => self.copy(tmem);
fn DString DString.copy(self, Allocator allocator)
fn DString DString.copy(self, Allocator allocator) @nodiscard
{
if (!self) return new(allocator);
StringData* data = self.data();
@@ -229,7 +242,7 @@ fn DString DString.copy(self, Allocator allocator)
return new_string;
}
fn ZString DString.copy_zstr(self, Allocator allocator)
fn ZString DString.copy_zstr(self, Allocator allocator) @nodiscard
{
usz str_len = self.len();
if (!str_len)
@@ -243,12 +256,12 @@ fn ZString DString.copy_zstr(self, Allocator allocator)
return (ZString)zstr;
}
fn String DString.copy_str(self, Allocator allocator)
fn String DString.copy_str(self, Allocator allocator) @nodiscard
{
return (String)self.copy_zstr(allocator)[:self.len()];
}
fn String DString.tcopy_str(self) => self.copy_str(tmem) @inline;
fn String DString.tcopy_str(self) @nodiscard => self.copy_str(tmem) @inline;
fn bool DString.equals(self, DString other_string)
{
@@ -558,7 +571,7 @@ fn usz? DString.appendfn(&self, String format, args...) @maydiscard
};
}
fn DString join(Allocator allocator, String[] s, String joiner)
fn DString join(Allocator allocator, String[] s, String joiner) @nodiscard
{
if (!s.len) return new(allocator);
usz total_size = joiner.len * s.len;
@@ -568,10 +581,10 @@ fn DString join(Allocator allocator, String[] s, String joiner)
}
DString res = new_with_capacity(allocator, total_size);
res.append(s[0]);
foreach (String* &str : s[1..])
foreach (String str : s[1..])
{
res.append(joiner);
res.append(*str);
res.append(str);
}
return res;
}

View File

@@ -120,6 +120,7 @@ const String COMPILER_BUILD_HASH = $$BUILD_HASH;
const String COMPILER_BUILD_DATE = $$BUILD_DATE;
const OsType OS_TYPE = OsType.from_ordinal($$OS_TYPE);
const ArchType ARCH_TYPE = ArchType.from_ordinal($$ARCH_TYPE);
const usz MAX_VECTOR_SIZE = $$MAX_VECTOR_SIZE;
const bool ARCH_32_BIT = $$REGISTER_SIZE == 32;
const bool ARCH_64_BIT = $$REGISTER_SIZE == 64;
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;

View File

@@ -342,7 +342,7 @@ macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig
@param $src_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@require src != null || len == 0 : "Copying a null with non-zero length is invalid"
@require src != null || $len == 0 : "Copying a null with non-zero length is invalid"
@require $len == 0 || dst + $len <= src || src + $len <= dst : "Ranges may not overlap"
*>
macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
@@ -627,14 +627,36 @@ macro TrackingEnv* get_tracking_env()
$endif
}
<*
@param value : "The value to clone"
@return "A pointer to the cloned value"
@require $alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'clone_aligned' instead"
*>
macro @clone(value) @builtin @nodiscard
{
return allocator::clone(mem, value);
}
<*
@param value : "The value to clone"
@return "A pointer to the cloned value, which must be released using free_aligned"
*>
macro @clone_aligned(value) @builtin @nodiscard
{
return allocator::clone_aligned(mem, value);
}
<*
@param value : "The value to clone"
@return "A pointer to the cloned value"
*>
macro @tclone(value) @builtin @nodiscard
{
return tnew($typeof(value), value);
$if $alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT:
return tnew($typeof(value), value);
$else
return allocator::clone_aligned(tmem, value);
$endif
}
fn void* malloc(usz size) @builtin @inline @nodiscard
@@ -738,9 +760,9 @@ macro alloc_aligned($Type) @nodiscard
macro tnew($Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof) @inline;
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
$else
$Type* val = tmalloc($Type.sizeof) @inline;
$Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
*val = $vaexpr[0];
return val;
$endif
@@ -753,9 +775,9 @@ macro tnew($Type, ...) @nodiscard
macro temp_with_padding($Type, usz padding, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof + padding) @inline;
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
$else
$Type* val = tmalloc($Type.sizeof + padding) @inline;
$Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline;
*val = $vaexpr[0];
return val;
$endif
@@ -763,12 +785,12 @@ macro temp_with_padding($Type, usz padding, ...) @nodiscard
macro talloc($Type) @nodiscard
{
return tmalloc($Type.sizeof);
return tmalloc($Type.sizeof, $Type.alignof);
}
macro talloc_with_padding($Type, usz padding) @nodiscard
{
return tmalloc($Type.sizeof + padding);
return tmalloc($Type.sizeof + padding, $Type.alignof);
}
<*

View File

@@ -1,6 +1,18 @@
module std::core::mem::allocator;
import std::math;
// C3 has multiple different allocators available:
//
// Name Arena Uses buffer OOM Fallback? Mark? Reset?
// ArenaAllocator Yes Yes No Yes Yes
// BackedArenaAllocator Yes No Yes Yes Yes
// DynamicArenaAllocator Yes No Yes No Yes
// HeapAllocator No No No No No *Note: Not for normal use
// LibcAllocator No No No No No *Note: Wraps malloc
// OnStackAllocator Yes Yes Yes No No *Note: Used by @stack_mem
// TempAllocator Yes No Yes No* No* *Note: Mark/reset using @pool
// TrackingAllocator No No N/A No No *Note: Wraps other heap allocator
const DEFAULT_SIZE_PREFIX = usz.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
@@ -20,13 +32,18 @@ enum AllocInitType
interface Allocator
{
<*
Acquire memory from the allocator, with the given alignment and initialization type.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require size > 0
@require size > 0 : "The size must be 1 or more"
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? acquire(usz size, AllocInitType init_type, usz alignment = 0);
<*
Resize acquired memory from the allocator, with the given new size and alignment.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require ptr != null
@@ -34,8 +51,11 @@ interface Allocator
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? resize(void* ptr, usz new_size, usz alignment = 0);
<*
@require ptr != null
Release memory acquired using `acquire` or `resize`.
@require ptr != null : "Empty pointers should never be released"
*>
fn void release(void* ptr, bool aligned);
}
@@ -283,11 +303,31 @@ macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
}
<*
Clone a value.
@param [&inout] allocator : "The allocator to use to clone"
@param value : "The value to clone"
@return "A pointer to the cloned value"
@require $alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'clone_aligned' instead"
*>
macro clone(Allocator allocator, value) @nodiscard
{
return new(allocator, $typeof(value), value);
}
<*
Clone overaligned values. Must be released using free_aligned.
@param [&inout] allocator : "The allocator to use to clone"
@param value : "The value to clone"
@return "A pointer to the cloned value"
*>
macro clone_aligned(Allocator allocator, value) @nodiscard
{
return new_aligned(allocator, $typeof(value), value)!!;
}
fn any clone_any(Allocator allocator, any value) @nodiscard
{
usz size = value.type.sizeof;
@@ -435,7 +475,7 @@ fn Allocator create_temp_allocator_on_demand() @private
auto_create_temp = true;
abort("Use '@pool_init()' to enable the temp allocator on a new thread. A temp allocator is only implicitly created on the main thread.");
}
return create_temp_allocator(base_allocator(), temp_allocator_size());
return create_temp_allocator(temp_base_allocator, temp_allocator_size());
}
<*
@require !top_temp : "This should never be called when temp already exists"
@@ -452,7 +492,7 @@ macro Allocator temp()
alias tmem @builtin = current_temp;
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC)
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::WASM_NOLIBC)
{
auto_create_temp = true;
}

View File

@@ -143,7 +143,7 @@ uint128 x86_features;
fn void add_feature_if_bit(X86Feature feature, uint register, int bit)
{
if (register & 1U << bit) x86_features |= 1u128 << feature.ordinal;
if (register & 1U << bit) x86_features |= 1ULL << feature.ordinal;
}
fn void x86_initialize_cpu_features()

View File

@@ -22,6 +22,14 @@ struct SliceRaw
usz len;
}
macro @enum_lookup($Type, #value, value)
{
$foreach $val : $Type.values:
if ($val.#value == value) return $val;
$endforeach
return NOT_FOUND?;
}
module std::core::runtime @if(WASM_NOLIBC);

169
lib/std/core/slice2d.c3 Normal file
View File

@@ -0,0 +1,169 @@
module std::core::array::slice {Type};
<*
A slice2d allows slicing an array like int[10][10] into an arbitrary "int[][]"-like counterpart
Typically you'd use array::slice2d(...) to create one.
*>
struct Slice2d
{
Type* ptr;
usz inner_len;
usz ystart;
usz ylen;
usz xstart;
usz xlen;
}
<*
@return `The length of the "outer" slice`
*>
fn usz Slice2d.len(&self) @operator(len)
{
return self.ylen;
}
<*
@return `The total number of elements.`
*>
fn usz Slice2d.count(&self)
{
return self.ylen * self.xlen;
}
<*
Step through each element of the slice.
*>
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
{
foreach (y, line : *self)
{
foreach (x, val : line)
{
@body({ x, y }, val);
}
}
}
<*
Step through each element of the slice *by reference*
*>
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
{
foreach (y, line : *self)
{
foreach (x, &val : line)
{
@body({ x, y }, val);
}
}
}
<*
Return a row as a slice.
@param idy : "The row to return"
@return "The slice for the particular row"
@require idy >= 0 && idy < self.ylen
*>
macro Type[] Slice2d.get_row(self, usz idy) @operator([])
{
return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen];
}
<*
Get the value at a particular x/y position in the slice.
@param coord : "The xy coordinate"
@return "The value at that coordinate"
@require coord.y >= 0 && coord.y < self.ylen : "y value out of range"
@require coord.x >= 0 && coord.x < self.xlen : "x value out of range"
*>
macro Type Slice2d.get_coord(self, usz[<2>] coord)
{
return *self.get_coord_ref(coord);
}
<*
Get a pointer to the value at a particular x/y position in the slice.
@param coord : "The xy coordinate"
@return "A pointer to the value at that coordinate"
@require coord.y >= 0 && coord.y < self.ylen : "y value out of range"
@require coord.x >= 0 && coord.x < self.xlen : "x value out of range"
*>
macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord)
{
return self.get_xy_ref(coord.x, coord.y);
}
<*
Get the value at a particular x/y position in the slice.
@param x : "The x coordinate"
@param y : "The x coordinate"
@return "The value at that coordinate"
@require y >= 0 && y < self.ylen : "y value out of range"
@require x >= 0 && x < self.xlen : "x value out of range"
*>
macro Type Slice2d.get_xy(self, x, y)
{
return *self.get_xy_ref(x, y);
}
<*
Get the value at a particular x/y position in the slice by reference.
@param x : "The x coordinate"
@param y : "The y coordinate"
@return "A pointer to the value at that coordinate"
@require y >= 0 && y < self.ylen : "y value out of range"
@require x >= 0 && x < self.xlen : "x value out of range"
*>
macro Type* Slice2d.get_xy_ref(self, x, y)
{
return self.ptr + self.inner_len * (y + self.ystart) + self.xstart + x;
}
<*
Set the ´value at a particular x/y position in the slice.
@param coord : "The xy coordinate"
@param value : "The new value"
@require coord.y >= 0 && coord.y < self.ylen : "y value out of range"
@require coord.x >= 0 && coord.x < self.xlen : "x value out of range"
*>
macro void Slice2d.set_coord(self, usz[<2>] coord, Type value)
{
*self.get_coord_ref(coord) = value;
}
<*
Set the value at a particular x/y position in the slice.
@param x : "The x coordinate"
@param y : "The y coordinate"
@param value : "The new value"
@require y >= 0 && y < self.ylen : "y value out of range"
@require x >= 0 && x < self.xlen : "x value out of range"
*>
macro void Slice2d.set_xy(self, x, y, Type value)
{
*self.get_xy_ref(x, y) = value;
}
<*
Reslice a slice2d returning a new slice.
@param x : "The starting x"
@param xlen : "The length along x"
@param y : "The starting y"
@param ylen : "The length along y"
@require y >= 0 && y < self.ylen
@require x >= 0 && x < self.xlen
*>
fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0)
{
if (xlen < 1) xlen = self.xlen + xlen;
if (ylen < 1) ylen = self.ylen + ylen;
return { self.ptr, self.inner_len, y + self.ystart, ylen, x + self.xstart, xlen };
}

View File

@@ -1,41 +1,78 @@
module std::core::string;
import std::ascii;
import std::io;
typedef String @if(!$defined(String)) = inline char[];
<*
ZString is a pointer to a zero terminated array of chars.
Use ZString when you need to interop with C zero terminated strings.
*>
typedef ZString = inline char*;
<*
WString is a pointer to a zero terminated array of Char16.
Depending on the platform, this may or may not correspond to wchar_t.
For Windows, wchar_t is generally 16 bits, on MacOS it is 32 bits.
However, for both MacOS and Linux, regular C strings (ZString)
will be UTF-8 encoded, so there is no need to use the wchar_t versions
of functions outside of encoding functions.
*>
typedef WString = inline Char16*;
<*
Char32 is a UTF32 codepoint
*>
alias Char32 = uint;
<*
Char16 is a UTF16 "character"
*>
alias Char16 = ushort;
<*
Common faults used with strings
*>
faultdef INVALID_UTF8, INVALID_UTF16, CONVERSION_FAILED,
EMPTY_STRING, NEGATIVE_VALUE, MALFORMED_INTEGER,
INTEGER_OVERFLOW, MALFORMED_FLOAT, FLOAT_OUT_OF_RANGE;
const uint SURROGATE_OFFSET @private = 0x10000;
const uint SURROGATE_GENERIC_MASK @private = 0xF800;
const uint SURROGATE_MASK @private = 0xFC00;
const uint SURROGATE_CODEPOINT_MASK @private = 0x03FF;
const uint SURROGATE_BITS @private = 10;
const uint SURROGATE_LOW_VALUE @private = 0xDC00;
const uint SURROGATE_HIGH_VALUE @private = 0xD800;
<*
Create a pointer to an UTF32 encoded string at compile time.
@param $string : "The string to encode"
*>
macro Char32* @wstring32(String $string) @builtin
{
return (Char32*)&&$$wstr32($string);
}
<*
Create a slice of an UTF32 encoded string at compile time.
@param $string : "The string to encode"
*>
macro Char32[] @char32(String $string) @builtin
{
return $$wstr32($string)[..^2];
}
<*
Create a WString (an UTF16 encoded string) at compile time.
@param $string : "The string to encode"
*>
macro WString @wstring(String $string) @builtin
{
return (WString)&&$$wstr16($string);
}
<*
Create a slice of an UTF32 encoded string at compile time.
@param $string : "The string to encode"
*>
macro Char16[] @char16(String $string) @builtin
{
return $$wstr16($string)[..^2];
@@ -117,6 +154,39 @@ fn String join(Allocator allocator, String[] s, String joiner)
};
}
<*
Replace all instances of one substring with a different string.
@param [in] self
@param [in] needle : `The string to be replaced`
@param [in] new_str : `The replacement string`
@param [&inout] allocator : `The allocator to use for the String`
@return "The new string with the elements replaced"
*>
fn String String.replace(self, Allocator allocator, String needle, String new_str) @nodiscard
{
@pool()
{
String[] split = self.tsplit(needle);
return dstring::join(tmem, split, new_str).copy_str(mem);
};
}
<*
Replace all instances of one substring with a different string, allocating the new string on the temp allocator.
@param [in] self
@param [in] needle : `The string to be replaced`
@param [in] new_str : `The replacement string`
@return "The new string with the elements replaced"
*>
fn String String.treplace(self, String needle, String new_str)
{
String[] split = self.tsplit(needle);
return dstring::join(tmem, split, new_str).str_view();
}
<*
Remove characters from the front and end of a string.
@@ -231,7 +301,7 @@ fn String String.strip_end(self, String suffix)
@param [&inout] allocator : "The allocator to use for the String[]"
@require delimiter.len > 0 : "The delimiter must be at least 1 character long"
@ensure return.len > 0
@ensure return.len > 0 || skip_empty
*>
fn String[] String.split(self, Allocator allocator, String delimiter, usz max = 0, bool skip_empty = false)
{
@@ -290,7 +360,7 @@ faultdef BUFFER_EXCEEDED;
@param [inout] buffer
@param max : "Max number of elements, 0 means no limit, defaults to 0"
@require delimiter.len > 0 : "The delimiter must be at least 1 character long"
@ensure return.len > 0
@ensure return.len > 0 || skip_empty
@return? BUFFER_EXCEEDED : `If there are more elements than would fit the buffer`
*>
fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz max = 0, bool skip_empty = false)
@@ -338,6 +408,38 @@ fn bool String.contains(s, String substr)
return @ok(s.index_of(substr));
}
<*
Check how many non-overlapping instances of a substring there is.
If the substring has zero length, the number of matches is zero.
@param [in] self : "The string to search"
@param [in] substr : "The string to look for."
@pure
@return "The number of times matched"
*>
fn usz String.count(self, String substr)
{
usz count = 0;
usz needed = substr.len;
if (needed == 0) return 0;
char first = substr[0];
while OUTER: (self.len >= needed)
{
foreach (i, c: self[..^needed])
{
if (c == first && self[i : needed] == substr)
{
count++;
self = self[i + needed..];
continue OUTER;
}
}
break;
}
return count;
}
<*
Find the index of the first incidence of a string.
@@ -692,16 +794,64 @@ fn usz String.utf8_codepoints(s)
return len;
}
<*
Determine whether the current string actually points to a ZString-like string.
This is done by looking at the byte one step after the end of the string. If this
is zero, it is considered zero terminated.
This function can safely be used with data pointing to null. However, it will not
work correctly if the pointer is invalid, for example it is already freed.
*>
fn bool String.is_zstr(self)
{
return self.ptr && *(self.ptr + self.len) == 0;
}
<*
@require (base <= 10 && base > 1) || base == 16 : "Unsupported base"
Return a pointer to the string *iff* it is a pointer
to a zero terminated string, otherwise return a temp allocated zstring copy.
This function is suitable if you are converting strings to ZString on the temp
allocator, but suspect that the String might actually already point to zero
terminated data.
The function looks one step beyond the end of the slice to determine this,
which means that if that data is then modified after this call, this function
might behave incorrectly.
For this reason, try to ensure that the resulting ZString is immediately used.
@ensure return[self.len] == 0
*>
fn ZString String.quick_zstr(self)
{
if (self.is_zstr() @inline) return (ZString)self.ptr;
return self.zstr_tcopy();
}
<*
Convert a number to a given base. If the base is not given, then
it will be inferred from the number if the string starts with 0x 0o or 0b and the
base is given as 10.
Furthermore it will skip any spaces before and after the number.
@param $Type : "The type to convert to"
@param base : "The base to convert to"
@require base > 0 && base <= 16 : "Unsupported base"
@return? MALFORMED_INTEGER : "When the value has some illegal character"
@return? INTEGER_OVERFLOW : "If the value does not fit in the given type"
@return? EMPTY_STRING : "If the string was empty"
@return? NEGATIVE_VALUE : "If the type was unsigned, and the value had a - prefix"
*>
macro String.to_integer(self, $Type, int base = 10)
{
usz len = self.len;
usz index = 0;
char* ptr = self.ptr;
while (index < len && ascii::is_blank_m(ptr[index])) index++;
while (index < len && ptr[index].is_blank()) index++;
if (len == index) return EMPTY_STRING?;
bool is_negative;
switch (self[index])
@@ -746,7 +896,7 @@ macro String.to_integer(self, $Type, int base = 10)
char c = self[index++];
switch
{
case base_used != 16 || c < 'A': c -= '0';
case base_used < 10 || c < 'A': c -= '0';
case c <= 'F': c -= 'A' - 10;
case c < 'a' || c > 'f': return MALFORMED_INTEGER?;
default: c -= 'a' - 10;
@@ -784,22 +934,91 @@ fn char? String.to_uchar(self, int base = 10) => self.to_integer(char, base);
fn double? String.to_double(self) => self.to_real(double);
fn float? String.to_float(self) => self.to_real(float);
fn Splitter String.splitter(self, String split)
{
return { .string = self, .split = split };
}
<*
Create a Splitter to track tokenizing of a string. Tokenize will turn "foo:bar::baz" into
"foo", "bar" and "baz", if you want the empty string to be present, use `tokenize_all`
instead.
@param [in] split : "The string to use for splitting"
@return "A Splitter to track the state"
*>
fn Splitter String.tokenize(self, String split)
{
return { .string = self, .split = split, .tokenize = true };
return { .string = self, .split = split, .type = TOKENIZE };
}
<*
Create a Splitter to track tokenizing of a string. Tokenize will turn "foo:bar::baz" into
"foo", "bar" and "baz", if you want the empty string to be present, use `tokenize_all`
instead.
@param [in] split : "The string to use for splitting"
@param skip_last : "Set to true to not include the last empty token if present (default: false)"
@return "A Splitter to track the state"
*>
fn Splitter String.tokenize_all(self, String split, bool skip_last = false)
{
return {
.string = self,
.split = split,
.type = skip_last ? TOKENIZE_ALL_SKIP_LAST : TOKENIZE_ALL
};
}
fn Splitter String.splitter(self, String split) @deprecated("Use tokenize_all instead")
{
return self.tokenize_all(split, skip_last: true);
}
<*
This macro will create a string description of a struct.
@param [&inout] allocator : "The allocator to use"
@param x : "The struct to create a description of"
*>
macro String from_struct(Allocator allocator, x)
{
DString s;
@stack_mem(512; Allocator mem)
{
s.init(allocator: mem);
io::fprint(&s, x)!!;
return s.copy_str(allocator);
};
}
<*
This macro will create a temporary string description of a struct.
@param x : "The struct to create a description of"
*>
macro String tfrom_struct(x) => from_struct(tmem, x);
const uint SURROGATE_OFFSET @private = 0x10000;
const uint SURROGATE_GENERIC_MASK @private = 0xF800;
const uint SURROGATE_MASK @private = 0xFC00;
const uint SURROGATE_CODEPOINT_MASK @private = 0x03FF;
const uint SURROGATE_BITS @private = 10;
const uint SURROGATE_LOW_VALUE @private = 0xDC00;
const uint SURROGATE_HIGH_VALUE @private = 0xD800;
enum SplitterType
{
TOKENIZE,
TOKENIZE_ALL,
TOKENIZE_ALL_SKIP_LAST
}
<*
Splitter is handles tokenizing strings.
*>
struct Splitter
{
String string;
String split;
usz current;
bool tokenize;
SplitterType type;
int last_index;
}
@@ -814,29 +1033,22 @@ fn String? Splitter.next(&self)
{
usz len = self.string.len;
usz current = self.current;
if (current >= len) return NO_MORE_ELEMENT?;
if (current > len) return NO_MORE_ELEMENT?;
if (current == len)
{
if (self.type != TOKENIZE_ALL) return NO_MORE_ELEMENT?;
self.current++;
return self.string[current - 1:0];
}
String remaining = self.string[current..];
usz? next = remaining.index_of(self.split);
if (try next)
{
self.current = current + next + self.split.len;
if (!next && self.tokenize) continue;
if (!next && self.type == TOKENIZE) continue;
return remaining[:next];
}
self.current = len;
return remaining;
}
}
macro String from_struct(Allocator allocator, x)
{
DString s;
@stack_mem(512; Allocator mem)
{
s.init(allocator: mem);
io::fprint(&s, x)!!;
return s.copy_str(allocator);
};
}
macro String tfrom_struct(x) => from_struct(tmem, x);

View File

@@ -3,7 +3,6 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::encoding::json;
import std::io;
import std::ascii;
import std::collections::object;
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, DUPLICATE_MEMBERS, INVALID_NUMBER;

View File

@@ -93,7 +93,7 @@ macro @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[]
UIntBE be = { (uint)index };
hmac.update(&&bitcast(be, char[4]));
tmp = hmac.final();
out[..] = tmp;
out[..] = tmp[..];
for (int it = 1; it < iterations; it++)
{
hmac = *hmac_start;

View File

@@ -89,10 +89,10 @@ fn char[HASH_BYTES] Md5.final(&ctx)
body(ctx, &ctx.buffer, 64);
char[16] res @noinit;
res[0:4] = bitcast(ctx.a, char[4]);
res[4:4] = bitcast(ctx.b, char[4]);
res[8:4] = bitcast(ctx.c, char[4]);
res[12:4] = bitcast(ctx.d, char[4]);
res[0:4] = bitcast(ctx.a, char[4])[..];
res[4:4] = bitcast(ctx.b, char[4])[..];
res[8:4] = bitcast(ctx.c, char[4])[..];
res[12:4] = bitcast(ctx.d, char[4])[..];
*ctx = {};
return res;
}

View File

@@ -136,7 +136,7 @@ fn usz? Formatter.print_with_function(&self, Printable arg)
fn usz? Formatter.out_unknown(&self, String category, any arg) @private
{
return self.out_substr("[") + self.out_substr(category) + self.out_substr(" type:") + self.ntoa((iptr)arg.type, false, 16) + self.out_substr(", addr:") + self.ntoa((iptr)arg.ptr, false, 16) + self.out_substr("]");
return self.out_substr("<") + self.out_substr(category) + self.out_substr(" type:") + self.ntoa((iptr)arg.type, false, 16) + self.out_substr(", addr:") + self.ntoa((iptr)arg.ptr, false, 16) + self.out_substr(">");
}
fn usz? Formatter.out_str(&self, any arg) @private
{
@@ -196,7 +196,15 @@ fn usz? Formatter.out_str(&self, any arg) @private
case BITSTRUCT:
return self.out_unknown("bitstruct", arg);
case FUNC:
return self.out_unknown("function", arg);
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.width = 0;
return self.out_substr("0x")! + self.ntoa_any(arg, 16);
case DISTINCT:
if (arg.type == String.typeid)
{

View File

@@ -39,10 +39,11 @@ fn uint128? int_from_any(any arg, bool *is_neg) @private
{
switch (arg.type.kindof)
{
case TypeKind.POINTER:
case FUNC:
case POINTER:
*is_neg = false;
return (uint128)(uptr)*(void**)arg.ptr;
case TypeKind.DISTINCT:
case DISTINCT:
return int_from_any(arg.as_inner(), is_neg);
default:
break;

View File

@@ -1,9 +1,9 @@
module std::io::os;
import libc, std::os, std::io;
fn void? native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX || env::BSD_FAMILY) => @pool()
fn void? native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX || env::ANDROID || env::BSD_FAMILY) => @pool()
{
$if env::DARWIN || env::LINUX || env::BSD_FAMILY:
$if env::DARWIN || env::LINUX || env::ANDROID || env::BSD_FAMILY:
int res = libc::stat(path.zstr_tcopy(), stat);
$else
unreachable("Stat unimplemented");
@@ -69,6 +69,7 @@ fn bool native_file_or_dir_exists(String path)
$case env::NETBSD:
$case env::OPENBSD:
$case env::LINUX:
$case env::ANDROID:
Stat stat;
return @ok(native_stat(&stat, path));
$case env::WIN32:
@@ -94,6 +95,7 @@ fn bool native_is_file(String path)
$case env::NETBSD:
$case env::OPENBSD:
$case env::LINUX:
$case env::ANDROID:
Stat stat;
return @ok(native_stat(&stat, path)) && libc_S_ISTYPE(stat.st_mode, libc::S_IFREG);
$default:
@@ -105,7 +107,7 @@ fn bool native_is_file(String path)
fn bool native_is_dir(String path)
{
$if env::DARWIN || env::LINUX || env::BSD_FAMILY:
$if env::DARWIN || env::LINUX || env::ANDROID || env::BSD_FAMILY:
Stat stat;
return @ok(native_stat(&stat, path)) && libc_S_ISTYPE(stat.st_mode, libc::S_IFDIR);
$else

View File

@@ -125,6 +125,7 @@ extern fn ZString getenv(ZString name);
extern fn ZString gets(char* buffer);
extern fn Tm* gmtime(Time_t* timer);
extern fn Tm* gmtime_r(Time_t* timer, Tm* buf) @if(!env::WIN32);
extern fn CInt ioctl(CInt fd, ulong request, ...);
extern fn CInt isatty(Fd fd) @if(!env::WIN32);
extern fn CLong labs(CLong x);
extern fn LongDivResult ldiv(CLong number, CLong denom);
@@ -373,7 +374,7 @@ module libc;
alias CFile = void*;
const HAS_MALLOC_SIZE = env::LINUX || env::WIN32 || env::DARWIN;
const HAS_MALLOC_SIZE = env::LINUX || env::ANDROID || env::WIN32 || env::DARWIN;
// The following needs to be set per arch+os
// For now I have simply pulled the defaults from MacOS

View File

@@ -127,13 +127,13 @@ fn bool BigInt.is_negative(&self)
return self.data[MAX_LEN - 1] & 0x80000000 != 0;
}
fn BigInt BigInt.add(self, BigInt other)
fn BigInt BigInt.add(self, BigInt other) @operator(+)
{
self.add_this(other);
return self;
}
fn void BigInt.add_this(&self, BigInt other)
fn void BigInt.add_this(&self, BigInt other) @operator(+=)
{
bool sign = self.is_negative();
bool sign_arg = other.is_negative();
@@ -172,13 +172,13 @@ macro uint find_length(uint* data, uint length)
return length;
}
fn BigInt BigInt.mult(self, BigInt bi2)
fn BigInt BigInt.mult(self, BigInt bi2) @operator(*)
{
self.mult_this(bi2);
return self;
}
fn void BigInt.mult_this(&self, BigInt bi2)
fn void BigInt.mult_this(&self, BigInt bi2) @operator(*=)
{
if (bi2.is_zero())
{
@@ -270,13 +270,13 @@ fn void BigInt.negate(&self)
macro bool BigInt.is_zero(&self) => self.len == 1 && self.data[0] == 0;
fn BigInt BigInt.sub(self, BigInt other)
fn BigInt BigInt.sub(self, BigInt other) @operator(-)
{
self.sub_this(other);
return self;
}
fn BigInt* BigInt.sub_this(&self, BigInt other)
fn BigInt* BigInt.sub_this(&self, BigInt other) @operator(-=)
{
self.len = max(self.len, other.len);
@@ -325,7 +325,7 @@ fn int BigInt.bitcount(&self)
return bits;
}
fn BigInt BigInt.unary_minus(&self)
fn BigInt BigInt.unary_minus(&self) @operator(-)
{
if (self.is_zero()) return *self;
BigInt result = *self;
@@ -334,13 +334,13 @@ fn BigInt BigInt.unary_minus(&self)
}
macro BigInt BigInt.div(self, BigInt other)
macro BigInt BigInt.div(self, BigInt other) @operator(/)
{
self.div_this(other);
return self;
}
fn void BigInt.div_this(&self, BigInt other)
fn void BigInt.div_this(&self, BigInt other) @operator(/=)
{
bool negate_answer = self.is_negative();
@@ -377,13 +377,13 @@ fn void BigInt.div_this(&self, BigInt other)
*self = quotient;
}
fn BigInt BigInt.mod(self, BigInt bi2)
fn BigInt BigInt.mod(self, BigInt bi2) @operator(%)
{
self.mod_this(bi2);
return self;
}
fn void BigInt.mod_this(&self, BigInt bi2)
fn void BigInt.mod_this(&self, BigInt bi2) @operator(%=)
{
if (bi2.is_negative())
{
@@ -428,30 +428,30 @@ fn void BigInt.bit_negate_this(&self)
self.reduce_len();
}
fn BigInt BigInt.bit_negate(self)
fn BigInt BigInt.bit_negate(self) @operator(~)
{
self.bit_negate_this();
return self;
}
fn BigInt BigInt.shr(self, int shift)
fn BigInt BigInt.shr(self, int shift) @operator(>>)
{
self.shr_this(shift);
return self;
}
fn void BigInt.shr_this(self, int shift)
fn void BigInt.shr_this(self, int shift) @operator(>>=)
{
self.len = shift_right(&self.data, self.len, shift);
}
fn BigInt BigInt.shl(self, int shift)
fn BigInt BigInt.shl(self, int shift) @operator(<<)
{
self.shl_this(shift);
return self;
}
macro bool BigInt.equals(&self, BigInt other)
macro bool BigInt.equals(&self, BigInt other) @operator(==)
{
if (self.len != other.len) return false;
return self.data[:self.len] == other.data[:self.len];
@@ -764,7 +764,7 @@ fn BigInt BigInt.sqrt(&self)
return result;
}
fn BigInt BigInt.bit_and(self, BigInt bi2)
fn BigInt BigInt.bit_and(self, BigInt bi2) @operator(&)
{
self.bit_and_this(bi2);
return self;
@@ -782,7 +782,7 @@ fn void BigInt.bit_and_this(&self, BigInt bi2)
self.reduce_len();
}
fn BigInt BigInt.bit_or(self, BigInt bi2)
fn BigInt BigInt.bit_or(self, BigInt bi2) @operator(|)
{
self.bit_or_this(bi2);
return self;
@@ -800,7 +800,7 @@ fn void BigInt.bit_or_this(&self, BigInt bi2)
self.reduce_len();
}
fn BigInt BigInt.bit_xor(self, BigInt bi2)
fn BigInt BigInt.bit_xor(self, BigInt bi2) @operator(^)
{
self.bit_xor_this(bi2);
return self;
@@ -818,7 +818,7 @@ fn void BigInt.bit_xor_this(&self, BigInt bi2)
self.reduce_len();
}
fn void BigInt.shl_this(&self, int shift)
fn void BigInt.shl_this(&self, int shift) @operator(<<=)
{
self.len = shift_left(&self.data, self.len, shift);
}

66
lib/std/math/complex.c3 Normal file
View File

@@ -0,0 +1,66 @@
module std::math;
// Complex number aliases.
alias Complexf = Complex {float};
alias Complex = Complex {double};
alias COMPLEX_IDENTITY @builtin = complex::IDENTITY {double};
alias COMPLEXF_IDENTITY @builtin = complex::IDENTITY {float};
alias IMAGINARY @builtin @deprecated("Use I") = complex::IMAGINARY { double };
alias IMAGINARYF @builtin @deprecated("Use I_F") = complex::IMAGINARY { float };
alias I @builtin = complex::IMAGINARY { double };
alias I_F @builtin = complex::IMAGINARY { float };
<*
The generic complex number module, for float or double based complex number definitions.
@require Real.kindof == FLOAT : "A complex number must use a floating type"
*>
module std::math::complex {Real};
import std::io;
union Complex (Printable)
{
struct
{
Real r, c;
}
Real[<2>] v;
}
const Complex IDENTITY = { 1, 0 };
const Complex 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 = self }).div(c);
macro Complex Complex.div(self, Complex 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)
{
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;
fn usz? Complex.to_format(&self, Formatter* f) @dynamic
{
return f.printf("%g%+gi", self.r, self.c);
}

View File

@@ -6,7 +6,6 @@ import std::math::complex;
import std::math::matrix;
import std::math::quaternion;
// TODO Define these using quad precision.
const E = 2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427427466;
const LOG2E = 1.44269504088896340735992468100189214; // log2(e)
const LOG10E = 0.434294481903251827651128918916605082; // log10(e)
@@ -57,21 +56,6 @@ const DOUBLE_MAX_EXP = 1024;
const DOUBLE_MIN_EXP = -1021;
const DOUBLE_EPSILON = 2.22044604925031308085e-16;
const QUAD_MANT_DIG = 113;
/*
const QUAD_MAX = 1.18973149535723176508575932662800702e+4932;
const QUAD_MIN = 3.36210314311209350626267781732175260e-4932;
const QUAD_DENORM_MIN = 6.47517511943802511092443895822764655e-4966;
const QUAD_DIG = 33;
const QUAD_DEC_DIGITS = 36;
const QUAD_MAX_10_EXP = 4932;
const QUAD_MIN_10_EXP = -4931;
const QUAD_MAX_EXP = 16384;
const QUAD_MIN_EXP = -16481;
const QUAD_EPSILON = 1.92592994438723585305597794258492732e-34;
*/
enum RoundingMode : int
{
TOWARD_ZERO,
@@ -82,35 +66,6 @@ enum RoundingMode : int
faultdef OVERFLOW, MATRIX_INVERSE_DOESNT_EXIST;
alias Complexf = Complex {float};
alias Complex = Complex {double};
alias COMPLEX_IDENTITY @builtin = complex::IDENTITY {double};
alias COMPLEXF_IDENTITY @builtin = complex::IDENTITY {float};
alias Quaternionf = Quaternion {float};
alias Quaternion = Quaternion {double};
alias QUATERNION_IDENTITY @builtin = quaternion::IDENTITY {double};
alias QUATERNIONF_IDENTITY @builtin = quaternion::IDENTITY {float};
alias Matrix2f = Matrix2x2 {float};
alias Matrix2 = Matrix2x2 {double};
alias Matrix3f = Matrix3x3 {float};
alias Matrix3 = Matrix3x3 {double};
alias Matrix4f = Matrix4x4 {float};
alias Matrix4 = Matrix4x4 {double};
alias matrix4_ortho @builtin = matrix::ortho {double};
alias matrix4f_ortho @builtin = matrix::ortho {float};
alias matrix4_perspective @builtin = matrix::perspective {double};
alias matrix4f_perspective @builtin = matrix::perspective {float};
alias MATRIX2_IDENTITY @builtin = matrix::IDENTITY2 {double};
alias MATRIX2F_IDENTITY @builtin = matrix::IDENTITY2 {float};
alias MATRIX3_IDENTITY @builtin = matrix::IDENTITY3 {double};
alias MATRIX3F_IDENTITY @builtin = matrix::IDENTITY3 {float};
alias MATRIX4_IDENTITY @builtin = matrix::IDENTITY4 {double};
alias MATRIX4F_IDENTITY @builtin = matrix::IDENTITY4 {float};
<*
@require types::is_numerical($typeof(x)) : `The input must be a numerical value or numerical vector`
*>
@@ -129,7 +84,7 @@ macro is_approx(x, y, eps)
{
if (x == y) return true;
if (is_nan(x) || is_nan(y)) return false;
return abs(x-y) <= eps;
return abs(x - y) <= eps;
}
<*
@@ -140,7 +95,7 @@ macro is_approx_rel(x, y, eps)
{
if (x == y) return true;
if (is_nan(x) || is_nan(y)) return false;
return abs(x-y) <= eps * max(abs(x), abs(y));
return abs(x - y) <= eps * max(abs(x), abs(y));
}
<*
@@ -149,7 +104,7 @@ macro is_approx_rel(x, y, eps)
macro sign(x)
{
var $Type = $typeof(x);
$if $Type.kindof == TypeKind.UNSIGNED_INT:
$if $Type.kindof == UNSIGNED_INT:
return ($Type)(x > 0);
$else
return ($Type)(x > 0) - ($Type)(x < 0);
@@ -177,7 +132,7 @@ macro atan2(x, y)
*>
macro sincos_ref(x, sinp, cosp)
{
$if @typeid(*sinp) == float.typeid:
$if @typeis(sinp, float*.typeid):
return _sincosf(x, sinp, cosp);
$else
return _sincos(x, sinp, cosp);
@@ -192,7 +147,7 @@ macro sincos_ref(x, sinp, cosp)
*>
macro sincos(x)
{
$if @typeid(x) == float.typeid:
$if @typeis(x, float):
float[<2>] v @noinit;
_sincosf(x, &v[0], &v[1]);
$else
@@ -279,6 +234,13 @@ macro asinh(x)
*>
macro ceil(x) => $$ceil(x);
<*
Ceil for compile time evaluation.
@require @typeis($input, double) || @typeis($input, float) : "Only float and double may be used"
*>
macro @ceil($input) @const => $$ceil($input);
<*
Constrain the value to lie within the given interval.
@@ -552,7 +514,7 @@ macro bool is_finite(x)
$case float16:
return bitcast((float)x, uint) & 0x7fffffff < 0x7f800000;
$default:
return bitcast((double)x, ulong) & (~0u64 >> 1) < 0x7ffu64 << 52;
return bitcast((double)x, ulong) & (~0UL >> 1) < 0x7ffUL << 52;
$endswitch
}
@@ -566,7 +528,7 @@ macro is_nan(x)
$case float16:
return bitcast((float)x, uint) & 0x7fffffff > 0x7f800000;
$default:
return bitcast((double)x, ulong) & (~0u64 >> 1) > 0x7ffu64 << 52;
return bitcast((double)x, ulong) & (~0UL >> 1) > 0x7ffUL << 52;
$endswitch
}
@@ -580,7 +542,7 @@ macro is_inf(x)
$case float16:
return bitcast((float)x, uint) & 0x7fffffff == 0x7f800000;
$default:
return bitcast((double)x, ulong) & (~0u64 >> 1) == 0x7ffu64 << 52;
return bitcast((double)x, ulong) & (~0UL >> 1) == 0x7ffUL << 52;
$endswitch
}
@@ -1054,10 +1016,16 @@ extern fn double _atan(double x) @extern("atan");
extern fn float _atanf(float x) @extern("atanf");
extern fn double _atan2(double, double) @extern("atan2");
extern fn float _atan2f(float, float) @extern("atan2f");
extern fn void _sincos(double, double*, double*) @extern("__sincos") @link("m") @if(env::DARWIN);
extern fn void _sincosf(float, float*, float*) @extern("__sincosf") @link("m") @if(env::DARWIN);
extern fn void _sincos(double, double*, double*) @extern("sincos") @link("m") @if(!env::DARWIN);
extern fn void _sincosf(float, float*, float*) @extern("sincosf") @link("m") @if(!env::DARWIN);
extern fn void _sincos(double, double*, double*) @extern("sincos") @link("m") @if(!env::DARWIN && !env::WIN32);
extern fn void _sincosf(float, float*, float*) @extern("sincosf") @link("m") @if(!env::DARWIN && !env::WIN32);
fn void _sincos(double a, double* s, double* c) @extern("sincos") @if(env::WIN32) { *s = sin(a); *c = cos(a); }
fn void _sincosf(float a, float* s, float* c) @extern("sincosf") @if(env::WIN32) { *s = sin(a); *c = cos(a); }
extern fn double _tan(double x) @extern("tan");
extern fn float _tanf(float x) @extern("tanf");
extern fn double _scalbn(double x, int n) @extern("scalbn");
@@ -1092,8 +1060,8 @@ fn double _frexp(double x, int* e)
return x;
default:
*e = ee - 0x3fe;
i &= 0x800fffffffffffffu64;
i |= 0x3fe0000000000000u64;
i &= 0x800fffffffffffffUL;
i |= 0x3fe0000000000000UL;
return bitcast(i, double);
}
}
@@ -1118,8 +1086,8 @@ fn float _frexpf(float x, int* e)
return x;
default:
*e = ee - 0x7e;
i &= 0x807fffffu32;
i |= 0x3f000000u32;
i &= 0x807fffffU;
i |= 0x3f000000U;
return bitcast(i, float);
}
}
@@ -1145,6 +1113,33 @@ macro overflow_mul_helper(x, y) @local
return res;
}
<*
@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 values::@is_int(a) &&& values::@is_int(b) : "a and b must both be integers"
@require $defined(*out) &&& values::@is_same_type(*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 values::@is_int(a) &&& values::@is_int(b) : "a and b must both be integers"
@require $defined(*out) &&& values::@is_same_type(*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 values::@is_int(a) &&& values::@is_int(b) : "a and b must both be integers"
@require $defined(*out) &&& values::@is_same_type(*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);
<*
@require types::is_vector($Type) || ($Type.kindof == ARRAY &&& types::is_numerical($typefrom($Type.inner)))
*>

View File

@@ -1,31 +0,0 @@
module std::math::complex{Real};
union Complex
{
struct
{
Real r, c;
}
Real[<2>] v;
}
const Complex IDENTITY = { 1, 0 };
const Complex IMAGINARY = { 0, 1 };
macro Complex Complex.add(self, Complex b) => { .v = self.v + b.v };
macro Complex Complex.add_each(self, Real b) => { .v = self.v + b };
macro Complex Complex.sub(self, Complex b) => { .v = self.v - b.v };
macro Complex Complex.sub_each(self, Real b) => { .v = self.v - b };
macro Complex Complex.scale(self, Real s) => { .v = self.v * s };
macro Complex Complex.mul(self, Complex b) => { self.r * b.r - self.c * b.c, self.r * b.c + b.r * self.c };
macro Complex Complex.div(self, Complex b)
{
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)
{
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 bool Complex.equals(self, Complex b) => self.v == b.v;

View File

@@ -1,47 +0,0 @@
/* origin: FreeBSD /usr/src/lib/msun/src/s_atan.c
* ====================================================
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
*
* Developed at SunPro, a Sun Microsystems, Inc. business.
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
/* atan(x)
* Method
* 1. Reduce x to positive by atan(x) = -atan(-x).
* 2. According to the integer k=4t+0.25 chopped, t=x, the argument
* is further reduced to one of the following intervals and the
* arctangent of t is evaluated by the corresponding formula:
*
* [0,7/16] atan(x) = t-t^3*(a1+t^2*(a2+...(a10+t^2*a11)...)
* [7/16,11/16] atan(x) = atan(1/2) + atan( (t-0.5)/(1+t/2) )
* [11/16.19/16] atan(x) = atan( 1 ) + atan( (t-1)/(1+t) )
* [19/16,39/16] atan(x) = atan(3/2) + atan( (t-1.5)/(1+1.5t) )
* [39/16,INF] atan(x) = atan(INF) + atan( -1/t )
*
* Constants:
* The hexadecimal values are the intended ones for the following
* constants. The decimal values may be used, provided that the
* compiler will convert from decimal to binary accurately enough
* to produce the hexadecimal values shown.
*/
/* origin: FreeBSD /usr/src/lib/msun/src/s_atanf.c */
/*
* Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
*/
/*
* ====================================================
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
*
* Developed at SunPro, a Sun Microsystems, Inc. business.
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
module std::math;

View File

@@ -29,7 +29,7 @@ fn double _atanh(double x) @weak @extern("atanh") @nostrip
}
return double.nan;
/* x<2**-28 */
case ix < 0x3e300000 && (1e300 + x) > 0.:
case ix < 0x3e300000 && (1e300 + x) > 0.0:
return x;
}
x.set_high_word(ix);
@@ -37,11 +37,11 @@ fn double _atanh(double x) @weak @extern("atanh") @nostrip
if (ix < 0x3fe00000)
{
t = x + x;
t = 0.5 * _log1p(t + t * x / (1. - x));
t = 0.5 * _log1p(t + t * x / (1.0 - x));
}
else
{
t = 0.5 * _log1p((x + x) / (1. - x));
t = 0.5 * _log1p((x + x) / (1.0 - x));
}
return sign ? -t : t;
}

View File

@@ -47,13 +47,13 @@ fn double _exp2_specialcase(double tmp, ulong sbits, ulong ki) @private
if (ki & 0x80000000 == 0)
{
// k > 0, the exponent of scale might have overflowed by 1.
sbits -= 1u64 << 52;
sbits -= 1UL << 52;
double scale = bitcast(sbits, double);
double y = 2 * (scale + scale * tmp);
return y;
}
// k < 0, need special care in the subnormal range.
sbits += 1022u64 << 52;
sbits += 1022UL << 52;
double scale = bitcast(sbits, double);
double y = scale + scale * tmp;
if (y >= 1.0)

View File

@@ -90,7 +90,7 @@ fn double _log1p(double x) @weak @extern("log1p") @nostrip
/* correction term ~ log(1+x)-log(u), avoid underflow in c/u */
if (k < 54)
{
c = (k >= 2) ? 1. - (u - x) : x - (u - 1.);
c = (k >= 2) ? 1.0 - (u - x) : x - (u - 1.0);
c /= u;
}
else
@@ -100,10 +100,10 @@ fn double _log1p(double x) @weak @extern("log1p") @nostrip
/* reduce u into [sqrt(2)/2, sqrt(2)] */
hu = (hu & 0x000fffff) + 0x3fe6a09e;
u = bitcast(((ulong)hu << 32) | (bitcast(u, ulong) & 0xffffffff) , double);
f = u - 1.;
f = u - 1.0;
}
double hfsq = 0.5 * f * f;
double s = f / (2. + f);
double s = f / (2.0 + f);
double z = s * s;
double w = z * z;
double t1 = w * (LG2 + w * (LG4 + w * LG6));

View File

@@ -1,85 +0,0 @@
module std::math::quaternion{Real};
import std::math::vector;
union Quaternion
{
struct
{
Real i, j, k, l;
}
Real[<4>] v;
}
const Quaternion IDENTITY = { 0, 0, 0, 1 };
macro Quaternion Quaternion.add(Quaternion a, Quaternion b) => { .v = a.v + b.v };
macro Quaternion Quaternion.add_each(Quaternion a, Real b) => { .v = a.v + b };
macro Quaternion Quaternion.sub(Quaternion a, Quaternion b) => { .v = a.v - b.v };
macro Quaternion Quaternion.sub_each(Quaternion a, Real b) => { .v = a.v - b };
macro Quaternion Quaternion.scale(Quaternion a, Real s) => { .v = a.v * s };
macro Quaternion Quaternion.normalize(Quaternion q) => { .v = q.v.normalize() };
macro Real Quaternion.length(Quaternion q) => q.v.length();
macro Quaternion Quaternion.lerp(Quaternion q1, Quaternion q2, Real amount) => { .v = q1.v.lerp(q2.v, amount) };
macro Matrix4f Quaternion.to_matrixf(Quaternion* q) => into_matrix(q, Matrix4f);
macro Matrix4 Quaternion.to_matrix(Quaternion* q) => into_matrix(q, Matrix4);
fn Quaternion Quaternion.nlerp(Quaternion q1, Quaternion q2, Real amount) => { .v = q1.v.lerp(q2.v, amount).normalize() };
fn Quaternion Quaternion.invert(q)
{
Real length_sq = q.v.dot(q.v);
if (length_sq <= 0) return q;
Real inv_length = 1 / length_sq;
return { q.v[0] * -inv_length, q.v[1] * -inv_length, q.v[2] * -inv_length, q.v[3] * inv_length };
}
fn Quaternion Quaternion.slerp(q1, Quaternion q2, Real amount)
{
Quaternion result = {};
Real[<4>] q2v = q2.v;
Real cos_half_theta = q1.v.dot(q2v);
if (cos_half_theta < 0)
{
q2v = -q2v;
cos_half_theta = -cos_half_theta;
}
if (cos_half_theta >= 1) return q1;
Real[<4>] q1v = q1.v;
if (cos_half_theta > 0.95f) return { .v = q1v.lerp(q2v, amount) };
Real half_theta = math::cos(cos_half_theta);
Real sin_half_theta = math::sqrt(1 - cos_half_theta * cos_half_theta);
if (math::abs(sin_half_theta) < 0.001f)
{
return { .v = (q1v + q2v) * 0.5f };
}
Real ratio_a = math::sin((1 - amount) * half_theta) / sin_half_theta;
Real ratio_b = math::sin(amount * half_theta) / sin_half_theta;
return { .v = q1v * ratio_a + q2v * ratio_b };
}
fn Quaternion Quaternion.mul(a, Quaternion b)
{
return { a.i * b.l + a.l * b.i + a.j * b.k - a.k * b.j,
a.j * b.l + a.l * b.j + a.k * b.i - a.i * b.k,
a.k * b.l + a.l * b.k + a.i * b.j - a.j * b.i,
a.l * b.l - a.i * b.i - a.j * a.j - a.k * a.k };
}
macro into_matrix(Quaternion* q, $Type) @private
{
Quaternion rotation = q.normalize();
var x = rotation.i;
var y = rotation.j;
var z = rotation.k;
var w = rotation.l;
return ($Type) {
1 - 2*y*y - 2*z*z, 2*x*y - 2*z*w, 2*x*z + 2*y*w, 0,
2*x*y + 2*z*w, 1 - 2*x*x - 2*z*z, 2*y*z - 2*x*w, 0,
2*x*z - 2*y*w, 2*y*z + 2*x*w , 1 - 2*x*x - 2*y*y, 0,
0.0, 0.0, 0.0, 1.0,
};
}

View File

@@ -1,4 +1,32 @@
module std::math::matrix{Real};
module std::math;
// Predefined matrix types
alias Matrix2f = Matrix2x2 {float};
alias Matrix2 = Matrix2x2 {double};
alias Matrix3f = Matrix3x3 {float};
alias Matrix3 = Matrix3x3 {double};
alias Matrix4f = Matrix4x4 {float};
alias Matrix4 = Matrix4x4 {double};
// Predefined matrix functions
alias matrix4_ortho @builtin = matrix::ortho {double};
alias matrix4f_ortho @builtin = matrix::ortho {float};
alias matrix4_perspective @builtin = matrix::perspective {double};
alias matrix4f_perspective @builtin = matrix::perspective {float};
alias MATRIX2_IDENTITY @builtin = matrix::IDENTITY2 {double};
alias MATRIX2F_IDENTITY @builtin = matrix::IDENTITY2 {float};
alias MATRIX3_IDENTITY @builtin = matrix::IDENTITY3 {double};
alias MATRIX3F_IDENTITY @builtin = matrix::IDENTITY3 {float};
alias MATRIX4_IDENTITY @builtin = matrix::IDENTITY4 {double};
alias MATRIX4F_IDENTITY @builtin = matrix::IDENTITY4 {float};
<*
The generic matrix module, for float or double based matrix definitions.
@require Real.kindof == FLOAT : "A matrix must use a floating type"
*>
module std::math::matrix {Real};
import std::math::vector;
struct Matrix2x2
@@ -43,7 +71,7 @@ struct Matrix4x4
}
}
fn Real[<2>] Matrix2x2.apply(&self, Real[<2>] vec)
fn Real[<2>] Matrix2x2.apply(&self, Real[<2>] vec) @operator(*)
{
return {
self.m00 * vec[0] + self.m01 * vec[1],
@@ -51,7 +79,7 @@ fn Real[<2>] Matrix2x2.apply(&self, Real[<2>] vec)
};
}
fn Real[<3>] Matrix3x3.apply(&self, Real[<3>] vec)
fn Real[<3>] Matrix3x3.apply(&self, Real[<3>] vec) @operator(*)
{
return {
self.m00 * vec[0] + self.m01 * vec[1] + self.m02 * vec[2],
@@ -60,7 +88,7 @@ fn Real[<3>] Matrix3x3.apply(&self, Real[<3>] vec)
};
}
fn Real[<4>] Matrix4x4.apply(&self, Real[<4>] vec)
fn Real[<4>] Matrix4x4.apply(&self, Real[<4>] vec) @operator(*)
{
return {
self.m00 * vec[0] + self.m01 * vec[1] + self.m02 * vec[2] + self.m03 * vec[3],
@@ -71,7 +99,7 @@ fn Real[<4>] Matrix4x4.apply(&self, Real[<4>] vec)
}
fn Matrix2x2 Matrix2x2.mul(&self, Matrix2x2 b)
fn Matrix2x2 Matrix2x2.mul(&self, Matrix2x2 b) @operator(*)
{
return {
self.m00 * b.m00 + self.m01 * b.m10, self.m00 * b.m01 + self.m01 * b.m11,
@@ -79,7 +107,7 @@ fn Matrix2x2 Matrix2x2.mul(&self, Matrix2x2 b)
};
}
fn Matrix3x3 Matrix3x3.mul(&self, Matrix3x3 b)
fn Matrix3x3 Matrix3x3.mul(&self, Matrix3x3 b) @operator(*)
{
return {
self.m00 * b.m00 + self.m01 * b.m10 + self.m02 * b.m20,
@@ -96,28 +124,28 @@ fn Matrix3x3 Matrix3x3.mul(&self, Matrix3x3 b)
};
}
fn Matrix4x4 Matrix4x4.mul(Matrix4x4* a, Matrix4x4 b)
fn Matrix4x4 Matrix4x4.mul(Matrix4x4* self, Matrix4x4 b) @operator(*)
{
return {
a.m00 * b.m00 + a.m01 * b.m10 + a.m02 * b.m20 + a.m03 * b.m30,
a.m00 * b.m01 + a.m01 * b.m11 + a.m02 * b.m21 + a.m03 * b.m31,
a.m00 * b.m02 + a.m01 * b.m12 + a.m02 * b.m22 + a.m03 * b.m32,
a.m00 * b.m03 + a.m01 * b.m13 + a.m02 * b.m23 + a.m03 * b.m33,
self.m00 * b.m00 + self.m01 * b.m10 + self.m02 * b.m20 + self.m03 * b.m30,
self.m00 * b.m01 + self.m01 * b.m11 + self.m02 * b.m21 + self.m03 * b.m31,
self.m00 * b.m02 + self.m01 * b.m12 + self.m02 * b.m22 + self.m03 * b.m32,
self.m00 * b.m03 + self.m01 * b.m13 + self.m02 * b.m23 + self.m03 * b.m33,
a.m10 * b.m00 + a.m11 * b.m10 + a.m12 * b.m20 + a.m13 * b.m30,
a.m10 * b.m01 + a.m11 * b.m11 + a.m12 * b.m21 + a.m13 * b.m31,
a.m10 * b.m02 + a.m11 * b.m12 + a.m12 * b.m22 + a.m13 * b.m32,
a.m10 * b.m03 + a.m11 * b.m13 + a.m12 * b.m23 + a.m13 * b.m33,
self.m10 * b.m00 + self.m11 * b.m10 + self.m12 * b.m20 + self.m13 * b.m30,
self.m10 * b.m01 + self.m11 * b.m11 + self.m12 * b.m21 + self.m13 * b.m31,
self.m10 * b.m02 + self.m11 * b.m12 + self.m12 * b.m22 + self.m13 * b.m32,
self.m10 * b.m03 + self.m11 * b.m13 + self.m12 * b.m23 + self.m13 * b.m33,
a.m20 * b.m00 + a.m21 * b.m10 + a.m22 * b.m20 + a.m23 * b.m30,
a.m20 * b.m01 + a.m21 * b.m11 + a.m22 * b.m21 + a.m23 * b.m31,
a.m20 * b.m02 + a.m21 * b.m12 + a.m22 * b.m22 + a.m23 * b.m32,
a.m20 * b.m03 + a.m21 * b.m13 + a.m22 * b.m23 + a.m23 * b.m33,
self.m20 * b.m00 + self.m21 * b.m10 + self.m22 * b.m20 + self.m23 * b.m30,
self.m20 * b.m01 + self.m21 * b.m11 + self.m22 * b.m21 + self.m23 * b.m31,
self.m20 * b.m02 + self.m21 * b.m12 + self.m22 * b.m22 + self.m23 * b.m32,
self.m20 * b.m03 + self.m21 * b.m13 + self.m22 * b.m23 + self.m23 * b.m33,
a.m30 * b.m00 + a.m31 * b.m10 + a.m32 * b.m20 + a.m33 * b.m30,
a.m30 * b.m01 + a.m31 * b.m11 + a.m32 * b.m21 + a.m33 * b.m31,
a.m30 * b.m02 + a.m31 * b.m12 + a.m32 * b.m22 + a.m33 * b.m32,
a.m30 * b.m03 + a.m31 * b.m13 + a.m32 * b.m23 + a.m33 * b.m33,
self.m30 * b.m00 + self.m31 * b.m10 + self.m32 * b.m20 + self.m33 * b.m30,
self.m30 * b.m01 + self.m31 * b.m11 + self.m32 * b.m21 + self.m33 * b.m31,
self.m30 * b.m02 + self.m31 * b.m12 + self.m32 * b.m22 + self.m33 * b.m32,
self.m30 * b.m03 + self.m31 * b.m13 + self.m32 * b.m23 + self.m33 * b.m33,
};
}
@@ -125,13 +153,25 @@ fn Matrix2x2 Matrix2x2.component_mul(&self, Real s) => matrix_component_mul(self
fn Matrix3x3 Matrix3x3.component_mul(&self, Real s) => matrix_component_mul(self, s);
fn Matrix4x4 Matrix4x4.component_mul(&self, Real s) => matrix_component_mul(self, s);
fn Matrix2x2 Matrix2x2.add(&self, Matrix2x2 mat2) => matrix_add(self, mat2);
fn Matrix3x3 Matrix3x3.add(&self, Matrix3x3 mat2) => matrix_add(self, mat2);
fn Matrix4x4 Matrix4x4.add(&self, Matrix4x4 mat2) => matrix_add(self, mat2);
fn Matrix2x2 Matrix2x2.add(&self, Matrix2x2 mat2) @operator(+) => matrix_add(self, mat2);
fn Matrix3x3 Matrix3x3.add(&self, Matrix3x3 mat2) @operator(+) => matrix_add(self, mat2);
fn Matrix4x4 Matrix4x4.add(&self, Matrix4x4 mat2) @operator(+) => matrix_add(self, mat2);
fn Matrix2x2 Matrix2x2.sub(&self, Matrix2x2 mat2) => matrix_sub(self, mat2);
fn Matrix3x3 Matrix3x3.sub(&self, Matrix3x3 mat2) => matrix_sub(self, mat2);
fn Matrix4x4 Matrix4x4.sub(&self, Matrix4x4 mat2) => matrix_sub(self, mat2);
fn Matrix2x2 Matrix2x2.sub(&self, Matrix2x2 mat2) @operator(-) => matrix_sub(self, mat2);
fn Matrix3x3 Matrix3x3.sub(&self, Matrix3x3 mat2) @operator(-) => matrix_sub(self, mat2);
fn Matrix4x4 Matrix4x4.sub(&self, Matrix4x4 mat2) @operator(-) => matrix_sub(self, mat2);
fn Matrix2x2 Matrix2x2.negate(&self) @operator(-) => { .m = (Real[<4>])self.m };
fn Matrix3x3 Matrix3x3.negate(&self) @operator(-) => { .m = (Real[<9>])self.m };
fn Matrix4x4 Matrix4x4.negate(&self) @operator(-) => { .m = (Real[<16>])self.m };
fn bool Matrix2x2.eq(&self, Matrix2x2 mat2) @operator(==) => (Real[<4>])self.m == (Real[<4>])mat2.m;
fn bool Matrix3x3.eq(&self, Matrix3x3 mat2) @operator(==) => (Real[<9>])self.m == (Real[<9>])mat2.m;
fn bool Matrix4x4.eq(&self, Matrix4x4 mat2) @operator(==) => (Real[<16>])self.m == (Real[<16>])mat2.m;
fn bool Matrix2x2.neq(&self, Matrix2x2 mat2) @operator(!=) => (Real[<4>])self.m != (Real[<4>])mat2.m;
fn bool Matrix3x3.neq(&self, Matrix3x3 mat2) @operator(!=) => (Real[<9>])self.m != (Real[<9>])mat2.m;
fn bool Matrix4x4.neq(&self, Matrix4x4 mat2) @operator(!=) => (Real[<16>])self.m != (Real[<16>])mat2.m;
fn Matrix4x4 look_at(Real[<3>] eye, Real[<3>] target, Real[<3>] up) => matrix_look_at(Matrix4x4, eye, target, up);

101
lib/std/math/quaternion.c3 Normal file
View File

@@ -0,0 +1,101 @@
module std::math;
// Predefined quaternion aliases.
alias Quaternionf = Quaternion {float};
alias Quaternion = Quaternion {double};
alias QUATERNION_IDENTITY @builtin = quaternion::IDENTITY {double};
alias QUATERNIONF_IDENTITY @builtin = quaternion::IDENTITY {float};
<*
The generic quaternion module, for float or double based quaternion definitions.
@require Real.kindof == FLOAT : "A quaternion must use a floating type"
*>
module std::math::quaternion {Real};
import std::math::vector;
union Quaternion
{
struct
{
Real i, j, k, l;
}
Real[<4>] v;
}
const Quaternion 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() };
fn Quaternion Quaternion.invert(self)
{
Real length_sq = self.v.dot(self.v);
if (length_sq <= 0) return self;
Real inv_length = 1 / length_sq;
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)
{
Quaternion result = {};
Real[<4>] q2v = q2.v;
Real cos_half_theta = self.v.dot(q2v);
if (cos_half_theta < 0)
{
q2v = -q2v;
cos_half_theta = -cos_half_theta;
}
if (cos_half_theta >= 1) return self;
Real[<4>] q1v = self.v;
if (cos_half_theta > 0.95f) return { .v = q1v.lerp(q2v, amount) };
Real half_theta = math::cos(cos_half_theta);
Real sin_half_theta = math::sqrt(1 - cos_half_theta * cos_half_theta);
if (math::abs(sin_half_theta) < 0.001f)
{
return { .v = (q1v + q2v) * 0.5f };
}
Real ratio_a = math::sin((1 - amount) * half_theta) / sin_half_theta;
Real ratio_b = math::sin(amount * half_theta) / sin_half_theta;
return { .v = q1v * ratio_a + q2v * ratio_b };
}
fn Quaternion Quaternion.mul(self, Quaternion 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,
self.k * b.l + self.l * b.k + self.i * b.j - self.j * b.i,
self.l * b.l - self.i * b.i - self.j * self.j - self.k * self.k };
}
macro into_matrix(Quaternion* q, $Type) @private
{
Quaternion rotation = q.normalize();
var x = rotation.i;
var y = rotation.j;
var z = rotation.k;
var w = rotation.l;
return ($Type) {
1 - 2*y*y - 2*z*z, 2*x*y - 2*z*w, 2*x*z + 2*y*w, 0,
2*x*y + 2*z*w, 1 - 2*x*x - 2*z*z, 2*y*z - 2*x*w, 0,
2*x*z - 2*y*w, 2*y*z + 2*x*w , 1 - 2*x*x - 2*y*y, 0,
0.0, 0.0, 0.0, 1.0,
};
}

View File

@@ -31,4 +31,4 @@ fn char SimpleRandom.next_byte(&self) @dynamic => (char)self.next_int();
const long SIMPLE_RANDOM_MULTIPLIER @local = 0x5DEECE66D;
const long SIMPLE_RANDOM_ADDEND @local = 0xB;
const long SIMPLE_RANDOM_MASK @local = (1u64 << 48) - 1;
const long SIMPLE_RANDOM_MASK @local = (1UL << 48) - 1;

View File

@@ -1,4 +1,4 @@
module std::math;
module std::math::math_rt;
fn int128 __divti3(int128 a, int128 b) @extern("__divti3") @weak @nostrip
{
@@ -312,21 +312,21 @@ macro float_from_i128($Type, a) @private
$switch $Type:
$case double:
$Rep = ulong;
const MANT_DIG = DOUBLE_MANT_DIG;
const MANT_DIG = math::DOUBLE_MANT_DIG;
const SIGNIFICANT_BITS = 52;
const EXP_BIAS = 1023;
const MANTISSA_MASK = 0xFFFFF_FFFF_FFFFu64;
const SIGN_BIT = 1u64 << 63;
const MANTISSA_MASK = 0xFFFFF_FFFF_FFFFUL;
const SIGN_BIT = 1UL << 63;
$case float:
$Rep = uint;
const MANT_DIG = FLOAT_MANT_DIG;
const MANT_DIG = math::FLOAT_MANT_DIG;
const EXP_BIAS = 127;
const SIGNIFICANT_BITS = 23;
const MANTISSA_MASK = 0x7F_FFFFu32;
const SIGN_BIT = 1u32 << 31;
const MANTISSA_MASK = 0x7F_FFFFU;
const SIGN_BIT = 1U << 31;
$case float16:
$Rep = ushort;
const MANT_DIG = HALF_MANT_DIG;
const MANT_DIG = math::HALF_MANT_DIG;
$case float128:
$Rep = uint128;
const MANT_DIG = QUAD_MANT_DIG;
@@ -352,7 +352,7 @@ macro float_from_i128($Type, a) @private
a |= (uint128)((a & 4) != 0);
a++;
a >>= 2;
if (a & (1i128 << MANT_DIG))
if (a & (1LL << MANT_DIG))
{
a >>= 1;
e++;
@@ -371,22 +371,22 @@ macro float_from_u128($Type, a) @private
$switch $Type:
$case double:
$Rep = ulong;
const MANT_DIG = DOUBLE_MANT_DIG;
const MANT_DIG = math::DOUBLE_MANT_DIG;
const SIGNIFICANT_BITS = 52;
const EXP_BIAS = 1023;
const MANTISSA_MASK = 0xFFFFF_FFFF_FFFFu64;
const MANTISSA_MASK = 0xFFFFF_FFFF_FFFFUL;
$case float:
$Rep = uint;
const MANT_DIG = FLOAT_MANT_DIG;
const MANT_DIG = math::FLOAT_MANT_DIG;
const EXP_BIAS = 127;
const SIGNIFICANT_BITS = 23;
const MANTISSA_MASK = 0x7F_FFFFu32;
const MANTISSA_MASK = 0x7F_FFFFU;
$case float16:
$Rep = ushort;
const MANT_DIG = HALF_MANT_DIG;
const MANT_DIG = math::HALF_MANT_DIG;
$case float128:
$Rep = uint128;
const MANT_DIG = QUAD_MANT_DIG;
const MANT_DIG = math::QUAD_MANT_DIG;
$endswitch
if (a == 0) return ($Type)0;
int sd = 128 - (int)$$clz(a); // digits
@@ -406,7 +406,7 @@ macro float_from_u128($Type, a) @private
a |= (uint128)((a & 4) != 0);
a++;
a >>= 2;
if (a & (1i128 << MANT_DIG))
if (a & (1LL << MANT_DIG))
{
a >>= 1;
e++;
@@ -458,8 +458,8 @@ macro fixuint(a) @private
int sign = rep & SIGN_BIT ? -1 : 1;
int exponent = (int)((abs >> SIGNIFICANT_BITS) - EXPONENT_BIAS);
$Rep significand = (abs & SIGNIFICANT_MASK) | IMPLICIT_BIT;
if (sign == -1 || exponent < 0) return 0u128;
if ((uint)exponent >= uint128.sizeof * 8) return ~0u128;
if (sign == -1 || exponent < 0) return 0ULL;
if ((uint)exponent >= uint128.sizeof * 8) return ~0ULL;
if (exponent < SIGNIFICANT_BITS) return (uint128)significand >> (SIGNIFICANT_BITS - exponent);
return (uint128)significand << (exponent - SIGNIFICANT_BITS);
}

View File

@@ -1,15 +1,15 @@
module std::math;
module std::math::math_rt;
fn float __roundevenf(float f) @extern("roundevenf") @weak @nostrip
{
// Slow implementation
return round(f / 2) * 2;
return math::round(f / 2) * 2;
}
fn double __roundeven(double d) @extern("roundeven") @weak @nostrip
{
// Slow implementation
return round(d / 2) * 2;
return math::round(d / 2) * 2;
}
fn double __powidf2(double a, int b) @extern("__powidf2") @weak @nostrip

View File

@@ -1,3 +1,5 @@
// Vector supplemental methods
module std::math::vector;
import std::math;
@@ -51,6 +53,8 @@ fn double[<3>] double[<3>].unproject(self, Matrix4 projection, Matrix4 view) =>
fn void ortho_normalize(float[<3>]* v1, float[<3>]* v2) => ortho_normalize3(v1, v2);
fn void ortho_normalized(double[<3>]* v1, double[<3>]* v2) => ortho_normalize3(v1, v2);
// -- private helpers
macro towards(v, target, max_distance) @private
{
var delta = target - v;
@@ -80,7 +84,7 @@ macro rotate(v, angle) @private
{
var c = math::cos(angle);
var s = math::sin(angle);
return $typeof(v) { v[0] * c - v[1] * s, v[0] * s + v[1] * c };
return ($typeof(v)) { v[0] * c - v[1] * s, v[0] * s + v[1] * c };
}
macro perpendicular3(v) @private
@@ -111,7 +115,7 @@ macro cross3(v1, v2) @private
macro transform2(v, mat) @private
{
return $typeof(v) {
return ($typeof(v)) {
mat.m00 * v[0] + mat.m10 * v[1] + mat.m30 ,
mat.m01 * v[0] + mat.m11 * v[1] + mat.m31 };
}

View File

@@ -1,406 +0,0 @@
module std::net::http;
/*
enum HttpStatus
{
PENDING,
COMPLETED,
FAILED
}
struct Http
{
HttpStatus status;
int status_code;
String reason;
String content_type;
String response_data;
}
fn Http* http_get(String url, Allocator using = allocator::temp())
{
return null;
}
fn Http* http_post(String url, Allocator using = allocator::temp())
{
return null;
}
fn void Http.destroy(Http* this)
{
}
fn HttpStatus Http.process(Http* this)
{
unreachable();
}
// Common across implementations
struct HttpInternal @private
{
inline Http http;
Allocator allocator;
int connect_pending;
int request_sent;
char[256] address;
char[256] request_header;
char* request_header_large;
String request_data;
char[1024] reason_phrase;
char[256] content_type;
usz data_size;
usz data_capacity;
void* data;
}
fn String? parse_url(String url, String* port, String* resource) @private
{
if (url[:7] != "http://") return NetError.INVALID_URL?;
url = url[7..];
usz end_index = url.index_of(":") ?? url.index_of("/") ?? url.len;
String address = url[:end_index];
String end_part = url[end_index..];
if (!end_part.len)
{
*port = "80";
*resource = {};
return address;
}
switch (end_part[0])
{
case ':':
end_index = end_part.index_of("/") ?? end_part.len;
end_part[:end_index].to_uint() ?? NetError.INVALID_URL?!;
*port = end_part[:end_index];
*resource = url[end_index..];
case '/':
*port = "80";
*resource = end_part;
default:
unreachable();
}
return address;
}
fn Socket? http_internal_connect(String address, uint port) @private
{
return tcp::connect_async(address, port)!;
}
fn HttpInternal* http_internal_create(usz request_data_size, Allocator allocator) @private
{
HttpInternal* internal = allocator.alloc(HttpInternal.sizeof + request_data_size)!!;
internal.status = PENDING;
internal.status_code = 0;
internal.response_data = {};
internal.allocator = allocator;
internal.connect_pending = 1;
internal.request_sent = 0;
// internal.reason = "";
// internal.content_type = "";
internal.data_size = 0;
internal.data_capacity = 64 * 1024;
internal.data = allocator.alloc(internal.data_capacity)!!;
internal.request_data = {};
return internal;
}
fn Http*? http_get(String url, Allocator allocator = allocator::temp())
{
$if env::WIN32:
int[1024] wsa_data;
if (_wsa_startup(1, &wsa_data) != 0) return NetError.GENERAL_ERROR?;
$endif
uint port;
String resource;
String address = parse_url(url, &port, &resource)?;
Socket socket = tcp::connect(address, port)?;
HttpInternal* internal = http_internal_create(0, allocator);
internal.socket = socket;
char* request_header;
usz request_header_len = 64 + resource.len + address.len + port.len;
if (request_header_len < sizeof(internal.request_header))
{
internal.request_header_large = null;
request_header = internal.request_header;
}
else
{
internal.request_header_large = (char*)allocator.malloc(request_header_len + 1);
request_header = internal.request_header_large;
}
int default_http_port = port == "80";
sprintf( request_header, "GET %s HTTP/1.0\r\nHost: %s%s%s\r\n\r\n", resource, address, default_http_port ? "" : ":", default_http_port ? "" : port );
return internal;
}
fn Http*? http_post(String url, Allocator allocator = allocator::temp())
{
$if env::OS_TYPE == OsType::WIN32:
int[1024] wsa_data;
if (_wsa_startup(1, &wsa_data) != 0) return NetError.GENERAL_ERROR?;
$endif
String port;
String resource;
String address = parse_url(url, &port, &resource)?;
Socket socket = http_internal_connect(address, port)?;
HttpInternal* internal = http_internal_create(0, allocator);
internal.socket = socket;
char* request_header;
uz request_header_len = 64 + resource.len + address.len + port.len;
if (request_header_len < sizeof(internal.request_header))
{
internal.request_header_large = null;
request_header = internal.request_header;
}
else
{
internal.request_header_large = (char*)allocator.malloc(request_header_len + 1);
request_header = internal.request_header_large;
}
int default_http_port = port == "80";
sprintf( request_header, "POST %s HTTP/1.0\r\nHost: %s%s%s\r\nContent-Length: %d\r\n\r\n", resource, address, default_http_port ? "" : ":", default_http_port ? "" : port,
(int) size );
internal->request_data_size = size;
internal->request_data = ( internal + 1 );
memcpy( internal->request_data, data, size );
return internal;
}
fn HttpStatus Http.process(Http* http)
{
HttpInternal* internal = (HttpInternal*)http;
if (http.status == HttpStatus.FAILED) return http.status;
if (internal.connect_pending)
{
fd_set sockets_to_check;
FD_ZERO(&sockets_to_check);
FD_SET( internal->socket, &sockets_to_check );
struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0;
// check if socket is ready for send
if( select( (int)( internal->socket + 1 ), NULL, &sockets_to_check, NULL, &timeout ) == 1 )
{
int opt = -1;
socklen_t len = sizeof( opt );
if( getsockopt( internal->socket, SOL_SOCKET, SO_ERROR, (char*)( &opt ), &len) >= 0 && opt == 0 )
{
internal->connect_pending = 0; // if it is, we're connected
}
}
}
if (internal.connect_pending) retur http.status;
if (!internal.request_sent)
{
char* request_header = internal->request_header_large ?
internal.request_header_large : internal.request_header;
if (send(internal.socket, request_header, (int) strlen( request_header ), 0) == -1)
{
return http.status = FAILED;
}
if (internal.request_data_size)
{
int res = send(internal.socket, (char const*)internal->request_data, (int) internal->request_data_size, 0 );
if (res == -1)
{
http.status = HTTP_STATUS_FAILED;
return http.status;
}
}
internal.request_sent = 1;
return http.status;
}
// check if socket is ready for recv
fd_set sockets_to_check;
FD_ZERO( &sockets_to_check );
#pragma warning( push )
#pragma warning( disable: 4548 ) // expression before comma has no effect; expected expression with side-effect
FD_SET( internal->socket, &sockets_to_check );
#pragma warning( pop )
struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0;
}
http_status_t http_process( http_t* http )
{
while( select( (int)( internal->socket + 1 ), &sockets_to_check, NULL, NULL, &timeout ) == 1 )
{
char buffer[ 4096 ];
int size = recv( internal->socket, buffer, sizeof( buffer ), 0 );
if( size == -1 )
{
http->status = HTTP_STATUS_FAILED;
return http->status;
}
else if( size > 0 )
{
size_t min_size = internal->data_size + size + 1;
if( internal->data_capacity < min_size )
{
internal->data_capacity *= 2;
if( internal->data_capacity < min_size ) internal->data_capacity = min_size;
void* new_data = HTTP_MALLOC( memctx, internal->data_capacity );
memcpy( new_data, internal->data, internal->data_size );
HTTP_FREE( memctx, internal->data );
internal->data = new_data;
}
memcpy( (void*)( ( (uintptr_t) internal->data ) + internal->data_size ), buffer, (size_t) size );
internal->data_size += size;
}
else if( size == 0 )
{
char const* status_line = (char const*) internal->data;
int header_size = 0;
char const* header_end = strstr( status_line, "\r\n\r\n" );
if( header_end )
{
header_end += 4;
header_size = (int)( header_end - status_line );
}
else
{
http->status = HTTP_STATUS_FAILED;
return http->status;
}
// skip http version
status_line = strchr( status_line, ' ' );
if( !status_line )
{
http->status = HTTP_STATUS_FAILED;
return http->status;
}
++status_line;
// extract status code
char status_code[ 16 ];
char const* status_code_end = strchr( status_line, ' ' );
if( !status_code_end )
{
http->status = HTTP_STATUS_FAILED;
return http->status;
}
memcpy( status_code, status_line, (size_t)( status_code_end - status_line ) );
status_code[ status_code_end - status_line ] = 0;
status_line = status_code_end + 1;
http->status_code = atoi( status_code );
// extract reason phrase
char const* reason_phrase_end = strstr( status_line, "\r\n" );
if( !reason_phrase_end )
{
http->status = HTTP_STATUS_FAILED;
return http->status;
}
size_t reason_phrase_len = (size_t)( reason_phrase_end - status_line );
if( reason_phrase_len >= sizeof( internal->reason_phrase ) )
reason_phrase_len = sizeof( internal->reason_phrase ) - 1;
memcpy( internal->reason_phrase, status_line, reason_phrase_len );
internal->reason_phrase[ reason_phrase_len ] = 0;
status_line = reason_phrase_end + 1;
// extract content type
char const* content_type_start = strstr( status_line, "Content-Type: " );
if( content_type_start )
{
content_type_start += strlen( "Content-Type: " );
char const* content_type_end = strstr( content_type_start, "\r\n" );
if( content_type_end )
{
size_t content_type_len = (size_t)( content_type_end - content_type_start );
if( content_type_len >= sizeof( internal->content_type ) )
content_type_len = sizeof( internal->content_type ) - 1;
memcpy( internal->content_type, content_type_start, content_type_len );
internal->content_type[ content_type_len ] = 0;
}
}
http->status = http->status_code < 300 ? HTTP_STATUS_COMPLETED : HTTP_STATUS_FAILED;
http->response_data = (void*)( ( (uintptr_t) internal->data ) + header_size );
http->response_size = internal->data_size - header_size;
// add an extra zero after the received data, but don't modify the size, so ascii results can be used as
// a zero terminated string. the size returned will be the string without this extra zero terminator.
( (char*)http->response_data )[ http->response_size ] = 0;
return http->status;
}
}
return http->status;
}
void http_release( http_t* http )
{
http_internal_t* internal = (http_internal_t*) http;
#ifdef _WIN32
closesocket( internal->socket );
#else
close( internal->socket );
#endif
if( internal->request_header_large) HTTP_FREE( memctx, internal->request_header_large );
HTTP_FREE( memctx, internal->data );
HTTP_FREE( memctx, internal );
#ifdef _WIN32
WSACleanup();
#endif
}
#endif /* HTTP_IMPLEMENTATION */
/*
revision history:
1.0 first released version
*/
/*
------------------------------------------------------------------------------
This software is available under 2 licenses - you may choose the one you like.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2016 Mattias Gustavsson
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.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
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 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.
------------------------------------------------------------------------------
*/*/

View File

@@ -1,6 +1,5 @@
module std::net;
import std::io;
import std::ascii;
enum IpProtocol : char (AIFamily ai_family)
{

92
lib/std/net/os/android.c3 Normal file
View File

@@ -0,0 +1,92 @@
module std::net::os @if(env::ANDROID);
import libc;
const AIFamily PLATFORM_AF_AX25 = 3;
const AIFamily PLATFORM_AF_IPX = 4;
const AIFamily PLATFORM_AF_APPLETALK = 5;
const AIFamily PLATFORM_AF_NETROM = 6;
const AIFamily PLATFORM_AF_BRIDGE = 7;
const AIFamily PLATFORM_AF_AAL5 = 8;
const AIFamily PLATFORM_AF_X25 = 9;
const AIFamily PLATFORM_AF_INET6 = 10;
const PLATFORM_O_NONBLOCK = 0o4000;
// https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/socket.h
const int SOL_SOCKET = 1;
const int SO_DEBUG = 1; // turn on debugging info recording
const int SO_REUSEADDR = 2; // allow local address reuse
const int SO_TYPE = 3;
const int SO_ERROR = 4;
const int SO_DONTROUTE = 5; // just use interface addresses
const int SO_BROADCAST = 6; // permit sending of broadcast msgs
const int SO_SNDBUF = 7; // Send buffer size
const int SO_RCVBUF = 8; // Receive buffer size
const int SO_KEEPALIVE = 9; // keep connections alive
const int SO_OOBINLINE = 10; // leave received OOB data in line
const int SO_NO_CHECK = 11;
const int SO_PRIORITY = 12;
const int SO_LINGER = 13; // linger on close if data present (in ticks)
const int SO_BSDCOMPAT = 14;
const int SO_REUSEPORT = 15; // allow local address & port reuse
const int SO_RCVLOWAT = 18;
const int SO_SNDLOWAT = 19;
const int SO_RCVTIMEO = 20; // IMPORTANT Verify before use
const int SO_SNDTIMEO = 21; // IMPORTANT Verify before use
const int SO_BINDTODEVICE = 25;
const int SO_ATTACH_FILTER = 26;
const int SO_DETACH_FILTER = 27;
const int SO_PEERNAME = 28;
const int SO_TIMESTAMP = 29; // IMPORTANT Verify before use timestamp received dgram traffic
const int SO_ACCEPTCONN = 30;
const int SO_PEERSEC = 31;
const int SO_SNDBUFFORCE = 32;
const int SO_RCVBUFFORCE = 33;
const int SO_PASSSEC = 34;
const int SO_MARK = 36;
const int SO_PROTOCOL = 38;
const int SO_DOMAIN = 39;
const int SO_RXQ_OVFL = 40;
const int SO_WIFI_STATUS = 41;
const int SO_PEEK_OFF = 42;
const int SO_NOFCS = 43;
const int SO_LOCK_FILTER = 44;
const int SO_SELECT_ERR_QUEUE = 45;
const int SO_BUSY_POLL = 46;
const int SO_MAX_PACING_RATE = 47;
const int SO_BPF_EXTENSIONS = 48;
const int SO_INCOMING_CPU = 49;
const int SO_ATTACH_BPF = 50;
const int SO_ATTACH_REUSEPORT_CBPF = 51;
const int SO_ATTACH_REUSEPORT_EBPF = 52;
const int SO_CNX_ADVICE = 53;
const int SO_MEMINFO = 55;
const int SO_INCOMING_NAPI_ID = 56;
const int SO_COOKIE = 57;
const int SO_PEERGROUPS = 59;
const int SO_ZEROCOPY = 60;
const int SO_TXTIME = 61;
const int SO_BINDTOIFINDEX = 62;
const int SO_DETACH_REUSEPORT_BPF = 68;
const int SO_PREFER_BUSY_POLL = 69;
const int SO_BUSY_POLL_BUDGET = 70;
const int SO_NETNS_COOKIE = 71;
const int SO_BUF_LOCK = 72;
const int SO_RESERVE_MEM = 73;
const int SO_TXREHASH = 74;
const int SO_RCVMARK = 75;
const int SO_PASSPIDFD = 76;
const int SO_PEERPIDFD = 77;
const CUShort POLLRDNORM = 0x0040;
const CUShort POLLRDBAND = 0x0080;
const CUShort POLLWRNORM = 0x0100;
const CUShort POLLWRBAND = 0x0200;
const CUShort POLLMSG = 0x0400;
const CUShort POLLREMOVE = 0x1000;
const CUShort POLLRDHUP = 0x2000;
const CUShort POLLFREE = 0x4000;
const CUShort POLL_BUSY_LOOP = 0x8000;
const CInt MSG_PEEK = 0x0002;

View File

@@ -1,5 +1,5 @@
module std::net::os;
const bool SUPPORTS_INET = env::LIBC && (env::WIN32 || env::DARWIN || env::LINUX);
const bool SUPPORTS_INET = env::LIBC && (env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID);
typedef AIFamily = CInt;
typedef AIProtocol = CInt;
@@ -18,7 +18,7 @@ struct AddrInfo
AISockType ai_socktype;
AIProtocol ai_protocol;
Socklen_t ai_addrlen;
struct @if(env::WIN32 || env::DARWIN)
struct @if(env::WIN32 || env::DARWIN || env::ANDROID)
{
ZString ai_canonname;
SockAddrPtr ai_addr;
@@ -58,7 +58,7 @@ 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));
module std::net::os @if(!env::LIBC || !(env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID));
const AIFamily PLATFORM_AF_INET6 = 0;
const AIFamily PLATFORM_AF_IPX = 0;

View File

@@ -88,7 +88,7 @@ fn fault convert_error(WSAError error)
fn fault socket_error()
{
return convert_error(win32_WSAGetLastError());
return convert_error(win32::wsaGetLastError());
}
const CUShort POLLIN = win32::POLLIN;

View File

@@ -69,7 +69,7 @@ fn ulong? poll_ms(Poll[] polls, long timeout_ms)
{
if (timeout_ms > CInt.max) timeout_ms = CInt.max;
$if env::WIN32:
CInt result = win32_WSAPoll((Win32_LPWSAPOLLFD)polls.ptr, (Win32_ULONG)polls.len, (CInt)timeout_ms);
CInt result = win32::wsaPoll((Win32_LPWSAPOLLFD)polls.ptr, (Win32_ULONG)polls.len, (CInt)timeout_ms);
$else
CInt result = os::poll((Posix_pollfd*)polls.ptr, (Posix_nfds_t)polls.len, (CInt)timeout_ms);
$endif

View File

@@ -23,7 +23,7 @@ fn bool last_error_is_delayed_connect()
{
$switch:
$case env::WIN32:
switch (win32_WSAGetLastError())
switch (win32::wsaGetLastError())
{
case wsa::EWOULDBLOCK:
case wsa::EINPROGRESS: return true;

View File

@@ -0,0 +1,2 @@
module std::os::freebsd @if(env::FREEBSD);

View File

@@ -67,6 +67,17 @@ struct Darwin_segment_command_64
uint flags; /* flags */
}
struct Darwin_mach_timebase_info
{
uint numer;
uint denom;
}
alias Darwin_mach_timebase_info_t = Darwin_mach_timebase_info;
alias Darwin_mach_timebase_info_data_t = Darwin_mach_timebase_info;
extern fn void mach_timebase_info(Darwin_mach_timebase_info_data_t* timebase);
extern fn ulong mach_absolute_time();
fn String? executable_path(Allocator allocator)
{
@@ -155,3 +166,4 @@ fn BacktraceList? symbolize_backtrace(Allocator allocator, void*[] backtrace)
};
return list;
}

View File

@@ -0,0 +1,2 @@
module std::os::darwin @if(env::DARWIN);

View File

@@ -0,0 +1 @@
module std::os::netbsd @if(env::NETBSD);

View File

@@ -0,0 +1,2 @@
module std::os::openbsd @if(env::OPENBSD);

View File

@@ -4,7 +4,7 @@ import libc;
<*
Exit the process with a given exit code. This will typically call 'exit' in LibC
*>
fn void exit(int result, bool cleanup = true) @weak
fn void exit(int result) @weak @noreturn
{
$if env::LIBC:
libc::exit(result);
@@ -17,7 +17,7 @@ fn void exit(int result, bool cleanup = true) @weak
Exit the process with a given exit code. This will typically call '_Exit' in LibC
usually bypassing '@finalizer' functions.
*>
fn void fastexit(int result, bool cleanup = true) @weak
fn void fastexit(int result) @weak @noreturn
{
$if env::LIBC:
libc::_exit(result);

68
lib/std/os/posix/clock.c3 Normal file
View File

@@ -0,0 +1,68 @@
module std::os::posix @if(env::POSIX);
import libc;
extern fn CInt clock_gettime(int type, TimeSpec *time);
module std::os::posix @if(env::OPENBSD);
const CLOCK_REALTIME = 0;
const CLOCK_PROCESS_CPUTIME_ID = 2;
const CLOCK_MONOTONIC = 3;
const CLOCK_THREAD_CPUTIME_ID = 4;
const CLOCK_UPTIME = 5;
const CLOCK_BOOTTIME = 6;
module std::os::posix @if(env::FREEBSD);
const CLOCK_REALTIME = 0;
const CLOCK_VIRTUAL = 1;
const CLOCK_PROF = 2;
const CLOCK_MONOTONIC = 4;
const CLOCK_UPTIME = 5;
const CLOCK_UPTIME_PRECISE = 7;
const CLOCK_UPTIME_FAST = 8;
const CLOCK_REALTIME_PRECISE = 9;
const CLOCK_REALTIME_FAST = 10;
const CLOCK_MONOTONIC_PRECISE = 11;
const CLOCK_MONOTONIC_FAST = 12;
const CLOCK_SECOND = 13;
const CLOCK_THREAD_CPUTIME_ID = 14;
const CLOCK_PROCESS_CPUTIME_ID = 15;
const CLOCK_BOOTTIME = CLOCK_UPTIME;
const CLOCK_REALTIME_COARSE = CLOCK_REALTIME_FAST;
const CLOCK_MONOTONIC_COARSE = CLOCK_MONOTONIC_FAST;
module std::os::posix @if(env::NETBSD);
const CLOCK_REALTIME = 0;
const CLOCK_VIRTUAL = 1;
const CLOCK_PROF = 2;
const CLOCK_MONOTONIC = 3;
const CLOCK_THREAD_CPUTIME_ID = 0x20000000;
const CLOCK_PROCESS_CPUTIME_ID = 0x40000000;
module std::os::posix @if(env::WASI);
// Not implemented
const CLOCK_REALTIME = 0;
const CLOCK_MONOTONIC = 0;
module std::os::posix @if(env::DARWIN);
const CLOCK_REALTIME = 0;
const CLOCK_MONOTONIC = 6;
const CLOCK_MONOTONIC_RAW = 4;
const CLOCK_MONOTONIC_RAW_APPROX = 5;
const CLOCK_UPTIME_RAW = 8;
const CLOCK_UPTIME_RAW_APPROX = 9;
const CLOCK_PROCESS_CPUTIME_ID = 12;
const CLOCK_THREAD_CPUTIME_ID = 16;
module std::os::posix @if(env::LINUX || env::ANDROID);
const CLOCK_REALTIME = 0;
const CLOCK_MONOTONIC = 1;
const CLOCK_PROCESS_CPUTIME_ID = 2;
const CLOCK_THREAD_CPUTIME_ID = 3;
const CLOCK_MONOTONIC_RAW = 4;
const CLOCK_REALTIME_COARSE = 5;
const CLOCK_MONOTONIC_COARSE = 6;
const CLOCK_BOOTTIME = 7;
const CLOCK_REALTIME_ALARM = 8;
const CLOCK_BOOTTIME_ALARM = 9;
const CLOCK_TAI = 11;

View File

@@ -1,4 +1,3 @@
module std::os::posix;
module std::os::posix @if(env::POSIX);
extern ZString* environ;

1
lib/std/os/posix/net.c3 Normal file
View File

@@ -0,0 +1 @@
module std::os::posix @if(env::POSIX);

View File

@@ -45,6 +45,8 @@ bitstruct SubProcessOptions : int
// Note: this will **not** search for paths in any provided custom environment
// and instead uses the PATH of the spawning process.
bool search_user_path;
// Inherit the parent's stdin, stdout, and stderr handles instead of creating pipes
bool inherit_stdio;
}
fn void? create_named_pipe_helper(void** rd, void **wr) @local @if(env::WIN32)
@@ -115,22 +117,32 @@ fn WString convert_command_line_win32(String[] command_line) @inline @if(env::WI
*>
fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, String[] environment = {}) @if(env::WIN32)
{
void* rd, wr;
Win32_DWORD flags = win32::CREATE_UNICODE_ENVIRONMENT;
Win32_PROCESS_INFORMATION process_info;
Win32_SECURITY_ATTRIBUTES sa_attr = { Win32_SECURITY_ATTRIBUTES.sizeof, null, 1 };
Win32_STARTUPINFOW start_info = {
.cb = Win32_STARTUPINFOW.sizeof,
.dwFlags = win32::STARTF_USESTDHANDLES
};
if (options.no_window) flags |= win32::CREATE_NO_WINDOW;
if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return FAILED_TO_CREATE_PIPE?;
// TODO defer catch
if (!win32::setHandleInformation(wr, win32::HANDLE_FLAG_INHERIT, 0)) return FAILED_TO_CREATE_PIPE?;
// Only set STARTF_USESTDHANDLES if we're not inheriting stdio
if (!options.inherit_stdio) start_info.dwFlags = win32::STARTF_USESTDHANDLES;
CFile stdin;
CFile stdout;
CFile stderr;
void* rd = null;
void* wr = null;
// Only create pipes if not inheriting stdio
if (!options.inherit_stdio)
{
if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return FAILED_TO_CREATE_PIPE?;
// TODO defer catch
if (!win32::setHandleInformation(wr, win32::HANDLE_FLAG_INHERIT, 0)) return FAILED_TO_CREATE_PIPE?;
}
@pool()
{
WString used_environment = null;
@@ -149,39 +161,19 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
env.append("\0");
used_environment = env.str_view().to_temp_wstring()!;
}
int fd = win32::_open_osfhandle((iptr)wr, 0);
if (fd != -1)
{
stdin = win32::_fdopen(fd, "wb");
if (!stdin) return FAILED_TO_OPEN_STDIN?;
}
start_info.hStdInput = rd;
if (options.read_async)
{
create_named_pipe_helper(&rd, &wr)!;
}
else
{
if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return FAILED_TO_CREATE_PIPE?;
}
if (!win32::setHandleInformation(rd, win32::HANDLE_FLAG_INHERIT, 0)) return FAILED_TO_CREATE_PIPE?;
fd = win32::_open_osfhandle((iptr)rd, 0);
if (fd != -1)
{
stdout = win32::_fdopen(fd, "rb");
if (!stdout) return FAILED_TO_OPEN_STDOUT?;
}
start_info.hStdOutput = wr;
do
// Handle stdin pipe if not inheriting
if (!options.inherit_stdio)
{
if (options.combined_stdout_stderr)
int fd = win32::_open_osfhandle((iptr)wr, 0);
if (fd != -1)
{
stderr = stdout;
start_info.hStdError = start_info.hStdOutput;
break;
stdin = win32::_fdopen(fd, "wb");
if (!stdin) return FAILED_TO_OPEN_STDIN?;
}
start_info.hStdInput = rd;
// Handle stdout pipe
if (options.read_async)
{
create_named_pipe_helper(&rd, &wr)!;
@@ -195,18 +187,49 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
fd = win32::_open_osfhandle((iptr)rd, 0);
if (fd != -1)
{
stderr = win32::_fdopen(fd, "rb");
if (!stderr) return FAILED_TO_OPEN_STDERR?;
stdout = win32::_fdopen(fd, "rb");
if (!stdout) return FAILED_TO_OPEN_STDOUT?;
}
start_info.hStdError = wr;
};
void *event_output;
void *event_error;
if (options.read_async)
start_info.hStdOutput = wr;
// Handle stderr pipe or combine with stdout
do
{
if (options.combined_stdout_stderr)
{
stderr = stdout;
start_info.hStdError = start_info.hStdOutput;
break;
}
if (options.read_async)
{
create_named_pipe_helper(&rd, &wr)!;
}
else
{
if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return FAILED_TO_CREATE_PIPE?;
}
if (!win32::setHandleInformation(rd, win32::HANDLE_FLAG_INHERIT, 0)) return FAILED_TO_CREATE_PIPE?;
fd = win32::_open_osfhandle((iptr)rd, 0);
if (fd != -1)
{
stderr = win32::_fdopen(fd, "rb");
if (!stderr) return FAILED_TO_OPEN_STDERR?;
}
start_info.hStdError = wr;
};
}
void *event_output = null;
void *event_error = null;
if (!options.inherit_stdio && options.read_async)
{
event_output = win32::createEventA(&sa_attr, 1, 1, null);
event_error = win32::createEventA(&sa_attr, 1, 1, null);
}
if (!win32::createProcessW(
null,
convert_command_line_win32(command_line),
@@ -214,14 +237,17 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
null, // primary thread security attributes
1, // handles are inherited
flags, // creation flags
used_environment, // environment
used_environment, // environment
null, // use parent dir
&start_info, // startup info ptr
&process_info)) return FAILED_TO_START_PROCESS?;
};
// We don't need the handle of the primary thread in the called process.
win32::closeHandle(process_info.hThread);
if (start_info.hStdOutput)
// Close handles if not inheriting stdio
if (!options.inherit_stdio && start_info.hStdOutput)
{
win32::closeHandle(start_info.hStdOutput);
if (start_info.hStdOutput != start_info.hStdError) win32::closeHandle(start_info.hStdError);
@@ -229,7 +255,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
return {
.hProcess = process_info.hProcess,
.hStdInput = start_info.hStdInput,
.hStdInput = options.inherit_stdio ? null : start_info.hStdInput,
.stdin_file = stdin,
.stdout_file = stdout,
.stderr_file = stderr,
@@ -280,27 +306,37 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
CInt[2] stdinfd;
CInt[2] stdoutfd;
CInt[2] stderrfd;
if (posix::pipe(&stdinfd)) return FAILED_TO_OPEN_STDIN?;
if (posix::pipe(&stdoutfd)) return FAILED_TO_OPEN_STDOUT?;
if (!options.combined_stdout_stderr && posix::pipe(&stderrfd)) return FAILED_TO_OPEN_STDERR?;
CFile stdin = null;
CFile stdout = null;
CFile stderr = null;
Posix_spawn_file_actions_t actions;
if (posix::spawn_file_actions_init(&actions)) return FAILED_TO_INITIALIZE_ACTIONS?;
defer posix::spawn_file_actions_destroy(&actions);
if (posix::spawn_file_actions_addclose(&actions, stdinfd[1])) return FAILED_TO_OPEN_STDIN?;
if (posix::spawn_file_actions_adddup2(&actions, stdinfd[0], libc::STDIN_FD)) return FAILED_TO_OPEN_STDIN?;
if (posix::spawn_file_actions_addclose(&actions, stdoutfd[0])) return FAILED_TO_OPEN_STDOUT?;
if (posix::spawn_file_actions_adddup2(&actions, stdoutfd[1], libc::STDOUT_FD)) return FAILED_TO_OPEN_STDOUT?;
if (options.combined_stdout_stderr)
// Only set up pipes if not inheriting stdio
if (!options.inherit_stdio)
{
if (posix::spawn_file_actions_adddup2(&actions, libc::STDOUT_FD, libc::STDERR_FD)) return FAILED_TO_OPEN_STDERR?;
}
else
{
if (posix::spawn_file_actions_addclose(&actions, stderrfd[0])) return FAILED_TO_OPEN_STDERR?;
if (posix::spawn_file_actions_adddup2(&actions, stderrfd[1], libc::STDERR_FD)) return FAILED_TO_OPEN_STDERR?;
if (posix::pipe(&stdinfd)) return FAILED_TO_OPEN_STDIN?;
if (posix::pipe(&stdoutfd)) return FAILED_TO_OPEN_STDOUT?;
if (!options.combined_stdout_stderr && posix::pipe(&stderrfd)) return FAILED_TO_OPEN_STDERR?;
if (posix::spawn_file_actions_addclose(&actions, stdinfd[1])) return FAILED_TO_OPEN_STDIN?;
if (posix::spawn_file_actions_adddup2(&actions, stdinfd[0], libc::STDIN_FD)) return FAILED_TO_OPEN_STDIN?;
if (posix::spawn_file_actions_addclose(&actions, stdoutfd[0])) return FAILED_TO_OPEN_STDOUT?;
if (posix::spawn_file_actions_adddup2(&actions, stdoutfd[1], libc::STDOUT_FD)) return FAILED_TO_OPEN_STDOUT?;
if (options.combined_stdout_stderr)
{
if (posix::spawn_file_actions_adddup2(&actions, libc::STDOUT_FD, libc::STDERR_FD)) return FAILED_TO_OPEN_STDERR?;
}
else
{
if (posix::spawn_file_actions_addclose(&actions, stderrfd[0])) return FAILED_TO_OPEN_STDERR?;
if (posix::spawn_file_actions_adddup2(&actions, stderrfd[1], libc::STDERR_FD)) return FAILED_TO_OPEN_STDERR?;
}
}
Pid_t child;
@pool()
{
@@ -315,21 +351,27 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
if (posix::spawnp(&child, command_line_copy[0], &actions, null, command_line_copy, used_environment)) return FAILED_TO_START_PROCESS?;
}
};
libc::close(stdinfd[0]);
CFile stdin = libc::fdopen(stdinfd[1], "wb");
libc::close(stdoutfd[1]);
CFile stdout = libc::fdopen(stdoutfd[0], "rb");
CFile stderr @noinit;
do
// Only set up file handles if not inheriting stdio
if (!options.inherit_stdio)
{
if (options.combined_stdout_stderr)
libc::close(stdinfd[0]);
stdin = libc::fdopen(stdinfd[1], "wb");
libc::close(stdoutfd[1]);
stdout = libc::fdopen(stdoutfd[0], "rb");
do
{
stderr = stdout;
break;
}
libc::close(stderrfd[1]);
stderr = libc::fdopen(stderrfd[0], "rb");
};
if (options.combined_stdout_stderr)
{
stderr = stdout;
break;
}
libc::close(stderrfd[1]);
stderr = libc::fdopen(stderrfd[0], "rb");
};
}
return {
.stdin_file = stdin,
.stdout_file = stdout,
@@ -357,11 +399,13 @@ fn CInt? SubProcess.join(&self) @if(env::POSIX)
fn File SubProcess.stdout(&self)
{
if (!self.stdout_file) return (File){}; // Return an empty File struct
return file::from_handle(self.stdout_file);
}
fn File SubProcess.stderr(&self)
{
if (!self.stderr_file) return (File){}; // Return an empty File struct
return file::from_handle(self.stderr_file);
}
@@ -450,6 +494,8 @@ fn usz? read_from_file_posix(CFile file, char* buffer, usz size) @if(env::POSIX)
fn usz? SubProcess.read_stdout(&self, char* buffer, usz size)
{
if (!self.stdout_file) return 0; // No output available when inheriting stdio
$if env::WIN32:
return read_from_file_win32(self.stdout_file, self.hEventOutput, buffer, size);
$else
@@ -457,8 +503,11 @@ fn usz? SubProcess.read_stdout(&self, char* buffer, usz size)
$endif
}
fn usz? SubProcess.read_stderr(&self, char* buffer, usz size)
{
if (!self.stderr_file) return 0; // No output available when inheriting stdio
$if env::WIN32:
return read_from_file_win32(self.stderr_file, self.hEventError, buffer, size);
$else

View File

@@ -0,0 +1,6 @@
module std::os::win32 @if(env::WIN32);
import std::math;
extern fn void getSystemTimeAsFileTime(Win32_FILETIME* time) @extern("GetSystemTimeAsFileTime");
extern fn Win32_BOOL queryPerformanceFrequency(Win32_LARGE_INTEGER* lpFrequency) @extern("QueryPerformanceFrequency");
extern fn Win32_BOOL queryPerformanceCounter(Win32_LARGE_INTEGER* lpPerformanceCount) @extern("QueryPerformanceCounter");

View File

@@ -1,5 +1,6 @@
module std::os::win32 @if(env::WIN32);
import libc;
enum Win32_GET_FILEEX_INFO_LEVELS
{
STANDARD,

View File

@@ -222,4 +222,5 @@ const Win32_DWORD ERROR_MR_MID_NOT_FOUND = 0x13D;
const Win32_DWORD ERROR_SCOPE_NOT_FOUND = 0x13E;
const Win32_DWORD ERROR_UNDEFINED_SCOPE = 0x13F;
const Win32_DWORD ERROR_IO_INCOMPLETE = 0x3E4;
const Win32_DWORD ERROR_IO_PENDING = 0x3E5;
const Win32_DWORD ERROR_IO_PENDING = 0x3E5;
const Win32_DWORD ERROR_TIMEOUT = 0x5B4;

View File

@@ -46,6 +46,8 @@ const IMAGE_FILE_MACHINE_ARM64 = 0xAA64;
const IMAGE_FILE_MACHINE_AMD64 = 0x8664;
const UNDNAME_COMPLETE = 0x0000;
alias Win32_INIT_ONCE_FN = fn Win32_BOOL(Win32_INIT_ONCE* initOnce, void* parameter, void** context);
extern fn void initializeCriticalSection(Win32_CRITICAL_SECTION* section) @extern("InitializeCriticalSection");
extern fn void deleteCriticalSection(Win32_CRITICAL_SECTION* section) @extern("DeleteCriticalSection");
extern fn Win32_HANDLE createMutex(void*, Win32_BOOL, void*) @extern("CreateMutexA");
@@ -53,6 +55,19 @@ extern fn Win32_BOOL releaseMutex(Win32_HANDLE) @extern("ReleaseMutex");
extern fn void enterCriticalSection(Win32_CRITICAL_SECTION* section) @extern("EnterCriticalSection");
extern fn void leaveCriticalSection(Win32_CRITICAL_SECTION* section) @extern("LeaveCriticalSection");
extern fn Win32_BOOL tryEnterCriticalSection(Win32_CRITICAL_SECTION* section) @extern("TryEnterCriticalSection");
extern fn void initializeSRWLock(Win32_SRWLOCK* lock) @extern("InitializeSRWLock");
extern fn void acquireSRWLockExclusive(Win32_SRWLOCK* lock) @extern("AcquireSRWLockExclusive");
extern fn void acquireSRWLockShared(Win32_SRWLOCK* lock) @extern("AcquireSRWLockShared");
extern fn void releaseSRWLockExclusive(Win32_SRWLOCK* lock) @extern("ReleaseSRWLockExclusive");
extern fn void releaseSRWLockShared(Win32_SRWLOCK* lock) @extern("ReleaseSRWLockShared");
extern fn Win32_BOOL tryAcquireSRWLockExclusive(Win32_SRWLOCK* lock) @extern("TryAcquireSRWLockExclusive");
extern fn Win32_BOOL tryAcquireSRWLockShared(Win32_SRWLOCK* lock) @extern("TryAcquireSRWLockShared");
extern fn void initializeConditionVariable(Win32_CONDITION_VARIABLE* conditionVariable) @extern("InitializeConditionVariable");
extern fn void wakeConditionVariable(Win32_CONDITION_VARIABLE* conditionVariable) @extern("WakeConditionVariable");
extern fn void wakeAllConditionVariable(Win32_CONDITION_VARIABLE* conditionVariable) @extern("WakeAllConditionVariable");
extern fn Win32_BOOL sleepConditionVariableCS(Win32_CONDITION_VARIABLE* conditionVariable, Win32_CRITICAL_SECTION* section, Win32_DWORD dwMilliseconds) @extern("SleepConditionVariableCS");
extern fn Win32_BOOL sleepConditionVariableSRW(Win32_CONDITION_VARIABLE* conditionVariable, Win32_SRWLOCK* lock, Win32_DWORD dwMilliseconds, Win32_ULONG flags) @extern("SleepConditionVariableSRW");
extern fn Win32_BOOL initOnceExecuteOnce(Win32_INIT_ONCE* initOnce, Win32_INIT_ONCE_FN initFn, void* parameter, void** context) @extern("InitOnceExecuteOnce");
extern fn Win32_DWORD waitForSingleObject(Win32_HANDLE hHandle, Win32_DWORD dwMilliseconds) @extern("WaitForSingleObject");
extern fn Win32_DWORD waitForSingleObjectEx(Win32_HANDLE hHandle, Win32_DWORD dwMilliseconds, Win32_BOOL bAlertable) @extern("WaitForSingleObjectEx");
extern fn Win32_DWORD waitForMultipleObjects(Win32_DWORD nCount, Win32_HANDLE* lpHandles, Win32_BOOL bWaitAll, Win32_DWORD dwMilliseconds) @extern("WaitForMultipleObjects");

View File

@@ -190,6 +190,9 @@ union Win32_LARGE_INTEGER
}
typedef Win32_CRITICAL_SECTION = ulong[5];
typedef Win32_CONDITION_VARIABLE = void*;
typedef Win32_SRWLOCK = void*;
typedef Win32_INIT_ONCE = void*;
struct Win32_SECURITY_ATTRIBUTES
{
@@ -694,4 +697,3 @@ alias Win32_LPSTACKFRAME64 = Win32_STACKFRAME64*;
alias Win32_PMODLOAD_DATA = Win32_MODLOAD_DATA*;
alias Win32_PIMAGEHLP_LINE64 = Win32_IMAGEHLP_LINE64*;
alias Win32_LPMODULEINFO = Win32_MODULEINFO*;

View File

@@ -122,11 +122,11 @@ const SD_RECEIVE = 0x00;
const SD_SEND = 0x01;
const SD_BOTH = 0x02;
extern fn CInt win32_WSAPoll(Win32_LPWSAPOLLFD fdArray, Win32_ULONG fds, Win32_INT timeout) @extern("WSAPoll") @builtin;
extern fn WSAError win32_WSAGetLastError() @extern("WSAGetLastError") @builtin;
extern fn void win32_WSASetLastError(WSAError error) @extern("WSASetLastError") @builtin;
extern fn CInt win32_WSAStartup(Win32_WORD, void*) @extern("WSAStartup") @builtin;
extern fn CInt win32_WSACleanup() @extern("WSACleanup") @builtin;
extern fn CInt wsaPoll(Win32_LPWSAPOLLFD fdArray, Win32_ULONG fds, Win32_INT timeout) @extern("WSAPoll");
extern fn WSAError wsaGetLastError() @extern("WSAGetLastError");
extern fn void wsaSetLastError(WSAError error) @extern("WSASetLastError");
extern fn CInt wsaStartup(Win32_WORD, void*) @extern("WSAStartup");
extern fn CInt wsaCleanup() @extern("WSACleanup");
const int FIONBIO = -2147195266;
const int FIONREAD = 1074030207;
@@ -230,3 +230,4 @@ const WSAError QOS_EPSFILTERSPEC = 11028;
const WSAError QOS_ESDMODEOBJ = 11029;
const WSAError QOS_ESHAPERATEOBJ = 11030;
const WSAError QOS_RESERVED_PETYPE = 11031;

View File

@@ -1,17 +0,0 @@
/*module std::text::i18n;
import std::collections::map;
import std::hash::fnv32a;
typedef Language = char[];
const Language EN = "en";
alias TranslationMap = HashMap{String, String};
fn uint Language.hash(self) => fnv32a::encode((char[])self);
HashMap{Language, TranslationMap*} language_map @private;
TranslationMap? current_map;
macro String @localized(String string) @builtin
{
return current_map[string] ?? string;
}

View File

@@ -1,6 +1,7 @@
module std::thread::os @if (!env::POSIX && !env::WIN32);
typedef NativeMutex = int;
typedef NativeTimedMutex = int;
typedef NativeConditionVariable = int;
typedef NativeOnceFlag = int;
typedef NativeThread = int;

View File

@@ -7,6 +7,8 @@ struct NativeMutex
bool initialized;
}
alias NativeTimedMutex = NativeMutex;
alias NativeConditionVariable = Pthread_cond_t;
struct NativeThread
@@ -28,7 +30,7 @@ fn void? NativeMutex.init(&self, MutexType type)
if (posix::pthread_mutexattr_init(&attr)) return thread::INIT_FAILED?;
defer posix::pthread_mutexattr_destroy(&attr);
// TODO: make a fine grained error instead
if (type & thread::MUTEX_RECURSIVE)
if (type.recursive)
{
if (posix::pthread_mutexattr_settype(&attr, posix::PTHREAD_MUTEX_RECURSIVE)) return thread::INIT_FAILED?;
}

View File

@@ -5,239 +5,313 @@ typedef NativeThread = inline Win32_HANDLE;
struct NativeMutex
{
union
Win32_SRWLOCK srw_lock;
Win32_DWORD owner_thread;
bitstruct : uint
{
Win32_CRITICAL_SECTION critical_section;
Win32_HANDLE handle;
bool initialized : 0;
bool recursive : 1;
uint locks : 2..31;
}
// Size is less than a Win32_HANDLE so due to alignment
// there is no benefit to pack these into a bitstruct.
uint locks;
bool recursive;
bool timed;
}
struct NativeOnceFlag
struct NativeTimedMutex
{
int status;
Win32_CRITICAL_SECTION lock;
Win32_SRWLOCK srw_lock;
Win32_CONDITION_VARIABLE cond_var;
Win32_DWORD owner_thread;
bitstruct : uint
{
bool initialized : 0;
bool recursive : 1;
uint locks : 2..31;
}
}
struct NativeConditionVariable
{
union
{
struct
{
Win32_HANDLE event_one;
Win32_HANDLE event_all;
}
Win32_HANDLE[2] events;
}
uint waiters_count;
Win32_CRITICAL_SECTION waiters_count_lock;
Win32_CONDITION_VARIABLE cond_var;
}
fn void? NativeMutex.init(&mtx, MutexType type)
struct NativeOnceFlag
{
mtx.locks = 0;
mtx.recursive = (bool)(type & thread::MUTEX_RECURSIVE);
mtx.timed = (bool)(type & thread::MUTEX_TIMED);
if (!mtx.timed)
{
win32::initializeCriticalSection(&(mtx.critical_section));
return;
}
if (!(mtx.handle = win32::createMutex(null, 0, null))) return thread::INIT_FAILED?;
Win32_INIT_ONCE init_once;
}
fn void? NativeMutex.destroy(&mtx)
{
mtx.locks = 0;
if (!mtx.timed)
{
win32::deleteCriticalSection(&mtx.critical_section);
return;
}
if (!win32::closeHandle(mtx.handle)) return thread::DESTROY_FAILED?;
}
fn void? NativeMutex.lock(&mtx)
{
if (!mtx.timed)
{
win32::enterCriticalSection(&mtx.critical_section);
}
else
{
switch (win32::waitForSingleObject(mtx.handle, win32::INFINITE))
{
case win32::WAIT_OBJECT_0:
break;
case win32::WAIT_ABANDONED:
default:
return thread::LOCK_FAILED?;
}
}
if (!mtx.recursive && mtx.locks)
{
return thread::LOCK_FAILED?;
}
mtx.locks++;
}
<*
@require mtx.timed : "Only available for timed locks"
@require !mtx.initialized : "Mutex is already initialized"
@require !type.timed
@ensure mtx.initialized
*>
fn void? NativeMutex.lock_timeout(&mtx, ulong ms)
fn void? NativeMutex.init(&mtx, MutexType type)
{
if (ms > uint.max) ms = uint.max;
switch (win32::waitForSingleObject(mtx.handle, (uint)ms))
{
case win32::WAIT_OBJECT_0:
break;
case win32::WAIT_TIMEOUT:
return thread::LOCK_TIMEOUT?;
case win32::WAIT_ABANDONED:
default:
return thread::LOCK_FAILED?;
}
if (!mtx.recursive && mtx.locks)
{
return thread::LOCK_FAILED?;
}
mtx.locks++;
*mtx = {
.initialized = true,
.recursive = type.recursive,
};
}
<*
@require mtx.initialized : "Mutex was not initialized"
@require mtx.owner_thread != win32::getCurrentThreadId() : "Mutex was not unlocked before destroying it"
@ensure !mtx.initialized
*>
fn void? NativeMutex.destroy(&mtx)
{
*mtx = {};
}
<*
@require mtx.initialized : "Mutex was not initialized"
*>
fn void? NativeMutex.lock(&mtx)
{
Win32_DWORD current_thread = win32::getCurrentThreadId();
if (mtx.owner_thread == current_thread)
{
if (!mtx.recursive) return thread::LOCK_FAILED?;
mtx.locks++;
return;
}
win32::acquireSRWLockExclusive(&mtx.srw_lock);
mtx.owner_thread = current_thread;
mtx.locks = 1;
}
<*
@require mtx.initialized : "Mutex was not initialized"
*>
fn bool NativeMutex.try_lock(&mtx)
{
bool success = mtx.timed
? win32::waitForSingleObject(mtx.handle, 0) == win32::WAIT_OBJECT_0
: (bool)win32::tryEnterCriticalSection(&mtx.critical_section);
if (!success) return false;
if (!mtx.recursive)
Win32_DWORD current_thread = win32::getCurrentThreadId();
if (mtx.owner_thread == current_thread)
{
if (mtx.locks)
{
if (mtx.timed)
{
win32::releaseMutex(mtx.handle);
}
else
{
win32::leaveCriticalSection(&mtx.critical_section);
}
return false;
}
if (!mtx.recursive) return false;
mtx.locks++;
return true;
}
mtx.locks++;
if (!win32::tryAcquireSRWLockExclusive(&mtx.srw_lock)) return false;
mtx.owner_thread = current_thread;
mtx.locks = 1;
return true;
}
<*
@require mtx.initialized : "Mutex was not initialized"
*>
fn void? NativeMutex.unlock(&mtx)
{
if (!mtx.locks) return thread::UNLOCK_FAILED?;
mtx.locks--;
if (!mtx.timed)
$if env::COMPILER_SAFE_MODE:
if (mtx.owner_thread != win32::getCurrentThreadId()) return thread::UNLOCK_FAILED?; // Mutex was not locked by the current thread
$endif
if (--mtx.locks == 0)
{
win32::leaveCriticalSection(&mtx.critical_section);
return;
mtx.owner_thread = 0;
win32::releaseSRWLockExclusive(&mtx.srw_lock);
}
if (!win32::releaseMutex(mtx.handle)) return thread::UNLOCK_FAILED?;
}
const int CONDITION_EVENT_ONE = 0;
const int CONDITION_EVENT_ALL = 1;
<*
@require type.timed
@require !mtx.initialized : "Mutex is already initialized"
@ensure mtx.initialized
*>
fn void? NativeTimedMutex.init(&mtx, MutexType type)
{
*mtx = {
.initialized = true,
.recursive = type.recursive,
};
}
<*
@require mtx.initialized : "Mutex was not initialized"
@require mtx.owner_thread != win32::getCurrentThreadId() : "Mutex was not unlocked before destroying it"
@ensure !mtx.initialized
*>
fn void? NativeTimedMutex.destroy(&mtx)
{
*mtx = {};
}
fn void? NativeTimedMutex.wait_cond_var(&mtx, uint ms) @local
{
if (!win32::sleepConditionVariableSRW(&mtx.cond_var, &mtx.srw_lock, ms, 0))
{
if (win32::getLastError() != win32::ERROR_TIMEOUT)
{
return thread::WAIT_FAILED?;
}
}
}
<*
@require mtx.initialized : "Mutex was not initialized"
*>
fn void? NativeTimedMutex.lock(&mtx)
{
Win32_DWORD current_thread = win32::getCurrentThreadId();
if (mtx.owner_thread == current_thread)
{
if (!mtx.recursive) return thread::LOCK_FAILED?;
mtx.locks++;
return;
}
win32::acquireSRWLockExclusive(&mtx.srw_lock);
defer win32::releaseSRWLockExclusive(&mtx.srw_lock);
while (mtx.locks)
{
mtx.wait_cond_var(win32::INFINITE)!;
}
mtx.locks = 1;
mtx.owner_thread = current_thread;
}
<*
@require mtx.initialized : "Mutex was not initialized"
*>
fn void? NativeTimedMutex.lock_timeout(&mtx, ulong ms)
{
Win32_DWORD current_thread = win32::getCurrentThreadId();
if (mtx.owner_thread == current_thread)
{
if (!mtx.recursive) return thread::LOCK_FAILED?;
mtx.locks++;
return;
}
win32::acquireSRWLockExclusive(&mtx.srw_lock);
defer win32::releaseSRWLockExclusive(&mtx.srw_lock);
if (!mtx.locks)
{
// Got the lock without needing to wait
mtx.locks = 1;
mtx.owner_thread = current_thread;
return;
}
NanoDuration duration = time::ms(ms).to_nano();
Clock start = clock::now();
for (NanoDuration remaining = duration; remaining > 0; remaining = duration - start.to_now())
{
ulong remaining_ms = remaining.to_ms();
if (remaining_ms > uint.max) remaining_ms = uint.max;
mtx.wait_cond_var((uint)remaining_ms)!;
if (!mtx.locks)
{
// Got the lock
mtx.locks = 1;
mtx.owner_thread = current_thread;
return;
}
}
return thread::WAIT_FAILED?;
}
<*
@require mtx.initialized : "Mutex was not initialized"
*>
fn bool NativeTimedMutex.try_lock(&mtx)
{
Win32_DWORD current_thread = win32::getCurrentThreadId();
if (mtx.owner_thread == current_thread)
{
if (!mtx.recursive) return false;
mtx.locks++;
return true;
}
win32::acquireSRWLockExclusive(&mtx.srw_lock);
defer win32::releaseSRWLockExclusive(&mtx.srw_lock);
if (mtx.locks) return false;
mtx.locks = 1;
mtx.owner_thread = current_thread;
return true;
}
<*
@require mtx.initialized : "Mutex was not initialized"
*>
fn void? NativeTimedMutex.unlock(&mtx)
{
win32::acquireSRWLockExclusive(&mtx.srw_lock);
$if env::COMPILER_SAFE_MODE:
if (mtx.owner_thread != win32::getCurrentThreadId())
{
win32::releaseSRWLockExclusive(&mtx.srw_lock);
return thread::UNLOCK_FAILED?; // Mutex was not locked by the current thread
}
$endif
bool signal;
if (--mtx.locks == 0)
{
mtx.owner_thread = 0;
signal = true;
}
win32::releaseSRWLockExclusive(&mtx.srw_lock);
if (signal) win32::wakeConditionVariable(&mtx.cond_var);
}
fn void? NativeConditionVariable.init(&cond)
{
cond.waiters_count = 0;
win32::initializeCriticalSection(&cond.waiters_count_lock);
cond.event_one = win32::createEventA(null, 0, 0, null);
if (!cond.event_one)
{
cond.event_all = (Win32_HANDLE)0;
return thread::INIT_FAILED?;
}
cond.event_all = win32::createEventA(null, 1, 0, null);
if (!cond.event_all)
{
win32::closeHandle(cond.event_one);
cond.event_one = (Win32_HANDLE)0;
return thread::INIT_FAILED?;
}
cond.cond_var = {};
}
fn void? NativeConditionVariable.destroy(&cond) @maydiscard
{
if (cond.event_one) win32::closeHandle(cond.event_one);
if (cond.event_all) win32::closeHandle(cond.event_all);
win32::deleteCriticalSection(&cond.waiters_count_lock);
// Nothing to do
}
fn void? NativeConditionVariable.signal(&cond)
{
win32::enterCriticalSection(&cond.waiters_count_lock);
bool have_waiters = cond.waiters_count > 0;
win32::leaveCriticalSection(&cond.waiters_count_lock);
if (have_waiters && !win32::setEvent(cond.event_one)) return thread::SIGNAL_FAILED?;
win32::wakeConditionVariable(&cond.cond_var);
}
fn void? NativeConditionVariable.broadcast(&cond)
{
win32::enterCriticalSection(&cond.waiters_count_lock);
bool have_waiters = cond.waiters_count > 0;
win32::leaveCriticalSection(&cond.waiters_count_lock);
if (have_waiters && !win32::setEvent(cond.event_all)) return thread::SIGNAL_FAILED?;
win32::wakeAllConditionVariable(&cond.cond_var);
}
fn void? timedwait(NativeConditionVariable* cond, NativeMutex* mtx, uint timeout) @private
{
win32::enterCriticalSection(&cond.waiters_count_lock);
cond.waiters_count++;
win32::leaveCriticalSection(&cond.waiters_count_lock);
mtx.unlock()!;
uint result = win32::waitForMultipleObjects(2, &cond.events, 0, timeout);
switch (result)
Win32_DWORD owner_thread = mtx.owner_thread;
if (mtx.locks != 1 || owner_thread != win32::getCurrentThreadId()) return thread::WAIT_FAILED?;
mtx.owner_thread = 0;
mtx.locks = 0;
defer
{
case win32::WAIT_TIMEOUT:
mtx.lock()!;
return thread::WAIT_TIMEOUT?;
case win32::WAIT_FAILED:
mtx.lock()!;
return thread::WAIT_FAILED?;
default:
break;
mtx.owner_thread = owner_thread;
mtx.locks = 1;
}
win32::enterCriticalSection(&cond.waiters_count_lock);
cond.waiters_count--;
// If event all && no waiters
bool last_waiter = result == 1 && !cond.waiters_count;
win32::leaveCriticalSection(&cond.waiters_count_lock);
if (last_waiter)
if (!win32::sleepConditionVariableSRW(&cond.cond_var, &mtx.srw_lock, timeout, 0))
{
if (!win32::resetEvent(cond.event_all))
if (win32::getLastError() == win32::ERROR_TIMEOUT)
{
mtx.lock()!;
return thread::WAIT_FAILED?;
return thread::WAIT_TIMEOUT?;
}
return thread::WAIT_FAILED?;
}
mtx.lock()!;
}
<*
@require mtx.initialized : "Mutex was not initialized"
*>
fn void? NativeConditionVariable.wait(&cond, NativeMutex* mtx) @inline
{
return timedwait(cond, mtx, win32::INFINITE) @inline;
}
<*
@require mtx.initialized : "Mutex was not initialized"
*>
fn void? NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms) @inline
{
if (ms > uint.max) ms = uint.max;
@@ -267,30 +341,12 @@ fn void native_thread_yield()
fn void NativeOnceFlag.call_once(&flag, OnceFn func)
{
while (@volatile_load(flag.status) < 3)
var callback = fn Win32_BOOL(Win32_INIT_ONCE* init_once, void* parameter, void** context)
{
switch (@volatile_load(flag.status))
{
case 0:
if (mem::compare_exchange_volatile(&flag.status, 1, 0, AtomicOrdering.SEQ_CONSISTENT, AtomicOrdering.SEQ_CONSISTENT) == 0)
{
win32::initializeCriticalSection(&flag.lock);
win32::enterCriticalSection(&flag.lock);
@volatile_store(flag.status, 2);
func();
@volatile_store(flag.status, 3);
win32::leaveCriticalSection(&flag.lock);
return;
}
break;
case 1:
break;
case 2:
win32::enterCriticalSection(&flag.lock);
win32::leaveCriticalSection(&flag.lock);
break;
}
}
((OnceFn)parameter)();
return 1;
};
win32::initOnceExecuteOnce(&flag.init_once, callback, func, null);
}
fn int? NativeThread.join(thread)

View File

@@ -2,16 +2,16 @@ module std::thread;
import std::thread::os;
import std::time;
typedef MutexType = int;
const MutexType MUTEX_PLAIN = 0;
const MutexType MUTEX_TIMED = 1;
const MutexType MUTEX_RECURSIVE = 2;
bitstruct MutexType : int
{
bool timed;
bool recursive;
}
typedef Mutex = NativeMutex;
typedef TimedMutex = inline Mutex;
typedef RecursiveMutex = inline Mutex;
typedef TimedRecursiveMutex = inline Mutex;
typedef TimedMutex = NativeTimedMutex;
typedef TimedRecursiveMutex = inline TimedMutex;
typedef ConditionVariable = NativeConditionVariable;
typedef Thread = inline NativeThread;
typedef OnceFlag = NativeOnceFlag;
@@ -33,16 +33,21 @@ faultdef
INTERRUPTED,
CHANNEL_CLOSED;
macro void? Mutex.init(&mutex) => NativeMutex.init((NativeMutex*)mutex, MUTEX_PLAIN);
macro void? TimedMutex.init(&mutex) => NativeMutex.init((NativeMutex*)mutex, MUTEX_TIMED);
macro void? RecursiveMutex.init(&mutex) => NativeMutex.init((NativeMutex*)mutex, MUTEX_RECURSIVE);
macro void? TimedRecursiveMutex.init(&mutex) => NativeMutex.init((NativeMutex*)mutex, MUTEX_TIMED | MUTEX_RECURSIVE);
macro void? Mutex.init(&mutex) => NativeMutex.init((NativeMutex*)mutex, {});
macro void? RecursiveMutex.init(&mutex) => NativeMutex.init((NativeMutex*)mutex, {.recursive});
macro void? Mutex.destroy(&mutex) => NativeMutex.destroy((NativeMutex*)mutex);
macro void? Mutex.lock(&mutex) => NativeMutex.lock((NativeMutex*)mutex);
macro void? TimedMutex.lock_timeout(&mutex, ulong ms) => NativeMutex.lock_timeout((NativeMutex*)mutex, ms);
macro void? TimedRecursiveMutex.lock_timeout(&mutex, ulong ms) => NativeMutex.lock_timeout((NativeMutex*)mutex, ms);
macro bool Mutex.try_lock(&mutex) => NativeMutex.try_lock((NativeMutex*)mutex);
macro bool Mutex.try_lock(&mutex) => NativeMutex.try_lock((NativeMutex*)mutex);
macro void? Mutex.unlock(&mutex) => NativeMutex.unlock((NativeMutex*)mutex);
macro void? TimedMutex.init(&mutex) => NativeTimedMutex.init((NativeTimedMutex*)mutex, {.timed});
macro void? TimedRecursiveMutex.init(&mutex) => NativeTimedMutex.init((NativeTimedMutex*)mutex, {.timed, .recursive});
macro void? TimedMutex.destroy(&mutex) => NativeTimedMutex.destroy((NativeTimedMutex*)mutex);
macro void? TimedMutex.lock(&mutex) => NativeTimedMutex.lock((NativeTimedMutex*)mutex);
macro void? TimedMutex.lock_timeout(&mutex, ulong ms) => NativeTimedMutex.lock_timeout((NativeTimedMutex*)mutex, ms);
macro bool TimedMutex.try_lock(&mutex) => NativeTimedMutex.try_lock((NativeTimedMutex*)mutex);
macro void? TimedMutex.unlock(&mutex) => NativeTimedMutex.unlock((NativeTimedMutex*)mutex);
macro void Mutex.@in_lock(&mutex; @body)
{
(void)mutex.lock();

View File

@@ -13,22 +13,37 @@ fn Clock now()
fn NanoDuration Clock.mark(&self)
{
Clock mark = now();
NanoDuration diff = (NanoDuration)(mark - *self);
NanoDuration diff = mark - *self;
*self = mark;
return diff;
}
fn Clock Clock.add_nano_duration(self, NanoDuration nano)
fn Clock Clock.add_nano_duration(self, NanoDuration nano) @operator_s(+) @inline
{
return (Clock)((NanoDuration)self + nano);
}
fn Clock Clock.add_duration(self, Duration duration)
fn Clock Clock.sub_nano_duration(self, NanoDuration nano) @operator(-) @inline
{
return (Clock)((NanoDuration)self - nano);
}
fn Clock Clock.add_duration(self, Duration duration) @operator_s(+) @inline
{
return self.add_nano_duration(duration.to_nano());
}
fn NanoDuration Clock.to_now(self)
fn Clock Clock.sub_duration(self, Duration duration) @operator(-) @inline
{
return (NanoDuration)(now() - self);
return self.sub_nano_duration(duration.to_nano());
}
fn NanoDuration Clock.nano_diff(self, Clock other) @operator(-) @inline
{
return (NanoDuration)self - (NanoDuration)other;
}
fn NanoDuration Clock.to_now(self) @inline
{
return (NanoDuration)now() - (NanoDuration)self;
}

View File

@@ -106,7 +106,7 @@ fn TzDateTime DateTime.to_gmt_offset(self, int gmt_offset)
*>
fn TzDateTime TzDateTime.to_gmt_offset(self, int gmt_offset) {
if (self.gmt_offset == gmt_offset) return self;
Time time = self.time + (Time)gmt_offset * (Time)time::SEC;
Time time = self.time + gmt_offset * time::SEC;
DateTime dt = from_time(time);
dt.time = self.time;
return { dt, gmt_offset };
@@ -149,6 +149,8 @@ fn void DateTime.set_time(&self, Time time)
self.time = time;
}
fn DateTime DateTime.add_us(&self, Duration d) @operator_s(+) => from_time(self.time + d);
fn DateTime DateTime.sub_us(&self, Duration d) @operator(-) => from_time(self.time - d);
fn DateTime DateTime.add_seconds(&self, int seconds) => from_time(self.time.add_seconds(seconds));
fn DateTime DateTime.add_minutes(&self, int minutes) => from_time(self.time.add_minutes(minutes));
fn DateTime DateTime.add_hours(&self, int hours) => from_time(self.time.add_hours(hours));
@@ -187,6 +189,8 @@ fn DateTime DateTime.add_months(&self, int months)
}
fn TzDateTime TzDateTime.add_us(&self, Duration d) @operator_s(+) => self.date_time.add_us(d).to_gmt_offset(self.gmt_offset);
fn TzDateTime TzDateTime.sub_us(&self, Duration d) @operator(-) => self.date_time.sub_us(d).to_gmt_offset(self.gmt_offset);
fn TzDateTime TzDateTime.add_seconds(&self, int seconds) => self.date_time.add_seconds(seconds).to_gmt_offset(self.gmt_offset);
fn TzDateTime TzDateTime.add_minutes(&self, int minutes) => self.date_time.add_minutes(minutes).to_gmt_offset(self.gmt_offset);
fn TzDateTime TzDateTime.add_hours(&self, int hours) => self.date_time.add_hours(hours).to_gmt_offset(self.gmt_offset);
@@ -248,7 +252,8 @@ fn double DateTime.diff_sec(self, DateTime from)
{
return (double)self.time.diff_us(from.time) / (double)time::SEC;
}
fn Duration DateTime.diff_us(self, DateTime from)
fn Duration DateTime.diff_us(self, DateTime from) @operator(-)
{
return self.time.diff_us(from.time);
}

View File

@@ -1,23 +1,12 @@
module std::time::os @if(env::DARWIN);
struct Darwin_mach_timebase_info
{
uint numer;
uint denom;
}
alias Darwin_mach_timebase_info_t = Darwin_mach_timebase_info;
alias Darwin_mach_timebase_info_data_t = Darwin_mach_timebase_info;
extern fn void mach_timebase_info(Darwin_mach_timebase_info_data_t* timebase);
extern fn ulong mach_absolute_time();
import std::os::darwin;
fn Clock native_clock()
{
static Darwin_mach_timebase_info_data_t timebase;
if (!timebase.denom)
{
mach_timebase_info(&timebase);
darwin::mach_timebase_info(&timebase);
}
return (Clock)(mach_absolute_time() * timebase.numer / timebase.denom);
return (Clock)(darwin::mach_absolute_time() * timebase.numer / timebase.denom);
}

View File

@@ -1,82 +1,17 @@
module std::time::os @if(env::POSIX);
import libc;
extern fn CInt clock_gettime(int type, TimeSpec *time);
import libc, std::os::posix;
fn Time native_timestamp()
{
TimeSpec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return (Time)(ts.s * 1_000_000i64 + ts.ns / 1_000i64);
posix::clock_gettime(posix::CLOCK_REALTIME, &ts);
return (Time)(ts.s * 1_000_000L + ts.ns / 1_000L);
}
fn Clock native_clock() @if(!env::DARWIN)
{
TimeSpec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (Clock)((ulong)ts.s * 1_000_000_000u64 + (ulong)ts.ns);
posix::clock_gettime(posix::CLOCK_MONOTONIC, &ts);
return (Clock)((ulong)ts.s * 1_000_000_000UL + (ulong)ts.ns);
}
module std::time::os @if(env::OPENBSD);
const CLOCK_REALTIME = 0;
const CLOCK_PROCESS_CPUTIME_ID = 2;
const CLOCK_MONOTONIC = 3;
const CLOCK_THREAD_CPUTIME_ID = 4;
const CLOCK_UPTIME = 5;
const CLOCK_BOOTTIME = 6;
module std::time::os @if(env::FREEBSD);
const CLOCK_REALTIME = 0;
const CLOCK_VIRTUAL = 1;
const CLOCK_PROF = 2;
const CLOCK_MONOTONIC = 4;
const CLOCK_UPTIME = 5;
const CLOCK_UPTIME_PRECISE = 7;
const CLOCK_UPTIME_FAST = 8;
const CLOCK_REALTIME_PRECISE = 9;
const CLOCK_REALTIME_FAST = 10;
const CLOCK_MONOTONIC_PRECISE = 11;
const CLOCK_MONOTONIC_FAST = 12;
const CLOCK_SECOND = 13;
const CLOCK_THREAD_CPUTIME_ID = 14;
const CLOCK_PROCESS_CPUTIME_ID = 15;
const CLOCK_BOOTTIME = CLOCK_UPTIME;
const CLOCK_REALTIME_COARSE = CLOCK_REALTIME_FAST;
const CLOCK_MONOTONIC_COARSE = CLOCK_MONOTONIC_FAST;
module std::time::os @if(env::NETBSD);
const CLOCK_REALTIME = 0;
const CLOCK_VIRTUAL = 1;
const CLOCK_PROF = 2;
const CLOCK_MONOTONIC = 3;
const CLOCK_THREAD_CPUTIME_ID = 0x20000000;
const CLOCK_PROCESS_CPUTIME_ID = 0x40000000;
module std::time::os @if(env::WASI);
// Not implemented
const CLOCK_REALTIME = 0;
const CLOCK_MONOTONIC = 0;
module std::time::os @if(env::DARWIN);
const CLOCK_REALTIME = 0;
const CLOCK_MONOTONIC = 6;
const CLOCK_MONOTONIC_RAW = 4;
const CLOCK_MONOTONIC_RAW_APPROX = 5;
const CLOCK_UPTIME_RAW = 8;
const CLOCK_UPTIME_RAW_APPROX = 9;
const CLOCK_PROCESS_CPUTIME_ID = 12;
const CLOCK_THREAD_CPUTIME_ID = 16;
module std::time::os @if(env::LINUX || env::ANDROID);
const CLOCK_REALTIME = 0;
const CLOCK_MONOTONIC = 1;
const CLOCK_PROCESS_CPUTIME_ID = 2;
const CLOCK_THREAD_CPUTIME_ID = 3;
const CLOCK_MONOTONIC_RAW = 4;
const CLOCK_REALTIME_COARSE = 5;
const CLOCK_MONOTONIC_COARSE = 6;
const CLOCK_BOOTTIME = 7;
const CLOCK_REALTIME_ALARM = 8;
const CLOCK_BOOTTIME_ALARM = 9;
const CLOCK_TAI = 11;

View File

@@ -2,13 +2,8 @@ module std::time::os @if(env::WIN32);
import std::os::win32;
import std::math;
extern fn void win32_GetSystemTimeAsFileTime(Win32_FILETIME* time) @extern("GetSystemTimeAsFileTime");
extern fn Win32_BOOL win32_QueryPerformanceFrequency(Win32_LARGE_INTEGER* lpFrequency) @extern("QueryPerformanceFrequency");
extern fn Win32_BOOL win32_QueryPerformanceCounter(Win32_LARGE_INTEGER* lpPerformanceCount) @extern("QueryPerformanceCounter");
const ulong WINDOWS_TICK_US @local = 10;
const ulong WIN_TO_UNIX_EPOCH_US @local = 116444736000000000u64 / WINDOWS_TICK_US;
const ulong WIN_TO_UNIX_EPOCH_US @local = 116444736000000000UL / WINDOWS_TICK_US;
fn Clock native_clock()
{
@@ -17,17 +12,17 @@ fn Clock native_clock()
ulong mult = 0;
if (!freq.quadPart)
{
if (!win32_QueryPerformanceFrequency(&freq)) return 0;
if (!win32::queryPerformanceFrequency(&freq)) return 0;
}
Win32_LARGE_INTEGER counter @noinit;
if (!win32_QueryPerformanceCounter(&counter)) return 0;
if (!win32::queryPerformanceCounter(&counter)) return 0;
return (Clock)counter.quadPart.muldiv(1_000_000_000, freq.quadPart);
}
fn Time native_timestamp()
{
Win32_FILETIME ft @noinit;
win32_GetSystemTimeAsFileTime(&ft);
win32::getSystemTimeAsFileTime(&ft);
ulong result = (ulong)ft.dwHighDateTime << 32 | ft.dwLowDateTime;
return (Time)(result / WINDOWS_TICK_US - WIN_TO_UNIX_EPOCH_US);
}

View File

@@ -19,11 +19,11 @@ const Duration MONTH = 30 * DAY;
const Duration YEAR = 36525 * DAY / 100;
const Duration FOREVER = long.max;
fn Duration us(long l) @inline => (Duration)l * US;
fn Duration ms(long l) @inline => (Duration)l * MS;
fn Duration sec(long l) @inline => (Duration)l * SEC;
fn Duration min(long l) @inline => (Duration)l * MIN;
fn Duration hour(long l) @inline => (Duration)l * HOUR;
fn Duration us(long l) @inline => l * US;
fn Duration ms(long l) @inline => l * MS;
fn Duration sec(long l) @inline =>l * SEC;
fn Duration min(long l) @inline => l * MIN;
fn Duration hour(long l) @inline => l * HOUR;
fn Duration from_float(double s) @inline => (Duration)(s * (double)SEC);
struct DateTime
@@ -82,21 +82,22 @@ fn Time now()
$endif
}
fn Time Time.add_seconds(time, long seconds) => time + (Time)(seconds * (long)SEC);
fn Time Time.add_minutes(time, long minutes) => time + (Time)(minutes * (long)MIN);
fn Time Time.add_hours(time, long hours) => time + (Time)(hours * (long)HOUR);
fn Time Time.add_days(time, long days) => time + (Time)(days * (long)DAY);
fn Time Time.add_weeks(time, long weeks) => time + (Time)(weeks * (long)WEEK);
fn Time Time.add_duration(time, Duration duration) => time + (Time)duration;
fn Time Time.add_seconds(time, long seconds) => time + seconds * SEC;
fn Time Time.add_minutes(time, long minutes) => time + minutes * MIN;
fn Time Time.add_hours(time, long hours) => time + hours * HOUR;
fn Time Time.add_days(time, long days) => time + days * DAY;
fn Time Time.add_weeks(time, long weeks) => time + weeks * WEEK;
fn Time Time.add_duration(time, Duration duration) @operator_s(+) @inline => (Time)((long)time + (long)duration);
fn Time Time.sub_duration(time, Duration duration) @operator(-) @inline => (Time)((long)time - (long)duration);
fn int Time.compare_to(time, Time other)
{
if (time == other) return 0;
if ((long)time == (long)other) return 0;
return time > other ? 1 : -1;
}
fn double Time.to_seconds(time) => (long)time / (double)SEC;
fn Duration Time.diff_us(time, Time other) => (Duration)(time - other);
fn Duration Time.diff_us(time, Time other) @operator(-) => (Duration)((long)time - (long)other);
fn double Time.diff_sec(time, Time other) => (long)time.diff_us(other) / (double)SEC;
fn double Time.diff_min(time, Time other) => (long)time.diff_us(other) / (double)MIN;
fn double Time.diff_hour(time, Time other) => (long)time.diff_us(other) / (double)HOUR;
@@ -108,6 +109,7 @@ fn long NanoDuration.to_ms(nd) => (long)nd / 1_000_000;
fn Duration NanoDuration.to_duration(nd) => (Duration)nd / 1_000;
fn NanoDuration Duration.to_nano(td) => (NanoDuration)td * 1_000;
fn long Duration.to_ms(td) => (long)td / 1_000;
macro Duration Duration.mult(#td, long #val) @operator_s(*) @safemacro => (Duration)((long)#td * #val);
fn usz? NanoDuration.to_format(&self, Formatter* formatter) @dynamic
{

View File

@@ -1,5 +1,126 @@
# C3C Release Notes
## 0.7.2 Change list
### Changes / improvements
- Better default assert messages when no message is specified #2122
- Add `--run-dir`, to specify directory for running executable using `compile-run` and `run` #2121.
- Add `run-dir` to project.json.
- Add `quiet` to project.json.
- Deprecate uXX and iXX bit suffixes.
- Add experimental LL / ULL suffixes for int128 and uint128 literals.
- Allow the right hand side of `|||` and `&&&` be runtime values.
- Added `@rnd()` compile time random function (using the `$$rnd()` builtin). #2078
- Add `math::@ceil()` compile time ceil function. #2134
- Improve error message when using keywords as functions/macros/variables #2133.
- Deprecate `MyEnum.elements`.
- Deprecate `SomeFn.params`.
- Improve error message when encountering recursively defined structs. #2146
- Limit vector max size, default is 4096 bits, but may be increased using --max-vector-size.
- Allow the use of `has_tagof` on builtin types.
- `@jump` now included in `--list-attributes` #2155.
- Add `$$matrix_mul` and `$$matrix_transpose` builtins.
- Add `d` as floating point suffix for `double` types.
- Deprecate `f32`, `f64` and `f128` suffixes.
- Allow recursive generic modules.
- Add deprecation for `@param foo "abc"`.
- Add `--header-output` and `header-output` options for controlling header output folder.
- Generic faults is disallowed.
### Fixes
- Assert triggered when casting from `int[2]` to `uint[2]` #2115
- Assert when a macro with compile time value is discarded, e.g. `foo();` where `foo()` returns an untyped list. #2117
- Fix stringify for compound initializers #2120.
- Fix No index OOB check for `[:^n]` #2123.
- Fix regression in Time diff due to operator overloading #2124.
- attrdef with any invalid name causes compiler assert #2128.
- Correctly error on `@attrdef Foo = ;`.
- Contract on trying to use Object without initializing it.
- Variable aliases of aliases would not resolve correctly. #2131
- Variable aliases could not be assigned to.
- Some folding was missing in binary op compile time resolution #2135.
- Defining an enum like `ABC = { 1 2 }` was accidentally allowed.
- Using a non-const as the end range for a bitstruct would trigger an assert.
- Incorrect parsing of ad hoc generic types, like `Foo{int}****` #2140.
- $define did not correctly handle generic types #2140.
- Incorrect parsing of call attributes #2144.
- Error when using named argument on trailing macro body expansion #2139.
- Designated const initializers with `{}` would overwrite the parent field.
- Empty default case in @jump switch does not fallthrough #2147.
- `&&&` was accidentally available as a valid prefix operator.
- Missing error on default values for body with default arguments #2148.
- `--path` does not interact correctly with relative path arguments #2149.
- Add missing `@noreturn` to `os::exit`.
- Implicit casting from struct to interface failure for inheriting interfaces #2151.
- Distinct types could not be used with tagof #2152.
- `$$sat_mul` was missing.
- `for` with incorrect `var` declaration caused crash #2154.
- Check pointer/slice/etc on `[out]` and `&` params. #2156.
- Compiler didn't check foreach over flexible array member, and folding a flexible array member was allowed #2164.
- Too strict project view #2163.
- Bug using `#foo` arguments with `$defined` #2173
- Incorrect ensure on String.split.
- Removed the naive check for compile time modification, which fixes #1997 but regresses in detection.
### Stdlib changes
- Added `String.quick_ztr` and `String.is_zstr`
- std::ascii moved into std::core::ascii. Old _m variants are deprecated, as is uint methods.
- Add `String.tokenize_all` to replace the now deprecated `String.splitter`
- Add `String.count` to count the number of instances of a string.
- Add `String.replace` and `String.treplace` to replace substrings within a string.
- Add `Duration * Int` and `Clock - Clock` overload.
- Add `DateTime + Duration` overloads.
- Add `Maybe.equals` and respective `==` operator when the inner type is equatable.
- Add `inherit_stdio` option to `SubProcessOptions` to inherit parent's stdin, stdout, and stderr instead of creating pipes. #2012
- Remove superfluous `cleanup` parameter in `os::exit` and `os::fastexit`.
- Add `extern fn ioctl(CInt fd, ulong request, ...)` binding to libc;
## 0.7.1 Change list
### Changes / improvements
- Better errors on some common casting mistakes (pointer->slice, String->ZString, deref pointer->array) #2064.
- Better errors trying to convert an enum to an int and vice versa.
- Function `@require` checks are added to the caller in safe mode. #186
- Improved error message when narrowing isn't allowed.
- Operator overloading for `+ - * / % & | ^ << >> ~ == != += -= *= /= %= &= |= ^= <<= >>=`
- Add `@operator_r` and `@operator_s` attributes.
- More stdlib tests: `sincos`, `ArenaAllocator`, `Slice2d`.
- Make aliases able to use `@deprecated`.
- Refactored stdlib file organization.
- Allow `@if` on locals.
- String str = "" is now guaranteed to be null terminated. #2083
- Improved error messages on `Foo { 3, abc }` #2099.
- `Foo[1..2] = { .baz = 123 }` inference now works. #2095
- Deprecated old inference with slice copy. Copying must now ensure a slicing operator at the end of the right hand side: `foo[1..2] = bar[..]` rather than the old `foo[1..2] = bar`. The old behaviour can be mostly retained with `--use-old-slice-copy`).
- Added `Enum.lookup` and `Enum.lookup_field`.
- `c3c build` picks first target rather than the first executable #2105.
- New Win32 Mutex, ConditionVariable and OnceFlag implementation
### Fixes
- Trying to cast an enum to int and back caused the compiler to crash.
- Incorrect rounding at compile time going from double to int.
- Regression with invalid setup of the WASM temp allocator.
- Correctly detect multiple overloads of the same type.
- ABI bug on x64 Linux / MacOS when passing a union containing a struct of 3 floats. #2087
- Bug with slice acces as inline struct member #2088.
- `@if` now does implicit conversion to bool like `$if`. #2086
- Fix broken enum inline -> bool conversions #2094.
- `@if` was ignored on attrdef, regression 0.7 #2093.
- `@ensure` was not included when the function doesn't return a value #2098.
- Added missing `@clone_aligned` and add checks to `@tclone`
- Comparing a distinct type with an enum with an inline distinct type failed unexpectedly.
- The `%s` would not properly print function pointers.
- Compiler crash when passing an untyped list as an argument to `assert` #2108.
- `@ensure` should be allowed to read "out" variables. #2107
- Error message for casting generic to incompatible type does not work properly with nested generics #1953
- Fixed enum regression after 0.7.0 enum change.
- ConditionVariable now properly works on Win32
### Stdlib changes
- Hash functions for integer vectors and arrays.
- Prefer `math::I` and `math::I_F` for `math::IMAGINARY` and `math::IMAGINARYF` the latter is deprecated.
- Add `array::contains` to check for a value in an array or slice.
## 0.7.0 Change list
### Changes / improvements
@@ -18,7 +139,7 @@
- Removal of "any-switch".
- Allow swizzling assign, eg. `abc.xz += { 5, 10 };`
- Added `$$wstr16` and `$$wstr32` builtins.
- `$foreach` "()" replaced by trailing ":" `$foreach ($x, $y : $foo)` -> `$foreach $x, $y : $foo:`
- `$foreach` "()" replaced by trailing ":" `$foreach ($x, $y : $foo)` -> `$foreach $x, $y : $foo:`
- `$for` "()" replaced by trailing ":" `$for (var $x = 0; $x < FOO; $x++)` -> `$for var $x = 0; $x < FOO; $x++:`
- `$switch` "()" replaced by trailing ":" `$switch ($Type)` -> `$switch $Type:`
- Empty `$switch` requires trailing ":" `$switch` -> `$switch:`
@@ -71,7 +192,7 @@
- `mem::temp_new` changed to `mem::tnew`.
- `mem::temp_alloc` and related changed to `mem::talloc`.
- `mem::temp_new_array` changed to `mem::temp_array`.
- Add `ONHEAP` variants for List/HashMap for initializing global maps on the heap.
- Add `ONHEAP` variants for List/HashMap for initializing global maps on the heap.
- Remove Vec2 and other aliases from std::math. Replace `.length_sq()` with `sq_magnitude()`
- Change all hash functions to have a common `hash` function.
- `@wstring`, `@wstring32`, `@char32` and `@char16` compile time macros added.

View File

@@ -1,9 +1,11 @@
module std::container::faults;
faultdef KEY_NOT_FOUND;
module std::container::map{Key, Type};
import std::core::builtin;
import std::io;
faultdef KEY_NOT_FOUND;
struct Entry
{
Key key;
@@ -33,17 +35,17 @@ fn void Map.init(Map *map, uint capacity = 128)
fn Type? Map.valueForKey(Map *map, Key key)
{
if (!map.map) return KEY_NOT_FOUND?;
if (!map.map) return faults::KEY_NOT_FOUND?;
uint hash = key.hash();
usz pos = hash & map.mod;
Entry* entry = &map.map[pos];
if (!entry) return KEY_NOT_FOUND?;
if (!entry) return faults::KEY_NOT_FOUND?;
while (entry)
{
if (entry.hash == hash && entry.key == key) return entry.value;
entry = entry.next;
}
return KEY_NOT_FOUND?;
return faults::KEY_NOT_FOUND?;
}
fn Type? Map.set(Map *map, Key key, Type value) @maydiscard
@@ -65,7 +67,7 @@ fn Type? Map.set(Map *map, Key key, Type value) @maydiscard
entry.value = value;
entry.hash = hash;
entry.key = key;
return KEY_NOT_FOUND?;
return faults::KEY_NOT_FOUND?;
}
if (entry.hash == hash && entry.key == key)
{
@@ -85,7 +87,7 @@ fn Type? Map.set(Map *map, Key key, Type value) @maydiscard
new.next = null;
new.used = true;
entry.next = new;
return KEY_NOT_FOUND?;
return faults::KEY_NOT_FOUND?;
}
}

View File

@@ -1,7 +1,7 @@
module test;
import libc;
faultde NOPE;
faultdef NOPE;
fn int? eventually_succeed()
{

View File

@@ -23,7 +23,7 @@ P [Pp][+-]?{D}+
B64 [ \t\v\n\f]?[A-Za-z0-9+/][ \t\v\n\fA-Za-z0-9+/=]+
HEX [ \t\v\n\f]?[A-Fa-f0-9][ \t\v\n\fA-Fa-f0-9]+
INTTYPE ([ui](8|16|32|64|128)|[Uu][Ll]?|[Ll])
REALTYPE ([f](8|16|32|64|128)?)
REALTYPE ([fd])
INT {D}(_?{D})*
HINT {H}(_?{H})*
OINT {O}(_?{O})*
@@ -112,10 +112,11 @@ typedef struct {
[^*]+ { }
}
\/\/.* { }
"alias" { return(ALIAS); }
"any" { return(ANY); }
"anyfault" { return(ANYFAULT); }
"asm" { return(ASM); }
"assert" { return(ASSERT); }
"attrdef" { return(ATTRDEF); }
"bitstruct" { return(BITSTRUCT); }
"bool" { return(BOOL); }
"break" { return(BREAK); }
@@ -124,10 +125,8 @@ typedef struct {
"char" { return(CHAR); }
"const" { return(CONST); }
"continue" { return(CONTINUE); }
"def" { return(DEF); }
"default" { return(DEFAULT); }
"defer" { return(DEFER); }
"distinct" { return(DISTINCT); }
"do" { return(DO); }
"double" { return(DOUBLE); }
"else" { return(ELSE); }
@@ -135,6 +134,7 @@ typedef struct {
"extern" { return(EXTERN); }
"false" { return(FALSE); }
"fault" { return(FAULT); }
"faultdef" { return(FAULTDEF); }
"float" { return(FLOAT); }
"bfloat16" { return(BFLOAT16); }
"float16" { return(FLOAT16); }
@@ -165,6 +165,7 @@ typedef struct {
"tlocal" { return(TLOCAL); }
"true" { return(TRUE); }
"try" { return(TRY); }
"typedef" { return(TYPEDEF); }
"typeid" { return(TYPEID); }
"uint" { return(UINT); }
"uint128" { return(UINT128); }

View File

@@ -14,13 +14,13 @@ void yyerror(YYLTYPE * yylloc_param , yyscan_t yyscanner, const char *yymsgp);
%token IDENT HASH_IDENT CT_IDENT CONST_IDENT
%token TYPE_IDENT CT_TYPE_IDENT
%token AT_TYPE_IDENT AT_IDENT CT_INCLUDE
%token STRING_LITERAL INTEGER
%token STRING_LITERAL INTEGER ALIAS
%token CT_AND_OP CT_OR_OP CT_CONCAT_OP CT_EXEC
%token INC_OP DEC_OP SHL_OP SHR_OP LE_OP GE_OP EQ_OP NE_OP
%token AND_OP OR_OP MUL_ASSIGN DIV_ASSIGN MOD_ASSIGN ADD_ASSIGN
%token SUB_ASSIGN SHL_ASSIGN SHR_ASSIGN AND_ASSIGN
%token XOR_ASSIGN OR_ASSIGN VAR NUL ELVIS NEXTCASE ANYFAULT
%token MODULE IMPORT DEF EXTERN
%token XOR_ASSIGN OR_ASSIGN VAR NUL ELVIS NEXTCASE
%token MODULE IMPORT TYPEDEF ATTRDEF FAULTDEF EXTERN
%token CHAR SHORT INT LONG FLOAT DOUBLE CONST VOID USZ ISZ UPTR IPTR ANY
%token ICHAR USHORT UINT ULONG BOOL INT128 UINT128 FLOAT16 FLOAT128 BFLOAT16
%token TYPEID BITSTRUCT STATIC BANGBANG AT_CONST_IDENT HASH_TYPE_IDENT
@@ -536,7 +536,7 @@ base_type_no_user_defined
| UPTR
| ISZ
| USZ
| ANYFAULT
| FAULT
| ANY
| TYPEID
| CT_TYPE_IDENT
@@ -567,10 +567,8 @@ type_suffix
| '[' constant_expr ']'
| '[' ']'
| '[' '*' ']'
| '[' '?' ']'
| LVEC constant_expr RVEC
| LVEC '*' RVEC
| LVEC '?' RVEC
;
type
: base_type
@@ -623,17 +621,17 @@ ct_switch_body
;
ct_for_stmt
: CT_FOR '(' for_cond ')' opt_stmt_list CT_ENDFOR
: CT_FOR for_cond ':' opt_stmt_list CT_ENDFOR
;
ct_foreach_stmt
: CT_FOREACH '(' CT_IDENT ':' expr ')' opt_stmt_list CT_ENDFOREACH
| CT_FOREACH '(' CT_IDENT ',' CT_IDENT ':' expr ')' opt_stmt_list CT_ENDFOREACH
: CT_FOREACH CT_IDENT ':' expr ':' opt_stmt_list CT_ENDFOREACH
| CT_FOREACH CT_IDENT ',' CT_IDENT ':' expr ':' opt_stmt_list CT_ENDFOREACH
;
ct_switch
: CT_SWITCH '(' constant_expr ')'
| CT_SWITCH '(' type ')'
| CT_SWITCH
: CT_SWITCH constant_expr ':'
| CT_SWITCH type ':'
| CT_SWITCH ':'
;
ct_switch_stmt
@@ -1076,13 +1074,12 @@ enum_declaration
;
faults
: CONST_IDENT
| faults ',' CONST_IDENT
: CONST_IDENT opt_attributes
| faults ',' CONST_IDENT opt_attributes
;
fault_declaration
: FAULT TYPE_IDENT opt_interface_impl opt_attributes '{' faults '}'
| FAULT TYPE_IDENT opt_interface_impl opt_attributes '{' faults ',' '}'
: FAULTDEF faults ';'
;
func_macro_name
@@ -1188,9 +1185,24 @@ global_declaration
| global_storage optional_type IDENT opt_attributes '=' expr ';'
;
attribute_comma_list
: attribute
| attribute_comma_list ',' attribute
;
opt_comma
: ','
| empty
;
define_attribute_body
: empty
| '=' attribute_comma_list opt_comma
;
define_attribute
: AT_TYPE_IDENT '(' parameters ')' opt_attributes '=' '{' opt_attributes '}'
| AT_TYPE_IDENT opt_attributes '=' '{' opt_attributes '}'
: AT_TYPE_IDENT '(' parameters ')' opt_attributes define_attribute_body
| AT_TYPE_IDENT opt_attributes define_attribute_body
;
generic_expr
@@ -1203,15 +1215,15 @@ opt_generic_parameters
;
define_ident
: IDENT '=' path_ident opt_generic_parameters
| CONST_IDENT '=' path_const opt_generic_parameters
| AT_IDENT '=' path_at_ident opt_generic_parameters
: IDENT opt_attributes '=' path_ident opt_generic_parameters
| CONST_IDENT opt_attributes '=' path_const opt_generic_parameters
| AT_IDENT opt_attributes '=' path_at_ident opt_generic_parameters
;
define_declaration
: DEF define_ident opt_attributes ';'
| DEF define_attribute opt_attributes ';'
| DEF TYPE_IDENT opt_attributes '=' typedef_type opt_attributes ';'
: ALIAS define_ident ';'
| ATTRDEF define_attribute ';'
| ALIAS TYPE_IDENT opt_attributes '=' typedef_type opt_attributes ';'
;
interface_body
@@ -1235,7 +1247,7 @@ interface_declaration_name
;
distinct_declaration
: DISTINCT TYPE_IDENT opt_interface_impl opt_attributes '=' opt_inline type ';'
: TYPEDEF TYPE_IDENT opt_interface_impl opt_attributes '=' opt_inline type ';'
;
module_param

View File

@@ -0,0 +1,246 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Project Configuration Schema for C3 project.json",
"description": "Schema for project.json configuration file based on C3 documentation",
"type": "object",
"properties": {
"langrev": {
"type": "string",
"description": "Language version of C3.",
"default": "1"
},
"warnings": {
"type": "array",
"description": "Warnings used for all targets.",
"items": {
"type": "string"
}
},
"dependency-search-paths": {
"type": "array",
"description": "Directories where C3 library files may be found.",
"items": {
"type": "string"
}
},
"dependencies": {
"type": "array",
"description": "Libraries to use for all targets.",
"items": {
"type": "string"
}
},
"authors": {
"type": "array",
"description": "Authors, optionally with email.",
"items": {
"type": "string"
}
},
"version": {
"type": "string",
"description": "Version using semantic versioning."
},
"sources": {
"type": "array",
"description": "Sources compiled for all targets.",
"items": {
"type": "string"
}
},
"c-sources": {
"type": "array",
"description": "C sources if the project also compiles C sources relative to the project file.",
"items": {
"type": "string"
}
},
"c-include-dirs": {
"type": "array",
"description": "Include directories for C sources relative to the project file.",
"items": {
"type": "string"
}
},
"output": {
"type": "string",
"description": "Output location, relative to project file."
},
"targets": {
"type": "object",
"description": "Architecture and OS target specific configurations.",
"additionalProperties": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Executable or library type for the target.",
"enum": ["executable", "static-lib", "dynamic-lib"]
},
"dependencies": {
"type": "array",
"description": "Additional libraries for this target.",
"items": {
"type": "string"
}
},
"sources": {
"type": "array",
"description": "Additional sources for this target.",
"items": {
"type": "string"
}
},
"warnings": {
"type": "array",
"description": "Warnings specific to this target.",
"items": {
"type": "string"
}
}
},
"required": ["type"]
}
},
"cc": {
"type": "string",
"description": "C compiler to use for compiling C sources (if C sources are compiled together with C3 files).",
"default": "cc"
},
"cpu": {
"type": "string",
"description": "CPU name, used for optimizations in the LLVM backend.",
"default": "generic"
},
"debug-info": {
"type": "string",
"description": "Debug information level.",
"enum": ["none", "full", "line-tables"],
"default": "full"
},
"fp-math": {
"type": "string",
"description": "FP math behaviour.",
"enum": ["strict", "relaxed", "fast"],
"default": "strict"
},
"link-libc": {
"type": "boolean",
"description": "Link libc other default libraries.",
"default": true
},
"memory-env": {
"type": "string",
"description": "Memory environment.",
"enum": ["normal", "small", "tiny", "none"],
"default": "normal"
},
"opt": {
"type": "string",
"description": "Optimization setting.",
"enum": ["O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz"],
"default": "O0"
},
"optlevel": {
"type": "string",
"description": "Code optimization level.",
"enum": ["none", "less", "more", "max"],
"default": "none"
},
"optsize": {
"type": "string",
"description": "Code size optimization.",
"enum": ["none", "small", "tiny"],
"default": "none"
},
"reloc": {
"type": "string",
"description": "Relocation model.",
"enum": ["none", "pic", "PIC", "pie", "PIE"],
"default": "none"
},
"trap-on-wrap": {
"type": "boolean",
"description": "Trap on signed and unsigned integer wrapping for testing.",
"default": false
},
"safe": {
"type": "boolean",
"description": "Turn safety (contracts, runtime bounds checking, null pointer checks etc).",
"default": true
},
"single-module": {
"type": "boolean",
"description": "Compile all modules together, enables more inlining.",
"default": true
},
"soft-float": {
"type": "boolean",
"description": "Use / don't use soft float, value is otherwise target default.",
"default": false
},
"strip-unused": {
"type": "boolean",
"description": "Strip unused code and globals from the output.",
"default": true
},
"symtab": {
"type": "integer",
"description": "The size of the symtab, which limits the amount of symbols that can be used.",
"default": 1048576
},
"linker": {
"type": "string",
"description": "Use the system linker.",
"default": "cc"
},
"use-stdlib": {
"type": "boolean",
"description": "Include the standard library.",
"default": true
},
"x86cpu": {
"type": "string",
"description": "Set general level of x64 cpu.",
"enum": [
"baseline",
"ssse3",
"sse4",
"avx1",
"avx2-v1",
"avx2-v2",
"avx512",
"native"
],
"default": "native"
},
"x86vec": {
"type": "string",
"description": "Set max type of vector use.",
"enum": ["none", "mmx", "sse", "avx", "avx512", "native"],
"default": "sse"
},
"features": {
"type": "array",
"description": "List of upper-case constants that can be tested for in the source code using $feature(NAME_OF_FEATURE).",
"items": {
"type": "string"
}
},
"linker-search-paths": {
"type": "array",
"description": "This adds paths for the linker to search, when linking normal C libraries.",
"items": {
"type": "string"
}
},
"linked-libraries": {
"type": "array",
"description": "This is a list of C libraries to link to.",
"items": {
"type": "string"
}
}
},
"required": []
}

View File

@@ -76,7 +76,7 @@ fn void main()
testAllocator(tmem, 126);
testAllocator(tmem, 12600);
ArenaAllocator aa;
aa.init(&&char[1024] {});
aa.init(&&(char[1024]){});
testAllocator(&aa, 126);
io::printn("Test dynamic arena");
DynamicArenaAllocator dynamic_arena;

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