Compare commits

..

21 Commits

Author SHA1 Message Date
Christoffer Lerno
14d7fd1d08 Additional macro inlining error improvements. 2024-06-11 19:22:18 +02:00
Christoffer Lerno
43c11118ec Update tests for LLVM 19 2024-06-11 00:38:11 +02:00
Christoffer Lerno
407dec0238 Fix macro error trace. 2024-06-10 22:31:06 +02:00
Christoffer Lerno
9b21f411ba Use vswhere to find msvc. 2024-06-10 00:54:03 +02:00
Christoffer Lerno
cbb2cad2b8 Removed unused fields in find_msvc. 2024-06-09 17:13:29 +02:00
Christoffer Lerno
f99baf479e Compiler crash using enum nameof from different module #1205. 2024-06-09 16:51:32 +02:00
Christoffer Lerno
4fc1e39fa2 Improve inlining warning messages. Added index_of_char_from. 2024-06-09 14:38:57 +02:00
Christoffer Lerno
5a6443060e Improve inlining warning messages. 2024-06-08 22:31:36 +02:00
Christian Buttner
ab2e18e255 Allow linking libraries directly by file path. 2024-06-08 18:50:48 +02:00
Christoffer Lerno
4548c474bc Fix of default argument stacktrace. 2024-06-08 18:14:26 +02:00
Christoffer Lerno
c644af7ced Updated Linux stacktrace 2024-06-08 13:12:14 +02:00
Christoffer Lerno
f6697b33bf Error of @if depends on @if 2024-06-07 00:11:09 +02:00
Christoffer Lerno
a4df94d228 $foreach doesn't create an implicit syntactic scope. Fix of previous debug info fix. 2024-06-06 23:15:21 +02:00
Christoffer Lerno
edd0a4022b Improved debug information on defer. 2024-06-06 17:40:07 +02:00
Christoffer Lerno
f7e7e16c25 Fix problems using reflection on interface types #1203. 2024-06-03 21:59:18 +02:00
Christoffer Lerno
ddfc9313e0 Upgrade of mingw in CI 2024-05-26 23:53:27 +02:00
Christoffer Lerno
7e7a4094f1 Improve debug info exactness. 2024-05-25 23:35:57 +02:00
Christoffer Lerno
e90254da03 Rollback expression / macro unification. 2024-05-24 00:45:44 +02:00
Christoffer Lerno
c1b57f9391 Fix inline in different file. 2024-05-23 16:51:19 +02:00
Christoffer Lerno
522a7a9011 Add inline to macro expansion. 2024-05-23 15:54:09 +02:00
Christoffer Lerno
fc849c1440 0.6.0: init_new/init_temp removed. LinkedList API rewritten. List "pop" and "remove" function now return Optionals. RingBuffer API rewritten. Allocator interface changed. Deprecated Allocator, DString and mem functions removed. "identity" functions are now constants for Matrix and Complex numbers. @default implementations for interfaces removed. any* => any, same for interfaces. Emit local/private globals as "private" in LLVM, following C "static". Updated enum syntax. Add support [rgba] properties in vectors. Improved checks of aliased "void". Subarray -> slice. Fix of llvm codegen enum check. Improved alignment handling. Add --output-dir #1155. Removed List/Object append. GenericList renamed AnyList. Remove unused "unwrap". Fixes to cond. Optimize output in dead branches. Better checking of operator methods. Disallow any from implementing dynamic methods. Check for operator mismatch. Remove unnecessary bitfield. Remove numbering in --list* commands Old style enum declaration for params/type, but now the type is optional. Add note on #1086. Allow making distinct types out of "void", "typeid", "anyfault" and faults. Remove system linker build options. "Try" expressions must be simple expressions. Add optimized build to Mac tests. Register int. assert(false) only allowed in unused branches or in tests. Compile time failed asserts is a compile time error. Remove current_block_is_target. Bug when assigning an optional from an optional. Remove unused emit_zstring. Simplify phi code. Remove unnecessary unreachable blocks and remove unnecessary current_block NULL assignments. Proper handling of '.' and Win32 '//server' paths. Unify expression and macro blocks in the middle end. Add "no discard" to expression blocks with a return value. Detect "unsigned >= 0" as errors. Fix issue with distinct void as a member #1147. Improve callstack debug information #1184. Fix issue with absolute output-dir paths. Lambdas were not type checked thoroughly #1185. Fix compilation warning #1187. Request jump table using @jump for switches. Path normalization - fix possible null terminator out of bounds. Improved error messages on inlined macros. 2024-05-22 18:22:04 +02:00
2370 changed files with 30043 additions and 182362 deletions

View File

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

2
.gitattributes vendored
View File

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

2
.github/FUNDING.yml vendored
View File

@@ -3,7 +3,7 @@
github: [c3lang]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: c3lang
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username

View File

@@ -4,14 +4,11 @@ on:
push:
branches: [ master, dev, ci_testing, experiments ]
pull_request:
branches: [ master, dev ]
branches: [ master ]
env:
LLVM_RELEASE_VERSION_WINDOWS: 18
LLVM_RELEASE_VERSION_MAC: 17
LLVM_RELEASE_VERSION_LINUX: 17
LLVM_RELEASE_VERSION_UBUNTU20: 17
LLVM_DEV_VERSION: 21
LLVM_RELEASE_VERSION: 16
jobs:
build-msvc:
@@ -36,79 +33,62 @@ jobs:
- name: Compile and run some examples
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe compile-run -L C:\ --print-linking examples\hello_world_many.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\hello_world_many.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\time.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\fannkuch-redux.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\contextfree\boolerr.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\ls.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\load_world.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\process.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\args.c3 -- foo -bar "baz baz"
..\build\${{ matrix.build_type }}\c3c.exe compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run msvc_stack.c3
- name: Build testproject
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
cd resources/testproject
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32 --trust=full
dir build\llvm\windows-x64
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log --emit-llvm run hello_world_win32
dir build\llvm_ir
..\..\build\${{ matrix.build_type }}\c3c.exe clean
dir build\llvm\windows-x64
dir build\llvm_ir
- name: Build testproject lib
run: |
cd resources/testproject
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv build hello_world_win32_lib --trust=full
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv dynamic-lib add.c3
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv compile-run test.c3 -l ./add.lib
- name: Compile and run staticlib-test
run: |
cd resources/examples/staticlib-test
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv static-lib add.c3
..\..\..\build\${{ matrix.build_type }}\c3c.exe -vv compile-run test.c3 -l ./add.lib
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log build hello_world_win32_lib
- name: Vendor-fetch
run: |
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib
- name: Try raylib5
- name: Try raylib
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_arkanoid.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_snake.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_tetris.c3
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib --wincrt=none examples\raylib\raylib_arkanoid.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib --wincrt=none examples\raylib\raylib_snake.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib --wincrt=none examples\raylib\raylib_tetris.c3
- name: run compiler tests
run: |
cd test
python3.exe src/tester.py ..\build\${{ matrix.build_type }}\c3c.exe test_suite/
- name: Compile run unit tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
- name: run compiler tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-run -O1 src/test_suite_runner.c3 --stdlib ..\lib7 --enable-new-generics -- ..\build\${{ matrix.build_type }}\c3c.exe test_suite7/ --stdlib ../lib7 --no-terminal
- name: Test python script
run: |
py msvc_build_libraries.py --accept-license
dir msvc_sdk
- name: upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: c3-windows-${{ matrix.build_type }}
path: |
build\${{ matrix.build_type }}\c3c.exe
build\${{ matrix.build_type }}\c3c_rt
path: build\${{ matrix.build_type }}\c3c.exe
build-msys2-mingw:
runs-on: windows-latest
@@ -132,8 +112,8 @@ jobs:
install: git binutils mingw-w64-x86_64-clang mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
- shell: msys2 {0}
run: |
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-19.1.7-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-19.1.7-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-18.1.6-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-18.1.6-1-any.pkg.tar.zst
- name: CMake
run: |
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
@@ -143,33 +123,31 @@ jobs:
run: |
cd resources
../build/c3c compile-run --print-linking examples/hello_world_many.c3
../build/c3c compile-run --print-linking examples/process.c3
../build/c3c compile-run --print-linking examples/time.c3
../build/c3c compile-run --print-linking examples/fannkuch-redux.c3
../build/c3c compile-run --print-linking examples/contextfree/boolerr.c3
../build/c3c compile-run --print-linking examples/load_world.c3
../build/c3c compile-run --print-linking examples/args.c3 -- foo -bar "baz baz"
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
../build/c3c compile --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run --debug-log
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib5
./build/c3c vendor-fetch raylib
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib --cc cc -vvv --trust=full
../../build/c3c build hello_world_lib --debug-log
- name: run compiler tests
run: |
cd test
../build/c3c.exe compile --target windows-x64 -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics
./test_suite_runner.exe ../build/c3c.exe test_suite7/ --stdlib ../lib7 --no-terminal
python3 src/tester.py ../build/c3c.exe test_suite/
build-msys2-clang:
runs-on: windows-latest
@@ -205,31 +183,30 @@ jobs:
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
../build/c3c compile --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run --debug-log
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib -vvv --trust=full
../../build/c3c build hello_world_lib --debug-log
- name: run compiler tests
run: |
cd test
../build/c3c.exe compile-run -O1 --stdlib ../lib7 src/test_suite_runner.c3 --enable-new-generics -- ../build/c3c.exe test_suite7/ --stdlib ../lib7 --no-terminal
python3 src/tester.py ../build/c3c.exe test_suite/
build-linux:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20, 21]
llvm_version: [15, 16, 17, 18, 19]
steps:
- uses: actions/checkout@v4
@@ -240,29 +217,25 @@ jobs:
- name: Install Clang ${{matrix.llvm_version}}
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
if [[ "${{matrix.llvm_version}}" < 18 ]]; then
if [[ "${{matrix.llvm_version}}" < 16 ]]; then
sudo apt remove libllvm15
fi
if [[ "${{matrix.llvm_version}}" < 19 ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
sudo apt-get update
sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
else
if [[ "${{matrix.llvm_version}}" < "${{env.LLVM_DEV_VERSION}}" ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
sudo apt-get update
sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
sudo apt-get install -y -t llvm-toolchain-focal libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
fi
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
sudo apt-get install -y -t llvm-toolchain-focal libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
fi
- name: CMake
if: matrix.llvm_version < 18 || matrix.llvm_version == env.LLVM_DEV_VERSION
if: matrix.llvm_version != 18
run: |
cmake -B build \
-G Ninja \
@@ -276,7 +249,7 @@ jobs:
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: CMake18
if: matrix.llvm_version >= 18 && matrix.llvm_version != env.LLVM_DEV_VERSION
if: matrix.llvm_version == 18
run: |
cmake -B build \
-G Ninja \
@@ -287,12 +260,12 @@ jobs:
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
-DC3_LLVM_VERSION=18.1
cmake --build build
- name: Compile and run some examples
run: |
cd resources
cd resources
../build/c3c compile examples/base64.c3
../build/c3c compile examples/binarydigits.c3
../build/c3c compile examples/brainfk.c3
@@ -300,9 +273,9 @@ jobs:
../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile examples/levenshtein.c3
../build/c3c compile examples/load_world.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3
@@ -311,8 +284,8 @@ jobs:
../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
@@ -322,25 +295,6 @@ jobs:
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
../../../build/c3c -vv dynamic-lib add.c3
mv add.so libadd.so
cc test.c -L. -ladd -Wl,-rpath=.
./a.out
../../../build/c3c compile-run test.c3 -L . -l add -z -Wl,-rpath=.
- name: Compile and run staticlib-test
run: |
cd resources/examples/staticlib-test
../../../build/c3c -vv static-lib add.c3
mv add.a libadd.a
cc test.c -L. -ladd
./a.out
../../../build/c3c compile-run test.c3 -L . -l add
- name: Compile run unit tests
run: |
@@ -350,51 +304,30 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
- name: Test WASM
run: |
cd resources/testfragments
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
- name: Install QEMU and Risc-V toolchain
run: |
sudo apt-get install opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf
- name: Compile and run Baremetal Risc-V Example
run: |
cd resources/examples/embedded/riscv-qemu
make C3C_PATH=../../../../build/ run
../../build/c3c run --debug-log
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
- name: Init a library & a project
run: |
./build/c3c init-lib mylib
ls mylib.c3l
./build/c3c init myproject
ls myproject
../../build/c3c run --debug-log --linker=builtin
- name: run compiler tests
run: |
cd test
../build/c3c compile-run -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics -- ../build/c3c test_suite7/ --stdlib ../lib7
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
if: matrix.llvm_version == 16
run: |
mkdir c3
cp -r lib c3
cp msvc_build_libraries.py c3
cp build/c3c c3
tar czf c3-linux-${{matrix.build_type}}.tar.gz c3
mkdir linux
cp -r lib linux
cp msvc_build_libraries.py linux
cp build/c3c linux
tar czf c3-linux-${{matrix.build_type}}.tar.gz linux
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
uses: actions/upload-artifact@v4
if: matrix.llvm_version == 16
uses: actions/upload-artifact@v3
with:
name: c3-linux-${{matrix.build_type}}
path: c3-linux-${{matrix.build_type}}.tar.gz
@@ -406,7 +339,8 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20, 21]
llvm_version: [16]
steps:
- uses: actions/checkout@v4
- name: Install common deps
@@ -416,17 +350,16 @@ jobs:
- name: Install Clang ${{matrix.llvm_version}}
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
if [[ "${{matrix.llvm_version}}" < "${{env.LLVM_DEV_VERSION}}" ]]; then
if [[ "${{matrix.llvm_version}}" < 17 ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
fi
sudo apt-get update
sudo apt-get update
sudo apt-get install -y clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
sudo apt-get install -y libpolly-${{matrix.llvm_version}}-dev
- name: CMake Old
if: matrix.llvm_version < 18 || matrix.llvm_version == env.LLVM_DEV_VERSION
- name: CMake
run: |
cmake -B build \
-G Ninja \
@@ -439,26 +372,13 @@ jobs:
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: CMake
if: matrix.llvm_version >= 18 && matrix.llvm_version != env.LLVM_DEV_VERSION
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile examples/gameoflife.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/levenshtein.c3
../build/c3c compile examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/spectralnorm.c3
@@ -468,8 +388,8 @@ jobs:
../build/c3c compile-run examples/nbodies.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/contextfree/dynscope.c3
../build/c3c compile-run examples/contextfree/multi.c3
../build/c3c compile-run examples/contextfree/cleanup.c3
../build/c3c compile-run examples/contextfree/multi.c3
../build/c3c compile-run examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
@@ -482,99 +402,6 @@ jobs:
../build/c3c compile-run examples/process.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit --sanitize=address
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
- name: run compiler tests
run: |
cd test
../build/c3c compile-run -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics -- ../build/c3c test_suite7/ --stdlib ../lib7
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
run: |
mkdir c3
cp -r lib c3
cp msvc_build_libraries.py c3
cp build/c3c c3
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz c3
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
uses: actions/upload-artifact@v4
with:
name: c3-ubuntu-20-${{matrix.build_type}}
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
build-with-docker:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
ubuntu_version: [20.04, 22.04]
build_type: [Release, Debug]
llvm_version: [17, 18, 19]
steps:
- uses: actions/checkout@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Make script executable
run: chmod +x ./build-with-docker.sh
- name: Run build
run: |
LLVM_VERSION=${{ matrix.llvm_version }} UBUNTU_VERSION=${{ matrix.ubuntu_version }} CMAKE_BUILD_TYPE=${{ matrix.build_type }} ./build-with-docker.sh
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile examples/base64.c3
../build/c3c compile examples/binarydigits.c3
../build/c3c compile examples/brainfk.c3
../build/c3c compile examples/factorial_macro.c3
../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile examples/load_world.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile run unit tests
run: |
@@ -584,29 +411,33 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
- name: Test WASM
run: |
cd resources/testfragments
../../build/c3c compile --reloc=none --target wasm32 -g0 --no-entry -Os wasm4.c3
../../build/c3c run --debug-log
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
- name: Init a library & a project
run: |
./build/c3c init-lib mylib
ls mylib.c3l
./build/c3c init myproject
ls myproject
../../build/c3c run --debug-log --linker=builtin
- name: run compiler tests
run: |
cd test
../build/c3c compile-run -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics -- ../build/c3c test_suite7/ --stdlib ../lib7
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == 16
run: |
mkdir linux
cp -r lib linux
cp msvc_build_libraries.py linux
cp build/c3c linux
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz linux
- name: upload artifacts
if: matrix.llvm_version == 16
uses: actions/upload-artifact@v3
with:
name: c3-ubuntu-20-${{matrix.build_type}}
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
build-mac:
runs-on: macos-latest
@@ -615,30 +446,24 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18]
llvm_version: [15, 16, 17]
steps:
- uses: actions/checkout@v4
- name: Download LLVM
run: |
brew install llvm@${{ matrix.llvm_version }} ninja curl
echo "/opt/homebrew/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
echo "/opt/homebrew/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
TMP_PATH=$(xcrun --show-sdk-path)/user/include
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
- name: CMake
if: matrix.llvm_version < 18
run: |
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
- name: CMake18
if: matrix.llvm_version >= 18
run: |
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}}.1 -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib5
./build/c3c vendor-fetch raylib
- name: Compile and run some examples
run: |
@@ -650,52 +475,34 @@ jobs:
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run -O5 examples/load_world.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
../../../build/c3c -vv dynamic-lib add.c3
../../../build/c3c compile-run test.c3 -l ./add.dylib
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit -O1
- name: Test WASM
run: |
cd resources/testfragments
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
../build/c3c compile-test unit
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run --debug-log
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
../../build/c3c run --debug-log --linker=builtin
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib -vvv --trust=full
../../build/c3c build hello_world_lib --debug-log
- name: run compiler tests
run: |
cd test
../build/c3c compile -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics
./test_suite_runner ../build/c3c test_suite7/ --stdlib ../lib7
- name: run build test suite runner
run: |
cd test
../build/c3c compile -O1 src/test_suite_runner.c3 --stdlib ../lib7 --enable-new-generics
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
run: |
mkdir macos
cp -r lib macos
@@ -704,46 +511,16 @@ jobs:
zip -r c3-macos-${{matrix.build_type}}.zip macos
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
uses: actions/upload-artifact@v4
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
uses: actions/upload-artifact@v3
with:
name: c3-macos-${{matrix.build_type}}
path: c3-macos-${{matrix.build_type}}.zip
build-nix:
runs-on: ubuntu-22.04
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [ Release, Debug ]
nixpkgs: [ Lock, Latest ]
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: Update flake (if necessary)
run: |
if [[ matrix.nixpkgs == "Latest" ]]; then
nix flake update
fi
nix flake info
- name: Build and check
run: |
if [[ ${{ matrix.build_type }} = "Debug" ]]; then
nix build -L ".#c3c-debug-checks"
else
nix build -L ".#c3c-checks"
fi
release:
runs-on: ubuntu-22.04
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu20]
runs-on: ubuntu-latest
needs: [build-msvc, build-linux, build-mac]
if: github.ref == 'refs/heads/master'
steps:
@@ -770,22 +547,16 @@ jobs:
sha: context.sha
})
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
- run: cp -r lib c3-windows-Release
- run: cp -r lib c3-windows-Debug
- run: cp msvc_build_libraries.py c3-windows-Release
- run: cp msvc_build_libraries.py c3-windows-Debug
- run: zip -r c3-windows.zip c3-windows-Release
- run: zip -r c3-windows-debug.zip c3-windows-Debug
- run: mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux-Release/c3-linux.tar.gz
- run: mv c3-linux-Debug/c3-linux-Debug.tar.gz c3-linux-Debug/c3-linux-debug.tar.gz
- run: mv c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
- run: mv c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
- run: mv c3-macos-Release/c3-macos-Release.zip c3-macos-Release/c3-macos.zip
- run: mv c3-macos-Debug/c3-macos-Debug.zip c3-macos-Debug/c3-macos-debug.zip
- run: zip -r c3-windows-Release.zip c3-windows-Release
- run: zip -r c3-windows-Debug.zip c3-windows-Debug
- id: create_release
uses: softprops/action-gh-release@v2
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -793,12 +564,84 @@ jobs:
release_name: latest
draft: false
prerelease: true
files: |
c3-windows.zip
c3-windows-debug.zip
c3-linux-Release/c3-linux.tar.gz
c3-linux-Debug/c3-linux-debug.tar.gz
c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
c3-macos-Release/c3-macos.zip
c3-macos-Debug/c3-macos-debug.zip
- name: upload windows
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-windows-Release.zip
asset_name: c3-windows.zip
asset_content_type: application/zip
- name: upload windows debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-windows-Debug.zip
asset_name: c3-windows-debug.zip
asset_content_type: application/zip
- name: upload linux
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-linux-Release/c3-linux-Release.tar.gz
asset_name: c3-linux.tar.gz
asset_content_type: application/gzip
- name: upload linux debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-linux-Debug/c3-linux-Debug.tar.gz
asset_name: c3-linux-debug.tar.gz
asset_content_type: application/gzip
- name: upload ubuntu 20
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz
asset_name: c3-ubuntu-20.tar.gz
asset_content_type: application/gzip
- name: upload ubuntu 20 debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz
asset_name: c3-ubuntu-20-debug.tar.gz
asset_content_type: application/gzip
- name: upload macos
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-macos-Release/c3-macos-Release.zip
asset_name: c3-macos.zip
asset_content_type: application/zip
- name: upload macos debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-macos-Debug/c3-macos-Debug.zip
asset_name: c3-macos-debug.zip
asset_content_type: application/zip

18
.gitignore vendored
View File

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

View File

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

View File

