Compare commits

...

1107 Commits

Author SHA1 Message Date
Christoffer Lerno
443ee75ccd - @deprecated in function contracts would be processed twice, causing a compilation error despite being correct. 2026-02-27 01:23:04 +01:00
Christoffer Lerno
f4751737e4 - Detect large temporaries when creating slices on the stack #2665 2026-02-27 01:14:22 +01:00
Christoffer Lerno
596e550882 - Add contract on any_to_enum_ordinal and any_to_int to improve error when passed an empty any. #2977 2026-02-26 22:31:00 +01:00
Christoffer Lerno
54ddc9a81c Remove support for LLVM 17 and 18. Update to 0.7.11 2026-02-26 22:15:34 +01:00
Christoffer Lerno
1a3cdc01ec Fix deprecation warnings not silenced. 2026-02-26 03:30:35 +01:00
Christoffer Lerno
808ce6d605 Fix deprecation warnings not silenced. 2026-02-26 00:28:36 +01:00
Christoffer Lerno
09606a2998 Update hint for deprecation 2026-02-26 00:15:58 +01:00
Christoffer Lerno
0a21323652 Fixes 2026-02-26 00:11:55 +01:00
Christoffer Lerno
c990b5aaf1 Typekind enums are changed CONST_ENUM -> CONSTDEF, DISTINCT -> TYPEDEF. 2026-02-25 23:52:13 +01:00
Christoffer Lerno
98e0985a10 Fix error message on deprecated enum = 2026-02-25 23:27:22 +01:00
Christoffer Lerno
5974bf6cd0 Make first RC 0.7.10 2026-02-25 18:11:42 +01:00
Manu Linares
992890065a vendor-fetch lists available libraries (#2976)
* vendor-fetch lists available libraries

from `github.com/c3lang/vendor` when no library is specified.

* add release notes
2026-02-24 22:55:18 +01:00
Christoffer Lerno
d4dbe27072 Add mention of LLVM libs 2026-02-24 20:00:50 +01:00
Christoffer Lerno
56a11ae3ca Fix text in error message. 2026-02-24 19:07:38 +01:00
konimarti
f1d03c194f distributions: fix tests (#2975)
Remove stochastic elements in the distributions tests and ensure they
are deterministic.

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-24 16:18:53 +01:00
Christoffer Lerno
0085fe73ad Fix test 2026-02-24 15:18:57 +01:00
Christoffer Lerno
a238b62483 Typo 2026-02-24 14:58:28 +01:00
Book-reader
58022e7cca only enable emulated TLS on android compiling natively in termux (#2974) 2026-02-24 00:10:22 +01:00
Christoffer Lerno
d2f046780d - Compiler crash using ?? with a void? macro #2973 2026-02-23 23:28:32 +01:00
Christoffer Lerno
ec65c5761e Fixed test 2026-02-23 19:21:56 +01:00
Christoffer Lerno
0ed917cdc2 Improve error when trying to use an extern const as a compile time constant. #2969 2026-02-23 19:15:30 +01:00
Christoffer Lerno
8373ab0509 Typo in releasenotes. 2026-02-23 19:15:30 +01:00
srkkov
8f7610345d More x86 instructions (#2964)
* Added most x86 cryptographic instructions

* Fixed popcnt test

* Fixed asm_ops_x64_2.c3t test

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-23 15:27:36 +01:00
Zack Puhl
8bd963ecaf Allow Async w/ Subprocess for POSIX Systems (#2953)
* add native_fflush libc passthru

* add `pipe2` which combines `pipe` and `fcntl` - see docs

* POSIX subprocess: incorporate async reader capability and and fflush for writer

* use the real-name O_NONBLOCK since it's available

* typo

* ok... remove `pipe2` and do this with an existing method......

* Update releasenotes.md

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-22 01:59:44 +01:00
Christoffer Lerno
dc52478c09 - Warn on use of visibility modifiers on methods. #2962 2026-02-21 21:10:08 +01:00
Gantsev Denis
e1ec4b1235 add more x86 instructions 2026-02-20 21:23:35 +01:00
Lucas Alves
7c68396c0d Enable default NDK detection for Android x86_64 (#2910)
* android: detect NDK for x86_64 targets

* Treat Android as POSIX in is_posix

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-20 21:07:44 +01:00
Manu Linares
eae7d0c4a1 stdlib: std::compression::zip and std::compression::deflate (#2930)
* stdlib: implement `std::compression::zip` and `std::compression::deflate`

- C3 implementation of DEFLATE (RFC 1951) and ZIP archive handling.
- Support for reading and writing archives using STORE and DEFLATE
methods.
- Decompression supports both fixed and dynamic Huffman blocks.
- Compression using greedy LZ77 matching.
- Zero dependencies on libc.
- Stream-based entry reading and writing.
- Full unit test coverage.

NOTE: This is an initial implementation. Future improvements could be:

- Optimization of the LZ77 matching (lazy matching).
- Support for dynamic Huffman blocks in compression.
- ZIP64 support for large files/archives.
- Support for encryption and additional compression methods.

* optimizations+refactoring

deflate:
- replace linear search with hash-based match finding.
- implement support for dynamic Huffman blocks using the Package-Merge
algorithm.
- add streaming decompression.
- add buffered StreamBitReader.

zip:
- add ZIP64 support.
- add CP437 and UTF-8 filename encoding detection.
- add DOS date/time conversion and timestamp preservation.
- add ZipEntryReader for streaming entry reads.
- implement ZipArchive.extract and ZipArchive.recover helpers.

other:
- Add `set_modified_time` to std::io;
- Add benchmarks and a few more unit tests.

* zip: add archive comment support

add tests

* forgot to rename the benchmark :(

* detect utf8 names on weird zips

fix method not passed to open_writer

* another edge case where directory doesn't end with /

* testing utilities

- detect encrypted zip
- `ZipArchive.open_writer` default to DEFLATE

* fix zip64 creation, add tests

* fix ZIP header endianness for big-endian compatibility

Update ZipLFH, ZipCDH, ZipEOCD, Zip64EOCD, and Zip64Locator structs to
use little-endian bitstruct types from std::core::bitorder

* fix ZipEntryReader position tracking and seek logic ZIP_METHOD_STORE

added a test to track this

* add package-merge algorithm attribution

Thanks @konimarti

* standalone deflate_benchmark.c3 against `miniz`

* fix integer overflows, leaks and improve safety

* a few safety for 32-bit systems and tests

* deflate compress optimization

* improve match finding, hash updates, and buffer usage

* use ulong for zip offsets

* style changes (#18)

* style changes

* update tests

* style changes in `deflate.c3`

* fix typo

* Allocator first. Some changes to deflate to use `copy_to`

* Fix missing conversion on 32 bits.

* Fix deflate stream. Formatting. Prefer switch over if-elseif

* - Stream functions now use long/ulong rather than isz/usz for seek/available.
- `instream.seek` is replaced by `set_cursor` and `cursor`.
- `instream.available`, `cursor` etc are long/ulong rather than isz/usz to be correct on 32-bit.

* Update to constdef

* Fix test

---------

Co-authored-by: Book-reader <thevoid@outlook.co.nz>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-20 20:41:34 +01:00
mmoustafa8108
5055e86518 test: added comprehensive tests for in-string & complex nested comments (#2956)
* test: added comprehensive tests for in-string & complex nested comments

* Properly handle error

---------

Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
2026-02-20 13:24:08 +01:00
Manu Linares
4b13ad692a fixes for RISC-V ABI Implementation #1567 (#2937)
* fixes for RISC-V ABI Implementation #1567

Fixed RISC-V floating-point ABI by correcting the target triple to
`riscv64-unknown-linux-gnu`, adding the `target-abi` module flag, and
ensuring ABI-required CPU features are enabled.

I tested this with:
```bash
build/c3c compile-only --target linux-riscv64 rv_hello.c3
readelf -h obj/linux-riscv64/rv_hello.o | grep Flags
# Output: Flags: 0x5, RVC, double-float ABI
```

```bash
# and qemu because I don't have a riscv machine :/
qemu-riscv64-static -L /usr/riscv64-linux-gnu ./rv_hello
```

---

@lerno I purposedly left these two failing tests to clearly see the
difference.

`test/test_suite/abi/riscv64-lp64-lp64f-abi-1.c3t`
`test/test_suite/abi/riscv64-lp64-abi.c3t`

* improve Linux cross-compilation, specifically for RISC-V

- Implement automatic sysroot and CRT object discovery for RISC-V.
- Fix dynamic linker paths and emulation flags for LLD.
- Link against libgcc to resolve required arithmetic symbols.

* Update tests.

* fix linker CRT detection by centralizing host arch check

use target_host_arch() in linker.c
move target_host_arch() from hostinfo.c to target.c

* missing debug info

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-20 01:53:29 +01:00
Christoffer Lerno
7c81bb35ca Refactored contracts (#2960)
* - Properly support `@deprecated` as contract.
- Support deprecating enum values.
- Refactor contracts

* Always copy enum constants.

* Bugfix.
2026-02-20 01:51:28 +01:00
Christoffer Lerno
5a82f672b5 Update to constdef 2026-02-20 01:13:20 +01:00
Christoffer Lerno
0387d7666d - Trying to slice an indexable type leads to misleading error message #2958 2026-02-20 00:13:45 +01:00
Christoffer Lerno
392c78860d Add bitorder functions store_le, load_le, store_be, store_le. 2026-02-19 23:59:55 +01:00
Christoffer Lerno
585c66100d - Member access on a struct returned by the assignment expression, cause crash #2947 2026-02-19 20:43:45 +01:00
konimarti
8bb974829d math: implement discrete and continuous distributions (#2955)
* math: implement discrete and continuous distributions

Implement a comprehensive set of continuous and discrete probability
distributions with support for PDF, CDF, inverse CDF, random sampling,
mean, and variance calculations.

The following distributions are implemented:
* Normal
* Uniform
* Exponential
* Chi-Squared
* F-Distribution
* Student t
* Binomial
* Poisson

* update releasenotes.md

* Formatting

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-19 20:09:11 +01:00
Christoffer Lerno
6b3139940c Formatting to be consistent. Make unrolling use macro. 2026-02-19 18:22:21 +01:00
Damien Wilson
9efb9b90d1 Minor attribute combination checking (#2918)
* Minor attribute combination checking

* Added tests to the test suite.
2026-02-19 17:58:39 +01:00
soerlemans
152558f5bc Optimized adler32 hashing algorithm. (#2948)
* Optimized adler32 implementations.

 - Adapted adler32 implementation from Crypto++ public domain library.
 - Added unit tests for adler32 hashing algorithm.

* tabified adler32 implementation to match stdlib.

* Formatting to be consistent. Make unrolling use macro.

---------

Co-authored-by: soerlemans <sebasoerlemans+git@gmail.com>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-19 17:51:33 +01:00
Manuel Barrio Linares
7ae4c5a1ab some refactoring of io formatter internals
extracted logic for hex-buffers (%h), string-padding (%s), hex-floats
(%a), and collection printing into helper functions.
2026-02-19 16:39:27 +01:00
Fernando López Guevara
9fbf6bc213 enforce buff channel size > 0 (#2952)
* fix(stdlib): enforce BufferedChannel size > 0 and add regression test

* chore(repo): ignore cmake/test artifacts

* Formatting

---------

Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
2026-02-18 14:34:41 +01:00
Christoffer Lerno
143fa70f87 Remove init for Blake3 2026-02-17 17:43:54 +01:00
Christoffer Lerno
3ac701be0e Improve error message when providing alias with a typeid expression where a type was expected. #2944 2026-02-17 13:02:34 +01:00
Manu Linares
8cb23cff29 split test_hash_vector_macro into per-type functions (#2940)
* split `test_hash_vector_macro` into per-type functions

it went from a single 18367 instructions function, to eleven separate
functions with (1000 to 1600) instructions each
thats 15649 instructions total, 2718 less instructions overall.

```
$ build/c3c compile-test --suppress-run -O1 --print-large-functions -D
SLOW_TESTS test/unit/ 2>&1 | sort -n | grep "test_hash_vector"
    1041 instructions found in
std::core::builtins:test_hash_vector_int128
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1041 instructions found in
std::core::builtins:test_hash_vector_uint128
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1305 instructions found in std::core::builtins:test_hash_vector_long
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1305 instructions found in
std::core::builtins:test_hash_vector_ulong
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1453 instructions found in std::core::builtins:test_hash_vector_int
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1453 instructions found in std::core::builtins:test_hash_vector_uint
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1565 instructions found in
std::core::builtins:test_hash_vector_short
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1565 instructions found in
std::core::builtins:test_hash_vector_ushort
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1629 instructions found in std::core::builtins:test_hash_vector_char
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1629 instructions found in
std::core::builtins:test_hash_vector_ichar
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
    1663 instructions found in std::core::builtins:test_hash_vector_bool
(/home/mb/scripts/c3c/test/unit/stdlib/core/builtintests.c3) - function
is very long.
```

* made the `test_hash_vector_internal` generic
2026-02-17 02:30:30 +01:00
Manuel Barrio Linares
ec6ba8e7ca refactor md5.body function to reduce instruction count
- replaced manual unrolling with loop structures and constant arrays
- instruction count reduced from 12445 to 4016
- maybe about 1 to 2% performance loss on some benchs but take this
number with a grain of salt.
2026-02-16 14:12:54 +01:00
Manuel Barrio Linares
df030ac51c optimize blake3 using a runtime for loop
instruction count went from 60k to 9k
no difference in speed for -O2 or higher
2026-02-16 02:31:47 +01:00
Manu Linares
4b03a84b00 optimize test_ct_intlog2 test and whirlpool hash (#2938)
* optimize `test_ct_intlog2` while still covering all 128 bit positions

* refactor whirlpool to reduce code bloat

replaced the fully unrolled round loop with a runtime loop, reducing
instruction count by 80k in `process_block` and yielding aprox 30%
performance boost due to improved cache locality.

* use compile-time arrays for `test_ct_intlog2`
2026-02-16 01:54:46 +01:00
Christoffer Lerno
5d0c41da6b Refactor dylib name. 2026-02-16 01:21:05 +01:00
Christoffer Lerno
990e9685d2 - Adding the incorrect sized vector to a pointer vector would cause a crash. 2026-02-16 00:39:14 +01:00
Christoffer Lerno
a7309b217e Add --print-large-functions for checking which functions likely dominate the compile time. 2026-02-16 00:13:19 +01:00
Christoffer Lerno
d7cf8fa9ab Reduce macro inlining size. Remove non-const atomic ordering. 2026-02-15 00:43:29 +01:00
Christoffer Lerno
a78a169a17 Slim down the string_to_int tests by 800 MB. 2026-02-15 00:05:35 +01:00
Christoffer Lerno
d80a8629a6 - On assert known false, the message was not show for no-args.
- Fixup in socket_private.c3
2026-02-14 23:49:27 +01:00
Christoffer Lerno
14779bd467 Set CP_UTF8 to underlying number. 2026-02-14 01:49:03 +01:00
Christoffer Lerno
eb80776988 More fixes for typedef @constinit change 2026-02-14 01:37:53 +01:00
Samuel
fac9054f1b Fix array initializer analysis (#2925)
* Fix array initializer analysis: improved semantic checking for arrays with inferred or fixed length,
* Update phrasing

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-13 23:38:07 +01:00
Zack Puhl
768d24d580 [stdlib] Add array even, odd, and unlace macros 2026-02-13 23:19:31 +01:00
Christoffer Lerno
e299a4b630 - Change typedef and const enums to not convert from literals by default. (#2934)
- Add `@constinit` to allow old typedef behaviour.
2026-02-13 20:39:47 +01:00
Christoffer Lerno
bbf89815d6 - Individual warning settings added. 2026-02-13 18:25:19 +01:00
Christoffer Lerno
cc6d552e3d - --safe=no disabled compile-time errors on compile-time known runtime @require checks #2936 2026-02-13 16:34:45 +01:00
Christoffer Lerno
27ceded331 Add a few const char* 2026-02-12 16:51:34 +01:00
Christoffer Lerno
3f279b2f1c Refactoring: make CT_IDENT and unresolved expression. 2026-02-12 16:38:18 +01:00
Christoffer Lerno
acc4a900f5 - New const enum declaration syntax.
- New enum associated value syntax.
2026-02-12 14:43:56 +01:00
Fernando López Guevara
f079fa82b2 fix(std-io): make uint128 decimal formatting safe (#2924)
* fix(std-io): make uint128 decimal formatting safe and add all-base
numeric coverage
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-11 23:50:16 +01:00
Manuel Barrio Linares
7665720264 add cache for Embedded/QEMU Tests
I forgot to add this one
2026-02-11 23:48:18 +01:00
Christoffer Lerno
83705ab340 Fix chacha20. 2026-02-11 23:44:26 +01:00
Christoffer Lerno
adea3dd83f Fix chacha20 2026-02-11 23:39:47 +01:00
Christoffer Lerno
9b52be9ba6 - unsigned % signed and unsigned / signed is no longer allowed without explicit casts, except for const denominators. #2928 2026-02-11 23:18:08 +01:00
Christoffer Lerno
202349d88f - Improved underlining errors/warnings when unicode is used. #2887 2026-02-11 16:49:22 +01:00
Manuel Barrio Linares
18ab18958b CI: add retries and package caching across all platforms
- Add retry loops to all package manager commands (apt, apk, pacman,
pkg).
- Implement daily package caching to minimize external server hits.
- Enable Docker layer caching in CI using buildx and GHA backend.
  - Sync CI Docker build steps with the build-with-docker.sh logic.
  - We don't, or more accurately, can't actually use
`build-with-docker.sh` anymore in CI. Maybe we should move it to
scripts/tools/ to keep the root directory clean.
- add ramdisk for BSD, shave 30/60 seconds.
- change BDSs to cross-platform-actions/action for a cleaner ci and skip
SLOW_TESTS for BSDs: this shaves a few seconds
- set global CACHE_INVALIDATION_SEED
2026-02-11 10:01:14 +01:00
Christoffer Lerno
2237d3b836 Missing check 2026-02-11 02:55:07 +01:00
Christoffer Lerno
e2f17a770b Using [] or .foo on $$ functions would not raise error but instead crash #2919. 2026-02-11 02:53:17 +01:00
konimarti
40e6a2c4a3 codepage: add single-byte code page support (#2891)
* codepage: add single-byte code page support

Add std::encoding::codepage with a shared engine for converting between
single-byte code pages and UTF-8 using table-driven mappings.

Introduce generated tables and wrappers for several code pages[1] each
exposing encode/decode helpers built on a common CodePageTable
structure.

The mapping data is generated by cpgen[2] from the Unicode Consortium’s
published code page mapping files and follows the Unicode standard’s
interpretation of control characters (abstract characters) rather than
historical VGA glyph shapes.

[1] Code page overview/groups:

    DOS/OEM code pages (legacy PC):
    cp437 cp737 cp775 cp850 cp852 cp855 cp857 cp860 cp861 cp862 cp863
    cp864 cp865 cp866 cp869 cp874

    Windows code pages (ANSI/Windows):
    cp1250 cp1251 cp1252 cp1253 cp1254 cp1255 cp1256 cp1257 cp1258

    ISO/IEC 8859 series (Latin/Regional):
    iso_8859_1 iso_8859_2 iso_8859_3 iso_8859_4 iso_8859_5 iso_8859_6
    iso_8859_7 iso_8859_8 iso_8859_9 iso_8859_10 iso_8859_11 iso_8859_13
    iso_8859_14 iso_8859_15 iso_8859_16

[2] github.com/konimarti/cpgen

Signed-off-by: Koni Marti <koni.marti@gmail.com>

* codepage: change encoding format, streamline api

* Use enum to collect the data.

---------

Signed-off-by: Koni Marti <koni.marti@gmail.com>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-11 01:10:12 +01:00
g.denis
a80e40a798 fix fedora dyn lib (#2917)
* also look into /usr/lib64
2026-02-10 23:12:04 +01:00
Manuel Barrio Linares
de8a733c77 Implement lazy-loaded libcurl
- Uses `dlopen` to load libcurl at runtime for better portability.
- Replaces the FETCH_AVAILABLE macro with download_available() check.
- Provides descriptive error messages when libcurl is missing.
2026-02-10 13:05:43 +01:00
Christoffer Lerno
c5d7a03859 Remove unnecessary free. 2026-02-10 11:46:26 +01:00
Christoffer Lerno
0b82537700 Fix 2026-02-10 11:27:55 +01:00
Christoffer Lerno
7b8299c8dd - Reallocating overaligned memory with the LibcAllocator was unsafe. 2026-02-10 11:23:51 +01:00
Christoffer Lerno
dcbd0f8f2d Fix dwarf definitions 2026-02-10 10:23:53 +01:00
Boris Barbulovski
402c00e230 Add Android Aarch64 and x64 support status 2026-02-09 10:26:40 +01:00
Christoffer Lerno
0542c05b88 Update tests. 2026-02-09 10:23:03 +01:00
Christoffer Lerno
67e224c62f Fix windows main 2026-02-09 10:15:49 +01:00
Christoffer Lerno
565b08846f Remove use of tempnam. 2026-02-09 00:04:05 +01:00
Christoffer Lerno
80ad0e02ad Fix tests and error in returning error on function name. 2026-02-08 22:54:24 +01:00
Lucas Alves
b8ee5a4150 fix: add non-API Android NDK lib search path (#2884)
* fix: add sysroot/usr/lib/<triple> to Android linker search paths

* Fix library search order: move libc++_shared logic after API libc paths

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-08 15:02:37 +01:00
Christoffer Lerno
445dd155b7 Another #2814 2026-02-08 15:01:27 +01:00
Rauny
abb3efca00 fix: prevent Homebrew dylib contamination in macOS builds (#2908)
When C3_FETCH_LLVM=ON, the fetched LLVM artifact is self-contained.
Two issues caused the macOS release binary to embed hardcoded
/opt/homebrew paths, making it fail on systems without Homebrew:

1. /opt/homebrew/lib was unconditionally appended to LLVM_LIBRARY_DIRS,
   causing find_library to discover Homebrew's LLD dylibs (whose
   LC_ID_DYLIB points to /opt/homebrew/opt/lld/lib/...). Guard this
   behind NOT C3_FETCH_LLVM so it only applies when using a
   system-installed LLVM.

2. In the static linking block (C3_LINK_DYNAMIC=OFF), find_library NAMES
   listed .dylib before .a, so shared libraries were preferred even when
   static linking was intended. Reorder to prefer .a, keeping .dylib as
   a fallback for systems that only ship shared libraries.

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-08 13:57:07 +01:00
Christoffer Lerno
de0b317461 Fix default main template. 2026-02-08 13:38:43 +01:00
Christoffer Lerno
21758476d4 - For c3c init with library templates, provide example exported functions. #2898 2026-02-07 21:31:12 +01:00
Christoffer Lerno
b69f78be30 Flag --cpu-flags doesn't work if the first item is an exclusion. #2905 2026-02-07 21:21:57 +01:00
Christoffer Lerno
89b9f52f1e Add libc mktemp and tempnam. Update test. 2026-02-07 20:25:28 +01:00
Christoffer Lerno
02a67254cc Fix win forwarding functions. 2026-02-07 19:57:43 +01:00
Manuel Barrio Linares
120f56ac5f disable --fetch-msvc when no curl 2026-02-07 01:22:54 +01:00
Laura Kirsch
5b93212f43 Added Xorshiro128++ random number generator. (#2846)
* Added Xorshiro128++ random number generator.

* Fixes to xorshiro implementation + tests.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-06 20:14:56 +01:00
Zack Puhl
c57415ea78 Optional byte limit for readline
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
2026-02-06 19:15:29 +01:00
Manuel Barrio Linares
36dfbdff45 implement progress bar for fetch-msvc
using deterministic milestones
2026-02-06 17:59:36 +01:00
Christoffer Lerno
d07e454dfe Fix missing ) 2026-02-06 17:56:40 +01:00
Christoffer Lerno
40aa4d4dcd Add Murmur3 hash 2026-02-06 17:53:19 +01:00
Manu Linares
0be291e0d7 compiler: added c3c fetch-msvc command (#2854)
* compiler: added `c3c fetch-msvc` command
ported `msvc_build_libraries.py` to built-in compiler 'fetch-msvc'
subcommand.
* fix alpine build
* fix windows build
* fix WinHTTP download to correctly handle full URLs by splitting hostname and path.
* fix old `cp` and escape args for `file_copy_file`
also cleanup the temp directory when we are finished
it would be better to use a cache :|
* remove msiextract dependency
* Auto-fetch `msvc_sdk` if missing
- auto trigger `msvc_sdk` download when compiling for Windows if no SDK
is detected.
- make native Windows SDK detection non-fatal to allow fallback to the
portable SDK
- add debug logging for diagnostics
* removed the LZX decompression logic
- moved `msvc_sdk` to user cache locations to avoid permission issues.
- refined terminal messaging for better accuracy and clarity.
* use %LOCALAPPDATA% on Windows
* Add support for C3_MSVC_SDK
* Update release notes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-06 14:01:49 +01:00
Manuel Barrio Linares
35d84390bd Simplify CI, do more with less.
- Rename Docker job to **"LLVM ${{ matrix.llvm_version }}
Compatibility"** for better visibility.
- Limit `build_type` to **Debug** for MSYS2, OpenBSD, NetBSD, and Nix
jobs to reduce CI time.
- Refactor **Alpine Linux** job: updated to 3.23, simplified dependency
installation, and removed redundant LLVM matrix.
- Modernize **Docker** builds:
    - Added automatic architecture detection (x86_64/ARM) for CMake
downloads.
    - Generalized LLVM repository logic to support multiple Ubuntu
codenames (Focal/Jammy/Noble).
    - Fixed git permissions issues in containerized builds using
`safe.directory`.
- Standardized Android build packaging and added conditional testing for
Debug builds.
2026-02-06 12:00:00 +01:00
Christoffer Lerno
124efb2684 Improve error message on const int* 2026-02-06 02:00:26 +01:00
Christoffer Lerno
5c158e481b Always have wmain. Cleanup synthetic main generation. Some cleanup for generics. 2026-02-06 01:28:19 +01:00
Christoffer Lerno
42b79d19c1 Update all stub methods. 2026-02-06 00:55:57 +01:00
Zack Puhl
4521a25284 [stdlib] Add PEM Encoding/Decoding Module (#2858)
* [stdlib] Add PEM Encoding/Decoding Module
* release notes
* Removed some unnecessary macro usages. Fixed memory handling with headers.
* Make end of line a parameter. Internal encode method -> function. Use more tmem. Remove t-functions.
* Update API

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
2026-02-06 00:01:07 +01:00
Foxy-Boxes
1933d47ba1 add Intel user interrupt instructions (#2767)
* add Intel user interrupt instructions

* Example code did not actually work, so was removed.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-05 20:56:35 +01:00
Book-reader
bb9e9b54cf Improve android support & add CI (#2664)
* Change context destruction order.

* enable emulated tls on termux

* Fix stdlib on android

* Add a CI workflow for android termux

* update release notes

* use the new unified CI tests on android

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-05 20:04:16 +01:00
Manu Linares
eddbfb8ba3 CMakeLists and CI: Use our custom llvm (#2893)
* c3-llvm: new CMakeLists and CI

uses custom llvm version 21.x from
"https://github.com//ManuLinares/llvm-custom-builds/"

* use c3lang/llvm-for-c3 latest release

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-05 18:55:09 +01:00
Christoffer Lerno
2c55d6e220 Further fix shadowing of generics 2026-02-05 18:43:54 +01:00
Christoffer Lerno
472c49de25 Differentiate errors on generic mismatch. 2026-02-05 16:20:17 +01:00
Christoffer Lerno
016254d38c Improve error message when using functions as values #2856 2026-02-05 16:03:56 +01:00
Christoffer Lerno
054fcd2deb Update advice when an enum is found with "= 2" and no associated values. 2026-02-05 13:57:35 +01:00
Christoffer Lerno
d71aa10f62 - Const inline enums would not always implicitly get converted to the underlying type.
- Update to dstring.append_string to take any type converting to String.
2026-02-05 13:04:57 +01:00
Christoffer Lerno
1d0aef4522 Remove unnecessary macro. 2026-02-05 02:07:42 +01:00
Christoffer Lerno
71b673d241 - Shadowing not detected for generic declarations #2876 2026-02-05 01:33:47 +01:00
Christoffer Lerno
dcf695c726 Fix list[0].i = 5 when list[0] returns a pointer. #2888 2026-02-05 00:34:17 +01:00
Christoffer Lerno
5e656603a5 - Remove dependency on temp allocator in File.open. 2026-02-04 14:27:52 +01:00
Christoffer Lerno
b4426c095b - Remove dependency on temp allocator in String.join. 2026-02-04 13:06:12 +01:00
Christoffer Lerno
5f32c97094 - Compiler crash when using arrays of vectors in lists. #2889 2026-02-04 12:40:16 +01:00
Christoffer Lerno
1c8cb7fa11 Method resolution and $define now works together well unless definitions are out of order for real. 2026-02-04 12:23:37 +01:00
Christoffer Lerno
6bc606a9b1 Removed analyse top pass. Remove sub module tracking. Fix errors where ? should be ~ 2026-02-02 12:20:34 +01:00
Christoffer Lerno
d509b4caa1 Updated info about contributing bindings. 2026-01-31 22:07:14 +01:00
Zack Puhl
12975d07ac [stdlib] Reduce inline code volume from sorting macros (#2831)
* reduce codegen in sorting macros

* remove testing file...

* Fix and some renaming, removing some sub-modules that should not be in use.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-31 20:19:57 +01:00
Laura Kirsch
3d512abaf7 Fix non-LLVM build. 2026-01-31 14:07:48 +01:00
Kiana
a9d93c93d5 Improve linker error message (#2880)
* Improve linker error message

* use file_util/file_is_dir instead

* cleanup unused function

* Some minor updates and merging latest master.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-31 04:55:47 +01:00
Manu Linares
1b7601fdbb stdlib: optimize math::next_power_of_2 to O(1) using hardware CLZ (#2839)
* stdlib: optimize math::next_power_of_2 to O(1) using hardware CLZ

- updated DString.reserve to use this

* bit smearing

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-31 00:20:58 +01:00
Smite Rust
373013046d Fix formatting of path assignment in msvc_build_libraries.py 2026-01-30 22:01:40 +01:00
Christoffer Lerno
10d369d766 Updated grammar, and 0.7.10 first commit 2026-01-30 21:59:20 +01:00
Christoffer Lerno
161280ce7d Add deprecation silence notice. 2026-01-30 13:57:52 +01:00
Christoffer Lerno
4e129d4ae2 Fix test. 2026-01-30 13:18:56 +01:00
Christoffer Lerno
2b6f1c061d Fix of evaluation order warning. 2026-01-30 13:10:54 +01:00
Christoffer Lerno
cb19c7d9e7 Test update 2026-01-29 20:08:58 +01:00
Christoffer Lerno
224b120745 Another fix error => warn 2026-01-29 20:07:42 +01:00
Christoffer Lerno
534dd42472 Update test. 2026-01-29 17:51:50 +01:00
Christoffer Lerno
163976f85f Fix of second warning triggered on failing to detect a method. 2026-01-29 17:42:51 +01:00
Christoffer Lerno
d79a8808d7 Update readme example and last version 2026-01-29 14:36:13 +01:00
Christoffer Lerno
1cba5a0774 Release candidate 2026-01-29 11:44:45 +01:00
Christoffer Lerno
6c76a7ce4e Make methods not fully determined a warning. 2026-01-29 11:42:58 +01:00
Christoffer Lerno
084d5cbc94 Set target in test. 2026-01-29 02:30:06 +01:00
Christoffer Lerno
32b1df0f86 Set target in test. 2026-01-29 00:43:53 +01:00
Christoffer Lerno
50718cb905 - Crash in slice expression when it contains a rethrow #2872
- Multiple issues when rethrowing inside of expressions #2873
2026-01-29 00:42:20 +01:00
Christoffer Lerno
414c0c9438 - Initializer did not correctly handle second rethrow #2870
- Crash encountering panic in if-else style switch #2871
2026-01-28 22:56:59 +01:00
Christoffer Lerno
362d5680e4 - Optional in initializer cause a crash #2864
- Negating a global address with offset was a counted as a global runtime constant #2865
- Converting static "make_slice" to array failed to be handled #2866
- Narrowing a not expression was incorrectly handled #2867
- Vector shift by optional scalar failed #2868
2026-01-28 21:59:40 +01:00
Christoffer Lerno
fed7a74a75 Fix assert in #2863 2026-01-28 19:02:27 +01:00
Christoffer Lerno
d276d3767f Incorrect handling when reporting fn with optional compile time type #2862 2026-01-28 18:51:21 +01:00
Christoffer Lerno
a07660f957 Improve tracing of liveness error reporting. 2026-01-28 11:44:37 +01:00
Christoffer Lerno
4d02ce4414 Additional fix for #2853 2026-01-27 23:43:14 +01:00
Christoffer Lerno
c1e3cfaacc - Crash when trying to create a const zero untyped list #2847 2026-01-27 23:36:00 +01:00
Christoffer Lerno
c11385cf49 Fix wasm int128 size on LLVM 17-19. 2026-01-27 16:26:35 +01:00
Christoffer Lerno
0b5064683f Regression: Generic type could not be found in lambda inside macro #2853 2026-01-27 14:23:41 +01:00
Christoffer Lerno
f3ede27f60 Regression: "'out' parameters may not be read" when writing to array #2852 2026-01-27 13:48:50 +01:00
Christoffer Lerno
4fbb42833e - Crash when creating $Type* where $Type is an optional type #2848
- Crashes when using `io::EOF~!` in various unhandled places. #2848
2026-01-27 13:32:08 +01:00
Christoffer Lerno
3e76b7ff1c Fixes to optional rethrow in expressions. 2026-01-26 04:10:38 +01:00
Christoffer Lerno
e901a3de55 - Fix alignment for uint128 to 16 with WASM targets.
- Incorrect assert in struct alignment checking #2841
- Packed structs sometimes not lowered as such.
2026-01-25 23:06:16 +01:00
Christoffer Lerno
4899ee14e2 Resolving &X.b when X is a const incorrectly checked for runtime constness #2842
Creating a generic instance fails if it is created after interface checking #2840
2026-01-25 21:24:27 +01:00
Christoffer Lerno
3c04a326f4 Resolving &X.b when X is a const incorrectly checked for runtime constness #2842 2026-01-25 20:28:58 +01:00
Christoffer Lerno
a1ff3b05ed - Lowering of optional in && was incorrect #2843 2026-01-25 19:50:28 +01:00
Christoffer Lerno
74e228688a - Packed .c3l files without compressions weren't unpacked correctly. 2026-01-25 19:13:42 +01:00
Zack Puhl
75d454b6a6 Add mem_allocator realloc_array Macros (#2760)
* Add mem_allocator `realloc_array` Macros

* test, ensure `realloc_array` to 0 size returns `null`

* update release notes

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-25 16:12:44 +01:00
m0tholith
6cffb888ea Add maximum memory usage tracking to tracking allocator (#2772)
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-25 16:11:46 +01:00
soerlemans
cf03215564 Added unit tests for Crc32 hashing. (#2833)
* Adding crc32 unit tests.

* Fix formatting in crc32 test cases

---------

Co-authored-by: soerlemans <sebasoerlemans+git@gmail.com>
Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
2026-01-25 13:16:44 +01:00
Christoffer Lerno
5b90743120 Fix test compatibility 2026-01-25 13:15:26 +01:00
Christoffer Lerno
0fb91265b6 - Bug when initializing an inferred array with deep structure using designated init #2826 2026-01-25 13:13:59 +01:00
Christoffer Lerno
3cb7c489ee Updated releasenotes 2026-01-25 12:50:02 +01:00
Christoffer Lerno
c65c378b7f - Bitstruct as substruct fails to properly work with designated initializers. #2827 2026-01-25 12:48:36 +01:00
Christoffer Lerno
cf9784afee - Bitstruct accidentally allowed other arrays than char arrays #2836 2026-01-25 12:14:34 +01:00
Christoffer Lerno
8bd942c1b8 - Store of zero in lowering did not properly handle optionals in some cases #2837 2026-01-25 04:57:35 +01:00
Christoffer Lerno
109e15b5a0 - Empty enums would return the values as zero sized arrays #2838 2026-01-25 04:41:06 +01:00
Christoffer Lerno
0fdd6bdc81 - Early exit in macro call crashes codegen #2820 2026-01-25 00:52:57 +01:00
Christoffer Lerno
378b35265b - Raw vaargs with optional return not lowered correctly #2819 2026-01-24 23:54:20 +01:00
Christoffer Lerno
b5e25e3857 Constant deref of subscript had inserted checks #2818 2026-01-24 23:24:36 +01:00
Christoffer Lerno
9b2fc04959 ompile time dereference of a constant slice was too generous #2821 2026-01-24 22:42:04 +01:00
Christoffer Lerno
ce8167a102 - Incorrectly try compile time int check on vector #2815
- Generating typeid from function gives incorrect typeid #2816
- Recursive definitions not discovered when initializer is access on other const #2817
- Slice overrun detected late hit codegen assert #2822
2026-01-24 19:38:51 +01:00
Christoffer Lerno
397d065a74 - Constant shifting incorrectly doesn't flatten the underlying vector base #2825
- String not set as attributes resolved breaking has_tagof #2824
- Self referencing forward resolved const enum fails to be properly detected #2823
2026-01-24 18:32:26 +01:00
Christoffer Lerno
5e23817a3d - Comparing a flexible array member to another type would hit an assert. #2830
- Underlying slice type not checked correctly in $defined #2829
- Checking for exhaustive cases is done even in if-chain switch if all is enum #2828
2026-01-24 17:57:56 +01:00
Christoffer Lerno
396263f5c3 De-macro readline. 2026-01-23 22:45:58 +01:00
Christoffer Lerno
26d0760c0d Make counting sort normal. 2026-01-23 17:09:34 +01:00
Christoffer Lerno
11f090116f Allow disabling asserts. 2026-01-23 13:09:43 +01:00
Zack Puhl
5e1c343be4 Deprecate builtin EMPTY_MACRO_SLOT for optional macro arguments (#2805)
* add deprecations to macro slot builtins

* refactor all stdlib uses of now-deprecated EMPTY_MACRO_SLOT; release notes

* update incorrect releasenotes ref to this pr

* remove leftover comments from refactoring

* remove unnecessary `EmptySlot`-like type in countingsort; use private macro directly
2026-01-23 12:34:50 +01:00
Christoffer Lerno
0e69432e3d Improved error diagnostics for memory overflow. 2026-01-23 11:58:19 +01:00
Christoffer Lerno
94a26d9483 Add asserts, tracking Windows bug 2026-01-23 11:51:51 +01:00
Christoffer Lerno
efa5bdc6df Add better error message on VirtualAlloc failure on windows. 2026-01-23 11:45:00 +01:00
Christoffer Lerno
459969ddb2 Make sure we don't switch over bad things. 2026-01-23 01:22:58 +01:00
Christoffer Lerno
ae5047b73f Deprecating multi-level array length inference. int[*][*] is deprecated and will be removed 0.8.0. 2026-01-22 23:50:39 +01:00
Christoffer Lerno
17f3db835c - Remove dependency on test tmp library for stdlib compiler tests. #2800 2026-01-22 22:15:34 +01:00
Christoffer Lerno
1845a515ca - Empty ichar slice + byte concatenation hit an assert. #2789 2026-01-22 21:39:03 +01:00
Christoffer Lerno
bf50178eb3 $typeof(<type>) returns typeinfo, causing errors #2795 2026-01-22 20:04:09 +01:00
Christoffer Lerno
32675161c4 - Attrdef eval environment lacked rtype, causing error on invalid args #2797 2026-01-22 19:47:37 +01:00
Christoffer Lerno
e257500e03 Recursive definition of tag not detected with nested tag/tagof #2790 2026-01-22 19:16:54 +01:00
Christoffer Lerno
0add42b0a0 - Make foo.$abc implicitly mean foo.eval("$abc"). 2026-01-22 16:46:07 +01:00
Christoffer Lerno
b14053df41 - Too deeply nested scopes was a fatal crash and not a regular semantic error. #2796 2026-01-22 15:13:01 +01:00
Christoffer Lerno
6a78864b6c Instantiating an alias of a user-defined type was not properly caught #2798 2026-01-22 13:50:57 +01:00
Christoffer Lerno
b1fea45cd1 Using an optional type as generic parameter was not properly caught #2799 2026-01-22 13:34:16 +01:00
Christoffer Lerno
cef48482f1 Unable to access fields of a const inline enum with an aggregate underlying type. #2802 2026-01-21 13:22:46 +01:00
Christoffer Lerno
a126a25d66 Casting const bytes to vector with different element size was broken #2787 2026-01-21 12:56:25 +01:00
Christoffer Lerno
61c939059d int? ? was not correctly handled. #2786 2026-01-21 00:52:18 +01:00
Christoffer Lerno
5fa820a462 When a global const has invalid attributes, handling is incorrect, leading to a crash #2785. 2026-01-21 00:43:30 +01:00
Christoffer Lerno
472124dab3 Bug in sysv abi when passing union in with floats #2784 2026-01-21 00:34:37 +01:00
Christoffer Lerno
1e11b6c442 Inferring the size of a slice with an inner inferred array using {} isn't detected as error #2783 2026-01-20 23:58:45 +01:00
Christoffer Lerno
34a86852c6 Failed to reject void compile time variables, leading to crash. #2781 2026-01-20 23:07:11 +01:00
Christoffer Lerno
52afbdbde9 Recursive constant definition not properly detected, leading to assert #2780 2026-01-20 18:28:13 +01:00
Christoffer Lerno
888657cc97 - $typeof untyped list crashes when trying to create typeid from it. #2779 2026-01-20 17:54:05 +01:00
Christoffer Lerno
d7bfddf35e Broken cast from fault to array pointer #2778. 2026-01-20 17:43:16 +01:00
Christoffer Lerno
9c435352b9 - Second value in switch range not checked properly, causing an error on non-const values. #2777 2026-01-20 17:12:41 +01:00
Christoffer Lerno
3fe55b5e51 - Vectors not converted to arrays when passed as raw vaargs. #2776 2026-01-20 17:00:09 +01:00
Christoffer Lerno
cdabe8fd9e - Create optional with ~ instead of ?. return io::EOF?; becomes return io::EOF~.
- Deprecated use of `?` to create optional.
2026-01-20 16:10:28 +01:00
Christoffer Lerno
5390ca6250 - Eager evaluation of macro arguments would break inferred arrays on some platforms. #2771. 2026-01-20 13:02:14 +01:00
Christoffer Lerno
61e84e4d34 One last regression fix. 2026-01-20 11:47:17 +01:00
Christoffer Lerno
1b82b7d2f2 Fix regression with conditional methods. 2026-01-20 11:20:39 +01:00
Christoffer Lerno
49f59d82c9 Fix regression. 2026-01-20 01:26:29 +01:00
Christoffer Lerno
e84e23f7ec Fix Using @if with methods with generic params only inferred from the parent type fails #2770 2026-01-20 01:04:12 +01:00
Christoffer Lerno
0b9b49673e - Empty struct after @if processing was not detected, causing a crash instead of an error.
- Comparing an uint and int[<4>] was incorrectly assumed to be uint compared to int, causing a crash instead of an error.
- When an `int[*][6]` was given too few values, the compiler would assert instead of giving an error.
2026-01-20 00:04:18 +01:00
Christoffer Lerno
4512c6446d - Empty struct after @if processing was not detected, causing a crash instead of an error.
- Comparing an uint and int[<4>] was incorrectly assumed to be uint compared to int, causing a crash instead of an error.
- When an `int[*][6]` was given too few values, the compiler would assert instead of giving an error.
2026-01-19 15:01:08 +01:00
Christoffer Lerno
0fea6c6056 - Bitstruct with unevaluated user-defined type would cause a crash.
- Using named parameters with builtins would cause a crash.
- In some cases, using missing identifiers with builtins would cause a crash.
- Using `$defined` with function call missing arguments would cause a crash.
- Adding @nostrip to a test function would crash.
- Mixing struct splat, non-named params and named params would crash rather than to print an error.
- Creating a char vector from bytes would crash.
- Using $$wstr16 with an illegal argument would crash instead of printing an error.
2026-01-19 02:49:54 +01:00
Christoffer Lerno
dd8449576f - Passing a non-conststring to module attributes like @cname would trigger an assert rather than printing an error.
- Passing different types to arg 1 and 2 for $$matrix_transpose would trigger an assert.
- Zero init of optional compile time variable would crash the compiler.
- Using multiple declaration for generics in generic module would fail.
- Defining an extern const without a type would crash rather than print an error.
- Typedef followed by brace would trigger an assert.
- Union with too big member would trigger an assert.
2026-01-18 22:47:17 +01:00
Christoffer Lerno
c3b2694834 Generics with <>. Deprecation of {} generics. 2026-01-18 00:33:43 +01:00
Christoffer Lerno
d3ebd4a130 Migrate from @unaligned_load to mem::load 2026-01-17 19:12:32 +01:00
Christoffer Lerno
a326e31e57 Add missing fix. 2026-01-17 18:52:55 +01:00
Christoffer Lerno
945a3f3fc0 Add mem::store and mem::load which may combine both aligned and volatile operations. 2026-01-17 18:52:30 +01:00
Darvis
2a8fbb8fec Fix function signature for free to return void
free expects a return type void* but it should return void instead of void*
2026-01-17 18:52:11 +01:00
Book-reader
c0f1b02d0b native_file_size: use libc stat on more platforms
Co-authored-by: 김형근 (Nomota Hiongun KIM) <hiongun@gmail.com>
2026-01-17 16:09:21 +01:00
Christoffer Lerno
f2c557c3b7 Remove use of LLVMCreateBuilder #2666 2026-01-17 15:39:22 +01:00
Christoffer Lerno
d00a93f195 Update error on omitting end. 2026-01-17 15:37:30 +01:00
Christoffer Lerno
aa07969fc2 - HashSet.len() now returns usz instead of int. #2740 2026-01-17 15:12:31 +01:00
Christoffer Lerno
6dcd91c5ef - Assert on defining a const fault enum with enumerator and fault of the same name. #2732 2026-01-17 15:01:40 +01:00
Christoffer Lerno
c2e603ddd8 Expand inference. 2026-01-17 14:03:58 +01:00
Christoffer Lerno
70c4b24519 - In some cases, a type would not get implicitly converted to a typeid #2764. 2026-01-16 11:41:18 +01:00
Christoffer Lerno
51364a9d1b Another fix in immediately rethrowing a value in an assignment #2689 2026-01-15 23:33:26 +01:00
Christoffer Lerno
d9f4eb46d9 - Too little memory reserved when printing backtrace on Darwin #2698. 2026-01-15 22:50:12 +01:00
Book-reader
cd2d1a04d8 Use a Printable struct for ansi rgb formatting instead of explicit allocations (#2696)
* Use a `Printable` struct for ansi rgb formatting

* update release notes

* Some renaming.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-15 22:26:18 +01:00
Zack Puhl
3f7a547d8a Merge AsciiCharset CT/non-CT Functions (#2688)
* Merge AsciiCharset CT/non-CT Functions

* release notes

* incorporate helpful review feedback

* re-separate 'create_set' and 'contains' but keep 'combine_sets'; update tests

* tabs (annoying IDE)

* Restored old code verbatim for smaller diff. Split combine_sets into easier to macro/function for runtime / macro version, this also allows for more easy type checks.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-15 21:39:48 +01:00
Manu Linares
f254c27966 std/io/os/temp_directory.c3: fix INVALID_PATH in Win32 native_temp_directory (#2762)
* std/io/os/temp_directory.c3: fix INVALID_PATH in Windows native_temp_directory

- Use the actual length from GetTempPathW for Windows temp path slice
- We can remove the workaround in the test_suite_runner for WIN32 and
create all directories in %temp% now

* Updated releasenotes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-15 21:23:30 +01:00
Jasper Wilmes
8a1c02c840 Rework libc signal constants (#2724)
* Move libc::SIG* constants to posix.c3

* Add missing libc::SIG* constants for Win32 systems

* Add missing POSIX signals

* Add missing Linux signals

* Add missing BSD signals

* Moved common signals back to libc

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-15 21:17:50 +01:00
Christoffer Lerno
7f297a9d27 Fix type inference overrides parameterization #2761. Remove support for ^io::EOF 2026-01-15 15:19:33 +01:00
VarunVF
64ef33f09b Require parenthesized assignment expressions in condition of 'if' statements #2716 (#2729)
* Require parenthesized assignment expressions in condition of 'if' statements #2716

* Move analysis to semantic checker and also check while. And update tests and release notes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-15 00:43:58 +01:00
mikelgarai
cd4c586c3f Fixed macro hash_vec(vec): it was checking only the first bytes (4 or 8, not sure) (#2718)
* Fixed macro hash_vec(vec): it was checking only 8 bytes

* use $sizeof() directly as it is correct now

* Added unit test for vector types

Tests that hashes match for same values and that they don't match
when changing a single bit on any of the vector elements

* Changed hardcoded value for the compiler option

* Release note

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-14 23:22:10 +01:00
Kiana
0a9d4e398d Add $$VERSION and $$PRERELEASE (#2751)
* Add 64507VERSION and 64507PRERELEASE

* Migrate to compiler define method

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-14 23:15:55 +01:00
Christoffer Lerno
d61beef7e0 Fix String.replace not dropping temp mem. 2026-01-14 22:13:23 +01:00
Sergey Fedorov
7136b05019 common.h: define static_assert to _Static_assert when undefined 2026-01-14 14:57:19 +01:00
Christoffer Lerno
87fa253059 Add debug versions. 2026-01-14 02:00:07 +01:00
Manu Linares
04eb6fc451 CI: Major refactor, expanded coverage, and ~80% speedup (#2736)
* netbsd fail on error

- Group each BSD test in its own subshell. Without this, if an error
occurs, it exits, but we don't know "where" it failed
- added *usesh: true* to BSD for faster copying into the virtualbox VM

- Some test_suite_runners wheren't correctly printing. added
--no-terminal
- For testing I've changed *build-msvc* from Debug to RelWithDebInfo

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* separate runs

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* fix typo

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* Split msvc-debug into separate workflow

Run msvc-debug only on maintainer pushes, keep CI fast and still catches
errors.

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* Fix build and test errors on NetBSD

* Set proper sizes for various types used for threads
* Add num_cpu function
* Add sysctl constants
* Allow testproject to build on NetBSD
* Tweaks to the linker code so that it adds correct flags and finds dynamic linker on NetBSD

* bsd: force system linker test

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* system linker on openbsd

remove lld from netbsd

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* use cc linker in openbsd, and re-add lld for netbsd

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* openbsd: fixes

- implement num_cpu() for OPENBSD
- testproject: add openbsd
- linker.c: fix for openbsd
- bsd refactor main.yml and more fixes

* openbsd: fix for unit tests and test_suite_runner

openbsd now passes all tests except the 'dynlib-test' which yields
'relocation error' when running `cc test.c -L. -ladd -Wl,-rpath=.`

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* openbsd: guard/disable unit test test_ct_intlog2()

* build-msys2-clang: add all tests that build-msvc has

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* Fix Windows import library generation by adding /IMPLIB support and properly declaring static_lib_name()

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* build-msys2-mingw: add all tests that build-msvc has and enable it

configure CMake to link LLVM and LLD statically

* Revert "Revert windows"

This reverts commit 197f82d829.

* win-llvm 21.1.8

uses the RelWithDebInfo+Assertions

* refactor(CI): Centralize test logic and fix Docker execution

This commit refactors the GitHub Actions workflow to improve
maintainability and correct a flaw in the Docker build.

- Ensures Docker based tests run *inside* the generated container, not
on the host. This also fixes environment isolation and user permission
errors.
- Centralizes all common test steps (examples, libs, unit tests, etc)
into a single, auto-detecting Bash script: `scripts/tools/ci_tests.sh`.
- Centralize all release artifact packaging into a single
`scripts/tools/package_build.sh`
- fixes using correct paths for test_suite_runner when running on
windows under bash.

- This significantly reduces YAML duplication, making `main.yml` more
readable and easier to maintain.

This one supersedes pull/2677, and includes the fixes for:

- fix openbsd and netbsd
- added win-llvm 21.1.8 and fixed CMakeLists.txt
- added full unit-tests and suite for build-msys2-mingw and
build-msys2-clang

* macos: add/fix build for llvm (19,20,21)

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* ci: run tests in temp dir to avoid workspace pollution

Executes ci_tests.sh in a disposable temporary directory by copying
resources/ and test/ folders, preventing build artifacts from cluttering
the source tree.

This makes it easy to run `scripts/tools/ci_tests.sh` locally on any
platform.

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* add ilammy/msvc-dev-cmd@ and Ninja

we can simplify the windows runner and run everything in bash

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* windows: drain subprocess stderr before join to avoid test_suite_runner hang

* move last test to `ci_tests.sh`

thus completing the centralization of tests in `ci_tests.sh`

* Fix Windows CI cache

Updates the cache key to include hashes of CMakeLists.txt and the
workflow file.

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* `package_build.sh` runs in temp dir to avoid workspace pollution

- add cleanup

* nix: use `ci_tests.sh`

* remove annoying build-mac warnings

This now checks if package already installed trying to prevent
annoying warnings in the github ci:

Fixes:
```
build-mac (Release, 19)curl 8.17.0 is already installed and up-to-date.
To reinstall 8.17.0, run: brew reinstall curl
```

* refactor test_suite_runner

- less allocations, cleaner code, more maintainable.
- now can be killed and the temp dirs are correctly cleaned up, and the
output is correct.

* main.yml: bsd: change to using rsync, is twice as fast.

* default to `--wincrt=dynamic` when `--sanitize=address` on windows

Default to dynamic, as static ASan is removed in LLVM 21+ for Windows
https://github.com/llvm/llvm-project/pull/107899

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* Formatting (indent with tab, align with space). Fix K&R braces. Style: keep all cases multiline if one is.

---------

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
Co-authored-by: Alex Garrett <limit.ordinal17@gmail.com>
2026-01-14 01:16:06 +01:00
Koni Marti
15fc435d92 collections: fix LinkedList.to_format
For-loop in LinkedList.to_format goes brrrrrr and prints the value of
the first node an infinte number of times because it does not properly
iterate the linked list.

Fix the list iterations and add the missing string format specifier.

Bug can be reproduced with:
```
import std;
fn void main() => @pool()
{
	io::printfn("%s", linkedlist::@tnew{usz}({1,2,3}));
}
```
2026-01-13 23:09:05 +01:00
Manuel Barrio Linares
9d54e9e3c4 a fix to generated C header global
test:
```c3
module my_module;

alias Something = fn void(int);
Something something @export("something") = &something_else;
fn void something_else(int a) => {};
```

would generate
```c
/* GLOBALS */
my_module__Somethingextern  something;
```

now correctly generates
```c
/* GLOBALS */
extern my_module__Something  something;
```

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>
2026-01-13 23:07:49 +01:00
Book-reader
c73c7cb2a3 add Win32_CODEPAGES enum and enable utf8 console output on win32 (#2670)
* add Win32_CODEPAGES enum and enable utf8 console output on win32

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-13 23:00:01 +01:00
Gourmet
827ad18ef4 Ignore ctags and gtags files in .gitignore
This PR updates the .gitignore file to exclude automatically generated tags files from ctags and gtags.

Specifically, it ignores:
- ctags output file: tags
- GNU Global output files: GTAGS, GRTAGS, GPATH
2026-01-13 18:08:43 +01:00
Darvis
3fc562af6f Fix minor typos in README.md 2026-01-13 18:06:33 +01:00
Arnaud Duforat
87c42f1cd3 Add unit tests for HMAC 256 based on RFC 4231 (#2744)
* Add unit tests for HMAC 256 based on RFC 4231

* Formatting, shorten initializers.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-12 11:26:49 +01:00
Christoffer Lerno
e93f22fbda - bitorder::read and bitorder::write may fail because of unaligned access #2734. 2026-01-12 01:11:31 +01:00
Christoffer Lerno
8fa59cbb43 Creating recursive debug info for functions could cause assertions. 2026-01-07 23:50:56 +01:00
Christoffer Lerno
527766310f Update pointer type naming. 2026-01-07 23:12:09 +01:00
Christoffer Lerno
a02b0a1f4c Update LLVMHasUseList. 2026-01-07 20:29:55 +01:00
Christoffer Lerno
4d93db51ee Check properly has use list. 2026-01-07 19:59:40 +01:00
Christoffer Lerno
fa2e6e8189 Fix typo 2026-01-07 15:56:34 +01:00
Christoffer Lerno
0fc1871ddf - Assert on optional-returning-function in a comma expression. #2722 2026-01-07 15:53:54 +01:00
Christoffer Lerno
197f82d829 Revert windows 2026-01-07 03:36:48 +01:00
Christoffer Lerno
4ad11724d1 Update Windows flags 2026-01-07 03:31:03 +01:00
Christoffer Lerno
6d53fd6f6e Fix test. 2026-01-07 03:18:01 +01:00
Christoffer Lerno
db47b0555d Update windows LLVM version 2026-01-07 03:15:59 +01:00
Christoffer Lerno
ebd7b7243a Fix bug when evaluating a non-generic identifier. 2026-01-07 03:13:14 +01:00
Christoffer Lerno
e0771beabc Remove pointer names in debug, following Clang. 2026-01-07 02:35:05 +01:00
Christoffer Lerno
824d064710 Fix tests. 2026-01-07 00:55:30 +01:00
Christoffer Lerno
336ddb67c8 Incorrect alignment on typedef and local variable debug info. 2026-01-07 00:16:49 +01:00
Christoffer Lerno
9ad98beda7 - Crash when doing a type property lookup for const inline enums in some cases #2717. 2026-01-06 15:28:18 +01:00
Christoffer Lerno
702f836b40 io::read_fully now handles unbounded streams properly 2026-01-05 22:27:55 +01:00
Christoffer Lerno
d820a2356a Fix: Compiler assert when attempting to define multiple faults in a generic module. #2706 2026-01-05 21:04:23 +01:00
Christoffer Lerno
fa1951642b Add debug info 2026-01-05 00:11:48 +01:00
Christoffer Lerno
42dc2d541a Add testcase. 2026-01-04 22:05:01 +01:00
Chad Adams
fdbbe5c1aa improve c3c init error message (#2697)
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-03 17:17:12 +01:00
Christoffer Lerno
292bf1cbbc Designated initialization with ranges would not error on overflow by 1. 2026-01-03 02:32:40 +01:00
Christoffer Lerno
82769669ec Fix of generic with multiple parameters fail. #2702 2026-01-03 00:09:14 +01:00
Christoffer Lerno
29b211eefc - Fix the case where \u<unicode char> could crash the compiler on some platforms. 2026-01-01 21:44:37 +01:00
Zack Puhl
e4965ab408 Add BLAKE3 Hashing/XOF to stdlib (#2667)
* blake3: initial unit test passing!

* typo

* add key derivation macros; vanilla unit tests; working to test 18 atm

* mark it here - all tests passing o_o

* finish first-round unit tests - will add more if necessary

* add crypto shootout bench entertainment

* tests: add XOF unit w/ seek; assert NO finalization

* add another to finalizations unit test

* add all BLAKE3 scaffolding for later SIMD optimizations

* irksome

* tabs

* tabs2

* extra documentation / contracts

* extra detail

* try to make things a bit more arch-neutral

* release notes

* Formatting

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-01-01 21:11:33 +01:00
Christoffer Lerno
067a4f7cb1 Fixed codegen for vec->array in the boolean case. 2026-01-01 00:25:32 +01:00
Christoffer Lerno
739e91efa4 - Reduced memory usage for backtraces on Linux.
- `String.tokenize_all` would yield one too many empty tokens at the end.
- `String.replace` no longer depends on `String.split`.
2025-12-31 10:08:00 +01:00
Christoffer Lerno
26d733ef59 No optimization in test runner for MSVC 2025-12-31 00:45:35 +01:00
Christoffer Lerno
32d6025e29 Add panics just in case. 2025-12-31 00:43:19 +01:00
Christoffer Lerno
04706f2dcd Fix issue when removing test files on abort. 2025-12-31 00:13:38 +01:00
Christoffer Lerno
d8a7c57b56 - Miscompilation: global struct with vector could generate an incorrect initializer. 2025-12-30 23:54:00 +01:00
Christoffer Lerno
ad8769580a Methods registered with generic type will implicitly get templated. 2025-12-30 22:39:42 +01:00
Christoffer Lerno
d51dd09a1f Made contracts aggregate from types etc 2025-12-30 21:31:45 +01:00
Christoffer Lerno
5ed4f9519f - x'1234' +++ (ichar[1]) { 'A' } would fail due to missing const folding. 2025-12-30 16:06:28 +01:00
Christoffer Lerno
493a084745 Added additional type size limits. 2025-12-30 13:42:14 +01:00
Christoffer Lerno
9bd04526e8 foo.x was not always handled correctly when foo was optional. 2025-12-30 12:38:04 +01:00
Christoffer Lerno
90f0486334 - Assert when encountering a test function with raw vaarg parameters. 2025-12-30 12:28:03 +01:00
Christoffer Lerno
e4f1b57bd0 Assert when encountering a malformed module alias. 2025-12-29 22:41:42 +01:00
Christoffer Lerno
c949bd3108 Assert when struct size would exceed 4 GB. 2025-12-29 22:24:41 +01:00
Christoffer Lerno
56f8008d85 - Parse error in $defined was not handled correctly, leading to an assertion. 2025-12-29 22:09:39 +01:00
Christoffer Lerno
c6a96ad7f3 - i<n> suffixes were not caught when n < 8, causing an assert. 2025-12-29 21:57:06 +01:00
Christoffer Lerno
328e6f518c Fix bug when encountering Type{} where Type is a generic parameter. 2025-12-29 19:34:45 +01:00
Christoffer Lerno
8ec4a2cada Fix test. 2025-12-29 17:25:49 +01:00
Zack Puhl
9b318ec233 [stdlib] Impove SHA-256 Performance (#2671)
* [stdlib] Impove SHA-256 Performance

Cleaned up the code a bit. Seems to have improved performance anywhere from ~10-25%.

* trade-offs, trade-offs... reduce codegen

* Fix formatting

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-29 17:07:03 +01:00
Christoffer Lerno
d96624c578 Decoupled generics (#2695) 2025-12-29 17:01:03 +01:00
Christoffer Lerno
bf1d401566 Converting between simd/non-simd bool vector would hit a compiler assert. #2691 2025-12-29 16:59:34 +01:00
Christoffer Lerno
a2c886a2d9 - Compiler assert when passing returning CT failure immediately rethrown #2689. 2025-12-26 22:02:02 +01:00
Christoffer Lerno
e76278cfd7 Fix test 2025-12-25 22:50:06 +01:00
Christoffer Lerno
1028f85daa - Better error messages when slicing a pointer to a slice or vector. #2681 2025-12-25 22:01:15 +01:00
Christoffer Lerno
e706c914a8 Fix test. 2025-12-25 21:39:19 +01:00
Christoffer Lerno
f3b71ed7eb - $$MASK_TO_INT and $$INT_TO_MASK to create bool masks from integers and back.
- Fix bug when creating bool vectors in certain cases.
2025-12-25 20:55:11 +01:00
Christoffer Lerno
18b246c577 - Testing for the presence of methods at the top level is prohibited previous to method registration. 2025-12-24 15:32:21 +01:00
Christoffer Lerno
c205719563 Fix netbsd vm version 2025-12-23 11:43:53 +01:00
Josh Ring
02a5270c5a Add docstring to swizzle and swizzle2 (#2674)
* Add docstring to swizzle and swizzle2

* Fixup

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-23 11:38:04 +01:00
Manuel Barrio Linares
2ba244dd9c Split formatting options out of .clang-tidy into .clang-format
This splits out the .clang-format settings from the .clang-tidy file.

Right now, the .clang-tidy file has a bit of a mix: it contains both
clang-tidy checks and clang-format style options.
This actually works fine in some tools (like CLion which doesn't
completely fail), but newer versions of clangd-based tools (VSCode,
Neovim, etc.) are stricter and will outright reject the config because
they expect .clang-tidy to only contain tidy-related stuff.

So here's what I did:
- Moved all the formatting-related options into a brand new
.clang-format file
- Kept .clang-tidy focused only on checks, warnings-as-errors, and any
check-specific options

No changes to the actual style rules or tidy checks. Everything should
behave exactly the same as before.

This should now play nicely with clangd, VSCode/VSCodium, Neovim, and
other modern clang-based tools, while still working perfectly in CLion.

I did this because some/*my* PRs had some formatting errors that could
be prevented in the future, thus lessing the work while
reviewing/merging.

Test the old .clang-tidy with

```
clang-tidy --dump-config
./.clang-tidy:13:1: error: unknown key 'SpaceBeforeCaseColon'
... more errors
```

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>
2025-12-23 11:34:53 +01:00
Manu Linares
d33d0a232b threaded test_suite_runner.c3 (#2642)
* threaded test_suite_runner.c3

- Added a simple threadpool
- Fixed the status line updates
- Implemented the #skip for tests
- Added ansi color to the final status line

It works as one expects reducing the total runner time by the allocated
number of threads.

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* fix thread_number and a test

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* added choice of "--thread [N]" or defaults to os::num_cpu()

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* added unique explicit --build-dir to the compiler

and also print the c3c command line for debugging

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* disable "run compiler tests" for msvc-debug build

it takes like 1:30hs

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* buffer printouts and correct ordering of tests

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* added progress bar

- removed some \r carrier return stuff

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* Fix bug in fixed pool. Improve progress bar

* Add color to bar.

* Some renaming.

* fix some leaky leaks

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* each test output is printed immediately

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* Formatting, remove comment. Re-enable MSVC debug test.

---------

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-21 14:34:16 +01:00
Christoffer Lerno
5c4197debd Refactoring of decl_register 2025-12-21 14:10:06 +01:00
Christoffer Lerno
41261d40b2 - Incorrectly using LLVMStructType when emitting dynamic functions on MachO #2666 2025-12-20 21:20:35 +01:00
Zack Puhl
48ecceab54 Add Streebog-256 and -512 Hashing (aka "GOST-12") (#2659)
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-20 19:40:22 +01:00
Zack Puhl
0d9547a388 Add RIPEMD Hashing to stdlb (#2663)
* Add RIPE-MD Hashing to stdlib
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-20 19:39:12 +01:00
Manu Linares
d2f59c5b3f Linux: implement signal stacktrace using SA_SIGINFO + sigaltstack (#2653)
* Linux: implement signal stacktrace using SA_SIGINFO + sigaltstack

Adds proper signal handlers for SIGSEGV/SIGBUS/SIGILL on Linux,
enables backtraces from signal context, and exits with correct POSIX
signal codes (128+signal). Fixes missing "signal stacktrace" support

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* defer libc::dlclose(handle);

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* fix double backtrace on panic

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* add guards for Linux X86_64

- remove comments
- uncomment MContext_t for Linux AARCH64

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* fix guards, missed in two places

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

---------

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-20 03:07:23 +01:00
limit-ordinal
85166bc706 Add NetBSD Support (#2661)
* Add NetBSD Support

Includes:
- Hints to find non-compatibility libc functions
- Struct and constant definitions for sockets, polling, etc.
- Changes to the linker code to work around some quirks in the NetBSD dynamic linker
- A target triple for netbsd aarch64 so llvm builds/links the compiler properly on this platform

* Updated releasenotes and some compacting

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-19 19:23:06 +01:00
Christoffer Lerno
bf26898645 Fixed test. 2025-12-19 19:01:19 +01:00
Zack Puhl
1f2bcd5462 Implement BLAKE2 Hashing in stdlib (#2648)
* Implement BLAKE2 Hashing in stdlib

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-19 17:22:54 +01:00
Christoffer Lerno
4e6cd4283c - Remove use of LLVMGetGlobalContext for single module compilation.
- Fixed bug where constants would get modified when slicing them. #2660
2025-12-19 17:17:45 +01:00
Zack Puhl
9aec5de105 [stdlib] Add CT @in Macro/Builtin (#2662)
* [stdlib] Add CT `@in` Macro/Builtin
2025-12-19 15:55:20 +01:00
Manu Linares
dec49b05b8 Fix: Correct precision calculation for floating point formatting (#2657)
* Fix: Correct precision calculation for floating point formatting

---------

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-19 15:51:42 +01:00
Christoffer Lerno
29bcd2c96e Update libc test 2025-12-18 09:53:38 +01:00
Zack Puhl
97a9cab218 Fix ChaCha20 Alignment Issues 2025-12-18 09:07:51 +01:00
Christoffer Lerno
436af4dbca Re-enable OpenBSD 2025-12-17 20:11:14 +01:00
Christoffer Lerno
b08a2a2c0f Disable openbsd 2025-12-17 17:21:32 +01:00
Christoffer Lerno
b43dac24c4 Fix incorrect alignment in ChaCha20 2025-12-17 15:38:51 +01:00
Zack Puhl
bae0f0f579 Implement ChaCha20 Crypto in stdlib (#2643)
* ChaCha20 implementation, first pass

* fix bug with clone_slice when length is 0

* final ChaCha20 crypto tidying

* final adjustments; add benchmark

* add guards everywhere else or w/e

* stdlib 'i++' conformity

* release notes & security warning updates

* update tests; cleanup; default counter should be 0 not 1

* remove prints in test file

* add extra unit tests for unaligned buffers

Co-authored-by: Manu Linares <mbarriolinares@gmail.com>

* one final alignment test

* nice contraction of tests w/ some paranoia sprinkled in

* nearly double the efficiency of chacha20's transform

Co-authored-by: Manu Linares <mbarriolinares@gmail.com>

* fix memory leak in test case

* improve one of the unit tests to cover more cases

* greatly simplify chacha20 'transform'

Co-authored-by: Manu Linares <mbarriolinares@gmail.com>

---------

Co-authored-by: Manu Linares <mbarriolinares@gmail.com>
2025-12-17 15:10:45 +01:00
Zack Puhl
8055c340f6 Add Implicit @pool for Each Unit Test Invocation (#2654)
* Add Implicit `@pool` for Each Unit Test Invocation
2025-12-17 15:08:02 +01:00
Christoffer Lerno
00c1210625 Rename append_char_buffer 2025-12-16 22:11:58 +01:00
Christoffer Lerno
070b13c81c More fixes to $$LINE 2025-12-16 21:48:35 +01:00
Christoffer Lerno
415c9639e7 - Deprecated DString.append_chars, use DString.append_string
- Deprecated `DString.append_string` for DStrings, use `DString.append_dstring` instead.
- Added `DString.append_char_buffer`.
2025-12-16 20:17:54 +01:00
Christoffer Lerno
996f8a6a4d Shuffle names as <-> to 2025-12-16 16:15:01 +01:00
Christoffer Lerno
e49e3e32e7 - Fix error message when a method has the wrong type for the first argument.
- Add `any.to` and `any.as`.
2025-12-16 16:02:56 +01:00
Christoffer Lerno
86a05ba6c0 Fix issue with inlining span. 2025-12-16 14:22:25 +01:00
Christoffer Lerno
651735f9a0 - $$LINE would sometimes incorrectly be constant. 2025-12-16 13:30:51 +01:00
Michael Chernigin
759389066f Add Elf32_Shdr and Elf64_Shdr to std::os::linux 2025-12-16 00:17:14 +01:00
Christoffer Lerno
3577a2d6b8 Updated tests and error message for failed generic lookups. 2025-12-13 18:51:45 +01:00
Christoffer Lerno
b63886b879 Fix test 2025-12-13 18:06:16 +01:00
Christoffer Lerno
466d3bc1b6 - Hard limit of 127 characters for identifiers. 2025-12-13 03:51:39 +01:00
Lexi
2a2c0f5d91 Add a leniency flag to String.unescape(), and fix a memory leak in String.(un)escape() (#2640)
* fix memory leak in string_escape.c3
* add unescape_lenient and tunescape_lenient
* Optimize for the use of the temp allocator.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-13 00:49:01 +01:00
Christoffer Lerno
0c0d0ace4d Fix requirement to use prefix when using $defined 2025-12-13 00:38:19 +01:00
Zack Puhl
a0ab10c23e Add Poly1305 Universal Hashing Algorithm (MAC) (#2639)
* Add Poly1305 Universal Hashing Algorithm (MAC)
2025-12-12 23:08:49 +01:00
DylanDoesProgramming
0685260454 add --linux-libc=host option and set default to host (#2636)
* added `--linux-libc=host` and set default to `default_linux` from `src/build/builder.c`

* updated `resources/project_schema.json` for `linux-libc` host build option

* Update how libc is set.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-12 22:53:01 +01:00
Christoffer Lerno
15662bd32f Typedefs and structs with inline types supporting lengthof would not work with lengthof #2641. 2025-12-12 20:57:32 +01:00
Christoffer Lerno
475816251b - Strings assigned to longer arrays would crash codegen, e.g. char[10] x = "abcd. 2025-12-12 20:35:11 +01:00
Lexi
bf62d11a73 Improve the error message for miscasing types in the last argument of a method (#2629)
* Improve the error message for miscasing types in the last argument of a method
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-11 16:46:56 +01:00
Christoffer Lerno
37d42d555d - Compiler crash when concatenating structs and arrays to an untyped list. 2025-12-11 03:46:15 +01:00
Christoffer Lerno
1fc522ec9c - Subscripting of constant slices would sometimes be considered non-constant #2635. 2025-12-10 14:11:51 +01:00
Christoffer Lerno
3a8a6aa429 - Ignore const null check on deref in $defined and $sizeof #2633. 2025-12-10 00:02:24 +01:00
Technical Fowl
e15ee23925 Matrix math cleanup (#2620)
* Cleanup inconsistent matrix math functions
* Add more matrix unit tests
2025-12-07 00:08:51 +01:00
Savino Pio Liguori
85657c9200 Added "implement-libc" feature. (#2564)
This feature adds an option from project.json and build options to disable libc unreachable statements in order to be able to implement one for yourself. Useful feature for bare metal developing.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-07 00:07:54 +01:00
Sander van den Bosch
3f20e5af1d add join for ThreadPool without destroying the threads (#2579)
* add join for ThreadPool without destroying the threads
* Make the main Thread block waiting for the worker threads to finish instead of buzy looping and do proper initialization and freeing of all variables.
* Updated test to use `atomic_store` and  take into account the maximum queue size of the threadpool.
* - Add `ThreadPool` join function to wait for all threads to finish in the pool without destroying the threads.
- Return of Thread/Mutex/CondVar `destroy()` is now "@maydiscard" and should be ignored. It will return void in 0.8.0.
- Return of Mutex `unlock()` and `lock()` is now "@maydiscard" and should be ignored. They will return void in 0.8.0.
- Return of ConditionVariable `signal()` `broadcast()` and `wait()` are now "@maydiscard". They will return void in 0.8.0.
- Return of Thread `detatch()` is now "@maydiscard". It will return void in 0.8.0.
- Buffered/UnbufferedChannel, and both ThreadPools have `@maydiscard` on a set of functions. They will retunr void in 0.8.0.
- Pthread bindings correctly return Errno instead of CInt.
- Return of Thread `join()` is now "@maydiscard".

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-06 23:54:04 +01:00
Christoffer Lerno
18e2838772 - Hex escapes like "\x80" would be incorrectly lowered. #2623 2025-12-06 18:24:15 +01:00
Walther Chen
f023db8638 fix ByteBuffer.grow (#2622)
* fix ByteBuffer.grow

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-06 17:57:11 +01:00
Christoffer Lerno
cb0b94c064 - Optional does not play well with bit ops #2618. 2025-12-06 00:46:42 +01:00
Christoffer Lerno
7087665ccc Updated grammar 2025-12-05 23:00:58 +01:00
Christoffer Lerno
1793dd7f0b Correctly detect incorrect use of ~ and ^ 2025-12-05 20:42:37 +01:00
Christoffer Lerno
a61fd6d280 - Casting bitstruct to wider base type should be single step #2616.
- Experimental accept of ~ and ^ for optionals.
2025-12-05 20:18:32 +01:00
Christoffer Lerno
034a048c8a - Regression with npot vector in struct triggering an assert #2219. 2025-12-05 17:26:22 +01:00
Christoffer Lerno
608fc4df9f Update to version 0.7.9. Updated grammar. 2025-12-05 15:30:50 +01:00
Christoffer Lerno
5961bcaf86 Update current version. 2025-12-04 15:45:14 +01:00
Christoffer Lerno
fd5489458a Additional fix and updated error message. 2025-12-03 22:25:38 +01:00
Christoffer Lerno
cf5dd5496a @param name parsing too lenient #2614. 2025-12-03 21:54:06 +01:00
DylanDoesProgramming
ccffa03de2 musl-based linux distro support (#2577)
* added switch statement to link musl-based linux distros to ld-musl-x86-64.so.2

* Update linker.c

/lib/ld-musl-x86-64.so.1 is musl's ld so. My bad

* don't need ENV_MUSLEABI.* in the switch for x86_64

* typo

* Added a CI test for an Alpine Linux container

* Update main.yml

Forgot to have bundle_output job use `env.LLVM_RELEASE_VERSION_ALPINEv3_22`.

* Added env.LLVM_RELEASE_VERSION_ALPINEv3_22 to `upload artifacts`

* changed bundle name to c3-musl-${{matrix,build_type}}.tar.gz

* Undid an accidental name change in build-linux-ubuntu22

* Update main.yml

sudo doesn't exist in alpine by default, and runs in root by default.

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

`--linker=builtin` fails because it forces search of `/lib64/ld-linux-x86-64.so.2`. lib64 doesn't exist on musl unless created as a symlink, and the appropriate so is /lib/ld-musl-<arch>.so.1

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

make isn't in alpine by default. added it in for risc-v example.

* gcc-riscv-none-elf is alpine's package

* using realpath for c3c over using relative pathing

* Have to use relative path for arguments in compiler test

* added --linker=builtin to

* Added linux-musl-<arch> targets

* Added more ld targets for glibc

* set both testproject libs as folders until they behave better

* added linux-musl-x64 target to clib2

* added riscv targets for ld-linux

* ubuntu doesn't have ld in /lib, but solely in /lib64?

* Make MUSL distinct from the target.

* Fix default in project schema

* Fix define

* Fix manifests.

* Update main.yml

add --linux-libc flag for builtin linking

* Grammar refresh

* Update releasenotes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-12-03 00:42:36 +01:00
Christoffer Lerno
ce0ab62c78 Grammar refresh 2025-12-03 00:36:10 +01:00
Christoffer Lerno
0bc5cbca74 More raylib examples. 2025-12-01 22:33:01 +01:00
Christoffer Lerno
a2aa9fae6b - Reference macro parameters syntax does not error in certain cases. #2612 2025-11-30 02:23:00 +01:00
Christoffer Lerno
9d79c3f33d - $defined(hashmap.init(mem)) causes compiler segfault #2611. 2025-11-30 00:08:46 +01:00
Christoffer Lerno
a26055c932 Added more raylib examples. 2025-11-29 22:40:58 +01:00
Christoffer Lerno
b296875c05 Fix BigInt 2025-11-28 23:20:45 +01:00
Glenn Kirk
a54658d37f Add a missing enum value for NSEventModifierFlags 2025-11-28 17:05:57 +01:00
Christoffer Lerno
0a0e097bdf Improve printf("%d") speed. 2025-11-28 17:04:10 +01:00
Dmitry Atamanov
2df51bfe07 Remove unused tester.py 2025-11-28 16:09:06 +01:00
Dmitry Atamanov
cb065152ea Fix typo in @char16 macro 2025-11-28 16:08:38 +01:00
Dmitry Atamanov
07fa14f00b Change sema_decls.c mode to 0664 (rw-rw-r--) 2025-11-28 16:06:58 +01:00
Glenn Kirk
ff1b17d18b Fix the precompiled binary download links on the Github readme page 2025-11-28 16:04:41 +01:00
Christoffer Lerno
c3d5778ae0 Cache printf output. 2025-11-28 12:43:26 +01:00
Christoffer Lerno
373ad1a399 Added raylib example 2025-11-27 23:39:38 +01:00
Christoffer Lerno
6deed2d4a4 Update raylib in CI 2025-11-27 22:58:04 +01:00
Christoffer Lerno
6324d78c32 Added raylib example. 2025-11-27 21:53:32 +01:00
Christoffer Lerno
9e14338b77 Update raylib examples. 2025-11-27 20:42:35 +01:00
Christoffer Lerno
6e4614b6a4 - Fix bug when converting from vector to distinct type of wider vector. #2604 2025-11-27 16:40:55 +01:00
Christoffer Lerno
0b52819090 - Bug on rethrow in return with defer #2603. 2025-11-27 12:53:12 +01:00
Christoffer Lerno
404a78943d - Somewhat faster BigInt output. 2025-11-27 12:15:40 +01:00
Christoffer Lerno
7215a9fa12 Unexpected maybe-deref subscript error with out parameter #2600. 2025-11-27 00:21:59 +01:00
Christoffer Lerno
463c6957fc - Support int $foo... arguments. #2601 2025-11-26 23:54:18 +01:00
Christoffer Lerno
8ec3a52ef7 - Add float[<3>] x = { .xy = 1.2, .z = 3.3 } swizzle initialization for vectors. #2599 2025-11-26 11:31:22 +01:00
Glenn
ab1efdda73 Add NSApplicationTerminateReply and registerClassPair to objc stdlib
The missing registerClassPair function is required to register a class and make it active with objc after allocating with the existing allocateClassPair function.
2025-11-26 08:44:46 +01:00
Christoffer Lerno
4f3b6f922d - Resolving a missing property on a const enum with inline, reached an assert #2597. 2025-11-25 23:48:40 +01:00
Book-reader
869a1d93cb Disable Xtensa target by default, enabled with -DXTENSA_ENABLE (#2586)
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-11-24 23:50:52 +01:00
Christoffer Lerno
5d468ccbf0 - Make printing typeids give some helpful typeid data. 2025-11-24 17:11:19 +01:00
Christoffer Lerno
887ed5b9e9 - String.to_integer does not correctly return in some cases where it should #2590. 2025-11-24 12:46:31 +01:00
Christoffer Lerno
5c1a6d7623 - Incorrect error message when using generic type that isn't imported #2589 2025-11-24 12:27:09 +01:00
Christoffer Lerno
1b49ebf855 - Deprecate --test-nocapture in favour of --test-show-output #2588. 2025-11-24 11:46:06 +01:00
Christoffer Lerno
98155b61f1 - Name property would be used even under c3c test #2587. 2025-11-22 18:02:00 +01:00
m0tholith
60cdea5292 Add test option --test-log-level to choose tests' log level (#2560)
* Add test option `--test-log-level` to choose tests' log level
* draft: Improvements to `--test-log-level`
* Some fixes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-11-20 23:15:14 +01:00
Glenn Kirk
49e836b1ab [stdlib] macOS - Add objc and core foundation types and enums. (#2572)
* Add CGFloat, CGPoint, CGSize, CGRect
* Add WindowCollectionBehavior, WindowLevel, and WindowTabbingMode
* Change EventMask to ulong to match the objc unsigned long long
* Change int types to NS(U)Integer types to match objc
* Add core foundation tests
* Add objc tests
* Add darwin conditional to the test files
* Change enums to const inline to better match the NSEvent.h api.
Update the EventMask helper function to match the NSEvent.h api:
event_type_from -> event_mask_from_type.
* Update the release notes
* Deprecate original objc enums and replace with const inline enums backed with NS numerical types.
Rename the new objc enums with an NS prefix.
Update unit tests to account for new NS prefixed enums.
Add states item length constants to core_foundation.
Status item lengths don't really belong in either file, but as they are
dependant on CGFloat it made sense to move them to the same module.
Update release notes.
* Some tweaks

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-11-20 22:47:20 +01:00
Christoffer Lerno
5b83108dd1 - Add sigsegv stacktrace in test and regular errors for Darwin Arm64. #1105 2025-11-20 21:05:19 +01:00
Christoffer Lerno
a50de26c5d - With project.json, when overriding with an empty list the base settings would still be used. #2583 2025-11-20 14:52:02 +01:00
Christoffer Lerno
7b50c87858 - Fix issue when tests encounter a segmentation fault or similar. 2025-11-20 12:52:25 +01:00
Christoffer Lerno
a816a78e98 Fix alignment. 2025-11-20 12:09:38 +01:00
Christoffer Lerno
39694e65c0 - *(int*)1 incorrectly yielded an assert in LLVM IR lowering #2584. 2025-11-20 10:36:32 +01:00
Christoffer Lerno
2a41fa6281 Passing a single value to @wasm would ignore the renaming 2025-11-18 18:25:07 +01:00
Christoffer Lerno
49b8cfe267 - Allow splatting of structs. #2555 2025-11-17 16:33:22 +01:00
Christoffer Lerno
20dfdf5c5d Fix test breaking for MSVC 2025-11-17 10:46:01 +01:00
Christoffer Lerno
1e543dc286 - Support @param directives for ... parameters. #2578 2025-11-17 10:44:04 +01:00
Christoffer Lerno
06884720e5 - Improve error message for Foo{} when Foo is not a generic type #2574. 2025-11-16 23:54:19 +01:00
Christoffer Lerno
1ea181524e - Fix decl attribute in the wrong place causing an assertion. #2581 2025-11-16 22:57:37 +01:00
Christoffer Lerno
b16ee3119d - Using defer catch with a (void), would cause an assertion. #2580
- Fix testcase
2025-11-16 22:07:04 +01:00
Christoffer Lerno
4e66693065 - Refactored @simd implementation.
- Regression vector ABI: npot vectors would load incorrectly from pointers and other things. #2576
2025-11-16 01:37:39 +01:00
Christoffer Lerno
5f96b8e4c6 - Casting a distinct type based on a pointer to an any would accidentally be permitted. #2575 2025-11-13 15:54:29 +01:00
Christoffer Lerno
748a2f6530 - Enums now work with membersof to return the associated values. #2571
- Deprecated `SomeEnum.associated` in favour of `SomeEnum.membersof`
2025-11-13 13:15:02 +01:00
Christoffer Lerno
6360ddbc77 - When encountering a foreach over a ZString* it would not properly emit a compilation error, but hit an assert #2573. 2025-11-11 12:36:02 +01:00
Christoffer Lerno
eccc6700dc - Fix appending to c:\ or \ #2569. 2025-11-09 21:45:19 +01:00
Christoffer Lerno
52ececba37 - Fix to Path handling c:\foo and \home parent. #2569 2025-11-08 23:42:47 +01:00
Christoffer Lerno
ffc65bcbf4 - Compiler assert with const enum based on vector #2566 2025-11-08 22:30:18 +01:00
Christoffer Lerno
0da6bf4455 - Passing a compile time type implicitly converted to a typeid would crash instead of producing an error. #2568 2025-11-08 22:17:58 +01:00
m0tholith
7063e684ba Make expected error in test::@error macro optional
If not supplied with a fault, `test::@error` checks if a fault of any
type/value was returned
2025-11-07 11:46:37 +01:00
Christoffer Lerno
07363c6ecd - Error message with hashmap shows "mangled" name instead of original #2562. 2025-11-04 23:19:57 +01:00
Christoffer Lerno
5070840da9 - Regression vector ABI: initializing a struct containing a NPOT vector with a constant value would crash LLVM. #2559 2025-11-04 01:04:07 +01:00
Christoffer Lerno
4a25bcc5ee Function referencing in @return? for simplified fault declarations. Check @return? eagerly #2340. 2025-11-03 23:49:35 +01:00
Christoffer Lerno
d43d7100af Fix division-by-zero checks on a /= 0 and b /= 0f #2558. 2025-11-03 21:31:47 +01:00
Christoffer Lerno
791cbbfb62 Fix division-by-zero checks on a /= 0 and b /= 0f #2558. 2025-11-03 16:47:57 +01:00
Christoffer Lerno
9b05dfdef1 Update release notes. 2025-11-03 15:13:58 +01:00
Dave Akers
b072e88bb3 Add $schema to project_default_keys
Adding $schema to avoid compiler WARNING about an unknown parameter.
2025-11-03 15:13:18 +01:00
Christoffer Lerno
af33d2b1cc Update release notes. 2025-11-03 13:58:52 +01:00
Technical Fowl
d438d7510e Add default exception handler for win32 (#2557)
* Add default exception handler for win32
2025-11-03 13:58:07 +01:00
Christoffer Lerno
1673aef74f Fix test. 2025-11-03 12:55:00 +01:00
Christoffer Lerno
b3bce10699 Remove division-by-zero checks for floating point in safe mode #2556. 2025-11-03 02:43:01 +01:00
Christoffer Lerno
3ff922e12b - Missing imports allowed if module @if evaluates to false #2251. 2025-11-02 13:20:36 +01:00
Christoffer Lerno
3b718335ec - Improve multiline string parser inside compiler #2552 2025-11-01 00:12:32 +01:00
Christoffer Lerno
f25ad512a7 Foo.is_eq would return false if the type was a typedef and had an overload, but the underlying type was not comparable. Version 0.7.8. 2025-10-31 22:47:01 +01:00
Christoffer Lerno
5a3c484ceb Remove automatic win32 cleanup. RC2 2025-10-30 21:29:48 +01:00
Christoffer Lerno
331a77c1c2 Update 0.7.7 2025-10-30 11:01:01 +01:00
Christoffer Lerno
045053f6bf Fix for RISCV on LLVM 17 2025-10-28 18:22:32 +01:00
Christoffer Lerno
4809979898 - Add --riscv-cpu settings for RISC-V processors #2549. 2025-10-27 18:33:54 +01:00
Christoffer Lerno
a5b2636b2e Added cpu-flags to the command line help 2025-10-27 14:11:50 +01:00
Christoffer Lerno
c483c3b75f Update naming to cpu-flags 2025-10-27 14:08:21 +01:00
Christopher Coverdale
c10d449e43 Add local TcpSocketPair (#2526)
* Add extern fn socketpair() to posix
* Add extern fn getsockname() for local socketpair loopback in windows
* Add local TcpSocketPair
* Add unit test for TcpSocketPair
* Add implicit wsa startup

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-10-27 13:16:14 +01:00
Christoffer Lerno
54b110a367 Dev (#2547)
* Update feature handling for Wasm, RiscV, Aarch64, Arm
* - The option `--riscvfloat` renamed `--riscv-abi`.
- Add initial `--cpu-features` allowing fine grained control over CPU features.
2025-10-27 01:09:59 +01:00
Dmitry Atamanov
ee8dc3d681 Update Linux nolibc/hello_world (#2546) 2025-10-27 00:45:16 +01:00
Christoffer Lerno
a38a627a1d Allow (Foo)0 bitstruct casts even if type sizes do not match. 2025-10-25 20:33:47 +02:00
Christoffer Lerno
8aaf54e8b1 - Rename @extern to @cname, deprecating the old name #2493. 2025-10-25 15:55:25 +02:00
Christoffer Lerno
423152202f Dev (#2545)
* Optimize vector load / store. Fixes to alignment. Support typedef with `@simd` and `@align` #2543. Update vector ABI #2542
* Fix alignment issue with indirect arguments.
2025-10-25 12:31:06 +02:00
Christoffer Lerno
f37e7460aa Update OpenBSD llvm version. 2025-10-23 13:58:10 +02:00
Christoffer Lerno
8f5d5a0bb5 "Maybe-deref" subscripting foo.[i] += 1 #2540. 2025-10-23 00:42:38 +02:00
Christoffer Lerno
883052a6bb Improved generic inference in initializers #2541. 2025-10-22 23:48:32 +02:00
Christoffer Lerno
9cf271f5fb Refactoring codegen with Flat / Lowered types. Helpers for struct gep. type_get_indexed_type no longer returns the canonical type, fixes issues in #2534 2025-10-21 16:53:38 +02:00
Christoffer Lerno
5d8cad91b1 Fix lambda regression 2025-10-20 22:55:24 +02:00
Giuliano Macedo
614c6989d8 Fixed incorrect format strings when using error_exit. (#2530)
* Fixed incorrect format strings when using `error_exit`.
2025-10-20 11:24:07 +02:00
Tonis
03ad72afbb Quaternion math improvements (#2524)
* Add radians to deg function

* Quaternion math fixes

* Formatting, use splat/swizzling, divide into multiple tests.

---------

Co-authored-by: tonis2 <tanton@paysure.solutions>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-10-20 11:04:28 +02:00
Christoffer Lerno
b924ede71a Regression: Not printing backtrace when tests fail for MacOS #2536. 2025-10-20 02:50:06 +02:00
Christoffer Lerno
a81f857d8c Update to fix with splat. 2025-10-20 02:26:04 +02:00
Christoffer Lerno
6169d7acdf Correctly mention aliased type when method is not implemented #2534. 2025-10-20 00:19:51 +02:00
Christoffer Lerno
4af31da7ea Compiler segfault when getting a nonexistant member from an unnamed struct #2533. 2025-10-20 00:03:15 +02:00
Christoffer Lerno
0bd2c81757 Splatting optional compile-time macro parameter from inside lambda expression does not work #2532. 2025-10-19 23:05:50 +02:00
Book-reader
5ed1281451 fix nix hooks & patch phase 2025-10-15 22:06:10 +02:00
Christoffer Lerno
7b649314ec Fix tests. 2025-10-15 00:50:24 +02:00
Christoffer Lerno
e37343fbe3 Refactor the C ABI conversion to use frontend independent types. 2025-10-14 19:38:51 +02:00
Christoffer Lerno
7b02907830 Try to workaround tag lock. 2025-10-12 21:59:41 +02:00
Christoffer Lerno
6eee760239 Add --max-macro-iterations to set macro iteration limit. 2025-10-11 16:26:07 +02:00
Christoffer Lerno
ae33d1a206 Fix issue testing if something is global. Remove ScopeId. Adding comments to code. 2025-10-11 13:50:06 +02:00
Christoffer Lerno
3430240c2a Update readme with OpenBSD 2025-10-11 00:47:37 +02:00
Christoffer Lerno
6f11260a5c Disallow aliasing of @local symbols with a higher visibility in the alias. 2025-10-10 14:04:19 +02:00
Christoffer Lerno
df67b7dddd Allow .. ranges to use "a..a-1" in order to express zero length. 2025-10-10 00:34:30 +02:00
Christoffer Lerno
f3b7df2ab0 "build-dir" option now available for project.json, added to project. #2323 2025-10-09 23:41:58 +02:00
Christoffer Lerno
a000ae560a Add new builtins $$str_snakecase $$str_replace and $$str_pascalcase.
Added `@str_snakecase`, `@str_replace` and `@str_pascalcase` builtin compile time macros based on the `$$` builtins.
2025-10-09 22:13:59 +02:00
Christoffer Lerno
0d85caf21c Add splat defaults for designated initialization #2441.
Add ??? and +++= to list-precedence.
2025-10-09 12:45:55 +02:00
Christoffer Lerno
e34a26422f Change macro recursion depth to work on MSVC 2025-10-07 23:47:05 +02:00
Christoffer Lerno
fe70f10bcc Sorting functions correctly took slices by value, but also other types by value. Now, only slices are accepted by value, other containers are always by ref. 2025-10-07 22:43:40 +02:00
Christoffer Lerno
d6be1cbf65 Incorrect visibility on local globals with public aliases. #2519 2025-10-07 21:52:15 +02:00
Christoffer Lerno
04cd079d4e - Compiler segfault when accessing member of number cast to bitstruct #2516.
- Additional fix to #2515
- Compiler assert when getting a member of a `bitstruct : char @bigendian` #2517.
2025-10-07 00:12:41 +02:00
Christoffer Lerno
b4b14674b4 - Bitstruct truncated constant error escapes $defined #2515 2025-10-06 20:50:56 +02:00
Christoffer Lerno
5a1831c989 Error when using $vaarg/$vacount/$vasplat and similar in a macro without vaargs #2510. 2025-10-06 00:45:36 +02:00
Christoffer Lerno
e9ec421b3b Compiler fails to stop error print in recursive macro, and also prints unnecessary "inline at" #2513. 2025-10-06 00:31:27 +02:00
Christoffer Lerno
872f63eecc - Bitstruct value cannot be used to index a const array in compile time. #2512 2025-10-05 22:23:07 +02:00
Christoffer Lerno
1eb8c0ced1 Bug in io::write_using_write_byte. 2025-10-05 00:29:34 +02:00
Christoffer Lerno
b5ae2485a7 Update version to 0.7.7 2025-10-04 11:49:23 +02:00
Christoffer Lerno
fe6817f90d Update frontpage version. 2025-10-03 18:42:32 +02:00
Christoffer Lerno
98a72007f8 Releasenotes fixup 2025-10-03 18:41:11 +02:00
Christoffer Lerno
87c1e09a7a Compiler segfault when splatting variable that does not exist in untyped vaarg macro #2509 2025-10-03 14:08:19 +02:00
Christoffer Lerno
e0fbe31f00 Update release versions 2025-10-02 21:27:35 +02:00
Christoffer Lerno
7d6c844b99 Dead code analysis with labelled if did not work properly. 2025-10-02 21:24:05 +02:00
Christoffer Lerno
a03446a26d - Fix lambda-in-macro visibility, where lambdas would sometimes not correctly link if used through a macro. 2025-10-01 21:05:49 +02:00
Christoffer Lerno
a7e77fec78 $for int $a = 1; $a < 2; $a++ would not parse. 2025-10-01 14:53:52 +02:00
Christoffer Lerno
05c3fa1afd Update CI 2025-10-01 09:43:20 +02:00
Christoffer Lerno
30c8435669 Remove prerelease 2025-10-01 09:35:38 +02:00
Christoffer Lerno
94497c968b - Prevent foo.bar = {} when bar is a flexible array member. #2497
- Fix several issues relating to multi-level inference like `int[*][*]` #2505
2025-09-30 23:43:20 +02:00
Christoffer Lerno
281d4af464 Update contact information for CoC 2025-09-29 13:57:01 +02:00
Christoffer Lerno
cb2d0e798e Prevent foo.bar = {} when bar is a flexible array member. 2025-09-29 01:59:38 +02:00
Christoffer Lerno
da67cd4eb0 Assert when the binary doesn't get created and --run-once is used. #2502 2025-09-29 00:16:26 +02:00
Christoffer Lerno
7d06ca6d35 Crash during codegen when taking the typeid of an empty enum with associated values. 2025-09-29 00:01:20 +02:00
Christoffer Lerno
6d45450130 Renaming, and further refactoring. 2025-09-28 15:33:17 +02:00
Christoffer Lerno
27bbeaf79c Remove use of CheckType to simplify expression handling. 2025-09-28 11:41:32 +02:00
Christoffer Lerno
3af5a537da Cleanup around expressions. 2025-09-27 16:37:56 +02:00
Christoffer Lerno
6287e8dfbf Restore some out checking. 2025-09-26 21:19:38 +02:00
konimarti
1f49a5448e Add AES algorithm (#2496)
* crypto: add AES algorithm

* Some updates to the API

* Silence test.

* Fixed stdlib tests

* Some cleanup. Comments. Make internal methods functions.

---------

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

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

---------

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

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

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

* revert sweeping doc comment changes that impacted readability for now

* Some tweaks.

---------

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

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

---------

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

---------

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

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

---------

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

---------

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

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

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

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

* wip: documentation and other fixes

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

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

* update release notes again

* simplify, remove "summary operators" and related tests

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

---------

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

* add some initial string-based benchmarking

* update to non-const string

* do not account for mem times in bench

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

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

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

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

* Fix test failure formatting

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

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

---------

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

Fixes printing negative value

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

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

* Update releasenotes.

---------

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

* remove temp comment

* Update releasenotes.md

* Use sema_note to print the "$if" line

---------

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

* update releasenotes.md

---------

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

* Update sema_liveness.c

Remove comment

---------

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

---------

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

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

---------

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

* Fix import in linux.c3

* epoll: Add unit tests

* epoll: Fix imports in unit tests

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

* update release notes

---------

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

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

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

* Add more platforms for checks in nix build

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

---------

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

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

---------

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

---------

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

---------

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


---------

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

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

---------

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

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-08-02 22:53:36 +02:00
Christoffer Lerno
cb62554a26 Fix version. 2025-08-02 16:57:39 +02:00
Christoffer Lerno
9c58db99af With avx512, passing a 512 bit vector in a union would be lowered incorrectly, causing an assert. #2362 2025-08-02 16:56:43 +02:00
Christoffer Lerno
90c339ebdb List.remove_at would incorrectly trigger ASAN. 2025-08-02 16:56:43 +02:00
Christoffer Lerno
e3a8a3ec02 Support alias foo = module std::io module aliasing. 2025-08-02 16:56:43 +02:00
Christoffer Lerno
bdbe81fedd Fix libm regression. 2025-08-02 13:22:48 +02:00
Christoffer Lerno
f0142e3b1a Fix json check. 2025-08-01 17:07:37 +02:00
Christoffer Lerno
2c6ff00261 Fix alignment. 2025-08-01 10:55:04 +02:00
Christoffer Lerno
61a21203f4 Assigning string literal to char[<*>] stores pointer rather than characters. #2357 2025-08-01 10:32:53 +02:00
Christoffer Lerno
d7affc5028 Fix regression in file output placement. 2025-08-01 10:13:22 +02:00
Christoffer Lerno
eb9549a818 Release candidate 0.7.4 2025-08-01 00:06:16 +02:00
Christoffer Lerno
6d9906db0a Fix implicit linking from macros when it's not valid to add dependencies. 2025-07-31 16:21:50 +02:00
Christoffer Lerno
334ee975b9 Assignment evaluation order now right->left, following C++17. 2025-07-31 11:49:42 +02:00
Christoffer Lerno
d600d0898c Fix regression, not permitting macro parameters inlined into naked functions. 2025-07-30 10:38:37 +02:00
Christoffer Lerno
44f4efa5aa Do not allow parameters in naked functions. 2025-07-30 01:01:56 +02:00
Christoffer Lerno
8151305701 Fix so that rethrow is detected as well. 2025-07-29 20:50:32 +02:00
Christoffer Lerno
3ac9bfc387 Allow accessing parameters in a naked function, just disallow return, this fixes #1955. 2025-07-29 20:38:53 +02:00
BWindey
51b9cb85bc Fix get_config_dir() implementation (#2348)
* Fix get_config_dir() implementation

* Formatting and fallback to home if the path is empty.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-29 19:11:56 +02:00
Christoffer Lerno
d805ff9782 Lambda deduplication would be incorrect when generated at the global scope. 2025-07-29 17:45:11 +02:00
Christoffer Lerno
fa9ba3f607 Not setting android-ndk resulted in a "set ndk-path" error. 2025-07-29 11:33:40 +02:00
Zack Puhl
9d2be2851b Add @try Builtin (#2333)
* Add @try macro
* Add @try_catch. Update release notes.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-29 00:50:56 +02:00
Zack Puhl
2639338426 Support full paths with $embed (#2335)
* Support full paths with `$embed`

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-28 23:44:17 +02:00
Christoffer Lerno
d8daa4ac83 Fix dir check Win32 CI 2025-07-28 23:23:09 +02:00
Christoffer Lerno
6641155892 Lambdas now properly follow its attributes #2346. 2025-07-28 19:49:40 +02:00
Christoffer Lerno
86034353ec Remove unnecessary "ret" in naked functions #2344. 2025-07-28 18:36:33 +02:00
Christoffer Lerno
944ef0fc8d Fix incorrect LLVM output folder. 2025-07-28 18:07:47 +02:00
Christoffer Lerno
194b7c4772 Crash when parsing recursive type declaration #2345. 2025-07-28 18:02:25 +02:00
m0tholith
6963e143a1 Add gitignore template to project creation 2025-07-28 10:39:55 +02:00
Christoffer Lerno
4977bd1d78 @format did not work correctly with macros #2341. 2025-07-28 10:38:18 +02:00
Velikiy Kirill
a21e641748 LinkedHashMap: Fix head being null when initializing from map (#2334)
* LinkedHashMap: Fix head being null when initializing from map
2025-07-28 00:26:14 +02:00
Christoffer Lerno
208b0f6d0e Fix issue where recursively creating a dir would be incorrectly marked as a failure the first time.
Place output in `out` by default for projects. Use temp folder for building at the command line.
2025-07-28 00:19:12 +02:00
Christoffer Lerno
0bc546595d - Using @noreturn in a trailing body macro would not work properly #2326.
- Bug when reporting error in a macro return would crash the compiler #2326.
- Short body return expression would not have the correct span.
2025-07-26 02:03:02 +02:00
Velikiy Kirill
a673b4ad66 Add LinkedHashMap and LinkedHashSet implementations (#2324)
* Add LinkedHashMap and LinkedHashSet implementations

Add two new ordered collection types to std::collections:
- LinkedHashMap: insertion-order preserving hash map
- LinkedHashSet: insertion-order preserving hash set
2025-07-25 23:28:10 +02:00
Velikiy Kirill
9a68a5c063 HashSet: Contract message fixes 2025-07-25 23:06:18 +02:00
Velikiy Kirill
e790df539d Add HashSet implementation (#2322)
* Add HashSet implementation
Add a generic HashSet with full allocator support and standard set operations.
- Basic operations: add/remove/contains/clear
- Set operations:union_set/intersection/symmetric_difference/difference/is_subset
- Memory management with allocator support
- Iteration support
- Automatic resizing with load factor control
* Add "add_all" "add_all_from" "remove_all" "remove_all_from"
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-25 19:53:39 +02:00
Christoffer Lerno
fbeb779335 Fix typo in wait_timeout 2025-07-24 14:12:39 +02:00
Christoffer Lerno
625bfa5713 Updated releasenotes. 2025-07-23 17:04:33 +02:00
Christoffer Lerno
943a294900 Fix missing const cast when casting a const int expression. 2025-07-23 17:03:43 +02:00
Christoffer Lerno
3400dd5e42 char[*] b = *(char[*]*)&a; would crash the compiler if a was a slice. #2320 2025-07-23 16:10:31 +02:00
Christoffer Lerno
e8b3c44de3 Add a "wchar" type. 2025-07-23 15:09:55 +02:00
Christoffer Lerno
9575698fa4 Add String conversion functions snake_case -> PascalCase and vice versa. 2025-07-23 00:26:44 +02:00
Christoffer Lerno
5f0a7dd63e Rename AlignedRef -> UnalignedRef 2025-07-22 15:55:43 +02:00
Kiana
38bc11b7b8 Add libc::readlink() (#2316)
* Add libc::readlink()

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-22 15:46:23 +02:00
LowByteFox
04528aee1f fix openbsd CI not uploading artifacts 2025-07-22 15:44:58 +02:00
LowByteFox
5c82747970 Improved OpenBSD support (#2317)
* add openbsd support, compiles and passses all tests
* fix backtrace
* gh actions should include openbsd artifacts
2025-07-22 15:15:20 +02:00
Christoffer Lerno
b45c337515 Contract fix. 2025-07-22 15:14:10 +02:00
Christoffer Lerno
9dc6b0e660 Added AlignedRef generic type. 2025-07-22 15:11:14 +02:00
Christoffer Lerno
428165590e Add Volatile type. 2025-07-22 14:13:48 +02:00
Christoffer Lerno
53051e04a3 Add thread::fence (from $$fence builtin). Ref and RefCounted types. 2025-07-22 00:37:41 +02:00
Christoffer Lerno
869bcf8b2b Removing use of $assignable and deprecate it. Fix regression for stacktraces on MacOS. Added readline_to_stream. Regression: Chaining an optional together with contracts could in some cases lose the optional. 2025-07-21 03:20:40 +02:00
hamkoroke
382a65abcd Support asm x86 popcnt (#2314)
* Support asm x86 popcnt
2025-07-21 02:40:17 +02:00
Christoffer Lerno
908d705669 Removing use of $assignable and deprecate it. 2025-07-20 20:07:06 +02:00
Airtz
d422fb699f More Ed25519 cleanup (#2315)
* Cleanup Ed25519
2025-07-20 19:57:37 +02:00
Kiana
506e63284b Tidy up path.c3 (#2309)
* Tidy up path.c3
2025-07-20 12:26:44 +02:00
Zack Puhl
ed92476916 Add wyhash2 and metro64/128 modern hashing (#2293)
* add wyhash2, metro64, and metro128 hashes; best performing non-crypto hash functions
* add superfast 64-bit a5hash; not streamed, no 128-bit impl
* add komihash and associated tests/benchmarks
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-20 00:06:10 +02:00
LowByteFox
1218afd51f Add ability to compile on OpenBSD (#2310)
* fix curl in cmake, make c3c compile on OpenBSD, fix typo in OS_TYPE_FREEBSD
2025-07-19 21:09:39 +02:00
Christoffer Lerno
b88722b4a6 Copying const enums and regular enums incorrect #2313. 2025-07-19 21:07:25 +02:00
Christoffer Lerno
694d297eb8 "poison" the current function early when a declaration can't be correctly resolved. 2025-07-19 20:49:26 +02:00
Christian Buttner
2053f2767b Add ConditionVariable.wait_until and ConditionVariable.wait_for (#2302)
* Add `ConditionVariable.wait_until` and `ConditionVariable.wait_for`
* Add "@structlike" for typedefs.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-19 13:12:14 +02:00
Christoffer Lerno
448176b0b7 Rename ed25519 keys/signature. 2025-07-19 10:06:51 +02:00
Airtz
b1a22b5002 Implement Ed25519 (#2297)
* Implement Ed25519
* Overloading addition with a pointer would not work.
* Added @addr macro.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-19 01:13:15 +02:00
Christoffer Lerno
e9aee55714 A distinct type based on an array would yield .len == 0 2025-07-18 21:24:09 +02:00
Christoffer Lerno
2acf3c57c7 Check unaligned array access. 2025-07-18 20:45:57 +02:00
Christoffer Lerno
f2babb6063 Remove requirement of license. 2025-07-18 16:49:04 +02:00
Zack Puhl
b8d07474fe Add SipHash Family of Keyed PRFs (#2287)
* implement SipHash family of keyed PRFs
2025-07-18 16:16:47 +02:00
Christian Buttner
cf913b41c6 Fix Formatter.print returning incorrect size (#2306)
* Fix `Formatter.print` returning incorrect size
2025-07-18 15:54:07 +02:00
Christoffer Lerno
adb3df05c6 Allow even smaller memory limits. 2025-07-18 10:54:03 +02:00
Christoffer Lerno
34bded30eb Fix typo 2025-07-17 15:19:02 +02:00
Christoffer Lerno
774a375ce7 @links on macros would not be added to calling functions. 2025-07-17 15:17:58 +02:00
Christoffer Lerno
ee35001732 Suppress codegen of panic printing with when panic messages are set to "off".
Implicit linking of libc math when libc math functions are used.
2025-07-17 14:45:02 +02:00
Christoffer Lerno
da4105ffb1 Whereami fix 2025-07-16 22:34:14 +02:00
Christoffer Lerno
5c5692ae98 Dev (#2307)
* Refactor whereami
2025-07-16 15:56:36 +02:00
Christoffer Lerno
379d16abe7 Some refactoring of ranges. env::AUTHORS and env::AUTHOR_EMAILS 2025-07-16 12:23:24 +02:00
Christoffer Lerno
078ce38c57 Test fix. 2025-07-16 00:22:28 +02:00
Christoffer Lerno
f99b903d78 - Fix unexpected display of macro definition when passing a poisoned expression #2305. 2025-07-15 23:31:44 +02:00
Christoffer Lerno
3650b81970 Segfault when failing to cast subexpression to 'isz' in pointer subtraction #2305. 2025-07-15 21:47:49 +02:00
Christian Buttner
bb6fcdfa6f Improve atomic.c3
- Change `Atomic.xor` from function to macro
- Allow function pointers as native atomic type
- Use enum inference
2025-07-15 20:32:18 +02:00
Christian Buttner
8df112e157 Add WString.len 2025-07-15 20:30:14 +02:00
Christoffer Lerno
af91f35017 Fix stringify of $vaexpr #2301. 2025-07-15 17:13:10 +02:00
Zack Puhl
3cce90bba1 fix hashmap tinit_with_key_values 2025-07-15 00:26:59 +02:00
Christoffer Lerno
1a351bdb6d Error message for missing arg incorrect for methods with zero args #2296. 2025-07-14 23:21:15 +02:00
Christoffer Lerno
efaac43248 - Virtual memory library.
- New virtual emory arena allocator.
- Fixed resize bug when resizing memory down in ArenaAllocator, DynamicArenaAllocator, BackedArenaAllocator.
- Added feature flag "SLOW_TESTS"
2025-07-14 22:36:43 +02:00
Christoffer Lerno
f082cac762 Updates to API 2025-07-14 03:44:52 +02:00
Christoffer Lerno
2bd289ebd6 Added VirtualMemory 2025-07-14 03:07:03 +02:00
Christoffer Lerno
aba9baf207 Added Vmem allocator 2025-07-13 17:26:57 +02:00
Christoffer Lerno
e755c36ea2 Removed the use of temp allocator in backtrace printing.
Added string::bformat.
2025-07-13 02:58:57 +02:00
Christoffer Lerno
6c7dc2a28e Improved error on using cast expression as lvalue. 2025-07-11 13:47:52 +02:00
Christoffer Lerno
cdd530d807 Fixed bug splatting constants into constants. 2025-07-11 01:55:09 +02:00
Christoffer Lerno
02c0db7b8b Multiline contract comments #2113 2025-07-11 01:18:58 +02:00
Christoffer Lerno
8a62c12089 Update releasenotes with whirlpool 2025-07-10 18:32:26 +02:00
Christoffer Lerno
988549599d $is_const is deprecated in favour of @is_const based on $defined.
`$foo` variables could be assigned non-compile time values.
`$foo[0] = ...` was incorrectly requiring that the assigned values were compile time constants.
2025-07-10 18:31:44 +02:00
Zack Puhl
70159c00cc Add WHIRLPOOL hashing to stdlib (#2273)
* add WHIRLPOOL hashing to stdlib
2025-07-10 16:39:42 +02:00
Christoffer Lerno
2dfbdea889 Update error message for struct initialization #2286 2025-07-10 16:29:49 +02:00
Christoffer Lerno
299d1f530f Correctly poison the analysis after a failed $assert or $error. #2284 2025-07-09 16:51:43 +02:00
Christoffer Lerno
bf0ff8abbc Splat 8 arguments can sometimes cause incorrect behaviour in the compiler. #2283 2025-07-09 16:36:02 +02:00
Ero Mrinin
123b1c8f44 Added unpack macro for Triple (#2277) 2025-07-09 03:11:13 +02:00
Christoffer Lerno
a314e05826 Use hex consistently for .max is 64 bits or more. 2025-07-09 03:09:10 +02:00
Christoffer Lerno
83fd24faa2 Improve error on unsigned implicit conversion to signed. 2025-07-09 02:49:19 +02:00
Christoffer Lerno
1d4ad5f1d5 Function pointers are now compile time constants.
const enum cannot be set to function pointer unless it's a lambda #2282.
2025-07-08 12:42:16 +02:00
Christoffer Lerno
26d5cc694a Formatting option "%h" now supports pointers. 2025-07-08 11:43:49 +02:00
Christoffer Lerno
a2122e0153 Update error message. 2025-07-08 00:24:29 +02:00
Christoffer Lerno
10fc94aaa7 Add RISCV asm print. 2025-07-07 23:52:43 +02:00
Christoffer Lerno
0835bada39 Add --list-asm to view all supported asm instructions. 2025-07-07 23:49:39 +02:00
Christoffer Lerno
277af1a2b6 Fix rdtsc instruction. 2025-07-07 19:02:13 +02:00
Christoffer Lerno
5b835bec3e Fix to codegen when using a bitstruct constant defined using a cast with an operator #2248. 2025-07-07 17:09:32 +02:00
Christoffer Lerno
dc23cef59a LLVM 20 compatibility for test. 2025-07-07 03:01:09 +02:00
Christoffer Lerno
098079d317 Fix analysis error compiling. Fix $define for updated shift. 2025-07-07 02:50:17 +02:00
Christoffer Lerno
1ab57ecf20 Improve contract for readline. #2280 2025-07-07 02:42:48 +02:00
Christoffer Lerno
808ab56545 - Bit shift operation fails with inline uint enum despite matching underlying type #2279. 2025-07-07 02:30:54 +02:00
Christoffer Lerno
19acdc7a19 Do not do certain implicit conversions on enums in binary expression. 2025-07-06 21:44:06 +02:00
Christoffer Lerno
e15fdc709f Improve error message when doing a rethrow in a function that doesn't return an optional.
Array indices are now using int64 internally.
2025-07-06 20:20:42 +02:00
Disheng Su
457244e3de Fix json parser (#2272)
* Fix json parser number

* Fix json parser leading zero

* Fix json parser with duplicated keys

* Fix json parser with trailing characters

* Fix json parser: set recursive depth to 128

* Fix json parser: skip comment to false

* Fix json parser: reject number trailing with null

* Make max depth configurable. Simplify with defer catch. Accept `2.`

* Make max depth configurable. Simplify with defer catch. Accept `2.`

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-06 03:07:46 +02:00
Ero Mrinin
d5559ecafd Tuple update (#2235)
* 'next_float' macro patch

More optimized implementation.

* tuple-type update

* U suffix in 'next_float'

* Do not add triplet, quadruplet, keep Tuple but deprecate. Add unpack

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-06 02:08:59 +02:00
Kiana
802fbfcf1e Add ansi.c3 for AnsiColor support (#2261)
* Add ansi.c3 for AnsiColor support

* Added tests

updated functions to macros
added formatting codes

* Fixed indentation

* Update names. Add plain rgb version. Add runtime colors.

* Update indentation, add 21-29

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-07-06 01:37:54 +02:00
Christoffer Lerno
a20e74c401 Fixes to thread local pointer handling. 2025-07-06 00:22:21 +02:00
Christoffer Lerno
7cdb1ce9eb Catch accidental foo == BAR; where foo = BAR; was most likely intended. #2274 2025-07-05 20:40:55 +02:00
Christoffer Lerno
0d170a70b6 Make to_float more tolerant to spaces. 2025-07-05 19:42:44 +02:00
Christoffer Lerno
b19cd0b87d Casting a fault to a pointer would trigger an assert. 2025-07-05 01:38:44 +02:00
Christoffer Lerno
50efc95c83 Update sponsors 2025-07-05 00:57:23 +02:00
Christoffer Lerno
fa50268b4e Update max memory. 2025-07-04 17:46:02 +02:00
Christoffer Lerno
ae1d51d089 --max-mem now works correctly again. 2025-07-04 17:06:52 +02:00
Christoffer Lerno
1b8355ff07 Update error message on invalid operator. 2025-07-04 14:33:06 +02:00
Christoffer Lerno
f32afb70b8 Add contract to create thread. 2025-07-04 11:02:23 +02:00
Christoffer Lerno
60d96ca7b7 Initialize pool correctly in print_backtrace. 2025-07-04 02:43:49 +02:00
Christoffer Lerno
014f734260 Remove unused code. 2025-07-04 02:26:01 +02:00
Christoffer Lerno
de4963ef95 Add --echo-prefix to edit the prefix with $echo statements. Supports {FILE} and {LINE} 2025-07-04 01:09:02 +02:00
ryuukk
e7d3e60ebd Disable libxml2 dependency on linux (#2268)
* Disable libxml2 depdendency

* All the linuxes
2025-07-04 00:14:10 +02:00
Christoffer Lerno
a46f73ad24 Const slice indexing was not bounds checked. 2025-07-03 23:53:01 +02:00
Christoffer Lerno
759bc1d909 Const slice lengths were not always detected as constant. 2025-07-03 23:32:02 +02:00
Christoffer Lerno
c79c9dac8d Inline r / complex for complex numbers fixed. 2025-07-03 23:04:16 +02:00
Christoffer Lerno
635d4babc4 Fix tests. 2025-07-03 22:37:23 +02:00
Christoffer Lerno
9b3b4ae8be $for ct-state not properly popped. 2025-07-03 22:20:14 +02:00
Christoffer Lerno
b3e7f074e9 Forgot the fix... 2025-07-03 15:46:35 +02:00
Christoffer Lerno
ee1ed73fc5 Non-const macros may not return untyped lists. 2025-07-03 15:45:14 +02:00
Christoffer Lerno
10e11fb742 Better detect offending cast. 2025-07-03 15:14:24 +02:00
Christoffer Lerno
8b47317ec7 Fix issue with labelled break inside of a $switch. 2025-07-03 13:11:12 +02:00
Christoffer Lerno
04626b72cd Check up the hierarchy when considering if an interface cast is valid #2267. 2025-07-03 12:36:35 +02:00
Christoffer Lerno
2151cd0929 Missing bounds check on upper bound with const ranges foo[1:3]. 2025-07-02 13:12:49 +02:00
Christoffer Lerno
93ded9c1e0 Switch case with const non-int / enum would be treated as ints and crash. #2263 2025-07-02 12:26:26 +02:00
Christoffer Lerno
20964b43ce Fix of const enum resolution order #2264 2025-07-02 12:01:52 +02:00
Christoffer Lerno
af192354fd Comparing a null ZString with a non-null ZString would crash. 2025-07-02 01:22:34 +02:00
Christoffer Lerno
ad48637cbb Correctly reject interface methods type and ptr. 2025-07-01 20:08:46 +02:00
Christoffer Lerno
89507bd335 Improved error messages on missing qualifier on enum value. #2260 2025-07-01 17:08:12 +02:00
Christoffer Lerno
21533ffee4 Update sponsors. 2025-07-01 16:15:38 +02:00
Christoffer Lerno
4502a9286c Fix typeid on compile time types. 2025-07-01 00:27:17 +02:00
Christoffer Lerno
ba11511c69 Update release notes. 2025-07-01 00:03:28 +02:00
Christoffer Lerno
965ef19a5b Allow $typeof to return a compile time type. 2025-07-01 00:02:12 +02:00
Christoffer Lerno
8f86b331c1 Fix --use-old-enums 2025-06-30 23:10:32 +02:00
Christoffer Lerno
59a1590955 Hex string formatter check incorrectly rejected slices. 2025-06-30 21:41:52 +02:00
Christoffer Lerno
fad87b294b mkdir/rmdir would not work properly with substring paths on non-windows platforms. 2025-06-30 21:41:52 +02:00
Christoffer Lerno
13bb2b6690 Const Enums From / to ordinal using casts is back. Add "--use-old-enums", deprecating lookup. 2025-06-30 21:41:52 +02:00
Christoffer Lerno
4a803ed0cf Bump version 2025-06-30 19:33:41 +02:00
Christoffer Lerno
9e80f1b26c Release candidate. 2025-06-30 17:21:32 +02:00
Christoffer Lerno
9299c78747 Detect when a slice on the stack is accidentally returned from a function. 2025-06-30 15:56:19 +02:00
Christoffer Lerno
e1a125e326 - Initial support for #1925, does not affect C compilation yet, and doesn't try to link etc. Using "--emit-only" 2025-06-29 23:50:17 +02:00
cubedium
a13eb99962 Added colored error and warning compiler messages. (#2253)
* Added colored error and warning compiler messages.
* Fixed the warning messages to be colored yellow instead of blue.
* Made the use_ansi function public with compiler_internal.h and toggleable colored error messages with the --ansi flag
* Moved use_ansi declaration. No ansi on test/lsp output.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-06-29 20:22:09 +02:00
Disheng Su
d46733e11a Add string escaping and unescaping functionality (#2243)
* Add `String.escape`, `String.unescape` for escaping and unescaping a string.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-06-29 20:11:11 +02:00
Christian Buttner
ce569462f6 Improve CMakeLists.txt and Windows build 2025-06-29 18:21:11 +02:00
Christoffer Lerno
9285dfefad - $member.get supports bitstructs.
- $member.set for setting members without the *& trick.
- io::struct_to_format now supports bitstructs.
2025-06-29 01:19:09 +02:00
Book-reader
5246ef83e7 Fix typo in libc_allocator.c3 2025-06-28 14:33:18 +02:00
Zack Puhl
0448e50b3d Fix Incorrect SHA256 Hashes for Large Inputs (#2247)
* fix >256MiB sha256 bitcount computation overflow
2025-06-28 03:20:33 +02:00
Christoffer Lerno
2d535aaa25 Default assert() message drops parens #2249. 2025-06-28 00:07:26 +02:00
Christoffer Lerno
dc1e5323ab Segfault in the compiler when using a bitstruct constant defined using a cast with an operator #2248. 2025-06-27 23:00:14 +02:00
Christoffer Lerno
63abf1c2f8 - Compiler null pointer when building a static-lib with -o somedir/... #2246 2025-06-27 15:46:17 +02:00
Christoffer Lerno
df8904909b Fix bugs relating to optional interface addr-of #2244. 2025-06-27 15:02:12 +02:00
Christoffer Lerno
e986e3a8c0 Refactoring. 2025-06-25 15:08:57 +02:00
Christoffer Lerno
f67da4f315 Refactoring, optimize negation in if statement. 2025-06-25 12:33:17 +02:00
Christoffer Lerno
8a4e7b6ce8 Fix regression causing text output. 2025-06-25 02:48:22 +02:00
Christoffer Lerno
2b0fb52f65 Fix regression: Enum inference with compare operators #2241 2025-06-25 00:55:29 +02:00
Christoffer Lerno
faf073885f Updated fix of #2218 2025-06-24 22:28:14 +02:00
Christoffer Lerno
11b8a9808d Fix overeager constant folding. 2025-06-24 16:17:04 +02:00
Christoffer Lerno
1ef9c73342 Compile-time comparison of constant vectors. #1575. 2025-06-24 16:04:24 +02:00
Christoffer Lerno
92d56b7a35 Updated project suggestions 2025-06-24 14:19:41 +02:00
Christoffer Lerno
a894adbdd6 Assert comparing untyped lists #2240. 2025-06-24 12:47:08 +02:00
Christoffer Lerno
605a7c4091 Improve error message on pointer diff #2239. 2025-06-23 23:47:26 +02:00
Zack Puhl
adabae2a24 add 'strdup' reference to libc 2025-06-23 15:37:25 +02:00
Christoffer Lerno
affb722b23 @tag didn't work with members #2236. 2025-06-23 14:45:16 +02:00
Christoffer Lerno
1b2f5989e1 Assert casting bitstruct to short/char #2237 2025-06-23 14:12:34 +02:00
Christoffer Lerno
e0b6c83a62 Aaaand let's get that last test. 2025-06-23 02:05:31 +02:00
Christoffer Lerno
839f835845 Last test fix. 2025-06-23 00:58:16 +02:00
Christoffer Lerno
2636a855c4 Forgotten test directives. 2025-06-23 00:38:12 +02:00
Christoffer Lerno
0d147a48b2 Another fix to #2226 2025-06-23 00:36:15 +02:00
Christoffer Lerno
aff3a3f746 Compiler segfault when using distinct type in attribute imported from other module #2234. 2025-06-22 23:58:38 +02:00
Christoffer Lerno
c95204c3f7 Fix inc/dec vector ptr. 2025-06-22 18:09:08 +02:00
Christoffer Lerno
794e8371c8 Rename test. 2025-06-21 23:47:49 +02:00
Christoffer Lerno
2bbc6cbbca Further #2226 fixes. 2025-06-21 23:18:17 +02:00
Christoffer Lerno
07bd37da43 Further #2226 fixes. 2025-06-21 16:50:52 +02:00
Christoffer Lerno
a0497e9274 math::overflow_* wrappers incorrectly don't allow distinct integers #2221. 2025-06-21 13:28:45 +02:00
Christoffer Lerno
fa730e7ec2 Overload resolution fixes to inline typedef #2226. 2025-06-21 13:03:16 +02:00
Christoffer Lerno
b4a6e3704f Update grammar to interpret $Foo = int as a statement. 2025-06-21 00:30:43 +02:00
Christoffer Lerno
dd80e8b799 Compile time type assignment (eg $Foo = int) is no longer an expression. 2025-06-20 23:31:40 +02:00
Zack Puhl
5efc721b0c Add SHA512 Module to stdlib (#2227)
* add sha512 module to stdlib with passing unit tests

* fix release notes PR ref num for this

* deduplicate const SHA512 hash info
2025-06-20 19:08:45 +02:00
Christoffer Lerno
20c13c0bb4 - Incorrect handling of constant null fault causing compiler crash #2232. 2025-06-20 15:29:52 +02:00
Christoffer Lerno
cd3e924d1e Fix test for LLVM 20 2025-06-20 02:01:32 +02:00
Estanislao Pérez Nartallo
2e42868467 Fix error maybe-uninitialized (#2230)
* Add build instructions for Arch Linux

* Fix error maybe-uninitialized in llvm_codegen_expr.c when compiling with clang 20.1.6
2025-06-20 00:55:06 +02:00
Christoffer Lerno
8d698b5e40 Lambda C-style vaargs were not properly rejected, leading to crash #2229. 2025-06-20 00:52:03 +02:00
Christoffer Lerno
2f45beecbe @pool now has an optional reserve parameter, some minor changes to the temp_allocator API 2025-06-19 01:13:43 +02:00
Christoffer Lerno
1b4b9bca94 Linking fails on operator method imported as @public #2224. 2025-06-18 23:34:39 +02:00
Tanis Pérez Nartallo
40ae9d2e55 Add build instructions for Arch Linux 2025-06-18 23:34:16 +02:00
Christoffer Lerno
0df538d0e2 Test LLVM 20 compatibility 2025-06-18 22:41:35 +02:00
Christoffer Lerno
aa425a0886 Fixes to x += { 1, 1 } for enum and pointer vectors #2222. 2025-06-18 22:27:30 +02:00
Christoffer Lerno
842788e59d x += 1 and x -= 1 works propertly on pointer vectors #2222. 2025-06-18 17:02:56 +02:00
Christoffer Lerno
2b97d7d59c x++ and x-- works on pointer vectors #2222. 2025-06-18 13:17:21 +02:00
Christoffer Lerno
75f78551cf Rename TypdefDecl to TypeAliasDecl 2025-06-18 11:59:44 +02:00
Christoffer Lerno
01ef53a090 Bug when offsetting pointers of large structs using ++ and --. 2025-06-18 10:13:48 +02:00
Christoffer Lerno
a55f56a88f Linker errors when shadowing @local with public function #2198 2025-06-18 02:07:07 +02:00
Christoffer Lerno
eb75d8f82a Method ambiguity when importing parent module publicly in private submodule. #2208 2025-06-18 00:18:56 +02:00
Christoffer Lerno
f07bd3cbc6 $defined(#expr) broken with binary. #2219 2025-06-17 18:00:16 +02:00
Christoffer Lerno
93640699be Support distrinct types as the base type of bitstructs. #2218 2025-06-17 16:49:46 +02:00
Christoffer Lerno
99e29bff8d Bug in AST copying would make operator overloading like += compile incorrectly #2217 2025-06-17 16:02:43 +02:00
Ero Mrinin
95137db64b 'next_float' macro patch (#2213)
More optimized implementation.
2025-06-17 09:54:27 +02:00
Christoffer Lerno
e7ce79e731 Fix error for named arguments-order with compile-time arguments #2212 2025-06-16 23:56:03 +02:00
Christoffer Lerno
779f548a00 Allow generics over distinct types #2216. 2025-06-16 23:16:35 +02:00
Christoffer Lerno
f0bd93d1f0 Additional #2210 fixes. 2025-06-16 22:56:34 +02:00
Christoffer Lerno
3ce15bd7af Incorrect codegen if a macro ends with unreachable and is assigned to something. #2210 2025-06-15 22:35:44 +02:00
Christoffer Lerno
07eee04e94 In some cases, the compiler would dereference a compile time null. #2215 2025-06-15 21:58:39 +02:00
Christoffer Lerno
1f7b62b248 Fix to is_array_or_slice_of_char #2214.
`is_array_or_slice_of_char` and `is_arrayptr_or_slice_of_char` are replaced by constant `@` variants.
2025-06-15 21:27:47 +02:00
Christoffer Lerno
b2c994618f Fix to is_array_or_slice_of_char #2214.
`is_array_or_slice_of_char` and `is_arrayptr_or_slice_of_char` are replaced by constant `@` variants.
2025-06-15 16:54:20 +02:00
Christoffer Lerno
2afa544d7d Correctly format '%c' when given a width. #2199 2025-06-15 02:27:36 +02:00
Christoffer Lerno
dda2d2ecbe Show code that caused unreachable code #2207
`$echo` would suppress warning about unreachable code. #2205
2025-06-15 00:37:28 +02:00
Christoffer Lerno
f79f6d4001 - cflags additions for targets was not handed properly. #2209 2025-06-14 23:40:54 +02:00
Christoffer Lerno
cf167c9446 Make unreachable() only panic in safe mode. 2025-06-14 18:37:49 +02:00
Christoffer Lerno
f0201f971e Fix NULL -> false. 2025-06-13 22:51:51 +02:00
Christoffer Lerno
a3abea1a33 Reorder fields. 2025-06-13 22:08:07 +02:00
Christoffer Lerno
5f6f52838c The form-feed character '\f' is no longer valid white space. 2025-06-13 21:37:06 +02:00
Christoffer Lerno
e0237096d6 - Support untyped second argument for operator overloading.
- Distinct versions of builtin types ignore @operator overloads #2204.
- @operator macro using untyped parameter causes compiler segfault #2200.
- Add comparison with `==` for ZString types.
2025-06-13 17:12:39 +02:00
Christoffer Lerno
82491a6f85 - Fixes to @format checking #2199. 2025-06-12 02:26:39 +02:00
Christoffer Lerno
1aacb1fa60 Fixed regression compiler crash when using && for untyped parameters #2197. 2025-06-10 16:26:08 +02:00
Christoffer Lerno
bbd9f6dc96 Add --sources build option to add additional files to compile. #2097 2025-06-10 14:09:15 +02:00
Christoffer Lerno
496d23e93f Fix some @require comments. 2025-06-10 01:54:45 +02:00
Christoffer Lerno
e936b999d2 Update Android ABI lowering 2025-06-08 23:14:07 +02:00
Christoffer Lerno
becda6ea1d Improve Android linking 2025-06-08 15:06:19 +02:00
Christoffer Lerno
2ad17a04d4 Improve android detection. 2025-06-08 12:18:55 +02:00
Christoffer Lerno
1617792a35 Fix Android detection. 2025-06-08 11:53:47 +02:00
Christoffer Lerno
c7b3ae0cf9 Fix Android errno 2025-06-08 11:33:28 +02:00
Christoffer Lerno
1dcd40aa5f --lsp sometimes does not emit end tag #2194. 2025-06-08 00:30:11 +02:00
Christoffer Lerno
40554192b1 - Make accepting arguments for main a bit more liberal, accepting main(int argc, ZString* argv)
- Make `$echo` and `@sprintf` correctly stringify compile time initializers and slices.
- Fixes methodsof to pick up all sorts of extension methods. #2192
- Fix regression accidentally allowing `$assert $foo, $bar`
2025-06-08 00:23:04 +02:00
Book-reader
9bc5e259d2 fix types::may_load_atomic with enums 2025-06-07 13:54:27 +02:00
Christoffer Lerno
f66cadccd2 Add printf format to $assert and $error #2183. 2025-06-06 23:50:55 +02:00
Christoffer Lerno
be511b26cd Additional fixes on $define 2025-06-06 20:11:58 +02:00
Christoffer Lerno
4cfa5441d2 Additional fixes on $define 2025-06-06 15:38:46 +02:00
Christoffer Lerno
5e45c34f21 - Deprecate String.is_zstr and String.quick_zstr #2188. 2025-06-06 15:30:46 +02:00
Awang
d7a11903c7 Add external __errno() function for env::ANDROID (#2182) 2025-06-06 14:47:16 +02:00
Christoffer Lerno
b893697a87 Various fixes for $defined 2025-06-06 14:41:20 +02:00
Christoffer Lerno
f2daf2e11e @sprintf macro (based on the $$sprintf builtin) allows compile time format strings #1874. 2025-06-06 03:18:28 +02:00
Christoffer Lerno
9baeca3a8e $eval now also works with @foo, #foo, $Foo and $foo parameters #2114. 2025-06-06 01:23:23 +02:00
Christian Brendlin
ef649050c4 Add support for custom file extensions in project.json targets (Resolves #1315) (#2169)
* [Feat] add support for custom output file extensions in build process

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2025-06-05 23:04:09 +02:00
Christoffer Lerno
d6d0e08906 Allow inference across && #2172. 2025-06-05 14:20:40 +02:00
Christoffer Lerno
c9d9127da6 Deprecate foo.#bar. 2025-06-05 12:51:35 +02:00
Christoffer Lerno
7f85534414 - Implicitly convert from constant typeid to Type in $Type assignment, and $assignable.
- Make $Type parameters accept constant typeid values.
2025-06-05 00:37:16 +02:00
Christoffer Lerno
ba1332dc2a Splatting const slices would not be const. #2185 2025-06-04 16:09:15 +02:00
Christoffer Lerno
45a0895c39 Fix fmod regression. 2025-06-04 15:25:18 +02:00
Christoffer Lerno
72cc8e430a -0xFF will now be a signed integer.
- `-2147483648`, MIN literals work correctly.
2025-06-04 15:20:49 +02:00
Christoffer Lerno
9645bd3289 - $typefrom now also accepts a constant string, and so works like $evaltype.
- `$evaltype` is deprecated in favour of `$typefrom`.
2025-06-03 14:51:56 +02:00
Christoffer Lerno
8fc01d4e1a Simplify contract macros. 2025-06-02 22:53:06 +02:00
Bram Windey
a48e2274e5 Update options.run_dir from target.run_dir if options.run_dir is null 2025-06-02 14:09:29 +02:00
Christoffer Lerno
786e47408a Update compiler version 2025-06-02 13:40:16 +02:00
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
1332 changed files with 88708 additions and 23634 deletions

15
.clang-format Normal file
View File

@@ -0,0 +1,15 @@
IndentWidth: 4
UseCRLF: false
IndentCaseLabels: true
UseTab: ForIndentation
TabWidth: 4
BreakBeforeBraces: Allman
AllowShortBlocksOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: WithoutElse
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeParens: ControlStatementsExceptControlMacros
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInParentheses: false
SpacesInSquareBrackets: false

View File

@@ -1,21 +1,7 @@
---
# Configure clang-tidy for this project.
IndentWidth: 4
UseCRLF: false
IndentCaseLabels: true
UseTab: UT_ForIndentation
TabWidth: 4
BreakBeforeBraces: Allman
AllowShortBlocksOnASingleLine: SBS_Empty
AllowShortIfStatementsOnASingleLine: SIS_WithoutElse
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeParens: SBPO_ControlStatementsExceptControlMacros
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
# Disabled:
# -google-readability-namespace-comments the *_CLIENT_NS is a macro, and
@@ -40,17 +26,19 @@ Checks: >
# Turn all the warnings from the checks above into errors.
WarningsAsErrors: "*"
CheckOptions:
- { key: readability-function-cognitive-complexity.Threshold, value: 100 }
- { key: readability-identifier-naming.StructCase, value: CamelCase }
- { key: readability-identifier-naming.FunctionCase, value: lower_case }
- { key: readability-identifier-naming.VariableCase, value: lower_case }
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
- { key: readability-identifier-naming.ConstexprVariablePrefix, value: k }
- { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase }
- { key: readability-identifier-naming.GlobalConstantPrefix, value: k }
- { key: readability-identifier-naming.StaticConstantCase, value: CamelCase }
- { key: readability-identifier-naming.StaticConstantPrefix, value: k }
- { key: readability-function-cognitive-complexity.Threshold, value: 100 }
- { key: readability-identifier-naming.StructCase, value: CamelCase }
- { key: readability-identifier-naming.FunctionCase, value: lower_case }
- { key: readability-identifier-naming.VariableCase, value: lower_case }
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
- { key: readability-identifier-naming.ConstexprVariablePrefix, value: k }
- { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase }
- { key: readability-identifier-naming.GlobalConstantPrefix, value: k }
- { key: readability-identifier-naming.StaticConstantCase, value: CamelCase }
- { key: readability-identifier-naming.StaticConstantPrefix, value: k }
- { key: readability-identifier-naming.MinimumParameterNameLength, value: 0 }
MinimumParameterNameLength: 0

File diff suppressed because it is too large Load Diff

26
.gitignore vendored
View File

@@ -1,5 +1,7 @@
# Prerequisites
*.d
testrun
benchmarkrun
# Object files
*.o
@@ -7,6 +9,8 @@
*.obj
*.elf
*.ll
*.wasm
*.s
# Linker output
*.ilk
@@ -69,20 +73,34 @@ out/
/cmake-build-debug/
/cmake-build-release/
# Emacs files
CMakeFiles/cmake.check_cache
CMakeCache.txt
# etags(Emacs), ctags, gtags
TAGS
GPATH
GRTAGS
GTAGS
tags
# Clangd LSP files
/.cache/
/compile_commands.json
# 'nix build' resulting symlink
# Nix
result
/.envrc
/.direnv/
# macOS
.DS_Store
# tests
/test/tmp/*
/test/testrun
/test/test_suite_runner
testrun
test_suite_runner
# patches, originals and rejects
*.patch
*.rej
*.orig

View File

@@ -1,5 +1,13 @@
cmake_minimum_required(VERSION 3.20)
set(C3_LLVM_MIN_VERSION 19)
set(C3_LLVM_MAX_VERSION 22)
set(C3_LLVM_DEFAULT_VERSION 21)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
message(FATAL_ERROR "In-tree build detected, please build in a separate directory")
endif()
# Grab the version
file(READ "src/version.h" ver)
if (NOT ${ver} MATCHES "COMPILER_VERSION \"([0-9]+.[0-9]+.[0-9]+)\"")
@@ -7,8 +15,20 @@ if (NOT ${ver} MATCHES "COMPILER_VERSION \"([0-9]+.[0-9]+.[0-9]+)\"")
endif()
# Set the project and version
project(c3c VERSION ${CMAKE_MATCH_1})
message("C3C version: ${CMAKE_PROJECT_VERSION}")
project(c3c VERSION ${CMAKE_MATCH_1} LANGUAGES C CXX)
message("Configuring C3C ${CMAKE_PROJECT_VERSION} for ${CMAKE_SYSTEM_NAME}")
# Helper functions
function(c3_print_variables)
set(msg "")
foreach(var ${ARGN})
if(msg)
string(APPEND msg " ; ")
endif()
string(APPEND msg "${c3_print_prefix}${var}=\"${${var}}\"")
endforeach()
message(STATUS "${msg}")
endfunction()
# Avoid warning for FetchContent
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
@@ -16,7 +36,7 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
endif()
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
if (MSVC)
if (WIN32)
set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib")
set(CMAKE_INSTALL_BINDIR "c:\\c3c")
else ()
@@ -38,34 +58,63 @@ set(CMAKE_CXX_STANDARD 17)
if(MSVC)
message(STATUS "MSVC version ${MSVC_VERSION}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
else()
if (true)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O0 -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O0 -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined,address -fno-exceptions")
add_compile_options(/utf-8)
if(C3_WITH_LLVM)
FetchContent_GetProperties(LLVM_Windows)
if(NOT LLVM_Windows_URL MATCHES "msvcrt")
set(MSVC_CRT_SUFFIX "")
message(STATUS "Detected STATIC LLVM (libcmt)")
else()
set(MSVC_CRT_SUFFIX "DLL")
message(STATUS "Detected DYNAMIC LLVM (msvcrt)")
endif()
endif()
# Force the Runtime to Release (/MT or /MD) even in Debug
# This is required to match our RelWithDebInfo LLVM builds
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
set_property(GLOBAL PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
add_compile_definitions(_ITERATOR_DEBUG_LEVEL=0)
# Suppresses the LNK4098 mismatch warning in Debug builds
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:libcmtd /NODEFAULTLIB:msvcrtd")
else()
add_compile_options(-gdwarf-3 -fno-exceptions)
#add_compile_options(-fsanitize=address,undefined)
#add_link_options(-fsanitize=address,undefined)
endif()
option(C3_LINK_DYNAMIC "link dynamically with LLVM/LLD libs")
# Options
set(C3_LINK_DYNAMIC OFF CACHE BOOL "Link dynamically with LLVM/LLD libs")
set(C3_WITH_LLVM ON CACHE BOOL "Build with LLVM")
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
set(C3_USE_MIMALLOC OFF CACHE BOOL "Use built-in mimalloc")
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
set(C3_USE_TB OFF CACHE BOOL "Use TB")
set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory")
set(C3_LLD_INCLUDE_DIR "" CACHE STRING "Use custom LLD include directory")
set(C3_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation")
set(C3_FETCH_LLVM OFF CACHE BOOL "Automatically download LLVM artifacts")
set(C3_LLVM_TAG "llvm_21.x" CACHE STRING "Tag/Branch to download LLVM from")
set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
set(TCC_LIB_PATH "/usr/lib/tcc/libtcc1.a" CACHE STRING "Use custom libtcc1.a path")
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF)
option(C3_USE_TB "Use TB" OFF)
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
option(C3_WITH_LLVM "Build with LLVM" ON)
option(C3_LLD_DIR "Use custom LLD directory" "")
option(LLVM_CRT_LIBRARY_DIR "Use custom llvm's compiler-rt directory" "")
set(C3_OPTIONS
C3_LINK_DYNAMIC
C3_WITH_LLVM
C3_FETCH_LLVM
C3_LLVM_TAG
C3_LLVM_VERSION
C3_USE_MIMALLOC
C3_MIMALLOC_TAG
C3_USE_TB
C3_LLD_DIR
C3_ENABLE_CLANGD_LSP
LLVM_CRT_LIBRARY_DIR
)
set(C3_USE_MIMALLOC OFF)
if(C3_USE_MIMALLOC)
@@ -80,16 +129,6 @@ if(C3_USE_MIMALLOC)
)
FetchContent_MakeAvailable(mimalloc)
endif()
if (NOT WIN32)
find_package(CURL)
endif()
if(C3_WITH_LLVM)
if (NOT C3_LLVM_VERSION STREQUAL "auto")
if (${C3_LLVM_VERSION} VERSION_LESS 17 OR ${C3_LLVM_VERSION} VERSION_GREATER 21)
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
endif()
endif()
endif()
find_package(Git QUIET)
if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
@@ -107,7 +146,6 @@ if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
endif()
# Clangd LSP support
option(C3_ENABLE_CLANGD_LSP "Enable/Disable output of compile commands during generation." OFF)
if(C3_ENABLE_CLANGD_LSP)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
execute_process(
@@ -118,37 +156,84 @@ if(C3_ENABLE_CLANGD_LSP)
endif(C3_ENABLE_CLANGD_LSP)
if(C3_WITH_LLVM)
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
if (C3_LLVM_VERSION STREQUAL "auto")
set(C3_LLVM_VERSION "19")
if(C3_FETCH_LLVM)
# 1. Determine local platform ID
if (WIN32)
set(C3_LLVM_PLATFORM "windows-amd64")
elseif (APPLE)
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64")
set(C3_LLVM_PLATFORM "darwin-aarch64")
else()
set(C3_LLVM_PLATFORM "darwin-amd64")
endif()
else() # Linux
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "aarch64")
set(C3_LLVM_PLATFORM "linux-aarch64")
elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "riscv64")
set(C3_LLVM_PLATFORM "linux-riscv64")
else()
set(C3_LLVM_PLATFORM "linux-amd64")
endif()
endif()
FetchContent_Declare(
LLVM_Windows
URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt.7z
)
FetchContent_Declare(
LLVM_Windows_debug
URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt-dbg.7z
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message("Loading Windows LLVM debug libraries, this may take a while...")
FetchContent_MakeAvailable(LLVM_Windows_debug)
set(llvm_dir ${llvm_windows_debug_SOURCE_DIR})
else()
message("Loading Windows LLVM libraries, this may take a while...")
FetchContent_MakeAvailable(LLVM_Windows)
set(llvm_dir ${llvm_windows_SOURCE_DIR})
endif()
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH})
find_package(LLVM REQUIRED CONFIG)
find_package(LLD REQUIRED CONFIG)
else()
if (NOT C3_LLVM_VERSION STREQUAL "auto")
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
else()
find_package(LLVM REQUIRED CONFIG)
# 2. Determine if we want Debug or Release
set(C3_LLVM_SUFFIX "")
set(C3_LLVM_TYPE "Release")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(C3_LLVM_SUFFIX "-dbg")
set(C3_LLVM_TYPE "Debug")
endif()
# 3. Construct URL
set(C3_LLVM_ARTIFACT_NAME "llvm-${C3_LLVM_PLATFORM}${C3_LLVM_SUFFIX}")
#set(C3_LLVM_URL "https://github.com/c3lang/c3-llvm/releases/download/${C3_LLVM_TAG}/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
#set(C3_LLVM_URL "https://github.com//ManuLinares/llvm-custom-builds/releases/download/${C3_LLVM_TAG}/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
# We could also just set "latest" here to always fetch the latest release
set(C3_LLVM_URL "https://github.com/c3lang/llvm-for-c3/releases/latest/download/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
message(STATUS "Fetching ${C3_LLVM_TYPE} LLVM artifact for ${C3_LLVM_PLATFORM}...")
message(STATUS "URL: ${C3_LLVM_URL}")
FetchContent_Declare(
LLVM_Artifact
URL ${C3_LLVM_URL}
)
FetchContent_MakeAvailable(LLVM_Artifact)
# 4. Point CMake to the fetched location
set(llvm_dir ${llvm_artifact_SOURCE_DIR})
set(CMAKE_PREFIX_PATH ${llvm_dir} ${CMAKE_PREFIX_PATH})
set(LLVM_DIR "${llvm_dir}/lib/cmake/llvm")
set(LLD_DIR "${llvm_dir}/lib/cmake/lld")
# TEST: For Windows, we might need to add the bin dir to prefix path for find_package to work well
if (WIN32)
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH})
endif()
find_package(LLVM REQUIRED CONFIG NO_DEFAULT_PATH)
find_package(LLD REQUIRED CONFIG NO_DEFAULT_PATH)
else()
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
# Legacy MSVC path if someone explicitly disabled fetching but is on MSVC
find_package(LLVM REQUIRED CONFIG)
find_package(LLD REQUIRED CONFIG)
else()
# Default system search
file (GLOB LLVM_CMAKE_PATHS "/usr/lib/llvm/*/lib/cmake/llvm/")
list (APPEND CMAKE_PREFIX_PATH ${LLVM_CMAKE_PATHS} "/usr/lib/")
if (NOT C3_LLVM_VERSION STREQUAL "auto")
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
else()
find_package(LLVM REQUIRED CONFIG)
endif()
endif()
endif()
if (NOT C3_FETCH_LLVM AND EXISTS /opt/homebrew/lib)
list(APPEND LLVM_LIBRARY_DIRS /opt/homebrew/lib)
endif()
if (EXISTS /usr/lib)
@@ -158,12 +243,15 @@ if(C3_WITH_LLVM)
list(APPEND LLVM_LIBRARY_DIRS /usr/lib)
endif()
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
message(STATUS "LLVM libraries located in: ${LLVM_LIBRARY_DIRS}")
if (NOT LLVM_PACKAGE_VERSION VERSION_GREATER_EQUAL 15.0)
message(FATAL_ERROR "LLVM version 15.0 or later is required.")
if (${LLVM_PACKAGE_VERSION} VERSION_LESS C3_LLVM_MIN_VERSION OR
${LLVM_PACKAGE_VERSION} VERSION_GREATER C3_LLVM_MAX_VERSION)
message(FATAL_ERROR "LLVM ${LLVM_PACKAGE_VERSION} is not supported! LLVM version between ${C3_LLVM_MIN_VERSION} and ${C3_LLVM_MAX_VERSION} is required.")
endif()
if(LLVM_ENABLE_RTTI)
@@ -213,44 +301,48 @@ if(C3_WITH_LLVM)
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
if(NOT ${C3_LLD_DIR} EQUAL "" AND EXISTS ${C3_LLD_DIR})
message("C3_LLD_DIR: " ${C3_LLD_DIR})
set(LLVM_LIBRARY_DIRS
"${LLVM_LIBRARY_DIRS}"
"${C3_LLD_DIR}"
)
list(APPEND LLVM_LIBRARY_DIRS ${C3_LLD_DIR})
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
endif()
message(STATUS "Looking for static lld libraries in ${LLVM_LIBRARY_DIRS}")
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.dll.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.dll.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.dll.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.dll.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.dll.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COFF NAMES lldCOFF.a liblldCOFF.a liblldCOFF.dylib lldCOFF.lib liblldCOFF.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_COMMON NAMES lldCommon.a liblldCommon.a liblldCommon.dylib lldCommon.lib liblldCommon.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_ELF NAMES lldELF.a liblldELF.a liblldELF.dylib lldELF.lib liblldELF.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
find_library(LLD_MACHO NAMES lldMachO.a liblldMachO.a liblldMachO.dylib lldMachO.lib liblldMachO.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
else()
set(LLD_MACHO "")
endif()
find_library(LLD_MINGW NAMES lldMinGW.a liblldMinGW.a liblldMinGW.dylib lldMinGW.lib liblldMinGW.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_WASM NAMES lldWasm.a liblldWasm.a liblldWasm.dylib lldWasm.lib liblldWasm.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
else()
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
message(STATUS "Looking for shared lld libraries in ${LLVM_LIBRARY_DIRS}")
#find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
if(UNIX AND NOT WIN32)
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} REQUIRED)
else()
find_library(LLVM NAMES libLLVM.a LLVM.lib PATHS ${LLVM_LIBRARY_DIRS} REQUIRED)
endif()
set(llvm_libs ${LLVM})
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
else()
set(LLD_MACHO "")
endif()
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
endif()
endif()
if (NOT(${CMAKE_BINARY_DIR} EQUAL ${CMAKE_SOURCE_DIR}))
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
endif()
if(C3_WITH_LLVM)
find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
# find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(lld_libs
${LLD_COFF}
${LLD_WASM}
@@ -262,21 +354,40 @@ if(C3_WITH_LLVM)
if (APPLE)
set(lld_libs ${lld_libs} xar)
find_file(RT_ASAN_DYNAMIC NAMES libclang_rt.asan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
find_file(RT_TSAN_DYNAMIC NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
find_file(RT_UBSAN_DYNAMIC NAMES libclang_rt.ubsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
find_file(RT_LSAN_DYNAMIC NAMES libclang_rt.lsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
set(sanitizer_runtime_libraries
${RT_ASAN_DYNAMIC}
${RT_TSAN_DYNAMIC}
# Unused
# ${RT_UBSAN_DYNAMIC}
# ${RT_LSAN_DYNAMIC}
)
if (llvm_dir)
file(GLOB_RECURSE RT_ASAN_DYNAMIC "${llvm_dir}/*libclang_rt.asan_osx_dynamic.dylib")
file(GLOB_RECURSE RT_TSAN_DYNAMIC "${llvm_dir}/*libclang_rt.tsan_osx_dynamic.dylib")
endif()
if (NOT RT_ASAN_DYNAMIC OR NOT RT_TSAN_DYNAMIC)
# Fallback to searching in LLVM_LIBRARY_DIRS (for non-fetched LLVM)
find_file(RT_ASAN_DYNAMIC_PATH NAMES libclang_rt.asan_osx_dynamic.dylib PATHS ${LLVM_LIBRARY_DIRS} PATH_SUFFIXES "clang/${LLVM_MAJOR_VERSION}/lib/darwin" "clang/${LLVM_PACKAGE_VERSION}/lib/darwin")
find_file(RT_TSAN_DYNAMIC_PATH NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS ${LLVM_LIBRARY_DIRS} PATH_SUFFIXES "clang/${LLVM_MAJOR_VERSION}/lib/darwin" "clang/${LLVM_PACKAGE_VERSION}/lib/darwin")
if (RT_ASAN_DYNAMIC_PATH)
set(RT_ASAN_DYNAMIC ${RT_ASAN_DYNAMIC_PATH})
endif()
if (RT_TSAN_DYNAMIC_PATH)
set(RT_TSAN_DYNAMIC ${RT_TSAN_DYNAMIC_PATH})
endif()
endif()
if (RT_ASAN_DYNAMIC)
list(GET RT_ASAN_DYNAMIC 0 RT_ASAN_DYNAMIC)
endif()
if (RT_TSAN_DYNAMIC)
list(GET RT_TSAN_DYNAMIC 0 RT_TSAN_DYNAMIC)
endif()
if (RT_ASAN_DYNAMIC AND RT_TSAN_DYNAMIC)
set(sanitizer_runtime_libraries
${RT_ASAN_DYNAMIC}
${RT_TSAN_DYNAMIC}
)
endif()
endif()
message(STATUS "linking to llvm libs ${lld_libs}")
message(STATUS "Found lld libs ${lld_libs}")
message(STATUS "Linking to llvm libs ${llvm_libs}")
message(STATUS "Linking to lld libs ${lld_libs}")
endif()
add_library(miniz STATIC dependencies/miniz/miniz.c)
@@ -350,9 +461,12 @@ add_executable(c3c
src/utils/whereami.c
src/utils/cpus.c
src/utils/unzipper.c
src/utils/msi.c
src/compiler/c_codegen.c
src/compiler/decltable.c
src/compiler/methodtable.c
src/compiler/mac_support.c
src/utils/fetch_msvc.c
src/compiler/windows_support.c
src/compiler/codegen_asm.c
src/compiler/asm_target.c
@@ -394,9 +508,23 @@ if(C3_WITH_LLVM)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=1)
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
if (MSVC)
# Use the same detected CRT for the wrapper
set_target_properties(c3c_wrappers PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
set_target_properties(miniz PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
target_compile_options(c3c PRIVATE
"$<$<CONFIG:Debug>:/EHa>"
"$<$<CONFIG:Release>:/EHsc>")
endif()
if(C3_LLD_INCLUDE_DIR)
target_include_directories(c3c_wrappers PRIVATE ${C3_LLD_INCLUDE_DIR})
endif()
else()
target_sources(c3c PRIVATE src/utils/hostinfo.c)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0)
target_link_libraries(c3c m)
endif()
target_include_directories(c3c PRIVATE
@@ -446,7 +574,7 @@ else()
endif()
if(C3_WITH_LLVM)
target_link_libraries(c3c ${llvm_libs} miniz c3c_wrappers ${lld_libs})
target_link_libraries(c3c miniz c3c_wrappers)
target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/wrapper/include/")
@@ -454,11 +582,11 @@ if(C3_WITH_LLVM)
target_include_directories(c3c_wrappers PRIVATE
"${CMAKE_SOURCE_DIR}/wrapper/include/")
target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
target_link_libraries(c3c_wrappers PUBLIC ${lld_libs} ${llvm_libs})
else()
target_link_libraries(c3c ${llvm_libs} miniz ${lld_libs})
target_link_libraries(c3c miniz ${lld_libs} ${llvm_libs})
endif()
@@ -475,60 +603,79 @@ if(MINGW)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,8388608")
endif ()
if (CURL_FOUND)
target_link_libraries(c3c ${CURL_LIBRARIES})
target_include_directories(c3c PRIVATE ${CURL_INCLUDES})
target_compile_definitions(c3c PUBLIC CURL_FOUND=1)
else()
target_compile_definitions(c3c PUBLIC CURL_FOUND=0)
if (NOT WIN32)
# For dlopen support
if (CMAKE_DL_LIBS)
target_link_libraries(c3c ${CMAKE_DL_LIBS})
endif()
endif()
if(MSVC)
message("Adding MSVC options")
target_compile_options(c3c PRIVATE /wd4068 /wd4090 /WX /Wv:18)
target_compile_options(c3c PRIVATE
/wd4068
/wd4090
/WX
/Wv:18
)
if(C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /wd4624 /wd4267 /wd4244 /WX /Wv:18)
target_compile_options(c3c_wrappers PUBLIC
/wd4624
/wd4267
/wd4244
/WX
/Wv:18
)
if(NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PUBLIC /GR-)
endif()
target_link_options(c3c_wrappers PUBLIC /ignore:4099)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(c3c PUBLIC /MTd)
if (C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /MTd)
endif()
target_compile_options(miniz PUBLIC /MTd)
if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MTd)
endif()
else()
target_compile_options(c3c PUBLIC /MT)
if (C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /MT)
endif()
target_compile_options(miniz PUBLIC /MT)
if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MT)
endif()
endif()
if(C3_WITH_LLVM)
set(clang_lib_dir ${llvm_dir}/lib/clang/${C3_LLVM_VERSION}/lib/windows)
set(sanitizer_runtime_libraries
${clang_lib_dir}/clang_rt.asan-x86_64.lib
${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.lib
${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.dll
${clang_lib_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
# The the sanitizer libs are in the folder "lib/clang/21/lib/windows/" but use the find anyway
file(GLOB_RECURSE FOUND_ASAN_LIB "${llvm_dir}/*clang_rt.asan_dynamic-x86_64.lib")
if(FOUND_ASAN_LIB)
list(GET FOUND_ASAN_LIB 0 _asan_path)
get_filename_component(_asan_dir "${_asan_path}" DIRECTORY)
set(sanitizer_runtime_libraries
${_asan_dir}/clang_rt.asan_dynamic-x86_64.lib
${_asan_dir}/clang_rt.asan_dynamic-x86_64.dll
${_asan_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
message(STATUS "Found Sanitizer binaries at: ${_asan_dir}")
else()
message(WARNING "Could not find sanitizer runtime libraries in ${llvm_dir}")
endif()
endif()
else()
message(STATUS "using gcc/clang warning switches")
target_link_options(c3c PRIVATE -pthread)
if (C3_WITH_LLVM AND NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PRIVATE -fno-rtti)
endif()
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)
target_compile_options(c3c PRIVATE
-pthread
-Wall
-Werror
-Wno-unknown-pragmas
-Wno-unused-result
-Wno-unused-function
-Wno-unused-variable
-Wno-unused-parameter
-Wno-char-subscripts
)
target_link_options(c3c PRIVATE -pthread)
endif()
if(CMAKE_C_COMPILER_ID STREQUAL "TinyCC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-eh-frame-hdr -z noexecstack")
# Link the static tcc runtime archive if it exists
if(EXISTS "${TCC_LIB_PATH}")
target_link_libraries(c3c "${TCC_LIB_PATH}")
else()
message(FATAL_ERROR "TCC runtime not found at ${TCC_LIB_PATH}; Ensure the path is correct.")
endif()
endif()
install(TARGETS c3c DESTINATION bin)
@@ -539,6 +686,12 @@ if (NOT WIN32)
install(FILES c3c.1 DESTINATION "share/man/man1")
endif()
# Copy stdlib
if (NOT ${CMAKE_BINARY_DIR} EQUAL ${CMAKE_SOURCE_DIR})
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
endif()
if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
add_custom_command(TARGET c3c POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E rm -rf -- $<TARGET_FILE_DIR:c3c>/c3c_rt
@@ -549,8 +702,10 @@ if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
if (APPLE)
# Change LC_ID_DYLIB to be rpath-based instead of having an absolute path
# We set DYLD_LIBRARY_PATH so the tools (which might be dynamic) can find libLLVM.dylib in the artifact
string(REPLACE ";" ":" _dyld_path "${LLVM_LIBRARY_DIRS}")
add_custom_command(TARGET c3c POST_BUILD
COMMAND find $<TARGET_FILE_DIR:c3c>/c3c_rt -type f -name "*.dylib" -execdir ${LLVM_TOOLS_BINARY_DIR}/llvm-install-name-tool -id @rpath/{} {} $<SEMICOLON>
COMMAND ${CMAKE_COMMAND} -E env "DYLD_LIBRARY_PATH=${_dyld_path}:$ENV{DYLD_LIBRARY_PATH}" find $<TARGET_FILE_DIR:c3c>/c3c_rt -type f -name "*.dylib" -execdir ${LLVM_TOOLS_BINARY_DIR}/llvm-install-name-tool -id @rpath/{} {} $<SEMICOLON>
VERBATIM)
endif()
@@ -558,3 +713,35 @@ if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
endif()
feature_summary(WHAT ALL)
message(STATUS "Building ${CMAKE_PROJECT_NAME} with the following configuration:")
set(c3_print_prefix " ")
foreach(option IN LISTS C3_OPTIONS)
if (DEFINED ${option})
c3_print_variables(${option})
endif()
endforeach()
foreach(flag_var
CMAKE_BUILD_TYPE
CMAKE_C_COMPILER
CMAKE_CXX_COMPILER
CMAKE_LINKER
CMAKE_OBJCOPY
CMAKE_STRIP
CMAKE_DLLTOOL)
c3_print_variables(${flag_var})
endforeach()
message(STATUS "Build flags:")
foreach(flag_var
CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
c3_print_variables(${flag_var})
endforeach()
message(STATUS "Output to: \"${CMAKE_BINARY_DIR}\"")

57
CMakePresets.json Normal file
View File

@@ -0,0 +1,57 @@
{
"version": 3,
"configurePresets": [
{
"name": "windows-base",
"hidden": true,
"architecture": {
"value": "x64"
},
"toolset": {
"value": "host=x64"
}
},
{
"name": "windows-vs-2022-release",
"generator": "Visual Studio 17 2022",
"displayName": "Windows x64 Visual Studio 17 2022",
"inherits": "windows-base",
"binaryDir": "build",
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Release;RelWithDebInfo",
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "windows-vs-2022-debug",
"generator": "Visual Studio 17 2022",
"displayName": "Windows x64 Visual Studio 17 2022 (Debug)",
"inherits": "windows-base",
"binaryDir": "build-debug",
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Debug",
"CMAKE_BUILD_TYPE": "Debug"
}
}
],
"buildPresets": [
{
"name": "windows-vs-2022-debug",
"displayName": "Debug",
"configurePreset": "windows-vs-2022-debug",
"configuration": "Debug"
},
{
"name": "windows-vs-2022-release",
"displayName": "Release",
"configurePreset": "windows-vs-2022-release",
"configuration": "Release"
},
{
"name": "windows-vs-2022-release-with-debug-info",
"displayName": "RelWithDebInfo",
"configurePreset": "windows-vs-2022-release",
"configuration": "RelWithDebInfo"
}
]
}

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.

View File

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

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.

179
LICENSE
View File

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

165
LICENSE_SRC Normal file
View File

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

View File

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

271
README.md
View File

@@ -8,16 +8,19 @@ 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-tag/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/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-tag/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
- OpenBSD x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz), [install instructions](#installing-on-openbsd-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,10 +36,10 @@ 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 [generics](https://c3-lang.org/generic-programming/generics/) (more examples can be found at https://c3-lang.org/language-overview/examples/).
```cpp
module stack {Type};
```c3
module stack <Type>;
// Above: the parameterized type is applied to the entire module.
struct Stack
@@ -75,7 +78,7 @@ fn bool Stack.empty(Stack* this)
Testing it out:
```cpp
```c3
import stack;
// Define our new types, the first will implicitly create
@@ -124,6 +127,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,15 +142,16 @@ 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.10**.
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)
or discuss C3 on its dedicated Discord: [https://discord.gg/qN76R87](https://discord.gg/qN76R87).
The compiler is currently verified to compile on Linux, Windows and MacOS.
The compiler is currently verified to compile on Linux, OpenBSD, Windows and MacOS.
**Support matrix**
@@ -157,6 +162,8 @@ The compiler is currently verified to compile on Linux, Windows and MacOS.
| MacOS x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| MacOS Aarch64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| iOS Aarch64 | No | Untested | Untested | Yes | Yes | Yes* |
| Android Aarch64 | No | Untested | Untested | Untested | Untested | Yes* |
| Android x64 | No | Untested | Untested | Untested | Untested | Yes* |
| Linux x86 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux x64 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux Aarch64 | Yes | Yes | Yes | Yes | Yes | Yes* |
@@ -167,17 +174,21 @@ The compiler is currently verified to compile on Linux, Windows and MacOS.
| ELF freestanding Aarch64 | No | Untested | No | No | No | Yes* |
| ELF freestanding Riscv64 | No | Untested | No | No | No | Untested |
| ELF freestanding Riscv32 | No | Untested | No | No | No | Untested |
| ELF freestanding Xtensa* | No | Untested | No | No | No | Untested |
| FreeBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| FreeBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| NetBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| NetBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x64 | Yes* | Yes | Yes* | Yes | Untested | Yes* |
| MCU x86 | No | Untested | No | No | No | Yes* |
| Wasm32 | No | Yes | No | No | No | No |
| Wasm64 | No | Untested | No | No | No | No |
*\* Inline asm is still a work in progress*
*\* Inline asm is still a work in progress*<br>
*\* OpenBSD 7.7 is the only tested version*<br>
*\* OpenBSD has limited stacktrace, needs to be tested further*<br>
*\* Xtensa support is enabled by compiling with `-DXTENSA_ENABLE`. The [espressif llvm fork](https://github.com/espressif/llvm-project) is recommended for best compatibility*
More platforms will be supported in the future.
@@ -186,41 +197,82 @@ More platforms will be supported in the future.
- If you wish to contribute with ideas, please file issues or discuss on Discord.
- Interested in contributing to the stdlib? Please get in touch on Discord.
- Compilation instructions for other Linux and Unix variants are appreciated.
- Would you like to contribute bindings to some library? It would be nice to have support for SDL, Raylib and more.
- Would you like to contribute bindings to some library? It would be nice to have support for SDL3 and more. If you have created some bindings, please submit them to https://github.com/c3lang/vendor.
- Build something with C3 and show it off and give feedback. The language is still open for significant tweaks.
- Start work on the C -> C3 converter which takes C code and does a "best effort" to translate it to C3. The first version only needs to work on C headers.
- Do you have some specific area you have deep knowledge of and could help make C3 even better at doing? File or comment on issues.
### 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-tag/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/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 Windows with the install script
Open a PowerShell terminal (you may need to run it as an administrator) and run the following command:
```bash
iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex
```
The script will inform you once the installation is successful and add the `~/.c3` directory to your PATH, which will allow you to run the c3c command from any location.
You can choose another version with option `C3_VERSION`.
For example, you can force the installation of the 0.7.4 version:
```bash
$env:C3_VERSION='0.7.4'; powershell -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex"
```
If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
#### Installing on Debian with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/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-tag/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux-debug.tar.gz))
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on Debian with the install script
Open a terminal and run the following command:
```bash
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | bash
```
The C3 compiler will be installed, and the script will also update your ~/.bashrc to include `~/.c3` in your PATH, allowing you to invoke the c3c command from anywhere. You might need to restart your terminal or source your shell for the changes to take effect.
You can choose another version with option `C3_VERSION`.
For example, you can force the installation of the 0.7.4 version:
```bash
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | C3_VERSION=0.7.4 bash
```
#### Installing on Ubuntu with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/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-tag/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/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-tag/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos-debug.zip))
3. Unzip executable and standard lib.
4. Run `./c3c`.
(*Note that there is a known issue with debug symbol generation on MacOS 13, see [issue #1086](https://github.com/c3lang/c3c/issues/1086))
#### Installing on OpenBSD with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd-debug.tar.gz))
2. Unpack executable and standard lib.
3. Run `./c3c`.
(*Note that this is specifically for OpenBSD 7.7, running it on any other version is prone to ABI breaks)
#### Installing on Arch Linux
Arch includes c3c in the official 'extra' repo. It can be easily installed the usual way:
@@ -247,6 +299,60 @@ cd c3c-git
makepkg -si
```
#### Installing via Nix
You can access `c3c` via [flake.nix](./flake.nix), which will contain the latest commit of the compiler. To add `c3c` to your `flake.nix`, do the following:
```nix
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
c3c.url = "github:c3lang/c3c";
# Those are desired if you don't want to copy extra nixpkgs
c3c.inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import inputs.nixpkgs { inherit system; };
c3c = inputs.c3c.packages.${system}.c3c;
in
{
devShells.default = pkgs.mkShell {
buildInputs = [
pkgs.c3c
];
};
}
);
}
```
### Installing on Gentoo
`c3c` is available in the [Gentoo GURU overlay](https://wiki.gentoo.org/wiki/Project:GURU).
Enable and sync the GURU repository (if not already done):
```sh
sudo eselect repository enable guru
sudo emaint sync -r guru
```
Install `c3c` with:
```sh
sudo emerge -av dev-lang/c3c
```
* The compiler binary is installed to `/usr/bin/c3c`.
* The standard library is installed to `/usr/lib/c3`.
For Gentoo-specific issues, please use the [Gentoo Bugzilla](https://bugs.gentoo.org/) (Product: *GURU*).
#### Building via Docker
You can build `c3c` using an Ubuntu container. By default, the script will build through Ubuntu 22.04. You can specify the version by passing the `UBUNTU_VERSION` environment variable.
@@ -259,14 +365,15 @@ See the `build-with-docker.sh` script for more information on other configurable
#### Installing on OS X using Homebrew
2. Install CMake: `brew install cmake`
3. Install LLVM 17+: `brew install llvm`
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
5. Enter the C3C directory `cd c3c`.
6. Create a build directory `mkdir build`
7. Change directory to the build directory `cd build`
8. Set up CMake build for debug: `cmake ..`
9. Build: `cmake --build .`
1. Install [Homebrew](https://brew.sh/)
2. Install LLVM 19+: `brew install llvm`
3. Install lld: `brew install lld`
4. Install CMake: `brew install cmake`
5. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
6. Enter the C3C directory `cd c3c`.
7. Set up CMake build for debug: `cmake -B build -S .`
8. Build: `cmake --build build`
9. Change directory to the build directory `cd build`
#### Installing on Windows using Scoop
@@ -279,7 +386,7 @@ scoop install c3
#### Getting started with a "hello world"
Create a `main.c3` file with:
```c++
```c3
module hello_world;
import std::io;
@@ -307,28 +414,35 @@ called `hello_world` or `hello_world.exe`depending on platform.
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++"
2. Install CMake
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Set up the CMake build `cmake -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release`
6. Build: `cmake --build build --config Release`
7. You should now have the c3c.exe
4. Enter the C3C directory: `cd c3c`.
5. Set up the CMake build: `cmake --preset windows-vs-2022-release`
6. Build: `cmake --build --preset windows-vs-2022-release`
You should now have a `c3c` executable.
You should now have a `c3c` executable in `build\Release`.
You can try it out by running some sample code: `c3c.exe compile ../resources/examples/hash.c3`
You can try it out by running some sample code: `c3c.exe compile ../../resources/examples/hash.c3`
Building `c3c` using Visual Studio Code is also supported when using the `CMake Tools` extension. Simply select the `Windows x64 Visual Studio 17 2022` configure preset and build.
*Note that if you run into linking issues when building, make sure that you are using the latest version of VS17.*
#### Compiling on Windows (Debug)
Debug build requires a different set of LLVM libraries to be loaded for which a separate CMake configuration is used to avoid conflicts.
1. Configure: `cmake --preset windows-vs-2022-debug`
2. Build: `cmake --build --preset windows-vs-2022-debug`
You should now have a `c3c` executable in `build-debug\Debug`.
#### Compiling on Ubuntu 24.04 LTS
1. Make sure you have a C compiler that handles C11 and a C++ compiler, such as GCC or Clang. Git also needs to be installed.
2. Install LLVM 18 `sudo apt-get install cmake git clang zlib1g zlib1g-dev libllvm18 llvm llvm-dev llvm-runtime liblld-dev liblld-18 libpolly-18-dev`
2. Install LLVM 18 `sudo apt-get install cmake git clang zlib1g zlib1g-dev libllvm18 llvm llvm-dev llvm-runtime liblld-dev liblld-18 libpolly-18-dev`. If you're using Ubuntu 25.04, also install `libpolly-20-dev`.
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build`
6. Change directory to the build directory `cd build`
7. Set up CMake build: `cmake ..`
8. Build: `cmake --build .`
5. Set up CMake build: `cmake -B build -S .`
6. Build: `cmake --build build`
7. Change directory to the build directory `cd build`
You should now have a `c3c` executable.
@@ -341,13 +455,12 @@ You can try it out by running some sample code: `./c3c compile ../resources/exam
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone instead: `git clone https://github.com/c3lang/c3c.git --depth=1`
3. Enter the directory: `cd c3c`
4. Create a build directory: `mkdir build`
5. Enter the build directory: `cd build`
6. Create the CMake build cache: `cmake ..`
7. Build: `cmake --build .`
4. Create the CMake build cache: `cmake -B build -S .`
5. Build: `cmake --build build`
6. Enter the build directory: `cd build`
Your c3c executable should have compiled properly. You may want to test it: `./c3c compile ../resources/examples/hash.c3`
For a sytem-wide installation, run the following as root: `cmake --install .`
For a system-wide installation, run the following as root: `cmake --install .`
#### Compiling on Fedora
@@ -357,33 +470,62 @@ For a sytem-wide installation, run the following as root: `cmake --install .`
3. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
4. Enter the C3C directory: `cd c3c`
5. Create a build directory and navigate into it: `mkdir build && cd build`
6. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake .. -DC3_LINK_DYNAMIC=1`
7. Build the project: `cmake --build .`
5. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake -B build -S . -DC3_LINK_DYNAMIC=1`
6. Build the project: `cmake --build build`
7. Enter the build directory: `cd build`
The c3c binary should be created in the build directory. You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
#### Compiling on Arch Linux
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm libxml2`
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
3. Enter the C3C directory: `cd c3c`
4. Create the CMake build cache:
```bash
cmake -B build \
-D C3_LINK_DYNAMIC=ON \
-D CMAKE_BUILD_TYPE=Release
```
5. Build the project: `cmake --build build`.
After compilation, the `c3c` binary will be located in the `build` directory. You can test it by compiling an example: `./build/c3c compile resources/examples/ls.c3`.
6. To install the compiler globally: `sudo cmake --install build`
#### Compiling on NixOS
1. Enter nix shell, by typing `nix develop` in root directory
2. Configure cmake via `cmake . -Bbuild $=C3_CMAKE_FLAGS`. Note: passing `C3_CMAKE_FLAGS` is needed in due to generate `compile_commands.json` and find missing libs.
4. Build it `cmake --build build`
5. Test it out: `./build/c3c -V`
6. If you use `clangd` lsp server for your editor, it is recommended to make a symbolic link to `compile_command.json` in the root: `ln -s ./build/compile_commands.json compile_commands.json`
#### Compiling on other Linux / Unix variants
1. Install CMake.
2. Install or compile LLVM and LLD *libraries* (version 17+ or higher)
2. Install or compile LLVM and LLD *libraries* (version 19+ or higher)
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build`
6. Change directory to the build directory `cd build`
7. Set up CMake build for debug: `cmake ..`. At this point you may need to manually
provide the link path to the LLVM CMake directories, e.g. `cmake -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/ ..`
8. Build: `cmake --build .`
5. Set up CMake build for debug: `cmake -B build -S .`. At this point you may need to manually
provide the link path to the LLVM CMake directories, e.g. `cmake -B build -S . -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/`
6. Build: `cmake --build build`
7. Change directory to the build directory `cd build`
*A note on compiling for Linux/Unix/MacOS: to be able to fetch vendor libraries
libcurl is needed. The CMake script should detect it if it is available. Note that
this functionality is non-essential and it is perfectly fine to user the compiler without it.*
this functionality is non-essential and it is perfectly fine to use the compiler without it.*
#### Licensing
The C3 compiler is licensed under LGPL 3.0, the standard library itself is
MIT licensed.
Unless specified otherwise, the code in this repository is MIT licensed.
The exception is the compiler source code (the source code under `src`),
which is licensed under LGPL 3.0.
This means you are free to use all parts of standard library,
tests, benchmarks, grammar, examples and so on under the MIT license, including
using those libraries and tests if you build your own C3 compiler.
#### Editor plugins
@@ -402,8 +544,13 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
A huge **THANK YOU** goes out to all contributors and sponsors.
A special thank you to sponsors [Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad) for going the extra mile.
A special thank you to sponsors [Zack Puhl](https://github.com/NotsoanoNimus) and [konimarti](https://github.com/konimarti) for going the extra mile.
And honorable mention goes to past sponsors:
[Ygor Pontelo](https://github.com/ygorpontelo), [Simone Raimondi](https://github.com/SRaimondi),
[Jan Válek](https://github.com/jan-valek), [Pierre Curto](https://github.com/pierrec),
[Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad)
## 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

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

View File

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

View File

@@ -0,0 +1,51 @@
module deflate_benchmarks;
import std::compression::deflate;
const uint SMALL_ITERATIONS = 50000;
const uint LARGE_ITERATIONS = 100;
// Data to compress
const char[] SMALL_DATA = { [0..1023] = 'A' };
const char[] LARGE_DATA = { [0..1048575] = 'B' };
char[] small_compressed;
char[] large_compressed;
fn void initialize_bench() @init
{
small_compressed = deflate::compress(mem, SMALL_DATA)!!;
large_compressed = deflate::compress(mem, LARGE_DATA)!!;
set_benchmark_warmup_iterations(2);
set_benchmark_max_iterations(10);
set_benchmark_func_iterations($qnameof(deflate_compress_small), SMALL_ITERATIONS);
set_benchmark_func_iterations($qnameof(deflate_decompress_small), SMALL_ITERATIONS);
set_benchmark_func_iterations($qnameof(deflate_compress_large), LARGE_ITERATIONS);
set_benchmark_func_iterations($qnameof(deflate_decompress_large), LARGE_ITERATIONS);
}
// =======================================================================================
module deflate_benchmarks @benchmark;
import std::compression::deflate;
import std::core::mem;
fn void deflate_compress_small() => @pool()
{
char[]? compressed = deflate::compress(tmem, SMALL_DATA);
}
fn void deflate_decompress_small() => @pool()
{
char[]? decompressed = deflate::decompress(tmem, small_compressed);
}
fn void deflate_compress_large() => @pool()
{
char[]? compressed = deflate::compress(tmem, LARGE_DATA);
}
fn void deflate_decompress_large() => @pool()
{
char[]? decompressed = deflate::decompress(tmem, large_compressed);
}

View File

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

View File

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

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module chacha20_benchmarks;
import std::crypto::chacha20;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(1024);
}
const char[] KEY = x'98bef1469be7269837a45bfbc92a5a6ac762507cf96443bf33b96b1bd4c6f8f6';
const char[] NONCE = x'44e792d63335abb1582e9253';
const uint COUNTER = 42;
char[] one_mb @align(ulong.sizeof) = { [0..1024*1024] = 0xA5 };
// This doesn't test both encryption + decryption, because it's a symmetric operation that shares
// a single common data transformation. Testing one limb is enough.
fn void gogo_chacha20() @benchmark
{
chacha20::encrypt_mut(one_mb[..], KEY, NONCE, COUNTER);
}
// Check what the speed of an unligned buffer looks like.
fn void gogo_chacha20_unaligned() @benchmark => @pool()
{
char[] copy = mem::talloc_array(char, one_mb.len + 3);
char[] im_off_slightly = copy[3..];
copy[3..] = one_mb[..];
assert((usz)im_off_slightly.ptr % usz.sizeof > 0);
runtime::@start_benchmark();
chacha20::encrypt_mut(im_off_slightly, KEY, NONCE, COUNTER);
runtime::@end_benchmark();
}

View File

@@ -0,0 +1,113 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module crypto_hash_benchmarks;
import std::collections::pair;
const usz COMMON_ITERATIONS = 1 << 17;
char* common_1mib_ptr;
char[] common_16;
char[] common_256;
char[] common_4kib;
char[] common_1mib;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(COMMON_ITERATIONS + 3);
common_1mib_ptr = mem::alloc_array(char, 1024*1024);
common_1mib = common_1mib_ptr[:1024*1024];
common_1mib[..] = 0xA5;
common_16 = common_1mib[:16];
common_256 = common_1mib[:256];
common_4kib = common_1mib[:4096];
static String[] function_prefixes = {
$qnameof(md5_16)[..^4],
$qnameof(sha1_16)[..^4],
$qnameof(sha2_256_16)[..^4],
$qnameof(sha2_512_16)[..^4],
//$qnameof(blake2s_256_16)[..^4],
//$qnameof(blake2b_256_16)[..^4],
$qnameof(blake3_16)[..^4],
$qnameof(ripemd_160_16)[..^4],
$qnameof(whirlpool_16)[..^4],
$qnameof(streebog_256_16)[..^4],
$qnameof(streebog_512_16)[..^4],
};
static Pair{ String, uint }[] to_iters = {
{ "_4kib", 1 << 15 },
{ "_1mib", 1024 },
};
foreach (p : to_iters)
{
foreach (name : function_prefixes) set_benchmark_func_iterations(name.tconcat(p.first), p.second);
}
}
fn void teardown_bench() @finalizer
{
mem::free(common_1mib_ptr);
}
// =======================================================================================
module crypto_hash_benchmarks @benchmark;
import std::hash;
fn void md5_16() => md5::hash(common_16);
fn void sha1_16() => sha1::hash(common_16);
fn void sha2_256_16() => sha256::hash(common_16);
fn void sha2_512_16() => sha512::hash(common_16);
//fn void blake2s_256_16() => blake2::s(256, common_16);
//fn void blake2b_256_16() => blake2::b(256, common_16);
fn void blake3_16() => blake3::hash(common_16);
fn void ripemd_160_16() => ripemd::hash{160}(common_16);
fn void whirlpool_16() => whirlpool::hash(common_16);
fn void streebog_256_16() => streebog::hash_256(common_16);
fn void streebog_512_16() => streebog::hash_512(common_16);
fn void md5_256() => md5::hash(common_256);
fn void sha1_256() => sha1::hash(common_256);
fn void sha2_256_256() => sha256::hash(common_256);
fn void sha2_512_256() => sha512::hash(common_256);
//fn void blake2s_256_256() => blake2::s(256, common_256);
//fn void blake2b_256_256() => blake2::b(256, common_256);
fn void blake3_256() => blake3::hash(common_256);
fn void ripemd_160_256() => ripemd::hash{160}(common_256);
fn void whirlpool_256() => whirlpool::hash(common_256);
fn void streebog_256_256() => streebog::hash_256(common_256);
fn void streebog_512_256() => streebog::hash_512(common_256);
fn void md5_4kib() => md5::hash(common_4kib);
fn void sha1_4kib() => sha1::hash(common_4kib);
fn void sha2_256_4kib() => sha256::hash(common_4kib);
fn void sha2_512_4kib() => sha512::hash(common_4kib);
//fn void blake2s_256_4kib() => blake2::s(256, common_4kib);
//fn void blake2b_256_4kib() => blake2::b(256, common_4kib);
fn void blake3_4kib() => blake3::hash(common_4kib);
fn void ripemd_160_4kib() => ripemd::hash{160}(common_4kib);
fn void whirlpool_4kib() => whirlpool::hash(common_4kib);
fn void streebog_256_4kib() => streebog::hash_256(common_4kib);
fn void streebog_512_4kib() => streebog::hash_512(common_4kib);
fn void md5_1mib() => md5::hash(common_1mib);
fn void sha1_1mib() => sha1::hash(common_1mib);
fn void sha2_256_1mib() => sha256::hash(common_1mib);
fn void sha2_512_1mib() => sha512::hash(common_1mib);
//fn void blake2s_256_1mib() => blake2::s(256, common_1mib);
//fn void blake2b_256_1mib() => blake2::b(256, common_1mib);
fn void blake3_1mib() => blake3::hash(common_1mib);
fn void ripemd_160_1mib() => ripemd::hash{160}(common_1mib);
fn void whirlpool_1mib() => whirlpool::hash(common_1mib);
fn void streebog_256_1mib() => streebog::hash_256(common_1mib);
fn void streebog_512_1mib() => streebog::hash_512(common_1mib);

View File

@@ -0,0 +1,57 @@
module blake3_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(128);
input = mem::alloc_array(char, BUFSZ);
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
input_slice = input[:BUFSZ];
}
fn void teardown_bench() @finalizer
{
mem::free(input);
input = null;
}
char* input;
char[] input_slice;
const usz BUFSZ = 1024 * 1024;
module blake3_bench @benchmark;
import std::hash;
fn void blake3_hash()
{
runtime::@start_benchmark();
char[*] myset = blake3::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha256()
{
runtime::@start_benchmark();
char[*] myset = sha256::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha512()
{
runtime::@start_benchmark();
char[*] myset = sha512::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_whirlpool()
{
runtime::@start_benchmark();
char[*] myset = whirlpool::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}

View File

@@ -0,0 +1,41 @@
module md5_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(128);
input = mem::alloc_array(char, BUFSZ);
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
input_slice = input[:BUFSZ];
}
fn void teardown_bench() @finalizer
{
mem::free(input);
input = null;
}
char* input;
char[] input_slice;
const usz BUFSZ = 1024 * 1024;
module md5_bench @benchmark;
import std::hash;
fn void md5_hash()
{
runtime::@start_benchmark();
char[*] myset = md5::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha256()
{
runtime::@start_benchmark();
char[*] myset = sha256::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}

View File

@@ -0,0 +1,94 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module non_crypto_benchmarks;
const usz COMMON_ITERATIONS = 1 << 18;
const char[] COMMON_1 = { 0xA5 };
const char[] COMMON_4 = { 0xA5, 0xA5, 0xA5, 0xA5, };
const char[] COMMON_8 = { [0..7] = 0xA5 };
const char[] COMMON_16 = { [0..15] = 0xA5 };
const char[] COMMON_32 = { [0..31] = 0xA5 };
const char[] COMMON_64 = { [0..63] = 0xA5 };
const char[] COMMON_128 = { [0..127] = 0xA5 };
const char[] COMMON_1024 = { [0..1023] = 0xA5 };
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(COMMON_ITERATIONS + 3);
}
// =======================================================================================
module non_crypto_benchmarks @benchmark;
import std::hash;
fn void fnv64a_1() => fnv64a::hash(COMMON_1);
fn void fnv32a_1() => fnv32a::hash(COMMON_1);
fn void wyhash2_1() => wyhash2::hash(COMMON_1);
fn void metro64_1() => metro64::hash(COMMON_1);
fn void metro128_1() => metro128::hash(COMMON_1);
fn void a5hash_1() => a5hash::hash(COMMON_1);
fn void komi_1() => komi::hash(COMMON_1);
fn void fnv64a_4() => fnv64a::hash(COMMON_4);
fn void fnv32a_4() => fnv32a::hash(COMMON_4);
fn void wyhash2_4() => wyhash2::hash(COMMON_4);
fn void metro64_4() => metro64::hash(COMMON_4);
fn void metro128_4() => metro128::hash(COMMON_4);
fn void a5hash_4() => a5hash::hash(COMMON_4);
fn void komi_4() => komi::hash(COMMON_4);
fn void fnv64a_8() => fnv64a::hash(COMMON_8);
fn void fnv32a_8() => fnv32a::hash(COMMON_8);
fn void wyhash2_8() => wyhash2::hash(COMMON_8);
fn void metro64_8() => metro64::hash(COMMON_8);
fn void metro128_8() => metro128::hash(COMMON_8);
fn void a5hash_8() => a5hash::hash(COMMON_8);
fn void komi_8() => komi::hash(COMMON_8);
fn void fnv64a_16() => fnv64a::hash(COMMON_16);
fn void fnv32a_16() => fnv32a::hash(COMMON_16);
fn void wyhash2_16() => wyhash2::hash(COMMON_16);
fn void metro64_16() => metro64::hash(COMMON_16);
fn void metro128_16() => metro128::hash(COMMON_16);
fn void a5hash_16() => a5hash::hash(COMMON_16);
fn void komi_16() => komi::hash(COMMON_16);
fn void fnv64a_32() => fnv64a::hash(COMMON_32);
fn void fnv32a_32() => fnv32a::hash(COMMON_32);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_32() => metro64::hash(COMMON_32);
fn void metro128_32() => metro128::hash(COMMON_32);
fn void a5hash_32() => a5hash::hash(COMMON_32);
fn void komi_32() => komi::hash(COMMON_32);
fn void fnv64a_64() => fnv64a::hash(COMMON_64);
fn void fnv32a_64() => fnv32a::hash(COMMON_64);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_64() => metro64::hash(COMMON_64);
fn void metro128_64() => metro128::hash(COMMON_64);
fn void a5hash_64() => a5hash::hash(COMMON_64);
fn void komi_64() => komi::hash(COMMON_64);
fn void fnv64a_128() => fnv64a::hash(COMMON_128);
fn void fnv32a_128() => fnv32a::hash(COMMON_128);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_128() => metro64::hash(COMMON_128);
fn void metro128_128() => metro128::hash(COMMON_128);
fn void a5hash_128() => a5hash::hash(COMMON_128);
fn void komi_128() => komi::hash(COMMON_128);
fn void fnv64a_1024() => fnv64a::hash(COMMON_1024);
fn void fnv32a_1024() => fnv32a::hash(COMMON_1024);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_1024() => metro64::hash(COMMON_1024);
fn void metro128_1024() => metro128::hash(COMMON_1024);
fn void a5hash_1024() => a5hash::hash(COMMON_1024);
fn void komi_1024() => komi::hash(COMMON_1024);

View File

@@ -0,0 +1,76 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module ripemd_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(256);
input = mem::alloc_array(char, BUFSZ);
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
input_slice = input[:BUFSZ];
}
fn void teardown_bench() @finalizer
{
mem::free(input);
input = null;
}
char* input;
char[] input_slice;
const usz BUFSZ = 1024 * 1024;
module ripemd_bench @benchmark;
import std::hash;
fn void ripemd_128()
{
runtime::@start_benchmark();
char[*] myset = ripemd::hash{128}(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void ripemd_160()
{
runtime::@start_benchmark();
char[*] myset = ripemd::hash{160}(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void ripemd_256()
{
runtime::@start_benchmark();
char[*] myset = ripemd::hash{256}(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void ripemd_320()
{
runtime::@start_benchmark();
char[*] myset = ripemd::hash{320}(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha256()
{
runtime::@start_benchmark();
char[*] myset = sha256::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_whirlpool()
{
runtime::@start_benchmark();
char[*] myset = whirlpool::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}

View File

@@ -0,0 +1,51 @@
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module streebog_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(256);
}
const char[] INPUT = { [0..1024*1024] = 0xA5 };
const char[*] EXPECTED_256 = x'694676905b7cf099755db1cc186f741f0fd1877aaaa4badcbfb305537f986971';
const char[*] EXPECTED_512 = x'fe21d08857ea97e79035d1e5c9ba5130786e8d1875bc74d628349560d94d6bdff0b0dcd2f6347eb8b3f0239b6cca76b5028c0ff45f631fcdf77b1d551dd079f3';
module streebog_bench @benchmark;
import std::hash;
fn void get_in_the_bog_256()
{
runtime::@start_benchmark();
char[*] myset = streebog::hash_256(INPUT);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void get_in_the_bog_512()
{
runtime::@start_benchmark();
char[*] myset = streebog::hash_512(INPUT);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha256()
{
runtime::@start_benchmark();
char[*] myset = sha256::hash(INPUT);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_whirlpool()
{
runtime::@start_benchmark();
char[*] myset = whirlpool::hash(INPUT);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}

View File

@@ -0,0 +1,57 @@
module whirlpool_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(128);
input = mem::alloc_array(char, BUFSZ);
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
input_slice = input[:BUFSZ];
}
fn void teardown_bench() @finalizer
{
mem::free(input);
input = null;
}
char* input;
char[] input_slice;
const usz BUFSZ = 1024 * 1024;
module whirlpool_bench @benchmark;
import std::hash;
fn void whirlpool_hash()
{
runtime::@start_benchmark();
char[*] myset = whirlpool::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha256()
{
runtime::@start_benchmark();
char[*] myset = sha256::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha512()
{
runtime::@start_benchmark();
char[*] myset = sha512::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_streebog_512()
{
runtime::@start_benchmark();
char[*] myset = streebog::hash_512(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
: ${DOCKER:=docker}
: ${IMAGE:="c3c-builder"}
@@ -30,7 +30,8 @@ chmod -R 777 build bin
exec $DOCKER run -i --rm \
-v "$PWD":/home/c3c/source \
-w /home/c3c/source $IMAGE bash -c \
"cmake -S . -B build \
"git config --global --add safe.directory /home/c3c/source && \
cmake -S . -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
-DCMAKE_C_COMPILER=clang-$LLVM_VERSION \
@@ -41,4 +42,4 @@ exec $DOCKER run -i --rm \
-DCMAKE_DLLTOOL=llvm-dlltool-$LLVM_VERSION \
-DC3_LLVM_VERSION=auto && \
cmake --build build && \
cp -r build/c3c build/lib bin"
cp -r build/c3c build/lib bin"

View File

@@ -2,48 +2,44 @@ ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION}
ARG LLVM_VERSION=18
ENV LLVM_DEV_VERSION=20
ARG CMAKE_VERSION=3.20.0
ARG CMAKE_VERSION=3.20
# Prevent interactive prompts during apt install
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ && \
wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-$CMAKE_VERSION-linux-x86_64.sh && \
RUN for i in 1 2 3; do apt-get update && break || sleep 2; done && \
apt-get install -y --fix-missing \
wget gnupg software-properties-common lsb-release \
zlib1g zlib1g-dev python3 ninja-build curl g++ libcurl4-openssl-dev git && \
CODENAME=$(lsb_release -cs) && \
ARCH=$(uname -m) && \
wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${ARCH}.sh && \
mkdir -p /opt/cmake && \
sh cmake-${CMAKE_VERSION}-linux-x86_64.sh --prefix=/opt/cmake --skip-license && \
rm cmake-${CMAKE_VERSION}-linux-x86_64.sh && \
ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake
sh cmake-${CMAKE_VERSION}-linux-${ARCH}.sh --prefix=/opt/cmake --skip-license && \
ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake && \
rm cmake-${CMAKE_VERSION}-linux-${ARCH}.sh
RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \
if [ "${LLVM_VERSION}" -lt 18 ]; then \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev libmlir-${LLVM_VERSION} \
libmlir-${LLVM_VERSION}-dev mlir-${LLVM_VERSION}-tools; \
elif [ "${LLVM_VERSION}" -lt "${LLVM_DEV_VERSION}" ]; then \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} clang++-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
RUN CODENAME=$(lsb_release -cs) && \
for i in 1 2; do wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key && break || sleep 2; done | apt-key add - && \
if [ "${LLVM_VERSION}" -ge 16 ]; then \
add-apt-repository "deb http://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-${LLVM_VERSION} main"; \
else \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
add-apt-repository "deb http://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME} main"; \
fi && \
for i in 1 2 3; do apt-get update && break || sleep 2; done && \
apt-get install -y --fix-missing \
clang-${LLVM_VERSION} \
clang++-${LLVM_VERSION} \
llvm-${LLVM_VERSION} \
llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} \
liblld-${LLVM_VERSION}-dev \
libpolly-${LLVM_VERSION}-dev && \
rm -rf /var/lib/apt/lists/*
RUN groupadd -g 1337 c3c && \
useradd -m -u 1337 -g c3c c3c
# Add cmake to PATH for user c3c
USER c3c
ENV PATH="/opt/cmake/bin:${PATH}"
WORKDIR /home/c3c

View File

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

189
install/install.ps1 Normal file
View File

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

137
install/install.sh Normal file
View File

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

View File

@@ -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,9 +1,9 @@
// 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};
module std::atomic::types;
struct Atomic
struct Atomic <Type>
{
Type data;
}
@@ -11,147 +11,87 @@ struct Atomic
<*
Loads data atomically, by default this uses SEQ_CONSISTENT ordering.
@param ordering : "The ordering, cannot be release or acquire-release."
@require ordering != RELEASE && ordering != ACQUIRE_RELEASE : "Release and acquire-release are not valid for load"
@param $ordering : "The ordering, cannot be release or acquire-release."
@require $ordering != RELEASE && $ordering != ACQUIRE_RELEASE : "Release and acquire-release are not valid for load"
*>
macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.load(&self, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
switch(ordering)
{
case NOT_ATOMIC: return $$atomic_load(data, false, AtomicOrdering.NOT_ATOMIC.ordinal);
case UNORDERED: return $$atomic_load(data, false, AtomicOrdering.UNORDERED.ordinal);
case RELAXED: return $$atomic_load(data, false, AtomicOrdering.RELAXED.ordinal);
case ACQUIRE: return $$atomic_load(data, false, AtomicOrdering.ACQUIRE.ordinal);
case SEQ_CONSISTENT: return $$atomic_load(data, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
case ACQUIRE_RELEASE:
case RELEASE: unreachable("Invalid ordering.");
}
return $$atomic_load(&self.data, false, $ordering.ordinal);
}
<*
Stores data atomically, by default this uses SEQ_CONSISTENT ordering.
@param ordering : "The ordering, cannot be acquire or acquire-release."
@require ordering != ACQUIRE && ordering != ACQUIRE_RELEASE : "Acquire and acquire-release are not valid for store"
@param $ordering : "The ordering, cannot be acquire or acquire-release."
@require $ordering != ACQUIRE && $ordering != ACQUIRE_RELEASE : "Acquire and acquire-release are not valid for store"
*>
macro void Atomic.store(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro void Atomic.store(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
switch(ordering)
{
case NOT_ATOMIC: $$atomic_store(data, value, false, AtomicOrdering.NOT_ATOMIC.ordinal);
case UNORDERED: $$atomic_store(data, value, false, AtomicOrdering.UNORDERED.ordinal);
case RELAXED: $$atomic_store(data, value, false, AtomicOrdering.RELAXED.ordinal);
case RELEASE: $$atomic_store(data, value, false, AtomicOrdering.RELEASE.ordinal);
case SEQ_CONSISTENT: $$atomic_store(data, value, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
case ACQUIRE_RELEASE:
case ACQUIRE: unreachable("Invalid ordering.");
}
$$atomic_store(&self.data, value, false, $ordering.ordinal);
}
macro Type Atomic.add(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.add(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_add, data, value, ordering);
return atomic::fetch_add(&self.data, value, $ordering);
}
macro Type Atomic.sub(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.sub(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_sub, data, value, ordering);
return atomic::fetch_sub(&self.data, value, $ordering);
}
macro Type Atomic.mul(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.mul(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_mul, data, value, ordering);
return atomic::fetch_mul(&self.data, value, $ordering);
}
macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.div(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_div, data, value, ordering);
return atomic::fetch_div(&self.data, value, $ordering);
}
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.max(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_max, data, value, ordering);
return atomic::fetch_max(&self.data, value, $ordering);
}
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.min(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_min, data, value, ordering);
return atomic::fetch_min(&self.data, value, $ordering);
}
macro Type Atomic.or(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
macro Type Atomic.or(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_or, data, value, ordering);
return atomic::fetch_or(&self.data, value, $ordering);
}
fn Type Atomic.xor(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
macro Type Atomic.xor(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
return atomic::fetch_xor(&self.data, value, $ordering);
}
macro Type Atomic.and(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
macro Type Atomic.and(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_and, data, value, ordering);
return atomic::fetch_and(&self.data, value, $ordering);
}
macro Type Atomic.shr(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
macro Type Atomic.shr(&self, Type amount, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
return atomic::fetch_shift_right(&self.data, amount, $ordering);
}
macro Type Atomic.shl(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
macro Type Atomic.shl(&self, Type amount, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
return atomic::fetch_shift_left(&self.data, amount, $ordering);
}
macro Type Atomic.set(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
macro Type Atomic.set(&self, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
{
Type* data = &self.data;
return @atomic_exec_no_arg(atomic::flag_set, data, ordering);
return atomic::flag_set(&self.data, $ordering);
}
macro Type Atomic.clear(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
macro Type Atomic.clear(&self, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
{
Type* data = &self.data;
return @atomic_exec_no_arg(atomic::flag_clear, data, ordering);
}
macro @atomic_exec(#func, data, value, ordering) @local
{
switch(ordering)
{
case RELAXED: return #func(data, value, RELAXED);
case ACQUIRE: return #func(data, value, ACQUIRE);
case RELEASE: return #func(data, value, RELEASE);
case ACQUIRE_RELEASE: return #func(data, value, ACQUIRE_RELEASE);
case SEQ_CONSISTENT: return #func(data, value, SEQ_CONSISTENT);
default: unreachable("Ordering may not be non-atomic or unordered.");
}
}
macro @atomic_exec_no_arg(#func, data, ordering) @local
{
switch(ordering)
{
case RELAXED: return #func(data, RELAXED);
case ACQUIRE: return #func(data, ACQUIRE);
case RELEASE: return #func(data, RELEASE);
case ACQUIRE_RELEASE: return #func(data, ACQUIRE_RELEASE);
case SEQ_CONSISTENT: return #func(data, SEQ_CONSISTENT);
default: unreachable("Ordering may not be non-atomic or unordered.");
}
return atomic::flag_clear(&self.data, $ordering);
}
module std::atomic;
@@ -171,11 +111,13 @@ macro bool is_native_atomic_type($Type)
$case SIGNED_INT:
$case UNSIGNED_INT:
$case POINTER:
$case FUNC:
$case FLOAT:
$case BOOL:
return true;
$case DISTINCT:
return is_native_atomic_type($typefrom($Type.inner));
$case TYPEDEF:
$case CONSTDEF:
return is_native_atomic_type($Type.inner);
$default:
return false;
$endswitch
@@ -183,7 +125,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"
@@ -192,7 +134,7 @@ macro bool is_native_atomic_type($Type)
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr + y) : "+ must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -203,7 +145,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"
@@ -212,7 +154,7 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr - y) : "- must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -223,7 +165,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"
@@ -231,16 +173,16 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr * y) : "* must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
$StorageType* storage_ptr = ($StorageType*)ptr;
@@ -263,24 +205,24 @@ 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"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr * y) : "/ must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $defined(*ptr / y) : "/ must be defined between the values."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
$StorageType* storage_ptr = ($StorageType*)ptr;
@@ -303,7 +245,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"
@@ -312,7 +254,7 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr | y) : "| must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -320,7 +262,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"
@@ -329,7 +271,7 @@ macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "^ must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -337,7 +279,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"
@@ -346,7 +288,7 @@ macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "& must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -354,7 +296,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"
@@ -363,16 +305,16 @@ macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift right must be an integer"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
$StorageType* storage_ptr = ($StorageType*)ptr;
@@ -396,7 +338,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"
@@ -405,16 +347,16 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift left must be an integer"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
$StorageType* storage_ptr = ($StorageType*)ptr;
@@ -438,7 +380,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"
@@ -446,7 +388,7 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -454,7 +396,7 @@ macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
$typeof(*ptr) new_value = true;
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
do
{
@@ -466,14 +408,14 @@ 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"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -481,7 +423,7 @@ macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
$typeof(*ptr) new_value = false;
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$load_ordering = SEQ_CONSISTENT;
$endif
do
{
@@ -501,7 +443,7 @@ macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr > y) : "Only values that are comparable with > may be used"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -520,7 +462,7 @@ macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr > y) : "Only values that are comparable with > may be used"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{

View File

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

View File

@@ -2,301 +2,101 @@
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::anylist;
import std::io,std::math;
alias AnyPredicate = fn bool(any value);
alias AnyTest = fn bool(any type, any context);
struct AnyList (Printable)
{
usz size;
usz capacity;
Allocator allocator;
any* entries;
}
import std::collections::interfacelist;
alias AnyPredicate = InterfacePredicate {any};
alias AnyTest = InterfaceTest {any};
<*
@param [&inout] allocator : "The allocator to use"
@param initial_capacity : "The initial capacity to reserve"
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.
*>
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
typedef AnyList = inline InterfaceList {any};
<*
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)
{
self.allocator = allocator;
self.size = 0;
if (initial_capacity > 0)
{
initial_capacity = math::next_power_of_2(initial_capacity);
self.entries = allocator::alloc_array(allocator, any, initial_capacity);
}
else
{
self.entries = null;
}
self.capacity = initial_capacity;
return self;
return *anycast(self.first_any(), $Type);
}
<*
Initialize the list using the temp allocator.
Return the first element
@param initial_capacity : "The initial capacity to reserve"
@return "The first element"
@return? NO_MORE_ELEMENT
*>
fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16)
{
return self.init(tmem, initial_capacity) @inline;
}
fn any? AnyList.first_any(&self) @inline => InterfaceList {any}.first(self);
fn bool AnyList.is_initialized(&self) @inline => self.allocator != null;
<*
Return the last element by value, assuming it is the given type.
fn usz? AnyList.to_format(&self, Formatter* formatter) @dynamic
@param $Type : "The type of the last element"
@return "The last element"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*>
macro AnyList.last(&self, $Type)
{
switch (self.size)
{
case 0:
return formatter.print("[]")!;
case 1:
return formatter.printf("[%s]", self.entries[0])!;
default:
usz n = formatter.print("[")!;
foreach (i, element : self.entries[:self.size])
{
if (i != 0) formatter.print(", ")!;
n += formatter.printf("%s", element)!;
}
n += formatter.print("]")!;
return n;
}
return *anycast(self.last_any(), $Type);
}
<*
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));
}
Return the last element
fn void AnyList.append_internal(&self, any element) @local
{
self.ensure_capacity();
self.entries[self.size++] = element;
}
@return "The last element"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.last_any(&self) @inline => InterfaceList {any}.last(self);
<*
Free a retained element removed using *_retained.
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
*>
fn void AnyList.free_element(&self, any element) @inline
macro AnyList.pop(&self, $Type)
{
allocator::free(self.allocator, element.ptr);
if (!self.size) return NO_MORE_ELEMENT~;
defer self.free_element(self.entries[self.size]);
return *anycast(self.entries[--self.size], $Type);
}
<*
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(&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?;
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];
}
Return an element in the list by value, assuming it is the given type.
<*
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;
}
<*
@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)
@@ -305,148 +105,18 @@ macro AnyList.get(&self, usz 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
{
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;
}
fn any AnyList.get_any(&self, usz index) @inline @operator([]) => InterfaceList {any}.get(self, index);
<*
@param filter : "The function to determine if it should be removed or not"
@return "the number of deleted elements"
Return the length of the list.
@return "The number of elements in the list"
*>
fn usz AnyList.remove_if(&self, AnyPredicate filter)
{
return self._remove_if(filter, false);
}
<*
@param selection : "The function to determine if it should be kept or not"
@return "the number of deleted elements"
*>
fn usz AnyList.retain_if(&self, AnyPredicate selection)
{
return self._remove_if(selection, true);
}
macro usz AnyList._remove_if(&self, AnyPredicate filter, bool $invert) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && filter(&self.entries[i - 1])) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$endif
}
return size - self.size;
}
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
{
return self._remove_using_test(filter, false, context);
}
fn usz AnyList.retain_using_test(&self, AnyTest filter, any context)
{
return self._remove_using_test(filter, true, context);
}
macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$endif
}
return size - self.size;
}
<*
Reserve at least min_capacity
*>
fn void AnyList.reserve(&self, usz min_capacity)
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = 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)
{
self.push(value);
return;
}
self.free_element(self.entries[index]);
self.entries[index] = allocator::copy(self.allocator, value);
}
fn void AnyList.ensure_capacity(&self, usz added = 1) @inline @private
{
usz new_size = self.size + added;
if (self.capacity >= new_size) return;
assert(new_size < usz.max / 2U);
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
while (new_capacity < new_size) new_capacity *= 2U;
self.reserve(new_capacity);
}
fn usz AnyList.len(&self) @operator(len) @inline => InterfaceList {any}.len(self);

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,12 @@ 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"
@pure
*>
fn bool BitSet.get(&self, usz i) @operator([]) @inline
{
@@ -53,13 +145,23 @@ fn bool BitSet.get(&self, usz i) @operator([]) @inline
return self.data[q] & (1 << r) != 0;
}
<*
Return the number of bits.
@pure
*>
fn usz BitSet.len(&self) @operator(len) @inline
{
return SZ * BITS;
}
<*
@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
{
@@ -70,7 +172,7 @@ fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
<*
@require Type.kindof == UNSIGNED_INT
*>
module std::collections::growablebitset{Type};
module std::collections::growablebitset <Type>;
import std::collections::list;
const BITS = Type.sizeof * 8;

View File

@@ -4,14 +4,14 @@
<*
@require MAX_SIZE >= 1 : `The size must be at least 1 element big.`
*>
module std::collections::elastic_array {Type, MAX_SIZE};
module std::collections::elastic_array <Type, MAX_SIZE>;
import std::io, std::math, std::collections::list_common;
alias ElementPredicate = fn bool(Type *type);
alias ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
struct ElasticArray (Printable)
{
@@ -46,7 +46,7 @@ fn String ElasticArray.to_tstring(&self)
fn void? ElasticArray.push_try(&self, Type element) @inline
{
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY~;
self.entries[self.size++] = element;
}
@@ -60,7 +60,7 @@ fn void ElasticArray.push(&self, Type element) @inline
fn Type? ElasticArray.pop(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT~;
return self.entries[--self.size];
}
@@ -74,7 +74,7 @@ fn void ElasticArray.clear(&self)
*>
fn Type? ElasticArray.pop_first(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT~;
defer self.remove_at(0);
return self.entries[0];
}
@@ -121,7 +121,24 @@ fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
@param [in] array
*>
fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
fn usz ElasticArray.add_array_to_limit(&self, Type[] array) @deprecated("Use push_all_to_limit")
{
if (!array.len) return 0;
foreach (i, &value : array)
{
if (self.size == MAX_SIZE) return array.len - i;
self.entries[self.size++] = *value;
}
return 0;
}
<*
Add as many values from this array as possible, returning the
number of elements that didn't fit.
@param [in] array
*>
fn usz ElasticArray.push_all_to_limit(&self, Type[] array)
{
if (!array.len) return 0;
foreach (i, &value : array)
@@ -139,7 +156,7 @@ fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
@ensure self.size >= array.len
*>
fn void ElasticArray.add_array(&self, Type[] array)
fn void ElasticArray.add_array(&self, Type[] array) @deprecated("Use push_all")
{
if (!array.len) return;
foreach (&value : array)
@@ -148,6 +165,21 @@ fn void ElasticArray.add_array(&self, Type[] array)
}
}
<*
Add the values of an array to this list.
@param [in] array
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
@ensure self.size >= array.len
*>
fn void ElasticArray.push_all(&self, Type[] array)
{
if (!array.len) return;
foreach (&value : array)
{
self.entries[self.size++] = *value;
}
}
<*
@@ -209,7 +241,7 @@ fn void? ElasticArray.push_front_try(&self, Type type) @inline
*>
fn void? ElasticArray.insert_at_try(&self, usz index, Type value)
{
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY~;
self.insert_at(index, value);
}
@@ -237,25 +269,25 @@ fn void ElasticArray.set_at(&self, usz index, Type type)
fn void? ElasticArray.remove_last(&self) @maydiscard
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT~;
self.size--;
}
fn void? ElasticArray.remove_first(&self) @maydiscard
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT~;
self.remove_at(0);
}
fn Type? ElasticArray.first(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT~;
return self.entries[0];
}
fn Type? ElasticArray.last(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT~;
return self.entries[self.size - 1];
}
@@ -336,7 +368,7 @@ fn usz? ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
if (equals(v, type)) return i;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
@@ -345,7 +377,7 @@ fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
if (equals(v, type)) return i;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE)
@@ -426,4 +458,4 @@ fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER)
fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER)
{
return list_common::list_compact(self);
}
}

View File

@@ -1,11 +1,13 @@
<*
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap"
@require Enum.values.len > 0 : "Only non-empty enums may be used with enummap"
*>
module std::collections::enummap{Enum, ValueType};
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

@@ -5,12 +5,13 @@
<*
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enumset"
*>
module std::collections::enumset{Enum};
module std::collections::enumset <Enum>;
import std::io;
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

@@ -4,7 +4,7 @@
<*
@require $defined((Key){}.hash()) : `No .hash function found on the key`
*>
module std::collections::map{Key, Value};
module std::collections::map <Key, Value>;
import std::math;
import std::io @norecurse;
@@ -30,8 +30,10 @@ struct HashMap (Printable)
{
Entry*[] table;
Allocator allocator;
uint count; // Number of elements
uint threshold; // Resize limit
<* Last inserted LinkedEntry *>
uint count;
<* Resize limit *>
uint threshold;
float load_factor;
}
@@ -90,7 +92,7 @@ macro HashMap* HashMap.init_with_key_values(&self, Allocator allocator, ..., uin
*>
macro HashMap* HashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.tinit_with_key_values(tmem, capacity, load_factor);
return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
}
<*
@@ -148,7 +150,7 @@ fn bool HashMap.is_initialized(&map)
fn HashMap* HashMap.init_from_map(&self, Allocator allocator, HashMap* other_map)
{
self.init(allocator, other_map.table.len, other_map.load_factor);
self.put_all_for_create(other_map);
hashmap_put_all_for_create(self, other_map);
return self;
}
@@ -173,29 +175,48 @@ fn usz HashMap.len(&map) @inline
fn Value*? HashMap.get_ref(&map, Key key)
{
if (!map.count) return NOT_FOUND?;
if (!map.count) return NOT_FOUND~;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
fn Value* HashMap.get_or_create_ref(&map, Key key) @operator(&[])
{
uint hash = rehash(key.hash());
if (map.count)
{
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
}
map.set(key, {});
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
unreachable();
}
fn Entry*? HashMap.get_entry(&map, Key key)
{
if (!map.count) return NOT_FOUND?;
if (!map.count) return NOT_FOUND~;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
<*
Get the value or update and
@require $assignable(#expr, Value)
Get the value or set it to the value
@require $defined(Value val = #expr)
*>
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
{
@@ -212,7 +233,7 @@ macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
if (e.hash == hash && equals(key, e.key)) return e.value;
}
Value val = #expr;
map.add_entry(hash, key, val, index);
hashmap_add_entry(map, hash, key, val, index);
return val;
}
@@ -229,15 +250,15 @@ fn bool HashMap.has_key(&map, Key key)
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
switch (map.allocator.ptr)
{
case &dummy:
map.init(mem);
case null:
map.tinit();
default:
break;
}
switch (map.allocator.ptr)
{
case &dummy:
map.init(mem);
case null:
map.tinit();
default:
break;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
@@ -248,13 +269,13 @@ fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
return true;
}
}
map.add_entry(hash, key, value, index);
hashmap_add_entry(map, hash, key, value, index);
return false;
}
fn void? HashMap.remove(&map, Key key) @maydiscard
{
if (!map.remove_entry_for_key(key)) return NOT_FOUND?;
if (!hashmap_remove_entry_for_key(map, key)) return NOT_FOUND~;
}
fn void HashMap.clear(&map)
@@ -269,9 +290,9 @@ fn void HashMap.clear(&map)
{
Entry *to_delete = next;
next = next.next;
map.free_entry(to_delete);
hashmap_free_entry(map, to_delete);
}
map.free_entry(entry);
hashmap_free_entry(map, entry);
*entry_ref = null;
}
map.count = 0;
@@ -281,7 +302,7 @@ fn void HashMap.free(&map)
{
if (!map.is_initialized()) return;
map.clear();
map.free_internal(map.table.ptr);
hashmap_free_internal(map, map.table.ptr);
map.table = {};
}
@@ -332,10 +353,7 @@ macro HashMap.@each_entry(map; @body(entry))
}
}
fn Value[] HashMap.tvalues(&map)
{
return map.values(tmem) @inline;
}
fn Value[] HashMap.tvalues(&self) => self.values(tmem) @inline;
fn Value[] HashMap.values(&self, Allocator allocator)
{
@@ -384,7 +402,7 @@ fn HashMapKeyIterator HashMap.key_iter(&self)
// --- private methods
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
fn void hashmap_add_entry(HashMap* map, uint hash, Key key, Value value, uint bucket_index) @private
{
$if COPY_KEYS:
key = key.copy(map.allocator);
@@ -393,11 +411,11 @@ fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_ind
map.table[bucket_index] = entry;
if (map.count++ >= map.threshold)
{
map.resize(map.table.len * 2);
hashmap_resize(map, map.table.len * 2);
}
}
fn void HashMap.resize(&map, uint new_capacity) @private
fn void hashmap_resize(HashMap* map, uint new_capacity) @private
{
Entry*[] old_table = map.table;
uint old_capacity = old_table.len;
@@ -407,9 +425,9 @@ fn void HashMap.resize(&map, uint new_capacity) @private
return;
}
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
map.transfer(new_table);
hashmap_transfer(map, new_table);
map.table = new_table;
map.free_internal(old_table.ptr);
hashmap_free_internal(map, old_table.ptr);
map.threshold = (uint)(new_capacity * map.load_factor);
}
@@ -421,11 +439,11 @@ fn usz? HashMap.to_format(&self, Formatter* f) @dynamic
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s: %s", entry.key, entry.value)!;
};
};
return len + f.print(" }");
}
fn void HashMap.transfer(&map, Entry*[] new_table) @private
fn void hashmap_transfer(HashMap* map, Entry*[] new_table) @private
{
Entry*[] src = map.table;
uint new_capacity = new_table.len;
@@ -444,20 +462,20 @@ fn void HashMap.transfer(&map, Entry*[] new_table) @private
}
}
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
fn void hashmap_put_all_for_create(HashMap* map, HashMap* other_map) @private
{
if (!other_map.count) return;
foreach (Entry *e : other_map.table)
{
while (e)
{
map.put_for_create(e.key, e.value);
hashmap_put_for_create(map, e.key, e.value);
e = e.next;
}
}
}
fn void HashMap.put_for_create(&map, Key key, Value value) @private
fn void hashmap_put_for_create(HashMap* map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
@@ -469,15 +487,15 @@ fn void HashMap.put_for_create(&map, Key key, Value value) @private
return;
}
}
map.create_entry(hash, key, value, i);
hashmap_create_entry(map, hash, key, value, i);
}
fn void HashMap.free_internal(&map, void* ptr) @inline @private
fn void hashmap_free_internal(HashMap* map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
fn bool hashmap_remove_entry_for_key(HashMap* map, Key key) @private
{
if (!map.count) return false;
uint hash = rehash(key.hash());
@@ -498,7 +516,7 @@ fn bool HashMap.remove_entry_for_key(&map, Key key) @private
{
prev.next = next;
}
map.free_entry(e);
hashmap_free_entry(map, e);
return true;
}
prev = e;
@@ -507,7 +525,7 @@ fn bool HashMap.remove_entry_for_key(&map, Key key) @private
return false;
}
fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
fn void hashmap_create_entry(HashMap* map, uint hash, Key key, Value value, int bucket_index) @private
{
Entry *e = map.table[bucket_index];
$if COPY_KEYS:
@@ -518,12 +536,12 @@ fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_i
map.count++;
}
fn void HashMap.free_entry(&self, Entry *entry) @local
fn void hashmap_free_entry(HashMap* map, Entry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
allocator::free(map.allocator, entry.key);
$endif
self.free_internal(entry);
hashmap_free_internal(map, entry);
}
@@ -589,4 +607,4 @@ macro uint index_for(uint hash, uint capacity) @private
return hash & (capacity - 1);
}
int dummy @local;
int dummy @local;

View File

@@ -0,0 +1,654 @@
<*
@require $defined((Value){}.hash()) : `No .hash function found on the value`
*>
module std::collections::set <Value>;
import std::math;
import std::io @norecurse;
const uint DEFAULT_INITIAL_CAPACITY = 16;
const uint MAXIMUM_CAPACITY = 1u << 31;
const float DEFAULT_LOAD_FACTOR = 0.75;
const Allocator SET_HEAP_ALLOCATOR = (Allocator)&dummy;
<* Copy the ONHEAP allocator to initialize to a set that is heap allocated *>
const HashSet ONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
struct Entry
{
uint hash;
Value value;
Entry* next;
}
struct HashSet (Printable)
{
Entry*[] table;
Allocator allocator;
<* Number of elements *>
usz count;
<* Resize limit *>
usz threshold;
float load_factor;
}
fn usz HashSet.len(&self) @operator(len) => self.count;
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
self.threshold = (usz) (capacity * load_factor);
self.load_factor = load_factor;
self.table = allocator::new_array(allocator, Entry*, capacity);
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(tmem, capacity, load_factor) @inline;
}
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro HashSet* HashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
$for var $i = 0; $i < $vacount; $i++:
self.add($vaarg[$i]);
$endfor
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro HashSet* HashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
}
<*
@param [in] values : "The values for the HashSet"
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
foreach (v : values) self.add(v);
return self;
}
<*
@param [in] values : "The values for the HashSet entries"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_from_values(tmem, values, capacity, load_factor);
}
<*
Has this hash set been initialized yet?
@param [&in] set : "The hash set we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool HashSet.is_initialized(&set)
{
return set.allocator && set.allocator.ptr != &dummy;
}
<*
@param [&inout] allocator : "The allocator to use"
@param [&in] other_set : "The set to copy from."
@require !self.is_initialized() : "Set was already initialized"
*>
fn HashSet* HashSet.init_from_set(&self, Allocator allocator, HashSet* other_set)
{
self.init(allocator, other_set.table.len, other_set.load_factor);
hashset_put_all_for_create(self, other_set);
return self;
}
<*
@param [&in] other_set : "The set to copy from."
@require !set.is_initialized() : "Set was already initialized"
*>
fn HashSet* HashSet.tinit_from_set(&set, HashSet* other_set)
{
return set.init_from_set(tmem, other_set) @inline;
}
<*
Check if the set is empty
@return "true if it is empty"
@pure
*>
fn bool HashSet.is_empty(&set) @inline
{
return !set.count;
}
<*
Add all elements in the slice to the set.
@param [in] list
@return "The number of new elements added"
@ensure total <= list.len
*>
fn usz HashSet.add_all(&set, Value[] list)
{
usz total;
foreach (v : list)
{
if (set.add(v)) total++;
}
return total;
}
<*
@param [&in] other
@return "The number of new elements added"
@ensure return <= other.count
*>
fn usz HashSet.add_all_from(&set, HashSet* other)
{
usz total;
other.@each(;Value value)
{
if (set.add(value)) total++;
};
return total;
}
<*
@param value : "The value to add"
@return "true if the value didn't exist in the set"
*>
fn bool HashSet.add(&set, Value value)
{
// If the set isn't initialized, use the defaults to initialize it.
switch (set.allocator.ptr)
{
case &dummy:
set.init(mem);
case null:
set.tinit();
default:
break;
}
uint hash = rehash(value.hash());
uint index = index_for(hash, set.table.len);
for (Entry *e = set.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return false;
}
hashset_add_entry(set, hash, value, index);
return true;
}
<*
Iterate over all the values in the set
*>
macro HashSet.@each(set; @body(value))
{
if (!set.count) return;
foreach (Entry* entry : set.table)
{
while (entry)
{
@body(entry.value);
entry = entry.next;
}
}
}
<*
Check if the set contains the given value.
@param value : "The value to check"
@return "true if it exists in the set"
*>
fn bool HashSet.contains(&set, Value value)
{
if (!set.count) return false;
uint hash = rehash(value.hash());
for (Entry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return true;
}
return false;
}
<*
Remove a single value from the set.
@param value : "The value to remove"
@return? NOT_FOUND : "If the entry is not found"
*>
fn void? HashSet.remove(&set, Value value) @maydiscard
{
if (!hashset_remove_entry_for_value(set, value)) return NOT_FOUND~;
}
fn usz HashSet.remove_all(&set, Value[] values)
{
usz total;
foreach (v : values)
{
if (hashset_remove_entry_for_value(set, v)) total++;
}
return total;
}
<*
@param [&in] other : "Other set"
*>
fn usz HashSet.remove_all_from(&set, HashSet* other)
{
usz total;
other.@each(;Value val)
{
if (hashset_remove_entry_for_value(set, val)) total++;
};
return total;
}
<*
Free all memory allocated by the hash set.
*>
fn void HashSet.free(&set)
{
if (!set.is_initialized()) return;
set.clear();
hashset_free_internal(set, set.table.ptr);
*set = {};
}
<*
Clear all elements from the set while keeping the underlying storage
@ensure set.count == 0
*>
fn void HashSet.clear(&set)
{
if (!set.count) return;
foreach (Entry** &entry_ref : set.table)
{
Entry* entry = *entry_ref;
if (!entry) continue;
Entry *next = entry.next;
while (next)
{
Entry *to_delete = next;
next = next.next;
hashset_free_entry(set, to_delete);
}
hashset_free_entry(set, entry);
*entry_ref = null;
}
set.count = 0;
}
fn void HashSet.reserve(&set, usz capacity)
{
if (capacity > set.threshold)
{
hashset_resize(set, math::next_power_of_2(capacity));
}
}
fn Value[] HashSet.tvalues(&self) => self.values(tmem) @inline;
fn Value[] HashSet.values(&self, Allocator allocator)
{
if (!self.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, self.count);
usz index = 0;
foreach (Entry* entry : self.table)
{
while (entry)
{
list[index++] = entry.value;
entry = entry.next;
}
}
return list;
}
// --- Set Operations ---
<*
Returns the union of two sets (A | B)
@param [&in] other : "The other set to union with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the union of both sets"
*>
fn HashSet HashSet.set_union(&self, Allocator allocator, HashSet* other)
{
usz new_capacity = math::next_power_of_2(self.count + other.count);
HashSet result;
result.init(allocator, new_capacity, self.load_factor);
result.add_all_from(self);
result.add_all_from(other);
return result;
}
fn HashSet HashSet.tset_union(&self, HashSet* other) => self.set_union(tmem, other);
<*
Returns the intersection of the two sets (A & B)
@param [&in] other : "The other set to intersect with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the intersection of both sets"
*>
fn HashSet HashSet.intersection(&self, Allocator allocator, HashSet* other)
{
HashSet result;
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
// Iterate through the smaller set for efficiency
HashSet* smaller = self.count <= other.count ? self : other;
HashSet* larger = self.count > other.count ? self : other;
smaller.@each(;Value value)
{
if (larger.contains(value)) result.add(value);
};
return result;
}
fn HashSet HashSet.tintersection(&self, HashSet* other) => self.intersection(tmem, other);
<*
Return this set - other, so (A & ~B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set but not in the other"
*>
fn HashSet HashSet.difference(&self, Allocator allocator, HashSet* other)
{
HashSet result;
result.init(allocator, self.table.len, self.load_factor);
self.@each(;Value value)
{
if (!other.contains(value))
{
result.add(value);
}
};
return result;
}
fn HashSet HashSet.tdifference(&self, HashSet* other) => self.difference(tmem, other) @inline;
<*
Return (A ^ B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set or the other, but not both"
*>
fn HashSet HashSet.symmetric_difference(&self, Allocator allocator, HashSet* other)
{
HashSet result;
result.init(allocator, self.table.len, self.load_factor);
result.add_all_from(self);
other.@each(;Value value)
{
if (!result.add(value))
{
result.remove(value);
}
};
return result;
}
fn HashSet HashSet.tsymmetric_difference(&self, HashSet* other) => self.symmetric_difference(tmem, other) @inline;
<*
Check if this hash set is a subset of another set.
@param [&in] other : "The other set to check against"
@return "True if all elements of this set are in the other set"
*>
fn bool HashSet.is_subset(&self, HashSet* other)
{
if (self.count == 0) return true;
if (self.count > other.count) return false;
self.@each(;Value value)
{
if (!other.contains(value)) return false;
};
return true;
}
// --- private methods
fn void hashset_add_entry(HashSet* set, uint hash, Value value, uint bucket_index) @private
{
Entry* entry = allocator::new(set.allocator, Entry, { .hash = hash, .value = value, .next = set.table[bucket_index] });
set.table[bucket_index] = entry;
if (set.count++ >= set.threshold)
{
hashset_resize(set, set.table.len * 2);
}
}
fn void hashset_resize(HashSet* self, usz new_capacity) @private
{
Entry*[] old_table = self.table;
usz old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
self.threshold = uint.max;
return;
}
Entry*[] new_table = allocator::new_array(self.allocator, Entry*, new_capacity);
hashset_transfer(self, new_table);
self.table = new_table;
hashset_free_internal(self, old_table.ptr);
self.threshold = (uint)(new_capacity * self.load_factor);
}
fn usz? HashSet.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each(; Value value)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s", value)!;
};
return len + f.print(" }");
}
fn void hashset_transfer(HashSet* self, Entry*[] new_table) @private
{
Entry*[] src = self.table;
uint new_capacity = new_table.len;
foreach (uint j, Entry *e : src)
{
if (!e) continue;
do
{
Entry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void hashset_put_all_for_create(HashSet* set, HashSet* other_set) @private
{
if (!other_set.count) return;
foreach (Entry *e : other_set.table)
{
while (e)
{
hashset_put_for_create(set, e.value);
e = e.next;
}
}
}
fn void hashset_put_for_create(HashSet* set, Value value) @private
{
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
for (Entry *e = set.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value))
{
// Value already exists, no need to do anything
return;
}
}
hashset_create_entry(set, hash, value, i);
}
fn void hashset_free_internal(HashSet* self, void* ptr) @inline @private
{
allocator::free(self.allocator, ptr);
}
fn void hashset_create_entry(HashSet* set, uint hash, Value value, int bucket_index) @private
{
Entry* entry = allocator::new(set.allocator, Entry, {
.hash = hash,
.value = value,
.next = set.table[bucket_index]
});
set.table[bucket_index] = entry;
set.count++;
}
<*
Removes the entry for the specified value if present
@return "true if found and removed, false otherwise"
*>
fn bool hashset_remove_entry_for_value(HashSet* set, Value value) @private
{
if (!set.count) return false;
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
Entry* prev = set.table[i];
Entry* e = prev;
while (e)
{
Entry *next = e.next;
if (e.hash == hash && equals(value, e.value))
{
set.count--;
if (prev == e)
{
set.table[i] = next;
}
else
{
prev.next = next;
}
hashset_free_entry(set, e);
return true;
}
prev = e;
e = next;
}
return false;
}
fn void hashset_free_entry(HashSet* set, Entry *entry) @private
{
allocator::free(set.allocator, entry);
}
struct HashSetIterator
{
HashSet* set;
usz bucket_index;
Entry* current;
}
fn HashSetIterator HashSet.iter(&set) => { .set = set, .bucket_index = 0, .current = null };
fn Value? HashSetIterator.next(&self)
{
if (self.current)
{
Value value = self.current.value;
self.current = self.current.next;
return value;
}
while (self.bucket_index < self.set.table.len)
{
self.current = self.set.table[self.bucket_index++];
if (self.current)
{
Value value = self.current.value;
self.current = self.current.next;
return value;
}
}
return NOT_FOUND~;
}
fn usz HashSetIterator.len(&self) @operator(len)
{
return self.set.count;
}
<* @pure *>
fn uint rehash(uint hash) @inline @private
{
hash ^= (hash >> 20) ^ (hash >> 12);
return hash ^ ((hash >> 7) ^ (hash >> 4));
}
macro uint index_for(uint hash, uint capacity) @private => hash & (capacity - 1);
int dummy @local;

View File

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

View File

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

View File

@@ -0,0 +1,651 @@
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@require $defined((Key){}.hash()) : `No .hash function found on the key`
*>
module std::collections::map <Key, Value>;
import std::math;
import std::io @norecurse;
const LinkedHashMap LINKEDONHEAP = { .allocator = MAP_HEAP_ALLOCATOR };
struct LinkedEntry
{
uint hash;
Key key;
Value value;
<* For bucket chain *>
LinkedEntry* next;
<* Previous in insertion order *>
LinkedEntry* before;
<* Next in insertion order *>
LinkedEntry* after;
}
struct LinkedHashMap (Printable)
{
LinkedEntry*[] table;
Allocator allocator;
usz count;
usz threshold;
float load_factor;
<* First inserted LinkedEntry *>
LinkedEntry* head;
<* Last inserted LinkedEntry *>
LinkedEntry* tail;
}
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
self.load_factor = load_factor;
self.threshold = (usz)(capacity * load_factor);
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
self.head = null;
self.tail = null;
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(tmem, capacity, load_factor) @inline;
}
<*
@param [&inout] allocator : "The allocator to use"
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashMap* LinkedHashMap.init_with_key_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
$for var $i = 0; $i < $vacount; $i += 2:
self.set($vaarg[$i], $vaarg[$i + 1]);
$endfor
return self;
}
<*
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashMap* LinkedHashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
}
<*
@param [in] keys : "The keys for the LinkedHashMap entries"
@param [in] values : "The values for the LinkedHashMap entries"
@param [&inout] allocator : "The allocator to use"
@require keys.len == values.len : "Both keys and values arrays must be the same length"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
assert(keys.len == values.len);
self.init(allocator, capacity, load_factor);
for (usz i = 0; i < keys.len; i++)
{
self.set(keys[i], values[i]);
}
return self;
}
<*
@param [in] keys : "The keys for the LinkedHashMap entries"
@param [in] values : "The values for the LinkedHashMap entries"
@require keys.len == values.len : "Both keys and values arrays must be the same length"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_from_keys_and_values(tmem, keys, values, capacity, load_factor);
}
<*
Has this hash map been initialized yet?
@param [&in] map : "The hash map we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool LinkedHashMap.is_initialized(&map)
{
return map.allocator && map.allocator.ptr != &dummy;
}
<*
@param [&inout] allocator : "The allocator to use"
@param [&in] other_map : "The map to copy from."
@require !self.is_initialized() : "Map was already initialized"
*>
fn LinkedHashMap* LinkedHashMap.init_from_map(&self, Allocator allocator, LinkedHashMap* other_map)
{
self.init(allocator, other_map.table.len, other_map.load_factor);
linkedhashmap_put_all_for_create(self, other_map);
return self;
}
<*
@param [&in] other_map : "The map to copy from."
@require !map.is_initialized() : "Map was already initialized"
*>
fn LinkedHashMap* LinkedHashMap.tinit_from_map(&map, LinkedHashMap* other_map)
{
return map.init_from_map(tmem, other_map) @inline;
}
fn bool LinkedHashMap.is_empty(&map) @inline
{
return !map.count;
}
fn usz LinkedHashMap.len(&map) @inline => map.count;
fn Value*? LinkedHashMap.get_ref(&map, Key key)
{
if (!map.count) return NOT_FOUND~;
uint hash = rehash(key.hash());
for (LinkedEntry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
return NOT_FOUND~;
}
fn LinkedEntry*? LinkedHashMap.get_entry(&map, Key key)
{
if (!map.count) return NOT_FOUND~;
uint hash = rehash(key.hash());
for (LinkedEntry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return NOT_FOUND~;
}
<*
Get the value or set it to the value
@require $defined(Value val = #expr)
*>
macro Value LinkedHashMap.@get_or_set(&map, Key key, Value #expr)
{
if (!map.count)
{
Value val = #expr;
map.set(key, val);
return val;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (LinkedEntry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e.value;
}
Value val = #expr;
map.add_entry(hash, key, val, index);
return val;
}
fn Value? LinkedHashMap.get(&map, Key key) @operator([]) => *map.get_ref(key) @inline;
fn bool LinkedHashMap.has_key(&map, Key key) => @ok(map.get_ref(key));
fn bool LinkedHashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
switch (map.allocator.ptr)
{
case &dummy:
map.init(mem);
case null:
map.tinit();
default:
break;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (LinkedEntry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return true;
}
}
linkedhashmap_add_entry(map, hash, key, value, index);
return false;
}
fn void? LinkedHashMap.remove(&map, Key key) @maydiscard
{
if (!linkedhashmap_remove_entry_for_key(map, key)) return NOT_FOUND~;
}
fn void LinkedHashMap.clear(&map)
{
if (!map.count) return;
LinkedEntry* entry = map.head;
while (entry)
{
LinkedEntry* next = entry.after;
linkedhashmap_free_entry(map, entry);
entry = next;
}
foreach (LinkedEntry** &bucket : map.table)
{
*bucket = null;
}
map.count = 0;
map.head = null;
map.tail = null;
}
fn void LinkedHashMap.free(&map)
{
if (!map.is_initialized()) return;
map.clear();
linkedhashmap_free_internal(map, map.table.ptr);
map.table = {};
}
fn Key[] LinkedHashMap.tkeys(&self)
{
return self.keys(tmem) @inline;
}
fn Key[] LinkedHashMap.keys(&self, Allocator allocator)
{
if (!self.count) return {};
Key[] list = allocator::alloc_array(allocator, Key, self.count);
usz index = 0;
LinkedEntry* entry = self.head;
while (entry)
{
$if COPY_KEYS:
list[index++] = entry.key.copy(allocator);
$else
list[index++] = entry.key;
$endif
entry = entry.after;
}
return list;
}
macro LinkedHashMap.@each(map; @body(key, value))
{
map.@each_entry(; LinkedEntry* entry)
{
@body(entry.key, entry.value);
};
}
macro LinkedHashMap.@each_entry(map; @body(entry))
{
LinkedEntry* entry = map.head;
while (entry)
{
@body(entry);
entry = entry.after;
}
}
fn Value[] LinkedHashMap.tvalues(&map) => map.values(tmem) @inline;
fn Value[] LinkedHashMap.values(&self, Allocator allocator)
{
if (!self.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, self.count);
usz index = 0;
LinkedEntry* entry = self.head;
while (entry)
{
list[index++] = entry.value;
entry = entry.after;
}
return list;
}
fn bool LinkedHashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
{
if (!map.count) return false;
LinkedEntry* entry = map.head;
while (entry)
{
if (equals(v, entry.value)) return true;
entry = entry.after;
}
return false;
}
fn LinkedHashMapIterator LinkedHashMap.iter(&self) => { .map = self, .current = self.head, .started = false };
fn LinkedHashMapValueIterator LinkedHashMap.value_iter(&self) => { .map = self, .current = self.head, .started = false };
fn LinkedHashMapKeyIterator LinkedHashMap.key_iter(&self) => { .map = self, .current = self.head, .started = false };
fn bool LinkedHashMapIterator.next(&self)
{
if (!self.started)
{
self.current = self.map.head;
self.started = true;
}
else if (self.current)
{
self.current = self.current.after;
}
return self.current != null;
}
fn LinkedEntry*? LinkedHashMapIterator.get(&self)
{
return self.current ? self.current : NOT_FOUND~;
}
fn Value*? LinkedHashMapValueIterator.get(&self)
{
return self.current ? &self.current.value : NOT_FOUND~;
}
fn Key*? LinkedHashMapKeyIterator.get(&self)
{
return self.current ? &self.current.key : NOT_FOUND~;
}
fn bool LinkedHashMapIterator.has_next(&self)
{
if (!self.started) return self.map.head != null;
return self.current && self.current.after != null;
}
// --- private methods
fn void linkedhashmap_add_entry(LinkedHashMap* map, uint hash, Key key, Value value, uint bucket_index) @private
{
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, {
.hash = hash,
.key = key,
.value = value,
.next = map.table[bucket_index],
.before = map.tail,
.after = null
});
// Update bucket chain
map.table[bucket_index] = entry;
// Update linked list
if (map.tail)
{
map.tail.after = entry;
entry.before = map.tail;
}
else
{
map.head = entry;
}
map.tail = entry;
if (map.count++ >= map.threshold)
{
linkedhashmap_resize(map, map.table.len * 2);
}
}
fn void linkedhashmap_resize(LinkedHashMap* map, uint new_capacity) @private
{
LinkedEntry*[] old_table = map.table;
uint old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
map.threshold = uint.max;
return;
}
LinkedEntry*[] new_table = allocator::new_array(map.allocator, LinkedEntry*, new_capacity);
map.table = new_table;
map.threshold = (uint)(new_capacity * map.load_factor);
// Rehash all entries - linked list order remains unchanged
foreach (uint i, LinkedEntry *e : old_table)
{
if (!e) continue;
// Split the bucket chain into two chains based on new bit
LinkedEntry* lo_head = null;
LinkedEntry* lo_tail = null;
LinkedEntry* hi_head = null;
LinkedEntry* hi_tail = null;
do
{
LinkedEntry* next = e.next;
if ((e.hash & old_capacity) == 0)
{
if (!lo_tail)
{
lo_head = e;
}
else
{
lo_tail.next = e;
}
lo_tail = e;
}
else
{
if (!hi_tail)
{
hi_head = e;
}
else
{
hi_tail.next = e;
}
hi_tail = e;
}
e.next = null;
e = next;
}
while (e);
if (lo_tail)
{
lo_tail.next = null;
new_table[i] = lo_head;
}
if (hi_tail)
{
hi_tail.next = null;
new_table[i + old_capacity] = hi_head;
}
}
linkedhashmap_free_internal(map, old_table.ptr);
}
fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each_entry(; LinkedEntry* entry)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s: %s", entry.key, entry.value)!;
};
return len + f.print(" }");
}
fn void linkedhashmap_transfer(LinkedHashMap* map, LinkedEntry*[] new_table) @private
{
LinkedEntry*[] src = map.table;
uint new_capacity = new_table.len;
foreach (uint j, LinkedEntry *e : src)
{
if (!e) continue;
do
{
LinkedEntry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void linkedhashmap_put_all_for_create(LinkedHashMap* map, LinkedHashMap* other_map) @private
{
if (!other_map.count) return;
other_map.@each(; Key key, Value value) {
map.set(key, value);
};
}
fn void linkedhashmap_put_for_create(LinkedHashMap* map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
for (LinkedEntry *e = map.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return;
}
}
linkedhashmap_create_entry(map, hash, key, value, i);
}
fn void linkedhashmap_free_internal(LinkedHashMap* map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
fn bool linkedhashmap_remove_entry_for_key(LinkedHashMap* map, Key key) @private
{
if (!map.count) return false;
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
LinkedEntry* prev = null;
LinkedEntry* e = map.table[i];
while (e)
{
if (e.hash == hash && equals(key, e.key))
{
if (prev)
{
prev.next = e.next;
}
else
{
map.table[i] = e.next;
}
if (e.before)
{
e.before.after = e.after;
}
else
{
map.head = e.after;
}
if (e.after)
{
e.after.before = e.before;
}
else
{
map.tail = e.before;
}
map.count--;
linkedhashmap_free_entry(map, e);
return true;
}
prev = e;
e = e.next;
}
return false;
}
fn void linkedhashmap_create_entry(LinkedHashMap* map, uint hash, Key key, Value value, int bucket_index) @private
{
LinkedEntry *e = map.table[bucket_index];
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry;
map.count++;
}
fn void linkedhashmap_free_entry(LinkedHashMap* self, LinkedEntry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
$endif
linkedhashmap_free_internal(self, entry);
}
struct LinkedHashMapIterator
{
LinkedHashMap* map;
LinkedEntry* current;
bool started;
}
typedef LinkedHashMapValueIterator = inline LinkedHashMapIterator;
typedef LinkedHashMapKeyIterator = inline LinkedHashMapIterator;
fn usz LinkedHashMapValueIterator.len(self) @operator(len) => self.map.count;
fn usz LinkedHashMapKeyIterator.len(self) @operator(len) => self.map.count;
fn usz LinkedHashMapIterator.len(self) @operator(len) => self.map.count;
int dummy @local;

View File

@@ -0,0 +1,730 @@
<*
@require $defined((Value){}.hash()) : `No .hash function found on the value`
*>
module std::collections::set <Value>;
import std::math;
import std::io @norecurse;
const LinkedHashSet LINKEDONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
struct LinkedEntry
{
uint hash;
Value value;
<* For bucket chain *>
LinkedEntry* next;
<* Previous in insertion order *>
LinkedEntry* before;
<* Next in insertion order *>
LinkedEntry* after;
}
struct LinkedHashSet (Printable)
{
LinkedEntry*[] table;
Allocator allocator;
<* Number of elements *>
usz count;
<* Resize limit *>
usz threshold;
float load_factor;
<* Resize limit *>
LinkedEntry* head;
<* First inserted LinkedEntry *>
LinkedEntry* tail;
}
fn usz LinkedHashSet.len(&self) @operator(len) => self.count;
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
self.threshold = (usz)(capacity * load_factor);
self.load_factor = load_factor;
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
self.head = null;
self.tail = null;
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(tmem, capacity, load_factor) @inline;
}
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashSet* LinkedHashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
$for var $i = 0; $i < $vacount; $i++:
self.add($vaarg[$i]);
$endfor
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashSet* LinkedHashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
}
<*
@param [in] values : "The values for the LinkedHashSet"
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
foreach (v : values) self.add(v);
return self;
}
<*
@param [in] values : "The values for the LinkedHashSet entries"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_from_values(tmem, values, capacity, load_factor);
}
<*
Has this linked hash set been initialized yet?
@param [&in] set : "The linked hash set we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool LinkedHashSet.is_initialized(&set)
{
return set.allocator && set.allocator.ptr != &dummy;
}
<*
@param [&inout] allocator : "The allocator to use"
@param [&in] other_set : "The set to copy from."
@require !self.is_initialized() : "Set was already initialized"
*>
fn LinkedHashSet* LinkedHashSet.init_from_set(&self, Allocator allocator, LinkedHashSet* other_set)
{
self.init(allocator, other_set.table.len, other_set.load_factor);
LinkedEntry* entry = other_set.head;
while (entry) // Save insertion order
{
linkedhashset_put_for_create(self, entry.value);
entry = entry.after;
}
return self;
}
<*
@param [&in] other_set : "The set to copy from."
@require !set.is_initialized() : "Set was already initialized"
*>
fn LinkedHashSet* LinkedHashSet.tinit_from_set(&set, LinkedHashSet* other_set)
{
return set.init_from_set(tmem, other_set) @inline;
}
<*
Check if the set is empty
@return "true if it is empty"
@pure
*>
fn bool LinkedHashSet.is_empty(&set) @inline
{
return !set.count;
}
<*
Add all elements in the slice to the set.
@param [in] list
@return "The number of new elements added"
@ensure total <= list.len
*>
fn usz LinkedHashSet.add_all(&set, Value[] list)
{
usz total;
foreach (v : list)
{
if (set.add(v)) total++;
}
return total;
}
<*
@param [&in] other
@return "The number of new elements added"
@ensure return <= other.count
*>
fn usz LinkedHashSet.add_all_from(&set, LinkedHashSet* other)
{
usz total;
other.@each(;Value value)
{
if (set.add(value)) total++;
};
return total;
}
<*
@param value : "The value to add"
@return "true if the value didn't exist in the set"
*>
fn bool LinkedHashSet.add(&set, Value value)
{
// If the set isn't initialized, use the defaults to initialize it.
switch (set.allocator.ptr)
{
case &dummy:
set.init(mem);
case null:
set.tinit();
default:
break;
}
uint hash = rehash(value.hash());
uint index = index_for(hash, set.table.len);
for (LinkedEntry *e = set.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return false;
}
linkedhashset_add_entry(set, hash, value, index);
return true;
}
<*
Iterate over all the values in the set
*>
macro LinkedHashSet.@each(set; @body(value))
{
if (!set.count) return;
LinkedEntry* entry = set.head;
while (entry)
{
@body(entry.value);
entry = entry.after;
}
}
<*
Check if the set contains the given value.
@param value : "The value to check"
@return "true if it exists in the set"
*>
fn bool LinkedHashSet.contains(&set, Value value)
{
if (!set.count) return false;
uint hash = rehash(value.hash());
for (LinkedEntry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return true;
}
return false;
}
<*
Remove a single value from the set.
@param value : "The value to remove"
@return? NOT_FOUND : "If the entry is not found"
*>
fn void? LinkedHashSet.remove(&set, Value value) @maydiscard
{
if (!linkedhashset_remove_entry_for_value(set, value)) return NOT_FOUND~;
}
fn usz LinkedHashSet.remove_all(&set, Value[] values)
{
usz total;
foreach (v : values)
{
if (linkedhashset_remove_entry_for_value(set, v)) total++;
}
return total;
}
<*
@param [&in] other : "Other set"
*>
fn usz LinkedHashSet.remove_all_from(&set, LinkedHashSet* other)
{
usz total;
other.@each(;Value val)
{
if (linkedhashset_remove_entry_for_value(set, val)) total++;
};
return total;
}
<*
Free all memory allocated by the hash set.
*>
fn void LinkedHashSet.free(&set)
{
if (!set.is_initialized()) return;
set.clear();
linkedhashset_free_internal(set, set.table.ptr);
set.table = {};
}
<*
Clear all elements from the set while keeping the underlying storage
@ensure set.count == 0
*>
fn void LinkedHashSet.clear(&set)
{
if (!set.count) return;
LinkedEntry* entry = set.head;
while (entry)
{
LinkedEntry* next = entry.after;
linkedhashset_free_entry(set, entry);
entry = next;
}
foreach (LinkedEntry** &bucket : set.table)
{
*bucket = null;
}
set.count = 0;
set.head = null;
set.tail = null;
}
fn void LinkedHashSet.reserve(&set, usz capacity)
{
if (capacity > set.threshold)
{
linkedhashset_resize(set, math::next_power_of_2(capacity));
}
}
// --- Set Operations ---
<*
Returns the union of two sets (A | B)
@param [&in] other : "The other set to union with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the union of both sets"
*>
fn LinkedHashSet LinkedHashSet.set_union(&self, Allocator allocator, LinkedHashSet* other)
{
usz new_capacity = math::next_power_of_2(self.count + other.count);
LinkedHashSet result;
result.init(allocator, new_capacity, self.load_factor);
result.add_all_from(self);
result.add_all_from(other);
return result;
}
fn LinkedHashSet LinkedHashSet.tset_union(&self, LinkedHashSet* other) => self.set_union(tmem, other) @inline;
<*
Returns the intersection of the two sets (A & B)
@param [&in] other : "The other set to intersect with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the intersection of both sets"
*>
fn LinkedHashSet LinkedHashSet.intersection(&self, Allocator allocator, LinkedHashSet* other)
{
LinkedHashSet result;
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
// Iterate through the smaller set for efficiency
LinkedHashSet* smaller = self.count <= other.count ? self : other;
LinkedHashSet* larger = self.count > other.count ? self : other;
smaller.@each(;Value value)
{
if (larger.contains(value)) result.add(value);
};
return result;
}
fn LinkedHashSet LinkedHashSet.tintersection(&self, LinkedHashSet* other) => self.intersection(tmem, other) @inline;
<*
Return this set - other, so (A & ~B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set but not in the other"
*>
fn LinkedHashSet LinkedHashSet.difference(&self, Allocator allocator, LinkedHashSet* other)
{
LinkedHashSet result;
result.init(allocator, self.table.len, self.load_factor);
self.@each(;Value value)
{
if (!other.contains(value))
{
result.add(value);
}
};
return result;
}
fn LinkedHashSet LinkedHashSet.tdifference(&self, LinkedHashSet* other) => self.difference(tmem, other) @inline;
<*
Return (A ^ B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set or the other, but not both"
*>
fn LinkedHashSet LinkedHashSet.symmetric_difference(&self, Allocator allocator, LinkedHashSet* other)
{
LinkedHashSet result;
result.init(allocator, self.table.len, self.load_factor);
result.add_all_from(self);
other.@each(;Value value)
{
if (!result.add(value))
{
result.remove(value);
}
};
return result;
}
fn LinkedHashSet LinkedHashSet.tsymmetric_difference(&self, LinkedHashSet* other) => self.symmetric_difference(tmem, other) @inline;
<*
Check if this hash set is a subset of another set.
@param [&in] other : "The other set to check against"
@return "True if all elements of this set are in the other set"
*>
fn bool LinkedHashSet.is_subset(&self, LinkedHashSet* other)
{
if (self.count == 0) return true;
if (self.count > other.count) return false;
self.@each(; Value value) {
if (!other.contains(value)) return false;
};
return true;
}
// --- private methods
fn void linkedhashset_add_entry(LinkedHashSet* set, uint hash, Value value, uint bucket_index) @private
{
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
.hash = hash,
.value = value,
.next = set.table[bucket_index],
.before = set.tail,
.after = null
});
// Update bucket chain
set.table[bucket_index] = entry;
// Update linked list
if (set.tail)
{
set.tail.after = entry;
entry.before = set.tail;
}
else
{
set.head = entry;
}
set.tail = entry;
if (set.count++ >= set.threshold)
{
linkedhashset_resize(set, set.table.len * 2);
}
}
fn void linkedhashset_resize(LinkedHashSet* set, usz new_capacity) @private
{
LinkedEntry*[] old_table = set.table;
usz old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
set.threshold = uint.max;
return;
}
LinkedEntry*[] new_table = allocator::new_array(set.allocator, LinkedEntry*, new_capacity);
set.table = new_table;
set.threshold = (uint)(new_capacity * set.load_factor);
// Rehash all entries - linked list order remains unchanged
foreach (uint i, LinkedEntry *e : old_table)
{
if (!e) continue;
// Split the bucket chain into two chains based on new bit
LinkedEntry* lo_head = null;
LinkedEntry* lo_tail = null;
LinkedEntry* hi_head = null;
LinkedEntry* hi_tail = null;
do
{
LinkedEntry* next = e.next;
if ((e.hash & old_capacity) == 0)
{
if (!lo_tail)
{
lo_head = e;
}
else
{
lo_tail.next = e;
}
lo_tail = e;
}
else
{
if (!hi_tail)
{
hi_head = e;
}
else
{
hi_tail.next = e;
}
hi_tail = e;
}
e.next = null;
e = next;
}
while (e);
if (lo_tail)
{
lo_tail.next = null;
new_table[i] = lo_head;
}
if (hi_tail)
{
hi_tail.next = null;
new_table[i + old_capacity] = hi_head;
}
}
linkedhashset_free_internal(set, old_table.ptr);
}
fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each(; Value value)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s", value)!;
};
return len + f.print(" }");
}
fn void linked_hashset_transfer(LinkedHashSet* set, LinkedEntry*[] new_table) @private
{
LinkedEntry*[] src = set.table;
uint new_capacity = new_table.len;
foreach (uint j, LinkedEntry *e : src)
{
if (!e) continue;
do
{
LinkedEntry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void linkedhashset_put_for_create(LinkedHashSet* set, Value value) @private
{
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
for (LinkedEntry *e = set.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value))
{
// Value already exists, no need to do anything
return;
}
}
linkedhashset_create_entry(set, hash, value, i);
}
fn void linkedhashset_free_internal(LinkedHashSet* set, void* ptr) @inline @private
{
allocator::free(set.allocator, ptr);
}
fn void linkedhashset_create_entry(LinkedHashSet* set, uint hash, Value value, int bucket_index) @private
{
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
.hash = hash,
.value = value,
.next = set.table[bucket_index],
.before = set.tail,
.after = null
});
set.table[bucket_index] = entry;
// Update linked list
if (set.tail)
{
set.tail.after = entry;
entry.before = set.tail;
}
else
{
set.head = entry;
}
set.tail = entry;
set.count++;
}
fn bool linkedhashset_remove_entry_for_value(LinkedHashSet* set, Value value) @private
{
if (!set.count) return false;
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
LinkedEntry* prev = null;
LinkedEntry* e = set.table[i];
while (e)
{
if (e.hash == hash && equals(value, e.value))
{
if (prev)
{
prev.next = e.next;
}
else
{
set.table[i] = e.next;
}
if (e.before)
{
e.before.after = e.after;
}
else
{
set.head = e.after;
}
if (e.after)
{
e.after.before = e.before;
}
else
{
set.tail = e.before;
}
set.count--;
linkedhashset_free_entry(set, e);
return true;
}
prev = e;
e = e.next;
}
return false;
}
fn void linkedhashset_free_entry(LinkedHashSet* set, LinkedEntry *entry) @private
{
allocator::free(set.allocator, entry);
}
struct LinkedHashSetIterator
{
LinkedHashSet* set;
LinkedEntry* current;
bool started;
}
fn LinkedHashSetIterator LinkedHashSet.iter(&set) => { .set = set, .current = set.head, .started = false };
fn bool LinkedHashSetIterator.next(&self)
{
if (!self.started)
{
self.current = self.set.head;
self.started = true;
}
else if (self.current)
{
self.current = self.current.after;
}
return self.current != null;
}
fn Value*? LinkedHashSetIterator.get(&self)
{
return self.current ? &self.current.value : NOT_FOUND~;
}
fn bool LinkedHashSetIterator.has_next(&self)
{
if (!self.started) return self.set.head != null;
return self.current && self.current.after != null;
}
fn usz LinkedHashSetIterator.len(&self) @operator(len)
{
return self.set.count;
}
int dummy @local;

View File

@@ -1,14 +1,15 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::linkedlist{Type};
module std::collections::linkedlist <Type>;
import std::io;
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
struct Node @private
struct Node
{
Node *next;
Node *prev;
Node* next;
Node* prev;
Type value;
}
@@ -16,8 +17,31 @@ struct LinkedList
{
Allocator allocator;
usz size;
Node *_first;
Node *_last;
Node* _first;
Node* _last;
}
fn usz? LinkedList.to_format(&self, Formatter* f) @dynamic
{
usz len = f.print("{ ")!;
for (Node* node = self._first; node != null; node = node.next)
{
len += f.printf(node.next ? "%s, " : "%s", node.value)!;
}
return len + f.print(" }");
}
macro LinkedList @new(Allocator allocator, Type[] #default_values = {})
{
LinkedList new_list;
new_list.init(allocator);
new_list.push_all(#default_values);
return new_list;
}
macro LinkedList @tnew(Type[] #default_values = {})
{
return @new(tmem, #default_values);
}
<*
@@ -68,6 +92,11 @@ fn void LinkedList.push_front(&self, Type value)
self.size++;
}
fn void LinkedList.push_front_all(&self, Type[] value)
{
foreach_r (v : value) self.push_front(v);
}
fn void LinkedList.push(&self, Type value)
{
Node *last = self._last;
@@ -85,18 +114,23 @@ fn void LinkedList.push(&self, Type value)
self.size++;
}
fn void LinkedList.push_all(&self, Type[] value)
{
foreach (v : value) self.push(v);
}
fn Type? LinkedList.peek(&self) => self.first() @inline;
fn Type? LinkedList.peek_last(&self) => self.last() @inline;
fn Type? LinkedList.first(&self)
{
if (!self._first) return NO_MORE_ELEMENT?;
if (!self._first) return NO_MORE_ELEMENT~;
return self._first.value;
}
fn Type? LinkedList.last(&self)
{
if (!self._last) return NO_MORE_ELEMENT?;
if (!self._last) return NO_MORE_ELEMENT~;
return self._last.value;
}
@@ -133,6 +167,7 @@ macro Node* LinkedList.node_at_index(&self, usz index)
while (index--) node = node.next;
return node;
}
<*
@require index < self.size
*>
@@ -141,6 +176,14 @@ fn Type LinkedList.get(&self, usz index)
return self.node_at_index(index).value;
}
<*
@require index < self.size
*>
fn Type* LinkedList.get_ref(&self, usz index)
{
return &self.node_at_index(index).value;
}
<*
@require index < self.size
*>
@@ -149,12 +192,32 @@ fn void LinkedList.set(&self, usz index, Type element)
self.node_at_index(index).value = element;
}
fn usz? LinkedList.index_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
for (Node* node = self._first, usz i = 0; node != null; node = node.next, ++i)
{
if (node.value == t) return i;
}
return NOT_FOUND~;
}
fn usz? LinkedList.rindex_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
for (Node* node = self._last, usz i = self.size - 1; node != null; node = node.prev, --i)
{
if (node.value == t) return i;
if (i == 0) break;
}
return NOT_FOUND~;
}
<*
@require index < self.size
*>
fn void LinkedList.remove_at(&self, usz index)
{
self.unlink(self.node_at_index(index));
linked_list_unlink(self, self.node_at_index(index));
}
<*
@@ -169,47 +232,47 @@ fn void LinkedList.insert_at(&self, usz index, Type element)
case self.size:
self.push(element);
default:
self.link_before(self.node_at_index(index), element);
linked_list_link_before(self, self.node_at_index(index), element);
}
}
<*
@require succ != null
*>
fn void LinkedList.link_before(&self, Node *succ, Type value) @private
fn void linked_list_link_before(LinkedList* l, Node *succ, Type value) @private
{
Node* pred = succ.prev;
Node* new_node = self.alloc_node();
Node* new_node = l.alloc_node();
*new_node = { .prev = pred, .next = succ, .value = value };
succ.prev = new_node;
if (!pred)
{
self._first = new_node;
l._first = new_node;
}
else
{
pred.next = new_node;
}
self.size++;
l.size++;
}
<*
@require self._first != null
@require l._first != null
*>
fn void LinkedList.unlink_first(&self) @private
fn void linked_list_unlink_first(LinkedList* l) @private
{
Node* f = self._first;
Node* f = l._first;
Node* next = f.next;
self.free_node(f);
self._first = next;
l.free_node(f);
l._first = next;
if (!next)
{
self._last = null;
l._last = null;
}
else
{
next.prev = null;
}
self.size--;
l.size--;
}
fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
@@ -222,7 +285,7 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
case equals(node.value, t):
Node* next = node.next;
self.unlink(node);
linked_list_unlink(self, node);
node = next;
default:
node = node.next;
@@ -233,8 +296,8 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
fn Type? LinkedList.pop(&self)
{
if (!self._last) return NO_MORE_ELEMENT?;
defer self.unlink_last();
if (!self._last) return NO_MORE_ELEMENT~;
defer linked_list_unlink_last(self);
return self._last.value;
}
@@ -245,21 +308,21 @@ fn bool LinkedList.is_empty(&self)
fn Type? LinkedList.pop_front(&self)
{
if (!self._first) return NO_MORE_ELEMENT?;
defer self.unlink_first();
if (!self._first) return NO_MORE_ELEMENT~;
defer linked_list_unlink_first(self);
return self._first.value;
}
fn void? LinkedList.remove_last(&self) @maydiscard
{
if (!self._first) return NO_MORE_ELEMENT?;
self.unlink_last();
if (!self._first) return NO_MORE_ELEMENT~;
linked_list_unlink_last(self);
}
fn void? LinkedList.remove_first(&self) @maydiscard
{
if (!self._first) return NO_MORE_ELEMENT?;
self.unlink_first();
if (!self._first) return NO_MORE_ELEMENT~;
linked_list_unlink_first(self);
}
@@ -269,7 +332,7 @@ fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
if (node.value == t)
{
self.unlink(node);
linked_list_unlink(self, node);
return true;
}
}
@@ -282,7 +345,7 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
if (node.value == t)
{
self.unlink(node);
linked_list_unlink(self, node);
return true;
}
}
@@ -291,7 +354,7 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
<*
@require self._last != null
*>
fn void LinkedList.unlink_last(&self) @inline @private
fn void linked_list_unlink_last(LinkedList* self) @inline @private
{
Node* l = self._last;
Node* prev = l.prev;
@@ -311,7 +374,7 @@ fn void LinkedList.unlink_last(&self) @inline @private
<*
@require x != null
*>
fn void LinkedList.unlink(&self, Node* x) @private
fn void linked_list_unlink(LinkedList* self, Node* x) @private
{
Node* next = x.next;
Node* prev = x.prev;
@@ -334,3 +397,69 @@ fn void LinkedList.unlink(&self, Node* x) @private
self.free_node(x);
self.size--;
}
macro bool LinkedList.eq(&self, other) @operator(==) @if(ELEMENT_IS_EQUATABLE)
{
Node* node1 = self._first;
Node* node2 = other._first;
while (true)
{
if (!node1) return node2 == null;
if (!node2) return false;
if (node1.value != node2.value) return false;
node1 = node1.next;
node2 = node2.next;
}
return true;
}
fn LinkedListArrayView LinkedList.array_view(&self)
{
return { .list = self, .current_node = self._first };
}
struct LinkedListArrayView
{
LinkedList* list;
Node* current_node;
usz current_index;
}
fn usz LinkedListArrayView.len(&self) @operator(len) => self.list.size;
<*
@require index < self.list.size
*>
fn Type LinkedListArrayView.get(&self, usz index) @operator([])
{
return *self.get_ref(index);
}
<*
@require index < self.list.size
*>
fn Type* LinkedListArrayView.get_ref(&self, usz index) @operator(&[])
{
if (index == self.list.size - 1)
{
self.current_node = self.list._last;
self.current_index = index;
}
while (self.current_index != index)
{
switch
{
case index < self.current_index: // reverse iteration
self.current_node = self.current_node.prev;
self.current_index--;
case index > self.current_index:
self.current_node = self.current_node.next;
self.current_index++;
}
}
return &self.current_node.value;
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::list{Type};
module std::collections::list <Type>;
import std::io, std::math, std::collections::list_common;
alias ElementPredicate = fn bool(Type *type);
@@ -13,7 +13,7 @@ const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR };
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
struct List (Printable)
{
@@ -57,7 +57,7 @@ fn List* List.tinit(&self, usz initial_capacity = 16)
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
{
self.init(allocator, values.len) @inline;
self.add_array(values) @inline;
self.push_all(values) @inline;
return self;
}
@@ -70,7 +70,7 @@ fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
fn List* List.tinit_with_array(&self, Type[] values)
{
self.tinit(values.len) @inline;
self.add_array(values) @inline;
self.push_all(values) @inline;
return self;
}
@@ -82,7 +82,7 @@ fn void List.init_wrapping_array(&self, Allocator allocator, Type[] types)
self.allocator = allocator;
self.capacity = types.len;
self.entries = types.ptr;
self.set_size(types.len);
list_set_size(self, types.len);
}
fn bool List.is_initialized(&self) @inline => self.allocator != null && self.allocator != (Allocator)&dummy;
@@ -110,24 +110,24 @@ fn usz? List.to_format(&self, Formatter* formatter) @dynamic
fn void List.push(&self, Type element) @inline
{
self.reserve(1);
self.entries[self.set_size(self.size + 1)] = element;
self.entries[list_set_size(self, self.size + 1)] = element;
}
fn Type? List.pop(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.set_size(self.size - 1);
if (!self.size) return NO_MORE_ELEMENT~;
defer list_set_size(self, self.size - 1);
return self.entries[self.size - 1];
}
fn void List.clear(&self)
{
self.set_size(0);
list_set_size(self, 0);
}
fn Type? List.pop_first(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT~;
defer self.remove_at(0);
return self.entries[0];
}
@@ -137,16 +137,17 @@ fn Type? List.pop_first(&self)
*>
fn void List.remove_at(&self, usz index)
{
self.set_size(self.size - 1);
if (!self.size || index == self.size) return;
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
usz new_size = self.size - 1;
defer list_set_size(self, new_size);
if (!new_size || index == new_size) return;
self.entries[index .. new_size - 1] = self.entries[index + 1 .. new_size];
}
fn void List.add_all(&self, List* other_list)
{
if (!other_list.size) return;
self.reserve(other_list.size);
usz index = self.set_size(self.size + other_list.size);
usz index = list_set_size(self, self.size + other_list.size);
foreach (&value : other_list)
{
self.entries[index++] = *value;
@@ -198,11 +199,25 @@ fn Type[] List.array_view(&self)
@param [in] array
@ensure self.size >= array.len
*>
fn void List.add_array(&self, Type[] array)
fn void List.add_array(&self, Type[] array) @deprecated("Use push_all")
{
if (!array.len) return;
self.reserve(array.len);
usz index = self.set_size(self.size + array.len);
usz index = list_set_size(self, self.size + array.len);
self.entries[index : array.len] = array[..];
}
<*
Add the values of an array to this list.
@param [in] array
@ensure self.size >= array.len
*>
fn void List.push_all(&self, Type[] array)
{
if (!array.len) return;
self.reserve(array.len);
usz index = list_set_size(self, self.size + array.len);
self.entries[index : array.len] = array[..];
}
@@ -217,7 +232,7 @@ fn void List.push_front(&self, Type type) @inline
fn void List.insert_at(&self, usz index, Type type)
{
self.reserve(1);
self.set_size(self.size + 1);
list_set_size(self, self.size + 1);
for (isz i = self.size - 1; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
@@ -235,25 +250,25 @@ fn void List.set_at(&self, usz index, Type type)
fn void? List.remove_last(&self) @maydiscard
{
if (!self.size) return NO_MORE_ELEMENT?;
self.set_size(self.size - 1);
if (!self.size) return NO_MORE_ELEMENT~;
list_set_size(self, self.size - 1);
}
fn void? List.remove_first(&self) @maydiscard
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT~;
self.remove_at(0);
}
fn Type? List.first(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT~;
return self.entries[0];
}
fn Type? List.last(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return NO_MORE_ELEMENT~;
return self.entries[self.size - 1];
}
@@ -284,7 +299,7 @@ fn void List.free(&self)
{
if (!self.allocator || self.allocator.ptr == &dummy || !self.capacity) return;
self.pre_free(); // Remove sanitizer annotation
list_pre_free(self); // Remove sanitizer annotation
$if type_is_overaligned():
allocator::free_aligned(self.allocator, self.entries);
@@ -343,7 +358,7 @@ fn usz List.retain_using_test(&self, ElementTest filter, any context)
return list_common::list_remove_using_test(self, filter, true, context);
}
fn void List.ensure_capacity(&self, usz min_capacity) @local
fn void list_ensure_capacity(List* self, usz min_capacity) @local
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
@@ -359,7 +374,7 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
break;
}
self.pre_free(); // Remove sanitizer annotation
list_pre_free(self); // Remove sanitizer annotation
min_capacity = math::next_power_of_2(min_capacity);
$if type_is_overaligned():
@@ -369,7 +384,7 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
$endif;
self.capacity = min_capacity;
self.post_alloc(); // Add sanitizer annotation
list_post_alloc(self); // Add sanitizer annotation
}
<*
@@ -404,7 +419,7 @@ fn void List.reserve(&self, usz added)
assert(new_size < usz.max / 2U);
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
while (new_capacity < new_size) new_capacity *= 2U;
self.ensure_capacity(new_capacity);
list_ensure_capacity(self, new_capacity);
}
fn void List._update_size_change(&self,usz old_size, usz new_size)
@@ -421,7 +436,7 @@ fn void List._update_size_change(&self,usz old_size, usz new_size)
<*
@require new_size == 0 || self.capacity != 0
*>
fn usz List.set_size(&self, usz new_size) @inline @private
fn usz list_set_size(List* self, usz new_size) @inline @private
{
usz old_size = self.size;
self._update_size_change(old_size, new_size);
@@ -429,7 +444,7 @@ fn usz List.set_size(&self, usz new_size) @inline @private
return old_size;
}
macro void List.pre_free(&self) @private
macro void list_pre_free(List* self) @private
{
if (!self.capacity) return;
self._update_size_change(self.size, self.capacity);
@@ -438,7 +453,7 @@ macro void List.pre_free(&self) @private
<*
@require self.capacity > 0
*>
macro void List.post_alloc(&self) @private
macro void list_post_alloc(List* self) @private
{
self._update_size_change(self.capacity, self.size);
}
@@ -446,22 +461,22 @@ macro void List.post_alloc(&self) @private
// Functions for equatable types
fn usz? List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn usz? List.index_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
{
if (equals(v, type)) return i;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
fn usz? List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn usz? List.rindex_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE)
{
foreach_r (i, v : self)
{
if (equals(v, type)) return i;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
@@ -517,7 +532,8 @@ fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
usz old_size = self.size;
defer {
defer
{
if (old_size != self.size) self._update_size_change(old_size, self.size);
}
return list_common::list_remove_item(self, value);

View File

@@ -1,4 +1,4 @@
module std::collections::maybe{Type};
module std::collections::maybe <Type>;
import std::io;
struct Maybe (Printable)
@@ -32,5 +32,14 @@ const Maybe EMPTY = { };
macro Type? Maybe.get(self)
{
return self.has_value ? self.value : NOT_FOUND?;
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

@@ -151,7 +151,7 @@ fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array();
<*
@require self.is_keyable()
*>
fn void Object.init_map_if_needed(&self) @private
fn void object_init_map_if_needed(Object* self) @private
{
if (self.is_empty())
{
@@ -163,7 +163,7 @@ fn void Object.init_map_if_needed(&self) @private
<*
@require self.is_indexable()
*>
fn void Object.init_array_if_needed(&self) @private
fn void object_init_array_if_needed(Object* self) @private
{
if (self.is_empty())
{
@@ -175,17 +175,18 @@ fn void Object.init_array_if_needed(&self) @private
<*
@require self.is_keyable()
*>
fn void Object.set_object(&self, String key, Object* new_object) @private
fn void object_set_object(Object* self, String key, Object* new_object) @private
{
self.init_map_if_needed();
ObjectInternalMapEntry*? entry = self.map.get_entry(key);
defer
{
(void)entry.value.free();
}
object_init_map_if_needed(self);
Object*? val = self.map.get_entry(key).value;
defer (void)val.free();
self.map.set(key, new_object);
}
<*
@require self.allocator != null : "This object is not properly initialized, was it really created using 'new'"
@require $typeof(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,9 +202,8 @@ 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):
$case $defined(String x = value):
return new_string(value, self.allocator);
$default:
$error "Unsupported object type.";
@@ -214,7 +214,7 @@ macro Object* Object.object_from_value(&self, value) @private
macro Object* Object.set(&self, String key, value)
{
Object* val = self.object_from_value(value);
self.set_object(key, val);
object_set_object(self, key, val);
return val;
}
@@ -242,8 +242,7 @@ macro Object* Object.push(&self, value)
<*
@require self.is_keyable()
*>
fn Object*? Object.get(&self, String key) => self.is_empty() ? NOT_FOUND? : self.map.get(key);
fn Object*? Object.get(&self, String key) => self.is_empty() ? NOT_FOUND~ : self.map.get(key);
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
@@ -268,7 +267,7 @@ fn usz Object.get_len(&self)
*>
fn void Object.push_object(&self, Object* to_append)
{
self.init_array_if_needed();
object_init_array_if_needed(self);
self.array.push(to_append);
}
@@ -277,7 +276,7 @@ fn void Object.push_object(&self, Object* to_append)
*>
fn void Object.set_object_at(&self, usz index, Object* to_set)
{
self.init_array_if_needed();
object_init_array_if_needed(self);
while (self.array.len() < index)
{
self.array.push(&NULL_OBJECT);
@@ -308,7 +307,7 @@ macro get_integer_value(Object* value, $Type)
return ($Type)value.s.to_uint128();
$endif
}
if (!value.is_int()) return string::MALFORMED_INTEGER?;
if (!value.is_int()) return string::MALFORMED_INTEGER~;
return ($Type)value.i;
}
@@ -361,7 +360,7 @@ fn uint128? Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint1
fn String? Object.get_string(&self, String key)
{
Object* value = self.get(key)!;
if (!value.is_string()) return TYPE_MISMATCH?;
if (!value.is_string()) return TYPE_MISMATCH~;
return value.s;
}
@@ -371,7 +370,7 @@ fn String? Object.get_string(&self, String key)
fn String? Object.get_string_at(&self, usz index)
{
Object* value = self.get_at(index);
if (!value.is_string()) return TYPE_MISMATCH?;
if (!value.is_string()) return TYPE_MISMATCH~;
return value.s;
}
@@ -381,7 +380,7 @@ fn String? Object.get_string_at(&self, usz index)
macro String? Object.get_enum(&self, $EnumType, String key)
{
Object value = self.get(key)!;
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
if ($EnumType.typeid != value.type) return TYPE_MISMATCH~;
return ($EnumType)value.i;
}
@@ -391,7 +390,7 @@ macro String? Object.get_enum(&self, $EnumType, String key)
macro String? Object.get_enum_at(&self, $EnumType, usz index)
{
Object value = self.get_at(index);
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
if ($EnumType.typeid != value.type) return TYPE_MISMATCH~;
return ($EnumType)value.i;
}
@@ -401,7 +400,7 @@ macro String? Object.get_enum_at(&self, $EnumType, usz index)
fn bool? Object.get_bool(&self, String key)
{
Object* value = self.get(key)!;
if (!value.is_bool()) return TYPE_MISMATCH?;
if (!value.is_bool()) return TYPE_MISMATCH~;
return value.b;
}
@@ -412,7 +411,7 @@ fn bool? Object.get_bool(&self, String key)
fn bool? Object.get_bool_at(&self, usz index)
{
Object* value = self.get_at(index);
if (!value.is_bool()) return TYPE_MISMATCH?;
if (!value.is_bool()) return TYPE_MISMATCH~;
return value.b;
}
@@ -431,7 +430,7 @@ fn double? Object.get_float(&self, String key)
case FLOAT:
return value.f;
default:
return TYPE_MISMATCH?;
return TYPE_MISMATCH~;
}
}
@@ -450,7 +449,7 @@ fn double? Object.get_float_at(&self, usz index)
case FLOAT:
return value.f;
default:
return TYPE_MISMATCH?;
return TYPE_MISMATCH~;
}
}

View File

@@ -20,16 +20,13 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
module std::collections::priorityqueue{Type};
import std::collections::priorityqueue::private;
typedef PriorityQueue = inline PrivatePriorityQueue{Type, false};
typedef PriorityQueueMax = inline PrivatePriorityQueue{Type, true};
module std::collections::priorityqueue::private{Type, MAX};
module std::collections::priorityqueue;
import std::collections::list, std::io;
struct PrivatePriorityQueue (Printable)
typedef PriorityQueue <Type> = inline PrivatePriorityQueue{Type, false};
typedef PriorityQueueMax <Type> = inline PrivatePriorityQueue{Type, true};
struct PrivatePriorityQueue (Printable) <Type, MAX>
{
List{Type} heap;
}
@@ -86,7 +83,7 @@ fn Type? PrivatePriorityQueue.pop(&self)
{
usz i = 0;
usz len = self.heap.len();
if (!len) return NO_MORE_ELEMENT?;
if (!len) return NO_MORE_ELEMENT~;
usz new_count = len - 1;
self.heap.swap(0, new_count);
while OUTER: ((2 * i + 1) < new_count)

View File

@@ -1,7 +1,7 @@
<*
@require Type.is_ordered : "The type must be ordered"
*>
module std::collections::range{Type};
module std::collections::range <Type>;
import std::io;
struct Range (Printable)

View File

@@ -1,7 +1,7 @@
<*
@require Type.kindof == ARRAY : "Required an array type"
*>
module std::collections::ringbuffer{Type};
module std::collections::ringbuffer <Type>;
import std::io;
alias Element = $typeof((Type){}[0]);
@@ -48,7 +48,7 @@ fn Element? RingBuffer.pop(&self)
switch
{
case self.written == 0:
return NO_MORE_ELEMENT?;
return NO_MORE_ELEMENT~;
case self.written < self.buf.len:
self.written--;
return self.buf[self.written];

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -7,10 +7,12 @@ const uint PIXELS_MAX = 400000000;
Purely informative. It will be saved to the file header,
but does not affect how chunks are en-/decoded.
*>
enum QOIColorspace : char (char id)
constdef QOIColorspace : char
{
SRGB = 0, // sRGB with linear alpha
LINEAR = 1 // all channels linear
<* sRGB with linear alpha *>
SRGB = 0,
<* all channels linear *>
LINEAR = 1
}
<*
@@ -19,7 +21,7 @@ enum QOIColorspace : char (char id)
AUTO can be used when decoding to automatically determine
the channels from the file's header.
*>
enum QOIChannels : char (char id)
constdef QOIChannels : inline char
{
AUTO = 0,
RGB = 3,
@@ -98,7 +100,7 @@ fn usz? write(String filename, char[] input, QOIDesc* desc) => @pool()
fn char[]? read(Allocator allocator, String filename, QOIDesc* desc, QOIChannels channels = AUTO) => @pool()
{
// read file
char[] data = file::load_temp(filename) ?? FILE_OPEN_FAILED?!;
char[] data = file::load_temp(filename) ?? FILE_OPEN_FAILED~!;
// pass data to decode function
return decode(allocator, data, desc, channels);
}
@@ -126,14 +128,14 @@ import std::bits;
fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
{
// check info in desc
if (desc.width == 0 || desc.height == 0) return INVALID_PARAMETERS?;
if (desc.channels == AUTO) return INVALID_PARAMETERS?;
if (desc.width == 0 || desc.height == 0) return INVALID_PARAMETERS~;
if (desc.channels == AUTO) return INVALID_PARAMETERS~;
uint pixels = desc.width * desc.height;
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS~;
// check input data size
uint image_size = pixels * desc.channels.id;
if (image_size != input.len) return INVALID_DATA?;
uint image_size = pixels * desc.channels;
if (image_size != input.len) return INVALID_DATA~;
// allocate memory for encoded data (output)
// header + chunk tag and RGB(A) data for each pixel + end of stream
@@ -146,13 +148,13 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
.be_magic = bswap('qoif'),
.be_width = bswap(desc.width),
.be_height = bswap(desc.height),
.channels = desc.channels.id,
.colorspace = desc.colorspace.id
.channels = desc.channels,
.colorspace = desc.colorspace
};
uint pos = Header.sizeof; // Current position in output
uint loc; // Current position in image (top-left corner)
uint loc_end = image_size - desc.channels.id; // End of image data
uint loc_end = image_size - desc.channels; // End of image data
char run_length = 0; // Length of the current run
Pixel[64] palette; // Zero-initialized by default
@@ -163,7 +165,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
ichar[<3>] luma; // ...and luma
// write chunks
for (loc = 0; loc < image_size; loc += desc.channels.id)
for (loc = 0; loc < image_size; loc += desc.channels)
{
// set previous pixel
prev = p;
@@ -248,7 +250,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];
@@ -281,27 +283,27 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels channels = AUTO) @nodiscard
{
// check input data
if (data.len < Header.sizeof + END_OF_STREAM.len) return INVALID_DATA?;
if (data.len < Header.sizeof + END_OF_STREAM.len) return INVALID_DATA~;
// get header
Header* header = (Header*)data.ptr;
// check magic bytes (FourCC)
if (bswap(header.be_magic) != 'qoif') return INVALID_DATA?;
if (bswap(header.be_magic) != 'qoif') return INVALID_DATA~;
// copy header data to desc
desc.width = bswap(header.be_width);
desc.height = bswap(header.be_height);
desc.channels = @enumcast(QOIChannels, header.channels)!; // Rethrow if invalid
desc.colorspace = @enumcast(QOIColorspace, header.colorspace)!; // Rethrow if invalid
if (desc.channels == AUTO) return INVALID_DATA?; // Channels must be specified in the header
uint width = desc.width = bswap(header.be_width);
uint height = desc.height = bswap(header.be_height);
QOIChannels desc_channels = desc.channels = header.channels;
desc.colorspace = header.colorspace;
if (desc_channels == AUTO) return INVALID_DATA~; // Channels must be specified in the header
// check width and height
if (desc.width == 0 || desc.height == 0) return INVALID_DATA?;
if (width == 0 || height == 0) return INVALID_DATA~;
// check pixel count
ulong pixels = (ulong)desc.width * (ulong)desc.height;
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
ulong pixels = (ulong)width * (ulong)height;
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS~;
uint pos = Header.sizeof; // Current position in data
uint loc; // Current position in image (top-left corner)
@@ -311,14 +313,14 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c
Pixel[64] palette; // Zero-initialized by default
Pixel p = { 0, 0, 0, 255 };
if (channels == AUTO) channels = desc.channels;
if (channels == AUTO) channels = desc_channels;
// allocate memory for image data
usz image_size = (usz)pixels * channels.id;
usz image_size = (usz)pixels * channels;
char[] image = allocator::alloc_array(allocator, char, image_size);
defer catch allocator::free(allocator, image);
for (loc = 0; loc < image_size; loc += channels.id)
for (loc = 0; loc < image_size; loc += channels)
{
// get chunk tag
tag = data[pos];
@@ -364,7 +366,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;
@@ -391,31 +393,22 @@ const OP_RUN = 0b11;
struct Header @packed
{
uint be_magic; // magic bytes "qoif"
uint be_width; // image width in pixels (BE)
uint be_height; // image height in pixels (BE)
<* magic bytes "qoif" *>
uint be_magic;
<* image width in pixels (BE) *>
uint be_width;
<* image height in pixels (BE) *>
uint be_height;
// informative fields
char channels; // 3 = RGB, 4 = RGB
char colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
<* 3 = RGB, 4 = RGB *>
QOIChannels channels;
<* 0 = sRGB with linear alpha, 1 = all channels linear *>
QOIColorspace colorspace;
}
const char[*] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1};
// inefficient, but it's only run once at a time
<*
@return? INVALID_DATA
*>
macro @enumcast($Type, raw)
{
foreach (value : $Type.values)
{
if (value.id == raw) return value;
}
return INVALID_DATA?;
}
typedef Pixel = inline char[<4>];
macro char Pixel.hash(Pixel p)
{

1215
lib/std/compression/zip.c3 Normal file

File diff suppressed because it is too large Load Diff

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
@@ -63,12 +90,12 @@ fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz a
{
alignment = alignment_for_allocation(alignment);
usz total_len = self.data.len;
if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
if (size > total_len) return mem::INVALID_ALLOC_SIZE~;
void* start_mem = self.data.ptr;
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof;
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz end = (usz)(mem - self.data.ptr) + size;
if (end > total_len) return mem::OUT_OF_MEMORY?;
if (end > total_len) return mem::OUT_OF_MEMORY~;
self.used = end;
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
header.size = size;
@@ -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
@@ -88,7 +117,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
alignment = alignment_for_allocation(alignment);
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
usz total_len = self.data.len;
if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
if (size > total_len) return mem::INVALID_ALLOC_SIZE~;
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
usz old_size = header.size;
// Do last allocation and alignment match?
@@ -101,7 +130,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
else
{
usz new_used = self.used + size - old_size;
if (new_used > total_len) return mem::OUT_OF_MEMORY?;
if (new_used > total_len) return mem::OUT_OF_MEMORY~;
self.used = new_used;
}
header.size = size;
@@ -109,6 +138,14 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
}
// Otherwise just allocate new memory.
void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(mem, old_pointer, math::min(size, 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
@@ -47,7 +56,7 @@ fn BackedArenaAllocator*? new_backed_allocator(usz size, Allocator allocator)
fn void BackedArenaAllocator.destroy(&self)
{
self.reset(0);
if (self.last_page) (void)self._free_page(self.last_page);
if (self.last_page) (void)_free_page(self, self.last_page);
allocator::free(self.backing_allocator, self);
}
@@ -70,7 +79,7 @@ fn void BackedArenaAllocator.reset(&self, usz mark)
self.used = last_page.mark;
ExtraPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
_free_page(self, to_free)!!;
}
self.last_page = last_page;
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
@@ -89,13 +98,13 @@ fn void BackedArenaAllocator.reset(&self, usz mark)
self.used = mark;
}
fn void? BackedArenaAllocator._free_page(&self, ExtraPage* page) @inline @local
fn void? _free_page(BackedArenaAllocator* self, ExtraPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
}
fn void*? BackedArenaAllocator._realloc_page(&self, ExtraPage* page, usz size, usz alignment) @inline @local
fn void*? _realloc_page(BackedArenaAllocator* self, ExtraPage* page, usz size, usz alignment) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
@@ -124,11 +133,11 @@ fn void*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignm
assert(self.last_page, "Realloc of unrelated pointer");
// First grab the page
ExtraPage *page = pointer - ExtraPage.sizeof;
return self._realloc_page(page, size, alignment);
return _realloc_page(self, page, size, alignment);
}
AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(data, pointer, math::min(size, chunk.size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}

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), whereas the BackedArenaAllocator will have heap allocator characteristics.
*>
struct DynamicArenaAllocator (Allocator)
{
Allocator backing_allocator;
@@ -106,7 +117,7 @@ fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a
return old_pointer;
}
void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(new_mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT);
return new_mem;
}
@@ -126,36 +137,7 @@ fn void DynamicArenaAllocator.reset(&self)
self.page = page;
}
<*
@require math::is_power_of_2(alignment)
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @local
{
// First, make sure that we can align it, extending the page size if needed.
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + alignment, alignment));
assert(page_size > size + DynamicArenaChunk.sizeof);
// Grab the page without alignment (we do it ourselves)
void* mem = allocator::malloc_try(self.backing_allocator, page_size)!;
DynamicArenaPage*? page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
if (catch err = page)
{
allocator::free(self.backing_allocator, mem);
return err?;
}
page.memory = mem;
void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment);
assert(mem_start + size < mem + page_size);
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
chunk.size = size;
page.prev_arena = self.page;
page.total = page_size;
page.used = mem_start + size - page.memory;
self.page = page;
page.current_stack_ptr = mem_start;
return mem_start;
}
<*
@require size > 0 : `acquire expects size > 0`
@@ -178,7 +160,7 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
}
if (!page)
{
ptr = self._alloc_new(size, alignment)!;
ptr = _alloc_new(self, size, alignment)!;
break SET_DONE;
}
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
@@ -197,7 +179,7 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
break ALLOCATE_NEW;
}
}
ptr = self._alloc_new(size, alignment)!;
ptr = _alloc_new(self, size, alignment)!;
break SET_DONE;
}
page.used = new_used;
@@ -209,3 +191,34 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
return ptr;
}
<*
@require math::is_power_of_2(alignment)
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? _alloc_new(DynamicArenaAllocator* self, usz size, usz alignment) @local
{
// First, make sure that we can align it, extending the page size if needed.
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + alignment, alignment));
assert(page_size > size + DynamicArenaChunk.sizeof);
// Grab the page without alignment (we do it ourselves)
void* mem = allocator::malloc_try(self.backing_allocator, page_size)!;
DynamicArenaPage*? page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
if (catch err = page)
{
allocator::free(self.backing_allocator, mem);
return err~;
}
page.memory = mem;
void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment);
assert(mem_start + size < mem + page_size);
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
chunk.size = size;
page.prev_arena = self.page;
page.total = page_size;
page.used = mem_start + size - page.memory;
self.page = page;
page.current_stack_ptr = mem_start;
return mem_start;
}

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;
@@ -25,58 +32,58 @@ fn void*? SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type,
{
if (init_type == ZERO)
{
return alignment > 0 ? @aligned_alloc(self._calloc, size, alignment) : self._calloc(size);
return alignment > 0 ? @aligned_alloc_fn(self, simple_alloc_calloc, size, alignment) : simple_alloc_calloc(self, size);
}
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size);
return alignment > 0 ? @aligned_alloc_fn(self, simple_alloc_alloc, size, alignment) : simple_alloc_alloc(self, size);
}
fn void*? SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
return alignment > 0
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment)
: self._realloc(old_pointer, size);
? @aligned_realloc_fn(self, simple_alloc_calloc, simple_alloc_free, old_pointer, size, alignment)
: simple_alloc_realloc(self, old_pointer, size);
}
fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{
if (aligned)
{
@aligned_free(self._free, old_pointer)!!;
@aligned_free_fn(self, simple_alloc_free, old_pointer)!!;
}
else
{
self._free(old_pointer);
simple_alloc_free(self, old_pointer);
}
}
<*
@require old_pointer && bytes > 0
*>
fn void*? SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
fn void*? simple_alloc_realloc(SimpleHeapAllocator* self, void* old_pointer, usz bytes) @local
{
// Find the block header.
Header* block = (Header*)old_pointer - 1;
if (block.size >= bytes) return old_pointer;
void* new = self._alloc(bytes)!;
void* new = simple_alloc_alloc(self, bytes)!;
usz max_to_copy = math::min(block.size, bytes);
mem::copy(new, old_pointer, max_to_copy);
self._free(old_pointer);
simple_alloc_free(self, old_pointer);
return new;
}
fn void*? SimpleHeapAllocator._calloc(&self, usz bytes) @local
fn void*? simple_alloc_calloc(SimpleHeapAllocator* self, usz bytes) @local
{
void* data = self._alloc(bytes)!;
void* data = simple_alloc_alloc(self, bytes)!;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
fn void*? simple_alloc_alloc(SimpleHeapAllocator* self, usz bytes) @local
{
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
if (!self.free_list)
{
self.add_block(aligned_bytes)!;
simple_alloc_add_block(self, aligned_bytes)!;
}
Header* current = self.free_list;
@@ -116,22 +123,22 @@ fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
current = current.next;
}
}
self.add_block(aligned_bytes)!;
return self._alloc(aligned_bytes);
simple_alloc_add_block(self, aligned_bytes)!;
return simple_alloc_alloc(self, aligned_bytes);
}
fn void? SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
fn void? simple_alloc_add_block(SimpleHeapAllocator* self, usz aligned_bytes) @local
{
assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes);
char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!;
Header* new_block = (Header*)result.ptr;
new_block.size = result.len - Header.sizeof;
new_block.next = null;
self._free(new_block + 1);
simple_alloc_free(self, new_block + 1);
}
fn void SimpleHeapAllocator._free(&self, void* ptr) @local
fn void simple_alloc_free(SimpleHeapAllocator* self, void* ptr) @local
{
// Empty ptr -> do nothing.
if (!ptr) return;

View File

@@ -1,22 +1,20 @@
// 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;
<*
The LibcAllocator is a wrapper around malloc to conform to the Allocator interface.
*>
typedef LibcAllocator (Allocator) = uptr;
const LibcAllocator LIBC_ALLOCATOR = {};
typedef LibcAllocator (Allocator, Printable) = uptr;
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
fn usz? LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
module std::core::mem::allocator @if(env::POSIX);
import std::os;
import libc;
fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
if (init_type == ZERO)
@@ -24,22 +22,22 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
void* data @noinit;
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY~;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY~;
}
else
{
void* data @noinit;
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY~;
}
else
{
if (!(data = libc::malloc(bytes))) return mem::OUT_OF_MEMORY?;
if (!(data = libc::malloc(bytes))) return mem::OUT_OF_MEMORY~;
}
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
@@ -50,25 +48,32 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
void* new_ptr;
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return mem::OUT_OF_MEMORY?;
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~;
$switch:
$case env::DARWIN:
usz old_usable_size = darwin::malloc_size(old_ptr);
$case env::LINUX:
usz old_usable_size = linux::malloc_usable_size(old_ptr);
$default:
usz old_usable_size = new_bytes;
$endswitch
// Try realloc, even though it might be unaligned.
void* new_ptr = libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~!;
// If it's aligned then we're done!
uptr ptr_val = (uptr)new_ptr;
if (ptr_val & (alignment - 1) == 0) return new_ptr;
// We failed, so we need to use memalign
// We will free new_ptr before we exit.
defer libc::free(new_ptr);
// Create a pointer which is sure to be aligned.
void* aligned_ptr;
if (posix::posix_memalign(&aligned_ptr, alignment, new_bytes))
{
return mem::OUT_OF_MEMORY~;
}
// Now it is safe to copy the full range of data.
mem::copy(aligned_ptr, new_ptr, new_bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return aligned_ptr;
usz copy_size = new_bytes < old_usable_size ? new_bytes : old_usable_size;
mem::copy(new_ptr, old_ptr, copy_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
libc::free(old_ptr);
return new_ptr;
}
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
libc::free(old_ptr);
@@ -84,12 +89,12 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
{
if (alignment > 0)
{
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY?;
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY~;
}
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY~;
}
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
if (!data) return mem::OUT_OF_MEMORY?;
if (!data) return mem::OUT_OF_MEMORY~;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
@@ -100,9 +105,9 @@ fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignmen
{
if (alignment)
{
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY?;
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY~;
}
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~;
}
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
@@ -123,12 +128,12 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
if (init_type == ZERO)
{
void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1);
return data ?: mem::OUT_OF_MEMORYY?;
return data ?: mem::OUT_OF_MEMORY~;
}
else
{
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
if (!data) return mem::OUT_OF_MEMORY?;
if (!data) return mem::OUT_OF_MEMORY~;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
@@ -142,9 +147,9 @@ fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignmen
if (alignment)
{
void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!;
return data ?: mem::OUT_OF_MEMORY?;
return data ?: mem::OUT_OF_MEMORY~;
}
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~;
}

View File

@@ -1,5 +1,14 @@
module std::core::mem::allocator;
import std::math;
<*
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;
@@ -116,7 +124,7 @@ fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignm
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
usz old_size = header.size;
void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}

View File

@@ -1,13 +1,32 @@
module std::core::mem::allocator;
module std::core::mem::allocator @if(!(env::POSIX || env::WIN32) || !$feature(VMEM_TEMP));
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)
{
@@ -15,14 +34,22 @@ struct TempAllocator (Allocator)
TempAllocatorPage* last_page;
TempAllocator* derived;
bool allocated;
usz reserve_size;
usz realloc_size;
usz min_size;
usz used;
usz capacity;
usz original_capacity;
char[*] data;
}
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
struct TempAllocatorChunk @local
{
usz size;
char[*] data;
}
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
struct TempAllocatorPage
{
@@ -37,15 +64,20 @@ macro usz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
<*
@require size >= 16
@require size >= 64
@require realloc_size >= 64
@require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator."
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
*>
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size)
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve = temp_allocator_reserve_size, usz min_size = temp_allocator_min_size, usz realloc_size = temp_allocator_realloc_size)
{
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
temp.last_page = null;
temp.backing_allocator = allocator;
temp.used = 0;
temp.min_size = min_size;
temp.realloc_size = realloc_size;
temp.reserve_size = reserve;
temp.allocated = true;
temp.derived = null;
temp.original_capacity = temp.capacity = size;
@@ -53,19 +85,18 @@ fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size)
}
<*
@require !self.derived
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
@require mult > 0 : "The multiple can never be zero"
*>
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz min_size, usz buffer, usz mult)
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0)
{
if (!reserve) reserve = self.reserve_size;
usz remaining = self.capacity - self.used;
void* mem @noinit;
usz size @noinit;
if (min_size + buffer > remaining)
if (self.min_size + reserve > remaining)
{
return self.derived = new_temp_allocator(self.backing_allocator, min_size * mult)!;
return self.derived = new_temp_allocator(self.backing_allocator, self.realloc_size, self.reserve_size, self.min_size, self.realloc_size)!;
}
usz start = mem::aligned_offset(self.used + buffer, mem::DEFAULT_MEM_ALIGNMENT);
usz start = mem::aligned_offset(self.used + reserve, mem::DEFAULT_MEM_ALIGNMENT);
void* ptr = &self.data[start];
TempAllocator* temp = (TempAllocator*)ptr;
$if env::ADDRESS_SANITIZER:
@@ -74,6 +105,9 @@ fn TempAllocator*? TempAllocator.derive_allocator(&self, usz min_size, usz buffe
temp.last_page = null;
temp.backing_allocator = self.backing_allocator;
temp.used = 0;
temp.min_size = self.min_size;
temp.reserve_size = self.reserve_size;
temp.realloc_size = self.realloc_size;
temp.allocated = false;
temp.derived = null;
temp.original_capacity = temp.capacity = self.capacity - start - TempAllocator.sizeof;
@@ -82,6 +116,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;
@@ -90,7 +127,7 @@ fn void TempAllocator.reset(&self)
{
TempAllocator* old = child;
child = old.derived;
old.destroy();
temp_allocator_destroy(old);
}
self.capacity = self.original_capacity;
$if env::ADDRESS_SANITIZER:
@@ -105,17 +142,17 @@ fn void TempAllocator.reset(&self)
fn void TempAllocator.free(&self)
{
self.reset();
self.destroy();
temp_allocator_destroy(self);
}
fn void TempAllocator.destroy(&self) @local
fn void temp_allocator_destroy(TempAllocator* self)
{
TempAllocatorPage *last_page = self.last_page;
while (last_page)
{
TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
_free_page(self, to_free)!!;
}
if (self.allocated)
{
@@ -142,33 +179,6 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
}
fn void? TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
}
fn void*? TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
// Walk backwards to find the pointer to this page.
TempAllocatorPage **pointer_to_prev = &self.last_page;
// Remove the page from the list
while (*pointer_to_prev != page)
{
pointer_to_prev = &((*pointer_to_prev).prev_page);
}
*pointer_to_prev = page.prev_page;
usz page_size = page.pagesize();
// Clear on size > original size.
void* data = self.acquire(size, NO_ZERO, alignment)!;
if (page_size > size) page_size = size;
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
self.backing_allocator.release(real_pointer, page.is_aligned());
return data;
}
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
{
@@ -178,7 +188,7 @@ fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d
assert(self.last_page, "Realloc of non temp pointer");
// First grab the page
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
return self._realloc_page(page, size, alignment);
return _realloc_page(self, page, size, alignment);
}
bool is_realloc_of_last = chunk.size + pointer == &self.data[self.used];
if (is_realloc_of_last)
@@ -289,3 +299,111 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
return &page.data[0];
}
fn void? _free_page(TempAllocator* self, TempAllocatorPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
}
fn void*? _realloc_page(TempAllocator* self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
// Walk backwards to find the pointer to this page.
TempAllocatorPage **pointer_to_prev = &self.last_page;
// Remove the page from the list
while (*pointer_to_prev != page)
{
pointer_to_prev = &((*pointer_to_prev).prev_page);
}
*pointer_to_prev = page.prev_page;
usz page_size = page.pagesize();
// Clear on size > original size.
void* data = self.acquire(size, NO_ZERO, alignment)!;
if (page_size > size) page_size = size;
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
self.backing_allocator.release(real_pointer, page.is_aligned());
return data;
}
module std::core::mem::allocator @if((env::POSIX || env::WIN32) && $feature(VMEM_TEMP));
import std::math;
tlocal VmemOptions temp_allocator_default_options = {
.shrink_on_reset = env::MEMORY_ENV != NORMAL,
.protect_unused_pages = env::COMPILER_OPT_LEVEL <= O1 || env::COMPILER_SAFE_MODE,
.scratch_released_data = env::COMPILER_SAFE_MODE
};
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve = temp_allocator_reserve_size, usz min_size = temp_allocator_min_size, usz realloc_size = temp_allocator_realloc_size)
{
Vmem mem;
TempAllocator* t = allocator::new(allocator, TempAllocator);
defer catch allocator::free(allocator, t);
t.vmem.init(preferred_size: isz.sizeof > 4 ? 4 * mem::GB : 512 * mem::MB,
reserve_page_size: isz.sizeof > 4 ? 256 * mem::KB : 0,
options: temp_allocator_default_options)!;
t.allocator = allocator;
return t;
}
struct TempAllocator (Allocator)
{
Vmem vmem;
TempAllocator* derived;
Allocator allocator;
}
<*
@require size > 0
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
*>
fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
return self.vmem.acquire(size, init_type, alignment) @inline;
}
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0)
{
if (self.derived) return self.derived;
return self.derived = new_temp_allocator(self.allocator, 0)!;
}
<*
Reset the entire temp allocator, destroying all children
*>
fn void TempAllocator.reset(&self)
{
TempAllocator* child = self.derived;
if (!child) return;
child.reset();
child.vmem.reset(0);
}
fn void TempAllocator.free(&self)
{
_destroy(self);
}
fn void _destroy(TempAllocator* self) @local
{
TempAllocator* child = self.derived;
if (!child) return;
child.destroy();
self.vmem.free() @inline;
allocator::free(self.allocator, self) @inline;
}
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
{
return self.vmem.resize(pointer, size, alignment) @inline;
}
fn void TempAllocator.release(&self, void* old_pointer, bool b) @dynamic
{
self.vmem.release(old_pointer, b) @inline;
}

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,12 +18,18 @@ 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 embarrassingly single-threaded, so
// do not use it to track allocations that cross threads.
struct TrackingAllocator (Allocator)
{
Allocator inner_allocator;
AllocMap map;
usz mem_total;
usz allocs_total;
usz usage;
usz max_usage;
}
<*
@@ -66,6 +72,11 @@ fn usz TrackingAllocator.total_allocated(&self) => self.mem_total;
*>
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
<*
@return "the maximum amount of memory allocated"
*>
fn usz TrackingAllocator.max_allocated(&self) => self.max_usage;
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
{
return self.map.tvalues();
@@ -84,27 +95,34 @@ fn void*? TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, us
backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
self.usage += size;
if (self.usage > self.max_usage) self.max_usage = self.usage;
return data;
}
fn void*? TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
void* data = self.inner_allocator.resize(old_pointer, size, alignment)!;
self.usage -= self.map[(uptr)old_pointer]!!.size;
self.map.remove((uptr)old_pointer);
void*[MAX_BACKTRACE] bt;
backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
self.usage += size;
if (self.usage > self.max_usage) self.max_usage = self.usage;
self.allocs_total++;
return data;
}
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
{
usz? old_size = self.map[(uptr)old_pointer].size;
if (catch self.map.remove((uptr)old_pointer))
{
unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
}
self.usage -= old_size!!;
self.inner_allocator.release(old_pointer, is_aligned);
}
@@ -173,6 +191,7 @@ fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
io::fprintfn(out, "- Total current allocations: %d", entries)!;
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
io::fprintfn(out, "- Maximum memory usage: %d", self.max_usage)!;
if (leaks)
{
io::fprintn(out)!;
@@ -212,4 +231,4 @@ fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
}
}
}
}
}

View File

@@ -0,0 +1,255 @@
module std::core::mem::allocator @if(env::POSIX || env::WIN32);
import std::math, std::os::posix, libc, std::bits;
import std::core::mem;
import std::core::env;
// Virtual Memory allocator
faultdef VMEM_RESERVE_FAILED;
struct Vmem (Allocator)
{
VirtualMemory memory;
usz allocated;
usz pagesize;
usz page_pot;
usz last_page;
usz high_water;
VmemOptions options;
}
bitstruct VmemOptions : int
{
<* Release memory on reset *>
bool shrink_on_reset;
<* Protect unused pages on reset *>
bool protect_unused_pages;
<* Overwrite released data with 0xAA *>
bool scratch_released_data;
}
<*
Implements the Allocator interface method.
@require !reserve_page_size || math::is_power_of_2(reserve_page_size)
@require reserve_page_size <= preferred_size : "The min reserve_page_size size must be less or equal to the preferred size"
@require preferred_size >= 1 * mem::KB : "The preferred size must exceed 1 KB"
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY, VMEM_RESERVE_FAILED
*>
fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOptions options = { true, true, env::COMPILER_SAFE_MODE }, usz min_size = 0)
{
static usz page_size = 0;
if (!page_size) page_size = mem::os_pagesize();
if (page_size < reserve_page_size) page_size = reserve_page_size;
preferred_size = mem::aligned_offset(preferred_size, page_size);
if (!min_size) min_size = max(preferred_size / 1024, 1);
VirtualMemory? memory = mem::OUT_OF_MEMORY~;
while (preferred_size >= min_size)
{
memory = vm::virtual_alloc(preferred_size, PROTECTED);
// It worked?
if (try memory) break;
switch (@catch(memory))
{
case mem::OUT_OF_MEMORY:
case vm::RANGE_OVERFLOW:
// Try a smaller size.
preferred_size /= 2;
continue;
default:
break;
}
}
if (catch memory) return VMEM_RESERVE_FAILED~;
if (page_size > preferred_size) page_size = preferred_size;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(memory.ptr, memory.size);
$endif
*self = { .memory = memory,
.high_water = 0,
.pagesize = page_size,
.page_pot = page_size.ctz(),
.options = options,
};
}
<*
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
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? Vmem.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
usz total_len = self.memory.size;
if (size > total_len) return mem::INVALID_ALLOC_SIZE~;
void* start_mem = self.memory.ptr;
void* unaligned_pointer_to_offset = start_mem + self.allocated + VmemHeader.sizeof;
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz after = (usz)(mem - start_mem) + size;
if (after > total_len) return mem::OUT_OF_MEMORY~;
if (init_type == ZERO && self.high_water <= self.allocated)
{
init_type = NO_ZERO;
}
protect(self, after)!;
VmemHeader* header = mem - VmemHeader.sizeof;
header.size = size;
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
fn bool Vmem.owns_pointer(&self, void* ptr) @inline
{
return (uptr)ptr >= (uptr)self.memory.ptr && (uptr)ptr < (uptr)self.memory.ptr + self.memory.size;
}
<*
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
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
{
if (size > self.memory.size) return mem::INVALID_ALLOC_SIZE~;
alignment = alignment_for_allocation(alignment);
assert(self.owns_pointer(old_pointer), "Pointer originates from a different allocator: %p, not in %p - %p", old_pointer, self.memory.ptr, self.memory.ptr + self.allocated);
VmemHeader* header = old_pointer - VmemHeader.sizeof;
usz old_size = header.size;
if (old_size == size) return old_pointer;
// Do last allocation and alignment match?
if (self.memory.ptr + self.allocated == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
{
if (old_size > size)
{
unprotect(self, self.allocated + size - old_size);
}
else
{
usz allocated = self.allocated + size - old_size;
if (allocated > self.memory.size) return mem::OUT_OF_MEMORY~;
protect(self, allocated)!;
}
header.size = size;
return old_pointer;
}
if (old_size > size)
{
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(old_pointer + size, old_size - size);
$endif
header.size = size;
return old_pointer;
}
// Otherwise just allocate new memory.
void* mem = self.acquire(size, NO_ZERO, alignment)!;
assert(size > old_size);
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
<*
Implements the Allocator interface method.
@require ptr != null
*>
fn void Vmem.release(&self, void* ptr, bool) @dynamic
{
assert(self.owns_pointer(ptr), "Pointer originates from a different allocator %p.", ptr);
VmemHeader* header = ptr - VmemHeader.sizeof;
// Reclaim memory if it's the last element.
if (ptr + header.size == self.memory.ptr + self.allocated)
{
unprotect(self, self.allocated - header.size - VmemHeader.sizeof);
}
}
fn usz Vmem.mark(&self)
{
return self.allocated;
}
<*
@require mark <= self.allocated : "Invalid mark"
*>
fn void Vmem.reset(&self, usz mark)
{
if (mark == self.allocated) return;
unprotect(self, mark);
}
fn void Vmem.free(&self)
{
if (!self.memory.ptr) return;
$switch:
$case env::ADDRESS_SANITIZER:
asan::poison_memory_region(self.memory.ptr, self.memory.size);
$case env::COMPILER_SAFE_MODE:
((char*)self.memory.ptr)[0:self.allocated] = 0xAA;
$endswitch
(void)self.memory.destroy();
*self = {};
}
// Internal data
struct VmemHeader @local
{
usz size;
char[*] data;
}
macro void? protect(Vmem* mem, usz after) @local
{
usz shift = mem.page_pot;
usz page_after = (after + mem.pagesize - 1) >> shift;
usz last_page = mem.last_page;
bool over_high_water = mem.high_water < after;
if (page_after > last_page)
{
usz page_start = last_page << shift;
usz page_len = (page_after - last_page) << shift;
mem.memory.commit(page_start, page_len)!;
if (mem.options.protect_unused_pages || over_high_water)
{
mem.memory.protect(page_start, page_len, READWRITE)!;
}
mem.last_page = page_after;
}
$if env::ADDRESS_SANITIZER:
asan::unpoison_memory_region(mem.memory.ptr + mem.allocated, after - mem.allocated);
$endif
mem.allocated = after;
if (over_high_water) mem.high_water = after;
}
macro void unprotect(Vmem* mem, usz after) @local
{
usz shift = mem.page_pot;
usz last_page = mem.last_page;
usz page_after = mem.last_page = (after + mem.pagesize - 1) >> shift;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(mem.memory.ptr + after, mem.allocated - after);
$else
if (mem.options.scratch_released_data)
{
mem::set(mem.memory.ptr + after, 0xAA, mem.allocated - after);
}
$endif
if ((mem.options.shrink_on_reset || mem.options.protect_unused_pages) && page_after < last_page)
{
usz start = page_after << shift;
usz len = (last_page - page_after) << shift;
if (mem.options.shrink_on_reset) (void)mem.memory.decommit(start, len, false);
if (mem.options.protect_unused_pages) (void)mem.memory.protect(start, len, PROTECTED);
}
mem.allocated = after;
}

165
lib/std/core/ansi.c3 Normal file
View File

@@ -0,0 +1,165 @@
module std::core::string::ansi;
import std::io;
constdef Ansi : inline String
{
RESET = "\e[0m",
BOLD = "\e[1m",
DIM = "\e[2m",
ITALIC = "\e[3m",
UNDERLINE = "\e[4m",
BLINK = "\e[5m",
BLINK_FAST = "\e[6m",
INVERT = "\e[7m",
HIDDEN = "\e[8m",
STRIKETHROUGH = "\e[9m",
DOUBLE_UNDER = "\e[21m",
NO_DIM = "\e[22m",
NO_ITALIC = "\e[23m",
NO_UNDERLINE = "\e[24m",
NO_BLINK = "\e[25m",
NO_INVERT = "\e[27m",
NO_HIDDEN = "\e[28m",
NO_STRIKETHROUGH = "\e[29m",
BLACK = "\e[30m",
RED = "\e[31m",
GREEN = "\e[32m",
YELLOW = "\e[33m",
BLUE = "\e[34m",
MAGENTA = "\e[35m",
CYAN = "\e[36m",
WHITE = "\e[37m",
DEFAULT = "\e[39m",
BRIGHT_BLACK = "\e[90m",
BRIGHT_RED = "\e[91m",
BRIGHT_GREEN = "\e[92m",
BRIGHT_YELLOW = "\e[93m",
BRIGHT_BLUE = "\e[94m",
BRIGHT_MAGENTA = "\e[95m",
BRIGHT_CYAN = "\e[96m",
BRIGHT_WHITE = "\e[97m",
BG_BLACK = "\e[40m",
BG_RED = "\e[41m",
BG_GREEN = "\e[42m",
BG_YELLOW = "\e[43m",
BG_BLUE = "\e[44m",
BG_MAGENTA = "\e[45m",
BG_CYAN = "\e[46m",
BG_WHITE = "\e[47m",
BG_DEFAULT = "\e[49m",
BG_BRIGHT_BLACK = "\e[100m",
BG_BRIGHT_RED = "\e[101m",
BG_BRIGHT_GREEN = "\e[102m",
BG_BRIGHT_YELLOW = "\e[103m",
BG_BRIGHT_BLUE = "\e[104m",
BG_BRIGHT_MAGENTA = "\e[105m",
BG_BRIGHT_CYAN = "\e[106m",
BG_BRIGHT_WHITE = "\e[107m",
}
struct AnsiColor (Printable)
{
char r, g, b;
bool bg;
}
fn usz? AnsiColor.to_format(&self, Formatter* fmt) @dynamic
{
return fmt.printf("\e[%s8;2;%s;%s;%sm", self.bg ? 4 : 3, self.r, self.g, self.b);
}
<*
24-bit color code
@return `A struct that, when formatted with '%s', sets the foreground or background colour to the specified rgb value`
*>
fn AnsiColor get_color_rgb(char r, char g, char b, bool bg = false)
{
return {r, g, b, bg};
}
<*
24-bit color code
@return `A struct that, when formatted with '%s', sets the foreground or background colour of printed text to the specified rgb value`
*>
fn AnsiColor get_color(uint rgb, bool bg = false)
{
return {(char)(rgb >> 16), (char)((rgb & 0x00FF00) >> 8), (char)rgb, bg};
}
<*
8-bit color code
@return `the formatting char for the given background color`
*>
macro String color_8bit(char $index, bool $bg = false) @const
{
int $mode = $bg ? 4 : 3;
return @sprintf("\e[%s8;5;%sm", $mode, $index);
}
<*
24-bit color code
@return `the string for the given foreground color`
*>
macro String color_rgb(char $r, char $g, char $b, bool $bg = false) @const
{
int $mode = $bg ? 4 : 3;
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $r, $g, $b);
}
<*
24-bit color code rgb
@require $rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
@return `the string char for the given foreground color`
*>
macro String color(uint $rgb, bool $bg = false) @const
{
int $mode = $bg ? 4 : 3;
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $rgb >> 16, ($rgb & 0xFF00) >> 8, $rgb & 0xFF);
}
<*
24-bit color code rgb
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
@return `the string char for the given foreground color`
*>
fn String make_color(Allocator mem, uint rgb, bool bg = false) @deprecated("use get_color instead")
{
return make_color_rgb(mem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
}
<*
24-bit color code rgb
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
@return `the string char for the given foreground color`
*>
fn String make_tcolor(uint rgb, bool bg = false) @deprecated("use get_color instead")
{
return make_color_rgb(tmem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
}
<*
24-bit color code rgb
@return `the string char for the given foreground color`
*>
fn String make_color_rgb(Allocator mem, char r, char g, char b, bool bg = false) @deprecated("use get_color_rgb instead")
{
return string::format(mem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
}
<*
24-bit color code rgb
@return `the string char for the given foreground color`
*>
fn String make_tcolor_rgb(char r, char g, char b, bool bg = false) @deprecated("use get_color_rgb instead")
{
return string::format(tmem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
}

View File

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

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

@@ -0,0 +1,156 @@
<*
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' };
typedef AsciiCharset = uint128;
macro AsciiCharset @create_set(String $string) @const
{
AsciiCharset $set;
$foreach $c : $string:
$set |= 1ULL << $c;
$endforeach
return $set;
}
fn AsciiCharset create_set(String string)
{
AsciiCharset set;
foreach (c : string) set |= (AsciiCharset)1ULL << c;
return set;
}
macro bool AsciiCharset.@contains($set, char $c) @const => !!($c < 128) & !!($set & (AsciiCharset)(1ULL << $c));
macro AsciiCharset @combine_sets(AsciiCharset $first, AsciiCharset... $sets) @const
{
var $res = $first;
$foreach $c : $sets:
$res |= $c;
$endforeach
return $res;
}
fn AsciiCharset combine_sets(AsciiCharset first, AsciiCharset... sets)
{
foreach (c : sets) first |= c;
return first;
}
macro bool AsciiCharset.contains(set, char c) => !!(c < 128) & !!(set & (AsciiCharset)(1ULL << c));
const AsciiCharset WHITESPACE_SET = @create_set("\t\n\v\f\r ");
const AsciiCharset NUMBER_SET = @create_set("0123456789");
const AsciiCharset ALPHA_UPPER_SET = @create_set("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
const AsciiCharset ALPHA_LOWER_SET = @create_set("abcdefghijklmnopqrstuvwxyz");
const AsciiCharset ALPHA_SET = @combine_sets(ALPHA_UPPER_SET, ALPHA_LOWER_SET);
const AsciiCharset ALPHANUMERIC_SET = @combine_sets(ALPHA_SET, NUMBER_SET);

View File

@@ -1,8 +1,8 @@
// 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;
import std::bits;
// This module contains types of different endianness.
// *BE types represent big-endian types
// *LE types represent little-endian types.
@@ -88,93 +88,149 @@ bitstruct UInt128LE : uint128 @littleendian
}
<*
@require is_array_or_slice_of_char(bytes) : "argument must be an array, a pointer to an array or a slice of char"
@require is_bitorder($Type) : "type must be a bitorder integer"
@require $defined(*bytes) : "Pointer must be possible to dereference"
@require types::is_intlike($typeof(*bytes)) : "Type must be an integer or int vector"
*>
macro read(bytes, $Type)
macro load_be(bytes)
{
char[] s;
$switch @typekind(bytes):
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
s = bytes[:$Type.sizeof];
$endswitch
return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
$if env::BIG_ENDIAN:
return mem::load(bytes, $align: 1);
$else
return bswap(mem::load(bytes, $align: 1));
$endif
}
<*
@require is_arrayptr_or_slice_of_char(bytes) : "argument must be a pointer to an array or a slice of char"
@require $defined(*bytes) : "Pointer must be possible to dereference"
@require types::is_intlike($typeof(*bytes)) : "Type must be an integer or int vector"
*>
macro load_le(bytes)
{
$if env::BIG_ENDIAN:
return bswap(mem::load(bytes, $align: 1));
$else
return mem::load(bytes, $align: 1);
$endif
}
<*
@require types::is_intlike($typeof(value)) : "Type must be an integer or int vector"
*>
macro void store_be(void* dst, value)
{
$if env::BIG_ENDIAN:
mem::store(($typeof(value)*)dst, value, $align: 1);
$else
mem::store(($typeof(value)*)dst, bswap(value), $align: 1);
$endif
}
<*
@require types::is_intlike($typeof(value)) : "Type must be an integer or int vector"
*>
macro void store_le(void* dst, value)
{
$if env::BIG_ENDIAN:
mem::store(($typeof(value)*)dst, bswap(value), $align: 1);
$else
mem::store(($typeof(value)*)dst, value, $align: 1);
$endif
}
<*
@require @is_array_or_slice_of_char(bytes) : "argument must be an array, a pointer to an array or a slice of char"
@require is_bitorder($Type) : "type must be a bitorder integer"
@require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is too short to contain value"
*>
macro read(bytes, $Type)
{
char *ptr;
$switch $kindof(bytes):
$case POINTER:
ptr = bytes;
$default:
ptr = bytes[..].ptr;
$endswitch
return bitcast(mem::load((char[$Type.sizeof]*)ptr, $align: 1), $Type).val;
}
<*
@require @is_arrayptr_or_slice_of_char(bytes) : "argument must be a pointer to an array or a slice of char"
@require is_bitorder($Type) : "type must be a bitorder integer"
@require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is not sufficent to hold value"
*>
macro write(x, bytes, $Type)
{
char[] s;
$switch @typekind(bytes):
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
s = bytes[:$Type.sizeof];
char *ptr;
$switch $kindof(bytes):
$case POINTER:
ptr = bytes;
$default:
ptr = bytes[..].ptr;
$endswitch
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
mem::store(($typeof(x)*)ptr, bitcast(x, $Type).val, 1);
}
macro is_bitorder($Type)
macro bool is_bitorder($Type)
{
$switch $Type:
$case UShortLE:
$case ShortLE:
$case UIntLE:
$case IntLE:
$case ULongLE:
$case LongLE:
$case UInt128LE:
$case Int128LE:
$case UShortBE:
$case ShortBE:
$case UIntBE:
$case IntBE:
$case ULongBE:
$case LongBE:
$case UInt128BE:
$case Int128BE:
return true;
$default:
return false;
$case UShortLE:
$case ShortLE:
$case UIntLE:
$case IntLE:
$case ULongLE:
$case LongLE:
$case UInt128LE:
$case Int128LE:
$case UShortBE:
$case ShortBE:
$case UIntBE:
$case IntBE:
$case ULongBE:
$case LongBE:
$case UInt128BE:
$case Int128BE:
return true;
$default:
return false;
$endswitch
}
macro bool is_array_or_slice_of_char(bytes)
macro bool is_array_or_slice_of_char(bytes) @deprecated("Use @is_array_or_slice_of_char")
{
$switch @typekind(bytes):
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:
var $Inner2 = $typefrom($Inner.inner);
return $Inner2.typeid == char.typeid;
$endif
$case ARRAY:
$case SLICE:
var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid;
$default:
return false;
return @is_array_or_slice_of_char(bytes);
}
macro bool @is_array_or_slice_of_char(#bytes) @const
{
var $Type = $typeof(#bytes);
$switch $Type.kindof:
$case POINTER:
typeid $inner = $Type.inner;
return $inner.kindof == ARRAY &&& $inner.inner == char.typeid;
$case ARRAY:
$case SLICE:
return $Type.inner == char.typeid;
$default:
return false;
$endswitch
}
macro bool is_arrayptr_or_slice_of_char(bytes)
macro bool is_arrayptr_or_slice_of_char(bytes) @deprecated("Use @is_arrayptr_or_slice_of_char")
{
$switch @typekind(bytes):
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:
var $Inner2 = $typefrom($Inner.inner);
return $Inner2.typeid == char.typeid;
$endif
$case SLICE:
var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid;
$default:
return false;
return @is_arrayptr_or_slice_of_char(bytes);
}
macro bool @is_arrayptr_or_slice_of_char(#bytes) @const
{
var $Type = $typeof(#bytes);
$switch $Type.kindof:
$case POINTER:
typeid $inner = $Type.inner;
return $inner.kindof == ARRAY &&& $inner.inner == char.typeid;
$case SLICE:
return $Type.inner == char.typeid;
$default:
return false;
$endswitch
}
}

View File

@@ -7,7 +7,7 @@ import libc, std::hash, std::io, std::os::backtrace;
<*
EMPTY_MACRO_SLOT is a value used for implementing optional arguments for macros in an efficient
way. It relies on the fact that distinct types are not implicitly convertable.
way. It relies on the fact that distinct types are not implicitly convertible.
You can use `@is_empty_macro_slot()` and `@is_valid_macro_slot()` to figure out whether
the argument has been used or not.
@@ -18,33 +18,53 @@ import libc, std::hash, std::io, std::os::backtrace;
macro foo(a, #b = EMPTY_MACRO_SLOT)
{
$if @is_valid_macro_slot(#b):
return invoke_foo2(a, #b);
$else
return invoke_foo1(a);
$endif
return invoke_foo2(a, #b);
$else
return invoke_foo1(a);
$endif
}
*>
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
const EmptySlot EMPTY_MACRO_SLOT @builtin @deprecated("Use `#arg = ...` instead.") = null;
typedef EmptySlot = void*;
macro @is_empty_macro_slot(#arg) @const @builtin => @typeis(#arg, EmptySlot);
macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
typedef EmptySlot @constinit = void*;
macro bool @is_empty_macro_slot(#arg) @const @builtin
@deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.")
=> $typeof(#arg) == EmptySlot;
macro bool @is_valid_macro_slot(#arg) @const @builtin
@deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.")
=> $typeof(#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.
Use `NO_MORE_ELEMENT` when reading the end of an iterator, or accessing a result out of bounds.
*/
faultdef NO_MORE_ELEMENT @builtin;
/*
Use `SearchResult` when trying to return a value from some collection but the element is missing.
Use `NOT_FOUND` when trying to return a value from some collection but the element is missing.
*/
faultdef NOT_FOUND @builtin;
/*
Use `CastResult` when an attempt at conversion fails.
Use `TYPE_MISMATCH` when an attempt at conversion fails.
*/
faultdef TYPE_MISMATCH @builtin;
/*
Use `CAPACITY_EXCEEDED` when trying to add to a bounded list or similar.
*/
faultdef CAPACITY_EXCEEDED @builtin;
/*
Use `NOT_IMPLEMENTED` when something is conditionally available.
*/
faultdef NOT_IMPLEMENTED @builtin;
alias VoidFn = fn void();
@@ -53,7 +73,7 @@ alias VoidFn = fn void();
macro scope.
@param #variable : `the variable to store and restore`
@require values::@is_lvalue(#variable)
@require $defined(#variable = #variable) : `Expected an actual variable`
*>
macro void @scope(#variable; @body) @builtin
{
@@ -73,94 +93,160 @@ macro void @swap(#a, #b) @builtin
#b = temp;
}
macro usz bitsizeof($Type) @builtin @const => $Type.sizeof * 8u;
macro usz @bitsizeof(#expr) @builtin @const => $sizeof(#expr) * 8u;
<*
Compile-time check for whether a set of constants contains a certain expression.
@param #needle : "The expression whose value should be located."
*>
macro bool @in(#needle, ...) @builtin @const
{
$for var $x = 0; $x < $vacount; $x++:
$assert $defined(#needle == $vaconst[$x])
: "Index %s: types '%s' (needle) and '%s' are not equatable", $x, $typeof(#needle), $typeof($vaconst[$x]);
$if #needle == $vaconst[$x]: return true; $endif
$endfor
return false;
}
<*
Convert an `any` type to a type, returning an failure if there is a type mismatch.
@param v : `the any to convert to the given type.`
@param $Type : `the type to convert to`
@return `The any.ptr converted to its type.`
@ensure @typeis(return, $Type*)
@ensure $typeof(return) == $Type*
@return? TYPE_MISMATCH
*>
macro anycast(any v, $Type) @builtin
{
if (v.type != $Type.typeid) return TYPE_MISMATCH?;
if (v.type != $Type.typeid) return TYPE_MISMATCH~;
return ($Type*)v.ptr;
}
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
<*
@return "The value in the pointer"
@return? TYPE_MISMATCH
*>
macro any.to(self, $Type)
{
if (self.type != $Type.typeid) return TYPE_MISMATCH~;
return *($Type*)self.ptr;
}
<*
@require self.type == $Type : "The 'any' contained an unexpected type."
@return "The value in the pointer"
*>
macro any.as(self, $Type)
{
return *($Type*)self.ptr;
}
macro bool @assignable_to(#foo, $Type) @const @builtin @deprecated("use '$defined($Type x = #foo)'") => $defined(*&&($Type){} = #foo);
macro @addr(#val) @builtin
{
$if $defined(&#val):
return &#val;
$else
return &&#val;
$endif
}
macro typeid @typeid(#value) @const @builtin
{
return $typeof(#value).typeid;
}
macro TypeKind @typekind(#value) @const @builtin @deprecated("Use `$kindof(#value)`.")
{
return $kindof(#value);
}
macro bool @typeis(#value, $Type) @const @builtin @deprecated("Use `$typeof(#value) == $Type` instead.")
{
return $typeof(#value).typeid == $Type.typeid;
}
fn bool print_backtrace(String message, int backtraces_to_ignore, void *added_backtrace = null) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
{
Allocator t = allocator::current_temp;
TempAllocator* new_t = allocator::new_temp_allocator(smem, 0x1000)!!;
allocator::current_temp = new_t;
defer
{
allocator::current_temp = t;
new_t.free();
}
void*[256] buffer;
void*[] backtraces = backtrace::capture_current(&buffer);
backtraces_to_ignore++;
BacktraceList? backtrace = backtrace::symbolize_backtrace(tmem, backtraces);
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
if (added_backtrace)
{
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
{
io::eprintfn(" in ???%s", inline_suffix);
continue;
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
backtraces[++backtraces_to_ignore] = added_backtrace;
}
@stack_mem(4096; Allocator mem)
{
BacktraceList? backtrace = backtrace::symbolize_backtrace(mem, backtraces);
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
{
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
{
io::eprintfn(" in ???%s", inline_suffix);
continue;
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
}
};
return true;
}
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
{
$if $defined(io::stderr):
if (!print_backtrace(message, 2))
{
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
}
in_panic = true;
$if $defined(io::stderr) && env::PANIC_MSG:
if (!print_backtrace(message, 2))
{
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
}
$endif
$$trap();
}
macro void abort(String string = "Unrecoverable error reached", ...) @builtin @noreturn
macro void abort(String string = "Unrecoverable error reached", ...) @format(0) @builtin @noreturn
{
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
$$trap();
}
bool in_panic @local = false;
bool in_panic @private = false;
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
fn void default_panic(String message, String file, String function, uint line) @if (!env::NATIVE_STACKTRACE)
{
if (in_panic)
{
io::eprintn("Panic inside of panic.");
return;
}
in_panic = true;
$if $defined(io::stderr):
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintfn("', in %s (%s:%d)", function, file, line);
$if $defined(io::stderr) && env::PANIC_MSG:
if (in_panic)
{
io::eprintn("Panic inside of panic.");
return;
}
in_panic = true;
$if $defined(io::stderr):
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintfn("', in %s (%s:%d)", function, file, line);
$endif
in_panic = false;
$endif
in_panic = false;
$$trap();
}
@@ -170,21 +256,23 @@ PanicFn panic = &default_panic;
fn void panicf(String fmt, String file, String function, uint line, args...)
{
if (in_panic)
{
io::eprint("Panic inside of panic: ");
io::eprintn(fmt);
return;
}
in_panic = true;
@stack_mem(512; Allocator allocator)
{
DString s;
s.init(allocator);
s.appendf(fmt, ...args);
in_panic = false;
panic(s.str_view(), file, function, line);
};
$if $defined(io::stderr) && env::PANIC_MSG:
if (in_panic)
{
io::eprint("Panic inside of panic: ");
io::eprintn(fmt);
return;
}
in_panic = true;
@stack_mem(512; Allocator allocator)
{
DString s;
s.init(allocator);
s.appendf(fmt, ...args);
in_panic = false;
panic(s.str_view(), file, function, line);
};
$endif
}
<*
@@ -196,8 +284,9 @@ macro void unreachable(String string = "Unreachable statement reached.", ...) @b
{
$if env::COMPILER_SAFE_MODE:
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
$endif;
$else
$$unreachable();
$endif
}
<*
@@ -228,6 +317,9 @@ macro any.retype_to(&self, typeid type)
return $$any_make(self.ptr, type);
}
<*
@require (bool)self.type : "A typed any was expected"
*>
macro any.as_inner(&self)
{
return $$any_make(self.ptr, self.type.inner);
@@ -238,7 +330,7 @@ macro any.as_inner(&self)
@param $Type : "the type to cast to"
@require $sizeof(expr) == $Type.sizeof : "Cannot bitcast between types of different size."
@ensure @typeis(return, $Type)
@ensure $typeof(return) == $Type*
*>
macro bitcast(expr, $Type) @builtin
{
@@ -255,7 +347,7 @@ macro bitcast(expr, $Type) @builtin
@param $Type : `The type of the enum`
@param [in] enum_name : `The name of the enum to search for`
@require $Type.kindof == ENUM : `Only enums may be used`
@ensure @typeis(return, $Type)
@ensure $typeof(return) == $Type*
@return? NOT_FOUND
*>
macro enum_by_name($Type, String enum_name) @builtin
@@ -265,25 +357,24 @@ macro enum_by_name($Type, String enum_name) @builtin
{
if (name == enum_name) return $Type.from_ordinal(i);
}
return NOT_FOUND?;
return NOT_FOUND~;
}
<*
@param $Type : `The type of the enum`
@require $Type.kindof == ENUM : `Only enums may be used`
@require $defined($Type.#value) : `Expected '#value' to match an enum associated value`
@require $assignable(value, $typeof(($Type){}.#value)) : `Expected the value to match the type of the associated value`
@ensure @typeis(return, $Type)
@require $defined($typeof(($Type){}.#value) v = value) : `Expected the value to match the type of the associated value`
@ensure $typeof(return) == $Type*
@return? NOT_FOUND
*>
macro @enum_from_value($Type, #value, value) @builtin
macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.lookup_field and Enum.lookup")
{
usz elements = $Type.elements;
foreach (e : $Type.values)
{
if (e.#value == value) return e;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
<*
@@ -326,7 +417,7 @@ macro bool @unlikely(bool #value, $probability = 1.0) @builtin
<*
@require values::@is_int(#value) || values::@is_bool(#value)
@require $assignable(expected, $typeof(#value))
@require $defined($typeof(#value) v = expected)
@require $probability >= 0 && $probability <= 1.0
*>
macro @expect(#value, expected, $probability = 1.0) @builtin
@@ -367,20 +458,62 @@ macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write =
$endif
}
<*
Shuffle a vector by its index
int[<4>] a = { 1, 2, 3, 4 };
assert(swizzle(a, 0, 1, 1, 3) == (int[<4>]) { 1, 2, 2, 4 });
*>
macro swizzle(v, ...) @builtin
{
return $$swizzle(v, $vasplat);
}
<*
Shuffle two vectors by a common index from arranging the vectors sequentially in memory
int[<4>] a = { 1, 2, 3, 4 };
int[<4>] b = { 100, 1000, 10000, 100000 };
assert(swizzle2(a, b, 0, 1, 4, 6, 2) == (int[<5>]) { 1, 2, 100, 10000, 3 });
*>
macro swizzle2(v, v2, ...) @builtin
{
return $$swizzle2(v, v2, $vasplat);
}
<*
Returns the count of leading zero bits from an integer at compile-time.
@require types::is_int($typeof($value)) : "Input value must be an integer"
@require $sizeof($value) * 8 <= 128 : "Input value must be 128 bits wide or lower"
*>
macro uint @clz($value) @builtin @const
{
$if $value == 0:
return $sizeof($value) * 8; // it's all leading zeroes
$endif
usz $n = 0;
uint128 $x = (uint128)$value;
$if $x <= 0x0000_0000_0000_0000_FFFF_FFFF_FFFF_FFFF: $n += 64; $x <<= 64; $endif
$if $x <= 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 32; $x <<= 32; $endif
$if $x <= 0x0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 16; $x <<= 16; $endif
$if $x <= 0x00FF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 8; $x <<= 8; $endif
$if $x <= 0x0FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 4; $x <<= 4; $endif
$if $x <= 0x3FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 2; $x <<= 2; $endif
$if $x <= 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 1; $endif
return $n % ($sizeof($value) * 8); // mod by the bitsize of the input value to go back from uint128 -> it's-type
}
<*
Return the excuse in the Optional if it is Empty, otherwise
return a null fault.
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
@require $kindof(#expr) == OPTIONAL : `@catch expects an Optional value`
*>
macro fault @catch(#expr) @builtin
{
@@ -392,7 +525,7 @@ macro fault @catch(#expr) @builtin
Check if an Optional expression holds a value or is empty, returning true
if it has a value.
@require @typekind(#expr) == OPTIONAL : `@ok expects an Optional value`
@require $kindof(#expr) == OPTIONAL : `@ok expects an Optional value`
*>
macro bool @ok(#expr) @builtin
{
@@ -400,6 +533,67 @@ macro bool @ok(#expr) @builtin
return true;
}
<*
Check if an Optional expression evaluates to a fault. If so, return it;
else, assign the result to an expression.
@require $defined(#v = #v) : "#v must be a variable"
@require $defined(#expr!) : "Expected an optional expression"
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
*>
macro void? @try(#v, #expr) @builtin @maydiscard
{
var res = #expr;
if (catch err = res) return err~;
#v = res;
}
<*
Check if an Optional expression evaluates to a fault. If so, return true if it is the
expected fault, the optional if it is unexpected, or false if there was no fault and
the assign happened.
This can be used in like this:
while (true)
{
char[] data;
// Read until end of file
if (@try_catch(data, load_line(), io::EOF)!) break;
.. use data ..
}
In this example we read until we reach an EOF, which is expected. However, if we encounter some other
fault, we rethrow is. Without this macro, the code is instead written like:
while (true)
{
char[]? data;
data = load_line();
if (catch err = data)
{
if (err = io::EOF) break;
return err?
}
.. use data ..
}
@require $defined(#v = #v) : "#v must be a variable"
@require $defined(#expr!) : "Expected an optional expression"
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
@return "True if it was the expected fault, false if the variable was assigned, otherwise returns an optional."
*>
macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin
{
var res = #expr;
if (catch err = res)
{
return err == expected_fault ? true : err~;
}
#v = res;
return false;
}
<*
@require $defined(&#value, (char*)&#value) : "This must be a value that can be viewed as a char array"
*>
@@ -412,6 +606,27 @@ macro isz @str_find(String $string, String $needle) @builtin => $$str_find($stri
macro String @str_upper(String $str) @builtin => $$str_upper($str);
macro String @str_lower(String $str) @builtin => $$str_lower($str);
macro uint @str_hash(String $str) @builtin => $$str_hash($str);
macro String @str_pascalcase(String $str) @builtin => $$str_pascalcase($str);
macro String @str_snakecase(String $str) @builtin => $$str_snakecase($str);
macro String @str_camelcase(String $str) @builtin => @str_capitalize($$str_pascalcase($str));
macro String @str_constantcase(String $str) @builtin => @str_upper($$str_snakecase($str));
macro String @str_replace(String $str, String $pattern, String $replace, uint $limit = 0) @builtin => $$str_replace($str, $pattern, $replace, $limit);
macro String @str_capitalize(String $str) @builtin
{
$switch $str.len:
$case 0: return $str;
$case 1: return $$str_upper($str);
$default: return $$str_upper($str[0:1]) +++ $str[1..];
$endswitch
}
macro String @str_uncapitalize(String $str) @builtin
{
$switch $str.len:
$case 0: return $str;
$case 1: return $$str_lower($str);
$default: return $$str_lower($str[0:1]) +++ $str[1..];
$endswitch
}
macro @generic_hash_core(h, value)
{
@@ -421,7 +636,7 @@ macro @generic_hash_core(h, value)
return h;
}
macro @generic_hash(value)
macro uint @generic_hash(value)
{
uint h = @generic_hash_core((uint)0x3efd4391, value);
$for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4:
@@ -431,22 +646,75 @@ 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 String.hash(String c) => (uint)a5hash::hash(c);
macro uint char[].hash(char[] c) => (uint)a5hash::hash(c);
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
<*
@require $kindof(array_ptr) == POINTER &&& $kindof(*array_ptr) == ARRAY
*>
macro uint hash_array(array_ptr) @local
{
var $len = $sizeof(*array_ptr);
$if $len > 16:
return (uint)komi::hash(((char*)array_ptr)[:$len]);
$else
return (uint)wyhash2::hash(((char*)array_ptr)[:$len]);
$endif
}
<*
@require $kindof(vec) == VECTOR
*>
macro uint hash_vec(vec) @local
{
var $len = $sizeof(vec);
$if $len > 16:
return (uint)komi::hash(((char*)&&vec)[:$len]);
$else
return (uint)wyhash2::hash(((char*)&&vec)[:$len]);
$endif
}
const MAX_FRAMEADDRESS = 128;
<*
@@ -731,58 +999,74 @@ macro void* get_returnaddress(int n)
}
}
module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
import libc, std::io;
module std::core::builtin @if((env::LINUX || env::ANDROID || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
import libc, std::io, std::os::posix;
fn void sig_panic(String message)
{
default_panic(message, "???", "???", 0);
}
SignalFunction old_bus_error;
SignalFunction old_segmentation_fault;
fn void sig_bus_error(CInt i)
fn void sig_bus_error(CInt i, void* info, void* context)
{
$if !env::NATIVE_STACKTRACE:
sig_panic("Illegal memory access.");
$else
$if $defined(io::stderr):
if (!print_backtrace("Illegal memory access.", 1))
if (!print_backtrace("Illegal memory access.", 2, posix::stack_instruction(context)))
{
io::eprintn("\nERROR: 'Illegal memory access'.");
}
$endif
$endif
$$trap();
os::fastexit(128 + i);
}
fn void sig_segmentation_fault(CInt i)
fn void sig_segmentation_fault(CInt i, void* p1, void* context)
{
$if !env::NATIVE_STACKTRACE:
sig_panic("Out of bounds memory access.");
$else
$if $defined(io::stderr):
if (!print_backtrace("Out of bounds memory access.", 1))
if (!print_backtrace("Out of bounds memory access.", 2, posix::stack_instruction(context)))
{
io::eprintn("\nERROR: Memory error without backtrace, possible stack overflow.");
}
$endif
$endif
$$trap();
os::fastexit(128 + i);
}
fn void install_signal_handler(CInt signal, SignalFunction func) @local
fn void sig_illegal_instruction(CInt i, void* p1, void* context)
{
SignalFunction old = libc::signal(signal, func);
// Restore
if ((iptr)old > 1024) libc::signal(signal, old);
if (in_panic) os::fastexit(128 + i);
$if !env::NATIVE_STACKTRACE:
sig_panic("Illegal instruction.");
$else
$if $defined(io::stderr):
if (!print_backtrace("Illegal instruction.", 2, posix::stack_instruction(context)))
{
io::eprintn("\nERROR: Illegal instruction.");
}
$endif
$endif
os::fastexit(128 + i);
}
char[64 * 1024] sig_stack @local @if(env::BACKTRACE && env::LINUX);
// Clean this up
fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE)
{
install_signal_handler(libc::SIGBUS, &sig_bus_error);
install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
}
$if env::LINUX:
Stack_t ss = {
.ss_sp = &sig_stack,
.ss_size = sig_stack.len
};
libc::sigaltstack(&ss, null);
$endif
posix::install_signal_handler(libc::SIGBUS, &sig_bus_error);
posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
posix::install_signal_handler(libc::SIGILL, &sig_illegal_instruction);
}

View File

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

View File

@@ -2,6 +2,8 @@
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::cinterop;
import std::core::env;
const C_INT_SIZE = $$C_INT_SIZE;
const C_LONG_SIZE = $$C_LONG_SIZE;
@@ -59,3 +61,57 @@ macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
$default: $error("Invalid bitsize");
$endswitch
}
const USE_STACK_VALIST = env::ARCH_32_BIT || env::WIN32 || (env::DARWIN && env::AARCH64);
module std::core::cinterop @if(USE_STACK_VALIST);
typedef CVaList = void*;
macro CVaList.next(&self, $Type)
{
void *ptr = mem::aligned_pointer((void*)*self, max($Type.alignof, 8));
defer *self = (CVaList)(ptr + 1);
return *($Type*)ptr;
}
module std::core::cinterop @if(env::X86_64 && !env::WIN32);
struct CVaListData
{
uint gp_offset;
uint fp_offset;
void *overflow_arg_area;
void *reg_save_area;
}
typedef CVaList = CVaListData*;
macro CVaList.next(self, $Type)
{
CVaListData* data = (CVaListData*)self;
$switch:
$case $Type.kindof == FLOAT ||| ($Type.kindof == VECTOR && $Type.sizeof <= 16):
var $LoadType = $Type.sizeof < 8 ? double : $Type;
if (data.fp_offset < 6 * 8 + 8 * 16 )
{
defer data.fp_offset += (uint)mem::aligned_offset($Type.sizeof, 16);
return ($Type)*($LoadType*)(data.reg_save_area + data.fp_offset);
}
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
defer data.overflow_arg_area = ptr + $Type.sizeof;
return ($Type)*($LoadType*)ptr;
$case $Type.kindof == SIGNED_INT || $Type.kindof == UNSIGNED_INT:
var $LoadType = $Type.sizeof < 4 ? int : $Type;
if (data.gp_offset < 6 * 8 && $Type.sizeof <= 8)
{
defer data.gp_offset += (uint)mem::aligned_offset($Type.sizeof, 8);
return ($Type)*($LoadType*)(data.reg_save_area + data.gp_offset);
}
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
defer data.overflow_arg_area = ptr + $Type.sizeof;
return ($Type)*($LoadType*)ptr;
$default:
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
defer data.overflow_arg_area = ptr + $Type.sizeof;
return *($Type*)ptr;
$endswitch
}

View File

@@ -16,25 +16,25 @@ const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
*>
fn usz? char32_to_utf8(Char32 c, char[] output)
{
if (!output.len) return string::CONVERSION_FAILED?;
if (!output.len) return string::CONVERSION_FAILED~;
switch (true)
{
case c <= 0x7f:
output[0] = (char)c;
return 1;
case c <= 0x7ff:
if (output.len < 2) return string::CONVERSION_FAILED?;
if (output.len < 2) return string::CONVERSION_FAILED~;
output[0] = (char)(0xC0 | c >> 6);
output[1] = (char)(0x80 | (c & 0x3F));
return 2;
case c <= 0xffff:
if (output.len < 3) return string::CONVERSION_FAILED?;
if (output.len < 3) return string::CONVERSION_FAILED~;
output[0] = (char)(0xE0 | c >> 12);
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
output[2] = (char)(0x80 | (c & 0x3F));
return 3;
case c <= 0x10ffff:
if (output.len < 4) return string::CONVERSION_FAILED?;
if (output.len < 4) return string::CONVERSION_FAILED~;
output[0] = (char)(0xF0 | c >> 18);
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
@@ -42,7 +42,7 @@ fn usz? char32_to_utf8(Char32 c, char[] output)
return 4;
default:
// 0x10FFFF and above is not defined.
return string::CONVERSION_FAILED?;
return string::CONVERSION_FAILED~;
}
}
@@ -84,15 +84,15 @@ fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
return;
}
// Low surrogate first is an error
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return string::INVALID_UTF16?;
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return string::INVALID_UTF16~;
// Unmatched high surrogate is an error
if (*available == 1) return string::INVALID_UTF16?;
if (*available == 1) return string::INVALID_UTF16~;
Char16 low = ptr[1];
// Unmatched high surrogate, invalid
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return string::INVALID_UTF16?;
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return string::INVALID_UTF16~;
// The high bits of the codepoint are the value bits of the high surrogate
// The low bits of the codepoint are the value bits of the low surrogate
@@ -138,7 +138,7 @@ fn usz char32_to_utf8_unsafe(Char32 c, char** output)
fn Char32? utf8_to_char32(char* ptr, usz* size)
{
usz max_size = *size;
if (max_size < 1) return string::INVALID_UTF8?;
if (max_size < 1) return string::INVALID_UTF8~;
char c = (ptr++)[0];
if ((c & 0x80) == 0)
@@ -148,40 +148,40 @@ fn Char32? utf8_to_char32(char* ptr, usz* size)
}
if ((c & 0xE0) == 0xC0)
{
if (max_size < 2) return string::INVALID_UTF8?;
if (max_size < 2) return string::INVALID_UTF8~;
*size = 2;
Char32 uc = (c & 0x1F) << 6;
c = *ptr;
// Overlong sequence or invalid second.
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~;
return uc + c & 0x3F;
}
if ((c & 0xF0) == 0xE0)
{
if (max_size < 3) return string::INVALID_UTF8?;
if (max_size < 3) return string::INVALID_UTF8~;
*size = 3;
Char32 uc = (c & 0x0F) << 12;
c = ptr++[0];
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~;
uc += (c & 0x3F) << 6;
c = ptr++[0];
// Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~;
return uc + c & 0x3F;
}
if (max_size < 4) return string::INVALID_UTF8?;
if ((c & 0xF8) != 0xF0) return string::INVALID_UTF8?;
if (max_size < 4) return string::INVALID_UTF8~;
if ((c & 0xF8) != 0xF0) return string::INVALID_UTF8~;
*size = 4;
Char32 uc = (c & 0x07) << 18;
c = ptr++[0];
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~;
uc += (c & 0x3F) << 12;
c = ptr++[0];
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~;
uc += (c & 0x3F) << 6;
c = ptr++[0];
// Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~;
return uc + c & 0x3F;
}
@@ -329,7 +329,7 @@ fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
usz buf_len = utf32_buffer.len;
for (usz i = 0; i < len;)
{
if (len32 == buf_len) return string::CONVERSION_FAILED?;
if (len32 == buf_len) return string::CONVERSION_FAILED~;
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
i += width;

View File

@@ -1,12 +1,20 @@
module std::core::dstring;
import std::io;
import std::io, std::math;
<*
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)
@@ -82,7 +95,7 @@ fn void DString.replace(&self, String needle, String replacement)
match++;
if (match == needle_len)
{
self.append_chars(replacement);
self.append_string(replacement);
match = 0;
continue;
}
@@ -90,16 +103,16 @@ fn void DString.replace(&self, String needle, String replacement)
}
if (match > 0)
{
self.append_chars(str[i - match:match]);
self.append_string(str[i - match:match]);
match = 0;
}
self.append_char(c);
}
if (match > 0) self.append_chars(str[^match:match]);
if (match > 0) self.append_string(str[^match:match]);
};
}
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)
{
@@ -292,18 +305,23 @@ fn bool DString.less(self, DString other_string)
return true;
}
fn void DString.append_chars(&self, String str)
fn void DString.append_chars(&self, String str) @deprecated("Use append_string")
{
usz other_len = str.len;
self.append_bytes(str);
}
fn void DString.append_bytes(&self, char[] bytes)
{
usz other_len = bytes.len;
if (!other_len) return;
if (!*self)
{
*self = temp(str);
*self = temp((String)bytes);
return;
}
self.reserve(other_len);
StringData* data = self.data();
mem::copy(&data.chars[data.len], str.ptr, other_len);
mem::copy(&data.chars[data.len], bytes.ptr, other_len);
data.len += other_len;
}
@@ -312,7 +330,24 @@ fn Char32[] DString.copy_utf32(&self, Allocator allocator)
return self.str_view().to_utf32(allocator) @inline!!;
}
fn void DString.append_string(&self, DString str)
<*
@require $defined(String s = str) ||| $typeof(str) == DString : "Expected string or DString"
*>
macro void DString.append_string(&self, str)
{
$if $typeof(str) == DString:
self.append_string_deprecated(str);
$else
self.append_bytes((String)str);
$endif
}
macro void DString.append_string_deprecated(&self, DString str) @deprecated("Use .append_dstring()")
{
self.append_dstring(str);
}
fn void DString.append_dstring(&self, DString str)
{
StringData* other = str.data();
if (!other) return;
@@ -327,7 +362,7 @@ fn void DString.clear(self)
fn usz? DString.write(&self, char[] buffer) @dynamic
{
self.append_chars((String)buffer);
self.append_bytes(buffer);
return buffer.len;
}
@@ -387,9 +422,9 @@ macro void DString.append(&self, value)
$case ichar:
self.append_char(value);
$case DString:
self.append_string(value);
self.append_dstring(value);
$case String:
self.append_chars(value);
self.append_string(value);
$case Char32:
self.append_char32(value);
$default:
@@ -397,7 +432,7 @@ macro void DString.append(&self, value)
$case $defined((Char32)value):
self.append_char32((Char32)value);
$case $defined((String)value):
self.append_chars((String)value);
self.append_string((String)value);
$default:
$error "Unsupported type for append use appendf instead.";
$endswitch
@@ -558,7 +593,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 +603,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;
}
@@ -596,7 +631,7 @@ fn void DString.reverse(self)
}
}
fn StringData* DString.data(self) @inline @private
fn StringData* DString.data(self) @inline
{
return (StringData*)self;
}
@@ -613,7 +648,7 @@ fn void DString.reserve(&self, usz addition)
if (data.capacity >= len) return;
usz new_capacity = data.capacity * 2;
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
while (new_capacity < len) new_capacity *= 2;
if (new_capacity < len) new_capacity = math::next_power_of_2(len);
data.capacity = new_capacity;
*self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity);
}
@@ -623,9 +658,10 @@ fn usz? DString.read_from_stream(&self, InStream reader)
if (&reader.available)
{
usz total_read = 0;
while (usz available = reader.available()!)
while (ulong available = reader.available()!)
{
self.reserve(available);
if (available > isz.max) available = (ulong)isz.max;
self.reserve((usz)available);
StringData* data = self.data();
usz len = reader.read(data.chars[data.len..(data.capacity - 1)])!;
total_read += len;

View File

@@ -120,10 +120,13 @@ 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;
const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
const bool NO_LIBC = !LIBC && !CUSTOM_LIBC;
const bool CUSTOM_LIBC = $$CUSTOM_LIBC;
const bool OLD_IO = $feature(OLD_IO);
const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel.from_ordinal($$COMPILER_OPT_LEVEL);
const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED;
@@ -136,12 +139,13 @@ const bool BACKTRACE = $$BACKTRACE;
const usz LLVM_VERSION = $$LLVM_VERSION;
const bool BENCHMARKING = $$BENCHMARKING;
const bool TESTING = $$TESTING;
const bool PANIC_MSG = $$PANIC_MSG;
const MemoryEnvironment MEMORY_ENV = MemoryEnvironment.from_ordinal($$MEMORY_ENVIRONMENT);
const bool TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING);
const bool X86_64 = ARCH_TYPE == X86_64;
const bool X86 = ARCH_TYPE == X86;
const bool AARCH64 = ARCH_TYPE == AARCH64;
const bool NATIVE_STACKTRACE = LINUX || DARWIN || WIN32;
const bool NATIVE_STACKTRACE = LINUX || DARWIN || OPENBSD || WIN32;
const bool LINUX = LIBC && OS_TYPE == LINUX;
const bool DARWIN = LIBC && os_is_darwin();
const bool WIN32 = LIBC && OS_TYPE == WIN32;
@@ -151,13 +155,20 @@ const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
const bool NETBSD = LIBC && OS_TYPE == NETBSD;
const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD;
const bool WASI = LIBC && OS_TYPE == WASI;
const bool WASM = ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
const bool ANDROID = LIBC && OS_TYPE == ANDROID;
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
const bool WASM_NOLIBC @builtin @deprecated("Use 'FREESTANDING_WASM' instead") = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
const bool FREESTANDING_PE32 = NO_LIBC && OS_TYPE == WIN32;
const bool FREESTANDING_MACHO = NO_LIBC && OS_TYPE == MACOS;
const bool FREESTANDING_ELF = NO_LIBC && !env::FREESTANDING_PE32 && !env::FREESTANDING_MACHO && !env::FREESTANDING_WASM;
const bool FREESTANDING_WASM = NO_LIBC && WASM;
const bool FREESTANDING = env::FREESTANDING_PE32 || env::FREESTANDING_MACHO || env::FREESTANDING_ELF || env::FREESTANDING_WASM;
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
const int LANGUAGE_DEV_VERSION = $$LANGUAGE_DEV_VERSION;
const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::OPENBSD || env::DARWIN || env::WIN32 || env::NETBSD;
macro bool os_is_darwin() @const
{
@@ -196,6 +207,8 @@ macro bool os_is_posix() @const
return false;
$endswitch
}
const String[] AUTHORS = $$AUTHORS;
const String[] AUTHOR_EMAILS = $$AUTHOR_EMAILS;
const String PROJECT_VERSION = $$PROJECT_VERSION;
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);

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

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

View File

@@ -3,10 +3,15 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem;
import std::core::mem::allocator @public;
import std::os::posix, std::os::win32;
import std::math;
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
const DEFAULT_MEM_ALIGNMENT = env::WASM ? 16 : (void*.alignof) * 2;
const ulong KB = 1024;
const ulong MB = KB * 1024;
const ulong GB = MB * 1024;
const ulong TB = GB * 1024;
faultdef OUT_OF_MEMORY, INVALID_ALLOC_SIZE;
@@ -15,14 +20,35 @@ macro bool @constant_is_power_of_2($x) @const @private
return $x != 0 && ($x & ($x - 1)) == 0;
}
<*
@return "The os page size."
*>
fn usz os_pagesize()
{
$switch:
$case env::POSIX:
static usz pagesize;
if (pagesize) return pagesize;
return pagesize = posix::getpagesize();
$case env::WIN32:
static usz pagesize;
if (pagesize) return pagesize;
Win32_SYSTEM_INFO info;
win32::getSystemInfo(&info);
return pagesize = info.dwPageSize;
$default:
return 4096;
$endswitch
}
<*
Load a vector from memory according to a mask assuming default alignment.
@param ptr : "The pointer address to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $defined(*ptr = passthru) : "Pointer and passthru must match"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
@@ -40,12 +66,13 @@ macro masked_load(ptr, bool[<*>] mask, passthru)
@param passthru : "The value to use for non masked values"
@param $alignment : "The alignment to assume for the pointer"
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $defined(*ptr = passthru) : "Pointer and passthru must match"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
@ensure $typeof(return) == $typeof(*ptr)
*>
macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
{
@@ -59,9 +86,9 @@ macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
@require $defined(*ptrvec[0] = passthru[0]) : "Pointer and passthru must match"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@@ -81,9 +108,9 @@ macro gather(ptrvec, bool[<*>] mask, passthru)
@param passthru : "The value to use for non masked values"
@param $alignment : "The alignment to assume for the pointers"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
@require $defined(*ptrvec[0] = passthru[0]) : "Pointer and passthru must match"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -103,8 +130,8 @@ macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
@param value : "The value to store masked"
@param mask : "The mask for the store"
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $defined(*ptr = value) : "Pointer and value must match"
@require $kindof(value) == VECTOR : "Expected value to be a vector"
@require value.len == mask.len : "Mask and value must have the same length"
*>
macro masked_store(ptr, value, bool[<*>] mask)
@@ -118,8 +145,8 @@ macro masked_store(ptr, value, bool[<*>] mask)
@param mask : "The mask for the store"
@param $alignment : "The alignment of the pointer"
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $defined(*ptr = value) : "Pointer and value must match"
@require $kindof(value) == VECTOR : "Expected value to be a vector"
@require value.len == mask.len : "Mask and value must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -133,9 +160,9 @@ macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
@param ptrvec : "The vector pointer containing the addresses to store to."
@param value : "The value to store masked"
@param mask : "The mask for the store"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(value) == VECTOR : "Expected value to be a vector"
@require $defined(*ptrvec[0] = value[0]) : "Pointer and value must match"
@require value.len == mask.len : "Mask and value must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@@ -151,9 +178,9 @@ macro scatter(ptrvec, value, bool[<*>] mask)
@param mask : "The mask for the store"
@param $alignment : "The alignment of the load"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(value) == VECTOR : "Expected value to be a vector"
@require $defined(*ptrvec[0] = value[0]) : "Pointer and value must match"
@require value.len == mask.len : "Mask and value must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -173,7 +200,7 @@ macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
*>
macro @unaligned_load(#x, usz $alignment) @builtin
{
return $$unaligned_load(&#x, $alignment);
return $$unaligned_load(&#x, $alignment, false);
}
<*
@@ -188,9 +215,10 @@ macro @unaligned_load(#x, usz $alignment) @builtin
*>
macro @unaligned_store(#x, value, usz $alignment) @builtin
{
return $$unaligned_store(&#x, ($typeof(#x))value, $alignment);
return $$unaligned_store(&#x, ($typeof(#x))value, $alignment, false);
}
<*
@param #x : "The variable or dereferenced pointer to load."
@return "The value of the variable"
@@ -215,6 +243,39 @@ macro @volatile_store(#x, value) @builtin
return $$volatile_store(&#x, ($typeof(#x))value);
}
<*
@param ptr : "The pointer to load from"
@param $align : "The alignment to assume for the load"
@param $volatile : "Whether the load is volatile or not, defaults to false"
@return "The value of the variable"
@require $defined(*ptr) : "This must be a typed pointer"
@require @constant_is_power_of_2($align) : "The alignment must be a power of two"
*>
macro load(ptr, usz $align, bool $volatile = false)
{
return $$unaligned_load(ptr, $align, $volatile);
}
<*
@param ptr : "The pointer to store to."
@param value : "The value to store."
@param $align : "The alignment to assume for the store"
@param $volatile : "Whether the store is volatile, defaults to false"
@return "The value stored"
@require $defined(*ptr) : "This must be a typed pointer"
@require $defined(*ptr = value) : "The value doesn't match the variable"
@require @constant_is_power_of_2($align) : "The alignment must be a power of two"
*>
macro store(ptr, value, usz $align, bool $volatile = false)
{
return $$unaligned_store(ptr, ($typeof(*ptr))value, $align, $volatile);
}
<*
All possible atomic orderings
*>
enum AtomicOrdering : int
{
NOT_ATOMIC, // Not atomic
@@ -282,7 +343,7 @@ macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = S
*>
fn usz aligned_offset(usz offset, usz alignment)
{
return alignment * ((offset + alignment - 1) / alignment);
return (offset + alignment - 1) & ~(alignment - 1);
}
macro void* aligned_pointer(void* ptr, usz alignment)
@@ -298,6 +359,11 @@ fn bool ptr_is_aligned(void* ptr, usz alignment) @inline
return (uptr)ptr & ((uptr)alignment - 1) == 0;
}
fn bool ptr_is_page_aligned(void* ptr) @inline
{
return (uptr)ptr & ((uptr)os_pagesize() - 1) == 0;
}
macro void zero_volatile(char[] data)
{
$$memset(data.ptr, (char)0, data.len, true, (usz)1);
@@ -342,7 +408,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)
@@ -405,7 +471,7 @@ macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $i
@require values::@inner_kind(b) == TypeKind.SLICE || values::@inner_kind(b) == TypeKind.POINTER
@require values::@inner_kind(a) != TypeKind.SLICE || len == -1
@require values::@inner_kind(a) != TypeKind.POINTER || len > -1
@require values::@assign_to(a, b) && values::@assign_to(b, a)
@require $defined(a = b, b = a)
*>
macro bool equals(a, b, isz len = -1, usz $align = 0)
{
@@ -576,11 +642,20 @@ fn void temp_pop(PoolState old_state)
allocator::pop_pool(old_state) @inline;
}
macro void @pool_init(Allocator allocator, usz pool_size, usz buffer_size; @body) @builtin
<*
@require pool_size >= 64
@require realloc_size >= 64
@require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator."
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
*>
macro void @pool_init(Allocator allocator, usz pool_size,
usz reserve_size = allocator::temp_allocator_reserve_size,
usz min_size = allocator::temp_allocator_min_size,
usz realloc_size = allocator::temp_allocator_realloc_size; @body) @builtin
{
Allocator current = allocator::current_temp;
TempAllocator* top = allocator::top_temp;
allocator::create_temp_allocator(allocator, pool_size, buffer_size);
allocator::create_temp_allocator(allocator, pool_size, reserve_size, min_size, realloc_size);
defer
{
allocator::destroy_temp_allocators();
@@ -589,9 +664,19 @@ macro void @pool_init(Allocator allocator, usz pool_size, usz buffer_size; @body
}
@body();
}
macro void @pool(;@body) @builtin
<*
Create a new temporary allocator.
The `reserve` parameter allows you to determine how many bytes should be reserved for
allocations on the current temporary allocator, if allocations are made inside of the pool scope.
It is made available for optimization, and can usually be ignored.
@param reserve : "The amount of bytes to reserve for out-of-order allocations, 0 gives the default."
*>
macro void @pool(usz reserve = 0; @body) @builtin
{
PoolState state = allocator::push_pool() @inline;
PoolState state = allocator::push_pool(reserve) @inline;
defer
{
allocator::pop_pool(state) @inline;
@@ -599,7 +684,7 @@ macro void @pool(;@body) @builtin
@body();
}
module std::core::mem @if(WASM_NOLIBC);
module std::core::mem @if(env::FREESTANDING_WASM);
import std::core::mem::allocator @public;
SimpleHeapAllocator wasm_allocator @private;
extern int __heap_base;
@@ -627,16 +712,50 @@ 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"
*>
macro @clone_slice(value) @builtin @nodiscard => allocator::clone_slice(mem, value);
<*
@param value : "The value to clone"
@return "A pointer to the cloned value, which must be released using free_aligned"
*>
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
}
<*
@param value : "The value to clone"
@return "A pointer to the cloned value"
*>
macro @tclone_slice(value) @builtin @nodiscard => allocator::clone_slice(tmem, value);
fn void* malloc(usz size) @builtin @inline @nodiscard
{
return allocator::malloc(mem, size);
@@ -658,56 +777,70 @@ fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@param $Type : "The type to allocate"
@param #init : "The optional initializer"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
@return "A pointer to data of type $Type."
*>
macro new($Type, ...) @nodiscard
macro new($Type, #init = ...) @nodiscard @safemacro
{
$if $vacount == 0:
return ($Type*)calloc($Type.sizeof);
$else
$if $defined(#init):
$Type* val = malloc($Type.sizeof);
*val = $vaexpr[0];
*val = #init;
return val;
$else
return ($Type*)calloc($Type.sizeof);
$endif
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
@param $Type : "The type to allocate"
@param padding : "The padding to add after the allocation"
@param #init : "The optional initializer"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to data of type $Type."
*>
macro new_with_padding($Type, usz padding, ...) @nodiscard
macro new_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
{
$if $vacount == 0:
return ($Type*)calloc($Type.sizeof + padding);
$else
$if $defined(#init):
$Type* val = malloc($Type.sizeof + padding);
*val = $vaexpr[0];
*val = #init;
return val;
$else
return ($Type*)calloc($Type.sizeof + padding);
$endif
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@param $Type : "The type to allocate"
@param #init : "The optional initializer"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to data of type $Type with the proper alignment"
*>
macro new_aligned($Type, ...) @nodiscard
macro new_aligned($Type, #init = ...) @nodiscard @safemacro
{
$if $vacount == 0:
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
$else
$if $defined(#init):
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
*val = $vaexpr[0];
*val = #init;
return val;
$else
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
$endif
}
<*
@param $Type : "The type to allocate"
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
@return "A pointer to uninitialized data for the type $Type"
*>
macro alloc($Type) @nodiscard
{
@@ -715,7 +848,12 @@ macro alloc($Type) @nodiscard
}
<*
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
@param $Type : "The type to allocate"
@param padding : "The padding to add after the allocation"
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
@return "A pointer to uninitialized data for the type $Type"
*>
macro alloc_with_padding($Type, usz padding) @nodiscard
{
@@ -723,8 +861,13 @@ macro alloc_with_padding($Type, usz padding) @nodiscard
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@param $Type : "The type to allocate"
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@return "A pointer to uninitialized data for the type $Type with the proper alignment"
*>
macro alloc_aligned($Type) @nodiscard
{
@@ -732,46 +875,62 @@ macro alloc_aligned($Type) @nodiscard
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@param $Type : "The type to allocate"
@param #init : "The optional initializer"
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to temporary data of type $Type."
*>
macro tnew($Type, ...) @nodiscard
macro tnew($Type, #init = ...) @nodiscard @safemacro
{
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof) @inline;
$else
$Type* val = tmalloc($Type.sizeof) @inline;
*val = $vaexpr[0];
$if $defined(#init):
$Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
*val = #init;
return val;
$else
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
$endif
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@param $Type : "The type to allocate"
@param padding : "The padding to add after the allocation"
@param #init : "The optional initializer"
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to temporary data of type $Type with added padding at the end."
*>
macro temp_with_padding($Type, usz padding, ...) @nodiscard
macro temp_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
{
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof + padding) @inline;
$else
$Type* val = tmalloc($Type.sizeof + padding) @inline;
*val = $vaexpr[0];
$if $defined(#init):
$Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline;
*val = #init;
return val;
$else
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
$endif
}
<*
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
*>
macro talloc($Type) @nodiscard
{
return tmalloc($Type.sizeof);
}
macro talloc_with_padding($Type, usz padding) @nodiscard
{
return tmalloc($Type.sizeof + padding);
return tmalloc($Type.sizeof, $Type.alignof);
}
<*
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
*>
macro talloc_with_padding($Type, usz padding) @nodiscard
{
return tmalloc($Type.sizeof + padding, $Type.alignof);
}
<*
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
*>
macro new_array($Type, usz elements) @nodiscard
@@ -782,6 +941,8 @@ macro new_array($Type, usz elements) @nodiscard
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
*>
macro new_array_aligned($Type, usz elements) @nodiscard
{
@@ -789,6 +950,7 @@ macro new_array_aligned($Type, usz elements) @nodiscard
}
<*
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*>
macro alloc_array($Type, usz elements) @nodiscard
@@ -799,6 +961,8 @@ macro alloc_array($Type, usz elements) @nodiscard
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
*>
macro alloc_array_aligned($Type, usz elements) @nodiscard
{
@@ -862,6 +1026,18 @@ fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMEN
return tmem.resize(ptr, size, alignment)!!;
}
<*
Takes the address of a possibly unaligned variable or member,
and offers safe access to that member, by constructing an UnalignedRef.
@require $defined(&#arg) : "It must be possible to take the address of the argument."
@return "An 'UnalignedRef' with the proper type and alignment, with a pointer to argument"
*>
macro @unaligned_addr(#arg) @builtin
{
return (UnalignedRef{$typeof(#arg), $alignof(#arg)})&#arg;
}
module std::core::mem @if(env::NO_LIBC);
fn CInt __memcmp(void* s1, void* s2, usz n) @weak @export("memcmp")
@@ -899,3 +1075,39 @@ fn void* __memcpy(void* dst, void* src, usz n) @weak @export("memcpy")
}
return dst;
}
module std::core::mem::volatile <Type>;
typedef Volatile @structlike = Type;
macro Type Volatile.get(&self)
{
return @volatile_load(*(Type*)self);
}
macro Type Volatile.set(&self, Type val)
{
return @volatile_store(*(Type*)self, val);
}
<*
@require mem::@constant_is_power_of_2(ALIGNMENT) : "The alignment must be a power of 2"
*>
module std::core::mem::alignment <Type, ALIGNMENT>;
import std::core::mem @public;
<*
An UnalignedRef offers correctly aligned access to addresses that may be unaligned or overaligned.
*>
typedef UnalignedRef = Type*;
macro Type UnalignedRef.get(self)
{
return @unaligned_load(*(Type*)self, ALIGNMENT, false);
}
macro Type UnalignedRef.set(&self, Type val)
{
return @unaligned_store(*(Type*)self, val, ALIGNMENT, false);
}

View File

@@ -1,6 +1,19 @@
module std::core::mem::allocator;
import std::math;
// C3 has several 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
// Vmem Yes No No Yes Yes *Note: Can be set to huge sizes
const DEFAULT_SIZE_PREFIX = usz.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
@@ -20,13 +33,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 +52,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);
}
@@ -44,7 +65,7 @@ alias MemoryAllocFn = fn char[]?(usz);
fn usz alignment_for_allocation(usz alignment) @inline @private
fn usz alignment_for_allocation(usz alignment) @inline
{
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
}
@@ -146,7 +167,7 @@ macro void free_aligned(Allocator allocator, void* ptr)
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
*>
macro new(Allocator allocator, $Type, ...) @nodiscard
{
@@ -162,7 +183,7 @@ macro new(Allocator allocator, $Type, ...) @nodiscard
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
*>
macro new_try(Allocator allocator, $Type, ...) @nodiscard
{
@@ -179,7 +200,7 @@ macro new_try(Allocator allocator, $Type, ...) @nodiscard
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
*>
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
{
@@ -283,11 +304,75 @@ macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*>
macro realloc_array(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
{
return realloc_array_try(allocator, ptr, $Type, elements)!!;
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
*>
macro realloc_array_aligned(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
{
return (($Type*)realloc_aligned(allocator, ptr, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*>
macro realloc_array_try(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
{
return (($Type*)realloc_try(allocator, ptr, $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);
}
<*
@param [&inout] allocator : "The allocator used to clone"
@param slice : "The slice to clone"
@return "A pointer to the cloned slice"
@require $kindof(slice) == SLICE || $kindof(slice) == ARRAY
*>
macro clone_slice(Allocator allocator, slice) @nodiscard
{
if (!lengthof(slice)) return {};
var $Type = $typeof(slice[0]);
$Type[] new_arr = new_array(allocator, $Type, slice.len);
mem::copy(new_arr.ptr, &slice[0], slice.len * $Type.sizeof);
return new_arr;
}
<*
Clone overaligned values. Must be released using free_aligned.
@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;
@@ -307,7 +392,7 @@ macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
if (alignment < void*.alignof) alignment = void*.alignof;
usz header = AlignedBlock.sizeof + alignment;
usz alignsize = bytes + header;
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
$if $kindof(#alloc_fn(bytes)) == OPTIONAL:
void* data = #alloc_fn(alignsize)!;
$else
void* data = #alloc_fn(alignsize);
@@ -319,6 +404,28 @@ macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
return mem;
}
<*
@require bytes > 0
@require alignment > 0
@require bytes <= isz.max
*>
macro void*? @aligned_alloc_fn(context, #alloc_fn, usz bytes, usz alignment)
{
if (alignment < void*.alignof) alignment = void*.alignof;
usz header = AlignedBlock.sizeof + alignment;
usz alignsize = bytes + header;
$if $kindof(#alloc_fn(context, bytes)) == OPTIONAL:
void* data = #alloc_fn(context, alignsize)!;
$else
void* data = #alloc_fn(context, alignsize);
$endif
void* mem = mem::aligned_pointer(data + AlignedBlock.sizeof, alignment);
AlignedBlock* desc = (AlignedBlock*)mem - 1;
assert(mem > data);
*desc = { bytes, data };
return mem;
}
struct AlignedBlock
{
usz len;
@@ -328,13 +435,23 @@ struct AlignedBlock
macro void? @aligned_free(#free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
$if $kindof(#free_fn(desc.start)) == OPTIONAL:
#free_fn(desc.start)!;
$else
#free_fn(desc.start);
$endif
}
macro void? @aligned_free_fn(context, #free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if $kindof(#free_fn(context, desc.start)) == OPTIONAL:
#free_fn(context, desc.start)!;
$else
#free_fn(context, desc.start);
$endif
}
<*
@require bytes > 0
@require alignment > 0
@@ -345,7 +462,7 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
void* data_start = desc.start;
void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!;
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
$if @typekind(#free_fn(data_start)) == OPTIONAL:
$if $kindof(#free_fn(data_start)) == OPTIONAL:
#free_fn(data_start)!;
$else
#free_fn(data_start);
@@ -353,6 +470,23 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
return new_data;
}
<*
@require bytes > 0
@require alignment > 0
*>
macro void*? @aligned_realloc_fn(context, #calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
void* new_data = @aligned_alloc_fn(context, #calloc_fn, bytes, alignment)!;
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
$if $kindof(#free_fn(context, data_start)) == OPTIONAL:
#free_fn(context, data_start)!;
$else
#free_fn(context, data_start);
$endif
return new_data;
}
// All allocators
alias mem @builtin = thread_allocator ;
@@ -367,13 +501,13 @@ tlocal TempAllocator* top_temp;
tlocal bool auto_create_temp = false;
usz temp_allocator_min_size = temp_allocator_default_min_size();
usz temp_allocator_buffer_size = temp_allocator_default_buffer_size();
usz temp_allocator_new_mult = 4;
usz temp_allocator_reserve_size = temp_allocator_default_reserve_size();
usz temp_allocator_realloc_size = temp_allocator_default_min_size() * 4;
fn PoolState push_pool()
fn PoolState push_pool(usz reserve = 0)
{
Allocator old = top_temp ? current_temp : create_temp_allocator_on_demand();
current_temp = ((TempAllocator*)old).derive_allocator(temp_allocator_min_size, temp_allocator_buffer_size, temp_allocator_new_mult)!!;
current_temp = ((TempAllocator*)old).derive_allocator(reserve)!!;
return (PoolState)old.ptr;
}
@@ -413,7 +547,7 @@ macro usz temp_allocator_default_min_size() @local
$endswitch
}
macro usz temp_allocator_default_buffer_size() @local
macro usz temp_allocator_default_reserve_size() @local
{
$switch env::MEMORY_ENV:
$case NORMAL: return 1024;
@@ -423,7 +557,7 @@ macro usz temp_allocator_default_buffer_size() @local
$endswitch
}
macro Allocator heap() => thread_allocator;
macro Allocator heap() @deprecated("Use 'mem' instead.") => thread_allocator;
<*
@require !top_temp : "This should never be called when temp already exists"
@@ -435,24 +569,25 @@ 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(), temp_allocator_reserve_size, temp_allocator_min_size, temp_allocator_realloc_size);
}
<*
@require !top_temp : "This should never be called when temp already exists"
*>
fn Allocator create_temp_allocator(Allocator allocator, usz size, usz buffer = temp_allocator_default_buffer_size()) @private
fn Allocator create_temp_allocator(Allocator allocator, usz size, usz reserve, usz min_size, usz realloc_size) @private
{
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size)!!;
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size, reserve, min_size, realloc_size)!!;
}
macro Allocator temp()
macro Allocator temp() @deprecated("Use 'tmem' instead")
{
return current_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::FREESTANDING_WASM)
{
auto_create_temp = true;
}
@@ -497,12 +632,12 @@ typedef NullAllocator (Allocator) = uptr;
fn void*? NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
return mem::OUT_OF_MEMORY?;
return mem::OUT_OF_MEMORY~;
}
fn void*? NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
return mem::OUT_OF_MEMORY?;
return mem::OUT_OF_MEMORY~;
}
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic

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

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

353
lib/std/core/os/mem_vm.c3 Normal file
View File

@@ -0,0 +1,353 @@
<*
The VM module holds code for working with virtual memory on supported platforms (currently Win32 and Posix)
*>
module std::core::mem::vm;
import std::io, std::os::win32, std::os::posix, libc;
<*
VirtualMemory is an abstraction for working with an allocated virtual memory area. It will invoke vm:: functions
but will perform more checks and track its size (required to unmap the memory on Posix)
*>
struct VirtualMemory
{
void* ptr;
usz size;
VirtualMemoryAccess default_access;
}
faultdef RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, UNMAPPED_ACCESS, UNALIGNED_ADDRESS, RELEASE_FAILED, UPDATE_FAILED, INVALID_ARGS;
enum VirtualMemoryAccess
{
PROTECTED,
READ,
WRITE,
READWRITE,
EXEC,
EXECREAD,
EXECWRITE,
ANY
}
fn usz aligned_alloc_size(usz size)
{
$if env::WIN32:
return size > 0 ? mem::aligned_offset(size, win32::allocation_granularity()) : win32::allocation_granularity();
$else
return size > 0 ? mem::aligned_offset(size, mem::os_pagesize()) : mem::os_pagesize();
$endif
}
<*
Allocate virtual memory, size is rounded up to platform granularity (Win32) / page size (Posix).
@param size : "The size of the memory to allocate, will be rounded up"
@param access : "The initial access permissions."
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
@return "Pointer to the allocated memory, page aligned"
*>
fn void*? alloc(usz size, VirtualMemoryAccess access)
{
$switch:
$case env::POSIX:
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), posix::MAP_PRIVATE | posix::MAP_ANONYMOUS, -1, 0);
if (ptr != posix::MAP_FAILED) return ptr;
switch (libc::errno())
{
case errno::ENOMEM: return mem::OUT_OF_MEMORY~;
case errno::EOVERFLOW: return RANGE_OVERFLOW~;
case errno::EPERM: return ACCESS_DENIED~;
case errno::EINVAL: return INVALID_ARGS~;
default: return UNKNOWN_ERROR~;
}
$case env::WIN32:
void* ptr = win32::virtualAlloc(null, aligned_alloc_size(size), MEM_RESERVE, access.to_win32());
if (ptr) return ptr;
switch (win32::getLastError())
{
case win32::ERROR_NOT_ENOUGH_MEMORY:
case win32::ERROR_COMMITMENT_LIMIT: return mem::OUT_OF_MEMORY~;
default: return UNKNOWN_ERROR~;
}
$default:
unsupported("Virtual alloc only available on Win32 and Posix");
$endswitch
}
<*
Release memory allocated with "alloc".
@param [&inout] ptr : "Pointer to page to release, should be allocated using vm::alloc"
@param size : "The size of the allocated pointer"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
*>
fn void? release(void* ptr, usz size)
{
$switch:
$case env::POSIX:
if (posix::munmap(ptr, aligned_alloc_size(size)))
{
switch (libc::errno())
{
case errno::EINVAL: return INVALID_ARGS~; // Not a valid mapping or size
case errno::ENOMEM: return UNMAPPED_ACCESS~; // Address not mapped
default: return RELEASE_FAILED~;
}
}
$case env::WIN32:
if (win32::virtualFree(ptr, 0, MEM_RELEASE)) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return INVALID_ARGS~;
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY~;
default: return RELEASE_FAILED~;
}
$default:
unsupported("Virtual free only available on Win32 and Posix");
$endswitch
}
<*
Change the access protection of a region in memory. The region must be page aligned.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to update, must be page aligned"
@param access : "The new access"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? protect(void* ptr, usz len, VirtualMemoryAccess access)
{
$switch:
$case env::POSIX:
if (!posix::mprotect(ptr, len, access.to_posix())) return;
switch (libc::errno())
{
case errno::EACCES: return ACCESS_DENIED~;
case errno::EINVAL: return UNALIGNED_ADDRESS~;
case errno::EOVERFLOW: return RANGE_OVERFLOW~;
case errno::ENOMEM: return UNMAPPED_ACCESS~;
default: return UPDATE_FAILED~;
}
$case env::WIN32:
Win32_Protect old;
if (win32::virtualProtect(ptr, len, access.to_win32(), &old)) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~;
default: return UPDATE_FAILED~;
}
$default:
unsupported("'virtual_protect' is only available on Win32 and Posix.");
$endswitch
}
<*
Makes a region of memory available that was previously retrieved using 'alloc'. This is necessary on Win32,
but optional on Posix.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to commit, must be page aligned"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? UNKNOWN_ERROR, mem::OUT_OF_MEMORY, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? commit(void* ptr, usz len, VirtualMemoryAccess access = READWRITE)
{
$switch:
$case env::POSIX:
return protect(ptr, len, READWRITE) @inline;
$case env::WIN32:
void* result = win32::virtualAlloc(ptr, len, MEM_COMMIT, access.to_win32());
if (result) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~;
case win32::ERROR_COMMITMENT_LIMIT:
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY~;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~;
default: return UNKNOWN_ERROR~;
}
$default:
unsupported("'virtual_commit' is only available on Win32 and Posix.");
$endswitch
}
<*
Notifies that the memory in the region can be released back to the OS. On Win32 this decommits the region,
whereas on Posix it tells the system that it may be reused using madvise. The "block" parameter is only
respected on Posix, and protects the region from read/write/exec. On Win32 this always happens.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to commit, must be page aligned"
@param block : "Set the released memory to protected"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? decommit(void* ptr, usz len, bool block = true)
{
$switch:
$case env::POSIX:
if (posix::madvise(ptr, len, posix::MADV_DONTNEED))
{
switch (libc::errno())
{
case errno::EINVAL: return UNALIGNED_ADDRESS~;
case errno::ENOMEM: return UNMAPPED_ACCESS~;
default: return UPDATE_FAILED~;
}
}
if (block) (void)protect(ptr, len, PROTECTED) @inline;
$case env::WIN32:
if (!win32::virtualFree(ptr, len, MEM_DECOMMIT))
{
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~;
default: return UPDATE_FAILED~;
}
}
$default:
unsupported("'virtual_decommit' is only available on Win32 and Posix.");
$endswitch
}
<*
Map a portion of an already-opened file into memory.
@param fd : "File descriptor"
@param size : "Number of bytes to map, will be rounded up to page size"
@param offset : "Byte offset in file, must be page size aligned"
@param access : "The initial access permissions"
@param shared : "if True then MAP_SHARED else MAP_PRIVATE"
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
@return "Pointer to the mapped region"
*>
fn void*? mmap_file(Fd fd, usz size, usz offset = 0, VirtualMemoryAccess access = READ, bool shared = false) @if (env::POSIX)
{
CInt flags = shared ? posix::MAP_SHARED : posix::MAP_PRIVATE;
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), flags, fd, offset);
if (ptr != posix::MAP_FAILED) return ptr;
switch (libc::errno())
{
case errno::ENOMEM: return mem::OUT_OF_MEMORY~;
case errno::EOVERFLOW: return RANGE_OVERFLOW~;
case errno::EPERM: return ACCESS_DENIED~;
case errno::EINVAL: return INVALID_ARGS~;
case errno::EACCES: return io::NO_PERMISSION~;
case errno::EBADF: return io::FILE_NOT_VALID~;
case errno::EAGAIN: return io::WOULD_BLOCK~;
case errno::ENXIO: return io::FILE_NOT_FOUND~;
default: return UNKNOWN_ERROR~;
}
}
<*
Create a VirtualMemory using
@param size : "The size of the memory to allocate."
@require size > 0 : "The size must be non-zero"
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
*>
fn VirtualMemory? virtual_alloc(usz size, VirtualMemoryAccess access = PROTECTED)
{
size = aligned_alloc_size(size);
void* ptr = alloc(size, access)!;
return { ptr, size, access };
}
<*
Commits memory, using vm::commit
@param offset : "Starting from what offset to commit"
@param len : "To what len to commit"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len <= self.size : "Length out of range"
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
*>
macro void? VirtualMemory.commit(self, usz offset, usz len)
{
return commit(self.ptr + offset, len, self.default_access);
}
<*
Changes protection of a part of memory using vm::protect
@param offset : "Starting from what offset to update"
@param len : "To what len to update"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
*>
macro void? VirtualMemory.protect(self, usz offset, usz len, VirtualMemoryAccess access)
{
return protect(self.ptr + offset, len, access);
}
<*
Decommits a part of memory using vm::decommit
@param offset : "Starting from what offset to decommit"
@param len : "To what len to decommit"
@param block : "Should the memory be blocked from access after decommit"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@return? UPDATE_FAILED
*>
fn void? VirtualMemory.decommit(self, usz offset, usz len, bool block = true)
{
return decommit(self.ptr + offset, len, block);
}
<*
Releases the memory region
@require self.ptr != null : "Virtual memory must be initialized to call destroy"
*>
fn void? VirtualMemory.destroy(&self)
{
return release(self.ptr, self.size);
}
fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX)
{
switch (self)
{
case PROTECTED: return posix::PROT_NONE;
case READ: return posix::PROT_READ;
case WRITE: return posix::PROT_WRITE;
case EXEC: return posix::PROT_EXEC;
case READWRITE: return posix::PROT_READ | posix::PROT_WRITE;
case EXECREAD: return posix::PROT_READ | posix::PROT_EXEC;
case EXECWRITE: return posix::PROT_WRITE | posix::PROT_EXEC;
case ANY: return posix::PROT_WRITE | posix::PROT_READ | posix::PROT_EXEC;
}
}
fn Win32_Protect VirtualMemoryAccess.to_win32(self) @if(env::WIN32)
{
switch (self)
{
case PROTECTED: return PAGE_NOACCESS;
case READ: return PAGE_READONLY;
case WRITE: return PAGE_READWRITE;
case EXEC: return PAGE_EXECUTE;
case READWRITE: return PAGE_READWRITE;
case EXECWRITE: return PAGE_EXECUTE_READWRITE;
case EXECREAD: return PAGE_EXECUTE_READ;
case ANY: return PAGE_EXECUTE_READWRITE;
}
}

View File

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

View File

@@ -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

@@ -79,7 +79,7 @@ fn SegmentCommand64*? find_segment(MachHeader* header, char* segname)
}
command = (void*)command + command.cmdsize;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
fn Section64*? find_section(SegmentCommand64* command, char* sectname)
{
@@ -89,7 +89,7 @@ fn Section64*? find_section(SegmentCommand64* command, char* sectname)
if (name_cmp(sectname, &section.sectname)) return section;
section++;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type)

View File

@@ -7,16 +7,26 @@ macro usz _strlen(ptr) @private
return len;
}
macro int @main_to_err_main(#m, int, char**)
macro int @main_no_args(#m, int, char**)
{
if (catch #m()) return 1;
return 0;
$if $typeof(#m()) == void:
#m();
return 0;
$else
return #m();
$endif
}
macro int @main_to_int_main(#m, int, char**) => #m();
macro int @main_to_void_main(#m, int, char**)
macro int @main_args(#m, int argc, char** argv)
{
#m();
return 0;
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
$if $typeof(#m(list)) == void:
#m(list);
return 0;
$else
return #m(list);
$endif
}
macro String[] args_to_strings(int argc, char** argv) @private
@@ -32,21 +42,6 @@ macro String[] args_to_strings(int argc, char** argv) @private
}
macro int @main_to_err_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
if (catch #m(list)) return 1;
return 0;
}
macro int @main_to_int_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
return #m(list);
}
macro int @_main_runner(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
@@ -54,17 +49,20 @@ macro int @_main_runner(#m, int argc, char** argv)
return #m(list) ? 0 : 1;
}
macro int @main_to_void_main_args(#m, int argc, char** argv)
module std::core::main_stub @if(env::WIN32);
import std::os::win32;
macro win32_set_utf8_codepage() @local
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
#m(list);
return 0;
// By default windows uses an OEM codepage that differs based on locale
// and does not support printing utf-8 characters. This allows both
// printing utf-8 characters from strings and reading them from stdin.
win32::setConsoleCP(UTF8);
win32::setConsoleOutputCP(UTF8);
}
module std::core::main_stub @if(env::WIN32);
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @extern("CommandLineToArgvW");
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @cname("CommandLineToArgvW");
macro String[] win_command_line_to_strings(ushort* cmd_line) @private
{
@@ -91,90 +89,72 @@ macro void release_wargs(String[] list) @private
free(list.ptr);
}
macro int @win_to_err_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_main_no_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{
if (catch #m()) return 1;
return 0;
}
macro int @win_to_int_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd) => #m();
macro int @win_to_void_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{
#m();
return 0;
win32_set_utf8_codepage();
$if $typeof(#m()) == void:
#m();
return 0;
$else
return #m();
$endif
}
macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{
win32_set_utf8_codepage();
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
if (catch #m(args)) return 1;
return 0;
$if $typeof(#m(args)) == void:
#m(args);
return 0;
$else
return #m(args);
$endif
}
macro int @win_to_int_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{
win32_set_utf8_codepage();
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return #m(args);
$if $typeof(#m(handle, prev_handle, args, show_cmd)) == void:
#m(handle, prev_handle, args, show_cmd);
return 0;
$else
return #m(handle, prev_handle, args, show_cmd);
$endif
}
macro int @win_to_void_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
#m(args);
return 0;
}
macro int @win_to_err_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
if (catch #m(handle, prev_handle, args, show_cmd)) return 1;
return 0;
}
macro int @win_to_int_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return #m(handle, prev_handle, args, show_cmd);
}
macro int @win_to_void_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
#m(handle, prev_handle, args, show_cmd);
return 0;
}
macro int @wmain_to_err_main_args(#m, int argc, Char16** argv)
macro int @wmain_main(#m, int argc, Char16** argv)
{
win32_set_utf8_codepage();
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
if (catch #m(args)) return 1;
return 1;
$if $typeof(#m(args)) == void:
#m(args);
return 0;
$else
return #m(args);
$endif
}
macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
macro int @wmain_main_no_args(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
return #m(args);
win32_set_utf8_codepage();
$if $typeof(#m()) == void:
#m();
return 0;
$else
return #m();
$endif
}
macro int @_wmain_runner(#m, int argc, Char16** argv)
{
win32_set_utf8_codepage();
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
return #m(args) ? 0 : 1;
}
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
#m(args);
return 0;
}

132
lib/std/core/refcount.c3 Normal file
View File

@@ -0,0 +1,132 @@
<*
Ref provides a general *external* ref counted wrapper for a pointer. For convenience, a ref count of 0
means the reference is still valid.
When the rc drops to -1, it will first run the dealloc function on the underlying pointer (if it exists),
then free the pointer and the atomic variable assuming that they are allocated using the Allocator in the Ref.
@require !$defined(Type.dealloc) ||| $defined(Type.dealloc(&&(Type){})) : "'dealloc' must only take a pointer to the underlying type"
@require !$defined(Type.dealloc) ||| $typeof((Type){}.dealloc()) == void : "'dealloc' must return 'void'"
*>
module std::core::mem::ref <Type>;
import std::thread, std::atomic;
const OVERALIGNED @private = Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
alias DeallocFn = fn void(void*);
fn Ref wrap(Type* ptr, Allocator allocator = mem)
{
return { .refcount = allocator::new(allocator, Atomic{int}), .ptr = ptr, .allocator = allocator };
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $defined(Type a = $vaexpr[0]) : "The first argument must be an initializer for the type"
*>
macro Ref new(..., Allocator allocator = mem)
{
$switch:
$case OVERALIGNED && !$vacount:
Type* ptr = allocator::calloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
$case OVERALIGNED:
Type* ptr = allocator::malloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
*ptr = $vaexpr[0];
$case !$vacount:
Type* ptr = allocator::calloc(allocator, Type.sizeof);
$default:
Type* ptr = allocator::malloc(allocator, Type.sizeof);
*ptr = $vaexpr[0];
$endswitch
return { .refcount = allocator::new(allocator, Atomic{int}),
.ptr = ptr,
.allocator = allocator };
}
struct Ref
{
Atomic{int}* refcount;
Type* ptr;
Allocator allocator;
}
fn Ref* Ref.retain(&self)
{
assert(self.refcount != null, "Reference already released");
assert(self.refcount.load(RELAXED) >= 0, "Retaining zombie");
self.refcount.add(1, RELAXED);
return self;
}
fn void Ref.release(&self)
{
assert(self.refcount != null, "Reference already released");
assert(self.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
if (self.refcount.sub(1, RELAXED) == 0)
{
thread::fence(ACQUIRE);
$if $defined(Type.dealloc):
self.ptr.dealloc();
$endif
$if OVERALIGNED:
allocator::free_aligned(self.allocator, self.ptr);
$else
allocator::free(self.allocator, self.ptr);
$endif
allocator::free(self.allocator, self.refcount);
*self = {};
}
}
module std::core::mem::rc;
import std::thread, std::atomic;
<*
A RefCounted struct should be an inline base of a struct.
If a `dealloc` is defined, then it will be called rather than `free`
For convenience, a ref count of 0 is still valid, and the struct is
only freed when when ref count drops to -1.
The macros rc::retain and rc::release must be used on the full pointer,
not on the RefCounted substruct.
So `Foo* f = ...; RefCounted* rc = f; rc::release(rc);` will not do the right thing.
*>
struct RefCounted
{
Atomic{int} refcount;
}
<*
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
*>
macro retain(refcounted)
{
if (refcounted)
{
assert(refcounted.refcount.load(RELAXED) >= 0, "Retaining zombie");
refcounted.refcount.add(1, RELAXED);
}
return refcounted;
}
<*
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
@require !$defined(refcounted.dealloc()) ||| $typeof(refcounted.dealloc()) == void
: "Expected refcounted type to have a valid dealloc"
*>
macro void release(refcounted)
{
if (!refcounted) return;
assert(refcounted.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
if (refcounted.refcount.sub(1, RELAXED) == 0)
{
thread::fence(ACQUIRE);
$if $defined(refcounted.dealloc):
refcounted.dealloc();
$else
free(refcounted);
$endif
}
}

View File

@@ -22,11 +22,27 @@ 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);
macro @enum_lookup_new($Type, $name, value)
{
$foreach $val : $Type.values:
if ($val.$eval($name) == value) return $val;
$endforeach
return NOT_FOUND~;
}
module std::core::runtime @if(env::FREESTANDING_WASM);
extern fn void __wasm_call_ctors();
fn void wasm_initialize() @extern("_initialize") @wasm
fn void wasm_initialize() @cname("_initialize") @wasm
{
// The linker synthesizes this to call constructors.
__wasm_call_ctors();

View File

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

View File

@@ -4,8 +4,8 @@
module std::core::runtime;
import std::core::test @public;
import std::core::mem::allocator @public;
import libc, std::time, std::io, std::sort;
import std::os::env;
import libc, std::time, std::io, std::sort, std::os;
alias TestFn = fn void();
@@ -14,10 +14,12 @@ TestContext* test_context @private;
struct TestContext
{
JmpBuf buf;
// Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add'
<* Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add' *>
String test_filter;
// Triggers debugger breakpoint when assert or test:: checks failed
<* Triggers debugger breakpoint when assert or test:: checks failed *>
bool breakpoint_on_assert;
<* Controls level of printed logs *>
LogPriority log_level;
// internal state
bool assert_print_backtrace;
@@ -25,6 +27,8 @@ struct TestContext
bool is_in_panic;
bool is_quiet_mode;
bool is_no_capture;
bool sort;
bool check_leaks;
String current_test_name;
TestFn setup_fn;
TestFn teardown_fn;
@@ -86,7 +90,21 @@ fn bool terminal_has_ansi_codes() @local => @pool()
$endif
}
fn void sig_bus_error(CInt i, void*, void* context) @local @if(env::POSIX)
{
panic_test("Bus error", "Unknown", "Unknown", 1, posix::stack_instruction(context));
}
fn void sig_segmentation_fault(CInt i, void*, void* context) @local @if(env::POSIX)
{
panic_test("Segmentation fault", "Unknown", "Unknown", 1, posix::stack_instruction(context));
}
fn void test_panic(String message, String file, String function, uint line) @local
{
panic_test(message, file, function, line);
}
fn void panic_test(String message, String file, String function, uint line, void* extra_trace = null) @local
{
if (test_context.is_in_panic) return;
test_context.is_in_panic = true;
@@ -96,7 +114,7 @@ fn void test_panic(String message, String file, String function, uint line) @loc
if (test_context.assert_print_backtrace)
{
$if env::NATIVE_STACKTRACE:
builtin::print_backtrace(message, 0);
builtin::print_backtrace(message, extra_trace ? 3 : 0, extra_trace);
$endif
}
io::printf("\nTest failed ^^^ ( %s:%s ) %s\n", file, line, message);
@@ -124,7 +142,7 @@ fn void mute_output() @local
File* stderr = io::stderr();
*stderr = test_context.fake_stdout;
*stdout = test_context.fake_stdout;
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
(void)test_context.fake_stdout.set_cursor(0)!!;
}
fn void unmute_output(bool has_error) @local
@@ -137,10 +155,9 @@ fn void unmute_output(bool has_error) @local
*stderr = test_context.stored.stderr;
*stdout = test_context.stored.stdout;
usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
ulong log_size = test_context.fake_stdout.cursor()!!;
if (has_error)
{
io::printf("\nTesting %s ", test_context.current_test_name);
io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
}
@@ -148,7 +165,7 @@ fn void unmute_output(bool has_error) @local
{
test_context.fake_stdout.write_byte('\n')!!;
test_context.fake_stdout.write_byte('\0')!!;
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
test_context.fake_stdout.set_cursor(0)!!;
io::printfn("\n========== TEST LOG ============");
io::printfn("%s\n", test_context.current_test_name);
@@ -166,11 +183,19 @@ fn void unmute_output(bool has_error) @local
(void)stdout.flush();
}
fn bool run_tests(String[] args, TestUnit[] tests) @private
{
usz max_name;
bool sort_tests = true;
bool check_leaks = true;
if (!tests.len)
{
io::printn("There are no test units to run.");
return true; // no tests == technically a pass
}
$if !env::NO_LIBC && env::POSIX:
posix::install_signal_handler(libc::SIGBUS, &sig_bus_error);
posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
$endif
foreach (&unit : tests)
{
if (max_name < unit.name.len) max_name = unit.name.len;
@@ -179,6 +204,9 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
{
.assert_print_backtrace = true,
.breakpoint_on_assert = false,
.sort = true,
.check_leaks = true,
.log_level = LogPriority.ERROR,
.test_filter = "",
.has_ansi_codes = terminal_has_ansi_codes(),
.stored.allocator = mem,
@@ -192,10 +220,11 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
case "--test-breakpoint":
context.breakpoint_on_assert = true;
case "--test-nosort":
sort_tests = false;
context.sort = false;
case "--test-noleak":
check_leaks = false;
context.check_leaks = false;
case "--test-nocapture":
case "--test-show-output":
context.is_no_capture = true;
case "--noansi":
context.has_ansi_codes = false;
@@ -211,13 +240,30 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
}
context.test_filter = args[i + 1];
i++;
case "--test-log-level":
if (i == args.len - 1)
{
io::printn("Missing log level for argument `--test-log-level`.");
return false;
}
@pool()
{
String upper = args[i + 1].to_upper_copy(tmem);
if (catch @try(context.log_level, enum_by_name(LogPriority, upper)))
{
io::printn("Log level given to `--test-log-level` is not one of verbose, debug, info, warn, error or critical.");
return false;
}
};
i++;
default:
io::printfn("Unknown argument: %s", args[i]);
}
}
test_context = &context;
log::set_priority_all(test_context.log_level);
if (sort_tests)
if (context.sort)
{
quicksort(tests, &cmp_test_unit);
}
@@ -277,14 +323,17 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
{
mute_output();
mem.clear();
if (check_leaks) allocator::thread_allocator = &mem;
unit.func();
if (context.check_leaks) allocator::thread_allocator = &mem;
@pool()
{
unit.func();
};
// track cleanup that may take place in teardown_fn
if (context.teardown_fn)
{
context.teardown_fn();
}
if (check_leaks) allocator::thread_allocator = context.stored.allocator;
if (context.check_leaks) allocator::thread_allocator = context.stored.allocator;
unmute_output(false); // all good, discard output
if (mem.has_leaks())
@@ -305,7 +354,7 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
}
mem.free();
}
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count > 1 ? "s" : "");
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count != 1 ? "s" : "");
int n_failed = test_count - tests_passed - tests_skipped;
io::printf("Test Result: %s%s%s: ",

View File

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

View File

@@ -2,16 +2,16 @@ module std::core::sanitizer::tsan;
typedef MutexFlags = inline CUInt;
const MutexFlags MUTEX_LINKER_INIT = 1 << 0;
const MutexFlags MUTEX_WRITE_REENTRANT = 1 << 1;
const MutexFlags MUTEX_READ_REENTRANT = 1 << 2;
const MutexFlags MUTEX_NOT_STATIC = 1 << 8;
const MutexFlags MUTEX_READ_LOCK = 1 << 3;
const MutexFlags MUTEX_TRY_LOCK = 1 << 4;
const MutexFlags MUTEX_TRY_LOCK_FAILED = 1 << 5;
const MutexFlags MUTEX_RECURSIVE_LOCK = 1 << 6;
const MutexFlags MUTEX_RECURSIVE_UNLOCK = 1 << 7;
const MutexFlags MUTEX_TRY_READ_LOCK = MUTEX_READ_LOCK | MUTEX_TRY_LOCK;
const MutexFlags MUTEX_LINKER_INIT = (MutexFlags)1 << 0;
const MutexFlags MUTEX_WRITE_REENTRANT = (MutexFlags)1 << 1;
const MutexFlags MUTEX_READ_REENTRANT = (MutexFlags)1 << 2;
const MutexFlags MUTEX_NOT_STATIC = (MutexFlags)1 << 8;
const MutexFlags MUTEX_READ_LOCK = (MutexFlags)1 << 3;
const MutexFlags MUTEX_TRY_LOCK = (MutexFlags)1 << 4;
const MutexFlags MUTEX_TRY_LOCK_FAILED = (MutexFlags)1 << 5;
const MutexFlags MUTEX_RECURSIVE_LOCK = (MutexFlags)1 << 6;
const MutexFlags MUTEX_RECURSIVE_UNLOCK = (MutexFlags)1 << 7;
const MutexFlags MUTEX_TRY_READ_LOCK = MUTEX_READ_LOCK | MUTEX_TRY_LOCK;
const MutexFlags MUTEX_TRY_READ_LOCK_FAILED = MUTEX_TRY_READ_LOCK | MUTEX_TRY_LOCK_FAILED;
macro void mutex_create(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_create(addr, flags); $endif }

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

@@ -0,0 +1,169 @@
module std::core::array;
<*
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>
{
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,46 +1,90 @@
module std::core::string;
import std::ascii;
import std::io;
import std::io, std::ascii;
import std::core::mem::allocator;
typedef String @if(!$defined(String)) = inline char[];
typedef ZString = inline char*;
typedef String @constinit @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 @constinit = 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 UTF16 encoded string at compile time.
@param $string : "The string to encode"
*>
macro Char16[] @char16(String $string) @builtin
{
return $$wstr16($string)[..^2];
}
macro String @sprintf(String $format, ...) @builtin @const
{
return $$sprintf($format, $vasplat);
}
<*
Return a temporary ZString created using the formatting function.
@@ -54,7 +98,8 @@ fn ZString tformat_zstr(String fmt, args...) @format(0)
}
<*
Return a new String created using the formatting function.
Return a new String created using the formatting function, this function will implicitly
use the temp allocator.
@param [inout] allocator : `The allocator to use`
@param [in] fmt : `The formatting string`
@@ -66,6 +111,28 @@ fn String format(Allocator allocator, String fmt, args...) @format(1) => @pool()
return str.copy_str(allocator);
}
<*
Return a new String created using the formatting function, the resulting string must fit the buffer.
@param [inout] buffer : `The buffer to use`
@param [in] fmt : `The formatting string`
*>
fn String bformat(char[] buffer, String fmt, args...) @format(1)
{
Formatter f;
OutputFn format_fn = fn void?(void* buf, char c) {
char[]* buffer_ref = buf;
char[] buffer = *buffer_ref;
if (buffer.len == 0) return io::BUFFER_EXCEEDED~;
buffer[0] = c;
*buffer_ref = buffer[1..];
};
char[] buffer_copy = buffer;
f.init(format_fn, &buffer_copy);
usz len = f.vprintf(fmt, args)!!;
return (String)buffer[:len];
}
<*
Return a temporary String created using the formatting function.
@@ -82,7 +149,7 @@ fn String tformat(String fmt, args...) @format(0)
Check if a character is in a set.
@param c : `the character to check`
@param [in] set : `The formatting string`
@param [in] set : `String containing the characters`
@pure
@return `True if a character is in the set`
*>
@@ -92,31 +159,90 @@ macro bool char_in_set(char c, String set)
return false;
}
<*
Join together an array of strings via a "joiner" sequence, which is inserted between each element.
@param [&inout] allocator : "The allocator to use."
@param [in] s : "An array of strings to join in sequence."
@param [in] joiner : "The string used to join each element of `s`."
@return "A single string containing the result, allocated via `allocator`, safe to convert to a ZString."
*>
fn String join(Allocator allocator, String[] s, String joiner)
{
if (!s)
{
return (String)allocator::new_array(allocator, char, 2)[:0];
}
usz total_size = joiner.len * s.len;
usz joiner_len = joiner.len;
usz total_size = joiner_len * (s.len - 1) + 1;
foreach (String* &str : s)
{
total_size += str.len;
}
@pool()
char[] data = allocator::alloc_array(allocator, char, total_size);
usz offset = s[0].len;
data[:offset] = s[0][:offset];
foreach (String* &str : s[1..])
{
DString res = dstring::temp_with_capacity(total_size);
res.append(s[0]);
foreach (String* &str : s[1..])
{
res.append(joiner);
res.append(*str);
}
return res.copy_str(allocator);
data[offset:joiner_len] = joiner[:joiner_len];
offset += joiner_len;
usz len = str.len;
data[offset:len] = str.[:len];
offset += len;
};
data[offset] = 0;
return (String)data[:offset];
}
<* Alias for `string::join` using the temp allocator. *>
macro String tjoin(String[] s, String joiner) => join(tmem, s, 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()
{
Splitter s = self.tokenize_all(needle);
DString d;
d.init(tmem, new_str.len * 2 + self.len + 16);
(void)d.append(s.next());
while (try element = s.next())
{
d.append(new_str);
d.append(element);
}
return d.copy_str(allocator);
}
<*
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)
{
Splitter s = self.tokenize_all(needle);
DString d;
d.init(tmem, new_str.len * 2 + self.len + 16);
(void)d.append(s.next());
while (try element = s.next())
{
d.append(new_str);
d.append(element);
}
return d.str_view();
}
<*
Remove characters from the front and end of a string.
@@ -125,11 +251,28 @@ fn String join(Allocator allocator, String[] s, String joiner)
@pure
@return `a substring of the string passed in`
*>
fn String String.trim(self, String to_trim = "\t\n\r ")
fn String String.trim(self, String to_trim = " \n\t\r\f\v")
{
return self.trim_left(to_trim).trim_right(to_trim);
}
<*
Remove characters from the front and end of a string.
@param [in] self : `The string to trim`
@param to_trim : `The set of characters to trim, defaults to whitespace`
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_charset(self, AsciiCharset to_trim = ascii::WHITESPACE_SET)
{
usz start = 0;
usz len = self.len;
while (start < len && to_trim.contains(self[start])) start++;
while (len > start && to_trim.contains(self[len - 1])) len--;
return self[start..len - 1];
}
<*
Remove characters from the front of a string.
@@ -138,7 +281,7 @@ fn String String.trim(self, String to_trim = "\t\n\r ")
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_left(self, String to_trim = "\t\n\r ")
fn String String.trim_left(self, String to_trim = " \n\t\r\f\v")
{
usz start = 0;
usz len = self.len;
@@ -155,7 +298,7 @@ fn String String.trim_left(self, String to_trim = "\t\n\r ")
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_right(self, String to_trim = "\t\n\r ")
fn String String.trim_right(self, String to_trim = " \n\t\r\f\v")
{
usz len = self.len;
while (len > 0 && char_in_set(self[len - 1], to_trim)) len--;
@@ -231,7 +374,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)
{
@@ -241,7 +384,7 @@ fn String[] String.split(self, Allocator allocator, String delimiter, usz max =
bool no_more = false;
while (!no_more)
{
usz? index = i == max - 1 ? NOT_FOUND? : self.index_of(delimiter);
usz? index = i == max - 1 ? NOT_FOUND~ : self.index_of(delimiter);
String res @noinit;
if (try index)
{
@@ -290,7 +433,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)
@@ -300,7 +443,7 @@ fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz ma
bool no_more = false;
while (!no_more)
{
usz? index = i == max - 1 ? NOT_FOUND? : s.index_of(delimiter);
usz? index = i == max - 1 ? NOT_FOUND~ : s.index_of(delimiter);
String res @noinit;
if (try index)
{
@@ -318,7 +461,7 @@ fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz ma
}
if (i == max_capacity)
{
return BUFFER_EXCEEDED?;
return BUFFER_EXCEEDED~;
}
buffer[i++] = res;
}
@@ -338,6 +481,51 @@ fn bool String.contains(s, String substr)
return @ok(s.index_of(substr));
}
<*
Check if a character is found in the string.
@param [in] s
@param character : "The character to look for."
@pure
@return "true if the string contains the character, false otherwise"
*>
fn bool String.contains_char(s, char character)
{
return @ok(s.index_of_char(character));
}
<*
Check how many non-overlapping instances of a substring there is.
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.
@@ -354,7 +542,7 @@ fn usz? String.index_of_char(self, char character)
{
if (c == character) return i;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
<*
@@ -377,7 +565,7 @@ fn usz? String.index_of_chars(String self, char[] characters)
}
}
return NOT_FOUND?;
return NOT_FOUND~;
}
<*
@@ -394,12 +582,12 @@ fn usz? String.index_of_chars(String self, char[] characters)
fn usz? String.index_of_char_from(self, char character, usz start_index)
{
usz len = self.len;
if (len <= start_index) return NOT_FOUND?;
if (len <= start_index) return NOT_FOUND~;
for (usz i = start_index; i < len; i++)
{
if (self[i] == character) return i;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
<*
@@ -418,7 +606,7 @@ fn usz? String.rindex_of_char(self, char character)
{
if (c == character) return i;
}
return NOT_FOUND?;
return NOT_FOUND~;
}
<*
@@ -443,7 +631,7 @@ fn usz? String.index_of(self, String substr)
if (c == first && self[i : needed] == substr) return i;
}
}
return NOT_FOUND?;
return NOT_FOUND~;
}
<*
@@ -468,7 +656,21 @@ fn usz? String.rindex_of(self, String substr)
if (c == first && self[i : needed] == substr) return i;
}
}
return NOT_FOUND?;
return NOT_FOUND~;
}
fn bool ZString.eq(self, ZString other) @operator(==)
{
char* a = self;
char* b = other;
if (a == b) return true;
if (!a || !b) return false;
for (;; a++, b++)
{
char c = *a;
if (c != *b) return false;
if (!c) return true;
}
}
fn String ZString.str_view(self)
@@ -489,12 +691,17 @@ fn usz ZString.char_len(str)
fn usz ZString.len(self)
{
usz len = 0;
char* ptr = (char*)self;
while (char c = ptr++[0]) len++;
usz len;
for (char* ptr = (char*)self; *ptr; ptr++) len++;
return len;
}
fn usz WString.len(self)
{
usz len;
for (Char16* ptr = (Char16*)self; *ptr; ptr++) len++;
return len;
}
fn ZString String.zstr_copy(self, Allocator allocator)
{
@@ -637,6 +844,92 @@ fn String String.to_upper_copy(self, Allocator allocator)
return copy;
}
fn String String.capitalize_copy(self, Allocator allocator)
{
String s = self.copy(allocator);
if (s.len > 0 && s[0].is_lower())
{
s[0] &= (char)~0x20;
}
return s;
}
<*
Convert a string from `snake_case` to PascalCase.
@param [in] self
@return `"FooBar" from "foo_bar" the resulting pointer may safely be cast to ZString.`
*>
fn String String.snake_to_pascal_copy(self, Allocator allocator)
{
Splitter splitter = self.tokenize("_");
char[] new_string = allocator::alloc_array(allocator, char, self.len + 1);
usz index = 0;
while (try s = splitter.next())
{
assert(s.len > 0);
char c = s[0];
if (c.is_lower()) c = c.to_upper();
new_string[index++] = c;
s = s[1..];
new_string[index:s.len] = s[..];
index += s.len;
}
new_string[index] = 0;
return (String)new_string[:index];
}
<*
Movifies the current string from `snake_case` to PascalCase.
@param [inout] self
*>
fn void String.convert_snake_to_pascal(&self)
{
Splitter splitter = self.tokenize("_");
String new_string = *self;
usz index = 0;
while (try s = splitter.next())
{
assert(s.len > 0);
char c = s[0];
if (c.is_lower()) c = c.to_upper();
new_string[index++] = c;
s = s[1..];
new_string[index:s.len] = s[..];
index += s.len;
}
*self = new_string[:index];
}
<*
Convert a string from `PascalCase` to `snake_case`.
@param [in] self
@return `"foo_bar" from "FooBar" the resulting pointer may safely be cast to ZString.`
*>
fn String String.pascal_to_snake_copy(self, Allocator allocator) => @pool()
{
DString d;
d.init(tmem, (usz)(self.len * 1.5));
usz index = 0;
foreach (i, c : self)
{
if (c.is_upper())
{
if (i > 0 && ((self[i - 1].is_lower() || self[i - 1].is_digit()) || (i < self.len - 1 && self[i + 1].is_lower())))
{
d.append_char('_');
}
d.append_char(c.to_lower());
continue;
}
d.append_char(c);
}
return d.copy_str(allocator);
}
fn StringIterator String.iterator(self)
{
return { self, 0 };
@@ -692,22 +985,69 @@ 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) @deprecated("Unsafe, use copy instead")
{
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) @deprecated("Unsafe, use zstr_tcopy instead")
{
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++;
if (len == index) return EMPTY_STRING?;
while (index < len && ptr[index].is_blank()) index++;
if (len == index) return EMPTY_STRING~;
bool is_negative;
switch (self[index])
{
case '-':
if ($Type.min == 0) return NEGATIVE_VALUE?;
if ($Type.min == 0) return NEGATIVE_VALUE~;
is_negative = true;
index++;
case '+':
@@ -715,7 +1055,7 @@ macro String.to_integer(self, $Type, int base = 10)
default:
break;
}
if (len == index) return MALFORMED_INTEGER?;
if (len == index) return MALFORMED_INTEGER~;
$Type base_used = ($Type)base;
if (self[index] == '0' && base == 10)
{
@@ -738,7 +1078,7 @@ macro String.to_integer(self, $Type, int base = 10)
default:
break;
}
if (len == index) return MALFORMED_INTEGER?;
if (len == index) return MALFORMED_INTEGER~;
}
$Type value = 0;
while (index != len)
@@ -746,24 +1086,20 @@ 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?;
case c < 'a' || c > 'f': return MALFORMED_INTEGER~;
default: c -= 'a' - 10;
}
if (c >= base_used) return MALFORMED_INTEGER?;
if (c >= base_used) return MALFORMED_INTEGER~;
do
{
if (is_negative)
{
$Type new_value = value * base_used - c;
if (new_value > value) return INTEGER_OVERFLOW?;
value = new_value;
value = value.overflow_mul(base_used).overflow_sub(c) ?? INTEGER_OVERFLOW~!;
break;
}
$Type new_value = value * base_used + c;
if (new_value < value) return INTEGER_OVERFLOW?;
value = new_value;
value = value.overflow_mul(base_used).overflow_add(c) ?? INTEGER_OVERFLOW~!;
};
}
return value;
@@ -784,50 +1120,49 @@ 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 };
}
struct Splitter
<*
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)
{
String string;
String split;
usz current;
bool tokenize;
int last_index;
return {
.string = self,
.split = split,
.type = skip_last ? TOKENIZE_ALL_SKIP_LAST : TOKENIZE_ALL
};
}
fn void Splitter.reset(&self)
fn Splitter String.splitter(self, String split) @deprecated("Use tokenize_all instead")
{
self.current = 0;
return self.tokenize_all(split, skip_last: true);
}
fn String? Splitter.next(&self)
{
while (true)
{
usz len = self.string.len;
usz current = self.current;
if (current >= len) return NO_MORE_ELEMENT?;
String remaining = self.string[current..];
usz? next = remaining.index_of(self.split);
if (try next)
{
self.current = current + next + self.split.len;
if (!next && self.tokenize) continue;
return remaining[:next];
}
self.current = len;
return remaining;
}
}
<*
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;
@@ -839,4 +1174,72 @@ macro String from_struct(Allocator allocator, x)
};
}
<*
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;
SplitterType type;
int last_index;
}
fn void Splitter.reset(&self)
{
self.current = 0;
}
fn bool Splitter.at_end(&self)
{
return self.current > self.string.len;
}
fn String? Splitter.next(&self)
{
while (true)
{
usz len = self.string.len;
usz current = self.current;
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.type == TOKENIZE) continue;
return remaining[:next];
}
self.current = len + 1;
return remaining;
}
}

View File

@@ -0,0 +1,261 @@
// Copyright (c) 2024-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.
<*
This module provides functionality for escaping and unescaping strings
with standard C-style escape sequences, similar to what's used in JSON
and other string literals.
*>
module std::core::string;
import std::io;
faultdef INVALID_ESCAPE_SEQUENCE, UNTERMINATED_STRING, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE;
<*
Escape a string by adding quotes and converting special characters to escape sequences.
@param allocator : "The allocator to use for the result"
@param s : "The string to escape"
@param strip_quotes : "Do not include beginning and end quotes, defaults to false"
@return "The escaped string with surrounding quotes, can safely be cast to ZString"
*>
fn String String.escape(String s, Allocator allocator, bool strip_quotes = true)
{
// Conservative allocation: most strings need minimal escaping
usz initial_capacity = s.len + s.len / 5 + 2; // ~1.2x + quotes
if (allocator == tmem)
{
DString result = dstring::new_with_capacity(tmem, initial_capacity);
escape_dstring(s, result, strip_quotes);
return result.str_view();
}
@pool()
{
DString result = dstring::temp_with_capacity(initial_capacity);
escape_dstring(s, result, strip_quotes);
return result.copy_str(allocator);
};
}
fn void escape_dstring(String s, DString result, bool strip_quotes) @private
{
if (!strip_quotes) result.append_char('"');
foreach (char c : s)
{
switch (c)
{
case '"': result.append(`\"`);
case '\\': result.append(`\\`);
case '\b': result.append(`\b`);
case '\f': result.append(`\f`);
case '\n': result.append(`\n`);
case '\r': result.append(`\r`);
case '\t': result.append(`\t`);
case '\v': result.append(`\v`);
case '\0': result.append(`\0`);
default:
if (c >= 32 && c <= 126)
{
// Printable ASCII
result.append_char(c);
}
else
{
// Non-printable, use hex escape
result.appendf("\\x%02x", (uint)c);
}
}
}
if (!strip_quotes) result.append_char('"');
}
<*
Escape a string using the temp allocator.
@param s : "The string to escape"
@param strip_quotes : "Do not include beginning and end quotes, defaults to false"
@return "The escaped string with surrounding quotes"
*>
fn String String.tescape(String s, bool strip_quotes = false) => s.escape(tmem, strip_quotes);
<*
Calculate the length needed for an escaped string (including quotes).
@param s : "The string to check"
@return "The length needed for the escaped version"
*>
fn usz escape_len(String s)
{
usz len = 2; // For quotes
foreach (char c : s)
{
switch (c)
{
case '"':
case '\\':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
case '\v':
case '\0':
len += 2; // \X
default:
if (c >= 32 && c <= 126)
{
len += 1;
}
else
{
len += 4; // \xHH
}
}
}
return len;
}
<*
Unescape a quoted string by parsing escape sequences.
@param allocator : "The allocator to use for the result"
@param s : "The quoted string to unescape"
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
@param lenient : "Be lenient with escapes, resolving unknown sequences to the escape character, defaults to false"
@return "The unescaped string without quotes, safe to convert to ZString"
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
*>
fn String? String.unescape(String s, Allocator allocator, bool allow_unquoted = false, bool lenient = false)
{
if (s.len >= 2 && s[0] == '"' && s[^1] == '"')
{
// Remove quotes.
s = s[1:^2];
}
else if (!allow_unquoted) return UNTERMINATED_STRING~;
// Handle empty string case
if (!s.len)
{
return "".copy(allocator);
}
if (allocator == tmem)
{
DString result = dstring::new_with_capacity(tmem, s.len);
unescape_dstring(s, result, allow_unquoted, lenient)!;
return result.str_view();
}
@pool()
{
DString result = dstring::temp_with_capacity(s.len);
unescape_dstring(s, result, allow_unquoted, lenient)!;
return result.copy_str(allocator);
};
}
fn void? unescape_dstring(String s, DString result, bool allow_unquoted = false, bool lenient = false) @private
{
usz len = s.len;
for (usz i = 0; i < len; i++)
{
char c = s[i];
if (c != '\\')
{
result.append_char(c);
continue;
}
// Handle escape sequence
if (i + 1 >= len) return INVALID_ESCAPE_SEQUENCE~;
char escape_char = s[++i];
switch (escape_char)
{
case '"': result.append_char('"');
case '\\': result.append_char('\\');
case '/': result.append_char('/');
case 'b': result.append_char('\b');
case 'f': result.append_char('\f');
case 'n': result.append_char('\n');
case 'r': result.append_char('\r');
case 't': result.append_char('\t');
case 'v': result.append_char('\v');
case '0': result.append_char('\0');
case 'x':
// Hex escape \xHH
if (i + 2 >= len) return INVALID_HEX_ESCAPE~;
char h1 = s[++i];
char h2 = s[++i];
if (!h1.is_xdigit() || !h2.is_xdigit()) return INVALID_HEX_ESCAPE~;
uint val = h1 > '9' ? (h1 | 32) - 'a' + 10 : h1 - '0';
val = val << 4;
val += h2 > '9' ? (h2 | 32) - 'a' + 10 : h2 - '0';
result.append_char((char)val);
case 'u':
// Unicode escape \uHHHH
if (i + 4 >= len) return INVALID_UNICODE_ESCAPE~;
uint val;
for (int j = 0; j < 4; j++)
{
char hex_char = s[++i];
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~;
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
}
result.append_char32(val);
case 'U':
// Unicode escape \UHHHHHHHH
if (i + 8 >= len) return INVALID_UNICODE_ESCAPE~;
uint val;
for (int j = 0; j < 8; j++)
{
char hex_char = s[++i];
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~;
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
}
result.append_char32(val);
default:
if (!lenient) return INVALID_ESCAPE_SEQUENCE~;
result.append_char(escape_char);
}
}
}
<*
Unescape a quoted string using the temp allocator.
@param s : "The quoted string to unescape"
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
@param lenient : "Be lenient with escapes, resolving unknown sequences to the escape character, defaults to false"
@return "The unescaped string without quotes"
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
*>
fn String? String.tunescape(String s, bool allow_unquoted = false, bool lenient = false) => s.unescape(tmem, allow_unquoted, lenient);
<*
Check if a character needs to be escaped in a string literal.
@param c : "The character to check"
@return "True if the character needs escaping"
*>
fn bool needs_escape(char c)
{
switch (c)
{
case '"':
case '\\':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
case '\v':
case '\0':
return true;
default:
return c < 32 || c > 126;
}
}

View File

@@ -15,7 +15,7 @@ fn Char32? StringIterator.next(&self)
{
usz len = self.utf8.len;
usz current = self.current;
if (current >= len) return NO_MORE_ELEMENT?;
if (current >= len) return NO_MORE_ELEMENT~;
usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
self.current += read;
@@ -26,7 +26,7 @@ fn Char32? StringIterator.peek(&self)
{
usz len = self.utf8.len;
usz current = self.current;
if (current >= len) return NO_MORE_ELEMENT?;
if (current >= len) return NO_MORE_ELEMENT~;
usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
return res;
@@ -43,7 +43,7 @@ fn Char32? StringIterator.get(&self)
usz current = self.current;
usz read = (len - current < 4 ? len - current : 4);
usz index = current > read ? current - read : 0;
if (index >= len) return NO_MORE_ELEMENT?;
if (index >= len) return NO_MORE_ELEMENT~;
Char32 res = conv::utf8_to_char32(&self.utf8[index], &read)!;
return res;
}

View File

@@ -64,7 +64,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
got_rad = true;
if (index == last_char)
{
if (!got_digit) return MALFORMED_FLOAT?;
if (!got_digit) return MALFORMED_FLOAT~;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
@@ -83,7 +83,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
switch
{
case c == '.':
if (got_rad) return MALFORMED_FLOAT?;
if (got_rad) return MALFORMED_FLOAT~;
got_rad = true;
lrp = dc;
case k < KMAX - 3:
@@ -113,24 +113,24 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
c = chars[++index];
}
if (!got_rad) lrp = dc;
if (!got_digit) return MALFORMED_FLOAT?;
if (!got_digit) return MALFORMED_FLOAT~;
if ((c | 32) == 'e')
{
if (last_char == index) return MALFORMED_FLOAT?;
long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT?!;
if (last_char == index) return MALFORMED_FLOAT~;
long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT~!;
lrp += e10;
}
else if (index != last_char)
{
return MALFORMED_FLOAT?;
return MALFORMED_FLOAT~;
}
// Handle zero specially to avoid nasty special cases later
if (!x[0]) return sign * 0.0;
// Optimize small integers (w/no exponent) and over/under-flow
if (lrp == dc && dc < 10 && ($bits > 30 || (ulong)x[0] >> $bits == 0)) return sign * (double)x[0];
if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE?;
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE~;
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE~;
// Align incomplete final B1B digit
if (j)
@@ -158,7 +158,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
if (rp % 9)
{
long rpm9 = rp >= 0 ? rp % 9 : rp % 9 + 9;
int p10 = P10S[8 - rpm9];
uint p10 = P10S[8 - rpm9];
uint carry = 0;
for (k = a; k != z; k++)
{
@@ -320,7 +320,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
y *= 0.5;
e2++;
}
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT?;
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT~;
}
return math::scalbn(y, e2);
}
@@ -351,7 +351,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
got_rad = true;
if (index == last_char)
{
if (!got_digit) return MALFORMED_FLOAT?;
if (!got_digit) return MALFORMED_FLOAT~;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
@@ -369,7 +369,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
{
if (c == '.')
{
if (got_rad) return MALFORMED_FLOAT?;
if (got_rad) return MALFORMED_FLOAT~;
got_rad = true;
rp = dc;
}
@@ -393,20 +393,20 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
if (index == last_char) break;
c = chars[++index];
}
if (!got_digit) return MALFORMED_FLOAT?;
if (!got_digit) return MALFORMED_FLOAT~;
if (!got_rad) rp = dc;
for (; dc < 8; dc++) x *= 16;
long e2;
if ((c | 32) == 'p')
{
long e2val = String.to_long((String)chars[index + 1..]) ?? (MALFORMED_FLOAT?)!;
long e2val = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT~!;
e2 = e2val;
}
e2 += 4 * rp - 32;
if (!x) return sign * 0.0;
if (e2 > -$emin) return FLOAT_OUT_OF_RANGE?;
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
if (e2 > -$emin) return FLOAT_OUT_OF_RANGE~;
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE~;
while (x < 0x80000000)
{
@@ -441,7 +441,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
}
y = bias + sign * (double)x + sign * y;
y -= bias;
if (!y) return FLOAT_OUT_OF_RANGE?;
if (!y) return FLOAT_OUT_OF_RANGE~;
return math::scalbn(y, (int)e2);
}
@@ -462,8 +462,8 @@ macro String.to_real(chars, $Type) @private
$error "Unexpected type";
$endswitch
while (chars.len && chars[0] == ' ') chars = chars[1..];
if (!chars.len) return MALFORMED_FLOAT?;
chars = chars.trim();
if (!chars.len) return MALFORMED_FLOAT~;
if (chars.len != 1)
{
@@ -476,6 +476,9 @@ macro String.to_real(chars, $Type) @private
chars = chars[1..];
}
}
chars = chars.trim();
if (!chars.len) return MALFORMED_FLOAT~;
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
if (chars == "NAN" || chars == "nan") return $Type.nan;

View File

@@ -12,7 +12,7 @@ faultdef DIVISION_BY_ZERO;
fn double? divide(int a, int b)
{
if (b == 0) return MathError.DIVISION_BY_ZERO?;
if (b == 0) return MathError.DIVISION_BY_ZERO~;
return (double)(a) / (double)(b);
}
@@ -26,6 +26,7 @@ fn void? test_div() @test
test::le(2, 3);
test::eq_approx(m::divide(1, 3)!, 0.333, places: 3);
test::@check(2 == 2, "divide: %d", divide(6, 3)!);
test::@error(m::divide(3, 0));
test::@error(m::divide(3, 0), MathError.DIVISION_BY_ZERO);
}
@@ -78,14 +79,15 @@ macro @check(#condition, String format = "", args...)
}
<*
Check if function returns specific error
Check if function returns (specific) error
@param #funcresult : `result of function execution`
@param error_expected : `expected error of function execution`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro @error(#funcresult, fault error_expected)
macro @error(#funcresult, fault error_expected = ...)
{
$if $defined(error_expected):
if (catch err = #funcresult)
{
if (err != error_expected)
@@ -96,6 +98,10 @@ macro @error(#funcresult, fault error_expected)
return;
}
print_panicf("`%s` error [%s] was not returned.", $stringify(#funcresult), error_expected);
$else
if (catch err = #funcresult) return;
print_panicf("`%s` unexpectedly did not return error.", $stringify(#funcresult));
$endif
}
<*

View File

@@ -7,6 +7,7 @@ faultdef VALUE_OUT_OF_RANGE, VALUE_OUT_OF_UNSIGNED_RANGE;
<*
@require $Type.kindof.is_int() : "Type was not an integer"
@require (bool)v.type : "The value was empty"
@require v.type.kindof == ENUM : "Value was not an enum"
*>
macro any_to_enum_ordinal(any v, $Type)
@@ -16,6 +17,7 @@ macro any_to_enum_ordinal(any v, $Type)
<*
@require $Type.kindof.is_int() : "Type was not an integer"
@require (bool)v.type : "The value was empty"
@require v.type.kindof.is_int() : "Value was not an integer"
*>
macro any_to_int(any v, $Type)
@@ -29,47 +31,47 @@ macro any_to_int(any v, $Type)
{
case ichar:
ichar c = *(char*)v.ptr;
if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
return ($Type)c;
case short:
short s = *(short*)v.ptr;
if (is_mixed_signed && s < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && s < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
if (s > max || s < min) return VALUE_OUT_OF_RANGE~;
return ($Type)s;
case int:
int i = *(int*)v.ptr;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
return ($Type)i;
case long:
long l = *(long*)v.ptr;
if (is_mixed_signed && l < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && l < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
if (l > max || l < min) return VALUE_OUT_OF_RANGE~;
return ($Type)l;
case int128:
int128 i = *(int128*)v.ptr;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~;
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
return ($Type)i;
case char:
char c = *(char*)v.ptr;
if (c > max) return VALUE_OUT_OF_RANGE?;
if (c > max) return VALUE_OUT_OF_RANGE~;
return ($Type)c;
case ushort:
ushort s = *(ushort*)v.ptr;
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
if (s > max || s < min) return VALUE_OUT_OF_RANGE~;
return ($Type)s;
case uint:
uint i = *(uint*)v.ptr;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
return ($Type)i;
case ulong:
ulong l = *(ulong*)v.ptr;
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
if (l > max || l < min) return VALUE_OUT_OF_RANGE~;
return ($Type)l;
case uint128:
uint128 i = *(uint128*)v.ptr;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE~;
return ($Type)i;
default:
unreachable();
@@ -96,21 +98,33 @@ macro bool is_subtype_of($Type, $OtherType)
}
macro bool is_numerical($Type)
{
var $kind = $Type.kindof;
$if $kind == TypeKind.DISTINCT:
return is_numerical($typefrom($Type.inner));
$else
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|| $kind == TypeKind.VECTOR;
$endif
$switch $Type.kindof:
$case TYPEDEF:
$case CONSTDEF:
return is_numerical($Type.inner);
$case SIGNED_INT:
$case UNSIGNED_INT:
$case FLOAT:
$case VECTOR:
return true;
$default:
return false;
$endswitch
}
fn bool TypeKind.is_int(kind) @inline
{
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
return kind == SIGNED_INT || kind == UNSIGNED_INT;
}
macro bool is_slice_convertable($Type)
macro bool TypeKind.@is_int($kind) @const
{
return $kind == SIGNED_INT ||| $kind == UNSIGNED_INT;
}
macro bool is_slice_convertable($Type) @deprecated("Use is_slice_convertible") => is_slice_convertible($Type);
macro bool is_slice_convertible($Type)
{
$switch $Type.kindof:
$case SLICE:
@@ -135,7 +149,7 @@ macro bool is_signed($Type) @const
$case FLOAT:
return true;
$case VECTOR:
return is_signed($typefrom($Type.inner));
return is_signed($Type.inner);
$default:
return false;
$endswitch
@@ -150,7 +164,7 @@ macro bool is_unsigned($Type) @const
$case UNSIGNED_INT:
return true;
$case VECTOR:
return is_unsigned($typefrom($Type.inner));
return is_unsigned($Type.inner);
$default:
return false;
$endswitch
@@ -158,8 +172,8 @@ macro bool is_unsigned($Type) @const
macro typeid flat_type($Type) @const
{
$if $Type.kindof == DISTINCT:
return flat_type($typefrom($Type.inner));
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF:
return flat_type($Type.inner);
$else
return $Type.typeid;
$endif
@@ -167,8 +181,8 @@ macro typeid flat_type($Type) @const
macro TypeKind flat_kind($Type) @const
{
$if $Type.kindof == DISTINCT:
return flat_type($typefrom($Type.inner));
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF:
return flat_type($Type.inner);
$else
return $Type.kindof;
$endif
@@ -184,6 +198,21 @@ macro bool is_ref_indexable($Type) @const
return $defined(&($Type){}[0]);
}
macro bool is_flat_intlike($Type) @const
{
$switch $Type.kindof:
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
$case VECTOR:
$case TYPEDEF:
$case CONSTDEF:
return is_flat_intlike($Type.inner);
$default:
return false;
$endswitch
}
macro bool is_intlike($Type) @const
{
$switch $Type.kindof:
@@ -203,8 +232,8 @@ macro bool is_underlying_int($Type) @const
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
$case DISTINCT:
return is_underlying_int($typefrom($Type.inner));
$case TYPEDEF:
return is_underlying_int($Type.inner);
$default:
return false;
$endswitch
@@ -231,8 +260,8 @@ macro bool is_vector($Type) @const
macro typeid inner_type($Type) @const
{
$if $Type.kindof == TypeKind.DISTINCT:
return inner_type($typefrom($Type.inner));
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF:
return inner_type($Type.inner);
$else
return $Type.typeid;
$endif
@@ -271,7 +300,8 @@ macro bool may_load_atomic($Type) @const
$case POINTER:
$case FLOAT:
return true;
$case DISTINCT:
$case TYPEDEF:
$case ENUM:
return may_load_atomic($Type.inner);
$default:
return false;
@@ -285,7 +315,8 @@ macro lower_to_atomic_compatible_type($Type) @const
$case SIGNED_INT:
$case UNSIGNED_INT:
return $Type.typeid;
$case DISTINCT:
$case TYPEDEF:
$case CONSTDEF:
return lower_to_atomic_compatible_type($Type.inner);
$case FLOAT:
$switch $Type:
@@ -305,8 +336,8 @@ macro lower_to_atomic_compatible_type($Type) @const
$endswitch
}
macro bool is_promotable_to_floatlike($Type) @const => types::is_floatlike($Type) || types::is_int($Type);
macro bool is_promotable_to_float($Type) @const => types::is_float($Type) || types::is_int($Type);
macro bool is_promotable_to_floatlike($Type) @const => types::is_floatlike($Type) ||| types::is_int($Type);
macro bool is_promotable_to_float($Type) @const => types::is_float($Type) ||| types::is_int($Type);
macro bool is_same_vector_type($Type1, $Type2) @const
{
@@ -317,9 +348,11 @@ macro bool is_same_vector_type($Type1, $Type2) @const
$endif
}
macro bool has_equals($Type) @const => $defined(($Type){} == ($Type){});
macro bool is_equatable_type($Type) @const
{
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):
$if $defined($Type.less) ||| $defined($Type.compare_to) ||| $defined($Type.equals):
return true;
$else
return $Type.is_eq;
@@ -331,7 +364,7 @@ macro bool is_equatable_type($Type) @const
*>
macro bool implements_copy($Type) @const
{
return $defined($Type.copy) && $defined($Type.free);
return $defined($Type.copy) &&& $defined($Type.free);
}
macro bool @equatable_value(#value) @const
@@ -341,13 +374,16 @@ macro bool @equatable_value(#value) @const
macro bool @comparable_value(#value) @const
{
$if $defined(#value.less) || $defined(#value.compare_to):
$if $defined(#value.less) ||| $defined(#value.compare_to):
return true;
$else
return $typeof(#value).is_ordered;
$endif
}
const CONST_ENUM @builtin @deprecated("Use TypeKind.CONSTDEF instead") = TypeKind.CONSTDEF;
const DISTINCT @builtin @deprecated("Use TypeKind.TYPEDEF instead") = TypeKind.TYPEDEF;
enum TypeKind : char
{
VOID,
@@ -359,6 +395,7 @@ enum TypeKind : char
FAULT,
ANY,
ENUM,
CONSTDEF,
STRUCT,
UNION,
BITSTRUCT,
@@ -367,7 +404,7 @@ enum TypeKind : char
ARRAY,
SLICE,
VECTOR,
DISTINCT,
TYPEDEF,
POINTER,
INTERFACE,
}

View File

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

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

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

View File

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

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