@@ -10,8 +10,7 @@ Precompiled binaries for the following operating systems are available:
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-mac-with-precompiled-binaries).
- MacOS x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-mac-with-precompiled-binaries).
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
@@ -35,7 +34,7 @@ whole new language.
The following code shows [generic modules](https://c3-lang.org/references/docs/generics/) (more examples can be found at https://c3-lang.org/references/docs/examples/).
```cpp
```c++
module stack (<Type>);
// Above: the parameterized type is applied to the entire module.
@@ -55,7 +54,7 @@ fn void Stack.push(Stack* this, Type element)
if (this.capacity == this.size)
{
this.capacity *= 2;
if (this.capacity < 16) this.capacity = 16;
if (this.capacity < 16) this.capacity = 16;
this.elems = realloc(this.elems, Type.sizeof * this.capacity);
}
this.elems[this.size++] = element;
@@ -138,9 +137,9 @@ fn void main()
### Current status
The current stable version of the compiler is **version 0.6.7**.
The current stable version of the compiler is **version 0.5**.
The upcoming 0.6.x releases will focus on expanding the standard library.
The upcoming 0.6 release will focus on expanding the standard library.
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)
@@ -206,32 +205,19 @@ More platforms will be supported in the future.
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on Ubuntu with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20-debug.tar.gz))
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on MacOS with precompiled binaries
#### Installing on Mac 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))
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))
(*Note that there is a known issue with debug symbol generation on MacOS 13, see issue #1086)
#### Installing on Arch Linux
Arch includes c3c in the official 'extra' repo. It can be easily installed the usual way:
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
```sh
sudo pacman -S c3c
# or paru -S c3c
# or yay -S c3c
# or aura -A c3c
```
There is also an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
Due to some issues with the LLVM packaged for Arch Linux, the AUR package will download and use LLVM 16 for Ubuntu-23.04 to compile the c3c compiler.
You can use your AUR package manager:
```sh
@@ -267,7 +253,7 @@ A `c3c` executable will be found under `bin/`.
#### Installing on OS X using Homebrew
2. Install CMake: `brew install cmake`
3. Install LLVM 17+: `brew install llvm`
3. Install LLVM 15: `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`
@@ -275,14 +261,6 @@ A `c3c` executable will be found under `bin/`.
8. Set up CMake build for debug: `cmake ..`
9. Build: `cmake --build .`
#### Installing on Windows using Scoop
c3c is included in 'Main' bucket.
```sh
scoop install c3
```
#### Getting started with a "hello world"
Create a `main.c3` file with:
@@ -311,7 +289,7 @@ called `hello_world` or `hello_world.exe`depending on platform.
#### Compiling on Windows
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++"
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++" (there is also `c3c/resources/install_win_reqs.bat` to automate this)
2. Install CMake
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
@@ -326,16 +304,17 @@ You can try it out by running some sample code: `c3c.exe compile ../resources/ex
*Note that if you run into linking issues when building, make sure that you are using the latest version of VS17.*
#### Compiling on Ubuntu 24.04 LTS
#### Compiling on Ubuntu 20.10
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`
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 .`
2. Install CMake: `sudo apt install cmake`
3. Install LLVM 15 (or greater: C3C supports LLVM 15-17): `sudo apt-get install clang-15 zlib1g zlib1g-dev libllvm15 llvm-15 llvm-15-dev llvm-15-runtime liblld-15-dev liblld-15`
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: `cmake ..`
9. Build: `cmake --build .`
You should now have a `c3c` executable.
@@ -344,7 +323,7 @@ You can try it out by running some sample code: `./c3c compile ../resources/exam
#### Compiling on Void Linux
1. As root, ensure that all project dependencies are installed: `xbps-install git cmake llvm17 llvm17-devel lld17-devel libcurl-devel ncurses-devel zlib-devel libzstd-devel libxml2-devel`
1. As root, ensure that all project dependencies are installed: `xbps-install git cmake llvm15 lld-devel libcurl-devel ncurses-devel zlib-devel libzstd-devel libxml2-devel`
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone instead: `git clone https://github.com/c3lang/c3c.git --depth=1`
3. Enter the directory: `cd c3c`
@@ -357,24 +336,10 @@ Your c3c executable should have compiled properly. You may want to test it: `./c
For a sytem-wide installation, run the following as root: `cmake --install .`
#### Compiling on Fedora
1. Install required project dependencies: `dnf install cmake clang git llvm llvm-devel lld lld-devel ncurses-devel`
2. Optionally, install additional dependencies: `dnf install libcurl-devel zlib-devel libzstd-devel libxml2-devel libffi-devel`
3. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
4. Enter the C3C directory: `cd c3c`
5. Create a build directory and navigate into it: `mkdir build && cd build`
6. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake .. -DC3_LINK_DYNAMIC=1`
7. Build the project: `cmake --build .`
The c3c binary should be created in the build directory. You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
#### Compiling on other Linux / Unix variants
1. Install CMake.
2. Install or compile LLVM and LLD *libraries* (version 17+ or higher)
2. Install or compile LLVM and LLD *libraries* (version 15+ 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`
@@ -404,9 +369,3 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
3. Run tests and see that they pass. (Recommended settings: `c3c compile-test -O0 test/unit`.
- in this example `test/unit/` is the relative path to the test directory, so adjust as required)
4. Make a pull request for the new tests.
## Thank yous
A huge **THANK YOU** goes out to all contributors and sponsors.
A special thank you to sponsors [Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad) for going the extra mile.

View File

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

View File

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

552
c3c.1
View File

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

View File

@@ -1,49 +1,16 @@
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION}
ARG LLVM_VERSION=18
ENV LLVM_DEV_VERSION=20
ARG UBUNTU_VERSION
FROM ubuntu:$UBUNTU_VERSION
ARG CMAKE_VERSION=3.20
ARG DEPS
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 && \
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
RUN export DEBIAN_FRONTEND=noninteractive && export TERM=xterm && apt-get update && apt-get install -y build-essential cmake zlib1g zlib1g-dev \
$DEPS && \
rm -rf /var/lib/apt/lists/*
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; \
else \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
fi && \
rm -rf /var/lib/apt/lists/*
ARG GID=1000
ARG UID=1000
RUN groupadd -g 1337 c3c && \
useradd -m -u 1337 -g c3c c3c
RUN groupadd -o --gid=$GID c3c && useradd --gid=$GID --uid=$GID --create-home --shell /bin/bash c3c
# Add cmake to PATH for user c3c
USER c3c
ENV PATH="/opt/cmake/bin:${PATH}"
WORKDIR /home/c3c

61
flake.lock generated
View File

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

View File

@@ -1,44 +0,0 @@
{
description = "C3 compiler flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem
(system:
let pkgs = import inputs.nixpkgs { inherit system; };
call = set: pkgs.callPackage ./nix/default.nix (
set // {
rev = self.rev or "unknown";
}
);
in {
packages = {
default = self.packages.${system}.c3c;
c3c = call {};
c3c-checks = pkgs.callPackage ./nix/default.nix {
checks = true;
};
c3c-debug = pkgs.callPackage ./nix/default.nix {
debug = true;
};
c3c-debug-checks = pkgs.callPackage ./nix/default.nix {
debug = true;
checks = true;
};
};
devShells = {
default = pkgs.callPackage ./nix/shell.nix {
c3c = self.packages.${system}.c3c-debug;
};
};
}
);
}

View File

@@ -1,15 +0,0 @@
find_package(Git QUIET)
set(GIT_HASH "unknown")
if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git")
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY)
endif()
message("Git Hash: ${GIT_HASH}")
file(WRITE ${CMAKE_BINARY_DIR}/git_hash.h "#pragma once\n#define GIT_HASH \"${GIT_HASH}\"\n")

View File

@@ -53,9 +53,9 @@ fn bool char.is_blank(char c) => is_blank_m(c);
fn bool char.is_cntrl(char c) => is_cntrl_m(c);
fn 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()
*>
/**
* @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);

View File

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

View File

@@ -1,94 +1,94 @@
module std::bits;
<*
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
*>
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro reverse(i) => $$bitreverse(i);
<*
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
*>
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro bswap(i) @builtin => $$bswap(i);
macro uint[<?>].popcount(self) => $$popcount(self);
macro uint[<?>].ctz(self) => $$ctz(self);
macro uint[<?>].clz(self) => $$clz(self);
macro uint[<?>] uint[<?>].fshl(hi, uint[<?>] lo, uint[<?>] shift) => $$fshl(hi, lo, shift);
macro uint[<?>] uint[<?>].fshr(hi, uint[<?>] lo, uint[<?>] shift) => $$fshr(hi, lo, shift);
macro uint[<?>] uint[<?>].rotl(self, uint[<?>] shift) => $$fshl(self, self, shift);
macro uint[<?>] uint[<?>].rotr(self, uint[<?>] shift) => $$fshr(self, self, shift);
macro uint[<*>].popcount(self) => $$popcount(self);
macro uint[<*>].ctz(self) => $$ctz(self);
macro uint[<*>].clz(self) => $$clz(self);
macro uint[<*>] uint[<*>].fshl(hi, uint[<*>] lo, uint[<*>] shift) => $$fshl(hi, lo, shift);
macro uint[<*>] uint[<*>].fshr(hi, uint[<*>] lo, uint[<*>] shift) => $$fshr(hi, lo, shift);
macro uint[<*>] uint[<*>].rotl(self, uint[<*>] shift) => $$fshl(self, self, shift);
macro uint[<*>] uint[<*>].rotr(self, uint[<*>] shift) => $$fshr(self, self, shift);
macro int[<?>].popcount(self) => $$popcount(self);
macro int[<?>].ctz(self) => $$ctz(self);
macro int[<?>].clz(self) => $$clz(self);
macro int[<?>] int[<?>].fshl(hi, int[<?>] lo, int[<?>] shift) => $$fshl(hi, lo, shift);
macro int[<?>] int[<?>].fshr(hi, int[<?>] lo, int[<?>] shift) => $$fshr(hi, lo, shift);
macro int[<?>] int[<?>].rotl(self, int[<?>] shift) => $$fshl(self, self, shift);
macro int[<?>] int[<?>].rotr(self, int[<?>] shift) => $$fshr(self, self, shift);
macro int[<*>].popcount(self) => $$popcount(self);
macro int[<*>].ctz(self) => $$ctz(self);
macro int[<*>].clz(self) => $$clz(self);
macro int[<*>] int[<*>].fshl(hi, int[<*>] lo, int[<*>] shift) => $$fshl(hi, lo, shift);
macro int[<*>] int[<*>].fshr(hi, int[<*>] lo, int[<*>] shift) => $$fshr(hi, lo, shift);
macro int[<*>] int[<*>].rotl(self, int[<*>] shift) => $$fshl(self, self, shift);
macro int[<*>] int[<*>].rotr(self, int[<*>] shift) => $$fshr(self, self, shift);
macro ushort[<?>].popcount(self) => $$popcount(self);
macro ushort[<?>].ctz(self) => $$ctz(self);
macro ushort[<?>].clz(self) => $$clz(self);
macro ushort[<?>] ushort[<?>].fshl(hi, ushort[<?>] lo, ushort[<?>] shift) => $$fshl(hi, lo, shift);
macro ushort[<?>] ushort[<?>].fshr(hi, ushort[<?>] lo, ushort[<?>] shift) => $$fshr(hi, lo, shift);
macro ushort[<?>] ushort[<?>].rotl(self, ushort[<?>] shift) => $$fshl(self, self, shift);
macro ushort[<?>] ushort[<?>].rotr(self, ushort[<?>] shift) => $$fshr(self, self, shift);
macro ushort[<*>].popcount(self) => $$popcount(self);
macro ushort[<*>].ctz(self) => $$ctz(self);
macro ushort[<*>].clz(self) => $$clz(self);
macro ushort[<*>] ushort[<*>].fshl(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshl(hi, lo, shift);
macro ushort[<*>] ushort[<*>].fshr(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshr(hi, lo, shift);
macro ushort[<*>] ushort[<*>].rotl(self, ushort[<*>] shift) => $$fshl(self, self, shift);
macro ushort[<*>] ushort[<*>].rotr(self, ushort[<*>] shift) => $$fshr(self, self, shift);
macro short[<?>].popcount(self) => $$popcount(self);
macro short[<?>].ctz(self) => $$ctz(self);
macro short[<?>].clz(self) => $$clz(self);
macro short[<?>] short[<?>].fshl(hi, short[<?>] lo, short[<?>] shift) => $$fshl(hi, lo, shift);
macro short[<?>] short[<?>].fshr(hi, short[<?>] lo, short[<?>] shift) => $$fshr(hi, lo, shift);
macro short[<?>] short[<?>].rotl(self, short[<?>] shift) => $$fshl(self, self, shift);
macro short[<?>] short[<?>].rotr(self, short[<?>] shift) => $$fshr(self, self, shift);
macro short[<*>].popcount(self) => $$popcount(self);
macro short[<*>].ctz(self) => $$ctz(self);
macro short[<*>].clz(self) => $$clz(self);
macro short[<*>] short[<*>].fshl(hi, short[<*>] lo, short[<*>] shift) => $$fshl(hi, lo, shift);
macro short[<*>] short[<*>].fshr(hi, short[<*>] lo, short[<*>] shift) => $$fshr(hi, lo, shift);
macro short[<*>] short[<*>].rotl(self, short[<*>] shift) => $$fshl(self, self, shift);
macro short[<*>] short[<*>].rotr(self, short[<*>] shift) => $$fshr(self, self, shift);
macro char[<?>].popcount(self) => $$popcount(self);
macro char[<?>].ctz(self) => $$ctz(self);
macro char[<?>].clz(self) => $$clz(self);
macro char[<?>] char[<?>].fshl(hi, char[<?>] lo, char[<?>] shift) => $$fshl(hi, lo, shift);
macro char[<?>] char[<?>].fshr(hi, char[<?>] lo, char[<?>] shift) => $$fshr(hi, lo, shift);
macro char[<?>] char[<?>].rotl(self, char[<?>] shift) => $$fshl(self, self, shift);
macro char[<?>] char[<?>].rotr(self, char[<?>] shift) => $$fshr(self, self, shift);
macro char[<*>].popcount(self) => $$popcount(self);
macro char[<*>].ctz(self) => $$ctz(self);
macro char[<*>].clz(self) => $$clz(self);
macro char[<*>] char[<*>].fshl(hi, char[<*>] lo, char[<*>] shift) => $$fshl(hi, lo, shift);
macro char[<*>] char[<*>].fshr(hi, char[<*>] lo, char[<*>] shift) => $$fshr(hi, lo, shift);
macro char[<*>] char[<*>].rotl(self, char[<*>] shift) => $$fshl(self, self, shift);
macro char[<*>] char[<*>].rotr(self, char[<*>] shift) => $$fshr(self, self, shift);
macro ichar[<?>].popcount(self) => $$popcount(self);
macro ichar[<?>].ctz(self) => $$ctz(self);
macro ichar[<?>].clz(self) => $$clz(self);
macro ichar[<?>] ichar[<?>].fshl(hi, ichar[<?>] lo, ichar[<?>] shift) => $$fshl(hi, lo, shift);
macro ichar[<?>] ichar[<?>].fshr(hi, ichar[<?>] lo, ichar[<?>] shift) => $$fshr(hi, lo, shift);
macro ichar[<?>] ichar[<?>].rotl(self, ichar[<?>] shift) => $$fshl(self, self, shift);
macro ichar[<?>] ichar[<?>].rotr(self, ichar[<?>] shift) => $$fshr(self, self, shift);
macro ichar[<*>].popcount(self) => $$popcount(self);
macro ichar[<*>].ctz(self) => $$ctz(self);
macro ichar[<*>].clz(self) => $$clz(self);
macro ichar[<*>] ichar[<*>].fshl(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshl(hi, lo, shift);
macro ichar[<*>] ichar[<*>].fshr(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshr(hi, lo, shift);
macro ichar[<*>] ichar[<*>].rotl(self, ichar[<*>] shift) => $$fshl(self, self, shift);
macro ichar[<*>] ichar[<*>].rotr(self, ichar[<*>] shift) => $$fshr(self, self, shift);
macro ulong[<?>].popcount(self) => $$popcount(self);
macro ulong[<?>].ctz(self) => $$ctz(self);
macro ulong[<?>].clz(self) => $$clz(self);
macro ulong[<?>] ulong[<?>].fshl(hi, ulong[<?>] lo, ulong[<?>] shift) => $$fshl(hi, lo, shift);
macro ulong[<?>] ulong[<?>].fshr(hi, ulong[<?>] lo, ulong[<?>] shift) => $$fshr(hi, lo, shift);
macro ulong[<?>] ulong[<?>].rotl(self, ulong[<?>] shift) => $$fshl(self, self, shift);
macro ulong[<?>] ulong[<?>].rotr(self, ulong[<?>] shift) => $$fshr(self, self, shift);
macro ulong[<*>].popcount(self) => $$popcount(self);
macro ulong[<*>].ctz(self) => $$ctz(self);
macro ulong[<*>].clz(self) => $$clz(self);
macro ulong[<*>] ulong[<*>].fshl(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshl(hi, lo, shift);
macro ulong[<*>] ulong[<*>].fshr(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshr(hi, lo, shift);
macro ulong[<*>] ulong[<*>].rotl(self, ulong[<*>] shift) => $$fshl(self, self, shift);
macro ulong[<*>] ulong[<*>].rotr(self, ulong[<*>] shift) => $$fshr(self, self, shift);
macro long[<?>].popcount(self) => $$popcount(self);
macro long[<?>].ctz(self) => $$ctz(self);
macro long[<?>].clz(self) => $$clz(self);
macro long[<?>] long[<?>].fshl(hi, long[<?>] lo, long[<?>] shift) => $$fshl(hi, lo, shift);
macro long[<?>] long[<?>].fshr(hi, long[<?>] lo, long[<?>] shift) => $$fshr(hi, lo, shift);
macro long[<?>] long[<?>].rotl(self, long[<?>] shift) => $$fshl(self, self, shift);
macro long[<?>] long[<?>].rotr(self, long[<?>] shift) => $$fshr(self, self, shift);
macro long[<*>].popcount(self) => $$popcount(self);
macro long[<*>].ctz(self) => $$ctz(self);
macro long[<*>].clz(self) => $$clz(self);
macro long[<*>] long[<*>].fshl(hi, long[<*>] lo, long[<*>] shift) => $$fshl(hi, lo, shift);
macro long[<*>] long[<*>].fshr(hi, long[<*>] lo, long[<*>] shift) => $$fshr(hi, lo, shift);
macro long[<*>] long[<*>].rotl(self, long[<*>] shift) => $$fshl(self, self, shift);
macro long[<*>] long[<*>].rotr(self, long[<*>] shift) => $$fshr(self, self, shift);
macro uint128[<?>].popcount(self) => $$popcount(self);
macro uint128[<?>].ctz(self) => $$ctz(self);
macro uint128[<?>].clz(self) => $$clz(self);
macro uint128[<?>] uint128[<?>].fshl(hi, uint128[<?>] lo, uint128[<?>] shift) => $$fshl(hi, lo, shift);
macro uint128[<?>] uint128[<?>].fshr(hi, uint128[<?>] lo, uint128[<?>] shift) => $$fshr(hi, lo, shift);
macro uint128[<?>] uint128[<?>].rotl(self, uint128[<?>] shift) => $$fshl(self, self, shift);
macro uint128[<?>] uint128[<?>].rotr(self, uint128[<?>] shift) => $$fshr(self, self, shift);
macro uint128[<*>].popcount(self) => $$popcount(self);
macro uint128[<*>].ctz(self) => $$ctz(self);
macro uint128[<*>].clz(self) => $$clz(self);
macro uint128[<*>] uint128[<*>].fshl(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshl(hi, lo, shift);
macro uint128[<*>] uint128[<*>].fshr(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshr(hi, lo, shift);
macro uint128[<*>] uint128[<*>].rotl(self, uint128[<*>] shift) => $$fshl(self, self, shift);
macro uint128[<*>] uint128[<*>].rotr(self, uint128[<*>] shift) => $$fshr(self, self, shift);
macro int128[<?>].popcount(self) => $$popcount(self);
macro int128[<?>].ctz(self) => $$ctz(self);
macro int128[<?>].clz(self) => $$clz(self);
macro int128[<?>] int128[<?>].fshl(hi, int128[<?>] lo, int128[<?>] shift) => $$fshl(hi, lo, shift);
macro int128[<?>] int128[<?>].fshr(hi, int128[<?>] lo, int128[<?>] shift) => $$fshr(hi, lo, shift);
macro int128[<?>] int128[<?>].rotl(self, int128[<?>] shift) => $$fshl(self, self, shift);
macro int128[<?>] int128[<?>].rotr(self, int128[<?>] shift) => $$fshr(self, self, shift);
macro int128[<*>].popcount(self) => $$popcount(self);
macro int128[<*>].ctz(self) => $$ctz(self);
macro int128[<*>].clz(self) => $$clz(self);
macro int128[<*>] int128[<*>].fshl(hi, int128[<*>] lo, int128[<*>] shift) => $$fshl(hi, lo, shift);
macro int128[<*>] int128[<*>].fshr(hi, int128[<*>] lo, int128[<*>] shift) => $$fshr(hi, lo, shift);
macro int128[<*>] int128[<*>].rotl(self, int128[<*>] shift) => $$fshl(self, self, shift);
macro int128[<*>] int128[<*>].rotr(self, int128[<*>] shift) => $$fshr(self, self, shift);
macro uint.popcount(self) => $$popcount(self);
macro uint.ctz(self) => $$ctz(self);

View File

@@ -16,21 +16,11 @@ struct AnyList (Printable)
}
<*
Use `init` for to use a custom allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = null) @deprecated("Use init(mem)")
{
return self.init(allocator ?: allocator::heap(), initial_capacity) @inline;
}
<*
@param [&inout] allocator "The allocator to use"
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
/**
* @param initial_capacity "The initial capacity to reserve"
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
**/
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
{
self.allocator = allocator;
self.size = 0;
@@ -47,25 +37,14 @@ fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
return self;
}
<*
Initialize the list using the temp allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16) @deprecated("Use tinit")
/**
* Initialize the list using the temp allocator.
*
* @param initial_capacity "The initial capacity to reserve"
**/
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16)
{
return self.init(allocator::temp(), initial_capacity) @inline;
}
<*
Initialize the list using the temp allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16)
{
return self.init(allocator::temp(), initial_capacity) @inline;
return self.new_init(initial_capacity, allocator::temp()) @inline;
}
fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
@@ -88,22 +67,19 @@ fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
}
}
fn String AnyList.to_new_string(&self, Allocator allocator = null) @dynamic
fn String AnyList.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
return string::new_format("%s", *self, .allocator = allocator);
}
fn String AnyList.to_string(&self, Allocator allocator) @dynamic
fn String AnyList.to_tstring(&self)
{
return string::format("%s", *self, allocator: allocator);
return string::tformat("%s", *self);
}
fn String AnyList.to_tstring(&self) => string::tformat("%s", *self);
<*
Push an element on the list by cloning it.
*>
/**
* Push an element on the list by cloning it.
**/
macro void AnyList.push(&self, element)
{
if (!self.allocator) self.allocator = allocator::heap();
@@ -116,20 +92,20 @@ fn void AnyList.append_internal(&self, any element) @local
self.entries[self.size++] = element;
}
<*
Free a retained element removed using *_retained.
*>
/**
* Free a retained element removed using *_retained.
**/
fn void AnyList.free_element(&self, any element) @inline
{
allocator::free(self.allocator, element.ptr);
}
<*
Pop a value who's type is known. If the type is incorrect, this
will still pop the element.
@return! CastResult.TYPE_MISMATCH, IteratorResult.NO_MORE_ELEMENT
*>
/**
* Pop a value who's type is known. If the type is incorrect, this
* will still pop the element.
*
* @return! CastResult.TYPE_MISMATCH, IteratorResult.NO_MORE_ELEMENT
**/
macro AnyList.pop(&self, $Type)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
@@ -137,44 +113,27 @@ macro AnyList.pop(&self, $Type)
return *anycast(self.entries[--self.size], $Type);
}
<*
Pop the last value and allocate the copy using the given allocator.
@return! IteratorResult.NO_MORE_ELEMENT
*>
fn any! AnyList.copy_pop(&self, Allocator allocator = allocator::heap())
/**
* Pop the last value and allocate the copy using the given allocator.
* @return! IteratorResult.NO_MORE_ELEMENT
**/
fn any! AnyList.new_pop(&self, Allocator allocator = allocator::heap())
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return allocator::clone_any(allocator, self.entries[--self.size]);
}
<*
Pop the last value and allocate the copy using the given allocator.
@return! IteratorResult.NO_MORE_ELEMENT
@deprecated `use copy_pop`
*>
fn any! AnyList.new_pop(&self, Allocator allocator = allocator::heap())
{
return self.copy_pop(allocator);
}
/**
* Pop the last value and allocate the copy using the temp allocator
* @return! IteratorResult.NO_MORE_ELEMENT
**/
fn any! AnyList.temp_pop(&self) => self.new_pop(allocator::temp());
<*
Pop the last value and allocate the copy using the temp allocator
@return! IteratorResult.NO_MORE_ELEMENT
@deprecated `use tcopy_pop`
*>
fn any! AnyList.temp_pop(&self) => self.copy_pop(allocator::temp());
<*
Pop the last value and allocate the copy using the temp allocator
@return! IteratorResult.NO_MORE_ELEMENT
*>
fn any! AnyList.tcopy_pop(&self) => self.copy_pop(allocator::temp());
<*
Pop the last value. It must later be released using list.free_element()
@return! IteratorResult.NO_MORE_ELEMENT
*>
/**
* Pop the last value. It must later be released using list.free_element()
* @return! IteratorResult.NO_MORE_ELEMENT
**/
fn any! AnyList.pop_retained(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
@@ -190,9 +149,9 @@ fn void AnyList.clear(&self)
self.size = 0;
}
<*
Same as pop() but pops the first value instead.
*>
/**
* Same as pop() but pops the first value instead.
**/
macro AnyList.pop_first(&self, $Type)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
@@ -200,9 +159,9 @@ macro AnyList.pop_first(&self, $Type)
return *anycast(self.entries[0], $Type);
}
<*
Same as pop_retained() but pops the first value instead.
*>
/**
* Same as pop_retained() but pops the first value instead.
**/
fn any! AnyList.pop_first_retained(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
@@ -210,19 +169,10 @@ fn any! AnyList.pop_first_retained(&self)
return self.entries[0];
}
<*
Same as new_pop() but pops the first value instead.
@deprecated `use copy_pop_first`
*>
/**
* Same as new_pop() but pops the first value instead.
**/
fn any! AnyList.new_pop_first(&self, Allocator allocator = allocator::heap())
{
return self.copy_pop_first(allocator) @inline;
}
<*
Same as new_pop() but pops the first value instead.
*>
fn any! AnyList.copy_pop_first(&self, Allocator allocator = allocator::heap())
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
@@ -230,20 +180,14 @@ fn any! AnyList.copy_pop_first(&self, Allocator allocator = allocator::heap())
return allocator::clone_any(allocator, self.entries[0]);
}
<*
Same as temp_pop() but pops the first value instead.
*>
fn any! AnyList.tcopy_pop_first(&self) => self.copy_pop_first(allocator::temp());
<*
Same as temp_pop() but pops the first value instead.
@deprecated `use tcopy_pop_first`
*>
/**
* Same as temp_pop() but pops the first value instead.
**/
fn any! AnyList.temp_pop_first(&self) => self.new_pop_first(allocator::temp());
<*
@require index < self.size
*>
/**
* @require index < self.size
**/
fn void AnyList.remove_at(&self, usz index)
{
if (!--self.size || index == self.size) return;
@@ -261,9 +205,9 @@ fn void AnyList.add_all(&self, AnyList* other_list)
}
}
<*
Reverse the elements in a list.
*>
/**
* Reverse the elements in a list.
**/
fn void AnyList.reverse(&self)
{
if (self.size < 2) return;
@@ -280,26 +224,26 @@ fn any[] AnyList.array_view(&self)
return self.entries[:self.size];
}
<*
Push an element to the front of the list.
*>
/**
* 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
*>
/**
* @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
*>
/**
* @require index < self.size
**/
fn void AnyList.insert_at_internal(&self, usz index, any value) @local
{
self.ensure_capacity();
@@ -312,17 +256,17 @@ fn void AnyList.insert_at_internal(&self, usz index, any value) @local
}
<*
@require self.size > 0
*>
/**
* @require self.size > 0
**/
fn void AnyList.remove_last(&self)
{
self.free_element(self.entries[--self.size]);
}
<*
@require self.size > 0
*>
/**
* @require self.size > 0
**/
fn void AnyList.remove_first(&self)
{
self.remove_at(0);
@@ -358,17 +302,17 @@ fn usz AnyList.len(&self) @operator(len) @inline
return self.size;
}
<*
@require index < self.size "Index out of range"
*>
/**
* @require index < self.size "Index out of range"
**/
macro AnyList.get(&self, usz index, $Type)
{
return *anycast(self.entries[index], $Type);
}
<*
@require index < self.size "Index out of range"
*>
/**
* @require index < self.size "Index out of range"
**/
fn any AnyList.get_any(&self, usz index) @inline
{
return self.entries[index];
@@ -390,19 +334,19 @@ fn void AnyList.swap(&self, usz i, usz j)
self.entries[j] = temp;
}
<*
@param filter "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
/**
* @param filter "The function to determine if it should be removed or not"
* @return "the number of deleted elements"
**/
fn usz AnyList.remove_if(&self, AnyPredicate filter)
{
return self._remove_if(filter, false);
}
<*
@param selection "The function to determine if it should be kept or not"
@return "the number of deleted elements"
*>
/**
* @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);
@@ -470,9 +414,9 @@ macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @
return size - self.size;
}
<*
Reserve at least min_capacity
*>
/**
* Reserve at least min_capacity
**/
fn void AnyList.reserve(&self, usz min_capacity)
{
if (!min_capacity) return;
@@ -488,9 +432,9 @@ macro any AnyList.@item_at(&self, usz index) @operator([])
return self.entries[index];
}
<*
@require index <= self.size "Index out of range"
*>
/**
* @require index <= self.size "Index out of range"
**/
macro void AnyList.set(&self, usz index, value)
{
if (index == self.size)

View File

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

View File

@@ -1,455 +0,0 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@require MAX_SIZE >= 1 `The size must be at least 1 element big.`
*>
module std::collections::elastic_array(<Type, MAX_SIZE>);
import std::io, std::math, std::collections::list_common;
def ElementPredicate = fn bool(Type *type);
def ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
struct ElasticArray (Printable)
{
usz size;
Type[MAX_SIZE] entries;
}
fn usz! ElasticArray.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
case 0:
return formatter.print("[]")!;
case 1:
return formatter.printf("[%s]", self.entries[0])!;
default:
usz n = formatter.print("[")!;
foreach (i, element : self.entries[:self.size])
{
if (i != 0) formatter.print(", ")!;
n += formatter.printf("%s", element)!;
}
n += formatter.print("]")!;
return n;
}
}
fn String ElasticArray.to_string(&self, Allocator allocator) @dynamic
{
return string::format("%s", *self, allocator: allocator);
}
fn String ElasticArray.to_new_string(&self, Allocator allocator = nul) @dynamic
{
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
}
fn String ElasticArray.to_tstring(&self)
{
return string::tformat("%s", *self);
}
fn void! ElasticArray.push_try(&self, Type element) @inline
{
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
self.entries[self.size++] = element;
}
<*
@require self.size < MAX_SIZE `Tried to exceed the max size`
*>
fn void ElasticArray.push(&self, Type element) @inline
{
self.entries[self.size++] = element;
}
fn Type! ElasticArray.pop(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
fn void ElasticArray.clear(&self)
{
self.size = 0;
}
<*
@require self.size > 0
*>
fn Type! ElasticArray.pop_first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
<*
@require index < self.size
*>
fn void ElasticArray.remove_at(&self, usz index)
{
if (!--self.size || index == self.size) return;
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
}
<*
@require other_list.size + self.size <= MAX_SIZE
*>
fn void ElasticArray.add_all(&self, ElasticArray* other_list)
{
if (!other_list.size) return;
foreach (&value : other_list)
{
self.entries[self.size++] = *value;
}
}
<*
Add as many elements as possible to the new array,
returning the number of elements that didn't fit.
*>
fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
{
if (!other_list.size) return 0;
foreach (i, &value : other_list)
{
if (self.size == MAX_SIZE) return other_list.size - i;
self.entries[self.size++] = *value;
}
return 0;
}
<*
Add as many values from this array as possible, returning the
number of elements that didn't fit.
@param [in] array
*>
fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
{
if (!array.len) return 0;
foreach (i, &value : array)
{
if (self.size == MAX_SIZE) return array.len - i;
self.entries[self.size++] = *value;
}
return 0;
}
<*
Add the values of an array to this list.
@param [in] array
@require array.len + self.size <= MAX_SIZE `Size would exceed max.`
@ensure self.size >= array.len
*>
fn void ElasticArray.add_array(&self, Type[] array)
{
if (!array.len) return;
foreach (&value : array)
{
self.entries[self.size++] = *value;
}
}
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
fn Type[] ElasticArray.to_new_aligned_array(&self)
{
return list_common::list_to_new_aligned_array(Type, self, allocator::heap());
}
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
fn Type[] ElasticArray.to_aligned_array(&self, Allocator allocator)
{
return list_common::list_to_new_aligned_array(Type, self, allocator);
}
<*
@require !type_is_overaligned() : "This function is not available on overaligned types"
*>
macro Type[] ElasticArray.to_new_array(&self)
{
return list_common::list_to_array(Type, self, allocator::heap());
}
<*
@require !type_is_overaligned() : "This function is not available on overaligned types"
*>
macro Type[] ElasticArray.to_array(&self, Allocator allocator)
{
return list_common::list_to_new_array(Type, self, allocator);
}
fn Type[] ElasticArray.to_tarray(&self)
{
$if type_is_overaligned():
return self.to_aligned_array(allocator::temp());
$else
return self.to_array(allocator::temp());
$endif;
}
<*
Reverse the elements in a list.
*>
fn void ElasticArray.reverse(&self)
{
list_common::list_reverse(self);
}
fn Type[] ElasticArray.array_view(&self)
{
return self.entries[:self.size];
}
<*
@require self.size < MAX_SIZE `List would exceed max size`
*>
fn void ElasticArray.push_front(&self, Type type) @inline
{
self.insert_at(0, type);
}
<*
@require self.size < MAX_SIZE `List would exceed max size`
*>
fn void! ElasticArray.push_front_try(&self, Type type) @inline
{
return self.insert_at_try(0, type);
}
<*
@require index <= self.size
*>
fn void! ElasticArray.insert_at_try(&self, usz index, Type value)
{
if (self.size == MAX_SIZE) return AllocationFailure.OUT_OF_MEMORY?;
self.insert_at(index, value);
}
<*
@require self.size < MAX_SIZE `List would exceed max size`
@require index <= self.size
*>
fn void ElasticArray.insert_at(&self, usz index, Type type)
{
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.size++;
self.entries[index] = type;
}
<*
@require index < self.size
*>
fn void ElasticArray.set_at(&self, usz index, Type type)
{
self.entries[index] = type;
}
fn void! ElasticArray.remove_last(&self) @maydiscard
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.size--;
}
fn void! ElasticArray.remove_first(&self) @maydiscard
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.remove_at(0);
}
fn Type! ElasticArray.first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[0];
}
fn Type! ElasticArray.last(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[self.size - 1];
}
fn bool ElasticArray.is_empty(&self) @inline
{
return !self.size;
}
fn usz ElasticArray.byte_size(&self) @inline
{
return Type.sizeof * self.size;
}
fn usz ElasticArray.len(&self) @operator(len) @inline
{
return self.size;
}
fn Type ElasticArray.get(&self, usz index) @inline
{
return self.entries[index];
}
fn void ElasticArray.swap(&self, usz i, usz j)
{
@swap(self.entries[i], self.entries[j]);
}
<*
@param filter "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
fn usz ElasticArray.remove_if(&self, ElementPredicate filter)
{
return list_common::list_remove_if(self, filter, false);
}
<*
@param selection "The function to determine if it should be kept or not"
@return "the number of deleted elements"
*>
fn usz ElasticArray.retain_if(&self, ElementPredicate selection)
{
return list_common::list_remove_if(self, selection, true);
}
fn usz ElasticArray.remove_using_test(&self, ElementTest filter, any context)
{
return list_common::list_remove_using_test(self, filter, false, context);
}
fn usz ElasticArray.retain_using_test(&self, ElementTest filter, any context)
{
return list_common::list_remove_using_test(self, filter, true, context);
}
macro Type ElasticArray.@item_at(&self, usz index) @operator([])
{
return self.entries[index];
}
fn Type* ElasticArray.get_ref(&self, usz index) @operator(&[]) @inline
{
return &self.entries[index];
}
fn void ElasticArray.set(&self, usz index, Type value) @operator([]=)
{
self.entries[index] = value;
}
// Functions for equatable types
fn usz! ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
{
if (equals(v, type)) return i;
}
return SearchResult.MISSING?;
}
fn usz! ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach_r (i, v : self)
{
if (equals(v, type)) return i;
}
return SearchResult.MISSING?;
}
fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE)
{
if (self.size != other_list.size) return false;
foreach (i, v : self)
{
if (!equals(v, other_list.entries[i])) return false;
}
return true;
}
<*
Check for presence of a value in a list.
@param [&in] self "the list to find elements in"
@param value "The value to search for"
@return "True if the value is found, false otherwise"
*>
fn bool ElasticArray.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
{
if (equals(v, value)) return true;
}
return false;
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool ElasticArray.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
return @ok(self.remove_at(self.rindex_of(value)));
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool ElasticArray.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
return @ok(self.remove_at(self.index_of(value)));
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "the number of deleted elements."
*>
fn usz ElasticArray.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
return list_common::list_remove_item(self, value);
}
fn void ElasticArray.remove_all_from(&self, ElasticArray* other_list) @if(ELEMENT_IS_EQUATABLE)
{
if (!other_list.size) return;
foreach (v : other_list) self.remove_item(v);
}
<*
@param [&in] self
@return "The number non-null values in the list"
*>
fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER)
{
usz vals = 0;
foreach (v : self) if (v) vals++;
return vals;
}
fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER)
{
return list_common::list_compact(self);
}

View File

@@ -1,6 +1,3 @@
<*
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap"
*>
module std::collections::enummap(<Enum, ValueType>);
import std::io;
struct EnumMap (Printable)
@@ -22,20 +19,15 @@ fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
foreach (i, &value : self.values)
{
if (i != 0) formatter.print(", ")!;
n += formatter.printf("%s: %s", Enum.from_ordinal(i), *value)!;
n += formatter.printf("%s: %s", (Enum)i, *value)!;
}
n += formatter.print(" }")!;
return n;
}
fn String EnumMap.to_string(&self, Allocator allocator) @dynamic
fn String EnumMap.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return string::format("%s", *self, allocator: allocator);
}
fn String EnumMap.to_new_string(&self, Allocator allocator = null) @dynamic
{
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
return string::new_format("%s", *self, .allocator = allocator);
}
fn String EnumMap.to_tstring(&self) @dynamic
@@ -43,18 +35,18 @@ fn String EnumMap.to_tstring(&self) @dynamic
return string::tformat("%s", *self);
}
<*
@return "The total size of this map, which is the same as the number of enum values"
@pure
*>
/**
* @return "The total size of this map, which is the same as the number of enum values"
* @pure
**/
fn usz EnumMap.len(&self) @operator(len) @inline
{
return self.values.len;
}
<*
@return "Retrieve a value given the underlying enum, if there is no entry, then the zero value for the value is returned."
*>
/**
* @return "Retrieve a value given the underlying enum, if there is no entry, then the zero value for the value is returned."
**/
fn ValueType EnumMap.get(&self, Enum key) @operator([]) @inline
{
return self.values[key.ordinal];

View File

@@ -2,9 +2,9 @@
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enumset"
*>
/**
* @require Enum.kindof == TypeKind.ENUM : "Only enums maybe be used with an enumset"
**/
module std::collections::enumset(<Enum>);
import std::io;
@@ -16,9 +16,9 @@ distinct EnumSet (Printable) = EnumSetType;
fn void EnumSet.add(&self, Enum v)
{
$if IS_CHAR_ARRAY:
(*self)[(usz)v.ordinal / 8] |= (char)(1u << ((usz)v.ordinal % 8));
(*self)[(usz)v / 8] |= (char)(1u << ((usz)v % 8));
$else
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v.ordinal);
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v);
$endif
}
@@ -35,11 +35,11 @@ fn bool EnumSet.remove(&self, Enum v)
{
$if IS_CHAR_ARRAY:
if (!self.has(v) @inline) return false;
(*self)[(usz)v.ordinal / 8] &= (char)~(1u << ((usz)v.ordinal % 8));
(*self)[(usz)v / 8] &= (char)~(1u << ((usz)v % 8));
return true;
$else
EnumSetType old = (EnumSetType)*self;
EnumSetType new = old & ~(1u << (EnumSetType)v.ordinal);
EnumSetType new = old & ~(1u << (EnumSetType)v);
*self = (EnumSet)new;
return old != new;
$endif
@@ -48,9 +48,9 @@ fn bool EnumSet.remove(&self, Enum v)
fn bool EnumSet.has(&self, Enum v)
{
$if IS_CHAR_ARRAY:
return (bool)(((*self)[(usz)v.ordinal / 8] << ((usz)v.ordinal % 8)) & 0x01);
return (bool)(((*self)[(usz)v / 8] << ((usz)v % 8)) & 0x01);
$else
return ((EnumSetType)*self & (1u << (EnumSetType)v.ordinal)) != 0;
return ((EnumSetType)*self & (1u << (EnumSetType)v)) != 0;
$endif
}
@@ -143,12 +143,7 @@ fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
fn String EnumSet.to_new_string(&set, Allocator allocator = allocator::heap()) @dynamic
{
return string::format("%s", *set, allocator: allocator);
}
fn String EnumSet.to_string(&set, Allocator allocator) @dynamic
{
return string::format("%s", *set, allocator: allocator);
return string::new_format("%s", *set, .allocator = allocator);
}
fn String EnumSet.to_tstring(&set) @dynamic

View File

@@ -1,669 +0,0 @@
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@require $defined((Key){}.hash()) `No .hash function found on the key`
*>
module std::collections::map(<Key, Value>);
import std::math;
import std::io @norecurse;
struct HashMap (Printable)
{
Entry*[] table;
Allocator allocator;
uint count; // Number of elements
uint threshold; // Resize limit
float load_factor;
}
<*
@param [&inout] allocator "The allocator to use"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.new_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = null) @deprecated("Use init(mem)")
{
return self.init(allocator ?: allocator::heap(), capacity, load_factor);
}
<*
@param [&inout] allocator "The allocator to use"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
self.load_factor = load_factor;
self.threshold = (uint)(capacity * load_factor);
self.table = allocator::new_array(allocator, Entry*, capacity);
return self;
}
<*
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) @deprecated("Use tinit()")
{
return self.init(allocator::temp(), capacity, load_factor) @inline;
}
<*
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(allocator::temp(), capacity, load_factor) @inline;
}
<*
@param [&inout] allocator "The allocator to use"
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro HashMap* HashMap.new_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap()) @deprecated("Use init_with_key_values(mem)")
{
self.init(capacity, load_factor, allocator);
$for (var $i = 0; $i < $vacount; $i += 2)
self.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return self;
}
<*
@param [in] keys "The keys for the HashMap entries"
@param [in] values "The values for the HashMap entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.new_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap()) @deprecated("Use init_from_keys_and_values(mem)")
{
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;
}
<*
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro HashMap* HashMap.temp_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) @deprecated("Use tinit_with_key_values")
{
self.tinit(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.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro HashMap* HashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.tinit(capacity, load_factor);
$for (var $i = 0; $i < $vacount; $i += 2)
self.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return self;
}
<*
@param [in] keys "The keys for the HashMap entries"
@param [in] values "The values for the HashMap entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.temp_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap()) @deprecated("Use tinit_from_keys_and_values")
{
assert(keys.len == values.len);
self.tinit(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 HashMap entries"
@param [in] values "The values for the HashMap entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
assert(keys.len == values.len);
self.tinit(capacity, load_factor);
for (usz i = 0; i < keys.len; i++)
{
self.set(keys[i], values[i]);
}
return self;
}
<*
Has this hash map been initialized yet?
@param [&in] map "The hash map we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool HashMap.is_initialized(&map)
{
return (bool)map.allocator;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn HashMap* HashMap.new_init_from_map(&self, HashMap* other_map) @deprecated("Use init_from_map(mem, map)")
{
return self.init_from_map(allocator::heap(), other_map) @inline;
}
<*
@param [&inout] allocator "The allocator to use"
@param [&in] other_map "The map to copy from."
*>
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);
return self;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map) @deprecated("Use tinit_from_map")
{
return map.init_from_map(allocator::temp(), other_map) @inline;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn HashMap* HashMap.tinit_from_map(&map, HashMap* other_map)
{
return map.init_from_map(allocator::temp(), other_map) @inline;
}
fn bool HashMap.is_empty(&map) @inline
{
return !map.count;
}
fn usz HashMap.len(&map) @inline
{
return map.count;
}
fn Value*! HashMap.get_ref(&map, Key key)
{
if (!map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
return SearchResult.MISSING?;
}
fn Entry*! HashMap.get_entry(&map, Key key)
{
if (!map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return SearchResult.MISSING?;
}
<*
Get the value or update and
@require $assignable(#expr, Value)
*>
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
{
if (!map.count)
{
Value val = #expr;
map.set(key, val);
return val;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e.value;
}
Value val = #expr;
map.add_entry(hash, key, val, index);
return val;
}
fn Value! HashMap.get(&map, Key key) @operator([])
{
return *map.get_ref(key) @inline;
}
fn bool HashMap.has_key(&map, Key key)
{
return @ok(map.get_ref(key));
}
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
if (!map.allocator)
{
map.init(allocator::heap());
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return true;
}
}
map.add_entry(hash, key, value, index);
return false;
}
fn void! HashMap.remove(&map, Key key) @maydiscard
{
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
}
fn void HashMap.clear(&map)
{
if (!map.count) return;
foreach (Entry** &entry_ref : map.table)
{
Entry* entry = *entry_ref;
if (!entry) continue;
Entry *next = entry.next;
while (next)
{
Entry *to_delete = next;
next = next.next;
map.free_entry(to_delete);
}
map.free_entry(entry);
*entry_ref = null;
}
map.count = 0;
}
fn void HashMap.free(&map)
{
if (!map.allocator) return;
map.clear();
map.free_internal(map.table.ptr);
map.table = {};
}
fn Key[] HashMap.tcopy_keys(&map)
{
return map.copy_keys(allocator::temp()) @inline;
}
fn Key[] HashMap.key_tlist(&map) @deprecated("Use 'tcopy_keys'")
{
return map.copy_keys(allocator::temp()) @inline;
}
<*
@deprecated "use copy_keys"
*>
fn Key[] HashMap.key_new_list(&map, Allocator allocator = allocator::heap())
{
return map.copy_keys(allocator) @inline;
}
fn Key[] HashMap.copy_keys(&map, Allocator allocator = allocator::heap())
{
if (!map.count) return {};
Key[] list = allocator::alloc_array(allocator, Key, map.count);
usz index = 0;
foreach (Entry* entry : map.table)
{
while (entry)
{
$if COPY_KEYS:
list[index++] = entry.key.copy(allocator);
$else
list[index++] = entry.key;
$endif
entry = entry.next;
}
}
return list;
}
macro HashMap.@each(map; @body(key, value))
{
map.@each_entry(; Entry* entry) {
@body(entry.key, entry.value);
};
}
macro HashMap.@each_entry(map; @body(entry))
{
if (map.count)
{
foreach (Entry* entry : map.table)
{
while (entry)
{
@body(entry);
entry = entry.next;
}
}
}
}
<*
@deprecated `use tcopy_values`
*>
fn Value[] HashMap.value_tlist(&map)
{
return map.copy_values(allocator::temp()) @inline;
}
fn Value[] HashMap.tcopy_values(&map)
{
return map.copy_values(allocator::temp()) @inline;
}
<*
@deprecated `use copy_values`
*>
fn Value[] HashMap.value_new_list(&map, Allocator allocator = allocator::heap())
{
return map.copy_values(allocator);
}
fn Value[] HashMap.copy_values(&map, Allocator allocator = allocator::heap())
{
if (!map.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, map.count);
usz index = 0;
foreach (Entry* entry : map.table)
{
while (entry)
{
list[index++] = entry.value;
entry = entry.next;
}
}
return list;
}
fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
{
if (!map.count) return false;
foreach (Entry* entry : map.table)
{
while (entry)
{
if (equals(v, entry.value)) return true;
entry = entry.next;
}
}
return false;
}
fn HashMapIterator HashMap.iter(&self)
{
return { .map = self, .index = -1 };
}
fn HashMapValueIterator HashMap.value_iter(&self)
{
return { .map = self, .index = -1 };
}
fn HashMapKeyIterator HashMap.key_iter(&self)
{
return { .map = self, .index = -1 };
}
// --- private methods
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
{
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry;
if (map.count++ >= map.threshold)
{
map.resize(map.table.len * 2);
}
}
fn void HashMap.resize(&map, uint new_capacity) @private
{
Entry*[] old_table = map.table;
uint old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
map.threshold = uint.max;
return;
}
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
map.transfer(new_table);
map.table = new_table;
map.free_internal(old_table.ptr);
map.threshold = (uint)(new_capacity * map.load_factor);
}
fn usz! HashMap.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each_entry(; Entry* entry)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s: %s", entry.key, entry.value)!;
};
return len + f.print(" }");
}
fn void HashMap.transfer(&map, Entry*[] new_table) @private
{
Entry*[] src = map.table;
uint new_capacity = new_table.len;
foreach (uint j, Entry *e : src)
{
if (!e) continue;
do
{
Entry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
{
if (!other_map.count) return;
foreach (Entry *e : other_map.table)
{
while (e)
{
map.put_for_create(e.key, e.value);
e = e.next;
}
}
}
fn void HashMap.put_for_create(&map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
for (Entry *e = map.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return;
}
}
map.create_entry(hash, key, value, i);
}
fn void HashMap.free_internal(&map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
{
if (!map.count) return false;
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
Entry* prev = map.table[i];
Entry* e = prev;
while (e)
{
Entry *next = e.next;
if (e.hash == hash && equals(key, e.key))
{
map.count--;
if (prev == e)
{
map.table[i] = next;
}
else
{
prev.next = next;
}
map.free_entry(e);
return true;
}
prev = e;
e = next;
}
return false;
}
fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
{
Entry *e = map.table[bucket_index];
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry;
map.count++;
}
fn void HashMap.free_entry(&self, Entry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
$endif
self.free_internal(entry);
}
struct HashMapIterator
{
HashMap* map;
int top_index;
int index;
Entry* current_entry;
}
distinct HashMapValueIterator = HashMapIterator;
distinct HashMapKeyIterator = HashMapIterator;
<*
@require idx < self.map.count
*>
fn Entry HashMapIterator.get(&self, usz idx) @operator([])
{
if (idx < self.index)
{
self.top_index = 0;
self.current_entry = null;
self.index = -1;
}
while (self.index != idx)
{
if (self.current_entry)
{
self.current_entry = self.current_entry.next;
if (self.current_entry) self.index++;
continue;
}
self.current_entry = self.map.table[self.top_index++];
if (self.current_entry) self.index++;
}
return *self.current_entry;
}
fn Value HashMapValueIterator.get(&self, usz idx) @operator([])
{
return ((HashMapIterator*)self).get(idx).value;
}
fn Key HashMapKeyIterator.get(&self, usz idx) @operator([])
{
return ((HashMapIterator*)self).get(idx).key;
}
fn usz HashMapValueIterator.len(self) @operator(len) => self.map.count;
fn usz HashMapKeyIterator.len(self) @operator(len) => self.map.count;
fn usz HashMapIterator.len(self) @operator(len) => self.map.count;

View File

@@ -20,38 +20,25 @@ struct LinkedList
Node *_last;
}
<*
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
@return "the initialized list"
*>
fn LinkedList* LinkedList.init(&self, Allocator allocator)
/**
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
* @return "the initialized list"
**/
fn LinkedList* LinkedList.new_init(&self, Allocator allocator = allocator::heap())
{
*self = { .allocator = allocator };
return self;
}
<*
@return "the initialized list"
*>
fn LinkedList* LinkedList.new_init(&self) @deprecated("Use init(mem)")
fn LinkedList* LinkedList.temp_init(&self)
{
return self.init(allocator::heap()) @inline;
return self.new_init(allocator::temp()) @inline;
}
fn LinkedList* LinkedList.temp_init(&self) @deprecated("Use tinit()")
{
return self.init(allocator::temp()) @inline;
}
fn LinkedList* LinkedList.tinit(&self)
{
return self.init(allocator::temp()) @inline;
}
<*
@require self.allocator != null
*>
/**
* @require self.allocator
**/
macro void LinkedList.free_node(&self, Node* node) @private
{
allocator::free(self.allocator, node);
@@ -129,9 +116,9 @@ fn void LinkedList.clear(&self)
fn usz LinkedList.len(&self) @inline => self.size;
<*
@require index < self.size
*>
/**
* @require index < self.size
**/
macro Node* LinkedList.node_at_index(&self, usz index)
{
if (index * 2 >= self.size)
@@ -145,33 +132,33 @@ macro Node* LinkedList.node_at_index(&self, usz index)
while (index--) node = node.next;
return node;
}
<*
@require index < self.size
*>
/**
* @require index < self.size
**/
fn Type LinkedList.get(&self, usz index)
{
return self.node_at_index(index).value;
}
<*
@require index < self.size
*>
/**
* @require index < self.size
**/
fn void LinkedList.set(&self, usz index, Type element)
{
self.node_at_index(index).value = element;
}
<*
@require index < self.size
*>
/**
* @require index < self.size
**/
fn void LinkedList.remove_at(&self, usz index)
{
self.unlink(self.node_at_index(index));
}
<*
@require index <= self.size
*>
/**
* @require index <= self.size
**/
fn void LinkedList.insert_at(&self, usz index, Type element)
{
switch (index)
@@ -184,9 +171,9 @@ fn void LinkedList.insert_at(&self, usz index, Type element)
self.link_before(self.node_at_index(index), element);
}
}
<*
@require succ != null
*>
/**
* @require succ != null
**/
fn void LinkedList.link_before(&self, Node *succ, Type value) @private
{
Node* pred = succ.prev;
@@ -204,9 +191,9 @@ fn void LinkedList.link_before(&self, Node *succ, Type value) @private
self.size++;
}
<*
@require self._first != null
*>
/**
* @require self._first
**/
fn void LinkedList.unlink_first(&self) @private
{
Node* f = self._first;
@@ -250,11 +237,6 @@ fn Type! LinkedList.pop(&self)
return self._last.value;
}
fn bool LinkedList.is_empty(&self)
{
return !self._first;
}
fn Type! LinkedList.pop_front(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
@@ -300,9 +282,9 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
}
return false;
}
<*
@require self._last != null
*>
/**
* @require self._last
**/
fn void LinkedList.unlink_last(&self) @inline @private
{
Node* l = self._last;
@@ -320,9 +302,9 @@ fn void LinkedList.unlink_last(&self) @inline @private
self.size--;
}
<*
@require x != null
*>
/**
* @require x != null
**/
fn void LinkedList.unlink(&self, Node* x) @private
{
Node* next = x.next;

View File

@@ -2,15 +2,13 @@
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::list(<Type>);
import std::io, std::math, std::collections::list_common;
import std::io,std::math;
def ElementPredicate = fn bool(Type *type);
def ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
struct List (Printable)
{
usz size;
@@ -19,113 +17,47 @@ struct List (Printable)
Type *entries;
}
<*
@param initial_capacity "The initial capacity to reserve"
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
*>
fn List* List.init(&self, Allocator allocator, usz initial_capacity = 16)
/**
* @param initial_capacity "The initial capacity to reserve"
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
**/
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
{
self.allocator = allocator;
self.size = 0;
self.capacity = 0;
self.entries = null;
self.reserve(initial_capacity);
if (initial_capacity > 0)
{
initial_capacity = math::next_power_of_2(initial_capacity);
self.entries = allocator::malloc_aligned(allocator, Type.sizeof * initial_capacity, .alignment = Type[1].alignof)!!;
}
else
{
self.entries = null;
}
self.capacity = initial_capacity;
return self;
}
<*
@param initial_capacity "The initial capacity to reserve"
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
*>
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
/**
* Initialize the list using the temp allocator.
*
* @param initial_capacity "The initial capacity to reserve"
**/
fn List* List.temp_init(&self, usz initial_capacity = 16)
{
self.allocator = allocator;
self.size = 0;
self.capacity = 0;
self.entries = null;
self.reserve(initial_capacity);
return self;
return self.new_init(initial_capacity, allocator::temp()) @inline;
}
<*
Initialize the list using the temp allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn List* List.temp_init(&self, usz initial_capacity = 16) @deprecated("Use tinit()")
{
return self.init(allocator::temp(), initial_capacity) @inline;
}
<*
Initialize the list using the temp allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn List* List.tinit(&self, usz initial_capacity = 16)
{
return self.init(allocator::temp(), initial_capacity) @inline;
}
<*
Initialize a new list with an array.
@param [in] values `The values to initialize the list with.`
@require self.size == 0 "The List must be empty"
*>
fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = allocator::heap()) @deprecated("Use init_with_array(mem)")
{
return self.init_with_array(allocator, values);
}
<*
Initialize a new list with an array.
@param [in] values `The values to initialize the list with.`
@require self.size == 0 "The List must be empty"
*>
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
{
self.init(allocator, values.len) @inline;
self.add_array(values) @inline;
return self;
}
<*
Initialize a temporary list with an array.
@param [in] values `The values to initialize the list with.`
@require self.size == 0 "The List must be empty"
*>
fn List* List.temp_init_with_array(&self, Type[] values) @deprecated("Use tinit_with_array()")
{
self.tinit(values.len) @inline;
self.add_array(values) @inline;
return self;
}
<*
Initialize a temporary list with an array.
@param [in] values `The values to initialize the list with.`
@require self.size == 0 "The List must be empty"
*>
fn List* List.tinit_with_array(&self, Type[] values)
{
self.tinit(values.len) @inline;
self.add_array(values) @inline;
return self;
}
<*
@require self.capacity == 0 "The List must not be allocated"
*>
/**
* @require self.size == 0 "The List must be empty"
**/
fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allocator::heap())
{
self.allocator = allocator;
self.size = types.len;
self.capacity = types.len;
self.entries = types.ptr;
self.set_size(types.len);
}
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
@@ -150,7 +82,7 @@ fn usz! List.to_format(&self, Formatter* formatter) @dynamic
fn String List.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return string::format("%s", *self, allocator: allocator);
return string::new_format("%s", *self, .allocator = allocator);
}
fn String List.to_tstring(&self)
@@ -160,22 +92,24 @@ fn String List.to_tstring(&self)
fn void List.push(&self, Type element) @inline
{
self.reserve(1);
self.entries[self.set_size(self.size + 1)] = element;
self.ensure_capacity();
self.entries[self.size++] = element;
}
fn Type! List.pop(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.set_size(self.size - 1);
return self.entries[self.size - 1];
return self.entries[--self.size];
}
fn void List.clear(&self)
{
self.set_size(0);
self.size = 0;
}
/**
* @require self.size > 0
**/
fn Type! List.pop_first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
@@ -183,13 +117,12 @@ fn Type! List.pop_first(&self)
return self.entries[0];
}
<*
@require index < self.size `Removed element out of bounds`
*>
/**
* @require index < self.size
**/
fn void List.remove_at(&self, usz index)
{
self.set_size(self.size - 1);
if (!self.size || index == self.size) return;
if (!--self.size || index == self.size) return;
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
}
@@ -197,45 +130,38 @@ 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);
foreach (&value : other_list)
{
self.entries[index++] = *value;
self.entries[self.size++] = *value;
}
}
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
fn Type[] List.to_new_aligned_array(&self, Allocator allocator = allocator::heap())
fn Type[] List.to_new_array(&self, Allocator allocator = allocator::heap())
{
return list_common::list_to_new_aligned_array(Type, self, allocator);
}
<*
@require !type_is_overaligned() : "This function is not available on overaligned types"
*>
macro Type[] List.to_new_array(&self, Allocator allocator = allocator::heap())
{
return list_common::list_to_new_array(Type, self, allocator);
if (!self.size) return Type[] {};
Type[] result = allocator::alloc_array(allocator, Type, self.size);
result[..] = self.entries[:self.size];
return result;
}
fn Type[] List.to_tarray(&self)
{
$if type_is_overaligned():
return self.to_new_aligned_array(allocator::temp());
$else
return self.to_new_array(allocator::temp());
$endif;
}
<*
Reverse the elements in a list.
*>
/**
* Reverse the elements in a list.
**/
fn void List.reverse(&self)
{
list_common::list_reverse(self);
if (self.size < 2) return;
usz half = self.size / 2U;
usz end = self.size - 1;
for (usz i = 0; i < half; i++)
{
@swap(self.entries[i], self.entries[end - i]);
}
}
fn Type[] List.array_view(&self)
@@ -243,18 +169,14 @@ fn Type[] List.array_view(&self)
return self.entries[:self.size];
}
<*
Add the values of an array to this list.
@param [in] array
@ensure self.size >= array.len
*>
fn void List.add_array(&self, Type[] array)
{
if (!array.len) return;
self.reserve(array.len);
usz index = self.set_size(self.size + array.len);
self.entries[index : array.len] = array[..];
foreach (&value : array)
{
self.entries[self.size++] = *value;
}
}
fn void List.push_front(&self, Type type) @inline
@@ -262,23 +184,23 @@ fn void List.push_front(&self, Type type) @inline
self.insert_at(0, type);
}
<*
@require index <= self.size `Insert was out of bounds`
*>
/**
* @require index < self.size
**/
fn void List.insert_at(&self, usz index, Type type)
{
self.reserve(1);
self.set_size(self.size + 1);
for (isz i = self.size - 1; i > index; i--)
self.ensure_capacity();
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.size++;
self.entries[index] = type;
}
<*
@require index < self.size
*>
/**
* @require index < self.size
**/
fn void List.set_at(&self, usz index, Type type)
{
self.entries[index] = type;
@@ -287,7 +209,7 @@ fn void List.set_at(&self, usz index, Type type)
fn void! List.remove_last(&self) @maydiscard
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.set_size(self.size - 1);
self.size--;
}
fn void! List.remove_first(&self) @maydiscard
@@ -323,9 +245,6 @@ fn usz List.len(&self) @operator(len) @inline
return self.size;
}
<*
@require index < self.size `Access out of bounds`
*>
fn Type List.get(&self, usz index) @inline
{
return self.entries[index];
@@ -333,111 +252,125 @@ fn Type List.get(&self, usz index) @inline
fn void List.free(&self)
{
if (!self.allocator || !self.capacity) return;
self.pre_free(); // Remove sanitizer annotation
$if type_is_overaligned():
allocator::free_aligned(self.allocator, self.entries);
$else
allocator::free(self.allocator, self.entries);
$endif;
if (!self.allocator) return;
allocator::free_aligned(self.allocator, self.entries);
self.capacity = 0;
self.size = 0;
self.entries = null;
}
<*
@require i < self.size && j < self.size `Access out of bounds`
*>
fn void List.swap(&self, usz i, usz j)
{
@swap(self.entries[i], self.entries[j]);
}
<*
@param filter "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
/**
* @param filter "The function to determine if it should be removed or not"
* @return "the number of deleted elements"
**/
fn usz List.remove_if(&self, ElementPredicate filter)
{
return list_common::list_remove_if(self, filter, false);
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"
*>
/**
* @param selection "The function to determine if it should be kept or not"
* @return "the number of deleted elements"
**/
fn usz List.retain_if(&self, ElementPredicate selection)
{
return list_common::list_remove_if(self, selection, true);
return self._remove_if(selection, true);
}
macro usz List._remove_if(&self, ElementPredicate 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;
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 List.remove_using_test(&self, ElementTest filter, any context)
{
usz old_size = self.size;
defer
{
if (old_size != self.size) self._update_size_change(old_size, self.size);
}
return list_common::list_remove_using_test(self, filter, false, context);
return self._remove_using_test(filter, false, context);
}
fn usz List.retain_using_test(&self, ElementTest filter, any context)
{
usz old_size = self.size;
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
}
return list_common::list_remove_using_test(self, filter, true, context);
return self._remove_using_test(filter, true, context);
}
fn void List.ensure_capacity(&self, usz min_capacity) @local
macro usz List._remove_using_test(&self, ElementTest 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;
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 List.reserve(&self, usz min_capacity)
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = allocator::heap();
self.pre_free(); // Remove sanitizer annotation
min_capacity = math::next_power_of_2(min_capacity);
$if type_is_overaligned():
self.entries = allocator::realloc_aligned(self.allocator, self.entries, Type.sizeof * min_capacity, alignment: Type[1].alignof)!!;
$else
self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity);
$endif;
self.entries = allocator::realloc_aligned(self.allocator, self.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof) ?? null;
self.capacity = min_capacity;
self.post_alloc(); // Add sanitizer annotation
}
<*
@require index < self.size `Access out of bounds`
*>
macro Type List.@item_at(&self, usz index) @operator([])
{
return self.entries[index];
}
<*
@require index < self.size `Access out of bounds`
*>
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
{
return &self.entries[index];
}
<*
@require index < self.size `Access out of bounds`
*>
fn void List.set(&self, usz index, Type value) @operator([]=)
{
self.entries[index] = value;
}
fn void List.reserve(&self, usz added)
fn void List.ensure_capacity(&self, usz added = 1) @inline @private
{
usz new_size = self.size + added;
if (self.capacity >= new_size) return;
@@ -445,40 +378,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);
}
fn void List._update_size_change(&self,usz old_size, usz new_size)
{
if (old_size == new_size) return;
sanitizer::annotate_contiguous_container(self.entries,
&self.entries[self.capacity],
&self.entries[old_size],
&self.entries[new_size]);
}
<*
@require new_size == 0 || self.capacity != 0
*>
fn usz List.set_size(&self, usz new_size) @inline @private
{
usz old_size = self.size;
self._update_size_change(old_size, new_size);
self.size = new_size;
return old_size;
}
macro void List.pre_free(&self) @private
{
if (!self.capacity) return;
self._update_size_change(self.size, self.capacity);
}
<*
@require self.capacity > 0
*>
macro void List.post_alloc(&self) @private
{
self._update_size_change(self.capacity, self.size);
self.reserve(new_capacity);
}
// Functions for equatable types
@@ -512,13 +412,13 @@ fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
return true;
}
<*
Check for presence of a value in a list.
@param [&in] self "the list to find elements in"
@param value "The value to search for"
@return "True if the value is found, false otherwise"
*>
/**
* Check for presence of a value in a list.
*
* @param [&in] self "the list to find elements in"
* @param value "The value to search for"
* @return "True if the value is found, false otherwise"
**/
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
@@ -528,55 +428,57 @@ fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
return false;
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
/**
* @param [&inout] self "The list to remove elements from"
* @param value "The value to remove"
* @return "true if the value was found"
**/
fn bool List.remove_last_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
return @ok(self.remove_at(self.rindex_of(value)));
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
/**
* @param [&inout] self "The list to remove elements from"
* @param value "The value to remove"
* @return "true if the value was found"
**/
fn bool List.remove_first_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
return @ok(self.remove_at(self.index_of(value)));
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "the number of deleted elements."
*>
fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
/**
* @param [&inout] self "The list to remove elements from"
* @param value "The value to remove"
* @return "the number of deleted elements."
**/
fn usz List.remove_all_matches(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
usz old_size = self.size;
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (!equals(self.entries[i - 1], value)) continue;
for (usz j = i; j < self.size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return list_common::list_remove_item(self, value);
return size - self.size;
}
fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
{
if (!other_list.size) return;
usz old_size = self.size;
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
}
foreach (v : other_list) self.remove_item(v);
foreach (v : other_list) self.remove_all_matches(v);
}
<*
@param [&in] self
@return "The number non-null values in the list"
*>
/**
* @param [&in] self
* @return "The number non-null values in the list"
**/
fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
{
usz vals = 0;
@@ -586,42 +488,15 @@ fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
{
usz old_size = self.size;
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (self.entries[i - 1]) continue;
for (usz j = i; j < size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return list_common::list_compact(self);
}
// --> Deprecated
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_last_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_last_item(value) @inline;
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_first_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_first_item(value) @inline;
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "the number of deleted elements."
*>
fn usz List.remove_all_matches(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_item(value) @inline;
return size - self.size;
}

View File

@@ -1,112 +0,0 @@
module std::collections::list_common;
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
macro list_to_new_aligned_array($Type, self, Allocator allocator)
{
if (!self.size) return ($Type[]){};
$Type[] result = allocator::alloc_array_aligned(allocator, $Type, self.size);
result[..] = self.entries[:self.size];
return result;
}
macro list_to_new_array($Type, self, Allocator allocator)
{
if (!self.size) return ($Type[]){};
$Type[] result = allocator::alloc_array(allocator, $Type, self.size);
result[..] = self.entries[:self.size];
return result;
}
macro void list_reverse(self)
{
if (self.size < 2) return;
usz half = self.size / 2U;
usz end = self.size - 1;
for (usz i = 0; i < half; i++)
{
@swap(self.entries[i], self.entries[end - i]);
}
}
macro usz list_remove_using_test(self, filter, bool $invert, ctx)
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$endif
}
return size - self.size;
}
macro usz list_compact(self)
{
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (self.entries[i - 1]) continue;
for (usz j = i; j < size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return size - self.size;
}
macro usz list_remove_item(self, value)
{
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (!equals(self.entries[i - 1], value)) continue;
for (usz j = i; j < self.size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return size - self.size;
}
macro usz list_remove_if(self, filter, bool $invert)
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && filter(&self.entries[i - 1])) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$endif
}
return size - self.size;
}

View File

@@ -10,9 +10,7 @@ const float DEFAULT_LOAD_FACTOR = 0.75;
const VALUE_IS_EQUATABLE = Value.is_eq;
const bool COPY_KEYS = types::implements_copy(Key);
distinct Map = void*;
struct MapImpl
struct HashMap
{
Entry*[] table;
Allocator allocator;
@@ -21,147 +19,78 @@ struct MapImpl
float load_factor;
}
<*
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map new(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap()) @deprecated("Map is deprecated")
/**
* @param [&inout] allocator "The allocator to use"
* @require capacity > 0 "The capacity must be 1 or higher"
* @require load_factor > 0.0 "The load factor must be higher than 0"
* @require !self.allocator "Map was already initialized"
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
**/
fn HashMap* HashMap.new_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
MapImpl* map = allocator::alloc(allocator, MapImpl);
_init(map, capacity, load_factor, allocator);
return (Map)map;
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
self.load_factor = load_factor;
self.threshold = (uint)(capacity * load_factor);
self.table = allocator::new_array(allocator, Entry*, capacity);
return self;
}
<*
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map temp(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
/**
* @require capacity > 0 "The capacity must be 1 or higher"
* @require load_factor > 0.0 "The load factor must be higher than 0"
* @require !self.allocator "Map was already initialized"
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
**/
fn HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
MapImpl* map = mem::temp_alloc(MapImpl);
_init(map, capacity, load_factor, allocator::temp());
return (Map)map;
return self.new_init(capacity, load_factor, allocator::temp()) @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 capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro Map new_init_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
/**
* Has this hash map been initialized yet?
*
* @param [&in] map "The hash map we are testing"
* @return "Returns true if it has been initialized, false otherwise"
**/
fn bool HashMap.is_initialized(&map)
{
Map map = new(capacity, load_factor, allocator);
$for (var $i = 0; $i < $vacount; $i += 2)
map.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return map;
return (bool)map.allocator;
}
<*
@param [in] keys "Array of keys for the Map entries"
@param [in] values "Array of values for the Map entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map new_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
/**
* @param [&inout] allocator "The allocator to use"
* @param [&in] other_map "The map to copy from."
**/
fn HashMap* HashMap.new_init_from_map(&self, HashMap* other_map, Allocator allocator = allocator::heap())
{
assert(keys.len == values.len);
Map map = new(capacity, load_factor, allocator);
for (usz i = 0; i < keys.len; i++)
{
map.set(keys[i], values[i]);
}
return map;
self.new_init(other_map.table.len, other_map.load_factor, allocator);
self.put_all_for_create(other_map);
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 capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro Map temp_new_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
/**
* @param [&in] other_map "The map to copy from."
**/
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map)
{
Map map = temp(capacity, load_factor);
$for (var $i = 0; $i < $vacount; $i += 2)
map.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return map;
return map.new_init_from_map(other_map, allocator::temp()) @inline;
}
<*
@param [in] keys "The keys for the HashMap entries"
@param [in] values "The values for the HashMap entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map temp_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
fn bool HashMap.is_empty(&map) @inline
{
assert(keys.len == values.len);
Map map = temp(capacity, load_factor);
for (usz i = 0; i < keys.len; i++)
{
map.set(keys[i], values[i]);
}
return map;
return !map.count;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn Map new_from_map(Map other_map, Allocator allocator = null)
fn usz HashMap.len(&map) @inline
{
MapImpl* other_map_impl = (MapImpl*)other_map;
if (!other_map_impl)
{
if (allocator) return new(allocator: allocator);
return null;
}
MapImpl* map = (MapImpl*)new(other_map_impl.table.len, other_map_impl.load_factor, allocator ?: allocator::heap());
if (!other_map_impl.count) return (Map)map;
foreach (Entry *e : other_map_impl.table)
{
while (e)
{
map._put_for_create(e.key, e.value);
e = e.next;
}
}
return (Map)map;
return map.count;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn Map temp_from_map(Map other_map)
fn Value*! HashMap.get_ref(&map, Key key)
{
return new_from_map(other_map, allocator::temp());
}
fn bool Map.is_empty(map) @inline
{
return !map || !((MapImpl*)map).count;
}
fn usz Map.len(map) @inline
{
return map ? ((MapImpl*)map).count : 0;
}
fn Value*! Map.get_ref(self, Key key)
{
MapImpl *map = (MapImpl*)self;
if (!map || !map.count) return SearchResult.MISSING?;
if (!map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
@@ -170,26 +99,24 @@ fn Value*! Map.get_ref(self, Key key)
return SearchResult.MISSING?;
}
fn Entry*! Map.get_entry(map, Key key)
fn Entry*! HashMap.get_entry(&map, Key key)
{
MapImpl *map_impl = (MapImpl*)map;
if (!map_impl || !map_impl.count) return SearchResult.MISSING?;
if (!map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map_impl.table[index_for(hash, map_impl.table.len)]; e != null; e = e.next)
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return SearchResult.MISSING?;
}
<*
Get the value or update and
@require $assignable(#expr, Value)
*>
macro Value Map.@get_or_set(&self, Key key, Value #expr)
/**
* Get the value or update and
* @require $assignable(#expr, Value)
**/
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
{
MapImpl *map = (MapImpl*)*self;
if (!map || !map.count)
if (!map.count)
{
Value val = #expr;
map.set(key, val);
@@ -206,27 +133,23 @@ macro Value Map.@get_or_set(&self, Key key, Value #expr)
return val;
}
fn Value! Map.get(map, Key key) @operator([])
fn Value! HashMap.get(&map, Key key) @operator([])
{
return *map.get_ref(key) @inline;
}
fn bool Map.has_key(map, Key key)
fn bool HashMap.has_key(&map, Key key)
{
return @ok(map.get_ref(key));
}
macro Value Map.set_value_return(&map, Key key, Value value) @operator([]=)
{
map.set(key, value);
return value;
}
fn bool Map.set(&self, Key key, Value value)
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
if (!*self) *self = new();
MapImpl* map = (MapImpl*)*self;
if (!map.allocator)
{
map.new_init();
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
@@ -237,19 +160,18 @@ fn bool Map.set(&self, Key key, Value value)
return true;
}
}
map._add_entry(hash, key, value, index);
map.add_entry(hash, key, value, index);
return false;
}
fn void! Map.remove(map, Key key) @maydiscard
fn void! HashMap.remove(&map, Key key) @maydiscard
{
if (!map || !((MapImpl*)map)._remove_entry_for_key(key)) return SearchResult.MISSING?;
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
}
fn void Map.clear(self)
fn void HashMap.clear(&map)
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return;
if (!map.count) return;
foreach (Entry** &entry_ref : map.table)
{
Entry* entry = *entry_ref;
@@ -259,33 +181,30 @@ fn void Map.clear(self)
{
Entry *to_delete = next;
next = next.next;
map._free_entry(to_delete);
map.free_entry(to_delete);
}
map._free_entry(entry);
map.free_entry(entry);
*entry_ref = null;
}
map.count = 0;
}
fn void Map.free(self)
fn void HashMap.free(&map)
{
if (!self) return;
MapImpl* map = (MapImpl*)self;
self.clear();
map._free_internal(map.table.ptr);
if (!map.allocator) return;
map.clear();
map.free_internal(map.table.ptr);
map.table = {};
allocator::free(map.allocator, map);
}
fn Key[] Map.temp_keys_list(map)
fn Key[] HashMap.key_tlist(&map)
{
return map.new_keys_list(allocator::temp()) @inline;
return map.key_new_list(allocator::temp()) @inline;
}
fn Key[] Map.new_keys_list(self, Allocator allocator = allocator::heap())
fn Key[] HashMap.key_new_list(&map, Allocator allocator = allocator::heap())
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return {};
if (!map.count) return {};
Key[] list = allocator::alloc_array(allocator, Key, map.count);
usz index = 0;
@@ -300,36 +219,36 @@ fn Key[] Map.new_keys_list(self, Allocator allocator = allocator::heap())
return list;
}
macro Map.@each(map; @body(key, value))
macro HashMap.@each(map; @body(key, value))
{
map.@each_entry(; Entry* entry) {
@body(entry.key, entry.value);
};
}
macro Map.@each_entry(self; @body(entry))
macro HashMap.@each_entry(map; @body(entry))
{
MapImpl *map = (MapImpl*)self;
if (!map || !map.count) return;
foreach (Entry* entry : map.table)
if (map.count)
{
while (entry)
foreach (Entry* entry : map.table)
{
@body(entry);
entry = entry.next;
while (entry)
{
@body(entry);
entry = entry.next;
}
}
}
}
fn Value[] Map.temp_values_list(map)
fn Value[] HashMap.value_tlist(&map)
{
return map.new_values_list(allocator::temp()) @inline;
return map.value_new_list(allocator::temp()) @inline;
}
fn Value[] Map.new_values_list(self, Allocator allocator = allocator::heap())
fn Value[] HashMap.value_new_list(&map, Allocator allocator = allocator::heap())
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return {};
if (!map.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, map.count);
usz index = 0;
foreach (Entry* entry : map.table)
@@ -343,10 +262,9 @@ fn Value[] Map.new_values_list(self, Allocator allocator = allocator::heap())
return list;
}
fn bool Map.has_value(self, Value v) @if(VALUE_IS_EQUATABLE)
fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return false;
if (!map.count) return false;
foreach (Entry* entry : map.table)
{
while (entry)
@@ -360,7 +278,7 @@ fn bool Map.has_value(self, Value v) @if(VALUE_IS_EQUATABLE)
// --- private methods
fn void MapImpl._add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
{
$if COPY_KEYS:
key = key.copy(map.allocator);
@@ -369,11 +287,11 @@ fn void MapImpl._add_entry(&map, uint hash, Key key, Value value, uint bucket_in
map.table[bucket_index] = entry;
if (map.count++ >= map.threshold)
{
map._resize(map.table.len * 2);
map.resize(map.table.len * 2);
}
}
fn void MapImpl._resize(&map, uint new_capacity) @private
fn void HashMap.resize(&map, uint new_capacity) @private
{
Entry*[] old_table = map.table;
uint old_capacity = old_table.len;
@@ -383,9 +301,9 @@ fn void MapImpl._resize(&map, uint new_capacity) @private
return;
}
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
map._transfer(new_table);
map.transfer(new_table);
map.table = new_table;
map._free_internal(old_table.ptr);
map.free_internal(old_table.ptr);
map.threshold = (uint)(new_capacity * map.load_factor);
}
@@ -400,7 +318,7 @@ macro uint index_for(uint hash, uint capacity) @private
return hash & (capacity - 1);
}
fn void MapImpl._transfer(&map, Entry*[] new_table) @private
fn void HashMap.transfer(&map, Entry*[] new_table) @private
{
Entry*[] src = map.table;
uint new_capacity = new_table.len;
@@ -419,18 +337,17 @@ fn void MapImpl._transfer(&map, Entry*[] new_table) @private
}
}
fn void _init(MapImpl* impl, uint capacity, float load_factor, Allocator allocator) @private
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
{
capacity = math::next_power_of_2(capacity);
*impl = {
.allocator = allocator,
.load_factor = load_factor,
.threshold = (uint)(capacity * load_factor),
.table = allocator::new_array(allocator, Entry*, capacity)
};
if (!other_map.count) return;
foreach (Entry *e : other_map.table)
{
if (!e) continue;
map.put_for_create(e.key, e.value);
}
}
fn void MapImpl._put_for_create(&map, Key key, Value value) @private
fn void HashMap.put_for_create(&map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
@@ -442,17 +359,16 @@ fn void MapImpl._put_for_create(&map, Key key, Value value) @private
return;
}
}
map._create_entry(hash, key, value, i);
map.create_entry(hash, key, value, i);
}
fn void MapImpl._free_internal(&map, void* ptr) @inline @private
fn void HashMap.free_internal(&map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
fn bool MapImpl._remove_entry_for_key(&map, Key key) @private
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
{
if (!map.count) return false;
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
Entry* prev = map.table[i];
@@ -471,7 +387,7 @@ fn bool MapImpl._remove_entry_for_key(&map, Key key) @private
{
prev.next = next;
}
map._free_entry(e);
map.free_entry(e);
return true;
}
prev = e;
@@ -480,7 +396,7 @@ fn bool MapImpl._remove_entry_for_key(&map, Key key) @private
return false;
}
fn void MapImpl._create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
{
Entry *e = map.table[bucket_index];
$if COPY_KEYS:
@@ -491,12 +407,12 @@ fn void MapImpl._create_entry(&map, uint hash, Key key, Value value, int bucket_
map.count++;
}
fn void MapImpl._free_entry(&self, Entry *entry) @local
fn void HashMap.free_entry(&self, Entry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
$endif
self._free_internal(entry);
self.free_internal(entry);
}
struct Entry
@@ -505,4 +421,4 @@ struct Entry
Key key;
Value value;
Entry* next;
}
}

View File

@@ -1,43 +1,16 @@
module std::collections::maybe(<Type>);
import std::io;
struct Maybe (Printable)
struct Maybe
{
Type value;
bool has_value;
}
fn usz! Maybe.to_format(&self, Formatter* f) @dynamic
{
if (self.has_value) return f.printf("[%s]", self.value);
return f.printf("[EMPTY]");
}
fn void Maybe.set(&self, Type val)
{
*self = { .value = val, .has_value = true };
}
fn void Maybe.reset(&self)
{
*self = {};
}
fn Maybe value(Type val)
{
return { .value = val, .has_value = true };
}
fn Maybe Maybe.with_value(Type val) @deprecated("Use maybe::value instead.") @operator(construct)
{
return { .value = val, .has_value = true };
}
fn Maybe Maybe.empty() @deprecated("Use maybe::EMPTY instead.") @operator(construct)
{
return { };
}
const Maybe EMPTY = { };
macro Type! Maybe.get(self)

View File

@@ -48,9 +48,9 @@ fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
return n;
case ObjectInternalMap:
usz n = formatter.printf("{")!;
@stack_mem(1024; Allocator mem)
@pool()
{
foreach (i, key : self.map.copy_keys(mem))
foreach (i, key : self.map.key_tlist())
{
if (i > 0) n += formatter.printf(",")!;
n += formatter.printf(`"%s":`, key)!;
@@ -63,11 +63,11 @@ fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
switch (self.type.kindof)
{
case SIGNED_INT:
return formatter.printf("%d", (int128)self.i)!;
return formatter.printf("%d", self.i)!;
case UNSIGNED_INT:
return formatter.printf("%d", (uint128)self.i)!;
case FLOAT:
return formatter.printf("%g", self.f)!;
return formatter.printf("%d", self.f)!;
case ENUM:
return formatter.printf("%d", self.i)!;
default:
@@ -128,9 +128,9 @@ fn void Object.free(&self)
self.array.free();
case ObjectInternalMap:
self.map.@each_entry(; ObjectInternalMapEntry* entry) {
allocator::free(self.allocator, entry.key);
entry.value.free();
};
self.map.free();
default:
break;
}
@@ -148,47 +148,49 @@ fn bool Object.is_int(&self) @inline => self.type == int128.typeid;
fn bool Object.is_keyable(&self) => self.is_empty() || self.is_map();
fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array();
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn void Object.init_map_if_needed(&self) @private
{
if (self.is_empty())
{
self.type = ObjectInternalMap.typeid;
self.map.init(self.allocator);
self.map.new_init(.allocator = self.allocator);
}
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn void Object.init_array_if_needed(&self) @private
{
if (self.is_empty())
{
self.type = ObjectInternalList.typeid;
self.array.init(self.allocator);
self.array.new_init(.allocator = self.allocator);
}
}
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn void Object.set_object(&self, String key, Object* new_object) @private
{
self.init_map_if_needed();
ObjectInternalMapEntry*! entry = self.map.get_entry(key);
defer
{
(void)allocator::free(self.allocator, entry.key);
(void)entry.value.free();
}
self.map.set(key, new_object);
self.map.set(key.copy(self.map.allocator), new_object);
}
macro Object* Object.object_from_value(&self, value) @private
{
var $Type = $typeof(value);
$switch
$case types::is_int($Type):
return new_int(value, self.allocator);
@@ -218,9 +220,9 @@ macro Object* Object.set(&self, String key, value)
return val;
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
macro Object* Object.set_at(&self, usz index, String key, value)
{
Object* val = self.object_from_value(value);
@@ -228,10 +230,10 @@ macro Object* Object.set_at(&self, usz index, String key, value)
return val;
}
<*
@require self.is_indexable()
@ensure return != null
*>
/**
* @require self.is_indexable()
* @ensure return != null
**/
macro Object* Object.push(&self, value)
{
Object* val = self.object_from_value(value);
@@ -239,42 +241,42 @@ macro Object* Object.push(&self, value)
return val;
}
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn Object*! Object.get(&self, String key) => self.is_empty() ? SearchResult.MISSING? : self.map.get(key);
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn Object* Object.get_at(&self, usz index)
{
return self.array.get(index);
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn usz Object.get_len(&self)
{
return self.array.len();
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn void Object.push_object(&self, Object* to_append)
{
self.init_array_if_needed();
self.array.push(to_append);
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn void Object.set_object_at(&self, usz index, Object* to_set)
{
self.init_array_if_needed();
@@ -291,9 +293,9 @@ fn void Object.set_object_at(&self, usz index, Object* to_set)
self.array.set_at(index, to_set);
}
<*
@require $Type.kindof.is_int() "Expected an integer type."
*>
/**
* @require $Type.kindof.is_int() "Expected an integer type."
**/
macro get_integer_value(Object* value, $Type)
{
if (value.is_float())
@@ -313,19 +315,19 @@ macro get_integer_value(Object* value, $Type)
}
<*
@require self.is_indexable()
@require $Type.kindof.is_int() : "Expected an integer type"
*>
/**
* @require self.is_indexable()
* @require $Type.kindof.is_int() : "Expected an integer type"
**/
macro Object.get_integer_at(&self, $Type, usz index) @private
{
return get_integer_value(self.get_at(index), $Type);
}
<*
@require self.is_keyable()
@require $Type.kindof.is_int() : "Expected an integer type"
*>
/**
* @require self.is_keyable()
* @require $Type.kindof.is_int() : "Expected an integer type"
**/
macro Object.get_integer(&self, $Type, String key) @private
{
return get_integer_value(self.get(key), $Type);
@@ -355,9 +357,9 @@ fn uint! Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index
fn ulong! Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index);
fn uint128! Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn String! Object.get_string(&self, String key)
{
Object* value = self.get(key)!;
@@ -365,9 +367,9 @@ fn String! Object.get_string(&self, String key)
return value.s;
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn String! Object.get_string_at(&self, usz index)
{
Object* value = self.get_at(index);
@@ -375,9 +377,9 @@ fn String! Object.get_string_at(&self, usz index)
return value.s;
}
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
macro String! Object.get_enum(&self, $EnumType, String key)
{
Object value = self.get(key)!;
@@ -385,9 +387,9 @@ macro String! Object.get_enum(&self, $EnumType, String key)
return ($EnumType)value.i;
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
macro String! Object.get_enum_at(&self, $EnumType, usz index)
{
Object value = self.get_at(index);
@@ -395,9 +397,9 @@ macro String! Object.get_enum_at(&self, $EnumType, usz index)
return ($EnumType)value.i;
}
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn bool! Object.get_bool(&self, String key)
{
Object* value = self.get(key)!;
@@ -406,9 +408,9 @@ fn bool! Object.get_bool(&self, String key)
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn bool! Object.get_bool_at(&self, usz index)
{
Object* value = self.get_at(index);
@@ -416,9 +418,9 @@ fn bool! Object.get_bool_at(&self, usz index)
return value.b;
}
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn double! Object.get_float(&self, String key)
{
Object* value = self.get(key)!;
@@ -435,9 +437,9 @@ fn double! Object.get_float(&self, String key)
}
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn double! Object.get_float_at(&self, usz index)
{
Object* value = self.get_at(index);

View File

@@ -36,11 +36,6 @@ struct PrivatePriorityQueue (Printable)
Heap heap;
}
fn void PrivatePriorityQueue.init(&self, Allocator allocator, usz initial_capacity = 16, ) @inline
{
self.heap.new_init(initial_capacity, allocator);
}
fn void PrivatePriorityQueue.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @inline
{
self.heap.new_init(initial_capacity, allocator);
@@ -51,7 +46,6 @@ fn void PrivatePriorityQueue.temp_init(&self, usz initial_capacity = 16) @inline
self.heap.new_init(initial_capacity, allocator::temp()) @inline;
}
fn void PrivatePriorityQueue.push(&self, Type element)
{
self.heap.push(element);
@@ -72,18 +66,9 @@ fn void PrivatePriorityQueue.push(&self, Type element)
}
}
fn void PrivatePriorityQueue.remove_at(&self, usz index)
{
if (index == 0)
{
self.pop()!!;
return;
}
self.heap.remove_at(index);
}
<*
@require self != null
*>
/**
* @require self != null
*/
fn Type! PrivatePriorityQueue.pop(&self)
{
usz i = 0;
@@ -143,9 +128,9 @@ fn bool PrivatePriorityQueue.is_empty(&self)
return self.heap.is_empty();
}
<*
@require index < self.len()
*>
/**
* @require index < self.len()
*/
fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
{
return self.heap[index];

View File

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

View File

@@ -1,6 +1,3 @@
<*
@require values::@is_int(SIZE) &&& SIZE > 0 "The size must be positive integer"
*>
module std::collections::ringbuffer(<Type, SIZE>);
struct RingBuffer

View File

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

View File

@@ -10,14 +10,13 @@ struct ArenaAllocator (Allocator)
usz used;
}
<*
Initialize a memory arena for use using the provided bytes.
*>
fn ArenaAllocator* ArenaAllocator.init(&self, char[] data)
/**
* Initialize a memory arena for use using the provided bytes.
**/
fn void ArenaAllocator.init(&self, char[] data)
{
self.data = data;
self.used = 0;
return self;
}
fn void ArenaAllocator.clear(&self)
@@ -28,17 +27,12 @@ fn void ArenaAllocator.clear(&self)
struct ArenaAllocatorHeader @local
{
usz size;
char[?] data;
char[*] data;
}
macro ArenaAllocator* wrap(char[] bytes)
{
return (ArenaAllocator){}.init(bytes);
}
<*
@require ptr != null
*>
/*
* @require ptr != null
**/
fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
{
assert((uptr)ptr >= (uptr)self.data.ptr, "Pointer originates from a different allocator.");
@@ -53,11 +47,11 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
fn usz ArenaAllocator.mark(&self) @dynamic => self.used;
fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark;
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require size > 0
*>
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require size > 0
**/
fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
@@ -75,12 +69,12 @@ fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz a
return mem;
}
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require old_pointer != null
@require size > 0
*>
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require old_pointer != null
* @require size > 0
**/
fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);

View File

@@ -12,10 +12,10 @@ struct DynamicArenaAllocator (Allocator)
usz page_size;
}
<*
@param [&inout] allocator
@require page_size >= 128
*>
/**
* @param [&inout] allocator
* @require page_size >= 128
**/
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator allocator)
{
self.page = null;
@@ -30,7 +30,6 @@ fn void DynamicArenaAllocator.free(&self)
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
allocator::free(self.backing_allocator, page.memory);
allocator::free(self.backing_allocator, page);
page = next_page;
}
@@ -38,7 +37,6 @@ fn void DynamicArenaAllocator.free(&self)
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
allocator::free(self.backing_allocator, page.memory);
allocator::free(self.backing_allocator, page);
page = next_page;
}
@@ -60,10 +58,10 @@ struct DynamicArenaChunk @local
usz size;
}
<*
@require ptr != null
@require self.page != null `tried to free pointer on invalid allocator`
*>
/**
* @require ptr
* @require self.page `tried to free pointer on invalid allocator`
*/
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
{
DynamicArenaPage* current_page = self.page;
@@ -74,11 +72,11 @@ fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
current_page.current_stack_ptr = null;
}
<*
@require size > 0 `Resize doesn't support zeroing`
@require old_pointer != null `Resize doesn't handle null pointers`
@require self.page != null `tried to realloc pointer on invalid allocator`
*>
/**
* @require size > 0 `Resize doesn't support zeroing`
* @require old_pointer != null `Resize doesn't handle null pointers`
* @require self.page `tried to realloc pointer on invalid allocator`
*/
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
DynamicArenaPage* current_page = self.page;
@@ -126,15 +124,15 @@ fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
self.page = page;
}
<*
@require math::is_power_of_2(alignment)
@require size > 0
*>
/**
* @require math::is_power_of_2(alignment)
* @require size > 0
*/
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);
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof, alignment));
// 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);
@@ -156,29 +154,22 @@ fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @loca
return mem_start;
}
<*
@require size > 0 `acquire expects size > 0`
@require !alignment || math::is_power_of_2(alignment)
*>
/**
* @require size > 0 `acquire expects size > 0`
* @require !alignment || math::is_power_of_2(alignment)
*/
fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
DynamicArenaPage* page = self.page;
void* ptr @noinit;
do SET_DONE:
{
void* ptr = {|
if (!page && self.unused_page)
{
self.page = page = self.unused_page;
self.unused_page = page.prev_arena;
page.prev_arena = null;
}
if (!page)
{
ptr = self._alloc_new(size, alignment)!;
break SET_DONE;
}
if (!page) return self._alloc_new(size, alignment);
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
usz new_used = start - page.memory + size;
if ALLOCATE_NEW: (new_used > page.total)
@@ -195,15 +186,15 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
break ALLOCATE_NEW;
}
}
ptr = self._alloc_new(size, alignment)!;
break SET_DONE;
return self._alloc_new(size, alignment);
}
page.used = new_used;
assert(start + size == page.memory + page.used);
ptr = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)ptr - 1;
void* mem = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
chunk.size = size;
};
return mem;
|}!;
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
return ptr;
}

View File

@@ -11,10 +11,10 @@ struct SimpleHeapAllocator (Allocator)
Header* free_list;
}
<*
@require allocator != null "An underlying memory provider must be given"
@require !self.free_list "The allocator may not be already initialized"
*>
/**
* @require allocator "An underlying memory provider must be given"
* @require !self.free_list "The allocator may not be already initialized"
**/
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
{
self.alloc_fn = allocator;
@@ -49,9 +49,9 @@ fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dyn
}
}
<*
@require old_pointer && bytes > 0
*>
/**
* @require old_pointer && bytes > 0
**/
fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
{
// Find the block header.
@@ -98,7 +98,7 @@ fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
return current + 1;
case current.size > aligned_bytes:
Header* unallocated = (Header*)((char*)current + aligned_bytes + Header.sizeof);
unallocated.size = current.size - aligned_bytes - Header.sizeof;
unallocated.size = current.size - aligned_bytes;
unallocated.next = current.next;
if (current == self.free_list)
{

View File

@@ -2,21 +2,16 @@
// 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;
module std::core::mem::allocator;
import libc;
const LibcAllocator LIBC_ALLOCATOR = {};
distinct LibcAllocator (Allocator, Printable) = uptr;
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
fn usz! LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
distinct LibcAllocator (Allocator) = uptr;
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)
@@ -28,7 +23,7 @@ fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
}
else
{
@@ -41,17 +36,17 @@ fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
{
if (!(data = libc::malloc(bytes))) return AllocationFailure.OUT_OF_MEMORY?;
}
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
}
}
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
void* new_ptr;
void* new_ptr;
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return AllocationFailure.OUT_OF_MEMORY?;
$switch
@@ -84,16 +79,16 @@ fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
{
if (alignment > 0)
{
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
return win32::_aligned_recalloc(null, bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
}
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
}
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
$if env::TESTING:
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
return data;
}
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
@@ -109,13 +104,13 @@ fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
if (aligned)
{
win32::_aligned_free(old_ptr);
return;
win32::_aligned_free(old_ptr);
return;
}
libc::free(old_ptr);
}
module std::core::mem::allocator @if(!env::WIN32 && !env::POSIX && env::LIBC);
module std::core::mem::allocator @if(!env::WIN32 && !env::POSIX);
import libc;
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
@@ -123,16 +118,16 @@ 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 ?: AllocationFailure.OUT_OF_MEMORY?;
return data ?: AllocationFailure.OUT_OF_MEMORY?;
}
else
{
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
}
}

View File

@@ -16,11 +16,10 @@ struct OnStackAllocatorExtraChunk @local
void* data;
}
<*
Initialize a memory arena for use using the provided bytes.
@param [&inout] allocator
*>
/**
* @param [&inout] allocator
* Initialize a memory arena for use using the provided bytes.
**/
fn void OnStackAllocator.init(&self, char[] data, Allocator allocator)
{
self.data = data;
@@ -52,17 +51,17 @@ fn void OnStackAllocator.free(&self)
struct OnStackAllocatorHeader
{
usz size;
char[?] data;
char[*] data;
}
<*
@require old_pointer != null
*>
/**
* @require old_pointer
**/
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{
if (allocation_in_stack_mem(self, old_pointer)) return;
on_stack_allocator_remove_chunk(self, old_pointer);
self.backing_allocator.release(old_pointer, aligned);
self.release(old_pointer, aligned);
}
fn bool allocation_in_stack_mem(OnStackAllocator* a, void* ptr) @local
@@ -99,11 +98,11 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a
return null;
}
<*
@require size > 0
@require old_pointer != null
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
*>
/**
* @require size > 0
* @require old_pointer != null
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
**/
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
if (!allocation_in_stack_mem(self, old_pointer))
@@ -120,10 +119,10 @@ fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignm
return mem;
}
<*
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require size > 0
*>
/**
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require size > 0
**/
fn void*! OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
bool aligned = alignment > 0;

View File

@@ -4,7 +4,7 @@ import std::io, std::math;
struct TempAllocatorChunk @local
{
usz size;
char[?] data;
char[*] data;
}
struct TempAllocator (Allocator)
@@ -13,7 +13,7 @@ struct TempAllocator (Allocator)
TempAllocatorPage* last_page;
usz used;
usz capacity;
char[?] data;
char[*] data;
}
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
@@ -26,15 +26,15 @@ struct TempAllocatorPage
usz mark;
usz size;
usz ident;
char[?] data;
char[*] data;
}
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 >= 16
**/
fn TempAllocator*! new_temp_allocator(usz size, Allocator allocator)
{
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
@@ -45,13 +45,6 @@ fn TempAllocator*! new_temp_allocator(usz size, Allocator allocator)
return temp;
}
fn void TempAllocator.destroy(&self)
{
self.reset(0);
if (self.last_page) (void)self._free_page(self.last_page);
allocator::free(self.backing_allocator, self);
}
fn usz TempAllocator.mark(&self) @dynamic => self.used;
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
@@ -60,7 +53,6 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
if (old_pointer + old_size == &self.data[self.used])
{
self.used -= old_size;
asan::poison_memory_region(&self.data[self.used], old_size);
}
}
fn void TempAllocator.reset(&self, usz mark) @dynamic
@@ -68,25 +60,11 @@ fn void TempAllocator.reset(&self, usz mark) @dynamic
TempAllocatorPage *last_page = self.last_page;
while (last_page && last_page.mark > mark)
{
self.used = last_page.mark;
TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
}
self.last_page = last_page;
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
if (!last_page)
{
usz cleaned = self.used - mark;
if (cleaned > 0)
{
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
self.data[mark : cleaned] = 0xAA;
$endif
asan::poison_memory_region(&self.data[mark], cleaned);
}
}
$endif
self.used = mark;
}
@@ -134,11 +112,11 @@ fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d
return data;
}
<*
@require size > 0
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
*>
/**
* @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
{
alignment = alignment_for_allocation(alignment);
@@ -152,10 +130,9 @@ fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
}
usz new_usage = (usz)(mem - start_mem) + size;
// Arena allocation, simple!
// Arena alignment, simple!
if (new_usage <= self.capacity)
{
asan::unpoison_memory_region(starting_ptr, new_usage - self.used);
TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof;
chunk_start.size = size;
self.used = new_usage;
@@ -179,10 +156,8 @@ fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
{
mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
}
void* start = mem;
mem += mem::aligned_offset(TempAllocatorPage.sizeof, alignment);
page = (TempAllocatorPage*)mem - 1;
page.start = start;
page.start = mem;
page.size = size | PAGE_IS_ALIGNED;
}
else

View File

@@ -26,44 +26,47 @@ struct TrackingAllocator (Allocator)
usz allocs_total;
}
<*
Initialize a tracking allocator to wrap (and track) another allocator.
@param [&inout] allocator "The allocator to track"
*>
/**
* Initialize a tracking allocator to wrap (and track) another allocator.
*
* @param [&inout] allocator "The allocator to track"
**/
fn void TrackingAllocator.init(&self, Allocator allocator)
{
*self = { .inner_allocator = allocator };
self.map.init(allocator);
self.map.new_init(.allocator = allocator);
}
<*
Free this tracking allocator.
*>
/**
* Free this tracking allocator.
**/
fn void TrackingAllocator.free(&self)
{
self.map.free();
*self = {};
}
<*
@return "the total allocated memory not yet freed."
*>
fn usz TrackingAllocator.allocated(&self) => @pool()
/**
* @return "the total allocated memory not yet freed."
**/
fn usz TrackingAllocator.allocated(&self)
{
usz allocated = 0;
foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
@pool()
{
foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
};
return allocated;
}
<*
@return "the total memory allocated (freed or not)."
*>
/**
* @return "the total memory allocated (freed or not)."
**/
fn usz TrackingAllocator.total_allocated(&self) => self.mem_total;
<*
@return "the total number of allocations (freed or not)."
*>
/**
* @return "the total number of allocations (freed or not)."
**/
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
@@ -71,9 +74,9 @@ fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
return self.map.value_tlist();
}
<*
@return "the number of non-freed allocations."
*>
/**
* @return "the number of non-freed allocations."
**/
fn usz TrackingAllocator.allocation_count(&self) => self.map.count;
fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
@@ -83,7 +86,8 @@ fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, us
void*[MAX_BACKTRACE] bt;
backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
self.mem_total += size;
self.allocs_total++;
return data;
}
@@ -94,7 +98,7 @@ fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz align
void*[MAX_BACKTRACE] bt;
backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
self.mem_total += size;
self.allocs_total++;
return data;
}
@@ -102,9 +106,9 @@ fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz align
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
{
if (catch self.map.remove((uptr)old_pointer))
{
unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
}
{
unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
}
self.inner_allocator.release(old_pointer, is_aligned);
}
@@ -113,103 +117,101 @@ fn void TrackingAllocator.clear(&self)
self.map.clear();
}
fn bool TrackingAllocator.has_leaks(&self)
{
return self.map.len() > 0;
}
fn void TrackingAllocator.print_report(&self) => self.fprint_report(io::stdout())!!;
fn void! TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
fn void! TrackingAllocator.fprint_report(&self, OutStream out)
{
usz total = 0;
usz entries = 0;
bool leaks = false;
Allocation[] allocs = self.map.value_tlist();
if (allocs.len)
@pool()
{
if (!allocs[0].backtrace[0])
Allocation[] allocs = self.map.value_tlist();
if (allocs.len)
{
io::fprintn(out, "======== Memory Report ========")!;
io::fprintn(out, "Size in bytes Address")!;
foreach (i, &allocation : allocs)
if (!allocs[0].backtrace[0])
{
entries++;
total += allocation.size;
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
}
io::fprintn(out, "===============================")!;
io::fprintn(out, "======== Memory Report ========")!;
io::fprintn(out, "Size in bytes Address")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
}
io::fprintn(out, "===============================")!;
}
else
{
io::fprintn(out, "================================== Memory Report ==================================")!;
io::fprintn(out, "Size in bytes Address Function ")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
BacktraceList backtraces = {};
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
if (allocation.backtrace[3])
{
trace = backtrace::symbolize_backtrace(allocation.backtrace[3:1], allocator::temp()).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
}
if (trace.function.len) leaks = true;
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
allocation.ptr, trace.function.len ? trace.function : "???",
trace.line ? trace.line : 0)!;
}
io::fprintn(out, "===================================================================================")!;
}
}
else
{
io::fprintn(out, "================================== Memory Report ==================================")!;
io::fprintn(out, "Size in bytes Address Function ")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
BacktraceList backtraces = {};
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
if (allocation.backtrace[3])
{
trace = backtrace::symbolize_backtrace(allocation.backtrace[3:1], allocator::temp()).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
}
if (trace.function.len) leaks = true;
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
allocation.ptr, trace.function.len ? trace.function : "???",
trace.line ? trace.line : 0)!;
}
io::fprintn(out, "===================================================================================")!;
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
}
}
else
{
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
}
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
io::fprintfn(out, "- Total current allocations: %d", entries)!;
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
if (leaks)
{
io::fprintn(out)!;
io::fprintn(out, "Full leak report:")!;
foreach (i, &allocation : allocs)
{
if (!allocation.backtrace[3])
{
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
continue;
}
BacktraceList backtraces = {};
usz end = MAX_BACKTRACE;
foreach (j, val : allocation.backtrace)
{
if (!val)
{
end = j;
break;
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
io::fprintfn(out, "- Total current allocations: %d", entries)!;
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
if (leaks)
{
io::fprintn(out)!;
io::fprintn(out, "Full leak report:")!;
foreach (i, &allocation : allocs)
{
if (!allocation.backtrace[3])
{
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
continue;
}
}
BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], allocator::temp())!;
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
foreach (trace : list)
{
if (trace.has_file())
{
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
continue;
BacktraceList backtraces = {};
usz end = MAX_BACKTRACE;
foreach (j, val : allocation.backtrace)
{
if (!val)
{
end = j;
break;
}
}
if (trace.is_unknown())
{
io::fprintfn(out, " ??? (in unknown)");
continue;
BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], allocator::temp())!;
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
foreach (trace : list)
{
if (trace.has_file())
{
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
continue;
}
if (trace.is_unknown())
{
io::fprintfn(out, " ??? (in unknown)");
continue;
}
io::fprintfn(out, " %s (source unavailable)", trace.function);
}
io::fprintfn(out, " %s (source unavailable)", trace.function);
}
}
}
}
}
};
}

View File

@@ -1,12 +1,12 @@
module std::core::array;
import std::core::array::slice;
<*
@param [in] array
@param [in] element
@return "the first index of the element"
@return! SearchResult.MISSING
*>
/**
* @param [in] array
* @param [in] element
* @return "the first index of the element"
* @return! SearchResult.MISSING
**/
macro index_of(array, element)
{
foreach (i, &e : array)
@@ -16,26 +16,25 @@ macro index_of(array, element)
return SearchResult.MISSING?;
}
<*
@require @typekind(array_ptr) == POINTER
@require @typekind(*array_ptr) == VECTOR || @typekind(*array_ptr) == ARRAY
@require @typekind((*array_ptr)[0]) == VECTOR || @typekind((*array_ptr)[0]) == ARRAY
*>
macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
/**
* @require @typekind(array) == VECTOR || @typekind(array) == ARRAY
* @require @typekind(array[0]) == VECTOR || @typekind(array[0]) == ARRAY
**/
macro slice2d(array, 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 };
if (xlen < 1) xlen = $typeof(array[0]).len + xlen;
if (ylen < 1) ylen = $typeof(array).len + ylen;
var $ElementType = $typeof(array[0][0]);
return Slice2d(<$ElementType>) { ($ElementType*)&array, $typeof(array[0]).len, y, ylen, x, xlen };
}
<*
@param [in] array
@param [in] element
@return "the last index of the element"
@return! SearchResult.MISSING
*>
/**
* @param [in] array
* @param [in] element
* @return "the last index of the element"
* @return! SearchResult.MISSING
**/
macro rindex_of(array, element)
{
foreach_r (i, &e : array)
@@ -45,18 +44,18 @@ macro rindex_of(array, element)
return SearchResult.MISSING?;
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
@param [in] arr1
@param [in] arr2
@param [&inout] allocator "The allocator to use, default is the heap allocator"
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len
*>
macro concat(arr1, arr2, Allocator allocator) @nodiscard
/**
* Concatenate two arrays or slices, returning a slice containing the concatenation of them.
*
* @param [in] arr1
* @param [in] arr2
* @param [&inout] allocator "The allocator to use, default is the heap allocator"
* @require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
* @require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
* @require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
* @ensure result.len == arr1.len + arr2.len
**/
macro concat_new(arr1, arr2, Allocator allocator = allocator::heap())
{
var $Type = $typeof(arr1[0]);
$Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len);
@@ -70,34 +69,19 @@ macro concat(arr1, arr2, Allocator allocator) @nodiscard
}
return result;
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
@param [in] arr1
@param [in] arr2
@param [&inout] allocator "The allocator to use, default is the heap allocator"
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@ensure return.len == arr1.len + arr2.len
*>
macro concat_new(arr1, arr2, Allocator allocator = allocator::heap()) @nodiscard
{
return concat(arr1, arr2, allocator);
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them,
allocated using the temp allocator.
@param [in] arr1
@param [in] arr2
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@ensure return.len == arr1.len + arr2.len
*>
macro tconcat(arr1, arr2) @nodiscard => concat(arr1, arr2, allocator::temp());
/**
* Concatenate two arrays or slices, returning a slice containing the concatenation of them,
* allocated using the temp allocator.
*
* @param [in] arr1
* @param [in] arr2
* @require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
* @require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
* @require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
* @ensure result.len == arr1.len + arr2.len
**/
macro tconcat(arr1, arr2) => concat(arr1, arr2, allocator::temp());
module std::core::array::slice(<Type>);
@@ -143,48 +127,18 @@ macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
}
}
<*
@require idy >= 0 && idy < self.ylen
*>
macro Type[] Slice2d.get_row(self, usz idy) @operator([])
/**
* @require idy >= 0 && idy < self.ylen
**/
macro Type[] Slice2d.get(self, usz idy) @operator([])
{
return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen];
}
macro Type Slice2d.get_coord(self, usz[<2>] coord)
{
return *self.get_coord_ref(coord);
}
macro Type Slice2d.get_xy(self, x, y)
{
return *self.get_xy_ref(x, y);
}
macro Type* Slice2d.get_xy_ref(self, x, y)
{
return self.ptr + self.inner_len * (y + self.ystart) + self.xstart + x;
}
macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord)
{
return self.get_xy_ref(coord.x, coord.y);
}
macro void Slice2d.set_coord(self, usz[<2>] coord, Type value)
{
*self.get_coord_ref(coord) = value;
}
macro void Slice2d.set_xy(self, x, y, Type value)
{
*self.get_xy_ref(x, y) = value;
}
<*
@require y >= 0 && y < self.ylen
@require x >= 0 && x < self.xlen
*>
/**
* @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;

View File

@@ -87,10 +87,10 @@ bitstruct UInt128LE : uint128 @littleendian
uint128 val : 0..127;
}
<*
@require is_array_or_slice_of_char(bytes) "argument must be an array, a pointer to an array or a slice of char"
@require is_bitorder($Type) "type must be a bitorder integer"
*>
/**
* @require is_array_or_slice_of_char(bytes) "argument must be an array, a pointer to an array or a slice of char"
* @require is_bitorder($Type) "type must be a bitorder integer"
**/
macro read(bytes, $Type)
{
char[] s;
@@ -103,10 +103,10 @@ macro read(bytes, $Type)
return bitcast(*(char[$Type.sizeof]*)s.ptr, $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 is_arrayptr_or_slice_of_char(bytes) "argument must be a pointer to an array or a slice of char"
* @require is_bitorder($Type) "type must be a bitorder integer"
**/
macro write(x, bytes, $Type)
{
char[] s;

View File

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

View File

@@ -3,9 +3,9 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::builtin;
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro less(a, b) @builtin
{
$switch
@@ -18,9 +18,9 @@ macro less(a, b) @builtin
$endswitch
}
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro less_eq(a, b) @builtin
{
$switch
@@ -33,9 +33,9 @@ macro less_eq(a, b) @builtin
$endswitch
}
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro greater(a, b) @builtin
{
$switch
@@ -48,9 +48,9 @@ macro greater(a, b) @builtin
$endswitch
}
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro int compare_to(a, b) @builtin
{
$switch
@@ -62,9 +62,9 @@ macro int compare_to(a, b) @builtin
return (int)(a > b) - (int)(a < b);
$endswitch
}
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro greater_eq(a, b) @builtin
{
$switch
@@ -77,9 +77,9 @@ macro greater_eq(a, b) @builtin
$endswitch
}
<*
@require types::@equatable_value(a) && types::@equatable_value(b) `values must be equatable`
*>
/**
* @require types::is_equatable_value(a) && types::is_equatable_value(b) `values must be equatable`
**/
macro bool equals(a, b) @builtin
{
$switch
@@ -97,13 +97,13 @@ macro bool equals(a, b) @builtin
macro min(x, ...) @builtin
{
$if $vacount == 1:
return less(x, $vaarg[0]) ? x : $vaarg[0];
return less(x, $vaarg(0)) ? x : $vaarg(0);
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
if (less($vaarg[$i], result))
if (less($vaarg($i), result))
{
result = $vaarg[$i];
result = $vaarg($i);
}
$endfor
return result;
@@ -113,13 +113,13 @@ macro min(x, ...) @builtin
macro max(x, ...) @builtin
{
$if $vacount == 1:
return greater(x, $vaarg[0]) ? x : $vaarg[0];
return greater(x, $vaarg(0)) ? x : $vaarg(0);
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
if (greater($vaarg[$i], result))
if (greater($vaarg($i), result))
{
result = $vaarg[$i];
result = $vaarg($i);
}
$endfor
return result;

View File

@@ -29,12 +29,6 @@ def CUChar = char;
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
enum CBool : char
{
FALSE,
TRUE
}
// Helper macros
macro typeid signed_int_from_bitsize(usz $bitsize) @private
{

View File

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

View File

@@ -1,15 +1,14 @@
module std::core::dstring;
import std::io;
distinct DString (OutStream) = DStringOpaque*;
distinct DStringOpaque = void;
distinct DString (OutStream) = void*;
const usz MIN_CAPACITY @private = 16;
<*
@require !self.data() "String already initialized"
*>
fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
/**
* @require !self.data() "String already initialized"
**/
fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap())
{
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
@@ -19,40 +18,18 @@ fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator alloca
return *self = (DString)data;
}
<*
@require !self.data() "String already initialized"
*>
fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY)
/**
* @require !self.data() "String already initialized"
**/
fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY)
{
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
data.allocator = allocator;
data.len = 0;
data.capacity = capacity;
return *self = (DString)data;
}
<*
@require !self.data() "String already initialized"
*>
fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY) @deprecated("Use tinit()")
{
self.init(allocator::temp(), capacity) @inline;
return *self;
}
<*
@require !self.data() "String already initialized"
*>
fn DString DString.tinit(&self, usz capacity = MIN_CAPACITY)
{
self.init(allocator::temp(), capacity) @inline;
self.new_init(capacity, allocator::temp()) @inline;
return *self;
}
fn DString new_with_capacity(usz capacity, Allocator allocator = allocator::heap())
{
return (DString){}.init(allocator, capacity);
return DString{}.new_init(capacity, allocator);
}
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, allocator::temp()) @inline;
@@ -71,75 +48,16 @@ fn DString new(String c = "", Allocator allocator = allocator::heap())
fn DString temp_new(String s = "") => new(s, allocator::temp()) @inline;
fn void DString.replace_char(self, char ch, char replacement)
{
StringData* data = self.data();
foreach (&c : data.chars[:data.len])
{
if (*c == ch) *c = replacement;
}
}
fn void DString.replace(&self, String needle, String replacement)
{
StringData* data = self.data();
usz needle_len = needle.len;
if (!data || data.len < needle_len) return;
usz replace_len = replacement.len;
if (needle_len == 1 && replace_len == 1)
{
self.replace_char(needle[0], replacement[0]);
return;
}
@pool(data.allocator) {
String str = self.tcopy_str();
self.clear();
usz len = str.len;
usz match = 0;
foreach (i, c : str)
{
if (c == needle[match])
{
match++;
if (match == needle_len)
{
self.append_chars(replacement);
match = 0;
continue;
}
continue;
}
if (match > 0)
{
self.append_chars(str[i - match:match]);
match = 0;
}
self.append_char(c);
}
if (match > 0) self.append_chars(str[^match:match]);
};
}
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap()) @deprecated("Use concat(mem)")
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
{
DString string;
string.init(allocator, self.len() + b.len());
string.new_init(self.len() + b.len(), allocator);
string.append(self);
string.append(b);
return string;
}
fn DString DString.concat(self, Allocator allocator, DString b)
{
DString string;
string.init(allocator, self.len() + b.len());
string.append(self);
string.append(b);
return string;
}
fn DString DString.temp_concat(self, DString b) => self.concat(allocator::temp(), b);
fn DString DString.temp_concat(self, DString b) => self.new_concat(b, allocator::temp());
fn ZString DString.zstr_view(&self)
{
@@ -164,15 +82,15 @@ fn usz DString.capacity(self)
return self.data().capacity;
}
fn usz DString.len(&self) @dynamic @operator(len)
fn usz DString.len(&self) @dynamic
{
if (!*self) return 0;
return self.data().len;
}
<*
@require new_size <= self.len()
*>
/**
* @require new_size <= self.len()
*/
fn void DString.chop(self, usz new_size)
{
if (!self) return;
@@ -186,39 +104,19 @@ fn String DString.str_view(self)
return (String)data.chars[:data.len];
}
<*
@require index < self.len()
@require self.data() != null "Empty string"
*>
fn char DString.char_at(self, usz index) @operator([])
{
return self.data().chars[index];
}
<*
@require index < self.len()
@require self.data() != null "Empty string"
*>
fn char* DString.char_ref(&self, usz index) @operator(&[])
{
return &self.data().chars[index];
}
fn usz DString.append_utf32(&self, Char32[] chars)
fn void DString.append_utf32(&self, Char32[] chars)
{
self.reserve(chars.len);
usz end = self.len();
foreach (Char32 c : chars)
{
self.append_char32(c);
}
return self.data().len - end;
}
<*
@require index < self.len()
*>
fn void DString.set(self, usz index, char c) @operator([]=)
/**
* @require index < self.len()
**/
fn void DString.set(self, usz index, char c)
{
self.data().chars[index] = c;
}
@@ -234,10 +132,10 @@ fn void DString.append_repeat(&self, char c, usz times)
}
}
<*
@require c <= 0x10ffff
*>
fn usz DString.append_char32(&self, Char32 c)
/**
* @require c <= 0x10ffff
*/
fn void DString.append_char32(&self, Char32 c)
{
char[4] buffer @noinit;
char* p = &buffer;
@@ -246,7 +144,6 @@ fn usz DString.append_char32(&self, Char32 c)
StringData* data = self.data();
data.chars[data.len:n] = buffer[:n];
data.len += n;
return n;
}
fn DString DString.tcopy(&self) => self.copy(allocator::temp());
@@ -345,7 +242,7 @@ fn void DString.append_chars(&self, String str)
fn Char32[] DString.copy_utf32(&self, Allocator allocator = allocator::heap())
{
return self.str_view().to_utf32(allocator) @inline!!;
return self.str_view().to_new_utf32(allocator) @inline!!;
}
fn void DString.append_string(&self, DString str)
@@ -383,20 +280,20 @@ fn void DString.append_char(&self, char c)
data.chars[data.len++] = c;
}
<*
@require start < self.len()
@require end < self.len()
@require end >= start "End must be same or equal to the start"
*>
/**
* @require start < self.len()
* @require end < self.len()
* @require end >= start "End must be same or equal to the start"
**/
fn void DString.delete_range(&self, usz start, usz end)
{
self.delete(start, end - start + 1);
}
<*
@require start < self.len()
@require start + len <= self.len()
*>
/**
* @require start < self.len()
* @require start + len <= self.len()
**/
fn void DString.delete(&self, usz start, usz len = 1)
{
if (!len) return;
@@ -440,10 +337,7 @@ macro void DString.append(&self, value)
$endswitch
}
<*
@require index <= self.len()
*>
fn void DString.insert_chars_at(&self, usz index, String s)
fn void DString.insert_at(&self, usz index, String s)
{
if (s.len == 0) return;
self.reserve(s.len);
@@ -475,125 +369,20 @@ fn void DString.insert_chars_at(&self, usz index, String s)
}
}
<*
@require index <= self.len()
*>
fn void DString.insert_string_at(&self, usz index, DString str)
{
StringData* other = str.data();
if (!other) return;
self.insert_at(index, str.str_view());
}
<*
@require index <= self.len()
*>
fn void DString.insert_char_at(&self, usz index, char c)
{
self.reserve(1);
StringData* data = self.data();
char* start = &data.chars[index];
mem::move(start + 1, start, self.len() - index);
data.chars[index] = c;
data.len++;
}
<*
@require index <= self.len()
*>
fn usz DString.insert_char32_at(&self, usz index, Char32 c)
{
char[4] buffer @noinit;
char* p = &buffer;
usz n = conv::char32_to_utf8_unsafe(c, &p);
self.reserve(n);
StringData* data = self.data();
char* start = &data.chars[index];
mem::move(start + n, start, self.len() - index);
data.chars[index:n] = buffer[:n];
data.len += n;
return n;
}
<*
@require index <= self.len()
*>
fn usz DString.insert_utf32_at(&self, usz index, Char32[] chars)
{
usz n = conv::utf8len_for_utf32(chars);
self.reserve(n);
StringData* data = self.data();
char* start = &data.chars[index];
mem::move(start + n, start, self.len() - index);
char[4] buffer @noinit;
foreach(c : chars)
{
char* p = &buffer;
usz m = conv::char32_to_utf8_unsafe(c, &p);
data.chars[index:m] = buffer[:m];
index += m;
}
data.len += n;
return n;
}
macro void DString.insert_at(&self, usz index, value)
{
var $Type = $typeof(value);
$switch ($Type)
$case char:
$case ichar:
self.insert_char_at(index, value);
$case DString:
self.insert_string_at(index, value);
$case String:
self.insert_chars_at(index, value);
$case Char32:
self.insert_char32_at(index, value);
$default:
$switch
$case $defined((Char32)value):
self.insert_char32_at(index, (Char32)value);
$case $defined((String)value):
self.insert_chars_at(index, (String)value);
$default:
$error "Unsupported type for insert";
$endswitch
$endswitch
}
fn usz! DString.appendf(&self, String format, args...) @maydiscard
{
if (!self.data()) self.init(mem, format.len + 20);
@pool(self.data().allocator)
{
Formatter formatter;
formatter.init(&out_string_append_fn, self);
return formatter.vprintf(format, args);
};
Formatter formatter;
formatter.init(&out_string_append_fn, self);
return formatter.vprintf(format, args);
}
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
{
if (!self.data()) self.init(mem, format.len + 20);
@pool(self.data().allocator)
{
Formatter formatter;
formatter.init(&out_string_append_fn, self);
usz len = formatter.vprintf(format, args)!;
self.append('\n');
return len + 1;
};
Formatter formatter;
formatter.init(&out_string_append_fn, self);
usz len = formatter.vprintf(format, args)!;
self.append('\n');
return len + 1;
}
fn DString new_join(String[] s, String joiner, Allocator allocator = allocator::heap())
@@ -620,19 +409,6 @@ fn void! out_string_append_fn(void* data, char c) @private
s.append_char(c);
}
fn void DString.reverse(self)
{
StringData *data = self.data();
if (!data) return;
isz mid = data.len / 2;
for (isz i = 0; i < mid; i++)
{
char temp = data.chars[i];
isz reverse_index = data.len - 1 - i;
data.chars[i] = data.chars[reverse_index];
data.chars[reverse_index] = temp;
}
}
fn StringData* DString.data(self) @inline @private
{
@@ -691,5 +467,5 @@ struct StringData @private
Allocator allocator;
usz len;
usz capacity;
char[?] chars;
char[*] chars;
}

View File

@@ -112,18 +112,13 @@ enum ArchType
WASM64, // WebAssembly with 64-bit pointers
RSCRIPT32, // 32-bit RenderScript
RSCRIPT64, // 64-bit RenderScript
XTENSA, // Xtensa
}
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 bool ARCH_32_BIT = $$REGISTER_SIZE == 32;
const bool ARCH_64_BIT = $$REGISTER_SIZE == 64;
const OsType OS_TYPE = (OsType)$$OS_TYPE;
const ArchType ARCH_TYPE = (ArchType)$$ARCH_TYPE;
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel.from_ordinal($$COMPILER_OPT_LEVEL);
const CompilerOptLevel COMPILER_OPT_LEVEL = (CompilerOptLevel)$$COMPILER_OPT_LEVEL;
const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED;
const bool F16_SUPPORT = $$PLATFORM_F16_SUPPORTED;
@@ -131,11 +126,10 @@ const bool F128_SUPPORT = $$PLATFORM_F128_SUPPORTED;
const REGISTER_SIZE = $$REGISTER_SIZE;
const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE;
const bool DEBUG_SYMBOLS = $$DEBUG_SYMBOLS;
const bool BACKTRACE = $$BACKTRACE;
const usz LLVM_VERSION = $$LLVM_VERSION;
const bool BENCHMARKING = $$BENCHMARKING;
const bool TESTING = $$TESTING;
const MemoryEnvironment MEMORY_ENV = MemoryEnvironment.from_ordinal($$MEMORY_ENVIRONMENT);
const MemoryEnvironment MEMORY_ENV = (MemoryEnvironment)$$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;
@@ -148,15 +142,10 @@ const bool POSIX = LIBC && os_is_posix();
const bool OPENBSD = LIBC && OS_TYPE == OPENBSD;
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_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
macro bool os_is_darwin() @const
macro bool os_is_darwin()
{
$switch (OS_TYPE)
$case IOS:
@@ -169,7 +158,7 @@ macro bool os_is_darwin() @const
$endswitch
}
macro bool os_is_posix() @const
macro bool os_is_posix()
{
$switch (OS_TYPE)
$case IOS:

File diff suppressed because it is too large Load Diff

View File

@@ -20,22 +20,22 @@ interface Allocator
{
fn void reset(usz mark) @optional;
fn usz mark() @optional;
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require size > 0
*>
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require size > 0
**/
fn void*! acquire(usz size, AllocInitType init_type, usz alignment = 0);
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require ptr != null
@require new_size > 0
*>
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require ptr != null
* @require new_size > 0
**/
fn void*! resize(void* ptr, usz new_size, usz alignment = 0);
<*
@require ptr != null
*>
/**
* @require ptr != null
**/
fn void release(void* ptr, bool aligned);
}
@@ -49,7 +49,7 @@ fault AllocationFailure
fn usz alignment_for_allocation(usz alignment) @inline @private
{
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? alignment = mem::DEFAULT_MEM_ALIGNMENT : alignment;
}
macro void* malloc(Allocator allocator, usz size) @nodiscard
@@ -143,144 +143,84 @@ macro void free_aligned(Allocator allocator, void* ptr)
$if env::TESTING:
((char*)ptr)[0] = 0xBA;
$endif
allocator.release(ptr, aligned: true);
allocator.release(ptr, .aligned = true);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
/**
* @require $vacount < 2 : "Too many arguments."
* @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type"
**/
macro new(Allocator allocator, $Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc(allocator, $Type.sizeof);
$else
$Type* val = malloc(allocator, $Type.sizeof);
*val = $vaexpr[0];
*val = $vaexpr(0);
return val;
$endif
}
<*
@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 < 2 : "Too many arguments."
* @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type"
**/
macro new_try(Allocator allocator, $Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc_try(allocator, $Type.sizeof);
$else
$Type* val = malloc_try(allocator, $Type.sizeof)!;
*val = $vaexpr[0];
*val = $vaexpr(0);
return val;
$endif
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc_aligned(allocator, $Type.sizeof, $Type.alignof);
$else
$Type* val = malloc_aligned(allocator, $Type.sizeof, $Type.alignof)!;
*val = $vaexpr[0];
return val;
$endif
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT
*>
macro new_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
{
return ($Type*)calloc_try(allocator, $Type.sizeof + padding);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro alloc(Allocator allocator, $Type) @nodiscard
{
return ($Type*)malloc(allocator, $Type.sizeof);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro alloc_try(Allocator allocator, $Type) @nodiscard
{
return ($Type*)malloc_try(allocator, $Type.sizeof);
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
*>
macro alloc_aligned(Allocator allocator, $Type) @nodiscard
{
return ($Type*)malloc_aligned(allocator, $Type.sizeof, $Type.alignof);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT
*>
macro alloc_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
{
return ($Type*)malloc_try(allocator, $Type.sizeof + padding);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
*>
macro new_array(Allocator allocator, $Type, usz elements) @nodiscard
{
return new_array_try(allocator, $Type, elements)!!;
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
*>
macro new_array_try(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)calloc_try(allocator, $Type.sizeof * elements))[:elements];
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
*>
macro new_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)calloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
return ((Type*)calloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*>
macro alloc_array(Allocator allocator, $Type, usz elements) @nodiscard
{
return alloc_array_try(allocator, $Type, elements)!!;
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
*>
macro alloc_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)malloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
return ((Type*)malloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*>
macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
@@ -300,11 +240,11 @@ fn any clone_any(Allocator allocator, any value) @nodiscard
}
<*
@require bytes > 0
@require alignment > 0
@require bytes <= isz.max
*>
/**
* @require bytes > 0
* @require alignment > 0
* @require bytes <= isz.max
**/
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
{
if (alignment < void*.alignof) alignment = void*.alignof;
@@ -338,10 +278,10 @@ macro void! @aligned_free(#free_fn, void* old_pointer)
$endif
}
<*
@require bytes > 0
@require alignment > 0
*>
/**
* @require bytes > 0
* @require alignment > 0
**/
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
@@ -358,23 +298,10 @@ macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
// All allocators
def mem = thread_allocator @builtin;
tlocal Allocator thread_allocator @private = base_allocator();
Allocator temp_base_allocator @private = base_allocator();
tlocal Allocator thread_allocator @private = &allocator::LIBC_ALLOCATOR;
tlocal TempAllocator* thread_temp_allocator @private = null;
tlocal TempAllocator*[2] temp_allocator_pair @private;
macro Allocator base_allocator() @private
{
$if env::LIBC:
return &allocator::LIBC_ALLOCATOR;
$else
return &allocator::NULL_ALLOCATOR;
$endif
}
Allocator temp_base_allocator @private = &allocator::LIBC_ALLOCATOR;
macro TempAllocator* create_default_sized_temp_allocator(Allocator allocator) @local
{
@@ -408,23 +335,6 @@ fn void init_default_temp_allocators() @private
thread_temp_allocator = temp_allocator_pair[0];
}
fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::LIBC)
{
destroy_temp_allocators();
}
<*
Call this to destroy any memory used by the temp allocators. This will invalidate all temp memory.
*>
fn void destroy_temp_allocators()
{
if (!thread_temp_allocator) return;
temp_allocator_pair[0].destroy();
temp_allocator_pair[1].destroy();
temp_allocator_pair[..] = null;
thread_temp_allocator = null;
}
fn TempAllocator* temp_allocator_next() @private
{
if (!thread_temp_allocator)
@@ -434,22 +344,4 @@ fn TempAllocator* temp_allocator_next() @private
}
usz index = thread_temp_allocator == temp_allocator_pair[0] ? 1 : 0;
return thread_temp_allocator = temp_allocator_pair[index];
}
const NullAllocator NULL_ALLOCATOR = {};
distinct NullAllocator (Allocator) = uptr;
fn void*! NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
return AllocationFailure.OUT_OF_MEMORY?;
}
fn void*! NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
return AllocationFailure.OUT_OF_MEMORY?;
}
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
}
}

View File

@@ -25,22 +25,14 @@ enum X86Feature
{
ADX,
AES,
AMX_AVX512,
AMX_FP8,
AMX_MOVRS,
AMX_TF32,
AMX_TRANSPOSE,
AMX_BF16,
AMX_COMPLEX,
AMX_FP16,
AMX_INT8,
AMX_TILE,
APXF,
AVX,
AVX10_1_256,
AVX10_1_512,
AVX10_2_256,
AVX10_2_512,
AVX2,
AVX5124FMAPS,
AVX5124VNNIW,
@@ -91,7 +83,6 @@ enum X86Feature
MOVBE,
MOVDIR64B,
MOVDIRI,
MOVRS,
MWAITX,
PCLMUL,
PCONFIG,
@@ -150,14 +141,14 @@ fn void x86_initialize_cpu_features()
{
uint max_level = x86_cpuid(0).eax;
CpuId feat = x86_cpuid(1);
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : {};
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : {};
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : {};
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : {};
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : {};
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : {};
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : {};
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : {};
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : CpuId {};
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : CpuId {};
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : CpuId {};
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : CpuId {};
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : CpuId {};
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : CpuId {};
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : CpuId {};
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : CpuId {};
add_feature_if_bit(ADX, leaf7.ebx, 19);
add_feature_if_bit(AES, feat.ecx, 25);
add_feature_if_bit(AMX_BF16, leaf7.edx, 22);
@@ -165,7 +156,6 @@ fn void x86_initialize_cpu_features()
add_feature_if_bit(AMX_FP16, leaf7s1.eax, 21);
add_feature_if_bit(AMX_INT8, leaf7.edx, 25);
add_feature_if_bit(AMX_TILE, leaf7.edx, 24);
add_feature_if_bit(APXF, leaf7s1.edx, 21);
add_feature_if_bit(AVX, feat.ecx, 28);
add_feature_if_bit(AVX10_1_256, leaf7s1.edx, 19);
add_feature_if_bit(AVX10_1_512, leaf_24.ebx, 18);
@@ -265,4 +255,4 @@ fn void x86_initialize_cpu_features()
add_feature_if_bit(XSAVEOPT, leaf_d.eax, 0);
add_feature_if_bit(XSAVES, leaf_d.eax, 3);
}
}

View File

@@ -101,7 +101,7 @@ macro find_segment_section_body(MachHeader* header, char* segname, char* sectnam
Section64*! section = find_section(find_segment(header, segname), sectname);
if (catch section)
{
return ($Type[]){};
return $Type[] {};
}
$Type* ptr = (void*)header + section.offset;
return ptr[:section.size / $Type.sizeof];
@@ -175,7 +175,7 @@ fn void runtime_startup() @public @export("__c3_runtime_startup")
}
assert(runtime_state == RUN_CTORS);
runtime_state = READ_DYLIB;
ctor_first = null;
ctor = null;
}
fn void runtime_finalize() @public @export("__c3_runtime_finalize")
@@ -214,7 +214,7 @@ struct TypeId
usz sizeof;
TypeId* inner;
usz len;
typeid[?] additional;
typeid[*] additional;
}
fn void dl_reg_callback(MachHeader* mh, isz vmaddr_slide)

View File

@@ -47,13 +47,6 @@ macro int @main_to_int_main_args(#m, int argc, char** argv)
return #m(list);
}
macro int @_main_runner(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
return #m(list) ? 0 : 1;
}
macro int @main_to_void_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
@@ -91,19 +84,19 @@ 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_to_err_main_noargs(#m, void* 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)
macro int @win_to_int_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) => #m();
macro int @win_to_void_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd)
{
#m();
return 0;
}
macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_err_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
@@ -111,14 +104,14 @@ macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd
return 0;
}
macro int @win_to_int_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_int_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return #m(args);
}
macro int @win_to_void_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_void_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
@@ -126,26 +119,26 @@ macro int @win_to_void_main_args(#m, void* handle, void* prev_handle, Char16* cm
return 0;
}
macro int @win_to_err_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_err_main(#m, void* 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;
if (catch #m(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)
macro int @win_to_int_main(#m, void* 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);
return #m(handle, args, show_cmd);
}
macro int @win_to_void_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_void_main(#m, void* 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);
#m(handle, args, show_cmd);
return 0;
}
@@ -164,13 +157,6 @@ macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
return #m(args);
}
macro int @_wmain_runner(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
return #m(args) ? 0 : 1;
}
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
module std::core::string;
import std::ascii;
import std::io;
distinct String @if(!$defined(String)) = inline char[];
distinct ZString = inline char*;
distinct WString = inline Char16*;
def Char32 = uint;
@@ -33,72 +31,70 @@ fault NumberConversion
FLOAT_OUT_OF_RANGE,
}
<*
Return a temporary ZString created using the formatting function.
@param [in] fmt `The formatting string`
*>
fn ZString tformat_zstr(String fmt, args...)
/**
* Return a temporary String created using the formatting function.
*
* @param [in] fmt `The formatting string`
**/
macro String tformat(String fmt, ...)
{
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
str.appendf(fmt, ...args);
return str.zstr_view();
}
<*
Return a new String created using the formatting function.
@param [inout] allocator `The allocator to use`
@param [in] fmt `The formatting string`
*>
fn String format(String fmt, args..., Allocator allocator) => @pool(allocator)
{
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
str.appendf(fmt, ...args);
return str.copy_str(allocator);
}
<*
Return a heap allocated String created using the formatting function.
@param [in] fmt `The formatting string`
*>
fn String new_format(String fmt, args..., Allocator allocator = null) => format(fmt, ...args, allocator: allocator ?: allocator::heap());
<*
Return a temporary String created using the formatting function.
@param [in] fmt `The formatting string`
*>
fn String tformat(String fmt, args...)
{
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
str.appendf(fmt, ...args);
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.str_view();
}
<*
Return a new ZString created using the formatting function.
@param [in] fmt `The formatting string`
@param [inout] allocator `The allocator to use`
*>
fn ZString new_format_zstr(String fmt, args..., Allocator allocator = allocator::heap()) => @pool(allocator)
/**
* Return a temporary ZString created using the formatting function.
*
* @param [in] fmt `The formatting string`
**/
macro ZString tformat_zstr(String fmt, ...)
{
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
str.appendf(fmt, ...args);
return str.copy_zstr(allocator);
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.zstr_view();
}
<*
Check if a character is in a set.
/**
* Return a new String created using the formatting function.
*
* @param [in] fmt `The formatting string`
* @param [inout] allocator `The allocator to use`
**/
macro String new_format(String fmt, ..., Allocator allocator = allocator::heap())
{
@pool(allocator)
{
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.copy_str(allocator);
};
}
@param c `the character to check`
@param [in] set `The formatting string`
@pure
@return `True if a character is in the set`
*>
/**
* Return a new ZString created using the formatting function.
*
* @param [in] fmt `The formatting string`
* @param [inout] allocator `The allocator to use`
**/
macro ZString new_format_zstr(String fmt, ..., Allocator allocator = allocator::heap())
{
@pool(allocator)
{
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.copy_zstr(allocator);
};
}
/**
* Check if a character is in a set.
*
* @param c `the character to check`
* @param [in] set `The formatting string`
* @pure
* @return `True if a character is in the set`
**/
macro bool char_in_set(char c, String set)
{
foreach (ch : set) if (ch == c) return true;
@@ -130,59 +126,33 @@ fn String join_new(String[] s, String joiner, Allocator allocator = allocator::h
};
}
<*
Remove characters from the front and end of a string.
@param [in] string `The string to trim`
@param [in] to_trim `The set of characters to trim, defaults to whitespace`
@pure
@return `a substring of the string passed in`
*>
/**
* Remove characters from the front and end of a string.
*
* @param [in] string `The string to trim`
* @param [in] to_trim `The set of characters to trim, defaults to whitespace`
* @pure
* @return `a substring of the string passed in`
**/
fn String String.trim(string, String to_trim = "\t\n\r ")
{
return string.trim_left(to_trim).trim_right(to_trim);
}
<*
Remove characters from the front of a string.
@param [in] string `The string to trim`
@param [in] to_trim `The set of characters to trim, defaults to whitespace`
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_left(string, String to_trim = "\t\n\r ")
{
usz start = 0;
usz len = string.len;
while (start < len && char_in_set(string[start], to_trim)) start++;
if (start == len) return string[:0];
return string[start..];
usz end = len - 1;
while (end > start && char_in_set(string[end], to_trim)) end--;
return string[start..end];
}
<*
Remove characters from the end of a string.
@param [in] string `The string to trim`
@param [in] to_trim `The set of characters to trim, defaults to whitespace`
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_right(string, String to_trim = "\t\n\r ")
{
usz len = string.len;
while (len > 0 && char_in_set(string[len - 1], to_trim)) len--;
return string[:len];
}
<*
Check if the String starts with the needle.
@param [in] string
@param [in] needle
@pure
@return `'true' if the string starts with the needle`
*>
/**
* Check if the String starts with the needle.
*
* @param [in] string
* @param [in] needle
* @pure
* @return `'true' if the string starts with the needle`
**/
fn bool String.starts_with(string, String needle)
{
if (needle.len > string.len) return false;
@@ -190,14 +160,14 @@ fn bool String.starts_with(string, String needle)
return string[:needle.len] == needle;
}
<*
Check if the String ends with the needle.
@param [in] string
@param [in] needle
@pure
@return `'true' if the string ends with the needle`
*>
/**
* Check if the String ends with the needle.
*
* @param [in] string
* @param [in] needle
* @pure
* @return `'true' if the string ends with the needle`
**/
fn bool String.ends_with(string, String needle)
{
if (needle.len > string.len) return false;
@@ -205,28 +175,28 @@ fn bool String.ends_with(string, String needle)
return string[^needle.len..] == needle;
}
<*
Strip the front of the string if the prefix exists.
@param [in] string
@param [in] needle
@pure
@return `the substring with the prefix removed`
*>
/**
* Strip the front of the string if the prefix exists.
*
* @param [in] string
* @param [in] needle
* @pure
* @return `the substring with the prefix removed`
**/
fn String String.strip(string, String needle)
{
if (!needle.len || !string.starts_with(needle)) return string;
return string[needle.len..];
}
<*
Strip the end of the string if the suffix exists.
@param [in] string
@param [in] needle
@pure
@return `the substring with the suffix removed`
*>
/**
* Strip the end of the string if the suffix exists.
*
* @param [in] string
* @param [in] needle
* @pure
* @return `the substring with the suffix removed`
**/
fn String String.strip_end(string, String needle)
{
if (!needle.len || !string.ends_with(needle)) return string;
@@ -234,19 +204,18 @@ fn String String.strip_end(string, String needle)
return string[:(string.len - needle.len)];
}
<*
Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
@param [in] s
@param [in] needle
@param max "Max number of elements, 0 means no limit, defaults to 0"
@param skip_empty "True to skip empty elements"
@param [&inout] allocator "The allocator to use for the String[]"
@require needle.len > 0 "The needle must be at least 1 character long"
@ensure return.len > 0
*>
fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = allocator::heap(), bool skip_empty = false)
/**
* Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
*
* @param [in] s
* @param [in] needle
* @param [&inout] allocator "The allocator, defaults to the heap allocator"
* @param max "Max number of elements, 0 means no limit, defaults to 0"
* @require needle.len > 0 "The needle must be at least 1 character long"
* @ensure return.len > 0
**/
fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = allocator::heap())
{
usz capacity = 16;
usz i = 0;
@@ -266,11 +235,6 @@ fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = al
res = s;
no_more = true;
}
if (!res.len && skip_empty)
{
continue;
}
if (i == capacity)
{
capacity *= 2;
@@ -281,98 +245,42 @@ fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = al
return holder[:i];
}
<*
Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }, using the heap allocator
to store the parts.
@param [in] s
@param [in] needle
@param max "Max number of elements, 0 means no limit, defaults to 0"
@param skip_empty "True to skip empty elements"
@require needle.len > 0 "The needle must be at least 1 character long"
@ensure return.len > 0
*>
fn String[] String.new_split(s, String needle, usz max = 0, bool skip_empty) => s.split(needle, max, allocator::heap(), skip_empty) @inline;
<*
This function is identical to String.split, but implicitly uses the
temporary allocator.
@param [in] s
@param [in] needle
@param max "Max number of elements, 0 means no limit, defaults to 0"
@param skip_empty "True to skip empty elements"
*>
fn String[] String.tsplit(s, String needle, usz max = 0, bool skip_empty = false) => s.split(needle, max, allocator::temp(), skip_empty) @inline;
fault SplitResult { BUFFER_EXCEEDED }
<*
Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
@param [in] s
@param [in] needle
@param [inout] buffer
@param max "Max number of elements, 0 means no limit, defaults to 0"
@require needle.len > 0 "The needle must be at least 1 character long"
@ensure return.len > 0
@return! SplitResult.BUFFER_EXCEEDED `If there are more elements than would fit the buffer`
*>
fn String[]! String.split_to_buffer(s, String needle, String[] buffer, usz max = 0, bool skip_empty = false)
/**
* This function is identical to String.split, but implicitly uses the
* temporary allocator.
*
* @param [in] s
* @param [in] needle
* @param max "Max number of elements, 0 means no limit, defaults to 0"
**/
fn String[] String.tsplit(s, String needle, usz max = 0)
{
usz max_capacity = buffer.len;
usz i = 0;
bool no_more = false;
while (!no_more)
{
usz! index = i == max - 1 ? SearchResult.MISSING? : s.index_of(needle);
String res @noinit;
if (try index)
{
res = s[:index];
s = s[index + needle.len..];
}
else
{
res = s;
no_more = true;
}
if (!res.len && skip_empty)
{
continue;
}
if (i == max_capacity)
{
return SplitResult.BUFFER_EXCEEDED?;
}
buffer[i++] = res;
}
return buffer[:i];
return s.split(needle, max, allocator::temp()) @inline;
}
<*
Check if a substring is found in the string.
/**
* Check if a substring is found in the string.
@param [in] s
@param [in] needle "The string to look for."
@pure
@return "true if the string contains the substring, false otherwise"
*>
* @param [in] s
* @param [in] needle "The string to look for."
* @pure
* @return "true if the string contains the substring, false otherwise"
**/
fn bool String.contains(s, String needle)
{
return @ok(s.index_of(needle));
}
<*
Find the index of the first incidence of a string.
@param [in] s
@param needle "The character to look for"
@pure
@ensure return < s.len
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found"
*>
/**
* Find the index of the first incidence of a string.
*
* @param [in] s
* @param needle "The character to look for"
* @pure
* @ensure return < s.len
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.index_of_char(s, char needle)
{
foreach (i, c : s)
@@ -382,40 +290,17 @@ fn usz! String.index_of_char(s, char needle)
return SearchResult.MISSING?;
}
<*
Find the index of the first incidence of a one of the chars.
@param [in] s
@param [in] needle "The characters to look for"
@pure
@ensure return < s.len
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found"
*>
fn usz! String.index_of_chars(String s, char[] needle)
{
foreach (i, c : s)
{
foreach (j, pin : needle)
{
if (c == pin) return i;
}
}
return SearchResult.MISSING?;
}
<*
Find the index of the first incidence of a character.
@param [in] s
@param needle "The character to look for"
@param start_index "The index to start with, may exceed max index."
@pure
@ensure return < s.len
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found starting from the start_index"
*>
/**
* Find the index of the first incidence of a character.
*
* @param [in] s
* @param needle "The character to look for"
* @param start_index "The index to start with, may exceed max index."
* @pure
* @ensure return < s.len
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found starting from the start_index"
**/
fn usz! String.index_of_char_from(s, char needle, usz start_index)
{
usz len = s.len;
@@ -427,16 +312,16 @@ fn usz! String.index_of_char_from(s, char needle, usz start_index)
return SearchResult.MISSING?;
}
<*
Find the index of the first incidence of a character starting from the end.
@param [in] s
@param needle "the character to find"
@pure
@ensure return < s.len
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found"
*>
/**
* Find the index of the first incidence of a character starting from the end.
*
* @param [in] s
* @param needle "the character to find"
* @pure
* @ensure return < s.len
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.rindex_of_char(s, char needle)
{
foreach_r (i, c : s)
@@ -446,17 +331,17 @@ fn usz! String.rindex_of_char(s, char needle)
return SearchResult.MISSING?;
}
<*
Find the index of the first incidence of a string.
@param [in] s
@param [in] needle
@pure
@ensure return < s.len
@require needle.len > 0 : "The needle must be len 1 or more"
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found"
*>
/**
* Find the index of the first incidence of a string.
*
* @param [in] s
* @param [in] needle
* @pure
* @ensure return < s.len
* @require needle.len > 0 : "The needle must be len 1 or more"
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.index_of(s, String needle)
{
usz needed = needle.len;
@@ -471,17 +356,17 @@ fn usz! String.index_of(s, String needle)
return SearchResult.MISSING?;
}
<*
Find the index of the last incidence of a string.
@param [in] s
@param [in] needle
@pure
@ensure return < s.len
@require needle.len > 0 "The needle must be len 1 or more"
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found"
*>
/**
* Find the index of the last incidence of a string.
*
* @param [in] s
* @param [in] needle
* @pure
* @ensure return < s.len
* @require needle.len > 0 "The needle must be len 1 or more"
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.rindex_of(s, String needle)
{
usz needed = needle.len;
@@ -546,11 +431,6 @@ fn String String.tconcat(s1, String s2) => s1.concat(s2, allocator::temp());
fn ZString String.zstr_tcopy(s) => s.zstr_copy(allocator::temp()) @inline;
<*
Copy this string, by duplicating the string, always adding a zero byte
sentinel, so that it safely can be converted to a ZString by a
cast.
*>
fn String String.copy(s, Allocator allocator = allocator::heap())
{
usz len = s.len;
@@ -562,14 +442,14 @@ fn String String.copy(s, Allocator allocator = allocator::heap())
fn void String.free(&s, Allocator allocator = allocator::heap())
{
if (!s.ptr) return;
if (!s.len) return;
allocator::free(allocator, s.ptr);
*s = "";
}
fn String String.tcopy(s) => s.copy(allocator::temp()) @inline;
fn String ZString.copy(z, Allocator allocator = allocator::heap())
fn String ZString.copy(z, Allocator allocator = allocator::temp())
{
return z.str_view().copy(allocator) @inline;
}
@@ -579,12 +459,12 @@ fn String ZString.tcopy(z)
return z.str_view().copy(allocator::temp()) @inline;
}
<*
Convert an UTF-8 string to UTF-16
@return "The UTF-16 string as a slice, allocated using the given allocator"
@return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
@return! AllocationFailure "If allocation of the string fails"
*>
/**
* Convert an UTF-8 string to UTF-16
* @return "The UTF-16 string as a slice, allocated using the given allocator"
* @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
* @return! AllocationFailure "If allocation of the string fails"
**/
fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::heap())
{
usz len16 = conv::utf16len_for_utf8(s);
@@ -594,26 +474,28 @@ fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::heap())
return data[:len16];
}
<*
Convert an UTF-8 string to UTF-16
@return "The UTF-16 string as a slice, allocated using the given allocator"
@return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
@return! AllocationFailure "If allocation of the string fails"
*>
/**
* Convert an UTF-8 string to UTF-16
* @return "The UTF-16 string as a slice, allocated using the given allocator"
* @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
* @return! AllocationFailure "If allocation of the string fails"
**/
fn Char16[]! String.to_temp_utf16(s)
{
return s.to_new_utf16(allocator::temp());
}
fn WString! String.to_wstring(s, Allocator allocator)
fn WString! String.to_new_wstring(s, Allocator allocator = allocator::heap())
{
return (WString)s.to_new_utf16(allocator).ptr;
}
fn WString! String.to_temp_wstring(s) => s.to_wstring(allocator::temp());
fn WString! String.to_new_wstring(s) => s.to_wstring(allocator::heap());
fn WString! String.to_temp_wstring(s)
{
return (WString)s.to_temp_utf16().ptr;
}
fn Char32[]! String.to_utf32(s, Allocator allocator)
fn Char32[]! String.to_new_utf32(s, Allocator allocator = allocator::heap())
{
usz codepoints = conv::utf8_codepoints(s);
Char32* data = allocator::alloc_array_try(allocator, Char32, codepoints + 1)!;
@@ -622,15 +504,17 @@ fn Char32[]! String.to_utf32(s, Allocator allocator)
return data[:codepoints];
}
fn Char32[]! String.to_new_utf32(s) => s.to_utf32(allocator::heap()) @inline;
fn Char32[]! String.to_temp_utf32(s) => s.to_utf32(allocator::temp()) @inline;
fn Char32[]! String.to_temp_utf32(s)
{
return s.to_new_utf32(allocator::temp());
}
<*
Convert a string to ASCII lower case.
@param [inout] s
@pure
*>
/**
* Convert a string to ASCII lower case.
*
* @param [inout] s
* @pure
**/
fn void String.convert_ascii_to_lower(s)
{
foreach (&c : s) if (c.is_upper() @pure) *c += 'a' - 'A';
@@ -643,30 +527,30 @@ fn String String.new_ascii_to_lower(s, Allocator allocator = allocator::heap())
return copy;
}
fn String String.temp_ascii_to_lower(s)
fn String String.temp_ascii_to_lower(s, Allocator allocator = allocator::heap())
{
return s.new_ascii_to_lower(allocator::temp());
}
<*
Convert a string to ASCII upper case.
@param [inout] s
@pure
*>
/**
* Convert a string to ASCII upper case.
*
* @param [inout] s
* @pure
**/
fn void String.convert_ascii_to_upper(s)
{
foreach (&c : s) if (c.is_lower() @pure) *c -= 'a' - 'A';
}
<*
Returns a string converted to ASCII upper case.
@param [in] s
@param [inout] allocator
@return `a new String converted to ASCII upper case.`
*>
/**
* Returns a string converted to ASCII upper case.
*
* @param [in] s
* @param [inout] allocator
*
* @return `a new String converted to ASCII upper case.`
**/
fn String String.new_ascii_to_upper(s, Allocator allocator = allocator::heap())
{
String copy = s.copy(allocator);
@@ -679,10 +563,10 @@ fn StringIterator String.iterator(s)
return { s, 0 };
}
<*
@param [in] s
@return `a temporary String converted to ASCII upper case.`
*>
/**
* @param [in] s
* @return `a temporary String converted to ASCII upper case.`
**/
fn String String.temp_ascii_to_upper(s)
{
return s.new_ascii_to_upper(allocator::temp());
@@ -730,9 +614,9 @@ fn usz String.utf8_codepoints(s)
}
<*
@require (base <= 10 && base > 1) || base == 16 : "Unsupported base"
*>
/**
* @require (base <= 10 && base > 1) || base == 16 : "Unsupported base"
**/
macro String.to_integer(string, $Type, int base = 10)
{
usz len = string.len;
@@ -780,28 +664,26 @@ macro String.to_integer(string, $Type, int base = 10)
$Type value = 0;
while (index != len)
{
char c = string[index++];
switch
{
case base_used != 16 || c < 'A': c -= '0';
case c <= 'F': c -= 'A' - 10;
case c < 'a' || c > 'f': return NumberConversion.MALFORMED_INTEGER?;
default: c -= 'a' - 10;
}
char c = {|
char ch = string[index++];
if (base_used != 16 || ch < 'A') return (char)(ch - '0');
if (ch <= 'F') return (char)(ch - 'A' + 10);
if (ch < 'a') return NumberConversion.MALFORMED_INTEGER?;
if (ch > 'f') return NumberConversion.MALFORMED_INTEGER?;
return (char)(ch - 'a' + 10);
|}!;
if (c >= base_used) return NumberConversion.MALFORMED_INTEGER?;
do
{
value = {|
if (is_negative)
{
$Type new_value = value * base_used - c;
if (new_value > value) return NumberConversion.INTEGER_OVERFLOW?;
value = new_value;
break;
return new_value;
}
$Type new_value = value * base_used + c;
if (new_value < value) return NumberConversion.INTEGER_OVERFLOW?;
value = new_value;
};
return new_value;
|}!;
}
return value;
}
@@ -823,12 +705,7 @@ fn float! String.to_float(s) => s.to_real(float);
fn Splitter String.splitter(self, String split)
{
return { .string = self, .split = split };
}
fn Splitter String.tokenize(self, String split)
{
return { .string = self, .split = split, .tokenize = true };
return Splitter { self, split, 0 };
}
struct Splitter
@@ -836,8 +713,6 @@ struct Splitter
String string;
String split;
usz current;
bool tokenize;
int last_index;
}
fn void Splitter.reset(&self)
@@ -847,33 +722,18 @@ fn void Splitter.reset(&self)
fn String! Splitter.next(&self)
{
while (true)
usz len = self.string.len;
usz current = self.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
String remaining = self.string[current..];
usz! next = remaining.index_of(self.split);
if (try next)
{
usz len = self.string.len;
usz current = self.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
String remaining = self.string[current..];
usz! next = remaining.index_of(self.split);
if (try next)
{
self.current = current + next + self.split.len;
if (!next && self.tokenize) continue;
return remaining[:next];
}
self.current = len;
return remaining;
defer self.current = current + next + self.split.len;
return remaining[:next];
}
self.current = len;
return remaining;
}
macro String new_struct_to_str(x, Allocator allocator = allocator::heap())
{
DString s;
@stack_mem(512; Allocator mem)
{
s.init(mem);
io::fprint(&s, x)!!;
return s.copy_str(allocator);
};
}
macro String temp_struct_to_str(x) => new_struct_to_str(x, allocator::temp());

View File

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

View File

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

View File

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

View File

@@ -8,24 +8,18 @@ fault ConversionResult
VALUE_OUT_OF_RANGE,
VALUE_OUT_OF_UNSIGNED_RANGE,
}
<*
@require $Type.kindof.is_int() "Type was not an integer"
@require v.type.kindof == ENUM "Value was not an enum"
*>
macro any_to_enum_ordinal(any v, $Type)
{
return any_to_int(v.as_inner(), $Type);
}
<*
@require $Type.kindof.is_int() "Type was not an integer"
@require v.type.kindof.is_int() "Value was not an integer"
*>
/**
* @require $Type.kindof.is_int() || $Type.kindof == TypeKind.ENUM "Argument was not an integer"
**/
macro any_to_int(any v, $Type)
{
typeid any_type = v.type;
TypeKind kind = any_type.kindof;
if (kind == TypeKind.ENUM)
{
any_type = any_type.inner;
kind = any_type.kindof;
}
bool is_mixed_signed = $Type.kindof != any_type.kindof;
$Type max = $Type.max;
$Type min = $Type.min;
@@ -126,51 +120,10 @@ macro bool is_slice_convertable($Type)
$endswitch
}
macro bool is_bool($Type) @const => $Type.kindof == TypeKind.BOOL;
macro bool is_int($Type) @const => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT;
macro bool is_bool($Type) => $Type.kindof == TypeKind.BOOL;
macro bool is_int($Type) => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT;
<*
@require is_numerical($Type) "Expected a numerical type"
*>
macro bool is_signed($Type) @const
{
$switch (inner_kind($Type))
$case SIGNED_INT:
$case FLOAT:
return true;
$case VECTOR:
return is_signed($typefrom($Type.inner));
$default:
return false;
$endswitch
}
<*
@require is_numerical($Type) "Expected a numerical type"
*>
macro bool is_unsigned($Type) @const
{
$switch (inner_kind($Type))
$case UNSIGNED_INT:
return true;
$case VECTOR:
return is_unsigned($typefrom($Type.inner));
$default:
return false;
$endswitch
}
macro bool is_indexable($Type) @const
{
return $defined(($Type){}[0]);
}
macro bool is_ref_indexable($Type) @const
{
return $defined(&($Type){}[0]);
}
macro bool is_intlike($Type) @const
macro bool is_intlike($Type)
{
$switch ($Type.kindof)
$case SIGNED_INT:
@@ -183,7 +136,7 @@ macro bool is_intlike($Type) @const
$endswitch
}
macro bool is_underlying_int($Type) @const
macro bool is_underlying_int($Type)
{
$switch ($Type.kindof)
$case SIGNED_INT:
@@ -196,9 +149,9 @@ macro bool is_underlying_int($Type) @const
$endswitch
}
macro bool is_float($Type) @const => $Type.kindof == TypeKind.FLOAT;
macro bool is_float($Type) => $Type.kindof == TypeKind.FLOAT;
macro bool is_floatlike($Type) @const
macro bool is_floatlike($Type)
{
$switch ($Type.kindof)
$case FLOAT:
@@ -210,48 +163,42 @@ macro bool is_floatlike($Type) @const
$endswitch
}
macro bool is_vector($Type) @const
macro bool is_vector($Type)
{
return $Type.kindof == TypeKind.VECTOR;
}
macro typeid inner_type($Type) @const
macro TypeKind inner_kind($Type)
{
$if $Type.kindof == TypeKind.DISTINCT:
return inner_type($typefrom($Type.inner));
return inner_kind($typefrom($Type.inner));
$else
return $Type.typeid;
return $Type.kindof;
$endif
}
macro TypeKind inner_kind($Type) @const
{
return inner_type($Type).kindof;
}
macro bool is_same($TypeA, $TypeB) @const
macro bool is_same($TypeA, $TypeB)
{
return $TypeA.typeid == $TypeB.typeid;
}
macro bool @has_same(#a, #b, ...) @const
macro bool @has_same(#a, #b, ...)
{
var $type_a = @typeid(#a);
$if $type_a != @typeid(#b):
return false;
$endif
$for (var $i = 0; $i < $vacount; $i++)
$if @typeid($vaexpr[$i]) != $type_a:
$if @typeid($vaexpr($i)) != $type_a:
return false;
$endif
$endfor
return true;
}
macro bool may_load_atomic($Type) @const
macro bool may_load_atomic($Type)
{
$switch ($Type.kindof)
$case BOOL:
$case SIGNED_INT:
$case UNSIGNED_INT:
$case POINTER:
@@ -264,12 +211,12 @@ macro bool may_load_atomic($Type) @const
$endswitch
}
macro lower_to_atomic_compatible_type($Type) @const
macro lower_to_atomic_compatible_type($Type)
{
$switch ($Type.kindof)
$case SIGNED_INT:
$case SIGNED_INT:
$case UNSIGNED_INT:
return $Type.typeid;
return $Type.typeid;
$case DISTINCT:
return lower_to_atomic_compatible_type($Type.inner);
$case FLOAT:
@@ -290,10 +237,10 @@ 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) => types::is_floatlike($Type) || types::is_int($Type);
macro bool is_promotable_to_float($Type) => types::is_float($Type) || types::is_int($Type);
macro bool is_same_vector_type($Type1, $Type2) @const
macro bool is_same_vector_type($Type1, $Type2)
{
$if $Type1.kindof != TypeKind.VECTOR:
return $Type2.kindof != TypeKind.VECTOR;
@@ -302,7 +249,7 @@ macro bool is_same_vector_type($Type1, $Type2) @const
$endif
}
macro bool is_equatable_type($Type) @const
macro bool is_equatable_type($Type)
{
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):
return true;
@@ -311,34 +258,20 @@ macro bool is_equatable_type($Type) @const
$endif
}
<*
Checks if a type implements the copy protocol.
*>
macro bool implements_copy($Type) @const
/**
* Checks if a type implements the copy protocol.
**/
macro bool implements_copy($Type)
{
return $defined($Type.copy) && $defined($Type.free);
}
macro bool is_equatable_value(value) @deprecated
macro bool is_equatable_value(value)
{
return is_equatable_type($typeof(value));
}
macro bool @equatable_value(#value) @const
{
return is_equatable_type($typeof(#value));
}
macro bool @comparable_value(#value) @const
{
$if $defined(#value.less) || $defined(#value.compare_to):
return true;
$else
return $typeof(#value).is_ordered;
$endif
}
macro bool is_comparable_value(value) @deprecated
macro bool is_comparable_value(value)
{
$if $defined(value.less) || $defined(value.compare_to):
return true;

View File

@@ -1,22 +1,21 @@
module std::core::values;
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;
<*
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_bool(#value) @const => types::is_bool($typeof(#value));
macro bool @is_int(#value) @const => types::is_int($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 typeid @typeid(#value) @builtin => $typeof(#value).typeid;
macro TypeKind @typekind(#value) @builtin => $typeof(#value).kindof;
macro bool @typeis(#value, $Type) @builtin => $typeof(#value).typeid == $Type.typeid;
/**
* Return true if two values have the same type before any conversions.
**/
macro bool @is_same_type(#value1, #value2) => $typeof(#value1).typeid == $typeof(#value2).typeid;
macro bool @is_bool(#value) => types::is_bool($typeof(#value));
macro bool @is_int(#value) => types::is_int($typeof(#value));
macro bool @is_floatlike(#value) => types::is_floatlike($typeof(#value));
macro bool @is_float(#value) => types::is_float($typeof(#value));
macro bool @is_promotable_to_floatlike(#value) => types::is_promotable_to_floatlike($typeof(#value));
macro bool @is_promotable_to_float(#value) => types::is_promotable_to_float($typeof(#value));
macro bool @is_vector(#value) => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) => $assignable(#value1, $typeof(#value2));
macro promote_int(x)
{
@@ -27,40 +26,5 @@ macro promote_int(x)
$endif
}
<*
Select between two values at compile time,
the values do not have to be of the same type.
This acts like `$bool ? #value_1 : #value_2` but at compile time.
@param $bool `true for picking the first value, false for the other`
@param #value_1
@param #value_2
@returns `The selected value.`
*>
macro @select(bool $bool, #value_1, #value_2) @builtin
{
$if $bool:
return #value_1;
$else
return #value_2;
$endif
}
macro promote_int_same(x, y)
{
$if @is_int(x):
$switch
$case @is_vector(y) &&& $typeof(y).inner == float.typeid:
return (float)x;
$case $typeof(y).typeid == float.typeid:
return (float)x;
$default:
return (double)x;
$endswitch
$else
return x;
$endif
}
macro TypeKind @inner_kind(#value) @const => types::inner_kind($typeof(#value));
macro TypeKind @inner_kind(#value) => types::inner_kind($typeof(#value));

View File

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

View File

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

View File

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

View File

@@ -1,406 +0,0 @@
module std::encoding::base32;
// This module implements base32 encoding according to RFC 4648
// (https://www.rfc-editor.org/rfc/rfc4648)
struct Base32Alphabet
{
char[32] encoding;
char[256] reverse;
}
const char NO_PAD = 0;
const char DEFAULT_PAD = '=';
<*
Encode the content of src into a newly allocated string
@param [in] src "The input to be encoded."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@return "The encoded string."
*>
fn String! encode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding));
return encode_buffer(src, dst, padding, alphabet);
}
<*
Decode the content of src into a newly allocated char array.
@param [in] src "The input to be encoded."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@return "The decoded data."
*>
fn char[]! decode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding));
return decode_buffer(src, dst, padding, alphabet);
}
fn String! encode_new(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::heap(), padding, alphabet);
fn String! encode_temp(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::temp(), padding, alphabet);
fn char[]! decode_new(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::heap(), padding, alphabet);
fn char[]! decode_temp(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::temp(), padding, alphabet);
<*
Calculate the length in bytes of the decoded data.
@param n "Length in bytes of input."
@param padding "The padding character or 0 if none"
@require padding < 0xFF "Invalid padding character"
@return "Length in bytes of the decoded data."
*>
fn usz decode_len(usz n, char padding)
{
if (padding) return (n / 8) * 5;
// no padding
usz trailing = n % 8;
return n / 8 * 5 + (trailing * 5 ) / 8;
}
<*
Calculate the length in bytes of the encoded data.
@param n "Length in bytes on input."
@param padding "The padding character or 0 if none"
@require padding < 0xFF "Invalid padding character"
@return "Length in bytes of the encoded data."
*>
fn usz encode_len(usz n, char padding)
{
// A character is encoded into 8 x 5-bit blocks.
if (padding) return (n + 4) / 5 * 8;
// no padding
usz trailing = n % 5;
return n / 5 * 8 + (trailing * 8 + 4) / 5;
}
<*
Decode the content of src into dst, which must be properly sized.
@param src "The input to be decoded."
@param dst "The decoded input."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@require dst.len >= decode_len(src.len, padding) "Destination buffer too small"
@return "The resulting dst buffer"
@return! DecodingFailure
*>
fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return dst[:0];
char* dst_ptr = dst;
usz dn = decode_len(src.len, padding);
usz n;
char[8] buf;
while (src.len > 0 && dst.len > 0)
{
usz i @noinit;
// load 8 bytes into buffer
for (i = 0; i < 8; i++)
{
if (src.len == 0)
{
if (padding > 0) return DecodingFailure.INVALID_PADDING?;
break;
}
if (src[0] == padding) break;
buf[i] = alphabet.reverse[src[0]];
if (buf[i] == INVALID) return DecodingFailure.INVALID_CHARACTER?;
src = src[1..];
}
// extract 5-bytes from the buffer which contains 8 x 5 bit chunks
switch (i)
{
case 8:
// |66677777| dst[4]
// | 77777| buf[7]
// |666 | buf[6] << 5
dst[4] = buf[7] | buf[6] << 5;
n++;
nextcase 7;
case 7:
// |45555566| dst[3]
// | 66| buf[6] >> 3
// | 55555 | buf[5] << 2
// |4 | buf[4] << 7
dst[3] = buf[6] >> 3 | buf[5] << 2 | buf[4] << 7;
n++;
nextcase 5;
case 5:
// |33334444| dst[2]
// | 4444| buf[4] >> 1
// |3333 | buf[3] << 4
dst[2] = buf[4] >> 1 | buf[3] << 4;
n++;
nextcase 4;
case 4:
// |11222223| dst[1]
// | 3| buf[3] >> 4
// | 22222 | buf[2] << 1
// |11 | buf[1] << 6
dst[1] = buf[3] >> 4 | buf[2] << 1 | buf[1] << 6;
n++;
nextcase 2;
case 2:
// |00000111| dst[0]
// | 111| buf[1] >> 2
// |00000 | buf[0] << 3
dst[0] = buf[1] >> 2 | buf[0] << 3;
n++;
default:
return DecodingFailure.INVALID_CHARACTER?;
}
if (dst.len < 5) break;
dst = dst[5..];
}
return dst_ptr[:n];
}
<*
Encode the content of src into dst, which must be properly sized.
@param [in] src "The input to be encoded."
@param [inout] dst "The encoded input."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@require dst.len >= encode_len(src.len, padding) "Destination buffer too small"
@return "The encoded size."
*>
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return (String)dst[:0];
char* dst_ptr = dst;
usz n = (src.len / 5) * 5;
usz dn = encode_len(src.len, padding);
uint msb, lsb;
for (usz i = 0; i < n; i += 5)
{
// to fit 40 bits we need two 32-bit uints
msb = (uint)src[i] << 24 | (uint)src[i+1] << 16
| (uint)src[i+2] << 8 | (uint)src[i+3];
lsb = msb << 8 | (uint)src[i+4];
// now slice them into 5-bit chunks and translate to the
// alphabet.
dst[0] = alphabet.encoding[(msb >> 27) & MASK];
dst[1] = alphabet.encoding[(msb >> 22) & MASK];
dst[2] = alphabet.encoding[(msb >> 17) & MASK];
dst[3] = alphabet.encoding[(msb >> 12) & MASK];
dst[4] = alphabet.encoding[(msb >> 7) & MASK];
dst[5] = alphabet.encoding[(msb >> 2) & MASK];
dst[6] = alphabet.encoding[(lsb >> 5) & MASK];
dst[7] = alphabet.encoding[lsb & MASK];
dst = dst[8..];
}
usz trailing = src.len - n;
if (trailing == 0) return (String)dst_ptr[:dn];
msb = 0;
switch (trailing)
{
case 4:
msb |= (uint)src[n+3];
lsb = msb << 8;
dst[6] = alphabet.encoding[(lsb >> 5) & MASK];
dst[5] = alphabet.encoding[(msb >> 2) & MASK];
nextcase 3;
case 3:
msb |= (uint)src[n+2] << 8;
dst[4] = alphabet.encoding[(msb >> 7) & MASK];
nextcase 2;
case 2:
msb |= (uint)src[n+1] << 16;
dst[3] = alphabet.encoding[(msb >> 12) & MASK];
dst[2] = alphabet.encoding[(msb >> 17) & MASK];
nextcase 1;
case 1:
msb |= (uint)src[n] << 24;
dst[1] = alphabet.encoding[(msb >> 22) & MASK];
dst[0] = alphabet.encoding[(msb >> 27) & MASK];
}
// add the padding
if (padding > 0)
{
for (usz i = (trailing * 8 / 5) + 1; i < 8; i++)
{
dst[i] = padding;
}
}
return (String)dst_ptr[:dn];
}
const uint MASK @private = 0b11111;
const char INVALID @private = 0xff;
const int STD_PADDING = '=';
const int NO_PADDING = -1;
fault Base32Error
{
DUPLICATE_IN_ALPHABET,
PADDING_IN_ALPHABET,
INVALID_CHARACTER_IN_ALPHABET,
DESTINATION_TOO_SMALL,
INVALID_PADDING,
CORRUPT_INPUT
}
struct Base32Encoder @deprecated
{
Base32Alphabet alphabet;
char padding;
}
<*
@param encoder "The 32-character alphabet for encoding."
@param padding "Set to a negative value to disable padding."
@require padding < 256
*>
fn void! Base32Encoder.init(&self, Alphabet encoder = STD_ALPHABET, int padding = STD_PADDING)
{
encoder.validate(padding)!;
*self = { .alphabet = { .encoding = (char[32])encoder }, .padding = padding < 0 ? (char)0 : (char)padding};
}
<*
Calculate the length in bytes of the encoded data.
@param n "Length in bytes on input."
@return "Length in bytes of the encoded data."
*>
fn usz Base32Encoder.encode_len(&self, usz n)
{
return encode_len(n, self.padding);
}
<*
Encode the content of src into dst, which must be properly sized.
@param [in] src "The input to be encoded."
@param [inout] dst "The encoded input."
@return "The encoded size."
@return! Base32Error.DESTINATION_TOO_SMALL
*>
fn usz! Base32Encoder.encode(&self, char[] src, char[] dst)
{
usz dn = self.encode_len(src.len);
if (dst.len < dn) return Base32Error.DESTINATION_TOO_SMALL?;
return encode_buffer(src, dst, self.padding, &self.alphabet).len;
}
struct Base32Decoder @deprecated
{
Base32Alphabet alphabet;
char padding;
}
<*
@param decoder "The alphabet used for decoding."
@param padding "Set to a negative value to disable padding."
@require padding < 256
*>
fn void! Base32Decoder.init(&self, Alphabet decoder = STD_ALPHABET, int padding = STD_PADDING)
{
decoder.validate(padding)!;
*self = { .alphabet = { .encoding = (char[32])decoder }, .padding = padding < 0 ? (char)0 : (char)padding };
self.alphabet.reverse[..] = INVALID;
foreach (char i, c : decoder)
{
self.alphabet.reverse[c] = i;
}
}
<*
Calculate the length in bytes of the decoded data.
@param n "Length in bytes of input."
@return "Length in bytes of the decoded data."
*>
fn usz Base32Decoder.decode_len(&self, usz n)
{
return decode_len(n, self.padding);
}
<*
Decode the content of src into dst, which must be properly sized.
@param src "The input to be decoded."
@param dst "The decoded input."
@return "The decoded size."
@return! Base32Error.DESTINATION_TOO_SMALL, Base32Error.CORRUPT_INPUT
*>
fn usz! Base32Decoder.decode(&self, char[] src, char[] dst)
{
if (src.len == 0) return 0;
usz dn = self.decode_len(src.len);
if (dst.len < dn) return Base32Error.DESTINATION_TOO_SMALL?;
return decode_buffer(src, dst, self.padding, &self.alphabet).len;
}
// Validate the 32-character alphabet to make sure that no character occurs
// twice and that the padding is not present in the alphabet.
fn void! Alphabet.validate(&self, int padding)
{
bool[256] checked;
foreach (c : self)
{
if (checked[c])
{
return Base32Error.DUPLICATE_IN_ALPHABET?;
}
checked[c] = true;
if (c == '\r' || c == '\n')
{
return Base32Error.INVALID_CHARACTER_IN_ALPHABET?;
}
}
if (padding >= 0)
{
char pad = (char)padding;
if (pad == '\r' || pad == '\n')
{
return Base32Error.INVALID_PADDING?;
}
if (checked[pad])
{
return Base32Error.PADDING_IN_ALPHABET?;
}
}
}
distinct Alphabet = char[32];
// Standard base32 Alphabet
const Alphabet STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
// Extended Hex Alphabet
const Alphabet HEX_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
const Base32Alphabet STANDARD = {
.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
.reverse = x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffff1a1b1c1d1e1fffffffffffffffff
ff000102030405060708090a0b0c0d0e0f10111213141516171819ffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
};
const Base32Alphabet HEX = {
.encoding = "0123456789ABCDEFGHIJKLMNOPQRSTUV",
.reverse = x`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff00010203040506070809ffffffffffff
ff0a0b0c0d0e0f101112131415161718191a1b1c1d1e1fffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
};

View File

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

View File

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

View File

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

View File

@@ -1,109 +0,0 @@
module std::encoding::hex;
import std::encoding @norecurse;
// The implementation is based on https://www.rfc-editor.org/rfc/rfc4648
fn String encode_buffer(char[] code, char[] buffer)
{
return (String)buffer[:encode_bytes(code, buffer)];
}
fn char[]! decode_buffer(char[] code, char[] buffer)
{
return buffer[:decode_bytes(code, buffer)!];
}
fn String encode(char[] code, Allocator allocator)
{
char[] data = allocator::alloc_array(allocator, char, encode_len(code.len));
return (String)data[:encode_bytes(code, data)];
}
fn char[]! decode(char[] code, Allocator allocator)
{
char[] data = allocator::alloc_array(allocator, char, decode_len(code.len));
return data[:decode_bytes(code, data)!];
}
fn String encode_new(char[] code) @inline => encode(code, allocator::heap());
fn String encode_temp(char[] code) @inline => encode(code, allocator::temp());
fn char[]! decode_new(char[] code) @inline => decode(code, allocator::heap());
fn char[]! decode_temp(char[] code) @inline => decode(code, allocator::temp());
<*
Calculate the size of the encoded data.
@param n "Size of the input to be encoded."
@return "The size of the input once encoded."
*>
fn usz encode_len(usz n) => n * 2;
<*
Encode the content of src into dst, which must be properly sized.
@param src "The input to be encoded."
@param dst "The encoded input."
@return "The encoded size."
@require dst.len >= encode_len(src.len) "Destination array is not large enough"
*>
fn usz encode_bytes(char[] src, char[] dst)
{
usz j = 0;
foreach (v : src)
{
dst[j] = HEXALPHABET[v >> 4];
dst[j + 1] = HEXALPHABET[v & 0x0f];
j = j + 2;
}
return src.len * 2;
}
<*
Calculate the size of the decoded data.
@param n "Size of the input to be decoded."
@return "The size of the input once decoded."
*>
macro usz decode_len(usz n) => n / 2;
<*
Decodes src into bytes. Returns the actual number of bytes written to dst.
Expects that src only contains hexadecimal characters and that src has even
length.
@param src "The input to be decoded."
@param dst "The decoded input."
@require src.len % 2 == 0 "src is not of even length"
@require dst.len >= decode_len(src.len) "Destination array is not large enough"
@return! DecodingFailure.INVALID_CHARACTER
*>
fn usz! decode_bytes(char[] src, char[] dst)
{
usz i;
for (usz j = 1; j < src.len; j += 2)
{
char a = HEXREVERSE[src[j - 1]];
char b = HEXREVERSE[src[j]];
if (a > 0x0f || b > 0x0f) return DecodingFailure.INVALID_CHARACTER?;
dst[i] = (a << 4) | b;
i++;
}
return i;
}
const char[?] HEXALPHABET @private = "0123456789abcdef";
const char[?] HEXREVERSE @private =
x`ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
00010203040506070809ffffffffffff
ff0a0b0c0d0e0fffffffffffffffffff
ffffffffffffffffffffffffffffffff
ff0a0b0c0d0e0fffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff`;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,225 +0,0 @@
module std::hash::md5;
import std::hash::hmac;
import std::bits;
const BLOCK_BYTES = 64;
const HASH_BYTES = 16;
struct Md5
{
uint lo, hi;
uint a, b, c, d;
char[64] buffer;
uint[16] block;
}
def HmacMd5 = Hmac(<Md5, HASH_BYTES, BLOCK_BYTES>);
def hmac = hmac::hash(<Md5, HASH_BYTES, BLOCK_BYTES>);
def pbkdf2 = hmac::pbkdf2(<Md5, HASH_BYTES, BLOCK_BYTES>);
fn char[HASH_BYTES] hash(char[] data)
{
Md5 md5;
md5.init();
md5.update(data);
return md5.final();
}
fn void Md5.init(&self)
{
self.a = 0x67452301;
self.b = 0xefcdab89;
self.c = 0x98badcfe;
self.d = 0x10325476;
self.lo = 0;
self.hi = 0;
}
fn void Md5.update(&ctx, char[] data)
{
uint saved_lo = ctx.lo;
if ((ctx.lo = (saved_lo + data.len) & 0x1fffffff) < saved_lo) ctx.hi++;
ctx.hi += data.len >> 29;
usz used = (usz)saved_lo & 0x3f;
if (used)
{
usz available = 64 - used;
if (data.len < available)
{
ctx.buffer[used:data.len] = data[..];
return;
}
ctx.buffer[used:available] = data[:available];
data = data[available..];
body(ctx, &ctx.buffer, 64);
}
if (data.len >= 64)
{
data = body(ctx, data, data.len & ~(usz)0x3f)[:data.len & 0x3f];
}
ctx.buffer[:data.len] = data[..];
}
fn char[HASH_BYTES] Md5.final(&ctx)
{
usz used = (usz)ctx.lo & 0x3f;
ctx.buffer[used++] = 0x80;
usz available = 64 - used;
if (available < 8)
{
ctx.buffer[used:available] = 0;
body(ctx, &ctx.buffer, 64);
used = 0;
available = 64;
}
ctx.buffer[used:available - 8] = 0;
ctx.lo <<= 3;
ctx.buffer[56:4] = bitcast(ctx.lo, char[4])[..];
ctx.buffer[60:4] = bitcast(ctx.hi, char[4])[..];
body(ctx, &ctx.buffer, 64);
char[16] res @noinit;
res[0:4] = bitcast(ctx.a, char[4]);
res[4:4] = bitcast(ctx.b, char[4]);
res[8:4] = bitcast(ctx.c, char[4]);
res[12:4] = bitcast(ctx.d, char[4]);
*ctx = {};
return res;
}
module std::hash::md5 @private;
// Implementation
macro @f(x, y, z) => z ^ (x & (y ^ z));
macro @g(x, y, z) => y ^ (z & (x ^ y));
macro @h(x, y, z) => (x ^ y) ^ z;
macro @h2(x, y, z) => x ^ (y ^ z);
macro @i(x, y, z) => y ^ (x | ~z);
macro @step(#f, a, b, c, d, ptr, n, t, s)
{
*a += #f(b, c, d) + @unaligned_load(*(uint *)&ptr[n * 4], 2) + t;
*a = (*a << s) | ((*a & 0xffffffff) >> (32 - s));
*a += b;
}
fn char* body(Md5* ctx, void* data, usz size)
{
char* ptr;
uint a, b, c, d;
uint saved_a, saved_b, saved_c, saved_d;
ptr = data;
a = ctx.a;
b = ctx.b;
c = ctx.c;
d = ctx.d;
do
{
saved_a = a;
saved_b = b;
saved_c = c;
saved_d = d;
/* Round 1 */
@step(@f, &a, b, c, d, ptr, 0, 0xd76aa478, 7) ;
@step(@f, &d, a, b, c, ptr, 1, 0xe8c7b756, 12) ;
@step(@f, &c, d, a, b, ptr, 2, 0x242070db, 17) ;
@step(@f, &b, c, d, a, ptr, 3, 0xc1bdceee, 22) ;
@step(@f, &a, b, c, d, ptr, 4, 0xf57c0faf, 7) ;
@step(@f, &d, a, b, c, ptr, 5, 0x4787c62a, 12) ;
@step(@f, &c, d, a, b, ptr, 6, 0xa8304613, 17) ;
@step(@f, &b, c, d, a, ptr, 7, 0xfd469501, 22) ;
@step(@f, &a, b, c, d, ptr, 8, 0x698098d8, 7) ;
@step(@f, &d, a, b, c, ptr, 9, 0x8b44f7af, 12) ;
@step(@f, &c, d, a, b, ptr, 10, 0xffff5bb1, 17);
@step(@f, &b, c, d, a, ptr, 11, 0x895cd7be, 22);
@step(@f, &a, b, c, d, ptr, 12, 0x6b901122, 7) ;
@step(@f, &d, a, b, c, ptr, 13, 0xfd987193, 12);
@step(@f, &c, d, a, b, ptr, 14, 0xa679438e, 17);
@step(@f, &b, c, d, a, ptr, 15, 0x49b40821, 22);
/* Round 2 */
@step(@g, &a, b, c, d, ptr, 1, 0xf61e2562, 5) ;
@step(@g, &d, a, b, c, ptr, 6, 0xc040b340, 9) ;
@step(@g, &c, d, a, b, ptr, 11, 0x265e5a51, 14);
@step(@g, &b, c, d, a, ptr, 0, 0xe9b6c7aa, 20) ;
@step(@g, &a, b, c, d, ptr, 5, 0xd62f105d, 5) ;
@step(@g, &d, a, b, c, ptr, 10, 0x02441453, 9) ;
@step(@g, &c, d, a, b, ptr, 15, 0xd8a1e681, 14);
@step(@g, &b, c, d, a, ptr, 4, 0xe7d3fbc8, 20) ;
@step(@g, &a, b, c, d, ptr, 9, 0x21e1cde6, 5) ;
@step(@g, &d, a, b, c, ptr, 14, 0xc33707d6, 9) ;
@step(@g, &c, d, a, b, ptr, 3, 0xf4d50d87, 14) ;
@step(@g, &b, c, d, a, ptr, 8, 0x455a14ed, 20) ;
@step(@g, &a, b, c, d, ptr, 13, 0xa9e3e905, 5) ;
@step(@g, &d, a, b, c, ptr, 2, 0xfcefa3f8, 9) ;
@step(@g, &c, d, a, b, ptr, 7, 0x676f02d9, 14) ;
@step(@g, &b, c, d, a, ptr, 12, 0x8d2a4c8a, 20);
/* Round 3 */
@step(@h, &a, b, c, d, ptr, 5, 0xfffa3942, 4);
@step(@h2, &d, a, b, c, ptr, 8, 0x8771f681, 11);
@step(@h, &c, d, a, b, ptr, 11, 0x6d9d6122, 16);
@step(@h2, &b, c, d, a, ptr, 14, 0xfde5380c, 23);
@step(@h, &a, b, c, d, ptr, 1, 0xa4beea44, 4);
@step(@h2, &d, a, b, c, ptr, 4, 0x4bdecfa9, 11);
@step(@h, &c, d, a, b, ptr, 7, 0xf6bb4b60, 16);
@step(@h2, &b, c, d, a, ptr, 10, 0xbebfbc70, 23);
@step(@h, &a, b, c, d, ptr, 13, 0x289b7ec6, 4) ;
@step(@h2, &d, a, b, c, ptr, 0, 0xeaa127fa, 11) ;
@step(@h, &c, d, a, b, ptr, 3, 0xd4ef3085, 16) ;
@step(@h2, &b, c, d, a, ptr, 6, 0x04881d05, 23) ;
@step(@h, &a, b, c, d, ptr, 9, 0xd9d4d039, 4) ;
@step(@h2, &d, a, b, c, ptr, 12, 0xe6db99e5, 11) ;
@step(@h, &c, d, a, b, ptr, 15, 0x1fa27cf8, 16) ;
@step(@h2, &b, c, d, a, ptr, 2, 0xc4ac5665, 23) ;
/* Round 4 */
@step(@i, &a, b, c, d, ptr, 0, 0xf4292244, 6) ;
@step(@i, &d, a, b, c, ptr, 7, 0x432aff97, 10) ;
@step(@i, &c, d, a, b, ptr, 14, 0xab9423a7, 15) ;
@step(@i, &b, c, d, a, ptr, 5, 0xfc93a039, 21) ;
@step(@i, &a, b, c, d, ptr, 12, 0x655b59c3, 6) ;
@step(@i, &d, a, b, c, ptr, 3, 0x8f0ccc92, 10) ;
@step(@i, &c, d, a, b, ptr, 10, 0xffeff47d, 15) ;
@step(@i, &b, c, d, a, ptr, 1, 0x85845dd1, 21) ;
@step(@i, &a, b, c, d, ptr, 8, 0x6fa87e4f, 6) ;
@step(@i, &d, a, b, c, ptr, 15, 0xfe2ce6e0, 10) ;
@step(@i, &c, d, a, b, ptr, 6, 0xa3014314, 15) ;
@step(@i, &b, c, d, a, ptr, 13, 0x4e0811a1, 21) ;
@step(@i, &a, b, c, d, ptr, 4, 0xf7537e82, 6) ;
@step(@i, &d, a, b, c, ptr, 11, 0xbd3af235, 10) ;
@step(@i, &c, d, a, b, ptr, 2, 0x2ad7d2bb, 15) ;
@step(@i, &b, c, d, a, ptr, 9, 0xeb86d391, 21) ;
a += saved_a;
b += saved_b;
c += saved_c;
d += saved_d;
ptr += 64;
} while (size -= 64);
ctx.a = a;
ctx.b = b;
ctx.c = c;
ctx.d = d;
return ptr;
}

View File

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

View File

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

View File

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

View File

@@ -19,11 +19,6 @@ fn File! open_path(Path path, String mode)
return from_handle(os::native_fopen(path.str_view(), mode));
}
fn bool exists(String file) => @pool()
{
return path::exists(path::temp_new(file)) ?? false;
}
fn File from_handle(CFile file)
{
return { .file = file };
@@ -42,17 +37,17 @@ fn usz! get_size(String path)
fn void! delete(String filename) => os::native_remove(filename) @inline;
<*
@require self.file != null
*>
/**
* @require self.file != null
**/
fn void! File.reopen(&self, String filename, String mode)
{
self.file = os::native_freopen(self.file, filename, mode)!;
}
<*
@require self.file != null
*>
/**
* @require self.file != null
**/
fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
{
os::native_fseek(self.file, offset, seek_mode)!;
@@ -62,9 +57,9 @@ fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
/*
Implement later
<*
@require self.file == null
*>
/**
* @require self.file == null
**/
fn void! File.memopen(File* file, char[] data, String mode)
{
@pool()
@@ -76,17 +71,17 @@ fn void! File.memopen(File* file, char[] data, String mode)
*/
<*
@require self.file != null
*>
/**
* @require self.file != null
*/
fn void! File.write_byte(&self, char c) @dynamic
{
return os::native_fputc(c, self.file);
if (!libc::fputc(c, self.file)) return IoError.EOF?;
}
<*
@param [&inout] self
*>
/**
* @param [&inout] self
*/
fn void! File.close(&self) @inline @dynamic
{
if (self.file && libc::fclose(self.file))
@@ -110,40 +105,31 @@ fn void! File.close(&self) @inline @dynamic
self.file = null;
}
<*
@require self.file != null
*>
/**
* @require self.file
*/
fn bool File.eof(&self) @inline
{
return libc::feof(self.file) != 0;
}
<*
@param [in] buffer
*>
/**
* @param [in] buffer
*/
fn usz! File.read(&self, char[] buffer) @dynamic
{
return os::native_fread(self.file, buffer);
}
<*
@param [out] buffer
@require self.file != null `File must be initialized`
*>
/**
* @param [out] buffer
* @require self.file `File must be initialized`
*/
fn usz! File.write(&self, char[] buffer) @dynamic
{
return os::native_fwrite(self.file, buffer);
}
fn Fd File.fd(self) @if(env::LIBC)
{
return libc::fileno(self.file);
}
fn bool File.isatty(self) @if(env::LIBC)
{
return libc::isatty(self.fd()) > 0;
}
fn char! File.read_byte(&self) @dynamic
{
@@ -152,13 +138,13 @@ fn char! File.read_byte(&self) @dynamic
return (char)c;
}
<*
Load up to buffer.len characters. Returns IoError.OVERFLOW if the file is longer
than the buffer.
@param filename "The path to the file to read"
@param [in] buffer "The buffer to read to"
*>
/**
* Load up to buffer.len characters. Returns IoError.OVERFLOW if the file is longer
* than the buffer.
*
* @param filename "The path to the file to read"
* @param [in] buffer "The buffer to read to"
**/
fn char[]! load_buffer(String filename, char[] buffer)
{
File file = open(filename, "rb")!;
@@ -175,8 +161,6 @@ fn char[]! load_buffer(String filename, char[] buffer)
}
fn char[]! load(Allocator allocator, String filename) => load_new(filename, allocator);
fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
{
File file = open(filename, "rb")!;
@@ -193,29 +177,14 @@ fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
return data[:len];
}
fn char[]! load_path_new(Path path, Allocator allocator = allocator::heap()) => load_new(path.str_view(), allocator);
fn char[]! load_temp(String filename)
{
return load_new(filename, allocator::temp());
}
fn char[]! load_path_temp(Path path) => load_temp(path.str_view());
fn void! save(String filename, char[] data)
{
File file = open(filename, "wb")!;
defer (void)file.close();
while (data.len)
{
usz written = file.write(data)!;
data = data[written..];
}
}
<*
@require self.file != null `File must be initialized`
*>
/**
* @require self.file `File must be initialized`
*/
fn void! File.flush(&self) @dynamic
{
libc::fflush(self.file);

View File

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

View File

@@ -4,34 +4,6 @@ import std::math;
const char[16] XDIGITS_H = "0123456789ABCDEF";
const char[16] XDIGITS_L = "0123456789abcdef";
fault FormattingFault
{
BAD_FORMAT
}
fn usz! print_hex_chars(Formatter* f, char[] out, bool uppercase) @inline
{
char past_10 = (uppercase ? 'A' : 'a') - 10;
usz len = 0;
foreach (c : out)
{
char digit = c >> 4;
f.out(digit + (digit < 10 ? '0' : past_10))!;
len++;
digit = c & 0xf;
f.out(digit + (digit < 10 ? '0' : past_10))!;
len++;
}
return len;
}
macro Formatter.first_err(&self, anyfault f)
{
if (self.first_fault) return self.first_fault;
self.first_fault = f;
return f;
}
fn usz! Formatter.adjust(&self, usz len) @local
{
if (!self.flags.left) return 0;
@@ -49,7 +21,6 @@ fn uint128! int_from_any(any arg, bool *is_neg) @private
case TypeKind.ENUM:
return int_from_any(arg.as_inner(), is_neg);
default:
break;
}
*is_neg = false;
switch (arg)
@@ -88,7 +59,7 @@ fn uint128! int_from_any(any arg, bool *is_neg) @private
double d = *arg;
return (uint128)((*is_neg = d < 0) ? -d : d);
default:
return FormattingFault.BAD_FORMAT?;
return PrintFault.INVALID_ARGUMENT_TYPE?;
}
}
@@ -130,19 +101,19 @@ fn FloatType! float_from_any(any arg) @private
case double:
return (FloatType)*arg;
default:
return FormattingFault.BAD_FORMAT?;
return PrintFault.INVALID_ARGUMENT_TYPE?;
}
}
<*
Read a simple integer value, typically for formatting.
@param [inout] len_ptr "the length remaining."
@param [in] buf "the buf to read from."
@param maxlen "the maximum len that can be read."
@return "The result of the atoi."
*>
/**
* Read a simple integer value, typically for formatting.
*
* @param [inout] len_ptr "the length remaining."
* @param [in] buf "the buf to read from."
* @param maxlen "the maximum len that can be read."
* @return "The result of the atoi."
**/
fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
{
uint i = 0;
@@ -230,7 +201,8 @@ fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
// Add padding
if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
String s = self.flags.uppercase ? "INF" : "inf";
if (math::is_nan(y)) s = self.flags.uppercase ? "NAN" : "nan";
if (y != y) s = self.flags.uppercase ? "NAN" : "nan";
len += s.len;
if (pl) len += self.out(is_neg ? '-' : '+')!;
len += self.out_chars(s)!;
if (self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
@@ -616,15 +588,12 @@ fn usz! Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
fn usz! Formatter.ntoa_any(&self, any arg, uint base) @private
{
bool is_neg;
return self.ntoa(int_from_any(arg, &is_neg)!!, is_neg, base) @inline;
uint128 val = int_from_any(arg, &is_neg)!!;
return self.ntoa(val, is_neg, base) @inline;
}
fn usz! Formatter.out_char(&self, any arg) @private
{
if (!arg.type.kindof.is_int())
{
return self.out_substr("<NOT CHAR>");
}
usz len = 1;
uint l = 1;
// pre padding
@@ -659,18 +628,29 @@ fn usz! Formatter.out_reverse(&self, char[] buf) @private
usz buffer_start_idx = self.idx;
usz len = buf.len;
// pad spaces up to given width
if (!self.flags.zeropad && !self.flags.left)
if (!self.flags.zeropad)
{
n += self.pad(' ', self.width, len)!;
n += self.adjust(len)!;
}
// reverse string
while (len) n += self.out(buf[--len])!;
// append pad spaces up to given width
n += self.adjust(n)!;
n += self.adjust(self.idx - buffer_start_idx)!;
return n;
}
fn void! printf_advance_format(usz format_len, usz *index_ptr) @inline @private
{
usz val = ++(*index_ptr);
if (val >= format_len) return FormattingFault.UNTERMINATED_FORMAT?;
}
fn any! next_any(any* args_ptr, usz args_len, usz* arg_index_ptr) @inline @private
{
if (*arg_index_ptr >= args_len) return FormattingFault.MISSING_ARG?;
return args_ptr[(*arg_index_ptr)++];
}
fn int! printf_parse_format_field(
any* args_ptr, usz args_len, usz* args_index_ptr,
@@ -679,11 +659,9 @@ fn int! printf_parse_format_field(
char c = format_ptr[*index_ptr];
if (c.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr);
if (c != '*') return 0;
usz len = ++(*index_ptr);
if (len >= format_len) return FormattingFault.BAD_FORMAT?;
if (*args_index_ptr >= args_len) return FormattingFault.BAD_FORMAT?;
any val = args_ptr[(*args_index_ptr)++];
if (!val.type.kindof.is_int()) return FormattingFault.BAD_FORMAT?;
printf_advance_format(format_len, index_ptr)!;
any val = next_any(args_ptr, args_len, args_index_ptr)!;
if (!val.type.kindof.is_int()) return FormattingFault.INVALID_WIDTH_ARG?;
uint! intval = types::any_to_int(val, int);
return intval ?? FormattingFault.BAD_FORMAT?;
return intval ?? FormattingFault.INVALID_WIDTH_ARG?;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import std::io, std::os;
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
{
PathList list;
list.init(allocator);
list.new_init(.allocator = allocator);
DIRPtr directory = posix::opendir(dir.str_view() ? dir.as_zstr() : (ZString)".");
defer if (directory) posix::closedir(directory);
if (!directory) return (path::is_dir(dir) ? IoError.CANNOT_READ_DIR : IoError.FILE_NOT_DIR)?;
@@ -27,7 +27,7 @@ import std::time, std::os, std::io;
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
{
PathList list;
list.init(allocator);
list.new_init(.allocator = allocator);
@pool(allocator)
{

View File

@@ -1,9 +1,9 @@
module std::io::os @if(env::POSIX);
import std::io, std::os, libc;
<*
@require dir.str_view().len > 0
*>
/**
* @require dir.str_view()
**/
fn void! native_rmtree(Path dir)
{
DIRPtr directory = posix::opendir(dir.as_zstr());
@@ -16,7 +16,7 @@ fn void! native_rmtree(Path dir)
{
String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue;
Path new_path = dir.temp_append(name)!;
Path new_path = dir.tappend(name)!;
if (entry.d_type == posix::DT_DIR)
{
native_rmtree(new_path)!;
@@ -49,7 +49,7 @@ fn void! native_rmtree(Path path)
{
String filename = string::new_from_wstring((WString)&find_data.cFileName, allocator::temp())!;
if (filename == "." || filename == "..") continue;
Path file_path = path.temp_append(filename)!;
Path file_path = path.tappend(filename)!;
if (find_data.dwFileAttributes & win32::FILE_ATTRIBUTE_DIRECTORY)
{
native_rmtree(file_path)!;

View File

@@ -11,17 +11,19 @@ fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(!env
return path::new("/tmp", allocator);
}
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(env::WIN32) => @pool(allocator)
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(env::WIN32)
{
Win32_DWORD len = win32::getTempPathW(0, null);
if (!len) return IoError.GENERAL_ERROR?;
Char16[] buff = mem::temp_alloc_array(Char16, len + (usz)1);
if (!win32::getTempPathW(len, buff)) return IoError.GENERAL_ERROR?;
return path::new(string::temp_from_utf16(buff[:len]), allocator);
@pool(allocator)
{
Win32_DWORD len = win32::getTempPathW(0, null);
if (!len) return IoError.GENERAL_ERROR?;
Char16[] buff = mem::temp_alloc_array(Char16, len + (usz)1);
if (!win32::getTempPathW(len, buff)) return IoError.GENERAL_ERROR?;
return path::new(string::temp_from_utf16(buff[:len]), allocator);
};
}
module std::io::os @if(env::NO_LIBC);
import std::io::path;
macro Path! native_temp_directory(Allocator allocator = allocator::heap())
{

View File

@@ -15,9 +15,7 @@ fault PathResult
NO_PARENT,
}
def Path = PathImp;
struct PathImp (Printable)
struct Path (Printable)
{
String path_string;
PathEnv env;
@@ -29,12 +27,7 @@ enum PathEnv
POSIX
}
fn Path! new_cwd(Allocator allocator = allocator::heap()) => @pool(allocator)
{
return new(os::getcwd(allocator::temp()), allocator);
}
fn Path! getcwd(Allocator allocator = allocator::heap()) @deprecated("Use new_cwd()")
fn Path! getcwd(Allocator allocator = allocator::heap())
{
@pool(allocator)
{
@@ -46,8 +39,7 @@ fn bool is_dir(Path path) => os::native_is_dir(path.str_view());
fn bool is_file(Path path) => os::native_is_file(path.str_view());
fn usz! file_size(Path path) => os::native_file_size(path.str_view());
fn bool exists(Path path) => os::native_file_or_dir_exists(path.str_view());
fn Path! temp_cwd() => new_cwd(allocator::temp()) @inline;
fn Path! tgetcwd() @deprecated("Use temp_cwd()") => new_cwd(allocator::temp()) @inline;
fn Path! tgetcwd() => getcwd(allocator::temp()) @inline;
fn void! chdir(Path path) => os::native_chdir(path) @inline;
fn Path! temp_directory(Allocator allocator = allocator::heap()) => os::native_temp_directory(allocator);
fn void! delete(Path path) => os::native_remove(path.str_view()) @inline;
@@ -67,17 +59,7 @@ macro bool is_win32_separator(char c)
return c == '/' || c == '\\';
}
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap()) @deprecated("use new_ls")
{
return new_ls(dir, no_dirs, no_symlinks, mask, allocator);
}
fn PathList! temp_ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "")
{
return new_ls(dir, no_dirs, no_symlinks, mask, allocator::temp()) @inline;
}
fn PathList! new_ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap())
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap())
{
$if $defined(os::native_ls):
return os::native_ls(dir, no_dirs, no_symlinks, mask, allocator);
@@ -93,13 +75,6 @@ enum MkdirPermissions
USER_AND_ADMIN
}
<*
Create a directory on a given path, optionally recursive.
@param path `The path to create`
@param recursive `If directories in between should be created if they're missing, defaults to false`
@param permissions `The permissions to set on the directory`
*>
fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
@@ -115,22 +90,12 @@ fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions =
return os::native_mkdir(path, permissions);
}
<*
Tries to delete directory, which must be empty.
@param path `The path to delete`
@return `true if there was a directory to delete, false otherwise`
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
fn bool! rmdir(Path path)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
return os::native_rmdir(path);
}
<*
Like [rmdir] but deletes a directory even if it contains items.
*>
fn void! rmtree(Path path)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
@@ -141,29 +106,22 @@ fn void! rmtree(Path path)
$endif
}
<*
Creates a new path.
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
fn Path! new(String path, Allocator allocator = allocator::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
{
return { normalize(path.copy(allocator), path_env), path_env };
}
<*
Creates a new path using the temp allocator.
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
fn Path! temp_new(String path, PathEnv path_env = DEFAULT_PATH_ENV)
{
return new(path, allocator::temp(), path_env);
}
fn Path! new_win32_wstring(WString path, Allocator allocator = allocator::heap()) => @pool(allocator)
fn Path! new_win32_wstring(WString path, Allocator allocator = allocator::heap())
{
return path::new(string::temp_from_wstring(path)!, allocator: allocator);
@pool(allocator)
{
return path::new(string::temp_from_wstring(path)!, .allocator = allocator);
};
}
fn Path! new_windows(String path, Allocator allocator = allocator::heap())
@@ -181,17 +139,12 @@ fn bool Path.equals(self, Path p2)
return self.env == p2.env && self.path_string == p2.path_string;
}
fn Path! Path.append(self, String filename, Allocator allocator = allocator::heap()) @deprecated("Use path.new_append(...)")
{
return self.new_append(filename, allocator) @inline;
}
<*
Append the string to the current path.
@param [in] filename
*>
fn Path! Path.new_append(self, String filename, Allocator allocator = allocator::heap())
/**
* Append the string to the current path.
*
* @param [in] filename
**/
fn Path! Path.append(self, String filename, Allocator allocator = allocator::heap())
{
if (!self.path_string.len) return new(filename, allocator, self.env)!;
assert(!is_separator(self.path_string[^1], self.env));
@@ -206,9 +159,7 @@ fn Path! Path.new_append(self, String filename, Allocator allocator = allocator:
};
}
fn Path! Path.temp_append(self, String filename) => self.new_append(filename, allocator::temp());
fn Path! Path.tappend(self, String filename) @deprecated("Use path.temp_append(...)") => self.new_append(filename, allocator::temp());
fn Path! Path.tappend(self, String filename) => self.append(filename, allocator::temp());
fn usz Path.start_of_base_name(self) @local
{
@@ -242,15 +193,10 @@ fn bool! Path.is_absolute(self)
return path_start < path_str.len && is_separator(path_str[path_start], self.env);
}
fn Path! Path.absolute(self, Allocator allocator = allocator::heap()) @deprecated("Use path.new_absolute()")
{
return self.new_absolute(allocator) @inline;
}
<*
@require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
*>
fn Path! Path.new_absolute(self, Allocator allocator = allocator::heap())
/**
* @require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
**/
fn Path! Path.absolute(self, Allocator allocator = allocator::heap())
{
String path_str = self.str_view();
if (!path_str.len) return PathResult.INVALID_PATH?;
@@ -274,7 +220,7 @@ fn Path! Path.new_absolute(self, Allocator allocator = allocator::heap())
};
$else
String cwd = os::getcwd(allocator::temp())!;
return (Path){ cwd, self.env }.new_append(path_str, allocator)!;
return Path { cwd, self.env }.append(path_str, allocator)!;
$endif
}
@@ -304,22 +250,6 @@ fn String Path.dirname(self)
return path_str[:basename_start - 1];
}
<*
Test if the path has the given extension, so given the path /foo/bar.c3
this would be true matching the extension "c3"
@param [in] extension `The extension name (not including the leading '.')`
@require extension.len > 0 : `The extension cannot be empty`
@return `true if the extension matches`
*>
fn bool Path.has_extension(self, String extension)
{
String basename = self.basename();
if (basename.len <= extension.len) return false;
if (basename[^extension.len + 1] != '.') return false;
return basename[^extension.len..] == extension;
}
fn String! Path.extension(self)
{
String basename = self.basename();
@@ -373,13 +303,6 @@ fn usz! volume_name_len(String path, PathEnv path_env) @local
}
}
<*
Get the path of the parent. This does not allocate, but returns a slice
of the path itself.
@return `The parent of the path as a non-allocated path`
@return! PathResult.NO_PARENT `if this path does not have a parent`
*>
fn Path! Path.parent(self)
{
if (self.path_string.len == 1 && is_separator(self.path_string[0], self.env)) return PathResult.NO_PARENT?;
@@ -395,7 +318,7 @@ fn Path! Path.parent(self)
fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
{
if (!path_str.len) return path_str;
if (!path_str.len) return "";
usz path_start = volume_name_len(path_str, path_env)!;
if (path_start > 0 && path_env == PathEnv.WIN32)
{
@@ -506,11 +429,7 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
if (len > path_start + 1 && is_separator(path_str[len - 1], path_env)) len--;
if (path_str.len > len) path_str.ptr[len] = 0;
// Empty path after normalization -> "."
if (!len)
{
path_str[0] = '.';
return path_str[:1];
}
if (!len) return ".";
return path_str[:len];
}
@@ -541,22 +460,22 @@ fn String Path.root_directory(self)
def PathWalker = fn bool! (Path, bool is_dir, void*);
<*
Walk the path recursively. PathWalker is run on every file and
directory found. Return true to abort the walk.
@require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
*>
/*
* Walk the path recursively. PathWalker is run on every file and
* directory found. Return true to abort the walk.
* @require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
*/
fn bool! Path.walk(self, PathWalker w, void* data)
{
const PATH_MAX = 512;
@stack_mem(PATH_MAX; Allocator allocator)
{
Path abs = self.new_absolute(allocator)!;
PathList files = new_ls(abs, allocator: allocator)!;
Path abs = self.absolute(allocator)!;
PathList files = ls(abs, .allocator = allocator)!;
foreach (f : files)
{
if (f.str_view() == "." || f.str_view() == "..") continue;
f = abs.new_append(f.str_view(), allocator)!;
f = abs.append(f.str_view(), allocator)!;
bool is_directory = is_dir(f);
if (w(f, is_directory, data)!) return true;
if (is_directory && f.walk(w, data)!) return true;

View File

@@ -47,28 +47,28 @@ macro bool @is_outstream(#expr)
return $assignable(#expr, OutStream);
}
<*
@param [&out] ref
@require @is_instream(stream)
*>
/**
* @param [&out] ref
* @require @is_instream(stream)
**/
macro usz! read_any(stream, any ref)
{
return read_all(stream, ((char*)ref)[:ref.type.sizeof]);
}
<*
@param [&in] ref "the object to write."
@require @is_outstream(stream)
@ensure return == ref.type.sizeof
*>
/**
* @param [&in] ref "the object to write."
* @require @is_outstream(stream)
* @ensure return == ref.type.sizeof
*/
macro usz! write_any(stream, any ref)
{
return write_all(stream, ((char*)ref)[:ref.type.sizeof]);
}
<*
@require @is_instream(stream)
*>
/**
* @require @is_instream(stream)
*/
macro usz! read_all(stream, char[] buffer)
{
if (buffer.len == 0) return 0;
@@ -77,41 +77,9 @@ macro usz! read_all(stream, char[] buffer)
return n;
}
<*
@require @is_instream(stream)
*>
macro char[]! read_new_fully(stream, Allocator allocator = allocator::heap()) @deprecated("Use read_fully(mem)")
{
usz len = available(stream)!;
char* data = allocator::malloc_try(allocator, len)!;
defer catch allocator::free(allocator, data);
usz read = 0;
while (read < len)
{
read += stream.read(data[read:len - read])!;
}
return data[:len];
}
<*
@require @is_instream(stream)
*>
macro char[]! read_fully(Allocator allocator, stream)
{
usz len = available(stream)!;
char* data = allocator::malloc_try(allocator, len)!;
defer catch allocator::free(allocator, data);
usz read = 0;
while (read < len)
{
read += stream.read(data[read:len - read])!;
}
return data[:len];
}
<*
@require @is_outstream(stream)
*>
/**
* @require @is_outstream(stream)
*/
macro usz! write_all(stream, char[] buffer)
{
if (buffer.len == 0) return 0;
@@ -120,12 +88,7 @@ macro usz! write_all(stream, char[] buffer)
return n;
}
macro usz! @read_using_read_byte(&s, char[] buffer) @deprecated
{
return read_using_read_byte(*s, buffer);
}
macro usz! read_using_read_byte(s, char[] buffer)
macro usz! @read_using_read_byte(&s, char[] buffer)
{
usz len = 0;
foreach (&cptr : buffer)
@@ -142,26 +105,17 @@ macro usz! read_using_read_byte(s, char[] buffer)
return len;
}
macro void! write_byte_using_write(s, char c)
macro void! @write_byte_using_write(&s, char c)
{
char[1] buff = { c };
s.write(&buff)!;
(*s).write(&buff)!;
}
macro void! @write_byte_using_write(&s, char c) @deprecated
{
return write_byte_using_write(*s, c);
}
macro char! @read_byte_using_read(&s) @deprecated
{
return read_byte_using_read(*s);
}
macro char! read_byte_using_read(s)
macro char! @read_byte_using_read(&s)
{
char[1] buffer;
usz read = s.read(&buffer)!;
usz read = (*s).read(&buffer)!;
if (read != 1) return IoError.EOF?;
return buffer[0];
}
@@ -169,23 +123,13 @@ macro char! read_byte_using_read(s)
def ReadByteFn = fn char!();
macro usz! write_using_write_byte(s, char[] bytes)
macro usz! @write_using_write_byte(&s, char[] bytes)
{
foreach (c : bytes) s.write_byte(self, c)!;
return bytes.len;
}
macro usz! @write_using_write_byte(&s, char[] bytes) @deprecated
{
return write_using_write_byte(*s, bytes);
}
macro void! pushback_using_seek(s)
{
s.seek(-1, CURSOR)!;
}
macro void! @pushback_using_seek(&s) @deprecated
macro void! @pushback_using_seek(&s)
{
s.seek(-1, CURSOR)!;
}
@@ -197,12 +141,18 @@ fn usz! copy_to(InStream in, OutStream dst, char[] buffer = {})
if (&dst.read_to) return dst.read_to(in);
$switch (env::MEMORY_ENV)
$case NORMAL:
return copy_through_buffer(in, dst, &&(char[4096]){});
@pool()
{
return copy_through_buffer(in, dst, mem::temp_alloc_array(char, 4096));
};
$case SMALL:
return copy_through_buffer(in, dst, &&(char[1024]){});
@pool()
{
return copy_through_buffer(in, dst, mem::temp_alloc_array(char, 1024));
};
$case TINY:
$case NONE:
return copy_through_buffer(in, dst, &&(char[256]){});
return copy_through_buffer(in, dst, &&(char[256]{}));
$endswitch
}
@@ -224,12 +174,12 @@ macro usz! copy_through_buffer(InStream in, OutStream dst, char[] buffer) @local
}
}
const char[?] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 };
const char[*] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 };
<*
@require @is_instream(stream)
@require @typekind(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
*>
/**
* @require @is_instream(stream)
* @require @typekind(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
**/
macro usz! read_varint(stream, x_ptr)
{
var $Type = $typefrom($typeof(x_ptr).inner);
@@ -263,10 +213,10 @@ macro usz! read_varint(stream, x_ptr)
}
return MathError.OVERFLOW?;
}
<*
@require @is_outstream(stream)
@require @typekind(x).is_int()
*>
/**
* @require @is_outstream(stream)
* @require @typekind(x).is_int()
**/
macro usz! write_varint(stream, x)
{
var $Type = $typeof(x);
@@ -281,205 +231,4 @@ macro usz! write_varint(stream, x)
}
buffer[i] = (char)x;
return write_all(stream, buffer[:i + 1]);
}
<*
@require @is_instream(stream)
*>
macro ushort! read_be_ushort(stream)
{
char hi_byte = stream.read_byte()!;
char lo_byte = stream.read_byte()!;
return (ushort)(hi_byte << 8 | lo_byte);
}
<*
@require @is_instream(stream)
*>
macro short! read_be_short(stream)
{
return read_be_ushort(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_short(stream, ushort s)
{
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_instream(stream)
*>
macro uint! read_be_uint(stream)
{
uint val = stream.read_byte()! << 24;
val += stream.read_byte()! << 16;
val += stream.read_byte()! << 8;
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro int! read_be_int(stream)
{
return read_be_uint(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_int(stream, uint s)
{
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_instream(stream)
*>
macro ulong! read_be_ulong(stream)
{
ulong val = (ulong)stream.read_byte()! << 56;
val += (ulong)stream.read_byte()! << 48;
val += (ulong)stream.read_byte()! << 40;
val += (ulong)stream.read_byte()! << 32;
val += (ulong)stream.read_byte()! << 24;
val += (ulong)stream.read_byte()! << 16;
val += (ulong)stream.read_byte()! << 8;
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro long! read_be_long(stream)
{
return read_be_ulong(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_long(stream, ulong s)
{
stream.write_byte((char)(s >> 56))!;
stream.write_byte((char)(s >> 48))!;
stream.write_byte((char)(s >> 40))!;
stream.write_byte((char)(s >> 32))!;
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_instream(stream)
*>
macro uint128! read_be_uint128(stream)
{
uint128 val = (uint128)stream.read_byte()! << 120;
val += (uint128)stream.read_byte()! << 112;
val += (uint128)stream.read_byte()! << 104;
val += (uint128)stream.read_byte()! << 96;
val += (uint128)stream.read_byte()! << 88;
val += (uint128)stream.read_byte()! << 80;
val += (uint128)stream.read_byte()! << 72;
val += (uint128)stream.read_byte()! << 64;
val += (uint128)stream.read_byte()! << 56;
val += (uint128)stream.read_byte()! << 48;
val += (uint128)stream.read_byte()! << 40;
val += (uint128)stream.read_byte()! << 32;
val += (uint128)stream.read_byte()! << 24;
val += (uint128)stream.read_byte()! << 16;
val += (uint128)stream.read_byte()! << 8;
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro int128! read_be_int128(stream)
{
return read_be_uint128(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_int128(stream, uint128 s)
{
stream.write_byte((char)(s >> 120))!;
stream.write_byte((char)(s >> 112))!;
stream.write_byte((char)(s >> 104))!;
stream.write_byte((char)(s >> 96))!;
stream.write_byte((char)(s >> 88))!;
stream.write_byte((char)(s >> 80))!;
stream.write_byte((char)(s >> 72))!;
stream.write_byte((char)(s >> 64))!;
stream.write_byte((char)(s >> 56))!;
stream.write_byte((char)(s >> 48))!;
stream.write_byte((char)(s >> 40))!;
stream.write_byte((char)(s >> 32))!;
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_outstream(stream)
@require data.len < 256 "Data exceeded 255"
*>
macro usz! write_tiny_bytearray(stream, char[] data)
{
stream.write_byte((char)data.len)!;
return stream.write(data) + 1;
}
<*
@require @is_instream(stream)
*>
macro char[]! read_tiny_bytearray(stream, Allocator allocator)
{
int len = stream.read_byte()!;
if (!len) return {};
char[] data = allocator::alloc_array(allocator, char, len);
io::read_all(stream, data)!;
return data;
}
<*
@require @is_outstream(stream)
@require data.len < 0x1000 "Data exceeded 65535"
*>
macro usz! write_short_bytearray(stream, char[] data)
{
io::write_be_short(stream, (ushort)data.len)!;
return stream.write(data) + 2;
}
<*
@require @is_instream(stream)
*>
macro char[]! read_short_bytearray(stream, Allocator allocator)
{
int len = io::read_be_ushort(stream)!;
if (!len) return {};
char[] data = allocator::alloc_array(allocator, char, len);
io::read_all(stream, data)!;
return data;
}
<*
Wrap bytes for reading using io functions.
*>
fn ByteReader wrap_bytes(char[] bytes)
{
return { bytes, 0 };
}

View File

@@ -8,12 +8,12 @@ struct ReadBuffer (InStream)
usz write_idx;
}
<*
Buffer reads from a stream.
@param [inout] self
@require bytes.len > 0
@require self.bytes.len == 0 "Init may not run on already initialized data"
*>
/**
* Buffer reads from a stream.
* @param [inout] self
* @require bytes.len > 0
* @require self.bytes.len == 0 "Init may not run on already initialized data"
**/
fn ReadBuffer* ReadBuffer.init(&self, InStream wrapped_stream, char[] bytes)
{
*self = { .wrapped_stream = wrapped_stream, .bytes = bytes };
@@ -68,12 +68,12 @@ struct WriteBuffer (OutStream)
usz index;
}
<*
Buffer writes to a stream. Call `flush` when done writing to the buffer.
@param [inout] self
@require bytes.len > 0 "Non-empty buffer required"
@require self.bytes.len == 0 "Init may not run on already initialized data"
*>
/**
* Buffer writes to a stream. Call `flush` when done writing to the buffer.
* @param [inout] self
* @require bytes.len > 0 "Non-empty buffer required"
* @require self.bytes.len == 0 "Init may not run on already initialized data"
**/
fn WriteBuffer* WriteBuffer.init(&self, OutStream wrapped_stream, char[] bytes)
{
*self = { .wrapped_stream = wrapped_stream, .bytes = bytes };
@@ -121,16 +121,13 @@ fn usz! WriteBuffer.write(&self, char[] bytes) @dynamic
fn void! WriteBuffer.write_byte(&self, char c) @dynamic
{
usz n = self.bytes.len - self.index;
if (n == 0)
{
self.write_pending()!;
}
self.bytes[self.index] = c;
self.index += 1;
if (n == 0) self.write_pending()!;
self.bytes[0] = c;
self.index = 1;
}
fn void! WriteBuffer.write_pending(&self) @local
{
self.index -= self.wrapped_stream.write(self.bytes[:self.index])!;
if (self.index != 0) return IoError.INCOMPLETE_WRITE?;
}
}

View File

@@ -11,47 +11,29 @@ struct ByteBuffer (InStream, OutStream)
bool has_last;
}
<*
ByteBuffer provides a streamable read/write buffer.
max_read defines how many bytes might be kept before its internal buffer is shrinked.
@require self.bytes.len == 0 "Buffer already initialized."
*>
fn ByteBuffer* ByteBuffer.init(&self, Allocator allocator, usz max_read, usz initial_capacity = 16)
/**
* ByteBuffer provides a streamable read/write buffer.
* max_read defines how many bytes might be kept before its internal buffer is shrinked.
* @require self.bytes.len == 0 "Buffer already initialized."
**/
fn ByteBuffer*! ByteBuffer.new_init(&self, usz max_read, usz initial_capacity = 16, Allocator allocator = allocator::heap())
{
*self = { .allocator = allocator, .max_read = max_read };
initial_capacity = max(initial_capacity, 16);
self.grow(initial_capacity);
self.grow(initial_capacity)!;
return self;
}
<*
ByteBuffer provides a streamable read/write buffer.
max_read defines how many bytes might be kept before its internal buffer is shrinked.
@require self.bytes.len == 0 "Buffer already initialized."
*>
fn ByteBuffer* ByteBuffer.new_init(&self, usz max_read, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
fn ByteBuffer*! ByteBuffer.temp_init(&self, usz max_read, usz initial_capacity = 16)
{
*self = { .allocator = allocator, .max_read = max_read };
initial_capacity = max(initial_capacity, 16);
self.grow(initial_capacity);
return self;
return self.new_init(max_read, initial_capacity, allocator::temp());
}
fn ByteBuffer* ByteBuffer.tinit(&self, usz max_read, usz initial_capacity = 16)
{
return self.init(allocator::temp(), max_read, initial_capacity);
}
fn ByteBuffer* ByteBuffer.temp_init(&self, usz max_read, usz initial_capacity = 16) @deprecated("Use tinit()")
{
return self.init(allocator::temp(), max_read, initial_capacity);
}
<*
@require buf.len > 0
@require self.bytes.len == 0 "Buffer already initialized."
*>
fn ByteBuffer* ByteBuffer.init_with_buffer(&self, char[] buf)
/**
* @require buf.len > 0
* @require self.bytes.len == 0 "Buffer already initialized."
**/
fn ByteBuffer*! ByteBuffer.init_with_buffer(&self, char[] buf)
{
*self = { .max_read = buf.len, .bytes = buf };
return self;
@@ -66,7 +48,7 @@ fn void ByteBuffer.free(&self)
fn usz! ByteBuffer.write(&self, char[] bytes) @dynamic
{
usz cap = self.bytes.len - self.write_idx;
if (cap < bytes.len) self.grow(bytes.len);
if (cap < bytes.len) self.grow(bytes.len)!;
self.bytes[self.write_idx:bytes.len] = bytes[..];
self.write_idx += bytes.len;
return bytes.len;
@@ -75,7 +57,7 @@ fn usz! ByteBuffer.write(&self, char[] bytes) @dynamic
fn void! ByteBuffer.write_byte(&self, char c) @dynamic
{
usz cap = self.bytes.len - self.write_idx;
if (cap == 0) self.grow(1);
if (cap == 0) self.grow(1)!;
self.bytes[self.write_idx] = c;
self.write_idx++;
}
@@ -111,9 +93,9 @@ fn char! ByteBuffer.read_byte(&self) @dynamic
return c;
}
<*
Only the last byte of a successful read can be pushed back.
*>
/*
* Only the last byte of a successful read can be pushed back.
*/
fn void! ByteBuffer.pushback_byte(&self) @dynamic
{
if (!self.has_last) return IoError.EOF?;
@@ -146,10 +128,10 @@ fn usz! ByteBuffer.available(&self) @inline @dynamic
return self.write_idx - self.read_idx;
}
fn void ByteBuffer.grow(&self, usz n)
fn void! ByteBuffer.grow(&self, usz n)
{
n = math::next_power_of_2(n);
char* p = allocator::realloc(self.allocator, self.bytes, n);
char* p = allocator::realloc_aligned(self.allocator, self.bytes, n, .alignment = char.alignof)!;
self.bytes = p[:n];
}

View File

@@ -8,48 +8,26 @@ struct ByteWriter (OutStream)
Allocator allocator;
}
<*
@param [&inout] self
@param [&inout] allocator
@require self.bytes.len == 0 "Init may not run on already initialized data"
@ensure (bool)allocator, self.index == 0
*>
fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
/**
* @param [&inout] self
* @param [&inout] allocator
* @require self.bytes.len == 0 "Init may not run on on already initialized data"
* @ensure (bool)allocator, self.index == 0
**/
fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap())
{
*self = { .bytes = {}, .allocator = allocator };
return self;
}
<*
@param [&inout] self
@param [&inout] allocator
@require self.bytes.len == 0 "Init may not run on already initialized data"
@ensure (bool)allocator, self.index == 0
*>
fn ByteWriter* ByteWriter.init(&self, Allocator allocator)
/**
* @param [&inout] self
* @require self.bytes.len == 0 "Init may not run on on already initialized data"
* @ensure self.index == 0
**/
fn ByteWriter* ByteWriter.temp_init(&self)
{
*self = { .bytes = {}, .allocator = allocator };
return self;
}
<*
@param [&inout] self
@require self.bytes.len == 0 "Init may not run on already initialized data"
@ensure self.index == 0
*>
fn ByteWriter* ByteWriter.tinit(&self)
{
return self.init(allocator::temp()) @inline;
}
<*
@param [&inout] self
@require self.bytes.len == 0 "Init may not run on already initialized data"
@ensure self.index == 0
*>
fn ByteWriter* ByteWriter.temp_init(&self) @deprecated("Use tinit")
{
return self.init(allocator::temp()) @inline;
return self.new_init(allocator::temp()) @inline;
}
fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
@@ -94,10 +72,10 @@ fn void! ByteWriter.write_byte(&self, char c) @dynamic
self.bytes[self.index++] = c;
}
<*
@param [&inout] self
@param reader
*>
/**
* @param [&inout] self
* @param reader
**/
fn usz! ByteWriter.read_from(&self, InStream reader) @dynamic
{
usz start_index = self.index;

View File

@@ -6,10 +6,10 @@ struct LimitReader (InStream)
usz limit;
}
<*
@param [&inout] wrapped_stream "The stream to read from"
@param limit "The max limit to read"
*>
/**
* @param [&inout] wrapped_stream "The stream to read from"
* @param limit "The max limit to read"
**/
fn LimitReader* LimitReader.init(&self, InStream wrapped_stream, usz limit)
{
*self = { .wrapped_stream = wrapped_stream, .limit = limit };

View File

@@ -1,94 +0,0 @@
module std::io;
/* MultiReader implements the InStream interface and provides a logical
* concatenation of the provided readers. They are read sequentially. If all the
* data has been read, IoError.EOF is returned.
*/
struct MultiReader (InStream)
{
InStream[] readers;
usz index;
Allocator allocator;
}
<*
@param [&inout] self
@param [&inout] allocator
@require self.readers.len == 0 "Init may not run on already initialized data"
@ensure self.index == 0
*>
fn MultiReader* MultiReader.new_init(&self, InStream... readers, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
{
InStream []copy = allocator::new_array(allocator, InStream, readers.len);
copy[..] = readers[..];
*self = { .readers = copy, .allocator = allocator };
return self;
}
<*
@param [&inout] self
@param [&inout] allocator
@require self.readers.len == 0 "Init may not run on already initialized data"
@ensure self.index == 0
*>
fn MultiReader* MultiReader.init(&self, Allocator allocator, InStream... readers)
{
InStream []copy = allocator::new_array(allocator, InStream, readers.len);
copy[..] = readers[..];
*self = { .readers = copy, .allocator = allocator };
return self;
}
<*
@param [&inout] self
@require self.readers.len == 0 "Init may not run on already initialized data"
@ensure self.index == 0
*>
fn MultiReader* MultiReader.temp_init(&self, InStream... readers) @deprecated("Use tinit()")
{
return self.init(allocator::temp(), ...readers);
}
<*
@param [&inout] self
@require self.readers.len == 0 "Init may not run on already initialized data"
@ensure self.index == 0
*>
fn MultiReader* MultiReader.tinit(&self, InStream... readers)
{
return self.init(allocator::temp(), ...readers);
}
fn void MultiReader.free(&self)
{
if (!self.allocator) return;
allocator::free(self.allocator, self.readers);
*self = {};
}
fn usz! MultiReader.read(&self, char[] bytes) @dynamic
{
InStream r = self.readers[self.index];
usz! n = r.read(bytes);
if (catch err = n)
{
case IoError.EOF:
self.index++;
if (self.index >= self.readers.len)
{
return IoError.EOF?;
}
return self.read(bytes);
default:
return err?;
}
return n;
}
fn char! MultiReader.read_byte(&self) @dynamic
{
char[1] data;
self.read(data[..])!;
return data[0];
}

View File

@@ -1,83 +0,0 @@
module std::io;
/* MultiWriter implements the OutStream interface and duplicates any write
* operation to all the wrapped writers.
*/
struct MultiWriter (OutStream)
{
OutStream[] writers;
Allocator allocator;
}
<*
@param [&inout] self
@param [&inout] allocator
@require writers.len > 0
@require self.writers.len == 0 "Init may not run on already initialized data"
*>
fn MultiWriter* MultiWriter.init(&self, Allocator allocator, OutStream... writers)
{
OutStream[] copy = allocator::new_array(allocator, OutStream, writers.len);
copy[..] = writers[..];
*self = { .writers = copy, .allocator = allocator };
return self;
}
<*
@param [&inout] self
@param [&inout] allocator
@require writers.len > 0
@require self.writers.len == 0 "Init may not run on already initialized data"
*>
fn MultiWriter* MultiWriter.new_init(&self, OutStream... writers, Allocator allocator = allocator::heap()) @deprecated("Use init(mem)")
{
OutStream[] copy = allocator::new_array(allocator, OutStream, writers.len);
copy[..] = writers[..];
*self = { .writers = copy, .allocator = allocator };
return self;
}
<*
@param [&inout] self
@require writers.len > 0
@require self.writers.len == 0 "Init may not run on already initialized data"
*>
fn MultiWriter* MultiWriter.temp_init(&self, OutStream... writers) @deprecated("Use tinit")
{
return self.init(allocator::temp(), ...writers);
}
<*
@param [&inout] self
@require writers.len > 0
@require self.writers.len == 0 "Init may not run on already initialized data"
*>
fn MultiWriter* MultiWriter.tinit(&self, OutStream... writers)
{
return self.init(allocator::temp(), ...writers);
}
fn void MultiWriter.free(&self)
{
if (!self.allocator) return;
allocator::free(self.allocator, self.writers);
*self = {};
}
fn usz! MultiWriter.write(&self, char[] bytes) @dynamic
{
usz n;
foreach (w : self.writers)
{
n = w.write(bytes)!;
if (n != bytes.len) return IoError.INCOMPLETE_WRITE?;
}
return bytes.len;
}
fn void! MultiWriter.write_byte(&self, char c) @dynamic
{
char[1] data;
data[0] = c;
self.write(data[..])!;
}

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