mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 20:11:17 +00:00
Compare commits
399 Commits
0
...
release_0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3255183ee4 | ||
|
|
66b65a042e | ||
|
|
b8db118e64 | ||
|
|
7c15cf2788 | ||
|
|
31538d5955 | ||
|
|
337eac6d2f | ||
|
|
e826f02da5 | ||
|
|
d5281b10dd | ||
|
|
87fdb5956e | ||
|
|
00019f9d76 | ||
|
|
f257befd86 | ||
|
|
07c27f3292 | ||
|
|
ffb0021d04 | ||
|
|
81c93e3488 | ||
|
|
587d5578ab | ||
|
|
9345e4270a | ||
|
|
1dde6092e5 | ||
|
|
5e8816e6df | ||
|
|
dc0aa35522 | ||
|
|
f39aa1a41e | ||
|
|
e31f2a03ba | ||
|
|
1e38ccdd2b | ||
|
|
69470b8738 | ||
|
|
5dedaa8e67 | ||
|
|
eab0b417de | ||
|
|
5e4cfacfcb | ||
|
|
120e21b80b | ||
|
|
cd7a03c2cf | ||
|
|
1aa038c92f | ||
|
|
e4c1328ef2 | ||
|
|
e17bb5f321 | ||
|
|
a0bc03a9f5 | ||
|
|
70e7e4b1d2 | ||
|
|
7d16d9acaf | ||
|
|
a7d032df21 | ||
|
|
9af37fe427 | ||
|
|
8a12dc5bd4 | ||
|
|
d01d8d3663 | ||
|
|
e380075852 | ||
|
|
7df5bc0017 | ||
|
|
9b61ddb876 | ||
|
|
76fa404b89 | ||
|
|
682dfd0e47 | ||
|
|
80a9842a25 | ||
|
|
89d4c2cab7 | ||
|
|
54f32ed71b | ||
|
|
fed343e3bb | ||
|
|
fd21b057eb | ||
|
|
e81e91be93 | ||
|
|
9b714e1dbb | ||
|
|
e67e17ef1e | ||
|
|
942d53a678 | ||
|
|
806d7e965f | ||
|
|
db3e9c7ec7 | ||
|
|
b657724d9b | ||
|
|
5a5b600490 | ||
|
|
1472d60c8a | ||
|
|
a9c28cce6d | ||
|
|
b7a896805d | ||
|
|
6b571fe427 | ||
|
|
3f77e868b1 | ||
|
|
31bc766944 | ||
|
|
ebddbfb416 | ||
|
|
3aa85cf641 | ||
|
|
312a39ee24 | ||
|
|
99cfaa1583 | ||
|
|
f3e3aa231d | ||
|
|
a1bce81ed0 | ||
|
|
dad21bfc6f | ||
|
|
9643a7c2b2 | ||
|
|
d16ad0b4c7 | ||
|
|
32f6d711ac | ||
|
|
a07ba63917 | ||
|
|
70f906c71a | ||
|
|
49c4595457 | ||
|
|
4cc30c0d33 | ||
|
|
757a5b58e8 | ||
|
|
2b9276b495 | ||
|
|
b2c7b713f2 | ||
|
|
99ec44ad78 | ||
|
|
db4298431d | ||
|
|
fc8b185b5b | ||
|
|
f3752d273c | ||
|
|
aa6101d8ea | ||
|
|
ee42992c37 | ||
|
|
c5404c6573 | ||
|
|
2a683a6a05 | ||
|
|
a1ecf2211f | ||
|
|
3675254af4 | ||
|
|
30d794653d | ||
|
|
709fe1c2c0 | ||
|
|
ad776c76a7 | ||
|
|
dde73e029c | ||
|
|
e5b990691e | ||
|
|
d6edd80f3b | ||
|
|
e706a8acd0 | ||
|
|
8dad8f2b1c | ||
|
|
c4228e08c5 | ||
|
|
c074e79069 | ||
|
|
6e0982327d | ||
|
|
a06cc76c9b | ||
|
|
9eef34049d | ||
|
|
e91cb85a66 | ||
|
|
05c2737f46 | ||
|
|
cbdc746c9d | ||
|
|
8d11794f83 | ||
|
|
8ed9be9c58 | ||
|
|
d49365b4a7 | ||
|
|
03345bef10 | ||
|
|
ff05128a87 | ||
|
|
f6e18ded5b | ||
|
|
d129cd49a5 | ||
|
|
9233305bd6 | ||
|
|
d6e9985a26 | ||
|
|
6be61aa19c | ||
|
|
4a232b7935 | ||
|
|
44fafdbd7c | ||
|
|
2eddda9061 | ||
|
|
b2ac4b4253 | ||
|
|
1d04b70efe | ||
|
|
d61482dffc | ||
|
|
37bb16cca1 | ||
|
|
ca1885fe09 | ||
|
|
d67e846712 | ||
|
|
dfe097931c | ||
|
|
4ef74a1205 | ||
|
|
b894e5be69 | ||
|
|
dc7f8057a3 | ||
|
|
66b436f7f8 | ||
|
|
224e38c6c7 | ||
|
|
51a72ccd37 | ||
|
|
40d5ce0937 | ||
|
|
51f76c69c4 | ||
|
|
b87e27d8a3 | ||
|
|
50e99b571f | ||
|
|
e3412da033 | ||
|
|
69418ba44d | ||
|
|
cfe5c649c5 | ||
|
|
f8fa9a057e | ||
|
|
5a2ef79fe6 | ||
|
|
74649ef672 | ||
|
|
e5f9cc26a8 | ||
|
|
6744a41644 | ||
|
|
ffb7935e12 | ||
|
|
53598b8c40 | ||
|
|
fe0ae4a9aa | ||
|
|
d1bb9c55ee | ||
|
|
29cc9ad8b1 | ||
|
|
4c081f59ff | ||
|
|
9a6d83f526 | ||
|
|
a6cff5c2a5 | ||
|
|
e56313a204 | ||
|
|
70b9e811bd | ||
|
|
46582af0ae | ||
|
|
0387816cb9 | ||
|
|
b6756b5b35 | ||
|
|
34e1f89ded | ||
|
|
0142e5fd33 | ||
|
|
cd950a0359 | ||
|
|
770115abd1 | ||
|
|
faf7782b1e | ||
|
|
82ba02a904 | ||
|
|
dd0dc1a936 | ||
|
|
eac19814e1 | ||
|
|
7aca8a02cb | ||
|
|
efb492eace | ||
|
|
79f964dce9 | ||
|
|
a23112fae6 | ||
|
|
092296984a | ||
|
|
565f511cc7 | ||
|
|
b8c92c69b0 | ||
|
|
6ebb3caa20 | ||
|
|
cc2c737357 | ||
|
|
69553fd80e | ||
|
|
190e1b19f1 | ||
|
|
c09b6154f4 | ||
|
|
120d5a672c | ||
|
|
307212a19c | ||
|
|
eedb2c3c52 | ||
|
|
b1f52cf8a9 | ||
|
|
bea9ac010c | ||
|
|
6ebd437a5f | ||
|
|
c0b109fbc1 | ||
|
|
b77f254ab1 | ||
|
|
f5fea69ef9 | ||
|
|
c63b3d4209 | ||
|
|
056ffa5876 | ||
|
|
0120498ec8 | ||
|
|
0b67f1a8e4 | ||
|
|
16e71c14b9 | ||
|
|
27445e6c1d | ||
|
|
fcb4bc0781 | ||
|
|
6c60b0d2a6 | ||
|
|
ed70f39da8 | ||
|
|
d5aebb434c | ||
|
|
17f69d8da8 | ||
|
|
c07dc700df | ||
|
|
957ce320ae | ||
|
|
7a39933c97 | ||
|
|
9b0da89a03 | ||
|
|
b05ba8d110 | ||
|
|
0448038c68 | ||
|
|
e694d60f23 | ||
|
|
8a4337e819 | ||
|
|
5bd21c10b6 | ||
|
|
87c9c29ee8 | ||
|
|
6c3d6a4b05 | ||
|
|
f39dd82adc | ||
|
|
f4ad9fcee0 | ||
|
|
65bea1cb2d | ||
|
|
f912e53038 | ||
|
|
3c8bbc2b90 | ||
|
|
e22afe5424 | ||
|
|
c060569599 | ||
|
|
d83f591184 | ||
|
|
b6f302d1c6 | ||
|
|
a846ab9cc0 | ||
|
|
f0d4c4d2ce | ||
|
|
3e765a3f3e | ||
|
|
356b6bb1b7 | ||
|
|
951a9f2b43 | ||
|
|
6d870fbef0 | ||
|
|
01a89e2145 | ||
|
|
6df6d2c084 | ||
|
|
2ca67d1489 | ||
|
|
eec6ce2210 | ||
|
|
c44a0528df | ||
|
|
5b5bc7fdbb | ||
|
|
68aadc958f | ||
|
|
91bb31856b | ||
|
|
a864822a89 | ||
|
|
a1a0958415 | ||
|
|
def97eea9d | ||
|
|
e4febe62ef | ||
|
|
fc0973f378 | ||
|
|
9b1c75d061 | ||
|
|
09bb7d3525 | ||
|
|
701d6a0746 | ||
|
|
a0df1fd728 | ||
|
|
6a3e618ffd | ||
|
|
d63bc10d74 | ||
|
|
4fd45700a2 | ||
|
|
f0c0efca8d | ||
|
|
72f5bac346 | ||
|
|
20699c1262 | ||
|
|
8a335fc64c | ||
|
|
8a9522a363 | ||
|
|
9315443866 | ||
|
|
95cb2cc28e | ||
|
|
151fc83815 | ||
|
|
b759abc954 | ||
|
|
6808a38c9f | ||
|
|
108b2244d8 | ||
|
|
283a95dea2 | ||
|
|
1219e8ba37 | ||
|
|
ada3ea08fc | ||
|
|
d0fa473d61 | ||
|
|
7b0408f79d | ||
|
|
c18526f10a | ||
|
|
499c82b089 | ||
|
|
a376d8e2bf | ||
|
|
59b077223b | ||
|
|
9c503cf6fd | ||
|
|
7954db9a89 | ||
|
|
3929e2057d | ||
|
|
242006d05d | ||
|
|
b74b62e242 | ||
|
|
d0c00b859b | ||
|
|
3e78a70552 | ||
|
|
fad0adfcd0 | ||
|
|
9e477056ed | ||
|
|
de9bb1d0cc | ||
|
|
45d1b1d671 | ||
|
|
afb902d792 | ||
|
|
5f1ebdcd28 | ||
|
|
e72ec2f605 | ||
|
|
d7d7fd3a10 | ||
|
|
7bccde72ed | ||
|
|
3f41e58dbd | ||
|
|
581ecdb2a8 | ||
|
|
c4f8d5f25e | ||
|
|
7fbedae604 | ||
|
|
b453186de5 | ||
|
|
49ea950f78 | ||
|
|
5aae9f3204 | ||
|
|
5c2e82fc8b | ||
|
|
5a2fe4c9d9 | ||
|
|
4dcfb7a675 | ||
|
|
491c5ceec5 | ||
|
|
2e6c8721bc | ||
|
|
fd5336c56e | ||
|
|
209d994336 | ||
|
|
aa216fa510 | ||
|
|
89e084938f | ||
|
|
11eb187fee | ||
|
|
8a4e6f7dd3 | ||
|
|
35bffdadc2 | ||
|
|
97e5dbf62c | ||
|
|
4325d017c8 | ||
|
|
34306cbf5d | ||
|
|
90d91b4891 | ||
|
|
ab32e0dc9d | ||
|
|
26ee4babcf | ||
|
|
c7d90baad1 | ||
|
|
c99f298cad | ||
|
|
fc316b1031 | ||
|
|
1ffe430df0 | ||
|
|
0efb142c88 | ||
|
|
dddeca1856 | ||
|
|
3b0370c8bb | ||
|
|
2437573a8f | ||
|
|
e2676a5c7f | ||
|
|
68af987c60 | ||
|
|
cd73b9bc42 | ||
|
|
943d010dfc | ||
|
|
38cc24af27 | ||
|
|
053f7880e5 | ||
|
|
9543fbbf1c | ||
|
|
8b605d9183 | ||
|
|
77b3214746 | ||
|
|
5f711408c0 | ||
|
|
d709c18f5f | ||
|
|
8780df8467 | ||
|
|
7dc1eab185 | ||
|
|
79e2d683b6 | ||
|
|
4f7b42cdc4 | ||
|
|
276281c3f9 | ||
|
|
b74de0b1e4 | ||
|
|
df9bc377dd | ||
|
|
97ded16ea2 | ||
|
|
731729cf1b | ||
|
|
daa952d990 | ||
|
|
f8a3e4f6f0 | ||
|
|
6231cc83d9 | ||
|
|
20c0bbc911 | ||
|
|
559dcffdf2 | ||
|
|
7ed0aeced2 | ||
|
|
c249c3f3b6 | ||
|
|
55d17ec990 | ||
|
|
bbbcd9bf48 | ||
|
|
c2c6f09d68 | ||
|
|
848a5212ef | ||
|
|
eaf45436f8 | ||
|
|
50784d4df6 | ||
|
|
21d8a8b6da | ||
|
|
45820d45e5 | ||
|
|
2ac213a3ce | ||
|
|
70ea6ce04b | ||
|
|
9102fc6032 | ||
|
|
378ea1deea | ||
|
|
f74e294dc2 | ||
|
|
57c8b5fc75 | ||
|
|
550b1f23ec | ||
|
|
f651a59294 | ||
|
|
fee80682b1 | ||
|
|
b88916214f | ||
|
|
685be0981f | ||
|
|
fc054dad81 | ||
|
|
83f8bbb91b | ||
|
|
0ec64c3be8 | ||
|
|
f878191e6f | ||
|
|
8c73a450a1 | ||
|
|
1dccd6af79 | ||
|
|
dcfbca076f | ||
|
|
b68b1e01b3 | ||
|
|
be04473af4 | ||
|
|
fedffc2f35 | ||
|
|
a187c55dfe | ||
|
|
55a1f794cf | ||
|
|
68f6cb1286 | ||
|
|
0ab0f727ad | ||
|
|
c46017f0dc | ||
|
|
1bd729a4bb | ||
|
|
0eee9daf1d | ||
|
|
d90fa5e292 | ||
|
|
503a4de277 | ||
|
|
ae9fca52ca | ||
|
|
eddae3b7f7 | ||
|
|
d5b01d3a8f | ||
|
|
ab93389031 | ||
|
|
5c9eb264e8 | ||
|
|
4d552ae44d | ||
|
|
3dd1741484 | ||
|
|
f9548cb213 | ||
|
|
c3da240bc0 | ||
|
|
f439539c6e | ||
|
|
57424d8b6b | ||
|
|
3bdeec3bc2 | ||
|
|
2d46bdf8e3 | ||
|
|
5f87cb4c4f | ||
|
|
76d75ac375 | ||
|
|
75a6ae7111 | ||
|
|
cf83651c79 | ||
|
|
4c1edfb941 | ||
|
|
82c3facb65 | ||
|
|
266dba466c | ||
|
|
379a5f670f | ||
|
|
8eaad81800 | ||
|
|
4baacc7d52 |
103
.github/workflows/main.yml
vendored
103
.github/workflows/main.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
run:
|
||||
shell: cmd
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: CMake
|
||||
run: |
|
||||
@@ -37,13 +37,20 @@ jobs:
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\time.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run 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 --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: |
|
||||
cd resources/testproject
|
||||
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log run hello_world_win32
|
||||
..\..\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_ir
|
||||
|
||||
|
||||
- name: Build testproject lib
|
||||
run: |
|
||||
@@ -62,7 +69,7 @@ jobs:
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -g1 --safe
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
|
||||
|
||||
- name: upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -72,6 +79,7 @@ jobs:
|
||||
|
||||
build-msys2-mingw:
|
||||
runs-on: windows-latest
|
||||
# if: ${{ false }}
|
||||
strategy:
|
||||
# Don't abort runners if a single one fails
|
||||
fail-fast: false
|
||||
@@ -82,20 +90,20 @@ jobs:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
update: true
|
||||
install: git binutils mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
|
||||
install: git binutils mingw-w64-x86_64-clang mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
|
||||
- shell: msys2 {0}
|
||||
run: |
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-15.0.3-1-any.pkg.tar.zst
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-15.0.3-1-any.pkg.tar.zst
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-17.0.4-1-any.pkg.tar.zst
|
||||
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-17.0.4-1-any.pkg.tar.zst
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake --build build
|
||||
|
||||
- name: Compile and run some examples
|
||||
@@ -106,6 +114,7 @@ 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 --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
@@ -140,7 +149,7 @@ jobs:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
@@ -161,7 +170,7 @@ 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 --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
|
||||
- name: Build testproject
|
||||
run: |
|
||||
cd resources/testproject
|
||||
@@ -184,10 +193,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
llvm_version: [16]
|
||||
llvm_version: [15, 16, 17, 18]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install common deps
|
||||
run: |
|
||||
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
|
||||
@@ -195,15 +204,23 @@ 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}}" < 17 ]]; then
|
||||
if [[ "${{matrix.llvm_version}}" < 16 ]]; then
|
||||
sudo apt remove libllvm15
|
||||
fi
|
||||
if [[ "${{matrix.llvm_version}}" < 18 ]]; 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
|
||||
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 libmlir-${{matrix.llvm_version}} \
|
||||
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
|
||||
fi
|
||||
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 libpolly-${{matrix.llvm_version}}-dev
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build \
|
||||
@@ -246,11 +263,15 @@ 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/process.c3
|
||||
../build/c3c compile-run examples/ls.c3
|
||||
../build/c3c compile-run --system-linker=no linux_stack.c3
|
||||
../build/c3c compile-run linux_stack.c3
|
||||
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit -g1 --safe
|
||||
../build/c3c compile-test unit
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
@@ -260,7 +281,7 @@ jobs:
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log --forcelinker
|
||||
../../build/c3c run --debug-log --system-linker=no
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
@@ -293,7 +314,7 @@ jobs:
|
||||
llvm_version: [16]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install common deps
|
||||
run: |
|
||||
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
|
||||
@@ -327,36 +348,37 @@ jobs:
|
||||
- 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 examples/levenshtein.c3
|
||||
../build/c3c compile examples/load_world.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
|
||||
../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/hash.c3
|
||||
../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/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/base64.c3
|
||||
../build/c3c compile-run examples/binarydigits.c3
|
||||
../build/c3c compile-run examples/brainfk.c3
|
||||
../build/c3c compile-run examples/factorial_macro.c3
|
||||
../build/c3c compile-run examples/fasta.c3
|
||||
../build/c3c compile-run examples/process.c3
|
||||
../build/c3c compile-run --system-linker=no linux_stack.c3
|
||||
../build/c3c compile-run linux_stack.c3
|
||||
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit -g1 --safe
|
||||
../build/c3c compile-test unit
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
@@ -366,7 +388,7 @@ jobs:
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log --forcelinker
|
||||
../../build/c3c run --debug-log --system-linker=no
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
@@ -398,7 +420,7 @@ jobs:
|
||||
build_type: [Release, Debug]
|
||||
llvm_version: [15, 16]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download LLVM
|
||||
run: |
|
||||
brew install llvm@${{ matrix.llvm_version }} ninja curl
|
||||
@@ -422,12 +444,13 @@ jobs:
|
||||
../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/process.c3
|
||||
../build/c3c compile-run examples/load_world.c3
|
||||
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit -g1 --safe
|
||||
../build/c3c compile-test unit
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
@@ -437,7 +460,7 @@ jobs:
|
||||
- name: Build testproject direct linker
|
||||
run: |
|
||||
cd resources/testproject
|
||||
../../build/c3c run --debug-log --forcelinker
|
||||
../../build/c3c run --debug-log --system-linker=no
|
||||
|
||||
- name: Build testproject lib
|
||||
run: |
|
||||
@@ -472,7 +495,7 @@ jobs:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: delete tag
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@v6
|
||||
|
||||
149
CMakeLists.txt
149
CMakeLists.txt
@@ -10,11 +10,20 @@ endif()
|
||||
project(c3c VERSION ${CMAKE_MATCH_1})
|
||||
message("C3C version: ${CMAKE_PROJECT_VERSION}")
|
||||
|
||||
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
|
||||
if (MSVC)
|
||||
set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib")
|
||||
set(CMAKE_INSTALL_BINDIR "c:\\c3c")
|
||||
else ()
|
||||
set(CMAKE_INSTALL_LIBDIR "/usr/local/lib/c3")
|
||||
set(CMAKE_INSTALL_BINDIR "/usr/local/bin/c3c")
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
# Enable fetching (for Windows)
|
||||
include(FetchContent)
|
||||
include(FeatureSummary)
|
||||
|
||||
|
||||
set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
|
||||
set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
|
||||
|
||||
@@ -25,19 +34,24 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
if(MSVC)
|
||||
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 /EHsc")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /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()
|
||||
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")
|
||||
if (true)
|
||||
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()
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined,address -fno-exceptions")
|
||||
endif()
|
||||
endif()
|
||||
#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O1 -fsanitize=undefined")
|
||||
#set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O1 -fsanitize=undefined")
|
||||
#set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined")
|
||||
#set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fsanitize=undefined")
|
||||
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined")
|
||||
|
||||
option(C3_LINK_DYNAMIC "link dynamically with LLVM/LLD libs")
|
||||
|
||||
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
|
||||
option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF)
|
||||
@@ -61,7 +75,7 @@ if (NOT WIN32)
|
||||
find_package(CURL)
|
||||
endif()
|
||||
if (NOT C3_LLVM_VERSION STREQUAL "auto")
|
||||
if (${C3_LLVM_VERSION} VERSION_LESS 15 OR ${C3_LLVM_VERSION} VERSION_GREATER 17)
|
||||
if (${C3_LLVM_VERSION} VERSION_LESS 15 OR ${C3_LLVM_VERSION} VERSION_GREATER 18)
|
||||
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
|
||||
endif()
|
||||
endif()
|
||||
@@ -115,54 +129,75 @@ 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(LLVM_ENABLE_RTTI)
|
||||
message(STATUS "LLVM was built with RTTI")
|
||||
else()
|
||||
message(STATUS "LLVM was not built with RTTI")
|
||||
endif()
|
||||
|
||||
include_directories(${LLVM_INCLUDE_DIRS})
|
||||
link_directories(${LLVM_LIBRARY_DIRS})
|
||||
add_definitions(${LLVM_DEFINITIONS})
|
||||
|
||||
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
|
||||
)
|
||||
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()
|
||||
|
||||
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
|
||||
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
|
||||
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
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
|
||||
@@ -201,17 +236,15 @@ add_executable(c3c
|
||||
src/build/project_creation.c
|
||||
src/compiler/ast.c
|
||||
src/compiler/bigint.c
|
||||
src/compiler/c_abi_internal.h
|
||||
src/compiler/codegen_general.c
|
||||
src/compiler/compiler.c
|
||||
src/compiler/compiler.h
|
||||
src/compiler/context.c
|
||||
src/compiler/copying.c
|
||||
src/compiler/diagnostics.c
|
||||
src/compiler/dwarf.h
|
||||
src/compiler/enums.h
|
||||
src/compiler/float.c
|
||||
src/compiler/headers.c
|
||||
src/compiler/json_output.c
|
||||
src/compiler/lexer.c
|
||||
src/compiler/libraries.c
|
||||
src/compiler/linker.c
|
||||
@@ -377,12 +410,10 @@ else()
|
||||
target_link_options(c3c PRIVATE -pthread)
|
||||
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
|
||||
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)
|
||||
if (WIN32)
|
||||
target_compile_definitions(c3c PRIVATE USE_PTHREAD=1)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
install(TARGETS c3c DESTINATION bin)
|
||||
install(DIRECTORY lib/ DESTINATION lib/c3)
|
||||
|
||||
feature_summary(WHAT ALL)
|
||||
|
||||
65
README.md
65
README.md
@@ -55,7 +55,7 @@ fn void Stack.push(Stack* this, Type element)
|
||||
{
|
||||
this.capacity *= 2;
|
||||
if (this.capacity < 16) this.capacity = 16;
|
||||
this.elems = mem::realloc(this.elems, Type.sizeof * this.capacity);
|
||||
this.elems = realloc(this.elems, Type.sizeof * this.capacity);
|
||||
}
|
||||
this.elems[this.size++] = element;
|
||||
}
|
||||
@@ -79,13 +79,13 @@ import stack;
|
||||
|
||||
// Define our new types, the first will implicitly create
|
||||
// a complete copy of the entire Stack module with "Type" set to "int"
|
||||
def IntStack = Stack<int>;
|
||||
def IntStack = Stack(<int>);
|
||||
// The second creates another copy with "Type" set to "double"
|
||||
def DoubleStack = Stack<double>;
|
||||
def DoubleStack = Stack(<double>);
|
||||
|
||||
// If we had added "define IntStack2 = Stack<int>"
|
||||
// If we had added "define IntStack2 = Stack(<int>)"
|
||||
// no additional copy would have been made (since we already
|
||||
// have an parameterization of Stack<int>) so it would
|
||||
// have an parameterization of Stack(<int>)) so it would
|
||||
// be same as declaring IntStack2 an alias of IntStack
|
||||
|
||||
// Importing an external C function is straightforward
|
||||
@@ -131,17 +131,15 @@ fn void main()
|
||||
- Value methods
|
||||
- Associated enum data
|
||||
- No preprocessor
|
||||
- Less undefined behaviour and runtime checks in "safe" mode
|
||||
- Less undefined behaviour and added runtime checks in "safe" mode
|
||||
- Limited operator overloading to enable userland dynamic arrays
|
||||
- Optional pre and post conditions
|
||||
|
||||
### Current status
|
||||
|
||||
The current version of the compiler is alpha release 0.4.
|
||||
The current stable version of the compiler is **version 0.5**.
|
||||
|
||||
Design work on C3 is complete aside from fleshing out details, such as
|
||||
inline asm. As the standard library work progresses, changes and improvements
|
||||
to the language will happen continuously.
|
||||
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)
|
||||
@@ -149,7 +147,38 @@ or discuss C3 on its dedicated Discord: [https://discord.gg/qN76R87](https://dis
|
||||
|
||||
The compiler is currently verified to compile on Linux, Windows and MacOS.
|
||||
|
||||
**Support matrix**
|
||||
|
||||
| Platform | Native C3 compiler available? | Target supported | Stack trace | Threads | Sockets | Inline asm |
|
||||
|--------------------------|-------------------------------|-------------------------|-------------|----------|----------|------------|
|
||||
| Win32 x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
|
||||
| Win32 Aarch64 | Untested | Untested | Untested | Untested | Untested | Yes* |
|
||||
| MacOS x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
|
||||
| MacOS Aarch64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
|
||||
| iOS Aarch64 | No | Untested | Untested | Yes | Yes | Yes* |
|
||||
| Linux x86 | Yes | Yes | Yes | Yes | Yes | Yes* |
|
||||
| Linux x64 | Yes | Yes | Yes | Yes | Yes | Yes* |
|
||||
| Linux Aarch64 | Yes | Yes | Yes | Yes | Yes | Yes* |
|
||||
| Linux Riscv32 | Yes | Yes | Yes | Yes | Yes | Untested |
|
||||
| Linux Riscv64 | Yes | Yes | Yes | Yes | Yes | Untested |
|
||||
| ELF freestanding x86 | No | Untested | No | No | No | Yes* |
|
||||
| ELF freestanding x64 | No | Untested | No | No | No | Yes* |
|
||||
| ELF freestanding Aarch64 | No | Untested | No | No | No | Yes* |
|
||||
| ELF freestanding Riscv64 | No | Untested | No | No | No | Untested |
|
||||
| ELF freestanding Riscv32 | No | Untested | No | No | No | Untested |
|
||||
| FreeBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| FreeBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| NetBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| NetBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| OpenBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| OpenBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
|
||||
| MCU x86 | No | Untested | No | No | No | Yes* |
|
||||
| Wasm32 | No | Yes | No | No | No | No |
|
||||
| Wasm64 | No | Untested | No | No | No | No |
|
||||
|
||||
*\* Inline asm is still a work in progress*
|
||||
|
||||
More platforms will be supported in the future.
|
||||
|
||||
#### What can you help with?
|
||||
|
||||
@@ -184,8 +213,18 @@ The compiler is currently verified to compile on Linux, Windows and MacOS.
|
||||
4. Run `./c3c`.
|
||||
|
||||
#### Installing on Arch Linux
|
||||
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git)
|
||||
You can use your AUR package manager or clone it manually:
|
||||
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
|
||||
|
||||
Due to some issues with the LLVM packaged for Arch Linux, the AUR package will download and use LLVM 16 for Ubuntu-23.04 to compile the c3c compiler.
|
||||
|
||||
You can use your AUR package manager:
|
||||
```sh
|
||||
paru -S c3c-git
|
||||
# or yay -S c3c-git
|
||||
# or aura -A c3c-git
|
||||
```
|
||||
|
||||
Or clone it manually:
|
||||
```sh
|
||||
git clone https://aur.archlinux.org/c3c-git.git
|
||||
cd c3c-git
|
||||
@@ -310,6 +349,6 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
|
||||
1. Write the test, either adding to existing test files in `/test/unit/` or add
|
||||
a new file. (If testing the standard library, put it in the `/test/unit/stdlib/` subdirectory).
|
||||
2. Make sure that the test functions have the `@test` attribute.
|
||||
3. Run tests and see that they pass. (Recommended settings: `c3c compile-test --safe -g1 -O0 test/unit`.
|
||||
3. Run tests and see that they pass. (Recommended settings: `c3c compile-test -O0 test/unit`.
|
||||
- in this example `test/unit/` is the relative path to the test directory, so adjust as required)
|
||||
4. Make a pull request for the new tests.
|
||||
|
||||
@@ -21,7 +21,6 @@ if type podman 2>/dev/null >/dev/null; then
|
||||
fi
|
||||
|
||||
if [ $config == "Debug" ]; then
|
||||
echo "debug???"
|
||||
CMAKE_BUILD_TYPE=Debug
|
||||
else
|
||||
CMAKE_BUILD_TYPE="$config"
|
||||
|
||||
@@ -53,6 +53,10 @@ fn bool char.is_blank(char c) => is_blank_m(c);
|
||||
fn bool char.is_cntrl(char c) => is_cntrl_m(c);
|
||||
fn char char.to_lower(char c) => (char)to_lower_m(c);
|
||||
fn char char.to_upper(char c) => (char)to_upper_m(c);
|
||||
/**
|
||||
* @require c.is_xdigit()
|
||||
**/
|
||||
fn char char.from_hex(char c) => c.is_digit() ? c - '0' : 10 + (c | 0x20) - 'a';
|
||||
|
||||
fn bool uint.in_range(uint c, uint start, uint len) => in_range_m(c, start, len);
|
||||
fn bool uint.is_lower(uint c) => is_lower_m(c);
|
||||
|
||||
@@ -1,5 +1,520 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::atomic;
|
||||
module std::atomic::types(<Type>);
|
||||
|
||||
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"
|
||||
**/
|
||||
macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
switch(ordering)
|
||||
{
|
||||
case NOT_ATOMIC: return $$atomic_load(data, false, AtomicOrdering.NOT_ATOMIC.ordinal);
|
||||
case UNORDERED: return $$atomic_load(data, false, AtomicOrdering.UNORDERED.ordinal);
|
||||
case RELAXED: return $$atomic_load(data, false, AtomicOrdering.RELAXED.ordinal);
|
||||
case ACQUIRE: return $$atomic_load(data, false, AtomicOrdering.ACQUIRE.ordinal);
|
||||
case SEQ_CONSISTENT: return $$atomic_load(data, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
|
||||
case ACQUIRE_RELEASE:
|
||||
case RELEASE: unreachable("Invalid ordering.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
switch(ordering)
|
||||
{
|
||||
case NOT_ATOMIC: $$atomic_store(data, value, false, AtomicOrdering.NOT_ATOMIC.ordinal);
|
||||
case UNORDERED: $$atomic_store(data, value, false, AtomicOrdering.UNORDERED.ordinal);
|
||||
case RELAXED: $$atomic_store(data, value, false, AtomicOrdering.RELAXED.ordinal);
|
||||
case RELEASE: $$atomic_store(data, value, false, AtomicOrdering.RELEASE.ordinal);
|
||||
case SEQ_CONSISTENT: $$atomic_store(data, value, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
|
||||
case ACQUIRE_RELEASE:
|
||||
case ACQUIRE: unreachable("Invalid ordering.");
|
||||
}
|
||||
}
|
||||
|
||||
macro Type Atomic.add(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_add, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.sub(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_sub, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.mul(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_mul, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_div, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_div, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_min, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.or(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_or, data, value, ordering);
|
||||
}
|
||||
|
||||
fn Type Atomic.xor(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.and(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_and, data, value, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.shift_right(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
|
||||
}
|
||||
|
||||
macro Type Atomic.shift_left(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
|
||||
{
|
||||
Type* data = &self.data;
|
||||
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
|
||||
}
|
||||
|
||||
macro @atomic_exec(#func, data, value, ordering) @local
|
||||
{
|
||||
switch(ordering)
|
||||
{
|
||||
case RELAXED: return #func(data, value, RELAXED);
|
||||
case ACQUIRE: return #func(data, value, ACQUIRE);
|
||||
case RELEASE: return #func(data, value, RELEASE);
|
||||
case ACQUIRE_RELEASE: return #func(data, value, ACQUIRE_RELEASE);
|
||||
case SEQ_CONSISTENT: return #func(data, value, SEQ_CONSISTENT);
|
||||
default: assert(false, "Ordering may not be non-atomic or unordered.");
|
||||
}
|
||||
}
|
||||
|
||||
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."
|
||||
**/
|
||||
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
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."
|
||||
**/
|
||||
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
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."
|
||||
**/
|
||||
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = old_value * y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
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."
|
||||
**/
|
||||
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = old_value / y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
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."
|
||||
**/
|
||||
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value | storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
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."
|
||||
**/
|
||||
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value ^ storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
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."
|
||||
**/
|
||||
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if types::is_int($typeof(*ptr)):
|
||||
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
$endif
|
||||
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value & storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
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."
|
||||
**/
|
||||
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value >> storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
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."
|
||||
**/
|
||||
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
var $load_ordering = $ordering;
|
||||
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
|
||||
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
|
||||
$endif
|
||||
|
||||
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
|
||||
|
||||
$StorageType* storage_ptr = ($StorageType*)ptr;
|
||||
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value;
|
||||
|
||||
$StorageType storage_old_value;
|
||||
$StorageType storage_new_value;
|
||||
$StorageType storage_y = ($StorageType)y;
|
||||
|
||||
do {
|
||||
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
|
||||
old_value = bitcast(storage_old_value, $typeof(*ptr));
|
||||
new_value = storage_old_value << storage_y;
|
||||
storage_new_value = bitcast(new_value, $StorageType);
|
||||
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
|
||||
|
||||
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."
|
||||
**/
|
||||
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value = true;
|
||||
|
||||
do {
|
||||
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
|
||||
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
|
||||
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."
|
||||
**/
|
||||
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
|
||||
{
|
||||
$typeof(*ptr) old_value;
|
||||
$typeof(*ptr) new_value = false;
|
||||
|
||||
do {
|
||||
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
|
||||
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
|
||||
|
||||
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."
|
||||
**/
|
||||
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
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."
|
||||
**/
|
||||
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
|
||||
{
|
||||
$if $alignment == 0:
|
||||
$alignment = $typeof(*ptr).sizeof;
|
||||
$endif
|
||||
return $$atomic_fetch_min(ptr, y, $volatile, $ordering.ordinal, $alignment);
|
||||
}
|
||||
|
||||
63
lib/std/atomic_nolibc.c3
Normal file
63
lib/std/atomic_nolibc.c3
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::atomic;
|
||||
|
||||
macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment) {
|
||||
switch(failure)
|
||||
{
|
||||
case AtomicOrdering.RELAXED.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.RELAXED.ordinal, $alignment);
|
||||
case AtomicOrdering.ACQUIRE.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.ACQUIRE.ordinal, $alignment);
|
||||
case AtomicOrdering.SEQ_CONSISTENT.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.SEQ_CONSISTENT.ordinal, $alignment);
|
||||
default: assert(false, "Unrecognized failure ordering");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, success, failure, $alignment)
|
||||
{
|
||||
switch(success)
|
||||
{
|
||||
case AtomicOrdering.RELAXED.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELAXED.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.ACQUIRE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELEASE.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.ACQUIRE_RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE_RELEASE.ordinal, failure, $alignment);
|
||||
case AtomicOrdering.SEQ_CONSISTENT.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.SEQ_CONSISTENT.ordinal, failure, $alignment);
|
||||
default: assert(false, "Unrecognized success ordering");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn CInt __atomic_compare_exchange(CInt size, any* ptr, any* expected, any* desired, CInt success, CInt failure) @extern("__atomic_compare_exchange") @export
|
||||
{
|
||||
switch (size)
|
||||
{
|
||||
case 1:
|
||||
char* pt = (char*)ptr;
|
||||
char ex = *(char*)expected;
|
||||
char de = *(char*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 1)) return 1;
|
||||
case 2:
|
||||
short* pt = (short*)ptr;
|
||||
short ex = *(short*)expected;
|
||||
short de = *(short*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 2)) return 1;
|
||||
case 4:
|
||||
int* pt = (int*)ptr;
|
||||
int ex = *(int*)expected;
|
||||
int de = *(int*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 4)) return 1;
|
||||
case 8:
|
||||
$if iptr.sizeof >= 8:
|
||||
long* pt = (long*)ptr;
|
||||
long ex = *(long*)expected;
|
||||
long de = *(long*)desired;
|
||||
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 8)) return 1;
|
||||
$else
|
||||
nextcase;
|
||||
$endif
|
||||
default:
|
||||
assert(false, "Unsuported size (%d) for atomic_compare_exchange", size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
282
lib/std/bits.c3
282
lib/std/bits.c3
@@ -1,6 +1,5 @@
|
||||
module std::bits;
|
||||
|
||||
|
||||
/**
|
||||
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
|
||||
**/
|
||||
@@ -11,163 +10,162 @@ macro reverse(i) => $$bitreverse(i);
|
||||
**/
|
||||
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(uint[<*>] i) => $$popcount(i);
|
||||
macro uint[<*>].ctz(uint[<*>] i) => $$ctz(i);
|
||||
macro uint[<*>].clz(uint[<*>] i) => $$clz(i);
|
||||
macro uint[<*>] uint[<*>].fshl(uint[<*>] hi, uint[<*>] lo, uint[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro uint[<*>] uint[<*>].fshr(uint[<*>] hi, uint[<*>] lo, uint[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro uint[<*>] uint[<*>].rotl(uint[<*>] i, uint[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro uint[<*>] uint[<*>].rotr(uint[<*>] i, uint[<*>] shift) => $$fshr(i, i, 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(int[<*>] i) => $$popcount(i);
|
||||
macro int[<*>].ctz(int[<*>] i) => $$ctz(i);
|
||||
macro int[<*>].clz(int[<*>] i) => $$clz(i);
|
||||
macro int[<*>] int[<*>].fshl(int[<*>] hi, int[<*>] lo, int[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro int[<*>] int[<*>].fshr(int[<*>] hi, int[<*>] lo, int[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro int[<*>] int[<*>].rotl(int[<*>] i, int[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro int[<*>] int[<*>].rotr(int[<*>] i, int[<*>] shift) => $$fshr(i, i, 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(ushort[<*>] i) => $$popcount(i);
|
||||
macro ushort[<*>].ctz(ushort[<*>] i) => $$ctz(i);
|
||||
macro ushort[<*>].clz(ushort[<*>] i) => $$clz(i);
|
||||
macro ushort[<*>] ushort[<*>].fshl(ushort[<*>] hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ushort[<*>] ushort[<*>].fshr(ushort[<*>] hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ushort[<*>] ushort[<*>].rotl(ushort[<*>] i, ushort[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro ushort[<*>] ushort[<*>].rotr(ushort[<*>] i, ushort[<*>] shift) => $$fshr(i, i, 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(short[<*>] i) => $$popcount(i);
|
||||
macro short[<*>].ctz(short[<*>] i) => $$ctz(i);
|
||||
macro short[<*>].clz(short[<*>] i) => $$clz(i);
|
||||
macro short[<*>] short[<*>].fshl(short[<*>] hi, short[<*>] lo, short[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro short[<*>] short[<*>].fshr(short[<*>] hi, short[<*>] lo, short[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro short[<*>] short[<*>].rotl(short[<*>] i, short[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro short[<*>] short[<*>].rotr(short[<*>] i, short[<*>] shift) => $$fshr(i, i, 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(char[<*>] i) => $$popcount(i);
|
||||
macro char[<*>].ctz(char[<*>] i) => $$ctz(i);
|
||||
macro char[<*>].clz(char[<*>] i) => $$clz(i);
|
||||
macro char[<*>] char[<*>].fshl(char[<*>] hi, char[<*>] lo, char[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro char[<*>] char[<*>].fshr(char[<*>] hi, char[<*>] lo, char[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro char[<*>] char[<*>].rotl(char[<*>] i, char[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro char[<*>] char[<*>].rotr(char[<*>] i, char[<*>] shift) => $$fshr(i, i, 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(ichar[<*>] i) => $$popcount(i);
|
||||
macro ichar[<*>].ctz(ichar[<*>] i) => $$ctz(i);
|
||||
macro ichar[<*>].clz(ichar[<*>] i) => $$clz(i);
|
||||
macro ichar[<*>] ichar[<*>].fshl(ichar[<*>] hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ichar[<*>] ichar[<*>].fshr(ichar[<*>] hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ichar[<*>] ichar[<*>].rotl(ichar[<*>] i, ichar[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro ichar[<*>] ichar[<*>].rotr(ichar[<*>] i, ichar[<*>] shift) => $$fshr(i, i, 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(ulong[<*>] i) => $$popcount(i);
|
||||
macro ulong[<*>].ctz(ulong[<*>] i) => $$ctz(i);
|
||||
macro ulong[<*>].clz(ulong[<*>] i) => $$clz(i);
|
||||
macro ulong[<*>] ulong[<*>].fshl(ulong[<*>] hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro ulong[<*>] ulong[<*>].fshr(ulong[<*>] hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro ulong[<*>] ulong[<*>].rotl(ulong[<*>] i, ulong[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro ulong[<*>] ulong[<*>].rotr(ulong[<*>] i, ulong[<*>] shift) => $$fshr(i, i, 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(long[<*>] i) => $$popcount(i);
|
||||
macro long[<*>].ctz(long[<*>] i) => $$ctz(i);
|
||||
macro long[<*>].clz(long[<*>] i) => $$clz(i);
|
||||
macro long[<*>] long[<*>].fshl(long[<*>] hi, long[<*>] lo, long[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro long[<*>] long[<*>].fshr(long[<*>] hi, long[<*>] lo, long[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro long[<*>] long[<*>].rotl(long[<*>] i, long[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro long[<*>] long[<*>].rotr(long[<*>] i, long[<*>] shift) => $$fshr(i, i, 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(uint128[<*>] i) => $$popcount(i);
|
||||
macro uint128[<*>].ctz(uint128[<*>] i) => $$ctz(i);
|
||||
macro uint128[<*>].clz(uint128[<*>] i) => $$clz(i);
|
||||
macro uint128[<*>] uint128[<*>].fshl(uint128[<*>] hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro uint128[<*>] uint128[<*>].fshr(uint128[<*>] hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro uint128[<*>] uint128[<*>].rotl(uint128[<*>] i, uint128[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro uint128[<*>] uint128[<*>].rotr(uint128[<*>] i, uint128[<*>] shift) => $$fshr(i, i, 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(int128[<*>] i) => $$popcount(i);
|
||||
macro int128[<*>].ctz(int128[<*>] i) => $$ctz(i);
|
||||
macro int128[<*>].clz(int128[<*>] i) => $$clz(i);
|
||||
macro int128[<*>] int128[<*>].fshl(int128[<*>] hi, int128[<*>] lo, int128[<*>] shift) => $$fshl(hi, lo, shift);
|
||||
macro int128[<*>] int128[<*>].fshr(int128[<*>] hi, int128[<*>] lo, int128[<*>] shift) => $$fshr(hi, lo, shift);
|
||||
macro int128[<*>] int128[<*>].rotl(int128[<*>] i, int128[<*>] shift) => $$fshl(i, i, shift);
|
||||
macro int128[<*>] int128[<*>].rotr(int128[<*>] i, int128[<*>] shift) => $$fshr(i, i, 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 uint.popcount(uint i) => $$popcount(i);
|
||||
macro uint.ctz(uint i) => $$ctz(i);
|
||||
macro uint.clz(uint i) => $$clz(i);
|
||||
macro uint uint.fshl(uint hi, uint lo, uint shift) => $$fshl(hi, lo, shift);
|
||||
macro uint uint.fshr(uint hi, uint lo, uint shift) => $$fshr(hi, lo, shift);
|
||||
macro uint uint.rotl(uint i, uint shift) => $$fshl(i, i, shift);
|
||||
macro uint uint.rotr(uint i, uint shift) => $$fshr(i, i, 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(int i) => $$popcount(i);
|
||||
macro int.ctz(int i) => $$ctz(i);
|
||||
macro int.clz(int i) => $$clz(i);
|
||||
macro int int.fshl(int hi, int lo, int shift) => $$fshl(hi, lo, shift);
|
||||
macro int int.fshr(int hi, int lo, int shift) => $$fshr(hi, lo, shift);
|
||||
macro int int.rotl(int i, int shift) => $$fshl(i, i, shift);
|
||||
macro int int.rotr(int i, int shift) => $$fshr(i, i, 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(ushort i) => $$popcount(i);
|
||||
macro ushort.ctz(ushort i) => $$ctz(i);
|
||||
macro ushort.clz(ushort i) => $$clz(i);
|
||||
macro ushort ushort.fshl(ushort hi, ushort lo, ushort shift) => $$fshl(hi, lo, shift);
|
||||
macro ushort ushort.fshr(ushort hi, ushort lo, ushort shift) => $$fshr(hi, lo, shift);
|
||||
macro ushort ushort.rotl(ushort i, ushort shift) => $$fshl(i, i, shift);
|
||||
macro ushort ushort.rotr(ushort i, ushort shift) => $$fshr(i, i, 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(short i) => $$popcount(i);
|
||||
macro short.ctz(short i) => $$ctz(i);
|
||||
macro short.clz(short i) => $$clz(i);
|
||||
macro short short.fshl(short hi, short lo, short shift) => $$fshl(hi, lo, shift);
|
||||
macro short short.fshr(short hi, short lo, short shift) => $$fshr(hi, lo, shift);
|
||||
macro short short.rotl(short i, short shift) => $$fshl(i, i, shift);
|
||||
macro short short.rotr(short i, short shift) => $$fshr(i, i, 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(char i) => $$popcount(i);
|
||||
macro char.ctz(char i) => $$ctz(i);
|
||||
macro char.clz(char i) => $$clz(i);
|
||||
macro char char.fshl(char hi, char lo, char shift) => $$fshl(hi, lo, shift);
|
||||
macro char char.fshr(char hi, char lo, char shift) => $$fshr(hi, lo, shift);
|
||||
macro char char.rotl(char i, char shift) => $$fshl(i, i, shift);
|
||||
macro char char.rotr(char i, char shift) => $$fshr(i, i, 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(ichar i) => $$popcount(i);
|
||||
macro ichar.ctz(ichar i) => $$ctz(i);
|
||||
macro ichar.clz(ichar i) => $$clz(i);
|
||||
macro ichar ichar.fshl(ichar hi, ichar lo, ichar shift) => $$fshl(hi, lo, shift);
|
||||
macro ichar ichar.fshr(ichar hi, ichar lo, ichar shift) => $$fshr(hi, lo, shift);
|
||||
macro ichar ichar.rotl(ichar i, ichar shift) => $$fshl(i, i, shift);
|
||||
macro ichar ichar.rotr(ichar i, ichar shift) => $$fshr(i, i, 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(ulong i) => $$popcount(i);
|
||||
macro ulong.ctz(ulong i) => $$ctz(i);
|
||||
macro ulong.clz(ulong i) => $$clz(i);
|
||||
macro ulong ulong.fshl(ulong hi, ulong lo, ulong shift) => $$fshl(hi, lo, shift);
|
||||
macro ulong ulong.fshr(ulong hi, ulong lo, ulong shift) => $$fshr(hi, lo, shift);
|
||||
macro ulong ulong.rotl(ulong i, ulong shift) => $$fshl(i, i, shift);
|
||||
macro ulong ulong.rotr(ulong i, ulong shift) => $$fshr(i, i, 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(long i) => $$popcount(i);
|
||||
macro long.ctz(long i) => $$ctz(i);
|
||||
macro long.clz(long i) => $$clz(i);
|
||||
macro long long.fshl(long hi, long lo, long shift) => $$fshl(hi, lo, shift);
|
||||
macro long long.fshr(long hi, long lo, long shift) => $$fshr(hi, lo, shift);
|
||||
macro long long.rotl(long i, long shift) => $$fshl(i, i, shift);
|
||||
macro long long.rotr(long i, long shift) => $$fshr(i, i, 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(uint128 i) => $$popcount(i);
|
||||
macro uint128.ctz(uint128 i) => $$ctz(i);
|
||||
macro uint128.clz(uint128 i) => $$clz(i);
|
||||
macro uint128 uint128.fshl(uint128 hi, uint128 lo, uint128 shift) => $$fshl(hi, lo, shift);
|
||||
macro uint128 uint128.fshr(uint128 hi, uint128 lo, uint128 shift) => $$fshr(hi, lo, shift);
|
||||
macro uint128 uint128.rotl(uint128 i, uint128 shift) => $$fshl(i, i, shift);
|
||||
macro uint128 uint128.rotr(uint128 i, uint128 shift) => $$fshr(i, i, shift);
|
||||
|
||||
macro int128.popcount(int128 i) => $$popcount(i);
|
||||
macro int128.ctz(int128 i) => $$ctz(i);
|
||||
macro int128.clz(int128 i) => $$clz(i);
|
||||
macro int128 int128.fshl(int128 hi, int128 lo, int128 shift) => $$fshl(hi, lo, shift);
|
||||
macro int128 int128.fshr(int128 hi, int128 lo, int128 shift) => $$fshr(hi, lo, shift);
|
||||
macro int128 int128.rotl(int128 i, int128 shift) => $$fshl(i, i, shift);
|
||||
macro int128 int128.rotr(int128 i, int128 shift) => $$fshr(i, i, 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);
|
||||
|
||||
160
lib/std/collections/bitset.c3
Normal file
160
lib/std/collections/bitset.c3
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* @require SIZE > 0
|
||||
**/
|
||||
module std::collections::bitset(<SIZE>);
|
||||
|
||||
def Type = uint;
|
||||
|
||||
const BITS = Type.sizeof * 8;
|
||||
const SZ = (SIZE + BITS - 1) / BITS;
|
||||
|
||||
struct BitSet
|
||||
{
|
||||
Type[SZ] data;
|
||||
}
|
||||
|
||||
fn usz BitSet.cardinality(&self)
|
||||
{
|
||||
usz n;
|
||||
foreach (x : self.data)
|
||||
{
|
||||
n += x.popcount();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require i < SIZE
|
||||
**/
|
||||
fn void BitSet.set(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
self.data[q] |= 1 << r;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require i < SIZE
|
||||
**/
|
||||
fn void BitSet.unset(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
self.data[q] &= ~(1 << r);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require i < SIZE
|
||||
**/
|
||||
fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
return self.data[q] & (1 << r) != 0;
|
||||
}
|
||||
|
||||
fn usz BitSet.len(&self) @operator(len) @inline
|
||||
{
|
||||
return SZ * BITS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
**/
|
||||
module std::collections::growablebitset(<Type>);
|
||||
import std::collections::list;
|
||||
|
||||
const BITS = Type.sizeof * 8;
|
||||
|
||||
def GrowableBitSetList = List(<Type>);
|
||||
|
||||
struct GrowableBitSet
|
||||
{
|
||||
GrowableBitSetList data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param initial_capacity
|
||||
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
**/
|
||||
fn GrowableBitSet* GrowableBitSet.init_new(&self, usz initial_capacity = 1, Allocator* allocator = mem::heap())
|
||||
{
|
||||
self.data.init_new(initial_capacity, allocator);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn GrowableBitSet* GrowableBitSet.init_temp(&self, usz initial_capacity = 1)
|
||||
{
|
||||
return self.init_new(initial_capacity, mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.free(&self)
|
||||
{
|
||||
self.data.free();
|
||||
}
|
||||
|
||||
fn usz GrowableBitSet.cardinality(&self)
|
||||
{
|
||||
usz n;
|
||||
foreach (x : self.data)
|
||||
{
|
||||
n += x.popcount();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.set(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
usz current_len = self.data.len();
|
||||
if (q >= 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));
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.unset(&self, usz i)
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
if (q >= self.data.len()) return;
|
||||
self.data.set(q, self.data[q] &~ (1 << r));
|
||||
}
|
||||
|
||||
fn bool GrowableBitSet.get(&self, usz i) @operator([]) @inline
|
||||
{
|
||||
usz q = i / BITS;
|
||||
usz r = i % BITS;
|
||||
if (q >= self.data.len()) return false;
|
||||
return self.data[q] & (1 << r) != 0;
|
||||
}
|
||||
|
||||
fn usz GrowableBitSet.len(&self) @operator(len)
|
||||
{
|
||||
usz n = self.data.len() * BITS;
|
||||
if (n > 0) n -= (usz)self.data[^1].clz();
|
||||
return n;
|
||||
}
|
||||
|
||||
fn void GrowableBitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
|
||||
{
|
||||
if (value) return self.set(i);
|
||||
self.unset(i);
|
||||
}
|
||||
@@ -1,18 +1,63 @@
|
||||
module std::collections::enummap<Enum, ValueType>;
|
||||
module std::collections::enummap(<Enum, ValueType>);
|
||||
|
||||
struct EnumMap
|
||||
struct EnumMap (Printable)
|
||||
{
|
||||
ValueType[Enum.len] values;
|
||||
ValueType[Enum.len] values;
|
||||
}
|
||||
|
||||
fn void EnumMap.init(EnumMap* this, ValueType init_value)
|
||||
fn void EnumMap.init(&self, ValueType init_value)
|
||||
{
|
||||
foreach(&a : this.values)
|
||||
{
|
||||
*a = init_value;
|
||||
}
|
||||
foreach (&a : self.values)
|
||||
{
|
||||
*a = init_value;
|
||||
}
|
||||
}
|
||||
|
||||
fn uint EnumMap.len(EnumMap* this) @operator(len) => this.values.len;
|
||||
fn ValueType EnumMap.get(EnumMap* this, Enum key) @operator([]) => this.values[key.ordinal];
|
||||
fn void EnumMap.set(EnumMap* this, Enum key, ValueType value) @operator([]=) => this.values[key.ordinal] = value;
|
||||
fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
usz n = formatter.print("{ ")!;
|
||||
foreach (i, &value : self.values)
|
||||
{
|
||||
if (i != 0) formatter.print(", ")!;
|
||||
n += formatter.printf("%s: %s", (Enum)i, *value)!;
|
||||
}
|
||||
n += formatter.print(" }")!;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn String EnumMap.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
return string::new_format("%s", *self, .allocator = allocator);
|
||||
}
|
||||
|
||||
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
|
||||
**/
|
||||
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."
|
||||
**/
|
||||
fn ValueType EnumMap.get(&self, Enum key) @operator([]) @inline
|
||||
{
|
||||
return self.values[key.ordinal];
|
||||
}
|
||||
|
||||
fn ValueType* EnumMap.get_ref(&self, Enum key) @operator(&[]) @inline
|
||||
{
|
||||
return &self.values[key.ordinal];
|
||||
}
|
||||
|
||||
fn void EnumMap.set(&self, Enum key, ValueType value) @operator([]=) @inline
|
||||
{
|
||||
self.values[key.ordinal] = value;
|
||||
}
|
||||
@@ -1,148 +1,171 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
/**
|
||||
* @require Enum.kindof == TypeKind.ENUM : "Only enums maybe be used with an enumset"
|
||||
**/
|
||||
module std::collections::enumset<Enum>;
|
||||
module std::collections::enumset(<Enum>);
|
||||
|
||||
def EnumSetType = $typefrom(private::type_for_enum_elements(Enum.elements)) @private ;
|
||||
|
||||
const IS_CHAR_ARRAY = Enum.elements > 128;
|
||||
distinct EnumSet (Printable) = EnumSetType;
|
||||
|
||||
$switch
|
||||
|
||||
$case (Enum.elements > 128):
|
||||
def EnumSetType @private = char[(Enum.elements + 7) / 8];
|
||||
|
||||
$case (Enum.elements > 64):
|
||||
def EnumSetType @private = uint128;
|
||||
|
||||
$case (Enum.elements > 32 || $$C_INT_SIZE > 32):
|
||||
def EnumSetType @private = ulong;
|
||||
|
||||
$case (Enum.elements > 16 || $$C_INT_SIZE > 16):
|
||||
def EnumSetType @private = uint;
|
||||
|
||||
$case (Enum.elements > 8 || $$C_INT_SIZE > 8):
|
||||
def EnumSetType @private = ushort;
|
||||
|
||||
$default:
|
||||
def EnumSetType @private = char;
|
||||
|
||||
$endswitch
|
||||
|
||||
def EnumSet = distinct EnumSetType;
|
||||
|
||||
fn void EnumSet.add(EnumSet* this, Enum v)
|
||||
fn void EnumSet.add(&self, Enum v)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
(*this)[v / 8] |= (char)(1u << (v % 8));
|
||||
$else
|
||||
*this = (EnumSet)((EnumSetType)*this | 1u << (EnumSetType)v);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
(*self)[(usz)v / 8] |= (char)(1u << ((usz)v % 8));
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.clear(EnumSet* this)
|
||||
fn void EnumSet.clear(&self)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
*this = {};
|
||||
$else
|
||||
*this = 0;
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
*self = {};
|
||||
$else
|
||||
*self = 0;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn bool EnumSet.remove(EnumSet* this, Enum v)
|
||||
fn bool EnumSet.remove(&self, Enum v)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
if (!this.has(v) @inline) return false;
|
||||
(*this)[v / 8] &= (char)~(1 << (v % 8));
|
||||
return true;
|
||||
$else
|
||||
EnumSetType old = (EnumSetType)*this;
|
||||
EnumSetType new = old & ~(1u << (EnumSetType)v);
|
||||
*this = (EnumSet)new;
|
||||
return old != new;
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
if (!self.has(v) @inline) return false;
|
||||
(*self)[(usz)v / 8] &= (char)~(1u << ((usz)v % 8));
|
||||
return true;
|
||||
$else
|
||||
EnumSetType old = (EnumSetType)*self;
|
||||
EnumSetType new = old & ~(1u << (EnumSetType)v);
|
||||
*self = (EnumSet)new;
|
||||
return old != new;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn bool EnumSet.has(EnumSet* this, Enum v)
|
||||
fn bool EnumSet.has(&self, Enum v)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
return (bool)(((*this)[v / 8] << (v % 8)) & 0x01);
|
||||
$else
|
||||
return ((EnumSetType)*this & (1u << (EnumSetType)v)) != 0;
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
return (bool)(((*self)[(usz)v / 8] << ((usz)v % 8)) & 0x01);
|
||||
$else
|
||||
return ((EnumSetType)*self & (1u << (EnumSetType)v)) != 0;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.add_all(EnumSet* this, EnumSet s)
|
||||
fn void EnumSet.add_all(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*this)[i] |= c;
|
||||
$else
|
||||
*this = (EnumSet)((EnumSetType)*this | (EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*self)[i] |= c;
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self | (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.retain_all(EnumSet* this, EnumSet s)
|
||||
fn void EnumSet.retain_all(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*this)[i] &= c;
|
||||
$else
|
||||
*this = (EnumSet)((EnumSetType)*this & (EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*self)[i] &= c;
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self & (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void EnumSet.remove_all(EnumSet* this, EnumSet s)
|
||||
fn void EnumSet.remove_all(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*this)[i] &= ~c;
|
||||
$else
|
||||
*this = (EnumSet)((EnumSetType)*this & ~(EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
foreach (i, c : s) (*self)[i] &= ~c;
|
||||
$else
|
||||
*self = (EnumSet)((EnumSetType)*self & ~(EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn EnumSet EnumSet.and_of(EnumSet* this, EnumSet s)
|
||||
fn EnumSet EnumSet.and_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *this;
|
||||
copy.retain_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*this & (EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
copy.retain_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self & (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn EnumSet EnumSet.or_of(EnumSet* this, EnumSet s)
|
||||
fn EnumSet EnumSet.or_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *this;
|
||||
copy.add_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*this | (EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
copy.add_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self | (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
fn EnumSet EnumSet.diff_of(EnumSet* this, EnumSet s)
|
||||
fn EnumSet EnumSet.diff_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *this;
|
||||
copy.remove_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*this & ~(EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
copy.remove_all(s);
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self & ~(EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn EnumSet EnumSet.xor_of(EnumSet* this, EnumSet s)
|
||||
fn EnumSet EnumSet.xor_of(&self, EnumSet s)
|
||||
{
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *this;
|
||||
foreach (i, c : s) copy[i] ^= c;
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*this ^ (EnumSetType)s);
|
||||
$endif
|
||||
$if IS_CHAR_ARRAY:
|
||||
EnumSet copy = *self;
|
||||
foreach (i, c : s) copy[i] ^= c;
|
||||
return copy;
|
||||
$else
|
||||
return (EnumSet)((EnumSetType)*self ^ (EnumSetType)s);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
|
||||
{
|
||||
usz n = formatter.print("[")!;
|
||||
bool found;
|
||||
foreach (value : Enum.values)
|
||||
{
|
||||
if (!set.has(value)) continue;
|
||||
if (found) n += formatter.print(", ")!;
|
||||
found = true;
|
||||
n += formatter.printf("%s", value)!;
|
||||
}
|
||||
n += formatter.print("]")!;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn String EnumSet.to_new_string(&set, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
return string::new_format("%s", *set, .allocator = allocator);
|
||||
}
|
||||
|
||||
fn String EnumSet.to_tstring(&set) @dynamic
|
||||
{
|
||||
return string::tformat("%s", *set);
|
||||
}
|
||||
|
||||
module std::collections::enumset::private;
|
||||
|
||||
macro typeid type_for_enum_elements(usz $elements)
|
||||
{
|
||||
$switch
|
||||
$case ($elements > 128):
|
||||
return char[($elements + 7) / 8].typeid;
|
||||
$case ($elements > 64):
|
||||
return uint128.typeid;
|
||||
$case ($elements > 32 || $$C_INT_SIZE > 32):
|
||||
return ulong.typeid;
|
||||
$case ($elements > 16 || $$C_INT_SIZE > 16):
|
||||
return uint.typeid;
|
||||
$case ($elements > 8 || $$C_INT_SIZE > 8):
|
||||
return ushort.typeid;
|
||||
$default:
|
||||
return char.typeid;
|
||||
$endswitch
|
||||
}
|
||||
@@ -1,312 +1,310 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::linkedlist<Type>;
|
||||
module std::collections::linkedlist(<Type>);
|
||||
|
||||
struct Node @private
|
||||
{
|
||||
Node *next;
|
||||
Node *prev;
|
||||
Type value;
|
||||
Node *next;
|
||||
Node *prev;
|
||||
Type value;
|
||||
}
|
||||
|
||||
struct LinkedList
|
||||
{
|
||||
Allocator *allocator;
|
||||
usz size;
|
||||
Node *_first;
|
||||
Node *_last;
|
||||
Allocator *allocator;
|
||||
usz size;
|
||||
Node *_first;
|
||||
Node *_last;
|
||||
}
|
||||
|
||||
fn void LinkedList.push(LinkedList* list, Type value)
|
||||
fn void LinkedList.push(&self, Type value)
|
||||
{
|
||||
list.link_first(value);
|
||||
self.link_first(value);
|
||||
}
|
||||
|
||||
fn void LinkedList.push_last(LinkedList* list, Type value)
|
||||
fn void LinkedList.push_last(&self, Type value)
|
||||
{
|
||||
list.link_last(value);
|
||||
self.link_last(value);
|
||||
}
|
||||
|
||||
fn void LinkedList.init(LinkedList* list, Allocator* using = mem::heap())
|
||||
{
|
||||
*list = { .allocator = using };
|
||||
}
|
||||
|
||||
fn void LinkedList.tinit(LinkedList* list) => list.init(mem::temp()) @inline;
|
||||
|
||||
/**
|
||||
* @require list.allocator
|
||||
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
* @return "the initialized list"
|
||||
**/
|
||||
macro void LinkedList.@free_node(LinkedList &list, Node* node) @private
|
||||
fn LinkedList* LinkedList.init_new(&self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
list.allocator.free(node)!!;
|
||||
}
|
||||
macro Node* LinkedList.@alloc_node(LinkedList &list) @private
|
||||
{
|
||||
if (!list.allocator) list.allocator = mem::heap();
|
||||
return malloc(Node, .using = list.allocator);
|
||||
*self = { .allocator = allocator };
|
||||
return self;
|
||||
}
|
||||
|
||||
fn void LinkedList.link_first(LinkedList* list, Type value) @private
|
||||
fn LinkedList* LinkedList.init_temp(&self)
|
||||
{
|
||||
Node *first = list._first;
|
||||
Node *new_node = list.@alloc_node();
|
||||
*new_node = { .next = first, .value = value };
|
||||
list._first = new_node;
|
||||
if (!first)
|
||||
{
|
||||
list._last = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
first.prev = new_node;
|
||||
}
|
||||
list.size++;
|
||||
return self.init_new(mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn void LinkedList.link_last(LinkedList* list, Type value) @private
|
||||
{
|
||||
Node *last = list._last;
|
||||
Node *new_node = list.@alloc_node();
|
||||
*new_node = { .prev = last, .value = value };
|
||||
list._last = new_node;
|
||||
if (!last)
|
||||
{
|
||||
list._first = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
last.next = new_node;
|
||||
}
|
||||
list.size++;
|
||||
}
|
||||
|
||||
fn Type! peek(LinkedList* list) => list.first() @inline;
|
||||
fn Type! peek_last(LinkedList* list) => list.last() @inline;
|
||||
|
||||
fn Type! LinkedList.first(LinkedList *list)
|
||||
{
|
||||
if (!list._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return list._first.value;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.last(LinkedList* list)
|
||||
{
|
||||
if (!list._last) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return list._last.value;
|
||||
}
|
||||
|
||||
fn void LinkedList.free(LinkedList* list) => list.clear() @inline;
|
||||
|
||||
fn void LinkedList.clear(LinkedList* list)
|
||||
{
|
||||
for (Node* node = list._first; node != null;)
|
||||
{
|
||||
Node* next = node.next;
|
||||
list.@free_node(node);
|
||||
node = next;
|
||||
}
|
||||
list._first = null;
|
||||
list._last = null;
|
||||
list.size = 0;
|
||||
}
|
||||
|
||||
fn usz LinkedList.len(LinkedList* list) @inline => list.size;
|
||||
|
||||
/**
|
||||
* @require index < list.size
|
||||
* @require self.allocator
|
||||
**/
|
||||
macro Node* LinkedList.node_at_index(LinkedList* list, usz index)
|
||||
macro void LinkedList.free_node(&self, Node* node) @private
|
||||
{
|
||||
if (index * 2 >= list.size)
|
||||
self.allocator.free(node);
|
||||
}
|
||||
macro Node* LinkedList.alloc_node(&self) @private
|
||||
{
|
||||
if (!self.allocator) self.allocator = mem::heap();
|
||||
return self.allocator.new(Node);
|
||||
}
|
||||
|
||||
fn void LinkedList.link_first(&self, Type value) @private
|
||||
{
|
||||
Node *first = self._first;
|
||||
Node *new_node = self.alloc_node();
|
||||
*new_node = { .next = first, .value = value };
|
||||
self._first = new_node;
|
||||
if (!first)
|
||||
{
|
||||
Node* node = list._last;
|
||||
index = list.size - index - 1;
|
||||
while (index--) node = node.prev;
|
||||
return node;
|
||||
self._last = new_node;
|
||||
}
|
||||
Node* node = list._first;
|
||||
while (index--) node = node.next;
|
||||
return node;
|
||||
else
|
||||
{
|
||||
first.prev = new_node;
|
||||
}
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn void LinkedList.link_last(&self, Type value) @private
|
||||
{
|
||||
Node *last = self._last;
|
||||
Node *new_node = self.alloc_node();
|
||||
*new_node = { .prev = last, .value = value };
|
||||
self._last = new_node;
|
||||
if (!last)
|
||||
{
|
||||
self._first = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
last.next = new_node;
|
||||
}
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.peek(&self) => self.first() @inline;
|
||||
fn Type! LinkedList.peek_last(&self) => self.last() @inline;
|
||||
|
||||
fn Type! LinkedList.first(&self)
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.last(&self)
|
||||
{
|
||||
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self._last.value;
|
||||
}
|
||||
|
||||
fn void LinkedList.free(&self) => self.clear() @inline;
|
||||
|
||||
fn void LinkedList.clear(&self)
|
||||
{
|
||||
for (Node* node = self._first; node != null;)
|
||||
{
|
||||
Node* next = node.next;
|
||||
self.free_node(node);
|
||||
node = next;
|
||||
}
|
||||
self._first = null;
|
||||
self._last = null;
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
fn usz LinkedList.len(&self) @inline => self.size;
|
||||
|
||||
/**
|
||||
* @require index < self.size
|
||||
**/
|
||||
macro Node* LinkedList.node_at_index(&self, usz index)
|
||||
{
|
||||
if (index * 2 >= self.size)
|
||||
{
|
||||
Node* node = self._last;
|
||||
index = self.size - index - 1;
|
||||
while (index--) node = node.prev;
|
||||
return node;
|
||||
}
|
||||
Node* node = self._first;
|
||||
while (index--) node = node.next;
|
||||
return node;
|
||||
}
|
||||
/**
|
||||
* @require index < list.size
|
||||
* @require index < self.size
|
||||
**/
|
||||
fn Type LinkedList.get(LinkedList* list, usz index)
|
||||
fn Type LinkedList.get(&self, usz index)
|
||||
{
|
||||
return list.node_at_index(index).value;
|
||||
return self.node_at_index(index).value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < list.size
|
||||
* @require index < self.size
|
||||
**/
|
||||
fn void LinkedList.set(LinkedList* list, usz index, Type element)
|
||||
fn void LinkedList.set(&self, usz index, Type element)
|
||||
{
|
||||
list.node_at_index(index).value = element;
|
||||
self.node_at_index(index).value = element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < list.size
|
||||
* @require index < self.size
|
||||
**/
|
||||
fn void LinkedList.remove(LinkedList* list, usz index)
|
||||
fn void LinkedList.remove(&self, usz index)
|
||||
{
|
||||
list.unlink(list.node_at_index(index));
|
||||
self.unlink(self.node_at_index(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index <= list.size
|
||||
* @require index <= self.size
|
||||
**/
|
||||
fn void LinkedList.insert(LinkedList* list, usz index, Type element)
|
||||
fn void LinkedList.insert(&self, usz index, Type element)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
list.push(element);
|
||||
case list.size:
|
||||
list.push_last(element);
|
||||
self.push(element);
|
||||
case self.size:
|
||||
self.push_last(element);
|
||||
default:
|
||||
list.link_before(list.node_at_index(index), element);
|
||||
self.link_before(self.node_at_index(index), element);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @require succ != null
|
||||
**/
|
||||
fn void LinkedList.link_before(LinkedList *list, Node *succ, Type value) @private
|
||||
fn void LinkedList.link_before(&self, Node *succ, Type value) @private
|
||||
{
|
||||
Node* pred = succ.prev;
|
||||
Node* new_node = malloc(Node);
|
||||
*new_node = { .prev = pred, .next = succ, .value = value };
|
||||
succ.prev = new_node;
|
||||
if (!pred)
|
||||
{
|
||||
list._first = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
pred.next = new_node;
|
||||
}
|
||||
list.size++;
|
||||
Node* pred = succ.prev;
|
||||
Node* new_node = self.alloc_node();
|
||||
*new_node = { .prev = pred, .next = succ, .value = value };
|
||||
succ.prev = new_node;
|
||||
if (!pred)
|
||||
{
|
||||
self._first = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
pred.next = new_node;
|
||||
}
|
||||
self.size++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require list && list._first
|
||||
* @require self._first
|
||||
**/
|
||||
fn void LinkedList.unlink_first(LinkedList* list) @private
|
||||
fn void LinkedList.unlink_first(&self) @private
|
||||
{
|
||||
Node* f = list._first;
|
||||
Node* next = f.next;
|
||||
list.@free_node(f);
|
||||
list._first = next;
|
||||
if (!next)
|
||||
{
|
||||
list._last = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
next.prev = null;
|
||||
}
|
||||
list.size--;
|
||||
Node* f = self._first;
|
||||
Node* next = f.next;
|
||||
self.free_node(f);
|
||||
self._first = next;
|
||||
if (!next)
|
||||
{
|
||||
self._last = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
next.prev = null;
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
|
||||
fn bool LinkedList.remove_value(LinkedList* list, Type t)
|
||||
fn bool LinkedList.remove_value(&self, Type t)
|
||||
{
|
||||
for (Node* node = list._first; node != null; node = node.next)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
list.unlink(node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
for (Node* node = self._first; node != null; node = node.next)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
self.unlink(node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn bool LinkedList.remove_last_value(LinkedList* list, Type t)
|
||||
fn bool LinkedList.remove_last_value(&self, Type t)
|
||||
{
|
||||
for (Node* node = list._last; node != null; node = node.prev)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
list.unlink(node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
for (Node* node = self._last; node != null; node = node.prev)
|
||||
{
|
||||
if (node.value == t)
|
||||
{
|
||||
self.unlink(node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn Type! LinkedList.pop(&self)
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer self.unlink_first();
|
||||
return self._first.value;
|
||||
}
|
||||
|
||||
fn void! LinkedList.remove_last(&self)
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.unlink_last();
|
||||
}
|
||||
|
||||
fn void! LinkedList.remove_first(&self)
|
||||
{
|
||||
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
self.unlink_first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list
|
||||
* @require self._last
|
||||
**/
|
||||
fn Type! LinkedList.pop(LinkedList* list)
|
||||
fn void LinkedList.unlink_last(&self) @inline @private
|
||||
{
|
||||
if (!list._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
defer list.unlink_first();
|
||||
return list._first.value;
|
||||
Node* l = self._last;
|
||||
Node* prev = l.prev;
|
||||
self._last = prev;
|
||||
self.free_node(l);
|
||||
if (!prev)
|
||||
{
|
||||
self._first = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = null;
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list
|
||||
* @require x != null
|
||||
**/
|
||||
fn void! LinkedList.remove_last(LinkedList* list)
|
||||
fn void LinkedList.unlink(&self, Node* x) @private
|
||||
{
|
||||
if (!list._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
list.unlink_last();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list
|
||||
**/
|
||||
fn void! LinkedList.remove_first(LinkedList* list)
|
||||
{
|
||||
if (!list._first) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
list.unlink_first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list
|
||||
* @require list._last
|
||||
**/
|
||||
fn void LinkedList.unlink_last(LinkedList *list) @inline @private
|
||||
{
|
||||
Node* l = list._last;
|
||||
Node* prev = l.prev;
|
||||
list._last = prev;
|
||||
list.@free_node(l);
|
||||
if (!prev)
|
||||
{
|
||||
list._first = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = null;
|
||||
}
|
||||
list.size--;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require list != null, x != null
|
||||
**/
|
||||
fn void LinkedList.unlink(LinkedList* list, Node* x) @private
|
||||
{
|
||||
Node* next = x.next;
|
||||
Node* prev = x.prev;
|
||||
if (!prev)
|
||||
{
|
||||
list._first = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
if (!next)
|
||||
{
|
||||
list._last = prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
next.prev = prev;
|
||||
}
|
||||
list.@free_node(x);
|
||||
list.size--;
|
||||
Node* next = x.next;
|
||||
Node* prev = x.prev;
|
||||
if (!prev)
|
||||
{
|
||||
self._first = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
if (!next)
|
||||
{
|
||||
self._last = prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
next.prev = prev;
|
||||
}
|
||||
self.free_node(x);
|
||||
self.size--;
|
||||
}
|
||||
|
||||
@@ -1,312 +1,420 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::collections::list<Type>;
|
||||
module std::collections::list(<Type>);
|
||||
import std::io;
|
||||
import 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;
|
||||
|
||||
struct List
|
||||
struct List (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator *allocator;
|
||||
Type *entries;
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator *allocator;
|
||||
Type *entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require using != null "A valid allocator must be provided"
|
||||
* @param initial_capacity "The initial capacity to reserve"
|
||||
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
|
||||
**/
|
||||
fn void List.init(List* list, usz initial_capacity = 16, Allocator* using = mem::heap())
|
||||
fn List* List.init_new(&self, usz initial_capacity = 16, Allocator* allocator = mem::heap())
|
||||
{
|
||||
list.allocator = using;
|
||||
list.size = 0;
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
if (initial_capacity > 0)
|
||||
{
|
||||
initial_capacity = math::next_power_of_2(initial_capacity);
|
||||
list.entries = malloc_aligned(Type, initial_capacity, .alignment = Type[1].alignof, .using = using)!!;
|
||||
self.entries = allocator.alloc_aligned(Type.sizeof * initial_capacity, .alignment = Type[1].alignof)!!;
|
||||
}
|
||||
else
|
||||
{
|
||||
list.entries = null;
|
||||
self.entries = null;
|
||||
}
|
||||
list.capacity = initial_capacity;
|
||||
}
|
||||
|
||||
fn void List.tinit(List* list, usz initial_capacity = 16)
|
||||
{
|
||||
list.init(initial_capacity, mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn void List.push(List* list, Type element) @inline
|
||||
{
|
||||
list.append(element);
|
||||
}
|
||||
|
||||
fn void List.append(List* list, Type element)
|
||||
{
|
||||
list.ensure_capacity();
|
||||
list.entries[list.size++] = element;
|
||||
self.capacity = initial_capacity;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require list.size > 0
|
||||
*/
|
||||
fn Type List.pop(List* list)
|
||||
* Initialize the list using the temp allocator.
|
||||
*
|
||||
* @param initial_capacity "The initial capacity to reserve"
|
||||
**/
|
||||
fn List* List.init_temp(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return list.entries[--list.size];
|
||||
}
|
||||
|
||||
fn void List.clear(List* list)
|
||||
{
|
||||
list.size = 0;
|
||||
return self.init_new(initial_capacity, mem::temp()) @inline;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require list.size > 0
|
||||
*/
|
||||
fn Type List.pop_first(List* list)
|
||||
* @require self.size == 0 "The List must be empty"
|
||||
**/
|
||||
fn void List.init_wrapping_array(&self, Type[] types, Allocator* allocator = mem::heap())
|
||||
{
|
||||
Type value = list.entries[0];
|
||||
list.remove_at(0);
|
||||
return value;
|
||||
self.allocator = allocator;
|
||||
self.size = types.len;
|
||||
self.capacity = types.len;
|
||||
self.entries = types.ptr;
|
||||
}
|
||||
|
||||
fn void List.remove_at(List* list, usz index)
|
||||
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
for (usz i = index + 1; i < list.size; i++)
|
||||
{
|
||||
list.entries[i - 1] = list.entries[i];
|
||||
}
|
||||
list.size--;
|
||||
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 void List.add_all(List* list, List* other_list)
|
||||
fn String List.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
return string::new_format("%s", *self, .allocator = allocator);
|
||||
}
|
||||
|
||||
fn String List.to_tstring(&self)
|
||||
{
|
||||
return string::tformat("%s", *self);
|
||||
}
|
||||
|
||||
fn void List.push(&self, Type element) @inline
|
||||
{
|
||||
self.append(element);
|
||||
}
|
||||
|
||||
fn void List.append(&self, Type element)
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require self.size > 0
|
||||
**/
|
||||
fn Type List.pop(&self)
|
||||
{
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
fn void List.clear(&self)
|
||||
{
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require self.size > 0
|
||||
**/
|
||||
fn Type List.pop_first(&self)
|
||||
{
|
||||
Type value = self.entries[0];
|
||||
self.remove_at(0);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < self.size
|
||||
**/
|
||||
fn void List.remove_at(&self, usz index)
|
||||
{
|
||||
for (usz i = index + 1; i < self.size; i++)
|
||||
{
|
||||
self.entries[i - 1] = self.entries[i];
|
||||
}
|
||||
self.size--;
|
||||
}
|
||||
|
||||
fn void List.add_all(&self, List* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
list.reserve(other_list.size);
|
||||
self.reserve(other_list.size);
|
||||
foreach (&value : other_list)
|
||||
{
|
||||
list.entries[list.size++] = *value;
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn Type[] List.to_array(List* list, Allocator* using = mem::heap())
|
||||
fn Type[] List.to_new_array(&self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (!list.size) return Type[] {};
|
||||
Type[] result = malloc(Type, list.size, .using = using);
|
||||
result[..] = list.entries[:list.size];
|
||||
if (!self.size) return Type[] {};
|
||||
Type[] result = allocator.new_array(Type, self.size);
|
||||
result[..] = self.entries[:self.size];
|
||||
return result;
|
||||
}
|
||||
|
||||
fn Type[] List.to_tarray(&self)
|
||||
{
|
||||
return self.to_new_array(mem::temp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the elements in a list.
|
||||
*
|
||||
* @param [&inout] list "The list to reverse"
|
||||
**/
|
||||
fn void List.reverse(List* list)
|
||||
fn void List.reverse(&self)
|
||||
{
|
||||
if (list.size < 2) return;
|
||||
usz half = list.size / 2U;
|
||||
usz end = list.size - 1;
|
||||
if (self.size < 2) return;
|
||||
usz half = self.size / 2U;
|
||||
usz end = self.size - 1;
|
||||
for (usz i = 0; i < half; i++)
|
||||
{
|
||||
@swap(list.entries[i], list.entries[end - i]);
|
||||
@swap(self.entries[i], self.entries[end - i]);
|
||||
}
|
||||
}
|
||||
|
||||
fn Type[] List.array_view(List* list)
|
||||
fn Type[] List.array_view(&self)
|
||||
{
|
||||
return list.entries[:list.size];
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
fn void List.add_array(List* list, Type[] array)
|
||||
fn void List.add_array(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return;
|
||||
list.reserve(array.len);
|
||||
self.reserve(array.len);
|
||||
foreach (&value : array)
|
||||
{
|
||||
list.entries[list.size++] = *value;
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
fn void List.push_front(List* list, Type type) @inline
|
||||
fn void List.push_front(&self, Type type) @inline
|
||||
{
|
||||
list.insert_at(0, type);
|
||||
}
|
||||
|
||||
fn void List.insert_at(List* list, usz index, Type type)
|
||||
{
|
||||
list.ensure_capacity();
|
||||
for (usz i = list.size; i > index; i--)
|
||||
{
|
||||
list.entries[i] = list.entries[i - 1];
|
||||
}
|
||||
list.size++;
|
||||
list.entries[index] = type;
|
||||
self.insert_at(0, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < list.size
|
||||
* @require index < self.size
|
||||
**/
|
||||
fn void List.set_at(List* list, usz index, Type type)
|
||||
fn void List.insert_at(&self, usz index, Type type)
|
||||
{
|
||||
list.entries[index] = type;
|
||||
}
|
||||
|
||||
fn void List.remove_last(List* list)
|
||||
{
|
||||
list.size--;
|
||||
}
|
||||
|
||||
fn void List.remove_first(List* list)
|
||||
{
|
||||
list.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type* List.first(List* list)
|
||||
{
|
||||
return list.size ? &list.entries[0] : null;
|
||||
}
|
||||
|
||||
fn Type* List.last(List* list)
|
||||
{
|
||||
return list.size ? &list.entries[list.size - 1] : null;
|
||||
}
|
||||
|
||||
fn bool List.is_empty(List* list)
|
||||
{
|
||||
return !list.size;
|
||||
}
|
||||
|
||||
fn usz List.len(List* list) @operator(len)
|
||||
{
|
||||
return list.size;
|
||||
}
|
||||
|
||||
fn Type List.get(List* list, usz index)
|
||||
{
|
||||
return list.entries[index];
|
||||
}
|
||||
|
||||
fn void List.free(List* list)
|
||||
{
|
||||
if (!list.allocator) return;
|
||||
free_aligned(list.entries, .using = list.allocator);
|
||||
list.capacity = 0;
|
||||
list.size = 0;
|
||||
list.entries = null;
|
||||
}
|
||||
|
||||
fn void List.swap(List* list, usz i, usz j)
|
||||
{
|
||||
@swap(list.entries[i], list.entries[j]);
|
||||
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
|
||||
**/
|
||||
fn void List.set_at(&self, usz index, Type type)
|
||||
{
|
||||
self.entries[index] = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require self.size > 0
|
||||
**/
|
||||
fn void List.remove_last(&self)
|
||||
{
|
||||
self.size--;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require self.size > 0
|
||||
**/
|
||||
fn void List.remove_first(&self)
|
||||
{
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
fn Type* List.first(&self)
|
||||
{
|
||||
return self.size ? &self.entries[0] : null;
|
||||
}
|
||||
|
||||
fn Type* List.last(&self)
|
||||
{
|
||||
return self.size ? &self.entries[self.size - 1] : null;
|
||||
}
|
||||
|
||||
fn bool List.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
fn usz List.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
fn Type List.get(&self, usz index) @inline
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn void List.free(&self)
|
||||
{
|
||||
if (!self.allocator) return;
|
||||
self.allocator.free_aligned(self.entries);
|
||||
self.capacity = 0;
|
||||
self.size = 0;
|
||||
self.entries = null;
|
||||
}
|
||||
|
||||
fn void List.swap(&self, usz i, usz j)
|
||||
{
|
||||
@swap(self.entries[i], self.entries[j]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list "The list to remove elements from"
|
||||
* @param filter "The function to determine if it should be removed or not"
|
||||
* @return "the number of deleted elements"
|
||||
**/
|
||||
fn usz List.remove_if(List* list, ElementPredicate filter)
|
||||
fn usz List.remove_if(&self, ElementPredicate filter)
|
||||
{
|
||||
usz size = list.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (filter(&list.entries[i - 1])) continue;
|
||||
for (usz j = i; j < size; j++)
|
||||
{
|
||||
list.entries[j - 1] = list.entries[j];
|
||||
}
|
||||
list.size--;
|
||||
}
|
||||
return size - list.size;
|
||||
return self._remove_if(filter, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] list "The list to remove elements from"
|
||||
* @param selection "The function to determine if it should be kept or not"
|
||||
* @return "the number of deleted elements"
|
||||
**/
|
||||
fn usz List.retain_if(List* list, ElementPredicate selection)
|
||||
fn usz List.retain_if(&self, ElementPredicate selection)
|
||||
{
|
||||
usz size = list.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (!selection(&list.entries[i - 1])) continue;
|
||||
for (usz j = i; j < size; j++)
|
||||
{
|
||||
list.entries[j - 1] = list.entries[j];
|
||||
}
|
||||
list.size--;
|
||||
}
|
||||
return size - list.size;
|
||||
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)
|
||||
{
|
||||
return self._remove_using_test(filter, false, context);
|
||||
}
|
||||
|
||||
fn usz List.retain_using_test(&self, ElementTest filter, any* context)
|
||||
{
|
||||
return self._remove_using_test(filter, true, context);
|
||||
}
|
||||
|
||||
macro usz List._remove_using_test(&self, ElementTest filter, bool $invert, ctx) @local
|
||||
{
|
||||
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(List* list, usz min_capacity)
|
||||
fn void List.reserve(&self, usz min_capacity)
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (list.capacity >= min_capacity) return;
|
||||
if (!list.allocator) list.allocator = mem::heap();
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = mem::heap();
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
list.entries = realloc_aligned(list.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof, .using = list.allocator) ?? null;
|
||||
list.capacity = min_capacity;
|
||||
self.entries = self.allocator.realloc_aligned(self.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof) ?? null;
|
||||
self.capacity = min_capacity;
|
||||
}
|
||||
|
||||
macro Type List.@item_at(List &list, usz index) @operator([])
|
||||
macro Type List.@item_at(&self, usz index) @operator([])
|
||||
{
|
||||
return list.entries[index];
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
fn Type* List.get_ref(List* list, usz index) @operator(&[]) @inline
|
||||
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
|
||||
{
|
||||
return &list.entries[index];
|
||||
return &self.entries[index];
|
||||
}
|
||||
|
||||
fn void List.ensure_capacity(List* list, usz added = 1) @inline @private
|
||||
fn void List.set(&self, usz index, Type value) @operator([]=)
|
||||
{
|
||||
usz new_size = list.size + added;
|
||||
if (list.capacity > new_size) return;
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
fn void List.ensure_capacity(&self, usz added = 1) @inline @private
|
||||
{
|
||||
usz new_size = self.size + added;
|
||||
if (self.capacity >= new_size) return;
|
||||
|
||||
assert(new_size < usz.max / 2U);
|
||||
usz new_capacity = list.capacity ? 2U * list.capacity : 16U;
|
||||
while (new_size >= new_capacity) new_capacity *= 2U;
|
||||
list.reserve(new_capacity);
|
||||
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
|
||||
while (new_capacity < new_size) new_capacity *= 2U;
|
||||
self.reserve(new_capacity);
|
||||
}
|
||||
|
||||
// Functions for equatable types
|
||||
|
||||
$if types::is_equatable_type(Type):
|
||||
|
||||
fn usz! List.index_of(List* list, Type type)
|
||||
fn usz! List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : list)
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (v == type) return i;
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn usz! List.rindex_of(List* list, Type type)
|
||||
fn usz! List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach_r (i, v : list)
|
||||
foreach_r (i, v : self)
|
||||
{
|
||||
if (v == type) return i;
|
||||
if (equals(v, type)) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn bool List.equals(List* list, List other_list)
|
||||
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (list.size != other_list.size) return false;
|
||||
foreach (i, v : list)
|
||||
if (self.size != other_list.size) return false;
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (v != other_list.entries[i]) return false;
|
||||
if (!equals(v, other_list.entries[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -314,75 +422,68 @@ fn bool List.equals(List* list, List other_list)
|
||||
/**
|
||||
* Check for presence of a value in a list.
|
||||
*
|
||||
* @param [&in] list "the list to find elements in"
|
||||
* @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(List* list, Type value)
|
||||
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
foreach (i, v : list)
|
||||
foreach (i, v : self)
|
||||
{
|
||||
if (v == value) return true;
|
||||
if (equals(v, value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param [&inout] list "The list to remove elements from"
|
||||
* @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(List* list, Type value)
|
||||
fn usz List.remove(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
usz size = list.size;
|
||||
usz size = self.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (list.entries[i - 1] != value) continue;
|
||||
if (!equals(self.entries[i - 1], value)) continue;
|
||||
for (usz j = i; j < size; j++)
|
||||
{
|
||||
list.entries[j - 1] = list.entries[j];
|
||||
self.entries[j - 1] = self.entries[j];
|
||||
}
|
||||
list.size--;
|
||||
self.size--;
|
||||
}
|
||||
return size - list.size;
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
fn void List.remove_all(List* list, List* other_list)
|
||||
fn void List.remove_all(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
foreach (v : other_list) list.remove(v);
|
||||
foreach (v : other_list) self.remove(v);
|
||||
}
|
||||
|
||||
|
||||
$endif
|
||||
|
||||
$if Type.kindof == POINTER:
|
||||
|
||||
/**
|
||||
* @param [&in] list
|
||||
* @param [&in] self
|
||||
* @return "The number non-null values in the list"
|
||||
**/
|
||||
fn usz List.compact_count(List* list)
|
||||
fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
usz vals = 0;
|
||||
foreach (v : list) if (v) vals++;
|
||||
foreach (v : self) if (v) vals++;
|
||||
return vals;
|
||||
}
|
||||
|
||||
fn usz List.compact(List* list)
|
||||
fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
usz size = list.size;
|
||||
usz size = self.size;
|
||||
for (usz i = size; i > 0; i--)
|
||||
{
|
||||
if (list.entries[i - 1]) continue;
|
||||
if (self.entries[i - 1]) continue;
|
||||
for (usz j = i; j < size; j++)
|
||||
{
|
||||
list.entries[j - 1] = list.entries[j];
|
||||
self.entries[j - 1] = self.entries[j];
|
||||
}
|
||||
list.size--;
|
||||
self.size--;
|
||||
}
|
||||
return size - list.size;
|
||||
return size - self.size;
|
||||
}
|
||||
|
||||
$endif
|
||||
@@ -1,13 +1,14 @@
|
||||
// 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.
|
||||
module std::collections::map<Key, Value>;
|
||||
module std::collections::map(<Key, Value>);
|
||||
import std::math;
|
||||
|
||||
const uint DEFAULT_INITIAL_CAPACITY = 16;
|
||||
const uint MAXIMUM_CAPACITY = 1u << 31;
|
||||
const float DEFAULT_LOAD_FACTOR = 0.75;
|
||||
|
||||
const VALUE_IS_EQUATABLE = Value.is_eq;
|
||||
const bool COPY_KEYS = types::implements_copy(Key);
|
||||
|
||||
struct HashMap
|
||||
{
|
||||
@@ -19,19 +20,20 @@ struct HashMap
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 !map.allocator "Map was already initialized"
|
||||
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
* @require using != null "The allocator must be non-null"
|
||||
**/
|
||||
fn void HashMap.init(HashMap* map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator* using = mem::heap())
|
||||
fn HashMap* HashMap.init_new(&map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator* allocator = mem::heap())
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
map.allocator = using;
|
||||
map.allocator = allocator;
|
||||
map.load_factor = load_factor;
|
||||
map.threshold = (uint)(capacity * load_factor);
|
||||
map.table = calloc(Entry*, capacity, .using = using);
|
||||
map.table = allocator.new_zero_array(Entry*, capacity);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,9 +42,9 @@ fn void HashMap.init(HashMap* map, uint capacity = DEFAULT_INITIAL_CAPACITY, flo
|
||||
* @require !map.allocator "Map was already initialized"
|
||||
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
|
||||
**/
|
||||
fn void HashMap.tinit(HashMap* map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
fn HashMap* HashMap.init_temp(&map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
map.init(capacity, load_factor, mem::temp());
|
||||
return map.init_new(capacity, load_factor, mem::temp());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,28 +53,41 @@ fn void HashMap.tinit(HashMap* map, uint capacity = DEFAULT_INITIAL_CAPACITY, fl
|
||||
* @param [&in] map "The hash map we are testing"
|
||||
* @return "Returns true if it has been initialized, false otherwise"
|
||||
**/
|
||||
fn bool HashMap.is_initialized(HashMap* map)
|
||||
fn bool HashMap.is_initialized(&map)
|
||||
{
|
||||
return map.allocator != null;
|
||||
return (bool)map.allocator;
|
||||
}
|
||||
|
||||
fn void HashMap.init_from_map(HashMap* map, HashMap* other_map, Allocator* using = mem::heap())
|
||||
/**
|
||||
* @param [&inout] allocator "The allocator to use"
|
||||
* @param [&in] other_map "The map to copy from."
|
||||
**/
|
||||
fn HashMap* HashMap.init_new_from_map(&self, HashMap* other_map, Allocator* allocator = mem::heap())
|
||||
{
|
||||
map.init(other_map.table.len, other_map.load_factor, using);
|
||||
map.put_all_for_create(other_map);
|
||||
self.init_new(other_map.table.len, other_map.load_factor, allocator);
|
||||
self.put_all_for_create(other_map);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn void HashMap.tinit_from_map(HashMap* map, HashMap* other_map)
|
||||
/**
|
||||
* @param [&in] other_map "The map to copy from."
|
||||
**/
|
||||
fn HashMap* HashMap.init_temp_from_map(&map, HashMap* other_map)
|
||||
{
|
||||
map.init_from_map(other_map, mem::temp()) @inline;
|
||||
return map.init_new_from_map(other_map, mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn bool HashMap.is_empty(HashMap* map) @inline
|
||||
fn bool HashMap.is_empty(&map) @inline
|
||||
{
|
||||
return !map.count;
|
||||
}
|
||||
|
||||
fn Value*! HashMap.get_ref(HashMap* map, Key key)
|
||||
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());
|
||||
@@ -83,7 +98,7 @@ fn Value*! HashMap.get_ref(HashMap* map, Key key)
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn Entry*! HashMap.get_entry(HashMap* map, Key key)
|
||||
fn Entry*! HashMap.get_entry(&map, Key key)
|
||||
{
|
||||
if (!map.count) return SearchResult.MISSING?;
|
||||
uint hash = rehash(key.hash());
|
||||
@@ -97,7 +112,7 @@ fn Entry*! HashMap.get_entry(HashMap* map, Key key)
|
||||
/**
|
||||
* Get the value or update and
|
||||
**/
|
||||
macro Value HashMap.@get_or_set(HashMap* map, Key key, Value #expr)
|
||||
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
{
|
||||
if (!map.count)
|
||||
{
|
||||
@@ -116,22 +131,22 @@ macro Value HashMap.@get_or_set(HashMap* map, Key key, Value #expr)
|
||||
return val;
|
||||
}
|
||||
|
||||
fn Value! HashMap.get(HashMap* map, Key key) @operator([])
|
||||
fn Value! HashMap.get(&map, Key key) @operator([])
|
||||
{
|
||||
return *map.get_ref(key) @inline;
|
||||
}
|
||||
|
||||
fn bool HashMap.has_key(HashMap* map, Key key)
|
||||
fn bool HashMap.has_key(&map, Key key)
|
||||
{
|
||||
return @ok(map.get_ref(key));
|
||||
}
|
||||
|
||||
fn bool HashMap.set(HashMap* map, Key key, Value value) @operator([]=)
|
||||
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();
|
||||
map.init_new();
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
@@ -147,42 +162,49 @@ fn bool HashMap.set(HashMap* map, Key key, Value value) @operator([]=)
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void! HashMap.remove(HashMap* map, Key key) @maydiscard
|
||||
fn void! HashMap.remove(&map, Key key) @maydiscard
|
||||
{
|
||||
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn void HashMap.clear(HashMap* map)
|
||||
fn void HashMap.clear(&map)
|
||||
{
|
||||
if (!map.count) return;
|
||||
foreach (Entry** &entry_ref : map.table)
|
||||
{
|
||||
Entry* entry = *entry_ref;
|
||||
if (!entry) continue;
|
||||
map.free_internal(entry);
|
||||
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(HashMap* map)
|
||||
fn void HashMap.free(&map)
|
||||
{
|
||||
if (!map.allocator) return;
|
||||
map.clear();
|
||||
map.free_internal(map.table.ptr);
|
||||
map.table = Entry*[] {};
|
||||
map.table = {};
|
||||
}
|
||||
|
||||
fn Key[] HashMap.key_tlist(HashMap* map)
|
||||
fn Key[] HashMap.key_tlist(&map)
|
||||
{
|
||||
return map.key_list(mem::temp()) @inline;
|
||||
return map.key_new_list(mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn Key[] HashMap.key_list(HashMap* map, Allocator* using = mem::heap())
|
||||
fn Key[] HashMap.key_new_list(&map, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (!map.count) return Key[] {};
|
||||
if (!map.count) return {};
|
||||
|
||||
Key[] list = calloc(Key, map.count, .using = using);
|
||||
Key[] list = allocator.new_array(Key, map.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
@@ -195,15 +217,37 @@ fn Key[] HashMap.key_list(HashMap* map, Allocator* using = mem::heap())
|
||||
return list;
|
||||
}
|
||||
|
||||
fn Value[] HashMap.value_tlist(HashMap* map)
|
||||
macro HashMap.@each(map; @body(key, value))
|
||||
{
|
||||
return map.value_list(mem::temp()) @inline;
|
||||
map.@each_entry(; Entry* entry) {
|
||||
@body(entry.key, entry.value);
|
||||
};
|
||||
}
|
||||
|
||||
fn Value[] HashMap.value_list(HashMap* map, Allocator* using = mem::heap())
|
||||
macro HashMap.@each_entry(map; @body(entry))
|
||||
{
|
||||
if (!map.count) return Value[] {};
|
||||
Value[] list = calloc(Value, map.count, .using = using);
|
||||
if (map.count)
|
||||
{
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
@body(entry);
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] HashMap.value_tlist(&map)
|
||||
{
|
||||
return map.value_new_list(mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn Value[] HashMap.value_new_list(&map, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (!map.count) return {};
|
||||
Value[] list = allocator.new_array(Value, map.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : map.table)
|
||||
{
|
||||
@@ -216,8 +260,7 @@ fn Value[] HashMap.value_list(HashMap* map, Allocator* using = mem::heap())
|
||||
return list;
|
||||
}
|
||||
|
||||
$if types::is_equatable(Value):
|
||||
fn bool HashMap.has_value(HashMap* map, Value v)
|
||||
fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
|
||||
{
|
||||
if (!map.count) return false;
|
||||
foreach (Entry* entry : map.table)
|
||||
@@ -230,22 +273,24 @@ fn bool HashMap.has_value(HashMap* map, Value v)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
$endif
|
||||
|
||||
// --- private methods
|
||||
|
||||
fn void HashMap.add_entry(HashMap* 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
|
||||
{
|
||||
Entry* entry = malloc(Entry, .using = map.allocator);
|
||||
Entry* entry = map.allocator.new(Entry);
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
*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(HashMap* 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;
|
||||
@@ -254,7 +299,7 @@ fn void HashMap.resize(HashMap* map, uint new_capacity) @private
|
||||
map.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
Entry*[] new_table = calloc(Entry*, new_capacity, .using = map.allocator);
|
||||
Entry*[] new_table = map.allocator.new_zero_array(Entry*, new_capacity);
|
||||
map.transfer(new_table);
|
||||
map.table = new_table;
|
||||
map.free_internal(old_table.ptr);
|
||||
@@ -263,8 +308,8 @@ fn void HashMap.resize(HashMap* map, uint new_capacity) @private
|
||||
|
||||
fn uint rehash(uint hash) @inline @private
|
||||
{
|
||||
hash ^= (hash >> 20) ^ (hash >> 12);
|
||||
return hash ^ ((hash >> 7) ^ (hash >> 4));
|
||||
hash ^= (hash >> 20) ^ (hash >> 12);
|
||||
return hash ^ ((hash >> 7) ^ (hash >> 4));
|
||||
}
|
||||
|
||||
macro uint index_for(uint hash, uint capacity) @private
|
||||
@@ -272,39 +317,39 @@ macro uint index_for(uint hash, uint capacity) @private
|
||||
return hash & (capacity - 1);
|
||||
}
|
||||
|
||||
fn void HashMap.transfer(HashMap* map, Entry*[] new_table) @private
|
||||
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);
|
||||
}
|
||||
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(HashMap* map, HashMap* other_map) @private
|
||||
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
|
||||
{
|
||||
if (!other_map.count) return;
|
||||
foreach (Entry *e : other_map.table)
|
||||
{
|
||||
if (!e) continue;
|
||||
map.put_for_create(e.key, e.value);
|
||||
}
|
||||
foreach (Entry *e : other_map.table)
|
||||
{
|
||||
if (!e) continue;
|
||||
map.put_for_create(e.key, e.value);
|
||||
}
|
||||
}
|
||||
|
||||
fn void HashMap.put_for_create(HashMap* 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);
|
||||
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))
|
||||
@@ -316,12 +361,12 @@ fn void HashMap.put_for_create(HashMap* map, Key key, Value value) @private
|
||||
map.create_entry(hash, key, value, i);
|
||||
}
|
||||
|
||||
fn void HashMap.free_internal(HashMap* map, void* ptr) @inline @private
|
||||
fn void HashMap.free_internal(&map, void* ptr) @inline @private
|
||||
{
|
||||
map.allocator.free(ptr)!!;
|
||||
map.allocator.free(ptr);
|
||||
}
|
||||
|
||||
fn bool HashMap.remove_entry_for_key(HashMap* map, Key key) @private
|
||||
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
@@ -341,7 +386,7 @@ fn bool HashMap.remove_entry_for_key(HashMap* map, Key key) @private
|
||||
{
|
||||
prev.next = next;
|
||||
}
|
||||
map.free_internal(e);
|
||||
map.free_entry(e);
|
||||
return true;
|
||||
}
|
||||
prev = e;
|
||||
@@ -350,19 +395,30 @@ fn bool HashMap.remove_entry_for_key(HashMap* map, Key key) @private
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void HashMap.create_entry(HashMap* 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];
|
||||
Entry* entry = malloc(Entry, .using = map.allocator);
|
||||
Entry* entry = map.allocator.new(Entry);
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
*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:
|
||||
self.allocator.free(entry.key);
|
||||
$endif
|
||||
self.free_internal(entry);
|
||||
}
|
||||
|
||||
struct Entry
|
||||
{
|
||||
uint hash;
|
||||
Key key;
|
||||
Value value;
|
||||
Entry* next;
|
||||
}
|
||||
}
|
||||
19
lib/std/collections/maybe.c3
Normal file
19
lib/std/collections/maybe.c3
Normal file
@@ -0,0 +1,19 @@
|
||||
module std::collections::maybe(<Type>);
|
||||
|
||||
struct Maybe
|
||||
{
|
||||
Type value;
|
||||
bool has_value;
|
||||
}
|
||||
|
||||
fn Maybe value(Type val)
|
||||
{
|
||||
return { .value = val, .has_value = true };
|
||||
}
|
||||
|
||||
const Maybe EMPTY = { };
|
||||
|
||||
macro Type! Maybe.get(self)
|
||||
{
|
||||
return self.has_value ? self.value : SearchResult.MISSING?;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ const Object TRUE_OBJECT = { .b = true, .type = bool.typeid };
|
||||
const Object FALSE_OBJECT = { .b = false, .type = bool.typeid };
|
||||
const Object NULL_OBJECT = { .type = void*.typeid };
|
||||
|
||||
struct Object
|
||||
struct Object (Printable)
|
||||
{
|
||||
typeid type;
|
||||
Allocator* allocator;
|
||||
@@ -27,59 +27,61 @@ struct Object
|
||||
}
|
||||
|
||||
|
||||
fn void! Object.to_format(Object* o, Formatter* formatter) @dynamic
|
||||
fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
switch (o.type)
|
||||
switch (self.type)
|
||||
{
|
||||
case void:
|
||||
formatter.printf("{}")!;
|
||||
return formatter.printf("{}")!;
|
||||
case void*:
|
||||
formatter.printf("null")!;
|
||||
return formatter.printf("null")!;
|
||||
case String:
|
||||
formatter.printf(`"%s"`, o.s)!;
|
||||
return formatter.printf(`"%s"`, self.s)!;
|
||||
case bool:
|
||||
formatter.printf(o.b ? "true" : "false")!;
|
||||
return formatter.printf(self.b ? "true" : "false")!;
|
||||
case ObjectInternalList:
|
||||
formatter.printf("[")!;
|
||||
foreach (i, ol : o.array)
|
||||
usz n = formatter.printf("[")!;
|
||||
foreach (i, ol : self.array)
|
||||
{
|
||||
formatter.printf(i == 0 ? " " : ", ")!;
|
||||
ol.to_format(formatter)!;
|
||||
if (i > 0) n += formatter.printf(",")!;
|
||||
n += ol.to_format(formatter)!;
|
||||
}
|
||||
formatter.printf(" ]")!;
|
||||
n += formatter.printf("]")!;
|
||||
return n;
|
||||
case ObjectInternalMap:
|
||||
formatter.printf("{")!;
|
||||
usz n = formatter.printf("{")!;
|
||||
@pool()
|
||||
{
|
||||
foreach (i, key : o.map.key_tlist())
|
||||
foreach (i, key : self.map.key_tlist())
|
||||
{
|
||||
formatter.printf(i == 0 ? " " : ", ")!;
|
||||
formatter.printf(`"%s": `, key)!;
|
||||
o.map.get(key).to_format(formatter)!;
|
||||
if (i > 0) n += formatter.printf(",")!;
|
||||
n += formatter.printf(`"%s":`, key)!;
|
||||
n += self.map.get(key).to_format(formatter)!;
|
||||
}
|
||||
};
|
||||
formatter.printf(" }")!;
|
||||
n += formatter.printf("}")!;
|
||||
return n;
|
||||
default:
|
||||
switch (o.type.kindof)
|
||||
switch (self.type.kindof)
|
||||
{
|
||||
case SIGNED_INT:
|
||||
formatter.printf("%d", o.i)!;
|
||||
return formatter.printf("%d", self.i)!;
|
||||
case UNSIGNED_INT:
|
||||
formatter.printf("%d", (uint128)o.i)!;
|
||||
return formatter.printf("%d", (uint128)self.i)!;
|
||||
case FLOAT:
|
||||
formatter.printf("%d", o.f)!;
|
||||
return formatter.printf("%d", self.f)!;
|
||||
case ENUM:
|
||||
formatter.printf("%d", o.i)!;
|
||||
return formatter.printf("%d", self.i)!;
|
||||
default:
|
||||
formatter.printf("<>")!;
|
||||
return formatter.printf("<>")!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn Object* new_obj(Allocator* using = mem::heap())
|
||||
fn Object* new_obj(Allocator* allocator)
|
||||
{
|
||||
Object* o = malloc(Object, .using = using);
|
||||
*o = { .allocator = using, .type = void.typeid };
|
||||
Object* o = allocator.new(Object);
|
||||
*o = { .allocator = allocator, .type = void.typeid };
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -88,31 +90,31 @@ fn Object* new_null()
|
||||
return &NULL_OBJECT;
|
||||
}
|
||||
|
||||
fn Object* new_int(int128 i, Allocator* using = mem::heap())
|
||||
fn Object* new_int(int128 i, Allocator* allocator)
|
||||
{
|
||||
Object* o = malloc(Object, .using = using);
|
||||
*o = { .i = i, .allocator = using, .type = int128.typeid };
|
||||
Object* o = allocator.new(Object);
|
||||
*o = { .i = i, .allocator = allocator, .type = int128.typeid };
|
||||
return o;
|
||||
}
|
||||
|
||||
macro Object* new_enum(e, Allocator* using = mem::heap())
|
||||
macro Object* new_enum(e, Allocator* allocator)
|
||||
{
|
||||
Object* o = malloc(Object, .using = using);
|
||||
*o = { .i = (int128)e, .allocator = using, .type = $typeof(e).typeid };
|
||||
Object* o = allocator.new(Object);
|
||||
*o = { .i = (int128)e, .allocator = allocator, .type = @typeid(e) };
|
||||
return o;
|
||||
}
|
||||
|
||||
fn Object* new_float(double f, Allocator* using = mem::current_allocator())
|
||||
fn Object* new_float(double f, Allocator* allocator)
|
||||
{
|
||||
Object* o = malloc(Object, .using = using);
|
||||
*o = { .f = f, .allocator = using, .type = double.typeid };
|
||||
Object* o = allocator.new(Object);
|
||||
*o = { .f = f, .allocator = allocator, .type = double.typeid };
|
||||
return o;
|
||||
}
|
||||
|
||||
fn Object* new_string(String s, Allocator* using = mem::heap())
|
||||
fn Object* new_string(String s, Allocator* allocator)
|
||||
{
|
||||
Object* o = malloc(Object, .using = using);
|
||||
*o = { .s = s.copy(using), .allocator = using, .type = String.typeid };
|
||||
Object* o = allocator.new(Object);
|
||||
*o = { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid };
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -122,187 +124,190 @@ fn Object* new_bool(bool b)
|
||||
return b ? &TRUE_OBJECT : &FALSE_OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] o
|
||||
**/
|
||||
fn void Object.free(Object* o)
|
||||
fn void Object.free(&self)
|
||||
{
|
||||
switch (o.type)
|
||||
switch (self.type)
|
||||
{
|
||||
case void:
|
||||
break;
|
||||
case String:
|
||||
free(o.s, .using = o.allocator);
|
||||
self.allocator.free(self.s);
|
||||
case ObjectInternalList:
|
||||
foreach (ol : o.array)
|
||||
foreach (ol : self.array)
|
||||
{
|
||||
ol.free();
|
||||
}
|
||||
o.array.free();
|
||||
self.array.free();
|
||||
case ObjectInternalMap:
|
||||
@pool()
|
||||
{
|
||||
foreach (key : o.map.key_tlist())
|
||||
{
|
||||
o.map.get(key).free();
|
||||
free(key, .using = o.allocator);
|
||||
}
|
||||
o.map.free();
|
||||
self.map.@each_entry(; ObjectInternalMapEntry* entry) {
|
||||
self.allocator.free(entry.key);
|
||||
entry.value.free();
|
||||
};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (o.allocator) free(o, .using = o.allocator);
|
||||
if (self.allocator) self.allocator.free(self);
|
||||
}
|
||||
|
||||
fn bool Object.is_null(Object* this) @inline => this == &NULL_OBJECT;
|
||||
fn bool Object.is_empty(Object* this) @inline => this.type == void.typeid;
|
||||
fn bool Object.is_map(Object* this) @inline => this.type == ObjectInternalMap.typeid;
|
||||
fn bool Object.is_array(Object* this) @inline => this.type == ObjectInternalList.typeid;
|
||||
fn bool Object.is_bool(Object* this) @inline => this.type == bool.typeid;
|
||||
fn bool Object.is_string(Object* this) @inline => this.type == String.typeid;
|
||||
fn bool Object.is_float(Object* this) @inline => this.type == double.typeid;
|
||||
fn bool Object.is_int(Object* this) @inline => this.type == int128.typeid;
|
||||
fn bool Object.is_keyable(Object* this) => this.is_empty() || this.is_map();
|
||||
fn bool Object.is_indexable(Object* this) => this.is_empty() || this.is_array();
|
||||
fn bool Object.is_null(&self) @inline => self == &NULL_OBJECT;
|
||||
fn bool Object.is_empty(&self) @inline => self.type == void.typeid;
|
||||
fn bool Object.is_map(&self) @inline => self.type == ObjectInternalMap.typeid;
|
||||
fn bool Object.is_array(&self) @inline => self.type == ObjectInternalList.typeid;
|
||||
fn bool Object.is_bool(&self) @inline => self.type == bool.typeid;
|
||||
fn bool Object.is_string(&self) @inline => self.type == String.typeid;
|
||||
fn bool Object.is_float(&self) @inline => self.type == double.typeid;
|
||||
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 o.is_keyable()
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
fn void Object.init_map_if_needed(Object* o) @private
|
||||
fn void Object.init_map_if_needed(&self) @private
|
||||
{
|
||||
if (o.is_empty())
|
||||
if (self.is_empty())
|
||||
{
|
||||
o.type = ObjectInternalMap.typeid;
|
||||
o.map.init(.using = o.allocator);
|
||||
self.type = ObjectInternalMap.typeid;
|
||||
self.map.init_new(.allocator = self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
fn void Object.init_array_if_needed(Object* o) @private
|
||||
fn void Object.init_array_if_needed(&self) @private
|
||||
{
|
||||
if (o.is_empty())
|
||||
if (self.is_empty())
|
||||
{
|
||||
o.type = ObjectInternalList.typeid;
|
||||
o.array.init(.using = o.allocator);
|
||||
self.type = ObjectInternalList.typeid;
|
||||
self.array.init_new(.allocator = self.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
fn void Object.set_object(Object* o, String key, Object* new_object) @private
|
||||
fn void Object.set_object(&self, String key, Object* new_object) @private
|
||||
{
|
||||
o.init_map_if_needed();
|
||||
ObjectInternalMapEntry*! entry = o.map.get_entry(key);
|
||||
self.init_map_if_needed();
|
||||
ObjectInternalMapEntry*! entry = self.map.get_entry(key);
|
||||
defer
|
||||
{
|
||||
(void)free(entry.key, .using = o.allocator);
|
||||
entry.value.free();
|
||||
(void)self.allocator.free(entry.key);
|
||||
(void)entry.value.free();
|
||||
}
|
||||
o.map.set(key.copy(o.map.allocator), new_object);
|
||||
self.map.set(key.copy(self.map.allocator), new_object);
|
||||
}
|
||||
|
||||
macro Object* object_from_value(value) @private
|
||||
macro Object* Object.object_from_value(&self, value) @private
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
|
||||
$switch
|
||||
$case types::is_int($Type):
|
||||
return new_int(value);
|
||||
$case types::is_float($Type):
|
||||
return new_float(value);
|
||||
$case $Type.typeid == String.typeid:
|
||||
return new_string(value);
|
||||
$case $Type.typeid == bool.typeid:
|
||||
return new_bool(value);
|
||||
$case $Type.typeid == Object*.typeid:
|
||||
return value;
|
||||
$case $Type.typeid == void*.typeid:
|
||||
assert(value == null);
|
||||
return &NULL_OBJECT;
|
||||
$case $checks(String s = value):
|
||||
return new_string(value);
|
||||
$default:
|
||||
$error "Unsupported object type.";
|
||||
$endswitch
|
||||
$switch
|
||||
$case types::is_int($Type):
|
||||
return new_int(value, self.allocator);
|
||||
$case types::is_float($Type):
|
||||
return new_float(value, self.allocator);
|
||||
$case $Type.typeid == String.typeid:
|
||||
return new_string(value, self.allocator);
|
||||
$case $Type.typeid == bool.typeid:
|
||||
return new_bool(value);
|
||||
$case $Type.typeid == Object*.typeid:
|
||||
return value;
|
||||
$case $Type.typeid == void*.typeid:
|
||||
if (value != null) return CastResult.TYPE_MISMATCH?;
|
||||
return &NULL_OBJECT;
|
||||
$case $assignable(value, String):
|
||||
return new_string(value, self.allocator);
|
||||
$default:
|
||||
$error "Unsupported object type.";
|
||||
$endswitch
|
||||
|
||||
}
|
||||
|
||||
macro Object* Object.set(Object* o, String key, value)
|
||||
macro Object* Object.set(&self, String key, value)
|
||||
{
|
||||
Object* val = object_from_value(value);
|
||||
o.set_object(key, val);
|
||||
Object* val = self.object_from_value(value);
|
||||
self.set_object(key, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
macro Object* Object.set_at(Object* o, usz index, String key, value)
|
||||
macro Object* Object.set_at(&self, usz index, String key, value)
|
||||
{
|
||||
Object* val = object_from_value(value);
|
||||
o.set_object_at(key, index, val);
|
||||
Object* val = self.object_from_value(value);
|
||||
self.set_object_at(key, index, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @require self.is_indexable()
|
||||
* @ensure return != null
|
||||
**/
|
||||
macro Object* Object.append(Object* o, value)
|
||||
macro Object* Object.append(&self, value)
|
||||
{
|
||||
Object* val = object_from_value(value);
|
||||
o.append_object(val);
|
||||
Object* val = self.object_from_value(value);
|
||||
self.append_object(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
fn Object*! Object.get(Object* o, String key) => o.is_empty() ? SearchResult.MISSING? : o.map.get(key);
|
||||
fn Object*! Object.get(&self, String key) => self.is_empty() ? SearchResult.MISSING? : self.map.get(key);
|
||||
|
||||
|
||||
fn bool Object.has_key(Object* o, String key) => o.is_map() && o.map.has_key(key);
|
||||
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
fn Object* Object.get_at(Object* o, usz index)
|
||||
fn Object* Object.get_at(&self, usz index)
|
||||
{
|
||||
return o.array.get(index);
|
||||
return self.array.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
fn void Object.append_object(Object* o, Object* to_append)
|
||||
fn usz Object.get_len(&self)
|
||||
{
|
||||
o.init_array_if_needed();
|
||||
o.array.append(to_append);
|
||||
return self.array.len();
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
fn void Object.set_object_at(Object* o, usz index, Object* to_set)
|
||||
fn void Object.append_object(&self, Object* to_append)
|
||||
{
|
||||
o.init_array_if_needed();
|
||||
while (o.array.len() < index)
|
||||
self.init_array_if_needed();
|
||||
self.array.append(to_append);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
fn void Object.set_object_at(&self, usz index, Object* to_set)
|
||||
{
|
||||
self.init_array_if_needed();
|
||||
while (self.array.len() < index)
|
||||
{
|
||||
o.array.append(&NULL_OBJECT);
|
||||
self.array.append(&NULL_OBJECT);
|
||||
}
|
||||
if (o.array.len() == index)
|
||||
if (self.array.len() == index)
|
||||
{
|
||||
o.array.append(to_set);
|
||||
self.array.append(to_set);
|
||||
return;
|
||||
}
|
||||
o.array.get(index).free();
|
||||
o.array.set_at(index, to_set);
|
||||
self.array.get(index).free();
|
||||
self.array.set_at(index, to_set);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require $Type.kindof.is_int() "Expected an integer type."
|
||||
**/
|
||||
macro get_integer_value(Object* value, $Type)
|
||||
{
|
||||
if (value.is_float())
|
||||
@@ -321,113 +326,116 @@ macro get_integer_value(Object* value, $Type)
|
||||
return ($Type)value.i;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @require self.is_indexable()
|
||||
* @require $Type.kindof.is_int() : "Expected an integer type"
|
||||
**/
|
||||
macro Object.get_integer_at(Object* o, $Type, usz index) @private
|
||||
macro Object.get_integer_at(&self, $Type, usz index) @private
|
||||
{
|
||||
return get_integer_value(o.get_at(index), $Type);
|
||||
return get_integer_value(self.get_at(index), $Type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
* @require self.is_keyable()
|
||||
* @require $Type.kindof.is_int() : "Expected an integer type"
|
||||
**/
|
||||
macro Object.get_integer(Object* o, $Type, String key) @private
|
||||
macro Object.get_integer(&self, $Type, String key) @private
|
||||
{
|
||||
return get_integer_value(o.get(key), $Type);
|
||||
return get_integer_value(self.get(key), $Type);
|
||||
}
|
||||
|
||||
fn ichar! Object.get_ichar(Object* o, String key) => o.get_integer(ichar, key);
|
||||
fn short! Object.get_short(Object* o, String key) => o.get_integer(short, key);
|
||||
fn int! Object.get_int(Object* o, String key) => o.get_integer(int, key);
|
||||
fn long! Object.get_long(Object* o, String key) => o.get_integer(long, key);
|
||||
fn int128! Object.get_int128(Object* o, String key) => o.get_integer(int128, key);
|
||||
fn ichar! Object.get_ichar(&self, String key) => self.get_integer(ichar, key);
|
||||
fn short! Object.get_short(&self, String key) => self.get_integer(short, key);
|
||||
fn int! Object.get_int(&self, String key) => self.get_integer(int, key);
|
||||
fn long! Object.get_long(&self, String key) => self.get_integer(long, key);
|
||||
fn int128! Object.get_int128(&self, String key) => self.get_integer(int128, key);
|
||||
|
||||
fn ichar! Object.get_ichar_at(Object* o, usz index) => o.get_integer_at(ichar, index);
|
||||
fn short! Object.get_short_at(Object* o, usz index) => o.get_integer_at(short, index);
|
||||
fn int! Object.get_int_at(Object* o, usz index) => o.get_integer_at(int, index);
|
||||
fn long! Object.get_long_at(Object* o, usz index) => o.get_integer_at(long, index);
|
||||
fn int128! Object.get_int128_at(Object* o, usz index) => o.get_integer_at(int128, index);
|
||||
fn ichar! Object.get_ichar_at(&self, usz index) => self.get_integer_at(ichar, index);
|
||||
fn short! Object.get_short_at(&self, usz index) => self.get_integer_at(short, index);
|
||||
fn int! Object.get_int_at(&self, usz index) => self.get_integer_at(int, index);
|
||||
fn long! Object.get_long_at(&self, usz index) => self.get_integer_at(long, index);
|
||||
fn int128! Object.get_int128_at(&self, usz index) => self.get_integer_at(int128, index);
|
||||
|
||||
fn char! Object.get_char(Object* o, String key) => o.get_integer(ichar, key);
|
||||
fn short! Object.get_ushort(Object* o, String key) => o.get_integer(ushort, key);
|
||||
fn uint! Object.get_uint(Object* o, String key) => o.get_integer(uint, key);
|
||||
fn ulong! Object.get_ulong(Object* o, String key) => o.get_integer(ulong, key);
|
||||
fn uint128! Object.get_uint128(Object* o, String key) => o.get_integer(uint128, key);
|
||||
fn char! Object.get_char(&self, String key) => self.get_integer(ichar, key);
|
||||
fn short! Object.get_ushort(&self, String key) => self.get_integer(ushort, key);
|
||||
fn uint! Object.get_uint(&self, String key) => self.get_integer(uint, key);
|
||||
fn ulong! Object.get_ulong(&self, String key) => self.get_integer(ulong, key);
|
||||
fn uint128! Object.get_uint128(&self, String key) => self.get_integer(uint128, key);
|
||||
|
||||
fn char! Object.get_char_at(Object* o, usz index) => o.get_integer_at(char, index);
|
||||
fn ushort! Object.get_ushort_at(Object* o, usz index) => o.get_integer_at(ushort, index);
|
||||
fn uint! Object.get_uint_at(Object* o, usz index) => o.get_integer_at(uint, index);
|
||||
fn ulong! Object.get_ulong_at(Object* o, usz index) => o.get_integer_at(ulong, index);
|
||||
fn uint128! Object.get_uint128_at(Object* o, usz index) => o.get_integer_at(uint128, index);
|
||||
fn char! Object.get_char_at(&self, usz index) => self.get_integer_at(char, index);
|
||||
fn ushort! Object.get_ushort_at(&self, usz index) => self.get_integer_at(ushort, index);
|
||||
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 o.is_keyable()
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
fn String! Object.get_string(Object* o, String key)
|
||||
fn String! Object.get_string(&self, String key)
|
||||
{
|
||||
Object* value = o.get(key)!;
|
||||
assert(value.is_string());
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
fn String Object.get_string_at(Object* o, usz index)
|
||||
fn String! Object.get_string_at(&self, usz index)
|
||||
{
|
||||
Object* value = o.get_at(index);
|
||||
assert(value.is_string());
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
macro String! Object.get_enum(Object* o, $EnumType, String key)
|
||||
macro String! Object.get_enum(&self, $EnumType, String key)
|
||||
{
|
||||
Object value = o.get(key)!;
|
||||
assert($EnumType.typeid == value.type);
|
||||
Object value = self.get(key)!;
|
||||
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
macro String Object.get_enum_at(Object* o, $EnumType, usz index)
|
||||
macro String! Object.get_enum_at(&self, $EnumType, usz index)
|
||||
{
|
||||
Object value = o.get_at(index);
|
||||
assert($EnumType.typeid == value.type);
|
||||
Object value = self.get_at(index);
|
||||
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
|
||||
return ($EnumType)value.i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
fn bool! Object.get_bool(Object* o, String key)
|
||||
fn bool! Object.get_bool(&self, String key)
|
||||
{
|
||||
Object* value = o.get(key)!;
|
||||
assert(value.is_bool());
|
||||
Object* value = self.get(key)!;
|
||||
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
fn bool Object.get_bool_at(Object* o, usz index)
|
||||
fn bool! Object.get_bool_at(&self, usz index)
|
||||
{
|
||||
Object* value = o.get_at(index);
|
||||
assert(value.is_bool());
|
||||
Object* value = self.get_at(index);
|
||||
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
|
||||
return value.b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_keyable()
|
||||
* @require self.is_keyable()
|
||||
**/
|
||||
fn double! Object.get_float(Object* o, String key)
|
||||
fn double! Object.get_float(&self, String key)
|
||||
{
|
||||
Object* value = o.get(key)!;
|
||||
Object* value = self.get(key)!;
|
||||
switch (value.type.kindof)
|
||||
{
|
||||
case SIGNED_INT:
|
||||
@@ -437,16 +445,16 @@ fn double! Object.get_float(Object* o, String key)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
unreachable();
|
||||
return CastResult.TYPE_MISMATCH?;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require o.is_indexable()
|
||||
* @require self.is_indexable()
|
||||
**/
|
||||
fn double Object.get_float_at(Object* o, usz index)
|
||||
fn double! Object.get_float_at(&self, usz index)
|
||||
{
|
||||
Object* value = o.get_at(index);
|
||||
Object* value = self.get_at(index);
|
||||
switch (value.type.kindof)
|
||||
{
|
||||
case SIGNED_INT:
|
||||
@@ -456,19 +464,19 @@ fn double Object.get_float_at(Object* o, usz index)
|
||||
case FLOAT:
|
||||
return value.f;
|
||||
default:
|
||||
unreachable();
|
||||
return CastResult.TYPE_MISMATCH?;
|
||||
}
|
||||
}
|
||||
|
||||
fn Object* Object.get_or_create_obj(Object* o, String key)
|
||||
fn Object* Object.get_or_create_obj(&self, String key)
|
||||
{
|
||||
if (try obj = o.get(key) && !obj.is_null()) return obj;
|
||||
Object* container = new_obj();
|
||||
o.set(key, container);
|
||||
if (try obj = self.get(key) && !obj.is_null()) return obj;
|
||||
Object* container = new_obj(self.allocator);
|
||||
self.set(key, container);
|
||||
return container;
|
||||
}
|
||||
|
||||
def ObjectInternalMap @private = HashMap<String, Object*>;
|
||||
def ObjectInternalList @private = List<Object*>;
|
||||
def ObjectInternalMapEntry @private = Entry<String, Object*>;
|
||||
def ObjectInternalMap = HashMap(<String, Object*>) @private;
|
||||
def ObjectInternalList = List(<Object*>) @private;
|
||||
def ObjectInternalMapEntry = Entry(<String, Object*>) @private;
|
||||
|
||||
|
||||
@@ -20,94 +20,126 @@
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
module std::collections::priorityqueue<Type>;
|
||||
module std::collections::priorityqueue(<Type>);
|
||||
import std::collections::priorityqueue::private;
|
||||
|
||||
distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
|
||||
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);
|
||||
|
||||
module std::collections::priorityqueue::private(<Type, MAX>);
|
||||
import std::collections::list;
|
||||
|
||||
def Heap = List<Type>;
|
||||
def Heap = List(<Type>);
|
||||
|
||||
struct PriorityQueue
|
||||
struct PrivatePriorityQueue (Printable)
|
||||
{
|
||||
Heap heap;
|
||||
bool max; // true if max-heap, false if min-heap
|
||||
}
|
||||
|
||||
fn void PriorityQueue.push(PriorityQueue* pq, Type element)
|
||||
fn void PrivatePriorityQueue.init_new(&self, usz initial_capacity = 16, Allocator* allocator = mem::heap()) @inline
|
||||
{
|
||||
pq.heap.push(element);
|
||||
usz i = pq.heap.len() - 1;
|
||||
self.heap.init_new(initial_capacity, allocator);
|
||||
}
|
||||
|
||||
fn void PrivatePriorityQueue.init_temp(&self, usz initial_capacity = 16) @inline
|
||||
{
|
||||
self.heap.init_new(initial_capacity, mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn void PrivatePriorityQueue.push(&self, Type element)
|
||||
{
|
||||
self.heap.push(element);
|
||||
usz i = self.heap.len() - 1;
|
||||
while (i > 0)
|
||||
{
|
||||
usz parent = (i - 1) / 2;
|
||||
if ((pq.max && greater(pq.heap.get(i), pq.heap.get(parent))) || (!pq.max && less(pq.heap.get(i), pq.heap.get(parent))))
|
||||
{
|
||||
pq.heap.swap(i, parent);
|
||||
i = parent;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
Type item = self.heap[i];
|
||||
Type parent_item = self.heap[parent];
|
||||
$if MAX:
|
||||
bool ok = greater(item, parent_item);
|
||||
$else
|
||||
bool ok = less(item, parent_item);
|
||||
$endif
|
||||
if (!ok) break;
|
||||
self.heap.swap(i, parent);
|
||||
i = parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require pq != null
|
||||
* @require self != null
|
||||
*/
|
||||
fn Type! PriorityQueue.pop(PriorityQueue* pq)
|
||||
fn Type! PrivatePriorityQueue.pop(&self)
|
||||
{
|
||||
usz i = 0;
|
||||
usz len = pq.heap.len() @inline;
|
||||
usz len = self.heap.len();
|
||||
if (!len) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
usz newCount = len - 1;
|
||||
pq.heap.swap(0, newCount);
|
||||
self.heap.swap(0, newCount);
|
||||
while ((2 * i + 1) < newCount)
|
||||
{
|
||||
usz j = 2 * i + 1;
|
||||
if (((j + 1) < newCount) &&
|
||||
((pq.max && greater(pq.heap.get(j + 1), pq.heap[j]))
|
||||
|| (!pq.max && less(pq.heap.get(j + 1), pq.heap.get(j)))))
|
||||
Type itemj = self.heap[j];
|
||||
if ((j + 1) < newCount)
|
||||
{
|
||||
j++;
|
||||
Type nextj = self.heap[j + 1];
|
||||
$if MAX:
|
||||
bool ok = greater(nextj, itemj);
|
||||
$else
|
||||
bool ok = less(nextj, itemj);
|
||||
$endif
|
||||
if (ok) j++;
|
||||
}
|
||||
if ((pq.max && less(pq.heap.get(i), pq.heap.get(j))) || (!pq.max && greater(pq.heap.get(i), pq.heap.get(j))))
|
||||
{
|
||||
pq.heap.swap(i, j);
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
Type item = self.heap[i];
|
||||
$if MAX:
|
||||
bool ok = less(item, itemj);
|
||||
$else
|
||||
bool ok = greater(item, itemj);
|
||||
$endif
|
||||
if (!ok) break;
|
||||
self.heap.swap(i, j);
|
||||
i = j;
|
||||
}
|
||||
|
||||
return pq.heap.pop();
|
||||
return self.heap.pop();
|
||||
}
|
||||
|
||||
fn Type! PrivatePriorityQueue.peek(&self)
|
||||
{
|
||||
if (!self.len()) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return self.heap.get(0);
|
||||
}
|
||||
|
||||
fn void PrivatePriorityQueue.free(&self)
|
||||
{
|
||||
self.heap.free();
|
||||
}
|
||||
|
||||
fn usz PrivatePriorityQueue.len(&self) @operator(len)
|
||||
{
|
||||
return self.heap.len();
|
||||
}
|
||||
|
||||
fn bool PrivatePriorityQueue.is_empty(&self)
|
||||
{
|
||||
return self.heap.is_empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @require pq != null
|
||||
* @require index < self.len()
|
||||
*/
|
||||
fn Type! PriorityQueue.peek(PriorityQueue* pq)
|
||||
fn Type PrivatePriorityQueue.peek_at(&self, usz index) @operator([])
|
||||
{
|
||||
if (!pq.len()) return IteratorResult.NO_MORE_ELEMENT?;
|
||||
return pq.heap.get(0);
|
||||
return self.heap[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @require pq != null
|
||||
*/
|
||||
fn void PriorityQueue.free(PriorityQueue* pq)
|
||||
fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
pq.heap.free();
|
||||
return self.heap.to_format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require pq != null
|
||||
*/
|
||||
fn usz PriorityQueue.len(PriorityQueue* pq) @operator(len)
|
||||
fn String PrivatePriorityQueue.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
return pq.heap.len();
|
||||
return self.heap.to_new_string(allocator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require pq != null, index < pq.len()
|
||||
*/
|
||||
fn Type PriorityQueue.peek_at(PriorityQueue* pq, usz index) @operator([])
|
||||
{
|
||||
return pq.heap[index];
|
||||
}
|
||||
|
||||
84
lib/std/collections/range.c3
Normal file
84
lib/std/collections/range.c3
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* @require Type.is_ordered : "The type must be ordered"
|
||||
**/
|
||||
module std::collections::range(<Type>);
|
||||
|
||||
struct Range (Printable)
|
||||
{
|
||||
Type start;
|
||||
Type end;
|
||||
}
|
||||
|
||||
fn usz Range.len(&self) @operator(len)
|
||||
{
|
||||
if (self.end < self.start) return 0;
|
||||
return (usz)(self.end - self.start) + 1;
|
||||
}
|
||||
|
||||
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"
|
||||
**/
|
||||
fn Type Range.get(&self, usz index) @operator([])
|
||||
{
|
||||
return (Type)(self.start + (usz)index);
|
||||
}
|
||||
|
||||
fn String Range.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
return string::new_format("[%s..%s]", self.start, self.end, .allocator = allocator);
|
||||
}
|
||||
|
||||
fn String Range.to_tstring(&self)
|
||||
{
|
||||
return self.to_new_string(mem::temp());
|
||||
}
|
||||
|
||||
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
return formatter.printf("[%s..%s]", self.start, self.end)!;
|
||||
}
|
||||
|
||||
struct ExclusiveRange (Printable)
|
||||
{
|
||||
Type start;
|
||||
Type end;
|
||||
}
|
||||
|
||||
fn usz ExclusiveRange.len(&self) @operator(len)
|
||||
{
|
||||
if (self.end < self.start) return 0;
|
||||
return (usz)(self.end - self.start);
|
||||
}
|
||||
|
||||
fn bool ExclusiveRange.contains(&self, Type value) @inline
|
||||
{
|
||||
return value >= self.start && value < self.end;
|
||||
}
|
||||
|
||||
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 = mem::heap()) @dynamic
|
||||
{
|
||||
return string::new_format("[%s..<%s]", self.start, self.end, .allocator = allocator);
|
||||
}
|
||||
|
||||
fn String ExclusiveRange.to_tstring(&self)
|
||||
{
|
||||
return self.to_new_string(mem::temp());
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < self.len() : "Can't index into an empty range"
|
||||
**/
|
||||
fn Type ExclusiveRange.get(&self, usz index) @operator([])
|
||||
{
|
||||
return (Type)(self.start + index);
|
||||
}
|
||||
103
lib/std/collections/ringbuffer.c3
Normal file
103
lib/std/collections/ringbuffer.c3
Normal file
@@ -0,0 +1,103 @@
|
||||
module std::collections::ringbuffer(<Type, SIZE>);
|
||||
|
||||
struct RingBuffer
|
||||
{
|
||||
Type[SIZE] buf;
|
||||
usz written;
|
||||
usz head;
|
||||
}
|
||||
|
||||
fn void RingBuffer.init(&self) @inline
|
||||
{
|
||||
*self = {};
|
||||
}
|
||||
|
||||
fn void RingBuffer.putc(&self, Type c)
|
||||
{
|
||||
if (self.written < SIZE)
|
||||
{
|
||||
self.buf[self.written] = c;
|
||||
self.written++;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.buf[self.head] = c;
|
||||
self.head = (self.head + 1) % SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
fn Type RingBuffer.getc(&self, usz index)
|
||||
{
|
||||
index %= SIZE;
|
||||
usz avail = SIZE - self.head;
|
||||
if (index < avail)
|
||||
{
|
||||
return self.buf[self.head + index];
|
||||
}
|
||||
return self.buf[index - avail];
|
||||
}
|
||||
|
||||
fn Type! RingBuffer.popc(&self)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case self.written == 0:
|
||||
return SearchResult.MISSING?;
|
||||
case self.written < SIZE:
|
||||
self.written--;
|
||||
return self.buf[self.written];
|
||||
default:
|
||||
self.head = (self.head - 1) % SIZE;
|
||||
return self.buf[self.head];
|
||||
}
|
||||
}
|
||||
|
||||
fn usz RingBuffer.get(&self, usz index, Type[] buffer)
|
||||
{
|
||||
index %= SIZE;
|
||||
if (self.written < SIZE)
|
||||
{
|
||||
if (index >= self.written) return 0;
|
||||
usz end = self.written - index;
|
||||
usz n = min(end, buffer.len);
|
||||
buffer[:n] = self.buf[index:n];
|
||||
return n;
|
||||
}
|
||||
usz end = SIZE - self.head;
|
||||
if (index >= end)
|
||||
{
|
||||
index -= end;
|
||||
if (index >= self.head) return 0;
|
||||
usz n = min(self.head - index, buffer.len);
|
||||
buffer[:n] = self.buf[index:n];
|
||||
return n;
|
||||
}
|
||||
if (buffer.len <= SIZE - index)
|
||||
{
|
||||
usz n = buffer.len;
|
||||
buffer[:n] = self.buf[self.head + index:n];
|
||||
return n;
|
||||
}
|
||||
usz n1 = SIZE - index;
|
||||
buffer[:n1] = self.buf[self.head + index:n1];
|
||||
buffer = buffer[n1..];
|
||||
index -= n1;
|
||||
usz n2 = min(self.head - index, buffer.len);
|
||||
buffer[:n2] = self.buf[index:n2];
|
||||
return n1 + n2;
|
||||
}
|
||||
|
||||
fn void RingBuffer.push(&self, Type[] buffer)
|
||||
{
|
||||
usz i;
|
||||
while (self.written < SIZE && i < buffer.len)
|
||||
{
|
||||
self.buf[self.written] = buffer[i++];
|
||||
self.written++;
|
||||
}
|
||||
foreach (c : buffer[i..])
|
||||
{
|
||||
self.buf[self.head] = c;
|
||||
self.head = (self.head + 1) % SIZE;
|
||||
}
|
||||
}
|
||||
16
lib/std/collections/tuple.c3
Normal file
16
lib/std/collections/tuple.c3
Normal file
@@ -0,0 +1,16 @@
|
||||
module std::collections::tuple(<Type1, Type2>);
|
||||
|
||||
struct Tuple
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
}
|
||||
|
||||
module std::collections::triple(<Type1, Type2, Type3>);
|
||||
|
||||
struct Triple
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
Type3 third;
|
||||
}
|
||||
@@ -3,147 +3,114 @@
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::mem::allocator;
|
||||
|
||||
struct ArenaAllocator
|
||||
struct ArenaAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
char[] data;
|
||||
usz used;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a memory arena for use using the provided bytes.
|
||||
*
|
||||
* @require this != null
|
||||
**/
|
||||
fn void ArenaAllocator.init(ArenaAllocator* this, char[] data)
|
||||
fn void ArenaAllocator.init(&self, char[] data)
|
||||
{
|
||||
this.function = &arena_allocator_function;
|
||||
this.data = data;
|
||||
this.used = 0;
|
||||
self.data = data;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require this != null
|
||||
**/
|
||||
fn void ArenaAllocator.reset(ArenaAllocator* this)
|
||||
fn void ArenaAllocator.clear(&self)
|
||||
{
|
||||
this.used = 0;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
struct ArenaAllocatorHeader
|
||||
struct ArenaAllocatorHeader @local
|
||||
{
|
||||
usz size;
|
||||
char[*] data;
|
||||
}
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require data `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! arena_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
|
||||
fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
{
|
||||
ArenaAllocator* arena = (ArenaAllocator*)data;
|
||||
bool clear = false;
|
||||
switch (kind)
|
||||
if (!ptr) return;
|
||||
assert((uptr)ptr >= (uptr)self.data.ptr, "Pointer originates from a different allocator.");
|
||||
ArenaAllocatorHeader* header = ptr - ArenaAllocatorHeader.sizeof;
|
||||
// Reclaim memory if it's the last element.
|
||||
if (ptr + header.size == &self.data[self.used])
|
||||
{
|
||||
case CALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
clear = true;
|
||||
nextcase;
|
||||
case ALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
assert(!old_pointer, "Unexpected old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
void* mem = arena._alloc(size, alignment, offset)!;
|
||||
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
case ALIGNED_REALLOC:
|
||||
case REALLOC:
|
||||
if (!size) nextcase FREE;
|
||||
if (!old_pointer) nextcase ALLOC;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
return arena._realloc(old_pointer, size, alignment, offset)!;
|
||||
case ALIGNED_FREE:
|
||||
case FREE:
|
||||
if (!old_pointer) return null;
|
||||
assert((uptr)old_pointer >= (uptr)arena.data.ptr, "Pointer originates from a different allocator.");
|
||||
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
|
||||
// Reclaim memory if it's the last element.
|
||||
if (old_pointer + header.size == &arena.data[arena.used])
|
||||
{
|
||||
arena.used -= header.size + ArenaAllocatorHeader.sizeof;
|
||||
}
|
||||
return null;
|
||||
case MARK:
|
||||
return (void*)(uptr)arena.used;
|
||||
case RESET:
|
||||
arena.used = size;
|
||||
return null;
|
||||
self.used -= header.size + ArenaAllocatorHeader.sizeof;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
fn usz ArenaAllocator.mark(&self) @dynamic => self.used;
|
||||
fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark;
|
||||
|
||||
/**
|
||||
* @require alignment > 0 `alignment must be non zero`
|
||||
* @require math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
|
||||
* @require this != null
|
||||
**/
|
||||
fn void*! ArenaAllocator._alloc(ArenaAllocator* this, usz size, usz alignment, usz offset) @private
|
||||
fn void*! ArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
usz total_len = this.data.len;
|
||||
if (!size) return null;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
|
||||
void* start_mem = this.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + this.used + ArenaAllocatorHeader.sizeof + offset;
|
||||
void* start_mem = self.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof + offset;
|
||||
void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(aligned_pointer_to_offset - this.data.ptr) + size - offset;
|
||||
usz end = (usz)(aligned_pointer_to_offset - self.data.ptr) + size - offset;
|
||||
if (end > total_len) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
this.used = end;
|
||||
self.used = end;
|
||||
void* mem = aligned_pointer_to_offset - offset;
|
||||
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
|
||||
header.size = size;
|
||||
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require alignment > 0 `alignment must be non zero`
|
||||
* @require math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
|
||||
* @require this != null
|
||||
**/
|
||||
fn void*! ArenaAllocator._realloc(ArenaAllocator* this, void *old_pointer, usz size, usz alignment, usz offset) @private
|
||||
fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
assert(old_pointer >= this.data.ptr, "Pointer originates from a different allocator.");
|
||||
usz total_len = this.data.len;
|
||||
if (!size)
|
||||
{
|
||||
self.release(old_pointer, alignment > 0);
|
||||
return null;
|
||||
}
|
||||
if (!old_pointer)
|
||||
{
|
||||
return self.acquire(size, true, alignment, offset);
|
||||
}
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
|
||||
usz total_len = self.data.len;
|
||||
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
|
||||
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
// Do last allocation and alignment match?
|
||||
if (&this.data[this.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer + offset, alignment))
|
||||
if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer + offset, alignment))
|
||||
{
|
||||
if (old_size >= size)
|
||||
{
|
||||
this.used -= old_size - size;
|
||||
}
|
||||
else
|
||||
{
|
||||
usz new_used = this.used + size - old_size;
|
||||
if (new_used > total_len) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
this.used = new_used;
|
||||
}
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
{
|
||||
self.used -= old_size - size;
|
||||
}
|
||||
else
|
||||
{
|
||||
usz new_used = self.used + size - old_size;
|
||||
if (new_used > total_len) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
self.used = new_used;
|
||||
}
|
||||
header.size = size;
|
||||
return old_pointer;
|
||||
}
|
||||
// Otherwise just allocate new memory.
|
||||
void* mem = this._alloc(size, alignment, offset)!;
|
||||
void* mem = self.acquire(size, false, alignment, offset)!;
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator;
|
||||
|
||||
struct DynamicArenaAllocator
|
||||
struct DynamicArenaAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
Allocator* backing_allocator;
|
||||
DynamicArenaPage* page;
|
||||
DynamicArenaPage* unused_page;
|
||||
@@ -14,48 +12,44 @@ struct DynamicArenaAllocator
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] allocator
|
||||
* @require page_size >= 128
|
||||
* @require this != null
|
||||
**/
|
||||
fn void DynamicArenaAllocator.init(DynamicArenaAllocator* this, usz page_size, Allocator* using = mem::heap())
|
||||
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator* allocator)
|
||||
{
|
||||
this.function = &dynamic_arena_allocator_function;
|
||||
this.page = null;
|
||||
this.unused_page = null;
|
||||
this.page_size = page_size;
|
||||
this.backing_allocator = using;
|
||||
self.page = null;
|
||||
self.unused_page = null;
|
||||
self.page_size = page_size;
|
||||
self.backing_allocator = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require this != null
|
||||
**/
|
||||
fn void DynamicArenaAllocator.free(DynamicArenaAllocator* this)
|
||||
fn void DynamicArenaAllocator.free(&self)
|
||||
{
|
||||
DynamicArenaPage* page = this.page;
|
||||
DynamicArenaPage* page = self.page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
free(page, .using = this.backing_allocator);
|
||||
self.backing_allocator.free(page);
|
||||
page = next_page;
|
||||
}
|
||||
page = this.unused_page;
|
||||
page = self.unused_page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
free(page, .using = this.backing_allocator);
|
||||
self.backing_allocator.free(page);
|
||||
page = next_page;
|
||||
}
|
||||
this.page = null;
|
||||
this.unused_page = null;
|
||||
self.page = null;
|
||||
self.unused_page = null;
|
||||
}
|
||||
|
||||
struct DynamicArenaPage
|
||||
struct DynamicArenaPage @local
|
||||
{
|
||||
void* memory;
|
||||
void* prev_arena;
|
||||
usz total;
|
||||
usz used;
|
||||
void* last_ptr;
|
||||
void* current_stack_ptr;
|
||||
}
|
||||
|
||||
struct DynamicArenaChunk @local
|
||||
@@ -64,26 +58,34 @@ struct DynamicArenaChunk @local
|
||||
}
|
||||
|
||||
/**
|
||||
* @require ptr && this
|
||||
* @require this.page `tried to free pointer on invalid allocator`
|
||||
* @require self.page `tried to free pointer on invalid allocator`
|
||||
*/
|
||||
fn void DynamicArenaAllocator.free_ptr(DynamicArenaAllocator* this, void* ptr) @private
|
||||
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
|
||||
{
|
||||
DynamicArenaPage* current_page = this.page;
|
||||
if (ptr == current_page.last_ptr)
|
||||
if (!ptr) return;
|
||||
DynamicArenaPage* current_page = self.page;
|
||||
if (ptr == current_page.current_stack_ptr)
|
||||
{
|
||||
current_page.used = (usz)((ptr - DEFAULT_SIZE_PREFIX) - current_page.memory);
|
||||
}
|
||||
current_page.last_ptr = null;
|
||||
current_page.current_stack_ptr = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require old_pointer && size > 0
|
||||
* @require this.page `tried to realloc pointer on invalid allocator`
|
||||
* @require self.page `tried to realloc pointer on invalid allocator`
|
||||
*/
|
||||
fn void*! DynamicArenaAllocator._realloc(DynamicArenaAllocator* this, void* old_pointer, usz size, usz alignment, usz offset) @local
|
||||
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
DynamicArenaPage* current_page = this.page;
|
||||
if (!size)
|
||||
{
|
||||
self.release(old_pointer, alignment > 0);
|
||||
return null;
|
||||
}
|
||||
if (!old_pointer)
|
||||
{
|
||||
return self.acquire(size, true, alignment, offset);
|
||||
}
|
||||
DynamicArenaPage* current_page = self.page;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
|
||||
usz old_size = *old_size_ptr;
|
||||
@@ -91,13 +93,13 @@ fn void*! DynamicArenaAllocator._realloc(DynamicArenaAllocator* this, void* old_
|
||||
if (old_size >= size && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
*old_size_ptr = size;
|
||||
if (current_page.last_ptr == old_pointer)
|
||||
if (current_page.current_stack_ptr == old_pointer)
|
||||
{
|
||||
current_page.used = (usz)((old_pointer - DEFAULT_SIZE_PREFIX) - current_page.memory);
|
||||
}
|
||||
return old_pointer;
|
||||
}
|
||||
if REUSE: (current_page.last_ptr == old_pointer && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
if REUSE: (current_page.current_stack_ptr == old_pointer && mem::ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
assert(size > old_size);
|
||||
usz add_size = size - old_size;
|
||||
@@ -106,142 +108,99 @@ fn void*! DynamicArenaAllocator._realloc(DynamicArenaAllocator* this, void* old_
|
||||
current_page.used += add_size;
|
||||
return old_pointer;
|
||||
}
|
||||
void* new_mem = this._alloc(size, alignment, offset)!;
|
||||
void* new_mem = self.acquire(size, false, alignment, offset)!;
|
||||
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return new_mem;
|
||||
}
|
||||
|
||||
fn void DynamicArenaAllocator.reset(DynamicArenaAllocator* this) @private
|
||||
fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
|
||||
{
|
||||
DynamicArenaPage* page = this.page;
|
||||
DynamicArenaPage** unused_page_ptr = &this.unused_page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
page.used = 0;
|
||||
DynamicArenaPage* prev_unused = *unused_page_ptr;
|
||||
*unused_page_ptr = page;
|
||||
page.prev_arena = prev_unused;
|
||||
page = next_page;
|
||||
}
|
||||
this.page = page;
|
||||
assert(mark == 0, "Unexpectedly reset dynamic arena allocator with mark %d", mark);
|
||||
DynamicArenaPage* page = self.page;
|
||||
DynamicArenaPage** unused_page_ptr = &self.unused_page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
page.used = 0;
|
||||
DynamicArenaPage* prev_unused = *unused_page_ptr;
|
||||
*unused_page_ptr = page;
|
||||
page.prev_arena = prev_unused;
|
||||
page = next_page;
|
||||
}
|
||||
self.page = page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
*/
|
||||
fn void*! DynamicArenaAllocator._alloc_new(DynamicArenaAllocator* this, usz size, usz alignment, usz offset) @local
|
||||
fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment, usz offset) @local
|
||||
{
|
||||
// First, make sure that we can align it, extending the page size if needed.
|
||||
usz page_size = max(this.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + offset, alignment) - offset);
|
||||
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + offset, alignment) - offset);
|
||||
|
||||
// Grab the page without alignment (we do it ourselves)
|
||||
void* mem = this.backing_allocator.alloc(page_size)!;
|
||||
DynamicArenaPage*! page = malloc(DynamicArenaPage, .using = this.backing_allocator);
|
||||
void* mem = self.backing_allocator.alloc_checked(page_size)!;
|
||||
DynamicArenaPage*! page = self.backing_allocator.new(DynamicArenaPage);
|
||||
if (catch err = page)
|
||||
{
|
||||
free(mem, .using = this.backing_allocator);
|
||||
self.backing_allocator.free(mem);
|
||||
return err?;
|
||||
}
|
||||
page.memory = mem;
|
||||
void* mem_start = mem::aligned_pointer(mem + offset + DynamicArenaChunk.sizeof, alignment) - offset;
|
||||
assert(mem_start + size < mem + page_size);
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
|
||||
chunk.size = size;
|
||||
page.prev_arena = this.page;
|
||||
page.memory = mem;
|
||||
void* mem_start = mem::aligned_pointer(mem + offset + DynamicArenaChunk.sizeof, alignment) - offset;
|
||||
assert(mem_start + size < mem + page_size);
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
|
||||
chunk.size = size;
|
||||
page.prev_arena = self.page;
|
||||
page.total = page_size;
|
||||
page.used = mem_start + size - page.memory;
|
||||
this.page = page;
|
||||
page.last_ptr = mem_start;
|
||||
self.page = page;
|
||||
page.current_stack_ptr = mem_start;
|
||||
return mem_start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
* @require this
|
||||
*/
|
||||
fn void*! DynamicArenaAllocator._alloc(DynamicArenaAllocator* this, usz size, usz alignment, usz offset) @local
|
||||
fn void*! DynamicArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (!size) return null;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
DynamicArenaPage* page = this.page;
|
||||
if (!page && this.unused_page)
|
||||
{
|
||||
this.page = page = this.unused_page;
|
||||
this.unused_page = page.prev_arena;
|
||||
page.prev_arena = null;
|
||||
}
|
||||
if (!page) return this._alloc_new(size, alignment, offset);
|
||||
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
|
||||
usz new_used = start - page.memory + size;
|
||||
if ALLOCATE_NEW: (new_used > page.total)
|
||||
{
|
||||
if ((page = this.unused_page))
|
||||
{
|
||||
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
|
||||
new_used = start + size - page.memory;
|
||||
if (page.total >= new_used)
|
||||
{
|
||||
this.unused_page = page.prev_arena;
|
||||
page.prev_arena = this.page;
|
||||
this.page = page;
|
||||
break ALLOCATE_NEW;
|
||||
}
|
||||
}
|
||||
return this._alloc_new(size, alignment, offset);
|
||||
}
|
||||
|
||||
page.used = new_used;
|
||||
assert(start + size == page.memory + page.used);
|
||||
void* mem = start;
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
|
||||
chunk.size = size;
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require data `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! dynamic_arena_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
{
|
||||
DynamicArenaAllocator* allocator = (DynamicArenaAllocator*)data;
|
||||
switch (kind)
|
||||
{
|
||||
case CALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
assert(!old_pointer, "Unexpected no old pointer for calloc.");
|
||||
if (!size) return null;
|
||||
void* mem = allocator._alloc(size, alignment, offset)!;
|
||||
mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
case ALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
assert(!old_pointer, "Unexpected no old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
return allocator._alloc(size, alignment, offset);
|
||||
case REALLOC:
|
||||
case ALIGNED_REALLOC:
|
||||
if (!size)
|
||||
DynamicArenaPage* page = self.page;
|
||||
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) return self._alloc_new(size, alignment, offset);
|
||||
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
|
||||
usz new_used = start - page.memory + size;
|
||||
if ALLOCATE_NEW: (new_used > page.total)
|
||||
{
|
||||
if ((page = self.unused_page))
|
||||
{
|
||||
if (!old_pointer) return null;
|
||||
allocator.free_ptr(old_pointer);
|
||||
return null;
|
||||
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
|
||||
new_used = start + size - page.memory;
|
||||
if (page.total >= new_used)
|
||||
{
|
||||
self.unused_page = page.prev_arena;
|
||||
page.prev_arena = self.page;
|
||||
self.page = page;
|
||||
break ALLOCATE_NEW;
|
||||
}
|
||||
}
|
||||
if (!old_pointer) return allocator._alloc(size, alignment, offset);
|
||||
void* mem = allocator._realloc(old_pointer, size, alignment, offset)!;
|
||||
return mem;
|
||||
case ALIGNED_FREE:
|
||||
case FREE:
|
||||
if (!old_pointer) return null;
|
||||
allocator.free_ptr(old_pointer);
|
||||
return null;
|
||||
case MARK:
|
||||
unreachable("Tried to mark a dynamic arena");
|
||||
case RESET:
|
||||
allocator.reset();
|
||||
return null;
|
||||
}
|
||||
unreachable();
|
||||
return self._alloc_new(size, alignment, offset);
|
||||
}
|
||||
page.used = new_used;
|
||||
assert(start + size == page.memory + page.used);
|
||||
void* mem = start;
|
||||
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
|
||||
chunk.size = size;
|
||||
return mem;
|
||||
|}!;
|
||||
if (clear) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -1,99 +1,95 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Copyright (c) 2021-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.
|
||||
|
||||
module std::core::mem::allocator;
|
||||
|
||||
def MemoryAllocFn = fn char[]!(usz);
|
||||
|
||||
struct SimpleHeapAllocator
|
||||
struct SimpleHeapAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
MemoryAllocFn alloc_fn;
|
||||
Header* free_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require this "Unexpectedly missing the allocator"
|
||||
* @require allocator "An underlying memory provider must be given"
|
||||
* @require !this.free_list "The allocator may not be already initialized"
|
||||
* @require !self.free_list "The allocator may not be already initialized"
|
||||
**/
|
||||
fn void SimpleHeapAllocator.init(SimpleHeapAllocator* this, MemoryAllocFn allocator)
|
||||
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
|
||||
{
|
||||
this.alloc_fn = allocator;
|
||||
this.allocator = { &simple_heap_allocator_function };
|
||||
this.free_list = null;
|
||||
self.alloc_fn = allocator;
|
||||
self.free_list = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require this `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! simple_heap_allocator_function(Allocator* this, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
fn void*! SimpleHeapAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
SimpleHeapAllocator* heap = (SimpleHeapAllocator*)this;
|
||||
switch (kind)
|
||||
{
|
||||
case ALIGNED_ALLOC:
|
||||
return @aligned_alloc(heap._alloc, size, alignment, offset);
|
||||
case ALLOC:
|
||||
return heap._alloc(size);
|
||||
case ALIGNED_CALLOC:
|
||||
return @aligned_calloc(heap._calloc, size, alignment, offset);
|
||||
case CALLOC:
|
||||
return heap._calloc(size);
|
||||
case ALIGNED_REALLOC:
|
||||
if (!size) nextcase ALIGNED_FREE;
|
||||
if (!old_pointer) nextcase ALIGNED_CALLOC;
|
||||
return @aligned_realloc(heap._calloc, heap._free, old_pointer, size, alignment, offset);
|
||||
case REALLOC:
|
||||
if (!size) nextcase FREE;
|
||||
if (!old_pointer) nextcase CALLOC;
|
||||
return heap._realloc(old_pointer, size);
|
||||
case RESET:
|
||||
return AllocationFailure.UNSUPPORTED_OPERATION?;
|
||||
case ALIGNED_FREE:
|
||||
@aligned_free(heap._free, old_pointer)!;
|
||||
return null;
|
||||
case FREE:
|
||||
heap._free(old_pointer);
|
||||
return null;
|
||||
default:
|
||||
unreachable();
|
||||
if (!size) return null;
|
||||
if (clear)
|
||||
{
|
||||
return alignment > 0 ? @aligned_calloc(self._calloc, size, alignment, offset) : self._calloc(size);
|
||||
}
|
||||
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment, offset) : self._alloc(size);
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
self.release(old_pointer, alignment > 0);
|
||||
return null;
|
||||
}
|
||||
if (!old_pointer)
|
||||
{
|
||||
return self.acquire(size, true, alignment, offset);
|
||||
}
|
||||
return alignment > 0
|
||||
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment, offset)
|
||||
: self._realloc(old_pointer, size);
|
||||
}
|
||||
|
||||
fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
|
||||
{
|
||||
if (aligned)
|
||||
{
|
||||
@aligned_free(self._free, old_pointer)!!;
|
||||
}
|
||||
else
|
||||
{
|
||||
self._free(old_pointer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require this && old_pointer && bytes > 0
|
||||
* @require old_pointer && bytes > 0
|
||||
**/
|
||||
fn void*! SimpleHeapAllocator._realloc(SimpleHeapAllocator* this, void* old_pointer, usz bytes)
|
||||
fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
|
||||
{
|
||||
// Find the block header.
|
||||
Header* block = (Header*)old_pointer - 1;
|
||||
if (block.size >= bytes) return old_pointer;
|
||||
void* new = this._alloc(bytes)!;
|
||||
void* new = self._alloc(bytes)!;
|
||||
usz max_to_copy = math::min(block.size, bytes);
|
||||
mem::copy(new, old_pointer, max_to_copy);
|
||||
this._free(old_pointer);
|
||||
self._free(old_pointer);
|
||||
return new;
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator._calloc(SimpleHeapAllocator* this, usz bytes) @local
|
||||
fn void*! SimpleHeapAllocator._calloc(&self, usz bytes) @local
|
||||
{
|
||||
void* data = this._alloc(bytes)!;
|
||||
void* data = self._alloc(bytes)!;
|
||||
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! SimpleHeapAllocator._alloc(SimpleHeapAllocator* this, usz bytes) @local
|
||||
fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
|
||||
{
|
||||
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
if (!this.free_list)
|
||||
{
|
||||
this.add_block(aligned_bytes)!;
|
||||
}
|
||||
if (!self.free_list)
|
||||
{
|
||||
self.add_block(aligned_bytes)!;
|
||||
}
|
||||
|
||||
Header* current = this.free_list;
|
||||
Header* current = self.free_list;
|
||||
Header* previous = current;
|
||||
while (current)
|
||||
{
|
||||
@@ -102,21 +98,21 @@ fn void*! SimpleHeapAllocator._alloc(SimpleHeapAllocator* this, usz bytes) @loca
|
||||
case current.size >= aligned_bytes && current.size <= aligned_bytes + Header.sizeof + 64:
|
||||
if (current == previous)
|
||||
{
|
||||
this.free_list = current.next;
|
||||
self.free_list = current.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
previous.next = current.next;
|
||||
}
|
||||
current.next = null;
|
||||
return current + 1;
|
||||
case current.size > aligned_bytes:
|
||||
}
|
||||
current.next = null;
|
||||
return current + 1;
|
||||
case current.size > aligned_bytes:
|
||||
Header* unallocated = (Header*)((char*)current + aligned_bytes + Header.sizeof);
|
||||
unallocated.size = current.size - aligned_bytes;
|
||||
unallocated.next = current.next;
|
||||
if (current == this.free_list)
|
||||
if (current == self.free_list)
|
||||
{
|
||||
this.free_list = unallocated;
|
||||
self.free_list = unallocated;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -130,22 +126,22 @@ fn void*! SimpleHeapAllocator._alloc(SimpleHeapAllocator* this, usz bytes) @loca
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
this.add_block(aligned_bytes)!;
|
||||
return this.alloc(aligned_bytes);
|
||||
self.add_block(aligned_bytes)!;
|
||||
return self._alloc(aligned_bytes);
|
||||
}
|
||||
|
||||
fn void! SimpleHeapAllocator.add_block(SimpleHeapAllocator* this, usz aligned_bytes) @local
|
||||
fn void! SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
|
||||
{
|
||||
assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes);
|
||||
char[] result = this.alloc_fn(aligned_bytes + Header.sizeof)!;
|
||||
char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!;
|
||||
Header* new_block = (Header*)result.ptr;
|
||||
new_block.size = result.len - Header.sizeof;
|
||||
new_block.next = null;
|
||||
this._free(new_block + 1);
|
||||
self._free(new_block + 1);
|
||||
}
|
||||
|
||||
|
||||
fn void SimpleHeapAllocator._free(SimpleHeapAllocator* this, void* ptr) @local
|
||||
fn void SimpleHeapAllocator._free(&self, void* ptr) @local
|
||||
{
|
||||
// Empty ptr -> do nothing.
|
||||
if (!ptr) return;
|
||||
@@ -153,15 +149,15 @@ fn void SimpleHeapAllocator._free(SimpleHeapAllocator* this, void* ptr) @local
|
||||
// Find the block header.
|
||||
Header* block = (Header*)ptr - 1;
|
||||
|
||||
// No free list? Then just return this.
|
||||
if (!this.free_list)
|
||||
// No free list? Then just return self.
|
||||
if (!self.free_list)
|
||||
{
|
||||
this.free_list = block;
|
||||
self.free_list = block;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find where in the list it should be inserted.
|
||||
Header* current = this.free_list;
|
||||
Header* current = self.free_list;
|
||||
Header* prev = current;
|
||||
while (current)
|
||||
{
|
||||
@@ -178,46 +174,46 @@ fn void SimpleHeapAllocator._free(SimpleHeapAllocator* this, void* ptr) @local
|
||||
if (current)
|
||||
{
|
||||
// Insert after the current block.
|
||||
// Are the blocks adjacent?
|
||||
if (current == (Header*)((char*)(block + 1) + block.size))
|
||||
{
|
||||
// Merge
|
||||
block.size += current.size + Header.sizeof;
|
||||
block.next = current.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chain to current
|
||||
block.next = current;
|
||||
}
|
||||
// Are the blocks adjacent?
|
||||
if (current == (Header*)((char*)(block + 1) + block.size))
|
||||
{
|
||||
// Merge
|
||||
block.size += current.size + Header.sizeof;
|
||||
block.next = current.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chain to current
|
||||
block.next = current;
|
||||
}
|
||||
}
|
||||
if (prev == current)
|
||||
{
|
||||
// Swap new start of free list
|
||||
this.free_list = block;
|
||||
self.free_list = block;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prev adjacent?
|
||||
if (block == (Header*)((char*)(prev + 1) + prev.size))
|
||||
{
|
||||
prev.size += block.size + Header.sizeof;
|
||||
prev.size += block.size + Header.sizeof;
|
||||
prev.next = block.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Link prev to block
|
||||
prev.next = block;
|
||||
// Link prev to block
|
||||
prev.next = block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
union Header @private
|
||||
union Header @local
|
||||
{
|
||||
struct
|
||||
{
|
||||
Header* next;
|
||||
usz size;
|
||||
}
|
||||
usz align;
|
||||
usz size;
|
||||
}
|
||||
usz align;
|
||||
}
|
||||
|
||||
63
lib/std/core/allocators/libc_allocator.c3
Normal file
63
lib/std/core/allocators/libc_allocator.c3
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator;
|
||||
import libc;
|
||||
|
||||
const LibcAllocator LIBC_ALLOCATOR = {};
|
||||
|
||||
|
||||
distinct LibcAllocator (Allocator) = uptr;
|
||||
|
||||
fn void*! LibcAllocator.acquire(&self, usz bytes, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
assert(alignment != 0 || offset == 0);
|
||||
if (clear)
|
||||
{
|
||||
void* data = alignment ? @aligned_calloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment, offset)!! : libc::calloc(bytes, 1);
|
||||
return data ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
else
|
||||
{
|
||||
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment, offset)!! : libc::malloc(bytes);
|
||||
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
$if env::TESTING:
|
||||
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
|
||||
$endif
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
assert(alignment != 0 || offset == 0);
|
||||
if (!new_bytes)
|
||||
{
|
||||
self.release(old_ptr, alignment > 0);
|
||||
return null;
|
||||
}
|
||||
if (!old_ptr)
|
||||
{
|
||||
return self.acquire(new_bytes, true, alignment, offset);
|
||||
}
|
||||
if (alignment)
|
||||
{
|
||||
void* data = @aligned_realloc(fn void*(usz bytes) => libc::calloc(bytes, 1), libc::free, old_ptr, new_bytes, alignment, offset)!!;
|
||||
return data ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
|
||||
}
|
||||
|
||||
|
||||
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
|
||||
{
|
||||
if (aligned)
|
||||
{
|
||||
@aligned_free(libc::free, old_ptr)!!;
|
||||
}
|
||||
else
|
||||
{
|
||||
libc::free(old_ptr);
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
|
||||
module std::core::mem::allocator;
|
||||
import libc;
|
||||
|
||||
const Allocator _NULL_ALLOCATOR @private = { &null_allocator_fn };
|
||||
const Allocator _SYSTEM_ALLOCATOR @private = { &libc_allocator_fn };
|
||||
|
||||
fn void*! null_allocator_fn(Allocator* this, usz bytes, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case ALLOC:
|
||||
case CALLOC:
|
||||
case REALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
case ALIGNED_REALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
return AllocationFailure.OUT_OF_MEMORY?;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
struct AlignedBlock
|
||||
{
|
||||
usz len;
|
||||
void* start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
|
||||
$if $checks(#alloc_fn(bytes)!):
|
||||
void* data = #alloc_fn(header + bytes)!;
|
||||
$else
|
||||
void* data = #alloc_fn(header + bytes);
|
||||
$endif
|
||||
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
|
||||
assert(mem > data);
|
||||
AlignedBlock* desc = (AlignedBlock*)mem - 1;
|
||||
*desc = { bytes, data };
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_calloc(#calloc_fn, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
|
||||
$if $checks(#calloc_fn(bytes)!):
|
||||
void* data = #calloc_fn(header + bytes)!;
|
||||
$else
|
||||
void* data = #calloc_fn(header + bytes);
|
||||
$endif
|
||||
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
|
||||
AlignedBlock* desc = (AlignedBlock*)mem - 1;
|
||||
assert(mem > data);
|
||||
*desc = { bytes, data };
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
void* data_start = desc.start;
|
||||
void* new_data = @aligned_calloc(#calloc_fn, bytes, alignment, offset)!;
|
||||
mem::copy(new_data, old_pointer, desc.len > bytes ? desc.len : bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
$if $checks(#free_fn(data_start)!):
|
||||
#free_fn(data_start)!;
|
||||
$else
|
||||
#free_fn(data_start);
|
||||
$endif
|
||||
return new_data;
|
||||
}
|
||||
|
||||
macro void! @aligned_free(#free_fn, void* old_pointer)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if $checks(#free_fn(desc.start)!):
|
||||
#free_fn(desc.start)!;
|
||||
$else
|
||||
#free_fn(desc.start);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void*! libc_allocator_fn(Allocator* unused, usz bytes, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @inline
|
||||
{
|
||||
if (!alignment) alignment = mem::DEFAULT_MEM_ALIGNMENT;
|
||||
assert(math::is_power_of_2(alignment), "Alignment was not a power of 2");
|
||||
|
||||
void* data;
|
||||
switch (kind)
|
||||
{
|
||||
case ALIGNED_ALLOC:
|
||||
data = @aligned_alloc(libc::malloc, bytes, alignment, offset)!!;
|
||||
case ALLOC:
|
||||
data = libc::malloc(bytes);
|
||||
case ALIGNED_CALLOC:
|
||||
data = @aligned_calloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment, offset)!!;
|
||||
case CALLOC:
|
||||
data = libc::calloc(bytes, 1);
|
||||
case ALIGNED_REALLOC:
|
||||
if (!bytes) nextcase ALIGNED_FREE;
|
||||
if (!old_pointer) nextcase ALIGNED_CALLOC;
|
||||
data = @aligned_realloc(fn void*(usz bytes) => libc::calloc(bytes, 1), libc::free, old_pointer, bytes, alignment, offset)!!;
|
||||
case REALLOC:
|
||||
if (!bytes) nextcase FREE;
|
||||
if (!old_pointer) nextcase CALLOC;
|
||||
data = libc::realloc(old_pointer, bytes);
|
||||
case RESET:
|
||||
return AllocationFailure.UNSUPPORTED_OPERATION?;
|
||||
case ALIGNED_FREE:
|
||||
@aligned_free(libc::free, old_pointer)!!;
|
||||
return null;
|
||||
case FREE:
|
||||
libc::free(old_pointer);
|
||||
return null;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
return data;
|
||||
}
|
||||
@@ -1,34 +1,13 @@
|
||||
module std::core::mem::allocator;
|
||||
|
||||
struct OnStackAllocator
|
||||
struct OnStackAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
Allocator* backing_allocator;
|
||||
char[] data;
|
||||
usz used;
|
||||
OnStackAllocatorExtraChunk* chunk;
|
||||
}
|
||||
|
||||
macro void @stack_mem(usz $size; @body(Allocator* mem)) @builtin
|
||||
{
|
||||
char[$size] buffer;
|
||||
OnStackAllocator allocator;
|
||||
allocator.init(&buffer, mem::heap());
|
||||
defer allocator.free();
|
||||
@body(&allocator);
|
||||
}
|
||||
|
||||
macro void @stack_pool(usz $size; @body) @builtin
|
||||
{
|
||||
char[$size] buffer;
|
||||
OnStackAllocator allocator;
|
||||
allocator.init(&buffer, mem::heap());
|
||||
defer allocator.free();
|
||||
mem::@scoped(&allocator)
|
||||
{
|
||||
@body();
|
||||
};
|
||||
}
|
||||
|
||||
struct OnStackAllocatorExtraChunk @local
|
||||
{
|
||||
@@ -38,40 +17,35 @@ struct OnStackAllocatorExtraChunk @local
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] allocator
|
||||
* Initialize a memory arena for use using the provided bytes.
|
||||
*
|
||||
* @require this != null
|
||||
**/
|
||||
fn void OnStackAllocator.init(OnStackAllocator* this, char[] data, Allocator* using = mem::heap())
|
||||
fn void OnStackAllocator.init(&self, char[] data, Allocator* allocator)
|
||||
{
|
||||
this.function = &on_stack_allocator_function;
|
||||
this.data = data;
|
||||
this.backing_allocator = using;
|
||||
this.used = 0;
|
||||
self.data = data;
|
||||
self.backing_allocator = allocator;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require this != null
|
||||
**/
|
||||
fn void OnStackAllocator.free(OnStackAllocator* this)
|
||||
fn void OnStackAllocator.free(&self)
|
||||
{
|
||||
OnStackAllocatorExtraChunk* chunk = this.chunk;
|
||||
OnStackAllocatorExtraChunk* chunk = self.chunk;
|
||||
while (chunk)
|
||||
{
|
||||
if (chunk.is_aligned)
|
||||
{
|
||||
this.backing_allocator.free_aligned(chunk.data)!!;
|
||||
self.backing_allocator.free_aligned(chunk.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.backing_allocator.free(chunk.data)!!;
|
||||
self.backing_allocator.free(chunk.data);
|
||||
}
|
||||
void* old = chunk;
|
||||
chunk = chunk.prev;
|
||||
this.backing_allocator.free(old)!!;
|
||||
self.backing_allocator.free(old);
|
||||
}
|
||||
this.chunk = null;
|
||||
this.used = 0;
|
||||
self.chunk = null;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
struct OnStackAllocatorHeader
|
||||
@@ -80,49 +54,12 @@ struct OnStackAllocatorHeader
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require data `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! on_stack_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
|
||||
{
|
||||
OnStackAllocator* allocator = (OnStackAllocator*)data;
|
||||
bool clear = false;
|
||||
switch (kind)
|
||||
{
|
||||
case CALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
clear = true;
|
||||
nextcase;
|
||||
case ALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
assert(!old_pointer, "Unexpected old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
return on_stack_allocator_alloc(allocator, size, alignment, offset, clear, kind == AllocationKind.ALIGNED_ALLOC || kind == AllocationKind.ALIGNED_CALLOC);
|
||||
case ALIGNED_REALLOC:
|
||||
case REALLOC:
|
||||
if (!size) nextcase FREE;
|
||||
if (!old_pointer) nextcase ALLOC;
|
||||
return on_stack_allocator_realloc(allocator, old_pointer, size, alignment, offset, kind == AllocationKind.ALIGNED_REALLOC);
|
||||
case ALIGNED_FREE:
|
||||
case FREE:
|
||||
if (!old_pointer) return null;
|
||||
if (allocation_in_stack_mem(allocator, old_pointer)) return null;
|
||||
on_stack_allocator_remove_chunk(allocator, old_pointer);
|
||||
if (kind == AllocationKind.ALIGNED_FREE)
|
||||
{
|
||||
allocator.backing_allocator.free_aligned(old_pointer)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator.backing_allocator.free(old_pointer)!;
|
||||
}
|
||||
return null;
|
||||
case MARK:
|
||||
case RESET:
|
||||
return AllocationFailure.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
unreachable();
|
||||
if (!old_pointer) return;
|
||||
if (allocation_in_stack_mem(self, old_pointer)) return;
|
||||
on_stack_allocator_remove_chunk(self, old_pointer);
|
||||
self.release(old_pointer, aligned);
|
||||
}
|
||||
|
||||
fn bool allocation_in_stack_mem(OnStackAllocator* a, void* ptr) @local
|
||||
@@ -139,7 +76,7 @@ fn void on_stack_allocator_remove_chunk(OnStackAllocator* a, void* ptr) @local
|
||||
if (chunk.data == ptr)
|
||||
{
|
||||
*addr = chunk.prev;
|
||||
a.backing_allocator.free(chunk)!!;
|
||||
a.backing_allocator.free(chunk);
|
||||
return;
|
||||
}
|
||||
addr = &chunk.prev;
|
||||
@@ -164,70 +101,52 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
|
||||
* @require a != null
|
||||
* @require mem::aligned_offset(offset, OnStackAllocatorExtraChunk.alignof) == offset
|
||||
**/
|
||||
fn void*! on_stack_allocator_realloc(OnStackAllocator* a, void* old_pointer, usz size, usz alignment, usz offset, bool aligned) @local @inline
|
||||
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (!allocation_in_stack_mem(a, old_pointer))
|
||||
if (!allocation_in_stack_mem(self, old_pointer))
|
||||
{
|
||||
OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(a, old_pointer);
|
||||
OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(self, old_pointer);
|
||||
assert(chunk, "Tried to realloc pointer not belonging to the allocator");
|
||||
if (aligned)
|
||||
{
|
||||
return chunk.data = a.backing_allocator.realloc_aligned(old_pointer, size, alignment, offset)!;
|
||||
}
|
||||
return chunk.data = a.backing_allocator.realloc(old_pointer, size)!;
|
||||
return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment, offset)!;
|
||||
}
|
||||
|
||||
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
|
||||
usz old_size = header.size;
|
||||
void* mem = on_stack_allocator_alloc(a, size, alignment, offset, true, aligned)!;
|
||||
void* mem = self.acquire(size, true, alignment, offset)!;
|
||||
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
import std::io;
|
||||
/**
|
||||
* @require size > 0
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
|
||||
* @require offset <= size && offset >= 0
|
||||
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
|
||||
* @require a != null
|
||||
* @require offset == 0 || alignment > 0
|
||||
* @require mem::aligned_offset(offset, OnStackAllocatorHeader.alignof) == offset
|
||||
**/
|
||||
fn void*! on_stack_allocator_alloc(OnStackAllocator* a, usz size, usz alignment, usz offset, bool clear, bool aligned) @local @inline
|
||||
fn void*! OnStackAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (size == 0) return null;
|
||||
bool aligned = alignment > 0;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usz total_len = a.data.len;
|
||||
void* start_mem = a.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + a.used + OnStackAllocatorHeader.sizeof + offset;
|
||||
usz total_len = self.data.len;
|
||||
void* start_mem = self.data.ptr;
|
||||
void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof + offset;
|
||||
void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
|
||||
usz end = (usz)(aligned_pointer_to_offset - a.data.ptr) + size - offset;
|
||||
|
||||
Allocator* backing_allocator = a.backing_allocator;
|
||||
usz end = (usz)(aligned_pointer_to_offset - self.data.ptr) + size - offset;
|
||||
Allocator* backing_allocator = self.backing_allocator;
|
||||
|
||||
if (end > total_len)
|
||||
{
|
||||
OnStackAllocatorExtraChunk* chunk = backing_allocator.alloc(OnStackAllocatorExtraChunk.sizeof)!;
|
||||
defer catch backing_allocator.free(chunk)!!;
|
||||
defer try a.chunk = chunk;
|
||||
*chunk = { .prev = a.chunk, .is_aligned = aligned };
|
||||
void* data @noinit;
|
||||
switch
|
||||
{
|
||||
case !aligned && !clear:
|
||||
data = backing_allocator.alloc(size)!;
|
||||
case aligned && !clear:
|
||||
data = backing_allocator.alloc_aligned(size, alignment, offset)!;
|
||||
case !aligned && clear:
|
||||
data = backing_allocator.calloc(size)!;
|
||||
case aligned && clear:
|
||||
data = backing_allocator.calloc_aligned(size, alignment, offset)!;
|
||||
}
|
||||
return chunk.data = data;
|
||||
OnStackAllocatorExtraChunk* chunk = backing_allocator.alloc_checked(OnStackAllocatorExtraChunk.sizeof)!;
|
||||
defer catch backing_allocator.free(chunk);
|
||||
defer try self.chunk = chunk;
|
||||
*chunk = { .prev = self.chunk, .is_aligned = aligned };
|
||||
return chunk.data = backing_allocator.acquire(size, clear, aligned ? alignment : 0, offset)!;
|
||||
}
|
||||
a.used = end;
|
||||
self.used = end;
|
||||
void *mem = aligned_pointer_to_offset - offset;
|
||||
OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader.sizeof;
|
||||
header.size = size;
|
||||
|
||||
@@ -7,9 +7,8 @@ struct TempAllocatorChunk @local
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
struct TempAllocator
|
||||
struct TempAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
Allocator* backing_allocator;
|
||||
TempAllocatorPage* last_page;
|
||||
usz used;
|
||||
@@ -17,7 +16,6 @@ struct TempAllocator
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
|
||||
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
|
||||
|
||||
|
||||
@@ -31,98 +29,59 @@ struct TempAllocatorPage
|
||||
char[*] data;
|
||||
}
|
||||
|
||||
macro usz TempAllocatorPage.pagesize(TempAllocatorPage* page) => page.size & ~PAGE_IS_ALIGNED;
|
||||
macro bool TempAllocatorPage.is_aligned(TempAllocatorPage* page) => page.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
|
||||
macro usz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
|
||||
macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
|
||||
|
||||
/**
|
||||
* @require size >= 16
|
||||
**/
|
||||
fn TempAllocator*! new_temp(usz size, Allocator* using)
|
||||
fn TempAllocator*! new_temp(usz size, Allocator* allocator)
|
||||
{
|
||||
TempAllocator* allocator = malloc_checked(TempAllocator, .using = using, .end_padding = size)!;
|
||||
allocator.last_page = null;
|
||||
allocator.function = &temp_allocator_function;
|
||||
allocator.backing_allocator = using;
|
||||
allocator.used = 0;
|
||||
allocator.capacity = size;
|
||||
return allocator;
|
||||
TempAllocator* temp = allocator.alloc_checked(TempAllocator.sizeof + size)!;
|
||||
temp.last_page = null;
|
||||
temp.backing_allocator = allocator;
|
||||
temp.used = 0;
|
||||
temp.capacity = size;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require data `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! temp_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
{
|
||||
TempAllocator* arena = (TempAllocator*)data;
|
||||
switch (kind)
|
||||
{
|
||||
case CALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
assert(!old_pointer, "Unexpected old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
return arena._alloc(size, alignment_for_allocation(alignment), offset, true);
|
||||
case ALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
assert(!old_pointer, "Unexpected old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
return arena._alloc(size, alignment_for_allocation(alignment), offset, false);
|
||||
case ALIGNED_REALLOC:
|
||||
case REALLOC:
|
||||
if (!size) nextcase FREE;
|
||||
if (!old_pointer) nextcase ALLOC;
|
||||
return arena._realloc(old_pointer, size, alignment_for_allocation(alignment), offset);
|
||||
case FREE:
|
||||
case ALIGNED_FREE:
|
||||
if (!old_pointer) return null;
|
||||
arena._free(old_pointer)!;
|
||||
return null;
|
||||
case MARK:
|
||||
return (void*)(uptr)arena.used;
|
||||
case RESET:
|
||||
arena._reset(size)!;
|
||||
return null;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
fn usz TempAllocator.mark(&self) @dynamic => self.used;
|
||||
|
||||
fn void! TempAllocator._free(TempAllocator* this, void* old_pointer) @local
|
||||
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
|
||||
{
|
||||
// TODO fix free
|
||||
assert((uptr)old_pointer >= (uptr)&this.data, "Pointer originates from a different allocator.");
|
||||
usz old_size = *(usz*)(old_pointer - DEFAULT_SIZE_PREFIX);
|
||||
if (old_pointer + old_size == &this.data[this.used])
|
||||
{
|
||||
this.used -= old_size;
|
||||
}
|
||||
if (old_pointer + old_size == &self.data[self.used])
|
||||
{
|
||||
self.used -= old_size;
|
||||
}
|
||||
}
|
||||
fn void! TempAllocator._reset(TempAllocator* this, usz mark) @local
|
||||
fn void TempAllocator.reset(&self, usz mark) @dynamic
|
||||
{
|
||||
TempAllocatorPage *last_page = this.last_page;
|
||||
TempAllocatorPage *last_page = self.last_page;
|
||||
while (last_page && last_page.mark > mark)
|
||||
{
|
||||
TempAllocatorPage *to_free = last_page;
|
||||
last_page = last_page.prev_page;
|
||||
this._free_page(to_free)!;
|
||||
self._free_page(to_free)!!;
|
||||
}
|
||||
this.last_page = last_page;
|
||||
this.used = mark;
|
||||
self.last_page = last_page;
|
||||
self.used = mark;
|
||||
}
|
||||
|
||||
fn void! TempAllocator._free_page(TempAllocator* this, TempAllocatorPage* page) @inline @local
|
||||
fn void! TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
|
||||
{
|
||||
void* mem = page.start;
|
||||
if (page.is_aligned()) return this.backing_allocator.free_aligned(mem);
|
||||
return this.backing_allocator.free(mem);
|
||||
if (page.is_aligned()) return self.backing_allocator.free_aligned(mem);
|
||||
return self.backing_allocator.free(mem);
|
||||
}
|
||||
|
||||
fn void*! TempAllocator._realloc_page(TempAllocator* this, TempAllocatorPage* page, usz size, usz alignment, usz offset) @inline @local
|
||||
fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment, usz offset) @inline @local
|
||||
{
|
||||
// Then the actual start pointer:
|
||||
void* real_pointer = page.start;
|
||||
|
||||
// Walk backwards to find the pointer to this page.
|
||||
TempAllocatorPage **pointer_to_prev = &this.last_page;
|
||||
TempAllocatorPage **pointer_to_prev = &self.last_page;
|
||||
// Remove the page from the list
|
||||
while (*pointer_to_prev != page)
|
||||
{
|
||||
@@ -131,47 +90,56 @@ fn void*! TempAllocator._realloc_page(TempAllocator* this, TempAllocatorPage* pa
|
||||
*pointer_to_prev = page.prev_page;
|
||||
usz page_size = page.pagesize();
|
||||
// Clear on size > original size.
|
||||
void* data = this._alloc(size, alignment, offset, false)!;
|
||||
void* data = self.acquire(size, size > page_size, alignment, offset)!;
|
||||
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
if (page.is_aligned())
|
||||
{
|
||||
this.backing_allocator.free_aligned(real_pointer)!;
|
||||
self.backing_allocator.free_aligned(real_pointer);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.backing_allocator.free(real_pointer)!;
|
||||
self.backing_allocator.free(real_pointer);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void*! TempAllocator._realloc(TempAllocator* this, void* pointer, usz size, usz alignment, usz offset) @inline @local
|
||||
fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
self.release(pointer, alignment > 0);
|
||||
return null;
|
||||
}
|
||||
if (!pointer)
|
||||
{
|
||||
return self.acquire(size, true, alignment, offset);
|
||||
}
|
||||
TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof;
|
||||
if (chunk.size == (usz)-1)
|
||||
{
|
||||
assert(this.last_page, "Realloc of non temp pointer");
|
||||
assert(self.last_page, "Realloc of non temp pointer");
|
||||
// First grab the page
|
||||
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
|
||||
return this._realloc_page(page, size, alignment, offset);
|
||||
return self._realloc_page(page, size, alignment, offset);
|
||||
}
|
||||
|
||||
// TODO optimize last allocation
|
||||
TempAllocatorChunk* data = this._alloc(size, alignment, offset, size > chunk.size)!;
|
||||
TempAllocatorChunk* data = self.acquire(size, size > chunk.size, alignment, offset)!;
|
||||
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require this != null
|
||||
**/
|
||||
fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz offset, bool clear) @local
|
||||
fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
void* start_mem = &this.data;
|
||||
void* starting_ptr = start_mem + this.used;
|
||||
if (!size) return null;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
void* start_mem = &self.data;
|
||||
void* starting_ptr = start_mem + self.used;
|
||||
void* aligned_header_start = mem::aligned_pointer(starting_ptr, TempAllocatorChunk.alignof);
|
||||
void* mem = aligned_header_start + TempAllocatorChunk.sizeof;
|
||||
if (alignment > TempAllocatorChunk.alignof)
|
||||
@@ -181,13 +149,13 @@ fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz
|
||||
usz new_usage = (usz)(mem - start_mem) + size;
|
||||
|
||||
// Arena alignment, simple!
|
||||
if (new_usage <= this.capacity)
|
||||
if (new_usage <= self.capacity)
|
||||
{
|
||||
TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof;
|
||||
chunk_start.size = size;
|
||||
this.used = new_usage;
|
||||
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
chunk_start.size = size;
|
||||
self.used = new_usage;
|
||||
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return mem;
|
||||
}
|
||||
|
||||
// Fallback to backing allocator
|
||||
@@ -198,13 +166,13 @@ fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz
|
||||
{
|
||||
// This is actually simpler, since it will create the offset for us.
|
||||
usz total_alloc_size = TempAllocatorPage.sizeof + size;
|
||||
if (clear)
|
||||
if (clear)
|
||||
{
|
||||
page = this.backing_allocator.calloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!;
|
||||
page = self.backing_allocator.calloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
page = this.backing_allocator.alloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!;
|
||||
page = self.backing_allocator.alloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!;
|
||||
}
|
||||
page.start = page;
|
||||
page.size = size | PAGE_IS_ALIGNED;
|
||||
@@ -214,7 +182,7 @@ fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz
|
||||
// Here we might need to pad
|
||||
usz padded_header_size = mem::aligned_offset(TempAllocatorPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
usz total_alloc_size = padded_header_size + size;
|
||||
void* alloc = (clear ? this.backing_allocator.calloc(total_alloc_size) : this.backing_allocator.alloc(total_alloc_size))!;
|
||||
void* alloc = self.backing_allocator.acquire(total_alloc_size, clear, 0, 0)!;
|
||||
|
||||
// Find the page.
|
||||
page = alloc + padded_header_size - TempAllocatorPage.sizeof;
|
||||
@@ -227,28 +195,28 @@ fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz
|
||||
// Mark it as a page
|
||||
page.ident = ~(usz)0;
|
||||
// Store when it was created
|
||||
page.mark = ++this.used;
|
||||
page.mark = ++self.used;
|
||||
// Hook up the page.
|
||||
page.prev_page = this.last_page;
|
||||
this.last_page = page;
|
||||
page.prev_page = self.last_page;
|
||||
self.last_page = page;
|
||||
return &page.data[0];
|
||||
}
|
||||
|
||||
fn void TempAllocator.print_pages(TempAllocator* this, File f)
|
||||
fn void! TempAllocator.print_pages(&self, File* f)
|
||||
{
|
||||
TempAllocatorPage *last_page = this.last_page;
|
||||
TempAllocatorPage *last_page = self.last_page;
|
||||
if (!last_page)
|
||||
{
|
||||
f.printf("No pages.\n");
|
||||
io::fprintf(f, "No pages.\n")!;
|
||||
return;
|
||||
}
|
||||
f.printf("---Pages----\n");
|
||||
io::fprintf(f, "---Pages----\n")!;
|
||||
uint index = 0;
|
||||
while (last_page)
|
||||
{
|
||||
bool is_not_aligned = !(last_page.size & (1u64 << 63));
|
||||
f.printf("%d. Alloc: %d %d at %p%s\n", ++index,
|
||||
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]");
|
||||
io::fprintf(f, "%d. Alloc: %d %d at %p%s\n", ++index,
|
||||
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]")!;
|
||||
last_page = last_page.prev_page;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,102 +4,227 @@
|
||||
|
||||
module std::core::mem::allocator;
|
||||
import std::collections::map;
|
||||
import std::collections::list;
|
||||
|
||||
def PtrMap = HashMap<uptr, usz>;
|
||||
const MAX_BACKTRACE = 16;
|
||||
struct Allocation
|
||||
{
|
||||
void* ptr;
|
||||
usz size;
|
||||
void*[MAX_BACKTRACE] backtrace;
|
||||
}
|
||||
|
||||
def AllocMap = HashMap(<uptr, Allocation>);
|
||||
|
||||
// A simple tracking allocator.
|
||||
// It tracks allocations using a hash map but
|
||||
// is not compatible with allocators that uses mark()
|
||||
struct TrackingAllocator
|
||||
struct TrackingAllocator (Allocator)
|
||||
{
|
||||
inline Allocator allocator;
|
||||
Allocator* inner_allocator;
|
||||
PtrMap map;
|
||||
AllocMap map;
|
||||
usz mem_total;
|
||||
usz allocs_total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a memory arena for use using the provided bytes.
|
||||
* Initialize a tracking allocator to wrap (and track) another allocator.
|
||||
*
|
||||
* @require this != null
|
||||
* @param [&inout] allocator "The allocator to track"
|
||||
**/
|
||||
fn void TrackingAllocator.init(TrackingAllocator* this, Allocator* using)
|
||||
fn void TrackingAllocator.init(&self, Allocator* allocator)
|
||||
{
|
||||
*this = { .inner_allocator = using, .allocator.function = &tracking_allocator_fn };
|
||||
this.map.init(.using = using);
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.free(TrackingAllocator* this)
|
||||
{
|
||||
this.map.free();
|
||||
*this = {};
|
||||
*self = { .inner_allocator = allocator };
|
||||
self.map.init_new(.allocator = allocator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [inout] data
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! tracking_allocator_fn(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
|
||||
* Free this tracking allocator.
|
||||
**/
|
||||
fn void TrackingAllocator.free(&self)
|
||||
{
|
||||
TrackingAllocator* this = (TrackingAllocator*)data;
|
||||
void* result = this.inner_allocator.function(this.inner_allocator, size, alignment, offset, old_pointer, kind)!;
|
||||
switch (kind)
|
||||
{
|
||||
case CALLOC:
|
||||
case ALIGNED_CALLOC:
|
||||
case ALLOC:
|
||||
case ALIGNED_ALLOC:
|
||||
this.map.set((uptr)result, size);
|
||||
this.mem_total += size;
|
||||
this.allocs_total++;
|
||||
return result;
|
||||
case REALLOC:
|
||||
case ALIGNED_REALLOC:
|
||||
this.map.remove((uptr)old_pointer);
|
||||
this.map.set((uptr)result, size);
|
||||
this.mem_total += size;
|
||||
if (size > 0) this.allocs_total++;
|
||||
return result;
|
||||
case ALIGNED_FREE:
|
||||
case FREE:
|
||||
if (!old_pointer) return null;
|
||||
this.map.remove((uptr)old_pointer);
|
||||
return null;
|
||||
case MARK:
|
||||
// Unsupported
|
||||
return null;
|
||||
case RESET:
|
||||
this.map.clear();
|
||||
return null;
|
||||
}
|
||||
unreachable();
|
||||
self.map.free();
|
||||
*self = {};
|
||||
}
|
||||
|
||||
fn usz TrackingAllocator.allocated(TrackingAllocator* this)
|
||||
/**
|
||||
* @return "the total allocated memory not yet freed."
|
||||
**/
|
||||
fn usz TrackingAllocator.allocated(&self)
|
||||
{
|
||||
usz allocated = 0;
|
||||
@pool()
|
||||
{
|
||||
foreach (usz allocation : this.map.value_tlist())
|
||||
{
|
||||
allocated += allocation;
|
||||
}
|
||||
foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
|
||||
};
|
||||
return allocated;
|
||||
}
|
||||
|
||||
fn usz TrackingAllocator.total_allocated(TrackingAllocator* this)
|
||||
/**
|
||||
* @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)."
|
||||
**/
|
||||
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
|
||||
|
||||
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator* allocator)
|
||||
{
|
||||
return this.mem_total;
|
||||
return self.map.value_tlist();
|
||||
}
|
||||
|
||||
fn usz TrackingAllocator.total_allocation_count(TrackingAllocator* this)
|
||||
/**
|
||||
* @return "the number of non-freed allocations."
|
||||
**/
|
||||
fn usz TrackingAllocator.allocation_count(&self) => self.map.count;
|
||||
|
||||
fn void*! TrackingAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
return this.allocs_total;
|
||||
void* data = self.inner_allocator.acquire(size, clear, alignment, offset)!;
|
||||
self.allocs_total++;
|
||||
if (data)
|
||||
{
|
||||
void*[MAX_BACKTRACE] bt;
|
||||
backtrace::capture_current(&bt);
|
||||
self.map.set((uptr)data, { data, size, bt });
|
||||
self.mem_total += size;
|
||||
self.allocs_total++;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
fn usz TrackingAllocator.allocation_count(TrackingAllocator* this)
|
||||
fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
|
||||
{
|
||||
return this.map.count;
|
||||
void* data = self.inner_allocator.resize(old_pointer, size, alignment, offset)!;
|
||||
if (old_pointer)
|
||||
{
|
||||
self.map.remove((uptr)old_pointer);
|
||||
}
|
||||
if (data)
|
||||
{
|
||||
void*[MAX_BACKTRACE] bt;
|
||||
backtrace::capture_current(&bt);
|
||||
self.map.set((uptr)data, { data, size, bt });
|
||||
self.mem_total += size;
|
||||
self.allocs_total++;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
|
||||
{
|
||||
if (old_pointer)
|
||||
{
|
||||
if (catch self.map.remove((uptr)old_pointer))
|
||||
{
|
||||
assert(false, "Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
|
||||
}
|
||||
}
|
||||
self.inner_allocator.release(old_pointer, is_aligned);
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.clear(&self)
|
||||
{
|
||||
self.map.clear();
|
||||
}
|
||||
|
||||
fn void TrackingAllocator.print_report(&self) => self.fprint_report(io::stdout())!!;
|
||||
|
||||
fn void! TrackingAllocator.fprint_report(&self, OutStream* out)
|
||||
{
|
||||
|
||||
usz total = 0;
|
||||
usz entries = 0;
|
||||
bool leaks = false;
|
||||
@pool()
|
||||
{
|
||||
Allocation[] allocs = self.map.value_tlist();
|
||||
if (allocs.len)
|
||||
{
|
||||
if (!allocs[0].backtrace[0])
|
||||
{
|
||||
io::fprintn(out, "======== Memory Report ========")!;
|
||||
io::fprintn(out, "Size in bytes Address")!;
|
||||
foreach (i, &allocation : allocs)
|
||||
{
|
||||
entries++;
|
||||
total += allocation.size;
|
||||
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
|
||||
}
|
||||
io::fprintn(out, "===============================")!;
|
||||
|
||||
}
|
||||
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], mem::temp()).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
|
||||
}
|
||||
if (trace.function.len) leaks = true;
|
||||
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
|
||||
allocation.ptr, trace.function.len ? trace.function : "???",
|
||||
trace.line ? trace.line : 0)!;
|
||||
}
|
||||
io::fprintn(out, "===================================================================================")!;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], mem::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -35,16 +35,16 @@ macro rindex_of(array, element)
|
||||
*
|
||||
* @param [in] arr1
|
||||
* @param [in] arr2
|
||||
* @param [&inout] using "The allocator to use, default is the heap allocator"
|
||||
* @param [&inout] allocator "The allocator to use, default is the heap allocator"
|
||||
* @require @typekind(arr1) == SUBARRAY || @typekind(arr1) == ARRAY
|
||||
* @require @typekind(arr2) == SUBARRAY || @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* using = mem::heap())
|
||||
macro concat_new(arr1, arr2, Allocator* allocator = mem::heap())
|
||||
{
|
||||
var $Type = $typeof(arr1[0]);
|
||||
$Type[] result = malloc($Type, arr1.len + arr2.len, .using = using);
|
||||
$Type[] result = allocator.new_array($Type, arr1.len + arr2.len);
|
||||
if (arr1.len > 0)
|
||||
{
|
||||
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $Type.alignof, $Type.alignof);
|
||||
|
||||
@@ -87,3 +87,94 @@ bitstruct UInt128LE : uint128 @littleendian
|
||||
uint128 val : 0..127;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require is_array_or_sub_of_char(bytes) "argument must be an array, a pointer to an array or a subarray of char"
|
||||
* @require is_bitorder($Type) "type must be a bitorder integer"
|
||||
**/
|
||||
macro read(bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$endswitch
|
||||
return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require is_arrayptr_or_sub_of_char(bytes) "argument must be a pointer to an array or a subarray of char"
|
||||
* @require is_bitorder($Type) "type must be a bitorder integer"
|
||||
**/
|
||||
macro write(x, bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
s = bytes[:$Type.sizeof];
|
||||
$endswitch
|
||||
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
|
||||
}
|
||||
|
||||
macro is_bitorder($Type)
|
||||
{
|
||||
$switch ($Type)
|
||||
$case UShortLE:
|
||||
$case ShortLE:
|
||||
$case UIntLE:
|
||||
$case IntLE:
|
||||
$case ULongLE:
|
||||
$case LongLE:
|
||||
$case UInt128LE:
|
||||
$case Int128LE:
|
||||
$case UShortBE:
|
||||
$case ShortBE:
|
||||
$case UIntBE:
|
||||
$case IntBE:
|
||||
$case ULongBE:
|
||||
$case LongBE:
|
||||
$case UInt128BE:
|
||||
$case Int128BE:
|
||||
return true;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_array_or_sub_of_char(bytes)
|
||||
{
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
$if $Inner.kindof == ARRAY:
|
||||
var $Inner2 = $typefrom($Inner.inner);
|
||||
return $Inner2.typeid == char.typeid;
|
||||
$endif
|
||||
$case ARRAY:
|
||||
$case SUBARRAY:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
return $Inner.typeid == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_arrayptr_or_sub_of_char(bytes)
|
||||
{
|
||||
$switch (@typekind(bytes))
|
||||
$case POINTER:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
$if $Inner.kindof == ARRAY:
|
||||
var $Inner2 = $typefrom($Inner.inner);
|
||||
return $Inner2.typeid == char.typeid;
|
||||
$endif
|
||||
$case SUBARRAY:
|
||||
var $Inner = $typefrom($typeof(bytes).inner);
|
||||
return $Inner.typeid == char.typeid;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
@@ -37,19 +37,20 @@ fault CastResult
|
||||
**/
|
||||
macro void @scope(&variable; @body) @builtin
|
||||
{
|
||||
var temp = variable;
|
||||
defer variable = temp;
|
||||
var temp = *variable;
|
||||
defer *variable = temp;
|
||||
@body();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,45 +62,61 @@ macro void @swap(&a, &b) @builtin
|
||||
* @ensure @typeis(return, $Type*)
|
||||
* @return! CastResult.TYPE_MISMATCH
|
||||
**/
|
||||
macro anycast(any v, $Type) @builtin
|
||||
macro anycast(any* v, $Type) @builtin
|
||||
{
|
||||
if (v.type != $Type.typeid) return CastResult.TYPE_MISMATCH?;
|
||||
return ($Type*)v.ptr;
|
||||
}
|
||||
|
||||
struct CallstackElement
|
||||
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE)
|
||||
{
|
||||
CallstackElement* prev;
|
||||
String function;
|
||||
String file;
|
||||
uint line;
|
||||
@pool()
|
||||
{
|
||||
void*[256] buffer;
|
||||
void*[] backtraces = backtrace::capture_current(&buffer);
|
||||
backtraces_to_ignore++;
|
||||
BacktraceList! backtrace = backtrace::symbolize_backtrace(backtraces, mem::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;
|
||||
if (trace.is_unknown())
|
||||
{
|
||||
io::eprintn(" in ???");
|
||||
continue;
|
||||
}
|
||||
if (trace.has_file())
|
||||
{
|
||||
io::eprintfn(" in %s (%s:%d) [%s]", trace.function, trace.file, trace.line, trace.object_file);
|
||||
continue;
|
||||
}
|
||||
io::eprintfn(" in %s (source unavailable) [%s]", trace.function, trace.object_file);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
fn void default_panic(String message, String file, String function, uint line)
|
||||
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
|
||||
{
|
||||
CallstackElement* stack = $$stacktrace();
|
||||
$if $defined(io::stderr) && $defined(File.printf):
|
||||
|
||||
if (stack) stack = stack.prev;
|
||||
if (stack)
|
||||
{
|
||||
(void)io::stderr().print("\nERROR: '");
|
||||
(void)io::stderr().print(message);
|
||||
(void)io::stderr().printn("'");
|
||||
}
|
||||
else
|
||||
{
|
||||
(void)io::stderr().print("\nERROR: '");
|
||||
(void)io::stderr().print(message);
|
||||
(void)io::stderr().printfn("', in function %s (%s:%d)", function, file, line);
|
||||
}
|
||||
while (stack)
|
||||
{
|
||||
(void)io::stderr().printfn(" in function %s (%s:%d)", stack.function, stack.file, stack.line);
|
||||
if (stack == stack.prev) break;
|
||||
stack = stack.prev;
|
||||
}
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace(message, 2))
|
||||
{
|
||||
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
|
||||
return;
|
||||
}
|
||||
$endif
|
||||
$$trap();
|
||||
|
||||
}
|
||||
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
|
||||
{
|
||||
$if $defined(io::stderr):
|
||||
io::eprint("\nERROR: '");
|
||||
io::eprint(message);
|
||||
io::eprintfn("', in %s (%s:%d)", function, file, line);
|
||||
$endif
|
||||
$$trap();
|
||||
}
|
||||
@@ -110,12 +127,12 @@ PanicFn panic = &default_panic;
|
||||
|
||||
fn void panicf(String fmt, String file, String function, uint line, args...)
|
||||
{
|
||||
@stack_mem(512; Allocator* mem)
|
||||
@stack_mem(512; Allocator* allocator)
|
||||
{
|
||||
DString s;
|
||||
s.init(.using = mem);
|
||||
s.printf(fmt, ...args);
|
||||
panic(s.str(), file, function, line);
|
||||
s.init_new(.allocator = allocator);
|
||||
s.appendf(fmt, ...args);
|
||||
panic(s.str_view(), file, function, line);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -140,19 +157,37 @@ macro void unsupported(String string = "Unsupported function invoked") @builtin
|
||||
$$unreachable();
|
||||
}
|
||||
|
||||
macro any_make(void* ptr, typeid type) @builtin
|
||||
{
|
||||
return $$any_make(ptr, type);
|
||||
}
|
||||
|
||||
macro any.retype_to(&self, typeid type)
|
||||
{
|
||||
return $$any_make(self.ptr, type);
|
||||
}
|
||||
|
||||
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(result, $Type)
|
||||
* @ensure @typeis(return, $Type)
|
||||
**/
|
||||
macro bitcast(expr, $Type) @builtin
|
||||
{
|
||||
usz $size = $sizeof(expr);
|
||||
$Type x @noinit;
|
||||
mem::copy(&x, &expr, $size, $Type.alignof, $alignof(expr));
|
||||
return x;
|
||||
$if $Type.alignof <= $alignof(expr):
|
||||
return *($Type*)&expr;
|
||||
$else
|
||||
$Type x @noinit;
|
||||
$$memcpy(&x, &expr, $sizeof(expr), false, $Type.alignof, $alignof(expr));
|
||||
return x;
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,11 +216,14 @@ macro enum_by_name($Type, String enum_name) @builtin
|
||||
**/
|
||||
macro bool @likely(bool #value, $probability = 1.0) @builtin
|
||||
{
|
||||
$if $probability == 1.0:
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value;
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, true);
|
||||
$else
|
||||
$default:
|
||||
return $$expect_with_probability(#value, true, $probability);
|
||||
$endif
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,25 +235,31 @@ $endif
|
||||
**/
|
||||
macro bool @unlikely(bool #value, $probability = 1.0) @builtin
|
||||
{
|
||||
$if $probability == 1.0:
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value;
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, false);
|
||||
$else
|
||||
$default:
|
||||
return $$expect_with_probability(#value, false, $probability);
|
||||
$endif
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
* @require values::@is_int(#value) || values::@is_bool(#value)
|
||||
* @checked $typeof(#value) a = expected
|
||||
* @require $assignable(expected, $typeof(#value))
|
||||
* @require $probability >= 0 && $probability <= 1.0
|
||||
**/
|
||||
macro @expect(#value, expected, $probability = 1.0) @builtin
|
||||
{
|
||||
$if $probability == 1.0:
|
||||
$switch
|
||||
$case env::BUILTIN_EXPECT_IS_DISABLED:
|
||||
return #value == expected;
|
||||
$case $probability == 1.0:
|
||||
return $$expect(#value, ($typeof(#value))expected);
|
||||
$else
|
||||
$default:
|
||||
return $$expect_with_probability(#value, expected, $probability);
|
||||
$endif
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,9 +281,11 @@ enum PrefetchLocality
|
||||
* @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
|
||||
macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
|
||||
{
|
||||
$$prefetch(ptr, $write ? 1 : 0, $locality.ordinal);
|
||||
$if !env::BUILTIN_PREFETCH_IS_DISABLED:
|
||||
$$prefetch(ptr, $write ? 1 : 0, $locality.ordinal);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro swizzle(v, ...) @builtin
|
||||
@@ -252,26 +298,21 @@ macro swizzle2(v, v2, ...) @builtin
|
||||
return $$swizzle2(v, v2, $vasplat());
|
||||
}
|
||||
|
||||
macro bool @castable(#expr, $To) @builtin
|
||||
{
|
||||
return $checks(($To)#expr);
|
||||
}
|
||||
|
||||
macro bool @convertible(#expr, $To) @builtin
|
||||
{
|
||||
return $checks($To x = #expr);
|
||||
}
|
||||
|
||||
macro anyfault @catchof(#expr) @builtin
|
||||
macro anyfault @catch(#expr) @builtin
|
||||
{
|
||||
if (catch f = #expr) return f;
|
||||
return anyfault {};
|
||||
return anyfault {};
|
||||
}
|
||||
|
||||
macro bool @ok(#expr) @builtin
|
||||
{
|
||||
if (catch #expr) return false;
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
macro char[] @as_char_view(&value) @builtin
|
||||
{
|
||||
return ((char*)value)[:$sizeof(*value)];
|
||||
}
|
||||
|
||||
macro uint int.hash(int i) => i;
|
||||
@@ -287,4 +328,344 @@ macro uint uint128.hash(uint128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^
|
||||
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 char[].hash(char[] c) => (uint)fnv32a::encode(c);
|
||||
macro uint void*.hash(void* ptr) => ((ulong)(uptr)ptr).hash();
|
||||
|
||||
const MAX_FRAMEADDRESS = 128;
|
||||
/**
|
||||
* @require n >= 0
|
||||
**/
|
||||
macro void* get_frameaddress(int n)
|
||||
{
|
||||
if (n > MAX_FRAMEADDRESS) return null;
|
||||
switch (n)
|
||||
{
|
||||
case 0: return $$frameaddress(0);
|
||||
case 1: return $$frameaddress(1);
|
||||
case 2: return $$frameaddress(2);
|
||||
case 3: return $$frameaddress(3);
|
||||
case 4: return $$frameaddress(4);
|
||||
case 5: return $$frameaddress(5);
|
||||
case 6: return $$frameaddress(6);
|
||||
case 7: return $$frameaddress(7);
|
||||
case 8: return $$frameaddress(8);
|
||||
case 9: return $$frameaddress(9);
|
||||
case 10: return $$frameaddress(10);
|
||||
case 11: return $$frameaddress(11);
|
||||
case 12: return $$frameaddress(12);
|
||||
case 13: return $$frameaddress(13);
|
||||
case 14: return $$frameaddress(14);
|
||||
case 15: return $$frameaddress(15);
|
||||
case 16: return $$frameaddress(16);
|
||||
case 17: return $$frameaddress(17);
|
||||
case 18: return $$frameaddress(18);
|
||||
case 19: return $$frameaddress(19);
|
||||
case 20: return $$frameaddress(20);
|
||||
case 21: return $$frameaddress(21);
|
||||
case 22: return $$frameaddress(22);
|
||||
case 23: return $$frameaddress(23);
|
||||
case 24: return $$frameaddress(24);
|
||||
case 25: return $$frameaddress(25);
|
||||
case 26: return $$frameaddress(26);
|
||||
case 27: return $$frameaddress(27);
|
||||
case 28: return $$frameaddress(28);
|
||||
case 29: return $$frameaddress(29);
|
||||
case 30: return $$frameaddress(30);
|
||||
case 31: return $$frameaddress(31);
|
||||
case 32: return $$frameaddress(32);
|
||||
case 33: return $$frameaddress(33);
|
||||
case 34: return $$frameaddress(34);
|
||||
case 35: return $$frameaddress(35);
|
||||
case 36: return $$frameaddress(36);
|
||||
case 37: return $$frameaddress(37);
|
||||
case 38: return $$frameaddress(38);
|
||||
case 39: return $$frameaddress(39);
|
||||
case 40: return $$frameaddress(40);
|
||||
case 41: return $$frameaddress(41);
|
||||
case 42: return $$frameaddress(42);
|
||||
case 43: return $$frameaddress(43);
|
||||
case 44: return $$frameaddress(44);
|
||||
case 45: return $$frameaddress(45);
|
||||
case 46: return $$frameaddress(46);
|
||||
case 47: return $$frameaddress(47);
|
||||
case 48: return $$frameaddress(48);
|
||||
case 49: return $$frameaddress(49);
|
||||
case 50: return $$frameaddress(50);
|
||||
case 51: return $$frameaddress(51);
|
||||
case 52: return $$frameaddress(52);
|
||||
case 53: return $$frameaddress(53);
|
||||
case 54: return $$frameaddress(54);
|
||||
case 55: return $$frameaddress(55);
|
||||
case 56: return $$frameaddress(56);
|
||||
case 57: return $$frameaddress(57);
|
||||
case 58: return $$frameaddress(58);
|
||||
case 59: return $$frameaddress(59);
|
||||
case 60: return $$frameaddress(60);
|
||||
case 61: return $$frameaddress(61);
|
||||
case 62: return $$frameaddress(62);
|
||||
case 63: return $$frameaddress(63);
|
||||
case 64: return $$frameaddress(64);
|
||||
case 65: return $$frameaddress(65);
|
||||
case 66: return $$frameaddress(66);
|
||||
case 67: return $$frameaddress(67);
|
||||
case 68: return $$frameaddress(68);
|
||||
case 69: return $$frameaddress(69);
|
||||
case 70: return $$frameaddress(70);
|
||||
case 71: return $$frameaddress(71);
|
||||
case 72: return $$frameaddress(72);
|
||||
case 73: return $$frameaddress(73);
|
||||
case 74: return $$frameaddress(74);
|
||||
case 75: return $$frameaddress(75);
|
||||
case 76: return $$frameaddress(76);
|
||||
case 77: return $$frameaddress(77);
|
||||
case 78: return $$frameaddress(78);
|
||||
case 79: return $$frameaddress(79);
|
||||
case 80: return $$frameaddress(80);
|
||||
case 81: return $$frameaddress(81);
|
||||
case 82: return $$frameaddress(82);
|
||||
case 83: return $$frameaddress(83);
|
||||
case 84: return $$frameaddress(84);
|
||||
case 85: return $$frameaddress(85);
|
||||
case 86: return $$frameaddress(86);
|
||||
case 87: return $$frameaddress(87);
|
||||
case 88: return $$frameaddress(88);
|
||||
case 89: return $$frameaddress(89);
|
||||
case 90: return $$frameaddress(90);
|
||||
case 91: return $$frameaddress(91);
|
||||
case 92: return $$frameaddress(92);
|
||||
case 93: return $$frameaddress(93);
|
||||
case 94: return $$frameaddress(94);
|
||||
case 95: return $$frameaddress(95);
|
||||
case 96: return $$frameaddress(96);
|
||||
case 97: return $$frameaddress(97);
|
||||
case 98: return $$frameaddress(98);
|
||||
case 99: return $$frameaddress(99);
|
||||
case 100: return $$frameaddress(100);
|
||||
case 101: return $$frameaddress(101);
|
||||
case 102: return $$frameaddress(102);
|
||||
case 103: return $$frameaddress(103);
|
||||
case 104: return $$frameaddress(104);
|
||||
case 105: return $$frameaddress(105);
|
||||
case 106: return $$frameaddress(106);
|
||||
case 107: return $$frameaddress(107);
|
||||
case 108: return $$frameaddress(108);
|
||||
case 109: return $$frameaddress(109);
|
||||
case 110: return $$frameaddress(110);
|
||||
case 111: return $$frameaddress(111);
|
||||
case 112: return $$frameaddress(112);
|
||||
case 113: return $$frameaddress(113);
|
||||
case 114: return $$frameaddress(114);
|
||||
case 115: return $$frameaddress(115);
|
||||
case 116: return $$frameaddress(116);
|
||||
case 117: return $$frameaddress(117);
|
||||
case 118: return $$frameaddress(118);
|
||||
case 119: return $$frameaddress(119);
|
||||
case 120: return $$frameaddress(120);
|
||||
case 121: return $$frameaddress(121);
|
||||
case 122: return $$frameaddress(122);
|
||||
case 123: return $$frameaddress(123);
|
||||
case 124: return $$frameaddress(124);
|
||||
case 125: return $$frameaddress(125);
|
||||
case 126: return $$frameaddress(126);
|
||||
case 127: return $$frameaddress(127);
|
||||
case 128: return $$frameaddress(128);
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require n >= 0
|
||||
**/
|
||||
macro void* get_returnaddress(int n)
|
||||
{
|
||||
if (n > MAX_FRAMEADDRESS) return null;
|
||||
switch (n)
|
||||
{
|
||||
case 0: return $$returnaddress(0);
|
||||
case 1: return $$returnaddress(1);
|
||||
case 2: return $$returnaddress(2);
|
||||
case 3: return $$returnaddress(3);
|
||||
case 4: return $$returnaddress(4);
|
||||
case 5: return $$returnaddress(5);
|
||||
case 6: return $$returnaddress(6);
|
||||
case 7: return $$returnaddress(7);
|
||||
case 8: return $$returnaddress(8);
|
||||
case 9: return $$returnaddress(9);
|
||||
case 10: return $$returnaddress(10);
|
||||
case 11: return $$returnaddress(11);
|
||||
case 12: return $$returnaddress(12);
|
||||
case 13: return $$returnaddress(13);
|
||||
case 14: return $$returnaddress(14);
|
||||
case 15: return $$returnaddress(15);
|
||||
case 16: return $$returnaddress(16);
|
||||
case 17: return $$returnaddress(17);
|
||||
case 18: return $$returnaddress(18);
|
||||
case 19: return $$returnaddress(19);
|
||||
case 20: return $$returnaddress(20);
|
||||
case 21: return $$returnaddress(21);
|
||||
case 22: return $$returnaddress(22);
|
||||
case 23: return $$returnaddress(23);
|
||||
case 24: return $$returnaddress(24);
|
||||
case 25: return $$returnaddress(25);
|
||||
case 26: return $$returnaddress(26);
|
||||
case 27: return $$returnaddress(27);
|
||||
case 28: return $$returnaddress(28);
|
||||
case 29: return $$returnaddress(29);
|
||||
case 30: return $$returnaddress(30);
|
||||
case 31: return $$returnaddress(31);
|
||||
case 32: return $$returnaddress(32);
|
||||
case 33: return $$returnaddress(33);
|
||||
case 34: return $$returnaddress(34);
|
||||
case 35: return $$returnaddress(35);
|
||||
case 36: return $$returnaddress(36);
|
||||
case 37: return $$returnaddress(37);
|
||||
case 38: return $$returnaddress(38);
|
||||
case 39: return $$returnaddress(39);
|
||||
case 40: return $$returnaddress(40);
|
||||
case 41: return $$returnaddress(41);
|
||||
case 42: return $$returnaddress(42);
|
||||
case 43: return $$returnaddress(43);
|
||||
case 44: return $$returnaddress(44);
|
||||
case 45: return $$returnaddress(45);
|
||||
case 46: return $$returnaddress(46);
|
||||
case 47: return $$returnaddress(47);
|
||||
case 48: return $$returnaddress(48);
|
||||
case 49: return $$returnaddress(49);
|
||||
case 50: return $$returnaddress(50);
|
||||
case 51: return $$returnaddress(51);
|
||||
case 52: return $$returnaddress(52);
|
||||
case 53: return $$returnaddress(53);
|
||||
case 54: return $$returnaddress(54);
|
||||
case 55: return $$returnaddress(55);
|
||||
case 56: return $$returnaddress(56);
|
||||
case 57: return $$returnaddress(57);
|
||||
case 58: return $$returnaddress(58);
|
||||
case 59: return $$returnaddress(59);
|
||||
case 60: return $$returnaddress(60);
|
||||
case 61: return $$returnaddress(61);
|
||||
case 62: return $$returnaddress(62);
|
||||
case 63: return $$returnaddress(63);
|
||||
case 64: return $$returnaddress(64);
|
||||
case 65: return $$returnaddress(65);
|
||||
case 66: return $$returnaddress(66);
|
||||
case 67: return $$returnaddress(67);
|
||||
case 68: return $$returnaddress(68);
|
||||
case 69: return $$returnaddress(69);
|
||||
case 70: return $$returnaddress(70);
|
||||
case 71: return $$returnaddress(71);
|
||||
case 72: return $$returnaddress(72);
|
||||
case 73: return $$returnaddress(73);
|
||||
case 74: return $$returnaddress(74);
|
||||
case 75: return $$returnaddress(75);
|
||||
case 76: return $$returnaddress(76);
|
||||
case 77: return $$returnaddress(77);
|
||||
case 78: return $$returnaddress(78);
|
||||
case 79: return $$returnaddress(79);
|
||||
case 80: return $$returnaddress(80);
|
||||
case 81: return $$returnaddress(81);
|
||||
case 82: return $$returnaddress(82);
|
||||
case 83: return $$returnaddress(83);
|
||||
case 84: return $$returnaddress(84);
|
||||
case 85: return $$returnaddress(85);
|
||||
case 86: return $$returnaddress(86);
|
||||
case 87: return $$returnaddress(87);
|
||||
case 88: return $$returnaddress(88);
|
||||
case 89: return $$returnaddress(89);
|
||||
case 90: return $$returnaddress(90);
|
||||
case 91: return $$returnaddress(91);
|
||||
case 92: return $$returnaddress(92);
|
||||
case 93: return $$returnaddress(93);
|
||||
case 94: return $$returnaddress(94);
|
||||
case 95: return $$returnaddress(95);
|
||||
case 96: return $$returnaddress(96);
|
||||
case 97: return $$returnaddress(97);
|
||||
case 98: return $$returnaddress(98);
|
||||
case 99: return $$returnaddress(99);
|
||||
case 100: return $$returnaddress(100);
|
||||
case 101: return $$returnaddress(101);
|
||||
case 102: return $$returnaddress(102);
|
||||
case 103: return $$returnaddress(103);
|
||||
case 104: return $$returnaddress(104);
|
||||
case 105: return $$returnaddress(105);
|
||||
case 106: return $$returnaddress(106);
|
||||
case 107: return $$returnaddress(107);
|
||||
case 108: return $$returnaddress(108);
|
||||
case 109: return $$returnaddress(109);
|
||||
case 110: return $$returnaddress(110);
|
||||
case 111: return $$returnaddress(111);
|
||||
case 112: return $$returnaddress(112);
|
||||
case 113: return $$returnaddress(113);
|
||||
case 114: return $$returnaddress(114);
|
||||
case 115: return $$returnaddress(115);
|
||||
case 116: return $$returnaddress(116);
|
||||
case 117: return $$returnaddress(117);
|
||||
case 118: return $$returnaddress(118);
|
||||
case 119: return $$returnaddress(119);
|
||||
case 120: return $$returnaddress(120);
|
||||
case 121: return $$returnaddress(121);
|
||||
case 122: return $$returnaddress(122);
|
||||
case 123: return $$returnaddress(123);
|
||||
case 124: return $$returnaddress(124);
|
||||
case 125: return $$returnaddress(125);
|
||||
case 126: return $$returnaddress(126);
|
||||
case 127: return $$returnaddress(127);
|
||||
case 128: return $$returnaddress(128);
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
|
||||
import libc;
|
||||
|
||||
fn void sig_panic(String message)
|
||||
{
|
||||
default_panic(message, "???", "???", 0);
|
||||
}
|
||||
|
||||
SignalFunction old_bus_error;
|
||||
SignalFunction old_segmentation_fault;
|
||||
|
||||
fn void sig_bus_error(CInt i)
|
||||
{
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Illegal memory access.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Illegal memory access.", 1))
|
||||
{
|
||||
io::eprintn("\nERROR: 'Illegal memory access'.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
$$trap();
|
||||
}
|
||||
|
||||
fn void sig_segmentation_fault(CInt i)
|
||||
{
|
||||
$if !env::NATIVE_STACKTRACE:
|
||||
sig_panic("Out of bounds memory access.");
|
||||
$else
|
||||
$if $defined(io::stderr):
|
||||
if (!print_backtrace("Out of bounds memory access.", 1))
|
||||
{
|
||||
io::eprintn("\nERROR: Memory error without backtrace, possible stack overflow.");
|
||||
}
|
||||
$endif
|
||||
$endif
|
||||
$$trap();
|
||||
}
|
||||
|
||||
fn void install_signal_handler(CInt signal, SignalFunction func) @local
|
||||
{
|
||||
SignalFunction old = libc::signal(signal, func);
|
||||
// Restore
|
||||
if ((iptr)old > 1024) libc::signal(signal, old);
|
||||
}
|
||||
|
||||
// Clean this up
|
||||
fn void install_signal_handlers() @init(101) @local
|
||||
{
|
||||
install_signal_handler(libc::SIGBUS, &sig_bus_error);
|
||||
install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@ module std::core::builtin;
|
||||
**/
|
||||
macro less(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return a.less(b);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) < 0;
|
||||
$default:
|
||||
return a < b;
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,14 +23,14 @@ $endswitch
|
||||
**/
|
||||
macro less_eq(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return !b.less(a);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) <= 0;
|
||||
$default:
|
||||
return a <= b;
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,29 +38,43 @@ $endswitch
|
||||
**/
|
||||
macro greater(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return b.less(a);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) > 0;
|
||||
$default:
|
||||
return a > b;
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
|
||||
**/
|
||||
macro int compare_to(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b);
|
||||
$case $defined(a.less):
|
||||
return (int)b.less(a) - (int)a.less(b);
|
||||
$default:
|
||||
return (int)(a > b) - (int)(a < b);
|
||||
$endswitch
|
||||
}
|
||||
/**
|
||||
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
|
||||
**/
|
||||
macro greater_eq(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$switch
|
||||
$case $defined(a.less):
|
||||
return !a.less(b);
|
||||
$case $defined(a.compare_to):
|
||||
return a.compare_to(b) >= 0;
|
||||
$default:
|
||||
return a >= b;
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,47 +82,47 @@ $endswitch
|
||||
**/
|
||||
macro bool equals(a, b) @builtin
|
||||
{
|
||||
$switch
|
||||
$case $defined(a.equals):
|
||||
$switch
|
||||
$case $defined(a.equals, a.equals(b)):
|
||||
return a.equals(b);
|
||||
$case $defined(a.compare_to):
|
||||
$case $defined(a.compare_to, a.compare_to(b)):
|
||||
return a.compare_to(b) == 0;
|
||||
$case $defined(a.less):
|
||||
return !a.less(b) && !b.less(a);
|
||||
$default:
|
||||
return a == b;
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro min(x, ...) @builtin
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return less(x, $vaarg(0)) ? x : $vaarg(0);
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (less($vaarg($i), result))
|
||||
{
|
||||
result = $vaarg($i);
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
$endif
|
||||
$if $vacount == 1:
|
||||
return less(x, $vaarg(0)) ? x : $vaarg(0);
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (less($vaarg($i), result))
|
||||
{
|
||||
result = $vaarg($i);
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro max(x, ...) @builtin
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return greater(x, $vaarg(0)) ? x : $vaarg(0);
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (greater($vaarg($i), result))
|
||||
{
|
||||
result = $vaarg($i);
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
$endif
|
||||
$if $vacount == 1:
|
||||
return greater(x, $vaarg(0)) ? x : $vaarg(0);
|
||||
$else
|
||||
var result = x;
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
if (greater($vaarg($i), result))
|
||||
{
|
||||
result = $vaarg($i);
|
||||
}
|
||||
$endfor
|
||||
return result;
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
@@ -16,72 +16,40 @@ $assert C_SHORT_SIZE <= C_INT_SIZE;
|
||||
$assert C_INT_SIZE <= C_LONG_SIZE;
|
||||
$assert C_LONG_SIZE <= C_LONG_LONG_SIZE;
|
||||
|
||||
$switch ($$C_INT_SIZE)
|
||||
$case 64:
|
||||
def CInt = long;
|
||||
def CUInt = ulong;
|
||||
$case 32:
|
||||
def CInt = int;
|
||||
def CUInt = uint;
|
||||
$case 16:
|
||||
def CInt = short;
|
||||
def CUInt = ushort;
|
||||
$default:
|
||||
$error "Invalid C int size";
|
||||
$endswitch
|
||||
|
||||
$switch ($$C_LONG_SIZE)
|
||||
$case 64:
|
||||
def CLong = long;
|
||||
def CULong = ulong;
|
||||
$case 32:
|
||||
def CLong = int;
|
||||
def CULong = uint;
|
||||
$case 16:
|
||||
def CLong = short;
|
||||
def CULong = ushort;
|
||||
$default:
|
||||
$error "Invalid C long size";
|
||||
$endswitch
|
||||
|
||||
$switch ($$C_SHORT_SIZE)
|
||||
$case 32:
|
||||
def CShort = int;
|
||||
def CUShort = uint;
|
||||
$case 16:
|
||||
def CShort = short;
|
||||
def CUShort = ushort;
|
||||
$case 8:
|
||||
def CShort = ichar;
|
||||
def CUShort = char;
|
||||
$default:
|
||||
$error "Invalid C short size";
|
||||
$endswitch
|
||||
|
||||
$switch ($$C_LONG_LONG_SIZE)
|
||||
$case 128:
|
||||
def CLongLong = int128;
|
||||
def CULongLong = uint128;
|
||||
$case 64:
|
||||
def CLongLong = long;
|
||||
def CULongLong = ulong;
|
||||
$case 32:
|
||||
def CLongLong = int;
|
||||
def CULongLong = uint;
|
||||
$case 16:
|
||||
def CLongLong = short;
|
||||
def CULongLong = ushort;
|
||||
$default:
|
||||
$error "Invalid C long long size";
|
||||
$endswitch
|
||||
|
||||
|
||||
|
||||
def CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
|
||||
def CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
|
||||
def CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
|
||||
def CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
|
||||
def CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
|
||||
def CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
|
||||
def CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
def CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
|
||||
def CSChar = ichar;
|
||||
def CUChar = char;
|
||||
|
||||
$if $$C_CHAR_IS_SIGNED:
|
||||
def CChar = ichar;
|
||||
$else
|
||||
def CChar = char;
|
||||
$endif
|
||||
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
|
||||
|
||||
// Helper macros
|
||||
macro typeid signed_int_from_bitsize(usz $bitsize) @private
|
||||
{
|
||||
$switch ($bitsize)
|
||||
$case 128: return int128.typeid;
|
||||
$case 64: return long.typeid;
|
||||
$case 32: return int.typeid;
|
||||
$case 16: return short.typeid;
|
||||
$case 8: return ichar.typeid;
|
||||
$default: $error("Invalid bitsize");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
|
||||
{
|
||||
$switch ($bitsize)
|
||||
$case 128: return uint128.typeid;
|
||||
$case 64: return ulong.typeid;
|
||||
$case 32: return uint.typeid;
|
||||
$case 16: return ushort.typeid;
|
||||
$case 8: return char.typeid;
|
||||
$default: $error("Invalid bitsize");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
@@ -12,37 +12,36 @@ const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
|
||||
/**
|
||||
* @param c `The utf32 codepoint to convert`
|
||||
* @param [out] output `the resulting buffer`
|
||||
* @param available `the size available`
|
||||
**/
|
||||
fn usz! char32_to_utf8(Char32 c, char* output, usz available)
|
||||
fn usz! char32_to_utf8(Char32 c, char[] output)
|
||||
{
|
||||
if (!available) return UnicodeResult.CONVERSION_FAILED?;
|
||||
if (!output.len) return UnicodeResult.CONVERSION_FAILED?;
|
||||
switch (true)
|
||||
{
|
||||
case c <= 0x7f:
|
||||
output[0] = (char)c;
|
||||
return 1;
|
||||
case c <= 0x7ff:
|
||||
if (available < 2) return UnicodeResult.CONVERSION_FAILED?;
|
||||
if (output.len < 2) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xC0 | c >> 6);
|
||||
output[1] = (char)(0x80 | (c & 0x3F));
|
||||
return 2;
|
||||
case c <= 0xffff:
|
||||
if (available < 3) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xE0 | c >> 12);
|
||||
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c & 0x3F));
|
||||
return 3;
|
||||
case c <= 0x10ffff:
|
||||
if (available < 4) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xF0 | c >> 18);
|
||||
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[3] = (char)(0x80 | (c & 0x3F));
|
||||
return 4;
|
||||
default:
|
||||
// 0x10FFFF and above is not defined.
|
||||
return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[1] = (char)(0x80 | (c & 0x3F));
|
||||
return 2;
|
||||
case c <= 0xffff:
|
||||
if (output.len < 3) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xE0 | c >> 12);
|
||||
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c & 0x3F));
|
||||
return 3;
|
||||
case c <= 0x10ffff:
|
||||
if (output.len < 4) return UnicodeResult.CONVERSION_FAILED?;
|
||||
output[0] = (char)(0xF0 | c >> 18);
|
||||
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
output[3] = (char)(0x80 | (c & 0x3F));
|
||||
return 4;
|
||||
default:
|
||||
// 0x10FFFF and above is not defined.
|
||||
return UnicodeResult.CONVERSION_FAILED?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +82,7 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
*available = 1;
|
||||
return;
|
||||
}
|
||||
// Low surrogate first is an error
|
||||
// Low surrogate first is an error
|
||||
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return UnicodeResult.INVALID_UTF16?;
|
||||
|
||||
// Unmatched high surrogate is an error
|
||||
@@ -94,10 +93,10 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
// Unmatched high surrogate, invalid
|
||||
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return UnicodeResult.INVALID_UTF16?;
|
||||
|
||||
// The high bits of the codepoint are the value bits of the high surrogate
|
||||
// The low bits of the codepoint are the value bits of the low surrogate
|
||||
Char32 uc = (high & UTF16_SURROGATE_CODEPOINT_MASK) << UTF16_SURROGATE_BITS
|
||||
| (low & UTF16_SURROGATE_CODEPOINT_MASK) + UTF16_SURROGATE_OFFSET;
|
||||
// The high bits of the codepoint are the value bits of the high surrogate
|
||||
// The low bits of the codepoint are the value bits of the low surrogate
|
||||
Char32 uc = (high & UTF16_SURROGATE_CODEPOINT_MASK) << UTF16_SURROGATE_BITS
|
||||
| (low & UTF16_SURROGATE_CODEPOINT_MASK) + UTF16_SURROGATE_OFFSET;
|
||||
char32_to_utf8_unsafe(uc, output);
|
||||
*available = 2;
|
||||
}
|
||||
@@ -105,24 +104,28 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
|
||||
* @param c `The utf32 codepoint to convert`
|
||||
* @param [inout] output `the resulting buffer`
|
||||
**/
|
||||
fn void char32_to_utf8_unsafe(Char32 c, char** output)
|
||||
fn usz char32_to_utf8_unsafe(Char32 c, char** output)
|
||||
{
|
||||
switch (true)
|
||||
switch
|
||||
{
|
||||
case c < 0x7f:
|
||||
(*output)++[0] = (char)c;
|
||||
return 1;
|
||||
case c < 0x7ff:
|
||||
(*output)++[0] = (char)(0xC0 | c >> 6);
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
case c < 0xffff:
|
||||
(*output)++[0] = (char)(0xE0 | c >> 12);
|
||||
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
default:
|
||||
(*output)++[0] = (char)(0xF0 | c >> 18);
|
||||
(*output)++[0] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
return 2;
|
||||
case c < 0xffff:
|
||||
(*output)++[0] = (char)(0xE0 | c >> 12);
|
||||
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
return 3;
|
||||
default:
|
||||
(*output)++[0] = (char)(0xF0 | c >> 18);
|
||||
(*output)++[0] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
(*output)++[0] = (char)(0x80 | (c & 0x3F));
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,26 +140,26 @@ fn Char32! utf8_to_char32(char* ptr, usz* size)
|
||||
if (max_size < 1) return UnicodeResult.INVALID_UTF8?;
|
||||
char c = (ptr++)[0];
|
||||
|
||||
if ((c & 0x80) == 0)
|
||||
{
|
||||
*size = 1;
|
||||
return c;
|
||||
}
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
if ((c & 0x80) == 0)
|
||||
{
|
||||
*size = 1;
|
||||
return c;
|
||||
}
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
if (max_size < 2) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 2;
|
||||
Char32 uc = (c & 0x1F) << 6;
|
||||
c = *ptr;
|
||||
// Overlong sequence or invalid second.
|
||||
*size = 2;
|
||||
Char32 uc = (c & 0x1F) << 6;
|
||||
c = *ptr;
|
||||
// Overlong sequence or invalid second.
|
||||
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
if (max_size < 3) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 3;
|
||||
Char32 uc = (c & 0x0F) << 12;
|
||||
*size = 3;
|
||||
Char32 uc = (c & 0x0F) << 12;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
uc += (c & 0x3F) << 6;
|
||||
@@ -164,11 +167,11 @@ fn Char32! utf8_to_char32(char* ptr, usz* size)
|
||||
// Overlong sequence or invalid last
|
||||
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if (max_size < 4) return UnicodeResult.INVALID_UTF8?;
|
||||
if ((c & 0xF8) != 0xF0) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 4;
|
||||
Char32 uc = (c & 0x07) << 18;
|
||||
}
|
||||
if (max_size < 4) return UnicodeResult.INVALID_UTF8?;
|
||||
if ((c & 0xF8) != 0xF0) return UnicodeResult.INVALID_UTF8?;
|
||||
*size = 4;
|
||||
Char32 uc = (c & 0x07) << 18;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
|
||||
uc += (c & 0x3F) << 12;
|
||||
@@ -272,8 +275,8 @@ fn usz utf16len_for_utf8(String utf8)
|
||||
if (c & 0xF0 == 0xE0) continue;
|
||||
i++;
|
||||
len16++;
|
||||
}
|
||||
return len16;
|
||||
}
|
||||
return len16;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -297,19 +300,17 @@ fn usz utf16len_for_utf32(Char32[] utf32)
|
||||
* @param [out] utf8_buffer
|
||||
* @return `the number of bytes written.`
|
||||
**/
|
||||
fn usz! utf32to8(Char32[] utf32, String utf8_buffer)
|
||||
fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
|
||||
{
|
||||
usz len = utf8_buffer.len;
|
||||
char* ptr = utf8_buffer.ptr;
|
||||
foreach (Char32 uc : utf32)
|
||||
char[] buffer = utf8_buffer;
|
||||
foreach (uc : utf32)
|
||||
{
|
||||
usz used = char32_to_utf8(uc, ptr, len) @inline!;
|
||||
len -= used;
|
||||
ptr += used;
|
||||
usz used = char32_to_utf8(uc, buffer) @inline!;
|
||||
buffer = buffer[used..];
|
||||
}
|
||||
// Zero terminate if there is space.
|
||||
if (len > 0) ptr[0] = 0;
|
||||
return utf8_buffer.len - len;
|
||||
if (buffer.len > 0) buffer[0] = 0;
|
||||
return utf8_buffer.len - buffer.len;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,19 +324,19 @@ fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
Char32* ptr = utf32_buffer.ptr;
|
||||
usz len32 = 0;
|
||||
usz buf_len = utf32_buffer.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED?;
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
ptr[len32++] = uc;
|
||||
}
|
||||
// Zero terminate if possible
|
||||
if (len32 + 1 < buf_len) ptr[len32] = 0;
|
||||
return len32;
|
||||
usz len32 = 0;
|
||||
usz buf_len = utf32_buffer.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED?;
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
ptr[len32++] = uc;
|
||||
}
|
||||
// Zero terminate if possible
|
||||
if (len32 + 1 < buf_len) ptr[len32] = 0;
|
||||
return len32;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,12 +370,12 @@ fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
(utf32_buffer++)[0] = uc;
|
||||
}
|
||||
{
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
i += width;
|
||||
(utf32_buffer++)[0] = uc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,13 +389,13 @@ fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
|
||||
fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
|
||||
{
|
||||
usz len = utf8.len;
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
char32_to_utf16_unsafe(uc, &utf16_buffer) @inline;
|
||||
i += width;
|
||||
}
|
||||
for (usz i = 0; i < len;)
|
||||
{
|
||||
usz width = len - i;
|
||||
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
|
||||
char32_to_utf16_unsafe(uc, &utf16_buffer) @inline;
|
||||
i += width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,40 +1,43 @@
|
||||
module std::core::dstring;
|
||||
import std::io;
|
||||
|
||||
def DString = distinct void*;
|
||||
distinct DString (OutStream) = void*;
|
||||
|
||||
const usz MIN_CAPACITY @private = 16;
|
||||
|
||||
/**
|
||||
* @require !str.data() "String already initialized"
|
||||
* @require !self.data() "String already initialized"
|
||||
**/
|
||||
fn void DString.init(DString *str, usz capacity = MIN_CAPACITY, Allocator* using = mem::heap())
|
||||
fn DString DString.init_new(&self, usz capacity = MIN_CAPACITY, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
|
||||
StringData* data = malloc(StringData, 1, .using = using, .end_padding = capacity);
|
||||
data.allocator = using;
|
||||
StringData* data = allocator.new(StringData, .end_padding = capacity);
|
||||
data.allocator = allocator;
|
||||
data.len = 0;
|
||||
data.capacity = capacity;
|
||||
*str = (DString)data;
|
||||
return *self = (DString)data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !str.data() "String already initialized"
|
||||
* @require !self.data() "String already initialized"
|
||||
**/
|
||||
fn void DString.tinit(DString *str, usz capacity = MIN_CAPACITY) => str.init(capacity, mem::temp()) @inline;
|
||||
|
||||
fn DString new_with_capacity(usz capacity, Allocator* using = mem::heap())
|
||||
fn DString DString.init_temp(&self, usz capacity = MIN_CAPACITY)
|
||||
{
|
||||
DString dstr;
|
||||
dstr.init(capacity, using);
|
||||
return dstr;
|
||||
self.init_new(capacity, mem::temp()) @inline;
|
||||
return *self;
|
||||
}
|
||||
|
||||
fn DString tnew_with_capacity(usz capacity) => new_with_capacity(capacity, mem::temp()) @inline;
|
||||
fn DString new_with_capacity(usz capacity, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return DString{}.init_new(capacity, allocator);
|
||||
}
|
||||
|
||||
fn DString new(String c = "", Allocator* using = mem::heap())
|
||||
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, mem::temp()) @inline;
|
||||
|
||||
fn DString new(String c = "", Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz len = c.len;
|
||||
StringData* data = (StringData*)new_with_capacity(len, using);
|
||||
StringData* data = (StringData*)new_with_capacity(len, allocator);
|
||||
if (len)
|
||||
{
|
||||
data.len = len;
|
||||
@@ -43,26 +46,27 @@ fn DString new(String c = "", Allocator* using = mem::heap())
|
||||
return (DString)data;
|
||||
}
|
||||
|
||||
fn DString tnew(String s = "") => new(s, mem::temp()) @inline;
|
||||
fn DString temp_new(String s = "") => new(s, mem::temp()) @inline;
|
||||
|
||||
fn DString DString.new_concat(DString a, DString b, Allocator* using = mem::heap())
|
||||
fn DString DString.new_concat(self, DString b, Allocator* allocator = mem::heap())
|
||||
{
|
||||
DString string;
|
||||
string.init(a.len() + b.len(), using);
|
||||
string.append(a);
|
||||
string.init_new(self.len() + b.len(), allocator);
|
||||
string.append(self);
|
||||
string.append(b);
|
||||
return string;
|
||||
}
|
||||
|
||||
fn DString DString.new_tconcat(DString a, DString b) => a.new_concat(b, mem::temp());
|
||||
fn DString DString.new_tconcat(self, DString b) => self.new_concat(b, mem::temp());
|
||||
|
||||
fn ZString DString.zstr(DString str)
|
||||
fn ZString DString.zstr_view(&self)
|
||||
{
|
||||
StringData* data = str.data();
|
||||
StringData* data = self.data();
|
||||
if (!data) return "";
|
||||
if (data.capacity == data.len)
|
||||
{
|
||||
str.reserve(1);
|
||||
self.reserve(1);
|
||||
data = self.data();
|
||||
data.chars[data.len] = 0;
|
||||
}
|
||||
else if (data.chars[data.len] != 0)
|
||||
@@ -72,139 +76,116 @@ fn ZString DString.zstr(DString str)
|
||||
return (ZString)&data.chars[0];
|
||||
}
|
||||
|
||||
fn usz DString.capacity(DString this)
|
||||
fn usz DString.capacity(self)
|
||||
{
|
||||
if (!this) return 0;
|
||||
return this.data().capacity;
|
||||
if (!self) return 0;
|
||||
return self.data().capacity;
|
||||
}
|
||||
|
||||
fn usz DString.len(DString this)
|
||||
fn usz DString.len(&self) @dynamic
|
||||
{
|
||||
if (!this) return 0;
|
||||
return this.data().len;
|
||||
if (!*self) return 0;
|
||||
return self.data().len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require new_size <= this.len()
|
||||
* @require new_size <= self.len()
|
||||
*/
|
||||
fn void DString.chop(DString this, usz new_size)
|
||||
fn void DString.chop(self, usz new_size)
|
||||
{
|
||||
if (!this) return;
|
||||
this.data().len = new_size;
|
||||
if (!self) return;
|
||||
self.data().len = new_size;
|
||||
}
|
||||
|
||||
fn String DString.str(DString str)
|
||||
fn String DString.str_view(self)
|
||||
{
|
||||
StringData* data = (StringData*)str;
|
||||
StringData* data = self.data();
|
||||
if (!data) return "";
|
||||
return (String)data.chars[:data.len];
|
||||
}
|
||||
|
||||
fn void DString.append_utf32(DString* str, Char32[] chars)
|
||||
fn void DString.append_utf32(&self, Char32[] chars)
|
||||
{
|
||||
str.reserve(chars.len);
|
||||
self.reserve(chars.len);
|
||||
foreach (Char32 c : chars)
|
||||
{
|
||||
str.append_char32(c);
|
||||
self.append_char32(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < str.len()
|
||||
* @require index < self.len()
|
||||
**/
|
||||
fn void DString.set(DString str, usz index, char c)
|
||||
fn void DString.set(self, usz index, char c)
|
||||
{
|
||||
str.data().chars[index] = c;
|
||||
self.data().chars[index] = c;
|
||||
}
|
||||
|
||||
fn void DString.append_repeat(DString* str, char c, usz times)
|
||||
fn void DString.append_repeat(&self, char c, usz times)
|
||||
{
|
||||
if (times == 0) return;
|
||||
str.reserve(times);
|
||||
StringData* data = str.data();
|
||||
self.reserve(times);
|
||||
StringData* data = self.data();
|
||||
for (usz i = 0; i < times; i++)
|
||||
{
|
||||
data.chars[data.len++] = c;
|
||||
data.chars[data.len++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require c <= 0x10ffff
|
||||
*/
|
||||
fn void DString.append_char32(DString* str, Char32 c)
|
||||
fn void DString.append_char32(&self, Char32 c)
|
||||
{
|
||||
if (c < 0x7f)
|
||||
{
|
||||
str.reserve(1);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)c;
|
||||
return;
|
||||
}
|
||||
if (c < 0x7ff)
|
||||
{
|
||||
str.reserve(2);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xC0 | c >> 6);
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
return;
|
||||
}
|
||||
if (c < 0xffff)
|
||||
{
|
||||
str.reserve(3);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xE0 | c >> 12);
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
return;
|
||||
}
|
||||
str.reserve(4);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xF0 | c >> 18);
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
char[4] buffer @noinit;
|
||||
char* p = &buffer;
|
||||
usz n = conv::char32_to_utf8_unsafe(c, &p);
|
||||
self.reserve(n);
|
||||
StringData* data = self.data();
|
||||
data.chars[data.len:n] = buffer[:n];
|
||||
data.len += n;
|
||||
}
|
||||
|
||||
fn DString DString.tcopy(DString* str) => str.copy(mem::temp());
|
||||
fn DString DString.tcopy(&self) => self.copy(mem::temp());
|
||||
|
||||
fn DString DString.copy(DString* str, Allocator* using = null)
|
||||
fn DString DString.copy(self, Allocator* allocator = null)
|
||||
{
|
||||
if (!str)
|
||||
if (!self)
|
||||
{
|
||||
if (using) return new_with_capacity(0, using);
|
||||
if (allocator) return new_with_capacity(0, allocator);
|
||||
return (DString)null;
|
||||
}
|
||||
if (!using) using = mem::heap();
|
||||
StringData* data = str.data();
|
||||
DString new_string = new_with_capacity(data.capacity, using);
|
||||
StringData* data = self.data();
|
||||
if (!allocator) allocator = mem::heap();
|
||||
DString new_string = new_with_capacity(data.capacity, allocator);
|
||||
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
|
||||
return new_string;
|
||||
}
|
||||
|
||||
fn ZString DString.copy_zstr(DString* str, Allocator* using = mem::heap())
|
||||
fn ZString DString.copy_zstr(self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz str_len = str.len();
|
||||
usz str_len = self.len();
|
||||
if (!str_len)
|
||||
{
|
||||
return (ZString)calloc(1, .using = using);
|
||||
return (ZString)allocator.calloc(1);
|
||||
}
|
||||
char* zstr = malloc(str_len + 1, .using = using);
|
||||
StringData* data = str.data();
|
||||
char* zstr = allocator.alloc(str_len + 1);
|
||||
StringData* data = self.data();
|
||||
mem::copy(zstr, &data.chars, str_len);
|
||||
zstr[str_len] = 0;
|
||||
return (ZString)zstr;
|
||||
}
|
||||
|
||||
fn String DString.copy_str(DString* str, Allocator* using = mem::heap())
|
||||
fn String DString.copy_str(self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return (String)str.copy_zstr(using)[:str.len()];
|
||||
return (String)self.copy_zstr(allocator)[:self.len()];
|
||||
}
|
||||
|
||||
fn String DString.tcopy_str(DString* str) => str.copy_str(mem::temp()) @inline;
|
||||
fn String DString.tcopy_str(self) => self.copy_str(mem::temp()) @inline;
|
||||
|
||||
fn bool DString.equals(DString str, DString other_string)
|
||||
fn bool DString.equals(self, DString other_string)
|
||||
{
|
||||
StringData *str1 = str.data();
|
||||
StringData *str1 = self.data();
|
||||
StringData *str2 = other_string.data();
|
||||
if (str1 == str2) return true;
|
||||
if (!str1) return str2.len == 0;
|
||||
@@ -218,18 +199,18 @@ fn bool DString.equals(DString str, DString other_string)
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void DString.free(DString* str)
|
||||
fn void DString.free(&self)
|
||||
{
|
||||
if (!*str) return;
|
||||
StringData* data = str.data();
|
||||
if (!*self) return;
|
||||
StringData* data = self.data();
|
||||
if (!data) return;
|
||||
free(data, .using = data.allocator);
|
||||
*str = (DString)null;
|
||||
data.allocator.free(data);
|
||||
*self = (DString)null;
|
||||
}
|
||||
|
||||
fn bool DString.less(DString str, DString other_string)
|
||||
fn bool DString.less(self, DString other_string)
|
||||
{
|
||||
StringData* str1 = str.data();
|
||||
StringData* str1 = self.data();
|
||||
StringData* str2 = other_string.data();
|
||||
if (str1 == str2) return false;
|
||||
if (!str1) return str2.len != 0;
|
||||
@@ -244,93 +225,136 @@ fn bool DString.less(DString str, DString other_string)
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void DString.append_chars(DString* this, String str)
|
||||
fn void DString.append_chars(&self, String str)
|
||||
{
|
||||
usz other_len = str.len;
|
||||
if (!other_len) return;
|
||||
if (!*this)
|
||||
if (!*self)
|
||||
{
|
||||
*this = new(str);
|
||||
*self = new(str);
|
||||
return;
|
||||
}
|
||||
this.reserve(other_len);
|
||||
StringData* data = (StringData*)*this;
|
||||
self.reserve(other_len);
|
||||
StringData* data = self.data();
|
||||
mem::copy(&data.chars[data.len], str.ptr, other_len);
|
||||
data.len += other_len;
|
||||
}
|
||||
|
||||
fn Char32[] DString.copy_utf32(DString* this, Allocator* using = mem::heap())
|
||||
fn Char32[] DString.copy_utf32(&self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return this.str().to_utf32(using) @inline!!;
|
||||
return self.str_view().to_new_utf32(allocator) @inline!!;
|
||||
}
|
||||
|
||||
fn void DString.append_string(DString* this, DString str)
|
||||
fn void DString.append_string(&self, DString str)
|
||||
{
|
||||
StringData* other = (StringData*)str;
|
||||
StringData* other = str.data();
|
||||
if (!other) return;
|
||||
this.append(str.str());
|
||||
self.append(str.str_view());
|
||||
}
|
||||
|
||||
fn void DString.clear(DString* str)
|
||||
fn void DString.clear(self)
|
||||
{
|
||||
str.data().len = 0;
|
||||
if (!self) return;
|
||||
self.data().len = 0;
|
||||
}
|
||||
|
||||
fn void DString.append_char(DString* str, char c)
|
||||
fn usz! DString.write(&self, char[] buffer) @dynamic
|
||||
{
|
||||
if (!*str)
|
||||
self.append_chars((String)buffer);
|
||||
return buffer.len;
|
||||
}
|
||||
|
||||
fn void! DString.write_byte(&self, char c) @dynamic
|
||||
{
|
||||
self.append_char(c);
|
||||
}
|
||||
|
||||
fn void DString.append_char(&self, char c)
|
||||
{
|
||||
if (!*self)
|
||||
{
|
||||
*str = new_with_capacity(MIN_CAPACITY);
|
||||
*self = new_with_capacity(MIN_CAPACITY);
|
||||
}
|
||||
str.reserve(1);
|
||||
StringData* data = (StringData*)*str;
|
||||
self.reserve(1);
|
||||
StringData* data = self.data();
|
||||
data.chars[data.len++] = c;
|
||||
}
|
||||
|
||||
|
||||
macro void DString.append(DString* str, value)
|
||||
macro void DString.append(&self, value)
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
$switch ($Type)
|
||||
$case char:
|
||||
$case ichar:
|
||||
str.append_char(value);
|
||||
self.append_char(value);
|
||||
$case DString:
|
||||
str.append_string(value);
|
||||
self.append_string(value);
|
||||
$case String:
|
||||
str.append_chars(value);
|
||||
self.append_chars(value);
|
||||
$case Char32:
|
||||
str.append_char32(value);
|
||||
self.append_char32(value);
|
||||
$default:
|
||||
$switch
|
||||
$case @convertible(value, Char32):
|
||||
str.append_char32(value);
|
||||
$case @convertible(value, String):
|
||||
str.append_chars(value);
|
||||
$case $defined((Char32)value):
|
||||
self.append_char32((Char32)value);
|
||||
$case $defined((String)value):
|
||||
self.append_chars((String)value);
|
||||
$default:
|
||||
$error "Unsupported type for append – use printf instead.";
|
||||
$error "Unsupported type for append – use appendf instead.";
|
||||
$endswitch
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn void DString.insert_at(&self, usz index, String s)
|
||||
{
|
||||
if (s.len == 0) return;
|
||||
self.reserve(s.len);
|
||||
StringData* data = self.data();
|
||||
usz len = self.len();
|
||||
if (data.chars[:len].ptr == s.ptr)
|
||||
{
|
||||
// Source and destination are the same: nothing to do.
|
||||
return;
|
||||
}
|
||||
index = min(index, len);
|
||||
data.len += s.len;
|
||||
|
||||
fn usz! DString.printf(DString* str, String format, args...) @maydiscard
|
||||
char* start = data.chars[index:s.len].ptr; // area to insert into
|
||||
mem::move(start + s.len, start, len - index); // move existing data
|
||||
switch
|
||||
{
|
||||
case s.ptr <= start && start < s.ptr + s.len:
|
||||
// Overlapping areas.
|
||||
foreach_r (i, c : s)
|
||||
{
|
||||
data.chars[index + i] = c;
|
||||
}
|
||||
case start <= s.ptr && s.ptr < start + len:
|
||||
// Source has moved.
|
||||
mem::move(start, s.ptr + s.len, s.len);
|
||||
default:
|
||||
mem::move(start, s, s.len);
|
||||
}
|
||||
}
|
||||
|
||||
fn usz! DString.appendf(&self, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_string_append_fn, str);
|
||||
formatter.init(&out_string_append_fn, self);
|
||||
return formatter.vprintf(format, args);
|
||||
}
|
||||
|
||||
fn usz! DString.printfn(DString* str, String format, args...) @maydiscard
|
||||
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_string_append_fn, str);
|
||||
formatter.init(&out_string_append_fn, self);
|
||||
usz len = formatter.vprintf(format, args)!;
|
||||
str.append('\n');
|
||||
self.append('\n');
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
fn DString new_join(String[] s, String joiner, Allocator* using = mem::heap())
|
||||
fn DString new_join(String[] s, String joiner, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (!s.len) return (DString)null;
|
||||
usz total_size = joiner.len * s.len;
|
||||
@@ -338,7 +362,7 @@ fn DString new_join(String[] s, String joiner, Allocator* using = mem::heap())
|
||||
{
|
||||
total_size += str.len;
|
||||
}
|
||||
DString res = new_with_capacity(total_size, using);
|
||||
DString res = new_with_capacity(total_size, allocator);
|
||||
res.append(s[0]);
|
||||
foreach (String* &str : s[1..])
|
||||
{
|
||||
@@ -348,42 +372,44 @@ fn DString new_join(String[] s, String joiner, Allocator* using = mem::heap())
|
||||
return res;
|
||||
}
|
||||
|
||||
fn void! out_string_append_fn(char c, void* data) @private
|
||||
fn void! out_string_append_fn(void* data, char c) @private
|
||||
{
|
||||
DString* s = data;
|
||||
s.append_char(c);
|
||||
}
|
||||
|
||||
|
||||
fn StringData* DString.data(DString str) @inline @private
|
||||
fn StringData* DString.data(self) @inline @private
|
||||
{
|
||||
return (StringData*)str;
|
||||
return (StringData*)self;
|
||||
}
|
||||
|
||||
fn void DString.reserve(DString* str, usz addition)
|
||||
fn void DString.reserve(&self, usz addition)
|
||||
{
|
||||
StringData* data = str.data();
|
||||
StringData* data = self.data();
|
||||
if (!data)
|
||||
{
|
||||
*str = dstring::new_with_capacity(addition);
|
||||
*self = dstring::new_with_capacity(addition);
|
||||
return;
|
||||
}
|
||||
usz len = data.len + addition;
|
||||
if (data.capacity >= len) return;
|
||||
usz new_capacity = data.capacity *= 2;
|
||||
usz new_capacity = data.capacity * 2;
|
||||
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
|
||||
*str = (DString)realloc(data, StringData.sizeof + new_capacity, .using = data.allocator);
|
||||
while (new_capacity < len) new_capacity *= 2;
|
||||
data.capacity = new_capacity;
|
||||
*self = (DString)data.allocator.realloc(data, StringData.sizeof + new_capacity);
|
||||
}
|
||||
|
||||
fn usz! DString.read_from_stream(DString* string, Stream* reader)
|
||||
fn usz! DString.read_from_stream(&self, InStream* reader)
|
||||
{
|
||||
if (reader.supports_available())
|
||||
if (&reader.available)
|
||||
{
|
||||
usz total_read = 0;
|
||||
while (usz available = reader.available()!)
|
||||
{
|
||||
string.reserve(available);
|
||||
StringData* data = string.data();
|
||||
self.reserve(available);
|
||||
StringData* data = self.data();
|
||||
usz len = reader.read(data.chars[data.len..(data.capacity - 1)])!;
|
||||
total_read += len;
|
||||
data.len += len;
|
||||
@@ -394,8 +420,8 @@ fn usz! DString.read_from_stream(DString* string, Stream* reader)
|
||||
while (true)
|
||||
{
|
||||
// Reserve at least 16 bytes
|
||||
string.reserve(16);
|
||||
StringData* data = string.data();
|
||||
self.reserve(16);
|
||||
StringData* data = self.data();
|
||||
// Read into the rest of the buffer
|
||||
usz read = reader.read(data.chars[data.len..(data.capacity - 1)])!;
|
||||
data.len += read;
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
// 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::env;
|
||||
import libc;
|
||||
|
||||
enum CompilerOptLevel
|
||||
{
|
||||
O0,
|
||||
O1,
|
||||
O2,
|
||||
O3
|
||||
O0,
|
||||
O1,
|
||||
O2,
|
||||
O3
|
||||
}
|
||||
|
||||
enum MemoryEnvironment
|
||||
@@ -116,22 +116,32 @@ enum ArchType
|
||||
|
||||
const OsType OS_TYPE = (OsType)$$OS_TYPE;
|
||||
const ArchType ARCH_TYPE = (ArchType)$$ARCH_TYPE;
|
||||
const bool COMPILER_LIBC_AVAILABLE = $$COMPILER_LIBC_AVAILABLE;
|
||||
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
|
||||
const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
|
||||
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;
|
||||
const bool F128_SUPPORT = $$PLATFORM_F128_SUPPORTED;
|
||||
const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE;
|
||||
const bool DEBUG_SYMBOLS = $$DEBUG_SYMBOLS;
|
||||
const usz LLVM_VERSION = $$LLVM_VERSION;
|
||||
const bool BENCHMARKING = $$BENCHMARKING;
|
||||
const bool TESTING = $$TESTING;
|
||||
const MemoryEnvironment MEMORY_ENV = (MemoryEnvironment)$$MEMORY_ENVIRONMENT;
|
||||
|
||||
macro bool os_is_win32()
|
||||
{
|
||||
return OS_TYPE == WIN32;
|
||||
}
|
||||
const bool TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING);
|
||||
const bool X86_64 = ARCH_TYPE == X86_64;
|
||||
const bool AARCH64 = ARCH_TYPE == AARCH64;
|
||||
const bool NATIVE_STACKTRACE = LINUX || DARWIN || WIN32;
|
||||
const bool LINUX = LIBC && OS_TYPE == LINUX;
|
||||
const bool DARWIN = LIBC && os_is_darwin();
|
||||
const bool WIN32 = LIBC && OS_TYPE == WIN32;
|
||||
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 WASI = LIBC && OS_TYPE == WASI;
|
||||
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
|
||||
macro bool os_is_darwin()
|
||||
{
|
||||
@@ -170,57 +180,5 @@ macro bool os_is_posix()
|
||||
$endswitch
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param [&in] name
|
||||
* @require name.len > 0
|
||||
**/
|
||||
fn String! get_var(String name)
|
||||
{
|
||||
$if COMPILER_LIBC_AVAILABLE && !os_is_win32():
|
||||
@pool()
|
||||
{
|
||||
ZString val = libc::getenv(name.zstr_tcopy());
|
||||
return val ? val.as_str() : SearchResult.MISSING?;
|
||||
};
|
||||
$else
|
||||
return "";
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param [&in] name
|
||||
* @param [&in] value
|
||||
* @require name.len > 0
|
||||
**/
|
||||
fn void set_var(String name, String value, bool overwrite = true)
|
||||
{
|
||||
$if COMPILER_LIBC_AVAILABLE && !os_is_win32():
|
||||
@pool()
|
||||
{
|
||||
if (libc::setenv(name.zstr_tcopy(), value.zstr_copy(), (int)overwrite))
|
||||
{
|
||||
unreachable();
|
||||
}
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] name
|
||||
* @require name.len > 0
|
||||
**/
|
||||
fn void clear_var(String name)
|
||||
{
|
||||
$if COMPILER_LIBC_AVAILABLE && !os_is_win32():
|
||||
@pool()
|
||||
{
|
||||
if (libc::unsetenv(name.zstr_tcopy()))
|
||||
{
|
||||
unreachable();
|
||||
}
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
|
||||
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);
|
||||
|
||||
@@ -7,21 +7,172 @@ const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
|
||||
const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
|
||||
|
||||
|
||||
macro @volatile_load(&x) @builtin
|
||||
/**
|
||||
* Load a vector from memory according to a mask assuming default alignment.
|
||||
*
|
||||
* @param ptr "The pointer address to load from."
|
||||
* @param mask "The mask for the load"
|
||||
* @param passthru "The value to use for non masked values"
|
||||
* @require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
|
||||
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
*
|
||||
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
**/
|
||||
macro masked_load(ptr, bool[<*>] mask, passthru)
|
||||
{
|
||||
return $$volatile_load(&x);
|
||||
return $$masked_load(ptr, mask, passthru, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a vector from memory according to a mask.
|
||||
*
|
||||
* @param ptr "The pointer address to load from."
|
||||
* @param mask "The mask for the load"
|
||||
* @param passthru "The value to use for non masked values"
|
||||
* @param $alignment "The alignment to assume for the pointer"
|
||||
*
|
||||
* @require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
|
||||
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
*
|
||||
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
**/
|
||||
macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
|
||||
{
|
||||
return $$masked_load(ptr, mask, passthru, $alignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load values from a pointer vector, assuming default alignment.
|
||||
*
|
||||
* @param ptrvec "The vector of pointers to load from."
|
||||
* @param mask "The mask for the load"
|
||||
* @param passthru "The value to use for non masked values"
|
||||
*
|
||||
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
* @require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
|
||||
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
*
|
||||
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
**/
|
||||
macro gather(ptrvec, bool[<*>] mask, passthru)
|
||||
{
|
||||
return $$gather(ptrvec, mask, passthru, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load values from a pointer vector.
|
||||
*
|
||||
* @param ptrvec "The vector of pointers to load from."
|
||||
* @param mask "The mask for the load"
|
||||
* @param passthru "The value to use for non masked values"
|
||||
* @param $alignment "The alignment to assume for the pointers"
|
||||
*
|
||||
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
* @require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
|
||||
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
*
|
||||
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
|
||||
**/
|
||||
macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
|
||||
{
|
||||
return $$gather(ptrvec, mask, passthru, $alignment);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store parts of a vector according to the mask, assuming default alignment.
|
||||
*
|
||||
* @param ptr "The pointer address to store to."
|
||||
* @param value "The value to store masked"
|
||||
* @param mask "The mask for the store"
|
||||
*
|
||||
* @require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
|
||||
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
* @require value.len == mask.len : "Mask and value must have the same length"
|
||||
**/
|
||||
macro masked_store(ptr, value, bool[<*>] mask)
|
||||
{
|
||||
return $$masked_store(ptr, value, mask, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ptr "The pointer address to store to."
|
||||
* @param value "The value to store masked"
|
||||
* @param mask "The mask for the store"
|
||||
* @param $alignment "The alignment of the pointer"
|
||||
*
|
||||
* @require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
|
||||
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
* @require value.len == mask.len : "Mask and value must have the same length"
|
||||
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
*
|
||||
**/
|
||||
macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
|
||||
{
|
||||
return $$masked_store(ptr, value, mask, $alignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ptrvec "The vector pointer containing the addresses to store to."
|
||||
* @param value "The value to store masked"
|
||||
* @param mask "The mask for the store"
|
||||
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
* @require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
|
||||
* @require value.len == mask.len : "Mask and value must have the same length"
|
||||
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
*
|
||||
**/
|
||||
macro scatter(ptrvec, value, bool[<*>] mask)
|
||||
{
|
||||
return $$scatter(ptrvec, value, mask, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ptrvec "The vector pointer containing the addresses to store to."
|
||||
* @param value "The value to store masked"
|
||||
* @param mask "The mask for the store"
|
||||
* @param $alignment "The alignment of the load"
|
||||
*
|
||||
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
* @require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
|
||||
* @require value.len == mask.len : "Mask and value must have the same length"
|
||||
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
|
||||
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
**/
|
||||
macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
|
||||
{
|
||||
return $$scatter(ptrvec, value, mask, $alignment);
|
||||
}
|
||||
|
||||
macro @volatile_load(&x) @builtin
|
||||
{
|
||||
return $$volatile_load(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require $assignable(y, $typeof(*x)) : "The value doesn't match the variable"
|
||||
**/
|
||||
macro @volatile_store(&x, y) @builtin
|
||||
{
|
||||
return $$volatile_store(&x, ($typeof(x))y);
|
||||
return $$volatile_store(x, ($typeof(*x))y);
|
||||
}
|
||||
|
||||
enum AtomicOrdering : int
|
||||
{
|
||||
NOT_ATOMIC, // Not atomic
|
||||
UNORDERED, // No lock
|
||||
MONOTONIC, // Consistent ordering
|
||||
RELAXED, // Consistent ordering
|
||||
ACQUIRE, // Barrier locking load/store
|
||||
RELEASE, // Barrier releasing load/store
|
||||
ACQUIRE_RELEASE, // Barrier fence to load/store
|
||||
@@ -37,10 +188,11 @@ enum AtomicOrdering : int
|
||||
* @require $ordering != AtomicOrdering.RELEASE "Release ordering is not valid for load."
|
||||
* @require $ordering != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid for load."
|
||||
* @require types::may_load_atomic($typeof(x)) "Only integer, float and pointers may be used."
|
||||
* @require @typekind(x) == POINTER "You can only load from a pointer"
|
||||
**/
|
||||
macro @atomic_load(&x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
|
||||
{
|
||||
return $$atomic_load(&x, $volatile, (int)$ordering);
|
||||
return $$atomic_load(x, $volatile, (int)$ordering);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,14 +207,22 @@ macro @atomic_load(&x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = fa
|
||||
**/
|
||||
macro void @atomic_store(&x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
|
||||
{
|
||||
$$atomic_store(&x, value, $volatile, (int)$ordering);
|
||||
$$atomic_store(x, value, $volatile, (int)$ordering);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
* @require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
|
||||
**/
|
||||
macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT, bool $volatile = true, bool $weak = false, usz $alignment = 0)
|
||||
{
|
||||
return $$compare_exchange(ptr, compare, value, $volatile, $weak, $success.ordinal, $failure.ordinal, $alignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
|
||||
* @require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
|
||||
**/
|
||||
macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT)
|
||||
{
|
||||
return compare_exchange(ptr, compare, value, $success, $failure, true);
|
||||
@@ -98,6 +258,19 @@ macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = fal
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy memory from src to dst efficiently, assuming the memory ranges do not overlap.
|
||||
*
|
||||
* @param [&out] dst "The destination to copy to"
|
||||
* @param [&in] src "The source to copy from"
|
||||
* @param len "The number of bytes to copy"
|
||||
* @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
* @param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
* @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
|
||||
* @param $inlined "True if this copy should never call the OS memcpy."
|
||||
*
|
||||
* @require len == 0 || dst + len <= src || src + len <= dst : "Ranges may not overlap"
|
||||
**/
|
||||
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false, bool $inlined = false)
|
||||
{
|
||||
$if $inlined:
|
||||
@@ -107,11 +280,33 @@ macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy memory from src to dst but correctly handle the possibility of overlapping ranges.
|
||||
*
|
||||
* @param [&out] dst "The destination to copy to"
|
||||
* @param [&in] src "The source to copy from"
|
||||
* @param len "The number of bytes to copy"
|
||||
* @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
* @param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
* @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
|
||||
**/
|
||||
macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
|
||||
{
|
||||
$$memmove(dst, src, len, $is_volatile, $dst_align, $src_align);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all memory in a region to that of the provided byte.
|
||||
*
|
||||
* @param [&out] dst "The destination to copy to"
|
||||
* @param val "The value to copy into memory"
|
||||
* @param len "The number of bytes to copy"
|
||||
* @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
|
||||
* @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
|
||||
* @param $inlined "True if this copy should never call the OS memset."
|
||||
*
|
||||
* @ensure !len || (dst[0] == val && dst[len - 1] == val)
|
||||
**/
|
||||
macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false, bool $inlined = false)
|
||||
{
|
||||
$if $inlined:
|
||||
@@ -126,7 +321,7 @@ macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volati
|
||||
* @require values::@inner_kind(b) == TypeKind.SUBARRAY || values::@inner_kind(b) == TypeKind.POINTER
|
||||
* @require values::@inner_kind(a) != TypeKind.SUBARRAY || len == -1
|
||||
* @require values::@inner_kind(a) != TypeKind.POINTER || len > -1
|
||||
* @checked (a = b), (b = a)
|
||||
* @require values::@assign_to(a, b) && values::@assign_to(b, a)
|
||||
**/
|
||||
macro bool equals(a, b, isz len = -1, usz $align = 0)
|
||||
{
|
||||
@@ -148,7 +343,7 @@ macro bool equals(a, b, isz len = -1, usz $align = 0)
|
||||
|
||||
if (!len) return true;
|
||||
var $Type;
|
||||
$switch ($align)
|
||||
$switch ($align)
|
||||
$case 1:
|
||||
$Type = char;
|
||||
$case 2:
|
||||
@@ -173,237 +368,122 @@ macro bool equals(a, b, isz len = -1, usz $align = 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
macro @clone(&value) @builtin
|
||||
{
|
||||
$typeof(value)* x = malloc($typeof(value));
|
||||
*x = value;
|
||||
return x;
|
||||
}
|
||||
|
||||
macro @tclone(&value) @builtin
|
||||
{
|
||||
$typeof(value)* x = talloc($typeof(value));
|
||||
*x = value;
|
||||
return x;
|
||||
}
|
||||
|
||||
macro type_alloc_must_be_aligned($Type)
|
||||
{
|
||||
return $Type.alignof > DEFAULT_MEM_ALIGNMENT;
|
||||
}
|
||||
/**
|
||||
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
|
||||
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
|
||||
**/
|
||||
macro malloc(..., Allocator* using = mem::heap(), usz end_padding = 0) @builtin
|
||||
{
|
||||
return malloc_checked($vasplat(), .using = using, .end_padding = end_padding)!!;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
|
||||
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
|
||||
**/
|
||||
macro malloc_checked(..., Allocator* using = mem::heap(), usz end_padding = 0) @builtin
|
||||
{
|
||||
$if $checks($vatype(0).sizeof):
|
||||
var $Type = $vatype(0);
|
||||
$assert !type_alloc_must_be_aligned($vatype(0)) : "Type must be allocated with malloc_aligned";
|
||||
$if $vacount == 2:
|
||||
usz size = $vaarg(1);
|
||||
return (($Type*)using.alloc($Type.sizeof * size + end_padding))[:size];
|
||||
$else
|
||||
return ($Type*)using.alloc($Type.sizeof + end_padding);
|
||||
$endif
|
||||
$else
|
||||
return using.alloc($vaarg(0) + end_padding);
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
|
||||
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
|
||||
* @require alignment && math::is_power_of_2(alignment)
|
||||
**/
|
||||
macro malloc_aligned(..., usz alignment = 0, usz end_padding = 0, Allocator* using = mem::heap()) @builtin
|
||||
{
|
||||
$if $checks($vatype(0).sizeof):
|
||||
var $Type = $vatype(0);
|
||||
$if $vacount == 2:
|
||||
usz size = $vaarg(1);
|
||||
return (($Type*)using.alloc_aligned($Type.sizeof * size + end_padding, alignment))[:size];
|
||||
$else
|
||||
return ($Type*)using.alloc_aligned($Type.sizeof + end_padding, alignment);
|
||||
$endif
|
||||
$else
|
||||
return using.alloc_aligned($vaarg(0) + end_padding, alignment);
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
|
||||
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
|
||||
**/
|
||||
macro calloc(..., Allocator* using = mem::heap(), usz end_padding = 0) @builtin
|
||||
{
|
||||
return calloc_checked($vasplat(), .using = using, .end_padding = end_padding)!!;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
|
||||
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
|
||||
**/
|
||||
macro calloc_checked(..., Allocator* using = mem::heap(), usz end_padding = 0) @builtin
|
||||
{
|
||||
$if $checks($vatype(0).sizeof):
|
||||
var $Type = $vatype(0);
|
||||
$assert !type_alloc_must_be_aligned($vatype(0)) : "Type must be allocated with calloc_aligned";
|
||||
$if $vacount == 2:
|
||||
usz size = $vaarg(1);
|
||||
return (($Type*)using.calloc($Type.sizeof * size + end_padding))[:size];
|
||||
$else
|
||||
return ($Type*)using.calloc($Type.sizeof + end_padding);
|
||||
$endif
|
||||
$else
|
||||
return using.calloc($vaarg(0) + end_padding);
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
|
||||
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
|
||||
* @require alignment && math::is_power_of_2(alignment)
|
||||
**/
|
||||
macro calloc_aligned(..., usz alignment = 0, Allocator* using = mem::heap(), usz end_padding = 0) @builtin
|
||||
{
|
||||
$if $checks($vatype(0).sizeof):
|
||||
var $Type = $vatype(0);
|
||||
$if $vacount == 2:
|
||||
usz size = $vaarg(1);
|
||||
return (($Type*)using.calloc_aligned($Type.sizeof * size + end_padding, alignment))[:size];
|
||||
$else
|
||||
return ($Type*)using.calloc_aligned($Type.sizeof + end_padding, alignment);
|
||||
$endif
|
||||
$else
|
||||
return using.calloc_aligned($vaarg(0) + end_padding, alignment);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void* realloc(void *ptr, usz new_size, Allocator* using = mem::heap()) @builtin @inline
|
||||
{
|
||||
return using.realloc(ptr, new_size)!!;
|
||||
}
|
||||
|
||||
fn void*! realloc_checked(void *ptr, usz new_size, Allocator* using = mem::heap()) @builtin @inline
|
||||
{
|
||||
return using.realloc(ptr, new_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require alignment && math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! realloc_aligned(void *ptr, usz new_size, usz alignment, Allocator* using = mem::heap()) @builtin @inline
|
||||
{
|
||||
return using.realloc_aligned(ptr, new_size, alignment);
|
||||
}
|
||||
|
||||
macro void free(void* ptr, Allocator* using = mem::heap()) @builtin => using.free(ptr)!!;
|
||||
macro void! free_checked(void* ptr, Allocator* using = mem::heap()) @builtin => using.free(ptr);
|
||||
macro void free_aligned(void* ptr, Allocator* using = mem::heap()) @builtin => using.free_aligned(ptr)!!;
|
||||
macro void! free_aligned_checked(void* ptr, Allocator* using = mem::heap()) @builtin => using.free_aligned(ptr);
|
||||
|
||||
/**
|
||||
* Run with a specific allocator inside of the macro body.
|
||||
**/
|
||||
macro void @scoped(Allocator* using; @body())
|
||||
macro void @scoped(Allocator* allocator; @body())
|
||||
{
|
||||
Allocator* old_allocator = thread_allocator;
|
||||
thread_allocator = using;
|
||||
thread_allocator = allocator;
|
||||
defer thread_allocator = old_allocator;
|
||||
@body();
|
||||
}
|
||||
|
||||
/**
|
||||
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
|
||||
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
|
||||
**/
|
||||
macro tmalloc(..., usz end_padding = 0, usz alignment = DEFAULT_MEM_ALIGNMENT) @builtin
|
||||
macro void @report_heap_allocs_in_scope(;@body())
|
||||
{
|
||||
$if $checks($vatype(0).sizeof):
|
||||
var $Type = $vatype(0);
|
||||
$if $vacount == 2:
|
||||
usz size = $vaarg(1);
|
||||
return (($Type*)temp().alloc_aligned($Type.sizeof * size + end_padding, alignment))[:size]!!;
|
||||
$else
|
||||
return ($Type*)temp().alloc_aligned($Type.sizeof + end_padding, alignment)!!;
|
||||
$endif
|
||||
$else
|
||||
return temp().alloc_aligned($vaarg(0) + end_padding, alignment)!!;
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
|
||||
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
|
||||
**/
|
||||
macro tcalloc(..., usz end_padding = 0, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin
|
||||
{
|
||||
$if $checks($vatype(0).sizeof):
|
||||
var $Type = $vatype(0);
|
||||
$if $vacount == 2:
|
||||
usz size = $vaarg(1);
|
||||
return (($Type*)temp().calloc_aligned($Type.sizeof * size + end_padding, alignment))[:size]!!;
|
||||
$else
|
||||
return ($Type*)temp().calloc_aligned($Type.sizeof + end_padding, alignment)!!;
|
||||
$endif
|
||||
$else
|
||||
return temp().calloc_aligned($vaarg(0) + end_padding, alignment)!!;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline
|
||||
{
|
||||
return temp().realloc_aligned(ptr, size, alignment)!!;
|
||||
}
|
||||
|
||||
macro void @pool(;@body) @builtin
|
||||
{
|
||||
TempAllocator* allocator = temp();
|
||||
usz mark = allocator.used;
|
||||
defer allocator.reset(mark);
|
||||
TrackingAllocator tracker;
|
||||
tracker.init(thread_allocator);
|
||||
Allocator* old_allocator = thread_allocator;
|
||||
thread_allocator = &tracker;
|
||||
defer
|
||||
{
|
||||
thread_allocator = old_allocator;
|
||||
tracker.print_report();
|
||||
tracker.free();
|
||||
}
|
||||
@body();
|
||||
}
|
||||
|
||||
macro void @allocating_pool(Allocator* using; @body(bool is_temp)) @builtin
|
||||
macro void @stack_mem(usz $size; @body(Allocator* mem)) @builtin
|
||||
{
|
||||
TempAllocator* allocator = temp();
|
||||
usz mark = allocator.used;
|
||||
bool is_temp = allocator == using;
|
||||
defer if (!is_temp) allocator.reset(mark);
|
||||
@body(is_temp);
|
||||
char[$size] buffer;
|
||||
OnStackAllocator allocator;
|
||||
allocator.init(&buffer, mem::heap());
|
||||
defer allocator.free();
|
||||
@body(&allocator);
|
||||
}
|
||||
|
||||
tlocal Allocator* thread_allocator @private = allocator::LIBC_ALLOCATOR;
|
||||
tlocal TempAllocator* thread_temp_allocator @private = null;
|
||||
macro void @stack_pool(usz $size; @body) @builtin
|
||||
{
|
||||
char[$size] buffer;
|
||||
OnStackAllocator allocator;
|
||||
allocator.init(&buffer, mem::heap());
|
||||
defer allocator.free();
|
||||
mem::@scoped(&allocator)
|
||||
{
|
||||
@body();
|
||||
};
|
||||
}
|
||||
|
||||
macro TempAllocator* temp_allocator() => temp();
|
||||
macro void @pool(TempAllocator* #other_temp = null; @body) @builtin
|
||||
{
|
||||
TempAllocator* current = temp();
|
||||
var $has_arg = !$is_const(#other_temp);
|
||||
$if $has_arg:
|
||||
TempAllocator* original = current;
|
||||
if (current == (void*)#other_temp) current = temp_allocator_next();
|
||||
$endif
|
||||
usz mark = current.used;
|
||||
defer
|
||||
{
|
||||
current.reset(mark);
|
||||
$if $has_arg:
|
||||
thread_temp_allocator = original;
|
||||
$endif;
|
||||
}
|
||||
@body();
|
||||
}
|
||||
|
||||
tlocal Allocator* thread_allocator @private = &allocator::LIBC_ALLOCATOR;
|
||||
tlocal TempAllocator* thread_temp_allocator @private = null;
|
||||
tlocal TempAllocator*[2] temp_allocator_pair @private;
|
||||
Allocator* temp_base_allocator @private = &allocator::LIBC_ALLOCATOR;
|
||||
|
||||
macro TempAllocator* create_default_sized_temp_allocator(Allocator* allocator) @local
|
||||
{
|
||||
$switch (env::MEMORY_ENV)
|
||||
$case NORMAL:
|
||||
return allocator::new_temp(1024 * 256, allocator)!!;
|
||||
$case SMALL:
|
||||
return allocator::new_temp(1024 * 16, allocator)!!;
|
||||
$case TINY:
|
||||
return allocator::new_temp(1024 * 2, allocator)!!;
|
||||
$case NONE:
|
||||
unreachable("Temp allocator must explicitly created when memory-env is set to 'none'.");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn TempAllocator *temp_allocator_next() @private
|
||||
{
|
||||
if (!thread_temp_allocator)
|
||||
{
|
||||
init_default_temp_allocators();
|
||||
return thread_temp_allocator;
|
||||
}
|
||||
usz index = thread_temp_allocator == temp_allocator_pair[0] ? 1 : 0;
|
||||
return thread_temp_allocator = temp_allocator_pair[index];
|
||||
}
|
||||
|
||||
import libc;
|
||||
|
||||
fn void init_default_temp_allocators() @private
|
||||
{
|
||||
temp_allocator_pair[0] = create_default_sized_temp_allocator(temp_base_allocator);
|
||||
temp_allocator_pair[1] = create_default_sized_temp_allocator(temp_base_allocator);
|
||||
thread_temp_allocator = temp_allocator_pair[0];
|
||||
}
|
||||
|
||||
macro TempAllocator* temp()
|
||||
{
|
||||
if (!thread_temp_allocator)
|
||||
{
|
||||
$switch (env::MEMORY_ENV)
|
||||
$case NORMAL:
|
||||
thread_temp_allocator = allocator::new_temp(1024 * 256, thread_allocator)!!;
|
||||
$case SMALL:
|
||||
thread_temp_allocator = allocator::new_temp(1024 * 16, thread_allocator)!!;
|
||||
$case TINY:
|
||||
thread_temp_allocator = allocator::new_temp(1024 * 2, thread_allocator)!!;
|
||||
$case NONE:
|
||||
unreachable("Temp allocator must explicitly created when memory-env is set to 'none'.");
|
||||
$endswitch
|
||||
init_default_temp_allocators();
|
||||
}
|
||||
return thread_temp_allocator;
|
||||
}
|
||||
@@ -411,21 +491,117 @@ macro TempAllocator* temp()
|
||||
macro Allocator* current_allocator() => thread_allocator;
|
||||
macro Allocator* heap() => thread_allocator;
|
||||
|
||||
$if !env::COMPILER_LIBC_AVAILABLE && env::ARCH_TYPE == ArchType.WASM32 || env::ARCH_TYPE == ArchType.WASM64:
|
||||
|
||||
module std::core::mem @if(WASM_NOLIBC);
|
||||
|
||||
SimpleHeapAllocator wasm_allocator @private;
|
||||
|
||||
extern int __heap_base;
|
||||
|
||||
static initialize @priority(1)
|
||||
fn void initialize_wasm_mem() @init(1) @private
|
||||
{
|
||||
allocator::wasm_memory.allocate_block(mem::DEFAULT_MEM_ALIGNMENT)!!; // Give us a valid null.
|
||||
// Check if we need to move the heap.
|
||||
uptr start = (uptr)&__heap_base;
|
||||
if (start > mem::DEFAULT_MEM_ALIGNMENT) allocator::wasm_memory.use = start;
|
||||
wasm_allocator.init(fn (x) => allocator::wasm_memory.allocate_block(x));
|
||||
temp_base_allocator = &wasm_allocator;
|
||||
thread_allocator = &wasm_allocator;
|
||||
}
|
||||
$endif
|
||||
|
||||
module std::core::mem;
|
||||
|
||||
|
||||
macro TrackingEnv* get_tracking_env()
|
||||
{
|
||||
$if env::TRACK_MEMORY:
|
||||
return &&TrackingEnv { $$FILE, $$FUNC, $$LINE };
|
||||
$else
|
||||
return null;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro @clone(value) @builtin
|
||||
{
|
||||
return mem::heap().clone(value);
|
||||
}
|
||||
|
||||
macro @tclone(value) @builtin
|
||||
{
|
||||
return mem::temp().clone(value);
|
||||
}
|
||||
|
||||
fn void* malloc(usz size) @builtin @inline
|
||||
{
|
||||
return mem::heap().alloc(size);
|
||||
}
|
||||
|
||||
fn void* tmalloc(usz size, usz alignment = 0, usz offset = 0) @builtin @inline
|
||||
{
|
||||
return temp().acquire(size, false, alignment, offset)!!;
|
||||
}
|
||||
|
||||
macro new($Type)
|
||||
{
|
||||
return heap().new($Type);
|
||||
}
|
||||
|
||||
macro new_clear($Type)
|
||||
{
|
||||
return heap().new_clear($Type);
|
||||
}
|
||||
|
||||
macro new_temp($Type)
|
||||
{
|
||||
return tmalloc($Type.sizeof);
|
||||
}
|
||||
|
||||
macro new_temp_clear($Type)
|
||||
{
|
||||
return tcalloc($Type.sizeof);
|
||||
}
|
||||
|
||||
macro new_array($Type, usz elements)
|
||||
{
|
||||
return heap().new_array($Type, elements);
|
||||
}
|
||||
|
||||
macro temp_array($Type, usz elements)
|
||||
{
|
||||
return (($Type*)tmalloc($Type.sizeof * elements, $Type.alignof))[:elements];
|
||||
}
|
||||
|
||||
macro new_zero_array($Type, usz elements)
|
||||
{
|
||||
return heap().new_zero_array($Type, elements);
|
||||
}
|
||||
|
||||
macro temp_zero_array($Type, usz elements)
|
||||
{
|
||||
return (($Type*)tcalloc($Type.sizeof * elements, $Type.alignof))[:elements];
|
||||
}
|
||||
|
||||
fn void* calloc(usz size) @builtin @inline
|
||||
{
|
||||
return heap().calloc(size);
|
||||
}
|
||||
|
||||
fn void* tcalloc(usz size, usz alignment = 0, usz offset = 0) @builtin @inline
|
||||
{
|
||||
return temp().acquire(size, false, alignment, offset)!!;
|
||||
}
|
||||
|
||||
fn void* realloc(void *ptr, usz new_size) @builtin @inline
|
||||
{
|
||||
return heap().realloc(ptr, new_size);
|
||||
}
|
||||
|
||||
fn void free(void* ptr) @builtin @inline
|
||||
{
|
||||
heap().free(ptr);
|
||||
}
|
||||
|
||||
fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline
|
||||
{
|
||||
return temp().resize(ptr, size, alignment, 0)!!;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,107 +3,222 @@ module std::core::mem::allocator;
|
||||
const DEFAULT_SIZE_PREFIX = usz.sizeof;
|
||||
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
|
||||
|
||||
const Allocator* NULL_ALLOCATOR = &_NULL_ALLOCATOR;
|
||||
const Allocator* LIBC_ALLOCATOR = &_SYSTEM_ALLOCATOR;
|
||||
|
||||
def AllocatorFunction = fn void*!(Allocator* allocator, usz new_size, usz alignment, usz offset, void* old_pointer, AllocationKind kind);
|
||||
|
||||
struct Allocator
|
||||
struct TrackingEnv
|
||||
{
|
||||
AllocatorFunction function;
|
||||
String file;
|
||||
String function;
|
||||
uint line;
|
||||
}
|
||||
|
||||
enum AllocationKind
|
||||
|
||||
interface Allocator
|
||||
{
|
||||
ALLOC,
|
||||
CALLOC,
|
||||
REALLOC,
|
||||
FREE,
|
||||
ALIGNED_ALLOC,
|
||||
ALIGNED_CALLOC,
|
||||
ALIGNED_REALLOC,
|
||||
ALIGNED_FREE,
|
||||
RESET,
|
||||
MARK,
|
||||
fn void reset(usz mark) @optional;
|
||||
fn usz mark() @optional;
|
||||
fn void*! acquire(usz size, bool clear, usz alignment, usz offset);
|
||||
fn void*! resize(void* ptr, usz new_size, usz alignment, usz offset);
|
||||
fn void release(void* ptr, bool aligned);
|
||||
}
|
||||
|
||||
struct AlignedBlock
|
||||
{
|
||||
usz len;
|
||||
void* start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
|
||||
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
|
||||
void* data = #alloc_fn(header + bytes)!;
|
||||
$else
|
||||
void* data = #alloc_fn(header + bytes);
|
||||
$endif
|
||||
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
|
||||
assert(mem > data);
|
||||
AlignedBlock* desc = (AlignedBlock*)mem - 1;
|
||||
*desc = { bytes, data };
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_calloc(#calloc_fn, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
|
||||
$if @typekind(#calloc_fn(bytes)) == OPTIONAL:
|
||||
void* data = #calloc_fn(header + bytes)!;
|
||||
$else
|
||||
void* data = #calloc_fn(header + bytes);
|
||||
$endif
|
||||
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
|
||||
AlignedBlock* desc = (AlignedBlock*)mem - 1;
|
||||
assert(mem > data);
|
||||
*desc = { bytes, data };
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require bytes > 0
|
||||
* @require alignment > 0
|
||||
**/
|
||||
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment, usz offset)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
void* data_start = desc.start;
|
||||
void* new_data = @aligned_calloc(#calloc_fn, bytes, alignment, offset)!;
|
||||
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
$if @typekind(#free_fn(data_start)) == OPTIONAL:
|
||||
#free_fn(data_start)!;
|
||||
$else
|
||||
#free_fn(data_start);
|
||||
$endif
|
||||
return new_data;
|
||||
}
|
||||
|
||||
macro void! @aligned_free(#free_fn, void* old_pointer)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
|
||||
#free_fn(desc.start)!;
|
||||
$else
|
||||
#free_fn(desc.start);
|
||||
$endif
|
||||
}
|
||||
|
||||
def MemoryAllocFn = fn char[]!(usz);
|
||||
|
||||
fault AllocationFailure
|
||||
{
|
||||
OUT_OF_MEMORY,
|
||||
UNSUPPORTED_OPERATION,
|
||||
CHUNK_TOO_LARGE,
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn void*! Allocator.alloc(Allocator* allocator, usz size) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, 0, 0, null, ALLOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require alignment && math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! Allocator.alloc_aligned(Allocator* allocator, usz size, usz alignment, usz offset = 0) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, alignment, offset, null, ALIGNED_ALLOC);
|
||||
}
|
||||
|
||||
fn void*! Allocator.realloc(Allocator* allocator, void* old_pointer, usz size) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, 0, 0, old_pointer, REALLOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require alignment && math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! Allocator.realloc_aligned(Allocator* allocator, void* old_pointer, usz size, usz alignment, usz offset = 0) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, alignment, offset, old_pointer, ALIGNED_REALLOC);
|
||||
}
|
||||
|
||||
fn usz! Allocator.mark(Allocator* allocator) @inline
|
||||
{
|
||||
return (usz)(uptr)allocator.function(allocator, 0, 0, 0, null, MARK);
|
||||
}
|
||||
|
||||
|
||||
fn void*! Allocator.calloc(Allocator* allocator, usz size) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, 0, 0, null, CALLOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require alignment && math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! Allocator.calloc_aligned(Allocator* allocator, usz size, usz alignment, usz offset = 0) @inline
|
||||
{
|
||||
return allocator.function(allocator, size, alignment, offset, null, ALIGNED_CALLOC);
|
||||
}
|
||||
|
||||
fn void! Allocator.free(Allocator* allocator, void* old_pointer) @inline
|
||||
{
|
||||
allocator.function(allocator, 0, 0, 0, old_pointer, FREE)!;
|
||||
}
|
||||
|
||||
fn void! Allocator.free_aligned(Allocator* allocator, void* old_pointer) @inline
|
||||
{
|
||||
allocator.function(allocator, 0, 0, 0, old_pointer, ALIGNED_FREE)!;
|
||||
}
|
||||
|
||||
fn void Allocator.reset(Allocator* allocator, usz mark = 0)
|
||||
{
|
||||
allocator.function(allocator, mark, 0, 0, null, RESET)!!;
|
||||
OUT_OF_MEMORY,
|
||||
CHUNK_TOO_LARGE,
|
||||
}
|
||||
|
||||
fn usz alignment_for_allocation(usz alignment) @inline @private
|
||||
{
|
||||
if (alignment < mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
alignment = mem::DEFAULT_MEM_ALIGNMENT;
|
||||
}
|
||||
return alignment;
|
||||
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? alignment = mem::DEFAULT_MEM_ALIGNMENT : alignment;
|
||||
}
|
||||
|
||||
// Allocator "functions"
|
||||
|
||||
macro void*! Allocator.alloc_checked(&self, usz size)
|
||||
{
|
||||
$if env::TESTING:
|
||||
char* data = self.acquire(size, false, 0, 0)!;
|
||||
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
$else
|
||||
return self.acquire(size, false, 0, 0);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro void*! Allocator.calloc_checked(&self, usz size)
|
||||
{
|
||||
return self.acquire(size, true, 0, 0);
|
||||
}
|
||||
macro void*! Allocator.realloc_checked(&self, void* ptr, usz new_size)
|
||||
{
|
||||
return self.resize(ptr, new_size, 0, 0);
|
||||
}
|
||||
|
||||
macro Allocator.new_array(&self, $Type, usz size, usz end_padding = 0)
|
||||
{
|
||||
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size]!!;
|
||||
}
|
||||
|
||||
macro Allocator.new_array_checked(&self, $Type, usz size, usz end_padding = 0)
|
||||
{
|
||||
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size];
|
||||
}
|
||||
|
||||
macro Allocator.new_zero_array(&self, $Type, usz size, usz end_padding = 0)
|
||||
{
|
||||
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size]!!;
|
||||
}
|
||||
|
||||
macro Allocator.new_zero_array_checked(&self, $Type, usz size, usz end_padding = 0)
|
||||
{
|
||||
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size];
|
||||
}
|
||||
|
||||
macro Allocator.new(&self, $Type, usz end_padding = 0) @nodiscard
|
||||
{
|
||||
return ($Type*)self.alloc_checked($Type.sizeof + end_padding)!!;
|
||||
}
|
||||
|
||||
macro Allocator.new_checked(&self, $Type, usz end_padding = 0) @nodiscard
|
||||
{
|
||||
return ($Type*)self.alloc_checked($Type.sizeof + end_padding);
|
||||
}
|
||||
|
||||
macro Allocator.new_clear(&self, $Type, usz end_padding = 0) @nodiscard
|
||||
{
|
||||
return ($Type*)self.calloc_checked($Type.sizeof + end_padding)!!;
|
||||
}
|
||||
|
||||
macro Allocator.new_clear_checked(&self, $Type, usz end_padding = 0) @nodiscard
|
||||
{
|
||||
return ($Type*)self.calloc_checked($Type.sizeof + end_padding);
|
||||
}
|
||||
|
||||
macro Allocator.clone(&self, value)
|
||||
{
|
||||
var x = self.alloc($typeof(value));
|
||||
*x = value;
|
||||
return x;
|
||||
}
|
||||
|
||||
macro void* Allocator.alloc(&self, usz size) @nodiscard
|
||||
{
|
||||
return self.alloc_checked(size)!!;
|
||||
}
|
||||
macro void* Allocator.calloc(&self, usz size) @nodiscard
|
||||
{
|
||||
return self.acquire(size, true, 0, 0)!!;
|
||||
}
|
||||
macro void* Allocator.realloc(&self, void* ptr, usz new_size) @nodiscard
|
||||
{
|
||||
return self.resize(ptr, new_size, 0, 0)!!;
|
||||
}
|
||||
|
||||
macro void*! Allocator.alloc_aligned(&self, usz size, usz alignment, usz offset = 0)
|
||||
{
|
||||
$if env::TESTING:
|
||||
char* data = self.acquire(size, false, alignment, offset)!;
|
||||
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
||||
return data;
|
||||
$else
|
||||
return self.acquire(size, false, alignment, offset);
|
||||
$endif
|
||||
}
|
||||
macro void*! Allocator.calloc_aligned(&self, usz size, usz alignment, usz offset = 0)
|
||||
{
|
||||
return self.acquire(size, true, alignment, offset);
|
||||
}
|
||||
macro void*! Allocator.realloc_aligned(&self, void* ptr, usz new_size, usz alignment = 0, usz offset = 0)
|
||||
{
|
||||
return self.resize(ptr, new_size, alignment, offset);
|
||||
}
|
||||
|
||||
macro void Allocator.free(&self, void* ptr)
|
||||
{
|
||||
$if env::TESTING:
|
||||
if (ptr) ((char*)ptr)[0] = 0xBA;
|
||||
$endif
|
||||
self.release(ptr, false);
|
||||
}
|
||||
macro void Allocator.free_aligned(&self, void* ptr)
|
||||
{
|
||||
$if env::TESTING:
|
||||
if (ptr) ((char*)ptr)[0] = 0xBA;
|
||||
$endif
|
||||
self.release(ptr, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11,22 +11,22 @@ struct WasmMemory
|
||||
uptr use;
|
||||
}
|
||||
|
||||
fn char[]! WasmMemory.allocate_block(WasmMemory* this, usz bytes)
|
||||
fn char[]! WasmMemory.allocate_block(&self, usz bytes)
|
||||
{
|
||||
if (!this.allocation)
|
||||
if (!self.allocation)
|
||||
{
|
||||
this.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
}
|
||||
isz bytes_required = bytes + this.use - this.allocation;
|
||||
if (bytes_required <= 0)
|
||||
isz bytes_required = bytes + self.use - self.allocation;
|
||||
if (bytes_required <= 0)
|
||||
{
|
||||
defer this.use += bytes;
|
||||
return ((char*)this.use)[:bytes];
|
||||
defer self.use += bytes;
|
||||
return ((char*)self.use)[:bytes];
|
||||
}
|
||||
|
||||
usz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE;
|
||||
if ($$wasm_memory_grow(0, blocks_required) == -1) return AllocationFailure.OUT_OF_MEMORY?;
|
||||
this.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
defer this.use += bytes;
|
||||
return ((char*)this.use)[:bytes];
|
||||
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
|
||||
defer self.use += bytes;
|
||||
return ((char*)self.use)[:bytes];
|
||||
}
|
||||
@@ -21,7 +21,7 @@ macro int @main_to_void_main(#m, int, char**)
|
||||
|
||||
macro String[] args_to_strings(int argc, char** argv) @private
|
||||
{
|
||||
String[] list = malloc(String, argc);
|
||||
String[] list = mem::new_array(String, argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
char* arg = argv[i];
|
||||
@@ -55,7 +55,7 @@ macro int @main_to_void_main_args(#m, int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
$if env::os_is_win32():
|
||||
module std::core::main_stub @if(env::WIN32);
|
||||
|
||||
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @extern("CommandLineToArgvW");
|
||||
|
||||
@@ -68,12 +68,12 @@ macro String[] win_command_line_to_strings(ushort* cmd_line) @private
|
||||
|
||||
macro String[] wargs_strings(int argc, Char16** argv) @private
|
||||
{
|
||||
String[] list = malloc(String, argc);
|
||||
String[] list = mem::new_array(String, argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
Char16* arg = argv[i];
|
||||
Char16[] argstring = arg[:_strlen(arg)];
|
||||
list[i] = string::from_utf16(argstring) ?? "?".copy();
|
||||
list[i] = string::new_from_utf16(argstring) ?? "?".copy();
|
||||
}
|
||||
return list[:argc];
|
||||
}
|
||||
@@ -101,14 +101,14 @@ 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);
|
||||
if (catch #m(args)) return 1;
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
return #m(args);
|
||||
}
|
||||
|
||||
macro int @win_to_void_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
@@ -124,14 +124,14 @@ 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, args, show_cmd)) return 1;
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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, args, show_cmd);
|
||||
return #m(handle, args, show_cmd);
|
||||
}
|
||||
|
||||
macro int @win_to_void_main(#m, void* handle, Char16* cmd_line, int show_cmd)
|
||||
@@ -154,7 +154,7 @@ macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
|
||||
{
|
||||
String[] args = wargs_strings(argc, argv);
|
||||
defer release_wargs(args);
|
||||
return #m(args);
|
||||
return #m(args);
|
||||
}
|
||||
|
||||
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
|
||||
@@ -164,5 +164,3 @@ macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
|
||||
#m(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$endif
|
||||
@@ -2,39 +2,178 @@
|
||||
// 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 libc;
|
||||
|
||||
struct VirtualAny
|
||||
struct AnyStruct
|
||||
{
|
||||
void* ptr;
|
||||
typeid type_id;
|
||||
void* ptr;
|
||||
typeid type;
|
||||
}
|
||||
|
||||
struct SubArrayContainer
|
||||
struct SubArrayStruct
|
||||
{
|
||||
void* ptr;
|
||||
usz len;
|
||||
void* ptr;
|
||||
usz len;
|
||||
}
|
||||
|
||||
def BenchmarkFn = fn void!();
|
||||
|
||||
struct BenchmarkUnit
|
||||
{
|
||||
String name;
|
||||
BenchmarkFn func;
|
||||
}
|
||||
|
||||
fn BenchmarkUnit[] benchmark_collection_create(Allocator* allocator = mem::heap())
|
||||
{
|
||||
BenchmarkFn[] fns = $$BENCHMARK_FNS;
|
||||
String[] names = $$BENCHMARK_NAMES;
|
||||
BenchmarkUnit[] benchmarks = allocator.new_array(BenchmarkUnit, names.len);
|
||||
foreach (i, benchmark : fns)
|
||||
{
|
||||
benchmarks[i] = { names[i], fns[i] };
|
||||
}
|
||||
return benchmarks;
|
||||
}
|
||||
|
||||
const DEFAULT_BENCHMARK_WARMUP_ITERATIONS = 3;
|
||||
const DEFAULT_BENCHMARK_MAX_ITERATIONS = 10000;
|
||||
|
||||
uint benchmark_warmup_iterations @private = DEFAULT_BENCHMARK_WARMUP_ITERATIONS;
|
||||
uint benchmark_max_iterations @private = DEFAULT_BENCHMARK_MAX_ITERATIONS;
|
||||
|
||||
fn void set_benchmark_warmup_iterations(uint value) @builtin
|
||||
{
|
||||
benchmark_warmup_iterations = value;
|
||||
}
|
||||
|
||||
fn void set_benchmark_max_iterations(uint value) @builtin
|
||||
{
|
||||
assert(value > 0);
|
||||
benchmark_max_iterations = value;
|
||||
}
|
||||
|
||||
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
|
||||
{
|
||||
int benchmarks_passed = 0;
|
||||
int benchmark_count = benchmarks.len;
|
||||
usz max_name;
|
||||
|
||||
foreach (&unit : benchmarks)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
}
|
||||
|
||||
usz len = max_name + 9;
|
||||
|
||||
DString name = dstring::temp_with_capacity(64);
|
||||
name.append_repeat('-', len / 2);
|
||||
name.append(" BENCHMARKS ");
|
||||
name.append_repeat('-', len - len / 2);
|
||||
|
||||
io::printn(name);
|
||||
|
||||
name.clear();
|
||||
|
||||
long sys_clock_started;
|
||||
long sys_clock_finished;
|
||||
long sys_clocks;
|
||||
Clock clock;
|
||||
anyfault err;
|
||||
|
||||
foreach(unit : benchmarks)
|
||||
{
|
||||
defer name.clear();
|
||||
name.appendf("Benchmarking %s ", unit.name);
|
||||
name.append_repeat('.', max_name - unit.name.len + 2);
|
||||
io::printf("%s ", name.str_view());
|
||||
|
||||
for (uint i = 0; i < benchmark_warmup_iterations; i++)
|
||||
{
|
||||
err = @catch(unit.func()) @inline;
|
||||
@volatile_load(err);
|
||||
}
|
||||
|
||||
clock = std::time::clock::now();
|
||||
sys_clock_started = $$sysclock();
|
||||
|
||||
for (uint i = 0; i < benchmark_max_iterations; i++)
|
||||
{
|
||||
err = @catch(unit.func()) @inline;
|
||||
@volatile_load(err);
|
||||
}
|
||||
|
||||
sys_clock_finished = $$sysclock();
|
||||
NanoDuration nano_seconds = clock.mark();
|
||||
sys_clocks = sys_clock_finished - sys_clock_started;
|
||||
|
||||
if (err)
|
||||
{
|
||||
io::printfn("[failed] Failed due to: %s", err);
|
||||
continue;
|
||||
}
|
||||
|
||||
io::printfn("[ok] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
|
||||
benchmarks_passed++;
|
||||
}
|
||||
|
||||
io::printfn("\n%d benchmark%s run.\n", benchmark_count, benchmark_count > 1 ? "s" : "");
|
||||
io::printfn("Benchmarks Result: %s. %d passed, %d failed.",
|
||||
benchmarks_passed < benchmark_count ? "FAILED" : "ok",
|
||||
benchmarks_passed,
|
||||
benchmark_count - benchmarks_passed);
|
||||
|
||||
return benchmark_count == benchmarks_passed;
|
||||
}
|
||||
|
||||
fn bool default_benchmark_runner()
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
return run_benchmarks(benchmark_collection_create(mem::temp()));
|
||||
};
|
||||
}
|
||||
|
||||
def TestFn = fn void!();
|
||||
|
||||
struct TestRunner
|
||||
struct TestUnit
|
||||
{
|
||||
String name;
|
||||
TestFn func;
|
||||
}
|
||||
|
||||
fn TestUnit[] test_collection_create(Allocator* allocator = mem::heap())
|
||||
{
|
||||
TestFn[] fns = $$TEST_FNS;
|
||||
String[] names = $$TEST_NAMES;
|
||||
TestUnit[] tests = allocator.new_array(TestUnit, names.len);
|
||||
foreach (i, test : fns)
|
||||
{
|
||||
tests[i] = { names[i], fns[i] };
|
||||
}
|
||||
return tests;
|
||||
}
|
||||
|
||||
struct TestContext
|
||||
{
|
||||
String[] test_names;
|
||||
TestFn[] test_fns;
|
||||
JmpBuf buf;
|
||||
}
|
||||
|
||||
fn TestRunner test_runner_create()
|
||||
// Sort the tests by their name in ascending order.
|
||||
fn int cmp_test_unit(TestUnit a, TestUnit b)
|
||||
{
|
||||
return TestRunner {
|
||||
.test_fns = $$TEST_FNS,
|
||||
.test_names = $$TEST_NAMES,
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
import libc;
|
||||
|
||||
TestRunner* current_runner @private;
|
||||
TestContext* test_context @private;
|
||||
|
||||
fn void test_panic(String message, String file, String function, uint line)
|
||||
{
|
||||
@@ -43,57 +182,69 @@ fn void test_panic(String message, String file, String function, uint line)
|
||||
io::print(message);
|
||||
io::printn();
|
||||
io::printfn(" - in %s %s:%s.\n", function, file, line);
|
||||
libc::longjmp(¤t_runner.buf, 1);
|
||||
libc::longjmp(&test_context.buf, 1);
|
||||
}
|
||||
|
||||
fn bool TestRunner.run(TestRunner* runner)
|
||||
fn bool run_tests(TestUnit[] tests)
|
||||
{
|
||||
current_runner = runner;
|
||||
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 tests = runner.test_names.len;
|
||||
io::printn("----- TESTS -----");
|
||||
foreach(i, String name : runner.test_names)
|
||||
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)
|
||||
{
|
||||
io::printf("Testing %s ... ", name);
|
||||
if (libc::setjmp(&runner.buf) == 0)
|
||||
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 = runner.test_fns[i]())
|
||||
if (catch err = unit.func())
|
||||
{
|
||||
io::printn("[failed]");
|
||||
io::printfn("[failed] Failed due to: %s", err);
|
||||
continue;
|
||||
}
|
||||
io::printn("[ok]");
|
||||
tests_passed++;
|
||||
}
|
||||
}
|
||||
io::printfn("\n%d test(s) run.\n", tests);
|
||||
io::print("Test Result: ");
|
||||
if (tests_passed < tests)
|
||||
{
|
||||
io::print("FAILED");
|
||||
}
|
||||
else
|
||||
{
|
||||
io::print("ok");
|
||||
}
|
||||
io::printfn(". %d passed, %d failed.", tests_passed, tests - tests_passed);
|
||||
return tests == 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 __run_default_test_runner()
|
||||
fn bool default_test_runner()
|
||||
{
|
||||
return test_runner_create().run();
|
||||
@pool()
|
||||
{
|
||||
return run_tests(test_collection_create(mem::temp()));
|
||||
};
|
||||
}
|
||||
|
||||
$if !env::COMPILER_LIBC_AVAILABLE && env::ARCH_TYPE == ArchType.WASM32 || env::ARCH_TYPE == ArchType.WASM64:
|
||||
module std::core::runtime @if(WASM_NOLIBC);
|
||||
|
||||
extern fn void __wasm_call_ctors();
|
||||
fn void wasm_initialize() @extern("_initialize") @wasm
|
||||
{
|
||||
// The linker synthesizes this to call constructors.
|
||||
__wasm_call_ctors();
|
||||
}
|
||||
$endif
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
module std::core::string;
|
||||
import std::ascii;
|
||||
|
||||
def ZString = distinct inline char*;
|
||||
distinct ZString = inline char*;
|
||||
distinct WString = inline Char16*;
|
||||
def Char32 = uint;
|
||||
def Char16 = ushort;
|
||||
|
||||
@@ -30,24 +31,23 @@ fault NumberConversion
|
||||
FLOAT_OUT_OF_RANGE,
|
||||
}
|
||||
|
||||
macro String printf(String fmt, ..., Allocator* using = mem::heap())
|
||||
macro String tformat(String fmt, ...)
|
||||
{
|
||||
@stack_mem(256; Allocator* mem)
|
||||
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
|
||||
str.appendf(fmt, $vasplat());
|
||||
return str.str_view();
|
||||
}
|
||||
|
||||
macro String new_format(String fmt, ..., Allocator* allocator = mem::heap())
|
||||
{
|
||||
@pool(allocator)
|
||||
{
|
||||
DString str;
|
||||
str.init(.using = mem);
|
||||
str.printf(fmt, $vasplat());
|
||||
return str.copy_str(using);
|
||||
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
|
||||
str.appendf(fmt, $vasplat());
|
||||
return str.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
macro String tprintf(String fmt, ...)
|
||||
{
|
||||
DString str;
|
||||
str.tinit();
|
||||
str.printf(fmt, $vasplat());
|
||||
return str.str();
|
||||
}
|
||||
|
||||
macro bool char_in_set(char c, String set)
|
||||
{
|
||||
@@ -55,11 +55,11 @@ macro bool char_in_set(char c, String set)
|
||||
return false;
|
||||
}
|
||||
|
||||
fn String join(String[] s, String joiner, Allocator* using = mem::heap())
|
||||
fn String join_new(String[] s, String joiner, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (!s)
|
||||
{
|
||||
return (String)(calloc(char, 2, .using = using)[:0]);
|
||||
return (String)allocator.new_zero_array(char, 2)[:0];
|
||||
}
|
||||
|
||||
usz total_size = joiner.len * s.len;
|
||||
@@ -67,16 +67,16 @@ fn String join(String[] s, String joiner, Allocator* using = mem::heap())
|
||||
{
|
||||
total_size += str.len;
|
||||
}
|
||||
@stack_mem(256; Allocator* mem)
|
||||
@pool(allocator)
|
||||
{
|
||||
DString res = dstring::new_with_capacity(total_size, .using = mem);
|
||||
DString res = dstring::temp_with_capacity(total_size);
|
||||
res.append(s[0]);
|
||||
foreach (String* &str : s[1..])
|
||||
{
|
||||
res.append(joiner);
|
||||
res.append(*str);
|
||||
}
|
||||
return res.copy_str(using);
|
||||
return res.copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ fn String join(String[] s, String joiner, Allocator* using = mem::heap())
|
||||
* @param [in] string
|
||||
* @param [in] to_trim
|
||||
**/
|
||||
fn String String.trim(String string, String to_trim = "\t\n\r ")
|
||||
fn String String.trim(string, String to_trim = "\t\n\r ")
|
||||
{
|
||||
usz start = 0;
|
||||
usz len = string.len;
|
||||
@@ -99,7 +99,7 @@ fn String String.trim(String string, String to_trim = "\t\n\r ")
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn bool String.starts_with(String string, String needle)
|
||||
fn bool String.starts_with(string, String needle)
|
||||
{
|
||||
if (needle.len > string.len) return false;
|
||||
if (!needle.len) return true;
|
||||
@@ -110,7 +110,7 @@ fn bool String.starts_with(String string, String needle)
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn bool String.ends_with(String string, String needle)
|
||||
fn bool String.ends_with(string, String needle)
|
||||
{
|
||||
if (needle.len > string.len) return false;
|
||||
if (!needle.len) return true;
|
||||
@@ -123,7 +123,7 @@ fn bool String.ends_with(String string, String needle)
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn String String.strip(String string, String needle)
|
||||
fn String String.strip(string, String needle)
|
||||
{
|
||||
if (!needle.len || !string.starts_with(needle)) return string;
|
||||
return string[needle.len..];
|
||||
@@ -135,7 +135,7 @@ fn String String.strip(String string, String needle)
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn String String.strip_end(String string, String needle)
|
||||
fn String String.strip_end(string, String needle)
|
||||
{
|
||||
if (!needle.len || !string.ends_with(needle)) return string;
|
||||
// Note that this is the safe way if we want to support zero length.
|
||||
@@ -148,16 +148,16 @@ fn String String.strip_end(String string, String needle)
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @param [&inout] using "The allocator, defaults to the heap allocator"
|
||||
* @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(String s, String needle, usz max = 0, Allocator* using = mem::heap())
|
||||
fn String[] String.split(s, String needle, usz max = 0, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz capacity = 16;
|
||||
usz i = 0;
|
||||
String* holder = malloc(String, capacity, .using = using);
|
||||
String* holder = allocator.new_array(String, capacity);
|
||||
bool no_more = false;
|
||||
while (!no_more)
|
||||
{
|
||||
@@ -176,7 +176,7 @@ fn String[] String.split(String s, String needle, usz max = 0, Allocator* using
|
||||
if (i == capacity)
|
||||
{
|
||||
capacity *= 2;
|
||||
holder = realloc(holder, String.sizeof * capacity, .using = using);
|
||||
holder = allocator.realloc(holder, String.sizeof * capacity);
|
||||
}
|
||||
holder[i++] = res;
|
||||
}
|
||||
@@ -191,16 +191,52 @@ fn String[] String.split(String s, String needle, usz max = 0, Allocator* using
|
||||
* @param [in] needle
|
||||
* @param max "Max number of elements, 0 means no limit, defaults to 0"
|
||||
**/
|
||||
fn String[] String.tsplit(String s, String needle, usz max = 0)
|
||||
fn String[] String.tsplit(s, String needle, usz max = 0)
|
||||
{
|
||||
return s.split(needle, max, mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn bool String.contains(String s, String needle)
|
||||
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
|
||||
* @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)
|
||||
{
|
||||
if (c == needle) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the first incidence of a string.
|
||||
*
|
||||
* @param [in] s
|
||||
* @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)
|
||||
{
|
||||
if (c == needle) return i;
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the first incidence of a string.
|
||||
*
|
||||
@@ -212,26 +248,15 @@ fn bool String.contains(String s, String needle)
|
||||
* @return "the index of the needle"
|
||||
* @return! SearchResult.MISSING "if the needle cannot be found"
|
||||
**/
|
||||
fn usz! String.index_of(String s, String needle)
|
||||
fn usz! String.index_of(s, String needle)
|
||||
{
|
||||
usz match = 0;
|
||||
usz needed = needle.len;
|
||||
usz index_start = 0;
|
||||
char search = needle[0];
|
||||
foreach (usz i, char c : s)
|
||||
if (needed > 0 && s.len >= needed)
|
||||
{
|
||||
if (c == search)
|
||||
char first = needle[0];
|
||||
foreach (i, c: s[..^needed])
|
||||
{
|
||||
if (!match) index_start = i;
|
||||
match++;
|
||||
if (match == needed) return index_start;
|
||||
search = needle[match];
|
||||
continue;
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
match = 0;
|
||||
search = needle[0];
|
||||
if (c == first && s[i:needed] == needle) return i;
|
||||
}
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
@@ -248,37 +273,26 @@ fn usz! String.index_of(String s, String needle)
|
||||
* @return "the index of the needle"
|
||||
* @return! SearchResult.MISSING "if the needle cannot be found"
|
||||
**/
|
||||
fn usz! String.rindex_of(String s, String needle)
|
||||
fn usz! String.rindex_of(s, String needle)
|
||||
{
|
||||
usz match = 0;
|
||||
usz needed = needle.len;
|
||||
usz index_start = 0;
|
||||
char search = needle[^1];
|
||||
foreach_r (usz i, char c : s)
|
||||
if (needed > 0 && s.len >= needed)
|
||||
{
|
||||
if (c == search)
|
||||
char first = needle[0];
|
||||
foreach_r (i, c: s[..^needed])
|
||||
{
|
||||
if (!match) index_start = i;
|
||||
match++;
|
||||
if (match == needed) return index_start - needle.len + 1;
|
||||
search = needle[^(match + 1)];
|
||||
continue;
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
match = 0;
|
||||
search = needle[^1];
|
||||
if (c == first && s[i:needed] == needle) return i;
|
||||
}
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
fn String ZString.as_str(ZString str)
|
||||
fn String ZString.str_view(str)
|
||||
{
|
||||
return (String)((char*)str)[:str.len()];
|
||||
return (String)(str[:str.len()]);
|
||||
}
|
||||
|
||||
fn usz ZString.char_len(ZString str)
|
||||
fn usz ZString.char_len(str)
|
||||
{
|
||||
usz len = 0;
|
||||
char* ptr = (char*)str;
|
||||
@@ -289,53 +303,67 @@ fn usz ZString.char_len(ZString str)
|
||||
return len;
|
||||
}
|
||||
|
||||
fn usz ZString.len(ZString str)
|
||||
fn usz ZString.len(str)
|
||||
{
|
||||
usz len = 0;
|
||||
char* ptr = (char*)str;
|
||||
while (char c = ptr++[0]) len++;
|
||||
return len;
|
||||
char* ptr = (char*)str;
|
||||
while (char c = ptr++[0]) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
fn ZString String.zstr_copy(String s, Allocator* using = mem::heap())
|
||||
fn ZString String.zstr_copy(s, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz len = s.len;
|
||||
char* str = malloc(len + 1, .using = using);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (ZString)str;
|
||||
usz len = s.len;
|
||||
char* str = allocator.alloc(len + 1);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (ZString)str;
|
||||
}
|
||||
|
||||
fn String String.concat(String s1, String s2, Allocator* using = mem::heap())
|
||||
fn String String.concat(s1, String s2, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz full_len = s1.len + s2.len;
|
||||
char* str = malloc(full_len + 1, .using = using);
|
||||
usz s1_len = s1.len;
|
||||
mem::copy(str, s1.ptr, s1_len);
|
||||
mem::copy(str + s1_len, s2.ptr, s2.len);
|
||||
str[full_len] = 0;
|
||||
return (String)str[:full_len];
|
||||
usz full_len = s1.len + s2.len;
|
||||
char* str = allocator.alloc(full_len + 1);
|
||||
usz s1_len = s1.len;
|
||||
mem::copy(str, s1.ptr, s1_len);
|
||||
mem::copy(str + s1_len, s2.ptr, s2.len);
|
||||
str[full_len] = 0;
|
||||
return (String)str[:full_len];
|
||||
}
|
||||
|
||||
fn String String.tconcat(String s1, String s2) => s1.concat(s2, mem::temp());
|
||||
fn String String.tconcat(s1, String s2) => s1.concat(s2, mem::temp());
|
||||
|
||||
|
||||
fn ZString String.zstr_tcopy(String s) => s.zstr_copy(mem::temp()) @inline;
|
||||
fn ZString String.zstr_tcopy(s) => s.zstr_copy(mem::temp()) @inline;
|
||||
|
||||
fn String String.copy(String s, Allocator* using = mem::heap())
|
||||
fn String String.copy(s, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz len = s.len;
|
||||
char* str = malloc(len + 1, .using = using);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (String)str[:len];
|
||||
usz len = s.len;
|
||||
char* str = allocator.alloc(len + 1);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (String)str[:len];
|
||||
}
|
||||
|
||||
fn String String.tcopy(String s) => s.copy(mem::temp()) @inline;
|
||||
fn void String.free(&s, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (!s.len) return;
|
||||
allocator.free(s.ptr);
|
||||
*s = "";
|
||||
}
|
||||
|
||||
fn String ZString.copy(ZString z, Allocator* using = mem::heap()) => z.as_str().copy(using) @inline;
|
||||
fn String ZString.tcopy(ZString z) => z.as_str().copy(mem::temp()) @inline;
|
||||
fn String String.tcopy(s) => s.copy(mem::temp()) @inline;
|
||||
|
||||
fn String ZString.copy(z, Allocator* allocator = mem::temp())
|
||||
{
|
||||
return z.str_view().copy(allocator) @inline;
|
||||
}
|
||||
|
||||
fn String ZString.tcopy(z)
|
||||
{
|
||||
return z.str_view().copy(mem::temp()) @inline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an UTF-8 string to UTF-16
|
||||
@@ -343,77 +371,116 @@ fn String ZString.tcopy(ZString z) => z.as_str().copy(mem::temp()) @inline;
|
||||
* @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_utf16(String s, Allocator* using = mem::heap())
|
||||
fn Char16[]! String.to_new_utf16(s, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz len16 = conv::utf16len_for_utf8(s);
|
||||
Char16* data = malloc_checked(Char16, len16 + 1, .using = using)!;
|
||||
Char16* data = allocator.new_array_checked(Char16, len16 + 1)!;
|
||||
conv::utf8to16_unsafe(s, data)!;
|
||||
data[len16] = 0;
|
||||
return data[:len16];
|
||||
}
|
||||
|
||||
fn Char32[]! String.to_utf32(String s, Allocator* using = mem::heap())
|
||||
/**
|
||||
* 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(mem::temp());
|
||||
}
|
||||
|
||||
fn WString! String.to_new_wstring(s, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return (WString)s.to_new_utf16(allocator).ptr;
|
||||
}
|
||||
|
||||
fn WString! String.to_temp_wstring(s)
|
||||
{
|
||||
return (WString)s.to_temp_utf16().ptr;
|
||||
}
|
||||
|
||||
fn Char32[]! String.to_new_utf32(s, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz codepoints = conv::utf8_codepoints(s);
|
||||
Char32* data = malloc_checked(Char32, codepoints + 1, .using = using)!;
|
||||
Char32* data = allocator.new_array(Char32, codepoints + 1);
|
||||
conv::utf8to32_unsafe(s, data)!;
|
||||
data[codepoints] = 0;
|
||||
return data[:codepoints];
|
||||
}
|
||||
|
||||
fn void String.convert_ascii_to_lower(String s)
|
||||
fn Char32[]! String.to_temp_utf32(s)
|
||||
{
|
||||
foreach (&c : s) if (*c >= 'A' && *c <= 'Z') *c += 'a' - 'A';
|
||||
return s.to_new_utf32(mem::temp());
|
||||
}
|
||||
|
||||
fn String String.ascii_to_lower(String s, Allocator* using = mem::heap())
|
||||
fn void String.convert_ascii_to_lower(s)
|
||||
{
|
||||
String copy = s.copy(using);
|
||||
foreach (&c : s) if (c.is_upper()) *c += 'a' - 'A';
|
||||
}
|
||||
|
||||
fn String String.new_ascii_to_lower(s, Allocator* allocator = mem::heap())
|
||||
{
|
||||
String copy = s.copy(allocator);
|
||||
copy.convert_ascii_to_lower();
|
||||
return copy;
|
||||
}
|
||||
|
||||
fn void String.convert_ascii_to_upper(String s)
|
||||
fn String String.temp_ascii_to_lower(s, Allocator* allocator = mem::heap())
|
||||
{
|
||||
foreach (&c : s) if (*c >= 'a' && *c <= 'z') *c -= 'a' - 'A';
|
||||
return s.new_ascii_to_lower(mem::temp());
|
||||
}
|
||||
|
||||
fn String String.ascii_to_upper(String s, Allocator* using = mem::heap())
|
||||
fn void String.convert_ascii_to_upper(s)
|
||||
{
|
||||
String copy = s.copy(using);
|
||||
foreach (&c : s) if (c.is_lower()) *c -= 'a' - 'A';
|
||||
}
|
||||
|
||||
fn String String.new_ascii_to_upper(s, Allocator* allocator = mem::heap())
|
||||
{
|
||||
String copy = s.copy(allocator);
|
||||
copy.convert_ascii_to_upper();
|
||||
return copy;
|
||||
}
|
||||
|
||||
fn String! from_utf32(Char32[] utf32, Allocator* using = mem::heap())
|
||||
fn String String.temp_ascii_to_upper(s)
|
||||
{
|
||||
return s.new_ascii_to_upper(mem::temp());
|
||||
}
|
||||
|
||||
fn String! new_from_utf32(Char32[] utf32, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz len = conv::utf8len_for_utf32(utf32);
|
||||
char* data = malloc_checked(len + 1, .using = using)!;
|
||||
defer catch free(data, .using = using);
|
||||
char* data = allocator.alloc_checked(len + 1)!;
|
||||
defer catch allocator.free(data);
|
||||
conv::utf32to8_unsafe(utf32, data);
|
||||
data[len] = 0;
|
||||
return (String)data[:len];
|
||||
}
|
||||
|
||||
fn String! from_utf16(Char16[] utf16, Allocator* using = mem::heap())
|
||||
fn String! new_from_utf16(Char16[] utf16, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz len = conv::utf8len_for_utf16(utf16);
|
||||
char* data = malloc_checked(len + 1, .using = using)!;
|
||||
defer catch free(data, .using = using);
|
||||
char* data = allocator.alloc_checked(len + 1)!;
|
||||
defer catch allocator.free(data);
|
||||
conv::utf16to8_unsafe(utf16, data)!;
|
||||
data[len] = 0;
|
||||
return (String)data[:len];
|
||||
}
|
||||
|
||||
fn String! from_zutf16(Char16* utf16_pointer, Allocator* using = mem::heap())
|
||||
fn String! new_from_wstring(WString wstring, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz utf16_len;
|
||||
while (utf16_pointer[utf16_len] != 0) utf16_len++;
|
||||
Char16[] utf16 = utf16_pointer[:utf16_len];
|
||||
return from_utf16(utf16, using);
|
||||
while (wstring[utf16_len] != 0) utf16_len++;
|
||||
Char16[] utf16 = wstring[:utf16_len];
|
||||
return new_from_utf16(utf16, allocator);
|
||||
}
|
||||
|
||||
fn usz String.utf8_codepoints(String s)
|
||||
fn String! temp_from_wstring(WString wstring) => new_from_wstring(wstring, mem::temp()) @inline;
|
||||
fn String! temp_from_utf16(Char16[] utf16) => new_from_utf16(utf16, mem::temp()) @inline;
|
||||
|
||||
fn usz String.utf8_codepoints(s)
|
||||
{
|
||||
usz len = 0;
|
||||
foreach (char c : s)
|
||||
@@ -423,8 +490,7 @@ fn usz String.utf8_codepoints(String s)
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
macro String.to_integer(String string, $Type)
|
||||
macro String.to_integer(string, $Type)
|
||||
{
|
||||
usz len = string.len;
|
||||
usz index = 0;
|
||||
@@ -495,20 +561,17 @@ macro String.to_integer(String string, $Type)
|
||||
return value;
|
||||
}
|
||||
|
||||
fn int128! String.to_int128(s) => s.to_integer(int128);
|
||||
fn long! String.to_long(s) => s.to_integer(long);
|
||||
fn int! String.to_int(s) => s.to_integer(int);
|
||||
fn short! String.to_short(s) => s.to_integer(short);
|
||||
fn ichar! String.to_ichar(s) => s.to_integer(ichar);
|
||||
|
||||
fn Char16[]! String.to_temp_utf16(String s) => s.to_utf16(mem::temp());
|
||||
fn uint128! String.to_uint128(s) => s.to_integer(uint128);
|
||||
fn ulong! String.to_ulong(s) => s.to_integer(ulong);
|
||||
fn uint! String.to_uint(s) => s.to_integer(uint);
|
||||
fn ushort! String.to_ushort(s) => s.to_integer(ushort);
|
||||
fn char! String.to_uchar(s) => s.to_integer(char);
|
||||
|
||||
fn int128! String.to_int128(String s) => s.to_integer(int128);
|
||||
fn long! String.to_long(String s) => s.to_integer(long);
|
||||
fn int! String.to_int(String s) => s.to_integer(int);
|
||||
fn short! String.to_short(String s) => s.to_integer(short);
|
||||
fn ichar! String.to_ichar(String s) => s.to_integer(ichar);
|
||||
|
||||
fn uint128! String.to_uint128(String s) => s.to_integer(uint128);
|
||||
fn ulong! String.to_ulong(String s) => s.to_integer(ulong);
|
||||
fn uint! String.to_uint(String s) => s.to_integer(uint);
|
||||
fn ushort! String.to_ushort(String s) => s.to_integer(ushort);
|
||||
fn char! String.to_uchar(String s) => s.to_integer(char);
|
||||
|
||||
fn double! String.to_double(String s) => s.to_real(double);
|
||||
fn float! String.to_float(String s) => s.to_real(float);
|
||||
fn double! String.to_double(s) => s.to_real(double);
|
||||
fn float! String.to_float(s) => s.to_real(float);
|
||||
|
||||
@@ -6,18 +6,18 @@ struct StringIterator
|
||||
usz current;
|
||||
}
|
||||
|
||||
fn void StringIterator.reset(StringIterator* this)
|
||||
fn void StringIterator.reset(&self)
|
||||
{
|
||||
this.current = 0;
|
||||
self.current = 0;
|
||||
}
|
||||
|
||||
fn Char32! StringIterator.next(StringIterator* this)
|
||||
fn Char32! StringIterator.next(&self)
|
||||
{
|
||||
usz len = this.utf8.len;
|
||||
usz current = this.current;
|
||||
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(&this.utf8[current], &read)!;
|
||||
this.current += read;
|
||||
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
|
||||
self.current += read;
|
||||
return res;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
got_digit = true;
|
||||
default:
|
||||
dc++;
|
||||
if (c != '0') x[KMAX - 4] |= 1;
|
||||
if (c != '0') x[KMAX - 4] |= 1;
|
||||
|
||||
}
|
||||
if (index == last_char) break;
|
||||
@@ -235,7 +235,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
carry = (1000000000 >> sh) * tmp;
|
||||
if (k == a && !x[k])
|
||||
{
|
||||
a = (a + 1) & MASK;
|
||||
a = (a + 1) & MASK;
|
||||
i--;
|
||||
rp -= 9;
|
||||
}
|
||||
@@ -404,7 +404,7 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
if ((c | 32) == 'p')
|
||||
{
|
||||
long e2val = String.to_long((String)chars[index + 1..]) ?? (NumberConversion.MALFORMED_FLOAT?)!;
|
||||
e2 = e2val;
|
||||
e2 = e2val;
|
||||
}
|
||||
e2 += 4 * rp - 32;
|
||||
if (!x) return sign * 0.0;
|
||||
@@ -449,7 +449,7 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
|
||||
return math::scalbn(y, (int)e2);
|
||||
}
|
||||
|
||||
macro String.to_real(String chars, $Type) @private
|
||||
macro String.to_real(chars, $Type) @private
|
||||
{
|
||||
int sign = 1;
|
||||
$switch ($Type)
|
||||
@@ -465,18 +465,18 @@ macro String.to_real(String chars, $Type) @private
|
||||
$error "Unexpected type";
|
||||
$endswitch
|
||||
|
||||
while (chars.len && chars[0] == ' ') chars = chars[1..];
|
||||
if (!chars.len) return NumberConversion.MALFORMED_FLOAT?;
|
||||
switch (chars[0])
|
||||
{
|
||||
case '-':
|
||||
sign = -1;
|
||||
nextcase;
|
||||
case '+':
|
||||
chars = chars[1..];
|
||||
}
|
||||
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
|
||||
if (chars == "NAN" || chars == "nan") return $Type.nan;
|
||||
while (chars.len && chars[0] == ' ') chars = chars[1..];
|
||||
if (!chars.len) return NumberConversion.MALFORMED_FLOAT?;
|
||||
switch (chars[0])
|
||||
{
|
||||
case '-':
|
||||
sign = -1;
|
||||
nextcase;
|
||||
case '+':
|
||||
chars = chars[1..];
|
||||
}
|
||||
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
|
||||
if (chars == "NAN" || chars == "nan") return $Type.nan;
|
||||
|
||||
if (chars.len > 2 && chars[0] == '0' && (chars[1] | 32) == 'x')
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ fault ConversionResult
|
||||
/**
|
||||
* @require $Type.kindof.is_int() || $Type.kindof == TypeKind.ENUM "Argument was not an integer"
|
||||
**/
|
||||
macro any_to_int(any v, $Type)
|
||||
macro any_to_int(any* v, $Type)
|
||||
{
|
||||
typeid any_type = v.type;
|
||||
TypeKind kind = any_type.kindof;
|
||||
@@ -74,44 +74,40 @@ macro any_to_int(any v, $Type)
|
||||
}
|
||||
}
|
||||
|
||||
fn bool typeid.is_subtype_of(self, typeid other)
|
||||
{
|
||||
while (self != void.typeid)
|
||||
{
|
||||
if (self == other) return true;
|
||||
self = self.parentof;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
macro bool is_subtype_of($Type, $OtherType)
|
||||
{
|
||||
var $typeid = $Type.typeid;
|
||||
$switch ($Type)
|
||||
$case $OtherType: return true;
|
||||
$default: return false;
|
||||
$endswitch
|
||||
}
|
||||
macro bool is_numerical($Type)
|
||||
{
|
||||
var $kind = $Type.kindof;
|
||||
$if $kind == TypeKind.DISTINCT:
|
||||
return is_numerical($Type.inner);
|
||||
return is_numerical($typefrom($Type.inner));
|
||||
$else
|
||||
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|
||||
|| $kind == TypeKind.VECTOR;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn bool TypeKind.is_int(TypeKind kind) @inline
|
||||
fn bool TypeKind.is_int(kind) @inline
|
||||
{
|
||||
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
|
||||
}
|
||||
|
||||
macro bool is_indexable($Type)
|
||||
{
|
||||
return $checks($Type t, int i, t[i]);
|
||||
}
|
||||
|
||||
macro bool is_comparable($Type)
|
||||
{
|
||||
var $kind = $Type.kindof;
|
||||
$if $kind == TypeKind.DISTINCT:
|
||||
return is_comparable($Type.inner);
|
||||
$else
|
||||
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|
||||
|| $kind == TypeKind.VECTOR || $kind == TypeKind.BOOL || $kind == TypeKind.POINTER
|
||||
|| $kind == TypeKind.ENUM;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro bool is_equatable($Type)
|
||||
{
|
||||
return $checks($Type a, a == a);
|
||||
}
|
||||
|
||||
macro bool is_subarray_convertable($Type)
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
@@ -140,6 +136,18 @@ macro bool is_intlike($Type)
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_underlying_int($Type)
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
return true;
|
||||
$case DISTINCT:
|
||||
return is_underlying_int($typefrom($Type.inner));
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool is_float($Type) => $Type.kindof == TypeKind.FLOAT;
|
||||
|
||||
@@ -169,11 +177,6 @@ macro TypeKind inner_kind($Type)
|
||||
$endif
|
||||
}
|
||||
|
||||
macro bool @convertable(#a, $TypeB) @builtin
|
||||
{
|
||||
return $checks($TypeB x = #a);
|
||||
}
|
||||
|
||||
macro bool is_same($TypeA, $TypeB)
|
||||
{
|
||||
return $TypeA.typeid == $TypeB.typeid;
|
||||
@@ -181,12 +184,12 @@ macro bool is_same($TypeA, $TypeB)
|
||||
|
||||
macro bool @has_same(#a, #b, ...)
|
||||
{
|
||||
var $type_a = $typeof(#a).typeid;
|
||||
$if $type_a != $typeof(#b).typeid:
|
||||
return false;
|
||||
var $type_a = @typeid(#a);
|
||||
$if $type_a != @typeid(#b):
|
||||
return false;
|
||||
$endif
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
$if $typeof($vaexpr($i)).typeid != $type_a:
|
||||
$if @typeid($vaexpr($i)) != $type_a:
|
||||
return false;
|
||||
$endif
|
||||
$endfor
|
||||
@@ -208,6 +211,32 @@ macro bool may_load_atomic($Type)
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro lower_to_atomic_compatible_type($Type)
|
||||
{
|
||||
$switch ($Type.kindof)
|
||||
$case SIGNED_INT:
|
||||
$case UNSIGNED_INT:
|
||||
return $Type.typeid;
|
||||
$case DISTINCT:
|
||||
return lower_to_atomic_compatible_type($Type.inner);
|
||||
$case FLOAT:
|
||||
$switch ($Type)
|
||||
$case float16:
|
||||
return ushort.typeid;
|
||||
$case float:
|
||||
return uint.typeid;
|
||||
$case double:
|
||||
return ulong.typeid;
|
||||
$case float128:
|
||||
return uint128.typeid;
|
||||
$default:
|
||||
return void.typeid;
|
||||
$endswitch
|
||||
$default:
|
||||
return void.typeid;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -225,10 +254,18 @@ macro bool is_equatable_type($Type)
|
||||
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):
|
||||
return true;
|
||||
$else
|
||||
return is_equatable($Type);
|
||||
return $Type.is_eq;
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
return is_equatable_type($typeof(value));
|
||||
@@ -239,32 +276,32 @@ macro bool is_comparable_value(value)
|
||||
$if $defined(value.less) || $defined(value.compare_to):
|
||||
return true;
|
||||
$else
|
||||
return is_comparable($typeof(value));
|
||||
return $typeof(value).is_ordered;
|
||||
$endif
|
||||
}
|
||||
|
||||
enum TypeKind : char
|
||||
{
|
||||
VOID,
|
||||
BOOL,
|
||||
SIGNED_INT,
|
||||
UNSIGNED_INT,
|
||||
FLOAT,
|
||||
TYPEID,
|
||||
ANYFAULT,
|
||||
ANY,
|
||||
ENUM,
|
||||
FAULT,
|
||||
STRUCT,
|
||||
UNION,
|
||||
BITSTRUCT,
|
||||
FUNC,
|
||||
OPTIONAL,
|
||||
ARRAY,
|
||||
SUBARRAY,
|
||||
VECTOR,
|
||||
DISTINCT,
|
||||
POINTER,
|
||||
VOID,
|
||||
BOOL,
|
||||
SIGNED_INT,
|
||||
UNSIGNED_INT,
|
||||
FLOAT,
|
||||
TYPEID,
|
||||
ANYFAULT,
|
||||
ANY,
|
||||
ENUM,
|
||||
FAULT,
|
||||
STRUCT,
|
||||
UNION,
|
||||
BITSTRUCT,
|
||||
FUNC,
|
||||
OPTIONAL,
|
||||
ARRAY,
|
||||
SUBARRAY,
|
||||
VECTOR,
|
||||
DISTINCT,
|
||||
POINTER,
|
||||
}
|
||||
|
||||
struct TypeEnum
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
module std::core::values;
|
||||
|
||||
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 @convertable_to(#a, #b) => $checks($typeof(#b) x = #a);
|
||||
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)
|
||||
{
|
||||
$if @is_int(x):
|
||||
|
||||
@@ -12,36 +12,34 @@ struct Rc4
|
||||
/**
|
||||
* Initialize the RC4 state.
|
||||
*
|
||||
* @param [inout] this "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(Rc4* this, char[] key)
|
||||
fn void Rc4.init(&self, char[] key)
|
||||
{
|
||||
// Init the state matrix
|
||||
foreach (char i, &c : this.state) *c = i;
|
||||
foreach (char i, &c : self.state) *c = i;
|
||||
for (int i = 0, int j = 0; i < 256; i++)
|
||||
{
|
||||
j = (j + this.state[i] + key[i % key.len]) & 0xFF;
|
||||
@swap(this.state[i], this.state[j]);
|
||||
j = (j + self.state[i] + key[i % key.len]) & 0xFF;
|
||||
@swap(self.state[i], self.state[j]);
|
||||
}
|
||||
this.i = 0;
|
||||
this.j = 0;
|
||||
self.i = 0;
|
||||
self.j = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt or decrypt a sequence of bytes.
|
||||
*
|
||||
* @param [inout] this "The RC4 State"
|
||||
* @param [in] in "The input"
|
||||
* @param [out] out "The output"
|
||||
* @require in.len <= out.len "Output would overflow"
|
||||
**/
|
||||
fn void Rc4.crypt(Rc4* this, char[] in, char[] out)
|
||||
fn void Rc4.crypt(&self, char[] in, char[] out)
|
||||
{
|
||||
uint i = this.i;
|
||||
uint j = this.j;
|
||||
char* state = &this.state;
|
||||
uint i = self.i;
|
||||
uint j = self.j;
|
||||
char* state = &self.state;
|
||||
isz len = in.len;
|
||||
foreach (idx, c : in)
|
||||
{
|
||||
@@ -50,16 +48,16 @@ fn void Rc4.crypt(Rc4* this, char[] in, char[] out)
|
||||
@swap(state[i], state[j]);
|
||||
out[idx] = in[idx] ^ state[(state[i] + state[j]) & 0xFF];
|
||||
}
|
||||
this.i = i;
|
||||
this.j = j;
|
||||
self.i = i;
|
||||
self.j = j;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the rc4 state.
|
||||
*
|
||||
* @param [out] this "The RC4 State"
|
||||
* @param [&out] self "The RC4 State"
|
||||
**/
|
||||
fn void Rc4.destroy(Rc4* this)
|
||||
fn void Rc4.destroy(&self)
|
||||
{
|
||||
*this = {};
|
||||
*self = {};
|
||||
}
|
||||
|
||||
288
lib/std/encoding/base64.c3
Normal file
288
lib/std/encoding/base64.c3
Normal file
@@ -0,0 +1,288 @@
|
||||
module std::encoding::base64;
|
||||
import std::core::bitorder;
|
||||
|
||||
// The implementation is based on https://www.rfc-editor.org/rfc/rfc4648
|
||||
// Specifically this section:
|
||||
// https://www.rfc-editor.org/rfc/rfc4648#section-4
|
||||
|
||||
const STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
||||
const MASK @private = 0b111111;
|
||||
|
||||
struct Base64Encoder
|
||||
{
|
||||
int padding;
|
||||
String alphabet;
|
||||
}
|
||||
|
||||
fault Base64Error
|
||||
{
|
||||
DUPLICATE_IN_ALPHABET,
|
||||
PADDING_IN_ALPHABET,
|
||||
DESTINATION_TOO_SMALL,
|
||||
INVALID_PADDING,
|
||||
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 void! Base64Encoder.init(&self, String alphabet, int padding = '=')
|
||||
{
|
||||
check_alphabet(alphabet, padding)!;
|
||||
*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."
|
||||
**/
|
||||
fn usz Base64Encoder.encode_len(&self, usz n)
|
||||
{
|
||||
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
|
||||
**/
|
||||
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?;
|
||||
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
|
||||
{
|
||||
int padding;
|
||||
String alphabet;
|
||||
char[256] reverse;
|
||||
char invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 = '=')
|
||||
{
|
||||
check_alphabet(alphabet, padding)!;
|
||||
*self = { .padding = padding, .alphabet = alphabet };
|
||||
|
||||
bool[256] checked;
|
||||
foreach (i, c : alphabet)
|
||||
{
|
||||
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
|
||||
**/
|
||||
fn usz! Base64Decoder.decode_len(&self, usz n)
|
||||
{
|
||||
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
|
||||
**/
|
||||
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?;
|
||||
|
||||
usz trailing = src.len % 4;
|
||||
char[] src4 = src;
|
||||
switch
|
||||
{
|
||||
case self.padding < 0:
|
||||
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;
|
||||
char pad = (char)self.padding;
|
||||
if (src[^1] == pad) src4 = src[:^4];
|
||||
}
|
||||
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
|
||||
// the padding is not present in the alphabet.
|
||||
fn void! check_alphabet(String alphabet, int padding) @local
|
||||
{
|
||||
bool[256] checked;
|
||||
if (padding < 0)
|
||||
{
|
||||
foreach (c : alphabet)
|
||||
{
|
||||
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
|
||||
checked[c] = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
char pad = (char)padding;
|
||||
foreach (c : alphabet)
|
||||
{
|
||||
if (c == pad) return Base64Error.PADDING_IN_ALPHABET?;
|
||||
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
|
||||
checked[c] = true;
|
||||
}
|
||||
}
|
||||
64
lib/std/encoding/csv.c3
Normal file
64
lib/std/encoding/csv.c3
Normal file
@@ -0,0 +1,64 @@
|
||||
module std::encoding::csv;
|
||||
import std::io;
|
||||
|
||||
struct CsvReader
|
||||
{
|
||||
InStream* stream;
|
||||
String separator;
|
||||
}
|
||||
|
||||
fn void CsvReader.init(&self, InStream* stream, String separator = ",")
|
||||
{
|
||||
self.stream = stream;
|
||||
self.separator = separator;
|
||||
}
|
||||
|
||||
fn String[]! CsvReader.read_new_row(self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return self.read_new_row_with_allocator(mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn String[]! CsvReader.read_new_row_with_allocator(self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
@pool(allocator)
|
||||
{
|
||||
return io::treadline(self.stream).split(self.separator, .allocator = allocator);
|
||||
};
|
||||
}
|
||||
|
||||
fn String[]! CsvReader.read_temp_row(self)
|
||||
{
|
||||
return self.read_new_row_with_allocator(mem::temp()) @inline;
|
||||
}
|
||||
|
||||
fn void! CsvReader.skip_row(self) @maydiscard
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
(void)io::treadline(self.stream);
|
||||
};
|
||||
}
|
||||
|
||||
macro CsvReader.@each_row(self, int rows = int.max; @body(String[] row))
|
||||
{
|
||||
InputStream* stream = self.stream;
|
||||
String sep = self.separator;
|
||||
while (rows--)
|
||||
{
|
||||
@stack_mem(512; Allocator* mem)
|
||||
{
|
||||
String[] parts;
|
||||
@pool()
|
||||
{
|
||||
String! s = stream.treadline();
|
||||
if (catch err = s)
|
||||
{
|
||||
if (err == IoError.EOF) return;
|
||||
return err?;
|
||||
}
|
||||
parts = s.split(sep, .allocator = mem);
|
||||
};
|
||||
@body(parts);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,25 @@ import std::io;
|
||||
import std::ascii;
|
||||
import std::collections::object;
|
||||
|
||||
enum JsonTokenType
|
||||
fault JsonParsingError
|
||||
{
|
||||
EOF,
|
||||
UNEXPECTED_CHARACTER,
|
||||
INVALID_ESCAPE_SEQUENCE,
|
||||
DUPLICATE_MEMBERS,
|
||||
INVALID_NUMBER,
|
||||
}
|
||||
|
||||
fn Object*! parse(InStream* s, Allocator* allocator = mem::heap())
|
||||
{
|
||||
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 --
|
||||
|
||||
enum JsonTokenType @local
|
||||
{
|
||||
NO_TOKEN,
|
||||
LBRACE,
|
||||
@@ -23,79 +41,67 @@ enum JsonTokenType
|
||||
EOF,
|
||||
}
|
||||
|
||||
struct JsonParser
|
||||
struct JsonContext @local
|
||||
{
|
||||
uint line;
|
||||
Stream stream;
|
||||
InStream* stream;
|
||||
Allocator* allocator;
|
||||
JsonTokenType token;
|
||||
DString last_string;
|
||||
double last_number;
|
||||
char current;
|
||||
anyfault current_err;
|
||||
bool skip_comments;
|
||||
bool reached_end;
|
||||
bitstruct : char {
|
||||
bool skip_comments;
|
||||
bool reached_end;
|
||||
bool pushed_back;
|
||||
}
|
||||
}
|
||||
|
||||
fault JsonParsingError
|
||||
{
|
||||
EOF,
|
||||
UNEXPECTED_CHARACTER,
|
||||
INVALID_ESCAPE_SEQUENCE,
|
||||
DUPLICATE_MEMBERS,
|
||||
INVALID_NUMBER,
|
||||
}
|
||||
|
||||
fn void JsonParser.init(JsonParser* parser, Stream s, Allocator* using = mem::heap())
|
||||
{
|
||||
*parser = { .last_string = dstring::new_with_capacity(64, using), .stream = s, .allocator = using };
|
||||
}
|
||||
|
||||
fn Object*! JsonParser.parse_from_token(JsonParser* this, JsonTokenType token)
|
||||
fn Object*! parse_from_token(JsonContext* context, JsonTokenType token) @local
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case NO_TOKEN: unreachable();
|
||||
case LBRACE: return this.parse_map();
|
||||
case LBRACKET: return this.parse_array();
|
||||
case LBRACE: return parse_map(context);
|
||||
case LBRACKET: return parse_array(context);
|
||||
case COMMA:
|
||||
case RBRACE:
|
||||
case RBRACKET:
|
||||
case COLON: return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
case STRING: return object::new_string(this.last_string.str(), this.allocator);
|
||||
case NUMBER: return object::new_float(this.last_number, this.allocator);
|
||||
case STRING: return object::new_string(context.last_string.str_view(), context.allocator);
|
||||
case NUMBER: return object::new_float(context.last_number, context.allocator);
|
||||
case TRUE: return object::new_bool(true);
|
||||
case FALSE: return object::new_bool(false);
|
||||
case NULL: return object::new_null();
|
||||
case EOF: return JsonParsingError.EOF?;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
fn Object*! JsonParser.parse_any(JsonParser* this)
|
||||
fn Object*! parse_any(JsonContext* context) @local
|
||||
{
|
||||
return this.parse_from_token(this.advance());
|
||||
return parse_from_token(context, advance(context));
|
||||
}
|
||||
|
||||
fn JsonTokenType! JsonParser.lex_number(JsonParser* this, char c)
|
||||
fn JsonTokenType! lex_number(JsonContext *context, char c) @local
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
DString t = dstring::tnew_with_capacity(32);
|
||||
DString t = dstring::temp_with_capacity(32);
|
||||
bool negate = c == '-';
|
||||
if (negate)
|
||||
{
|
||||
t.append(c);
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
}
|
||||
while (c >= '0' && c <= '9')
|
||||
while (c.is_digit())
|
||||
{
|
||||
t.append(c);
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
}
|
||||
if (c == '.')
|
||||
{
|
||||
t.append(c);
|
||||
while (c = this.read_next()!, c >= '0' && c <= '9')
|
||||
while (c = read_next(context)!, c.is_digit())
|
||||
{
|
||||
t.append(c);
|
||||
}
|
||||
@@ -103,113 +109,123 @@ fn JsonTokenType! JsonParser.lex_number(JsonParser* this, char c)
|
||||
if ((c | 32) == 'e')
|
||||
{
|
||||
t.append(c);
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
switch (c)
|
||||
{
|
||||
case '-':
|
||||
case '+':
|
||||
t.append(c);
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
}
|
||||
if (c < '0' || c > '9') return JsonParsingError.INVALID_NUMBER?;
|
||||
while (c >= '0' && c <= '9')
|
||||
if (!c.is_digit()) return JsonParsingError.INVALID_NUMBER?;
|
||||
while (c.is_digit())
|
||||
{
|
||||
t.append(c);
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
}
|
||||
}
|
||||
this.pushback();
|
||||
double! d = t.str().to_double() ?? JsonParsingError.INVALID_NUMBER?;
|
||||
this.last_number = d!;
|
||||
pushback(context, c);
|
||||
double! d = t.str_view().to_double() ?? JsonParsingError.INVALID_NUMBER?;
|
||||
context.last_number = d!;
|
||||
return NUMBER;
|
||||
};
|
||||
}
|
||||
|
||||
fn Object*! JsonParser.parse_map(JsonParser* this)
|
||||
fn Object*! parse_map(JsonContext* context) @local
|
||||
{
|
||||
Object* map = object::new_obj(this.allocator);
|
||||
JsonTokenType token = this.advance()!;
|
||||
Object* map = object::new_obj(context.allocator);
|
||||
JsonTokenType token = advance(context)!;
|
||||
defer catch map.free();
|
||||
|
||||
DString temp_key = dstring::new_with_capacity(32, this.allocator);
|
||||
DString temp_key = dstring::new_with_capacity(32, context.allocator);
|
||||
defer temp_key.free();
|
||||
while (token != JsonTokenType.RBRACE)
|
||||
{
|
||||
if (token != JsonTokenType.STRING) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
DString string = this.last_string;
|
||||
if (map.has_key(string.str())) 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);
|
||||
this.parse_expected(COLON)!;
|
||||
Object* element = this.parse_any()!;
|
||||
map.set(temp_key.str(), element);
|
||||
token = this.advance()!;
|
||||
if (token == JsonTokenType.COMMA)
|
||||
{
|
||||
token = this.advance()!;
|
||||
continue;
|
||||
}
|
||||
if (token != JsonTokenType.RBRACE) 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)
|
||||
{
|
||||
token = advance(context)!;
|
||||
continue;
|
||||
}
|
||||
if (token != JsonTokenType.RBRACE) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
fn Object*! JsonParser.parse_array(JsonParser* this)
|
||||
fn Object*! parse_array(JsonContext* context) @local
|
||||
{
|
||||
Object* list = object::new_obj(this.allocator);
|
||||
Object* list = object::new_obj(context.allocator);
|
||||
defer catch list.free();
|
||||
JsonTokenType token = this.advance()!;
|
||||
JsonTokenType token = advance(context)!;
|
||||
while (token != JsonTokenType.RBRACKET)
|
||||
{
|
||||
Object* element = this.parse_from_token(token)!;
|
||||
Object* element = parse_from_token(context, token)!;
|
||||
list.append(element);
|
||||
token = this.advance()!;
|
||||
token = advance(context)!;
|
||||
if (token == JsonTokenType.COMMA)
|
||||
{
|
||||
token = this.advance()!;
|
||||
continue;
|
||||
token = advance(context)!;
|
||||
continue;
|
||||
}
|
||||
if (token != JsonTokenType.RBRACKET) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
fn void JsonParser.pushback(JsonParser* this)
|
||||
fn void pushback(JsonContext* context, char c) @local
|
||||
{
|
||||
if (!this.reached_end) this.stream.pushback_byte()!!;
|
||||
if (!context.reached_end)
|
||||
{
|
||||
assert(!context.pushed_back);
|
||||
context.pushed_back = true;
|
||||
context.current = c;
|
||||
}
|
||||
}
|
||||
|
||||
fn char! JsonParser.read_next(JsonParser* this)
|
||||
fn char! read_next(JsonContext* context) @local
|
||||
{
|
||||
if (this.reached_end) return '\0';
|
||||
char! c = this.stream.read_byte();
|
||||
if (context.reached_end) return '\0';
|
||||
if (context.pushed_back)
|
||||
{
|
||||
context.pushed_back = false;
|
||||
return context.current;
|
||||
}
|
||||
char! c = context.stream.read_byte();
|
||||
if (catch err = c)
|
||||
{
|
||||
case IoError.EOF:
|
||||
this.reached_end = true;
|
||||
context.reached_end = true;
|
||||
return '\0';
|
||||
default:
|
||||
return err?;
|
||||
}
|
||||
if (c == 0)
|
||||
{
|
||||
this.reached_end = true;
|
||||
context.reached_end = true;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
fn JsonTokenType! JsonParser.advance(JsonParser* this)
|
||||
fn JsonTokenType! advance(JsonContext* context) @local
|
||||
{
|
||||
char c;
|
||||
// Skip whitespace
|
||||
while WS: (c = this.read_next()!)
|
||||
while WS: (c = read_next(context)!)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\n':
|
||||
this.line++;
|
||||
context.line++;
|
||||
nextcase;
|
||||
case ' ':
|
||||
case '\t':
|
||||
@@ -217,24 +233,24 @@ fn JsonTokenType! JsonParser.advance(JsonParser* this)
|
||||
case '\v':
|
||||
continue;
|
||||
case '/':
|
||||
if (!this.skip_comments) break;
|
||||
c = this.read_next()!;
|
||||
if (!context.skip_comments) break;
|
||||
c = read_next(context)!;
|
||||
if (c != '*')
|
||||
{
|
||||
this.pushback();
|
||||
pushback(context, c);
|
||||
break WS;
|
||||
}
|
||||
while COMMENT: (1)
|
||||
while COMMENT: (true)
|
||||
{
|
||||
// Skip to */
|
||||
while (c = this.read_next()!)
|
||||
while (c = read_next(context)!)
|
||||
{
|
||||
if (c == '\n') this.line++;
|
||||
if (c == '\n') context.line++;
|
||||
if (c != '*') continue;
|
||||
// Skip through all the '*'
|
||||
while (c = this.read_next()!)
|
||||
while (c = read_next(context)!)
|
||||
{
|
||||
if (c == '\n') this.line++;
|
||||
if (c == '\n') context.line++;
|
||||
if (c != '*') break;
|
||||
}
|
||||
if (c == '/') break COMMENT;
|
||||
@@ -262,44 +278,44 @@ fn JsonTokenType! JsonParser.advance(JsonParser* this)
|
||||
case ',':
|
||||
return COMMA;
|
||||
case '"':
|
||||
return this.lex_string();
|
||||
return lex_string(context);
|
||||
case '-':
|
||||
case '0'..'9':
|
||||
return this.lex_number(c);
|
||||
return lex_number(context, c);
|
||||
case 't':
|
||||
this.match("rue")!;
|
||||
match(context, "rue")!;
|
||||
return TRUE;
|
||||
case 'f':
|
||||
this.match("alse")!;
|
||||
match(context, "alse")!;
|
||||
return FALSE;
|
||||
case 'n':
|
||||
this.match("ull")!;
|
||||
match(context, "ull")!;
|
||||
return NULL;
|
||||
default:
|
||||
return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
}
|
||||
|
||||
fn void! JsonParser.match(JsonParser* this, String str)
|
||||
fn void! match(JsonContext* context, String str) @local
|
||||
{
|
||||
foreach (c : str)
|
||||
{
|
||||
char l = this.read_next()!;
|
||||
char l = read_next(context)!;
|
||||
if (l != c) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
}
|
||||
|
||||
fn void! JsonParser.parse_expected(JsonParser* this, JsonTokenType token) @local
|
||||
fn void! parse_expected(JsonContext* context, JsonTokenType token) @local
|
||||
{
|
||||
if (this.advance()! != token) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
if (advance(context)! != token) return JsonParsingError.UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
|
||||
fn JsonTokenType! JsonParser.lex_string(JsonParser *this)
|
||||
fn JsonTokenType! lex_string(JsonContext* context)
|
||||
{
|
||||
this.last_string.clear();
|
||||
while LOOP: (1)
|
||||
context.last_string.clear();
|
||||
while LOOP: (true)
|
||||
{
|
||||
char c = this.read_next()!;
|
||||
char c = read_next(context)!;
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
@@ -311,10 +327,10 @@ fn JsonTokenType! JsonParser.lex_string(JsonParser *this)
|
||||
case '\\':
|
||||
break;
|
||||
default:
|
||||
this.last_string.append(c);
|
||||
context.last_string.append(c);
|
||||
continue;
|
||||
}
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
@@ -339,11 +355,11 @@ fn JsonTokenType! JsonParser.lex_string(JsonParser *this)
|
||||
uint val;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
c = this.read_next()!;
|
||||
c = read_next(context)!;
|
||||
if (!c.is_xdigit()) return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
|
||||
val = val << 4 + (c > '9' ? (c | 32) - 'a' + 10 : c - '0');
|
||||
}
|
||||
this.last_string.append_char32(val);
|
||||
context.last_string.append_char32(val);
|
||||
continue;
|
||||
default:
|
||||
return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
|
||||
|
||||
@@ -8,46 +8,46 @@ const uint ADLER_CONST @private = 65521;
|
||||
|
||||
struct Adler32
|
||||
{
|
||||
uint a;
|
||||
uint b;
|
||||
uint a;
|
||||
uint b;
|
||||
}
|
||||
|
||||
fn void Adler32.init(Adler32 *this)
|
||||
fn void Adler32.init(&self)
|
||||
{
|
||||
*this = { 1, 0 };
|
||||
*self = { 1, 0 };
|
||||
}
|
||||
|
||||
fn void Adler32.updatec(Adler32* this, char c)
|
||||
fn void Adler32.updatec(&self, char c)
|
||||
{
|
||||
this.a = (this.a + c) % ADLER_CONST;
|
||||
this.b = (this.b + this.a) % ADLER_CONST;
|
||||
self.a = (self.a + c) % ADLER_CONST;
|
||||
self.b = (self.b + self.a) % ADLER_CONST;
|
||||
}
|
||||
|
||||
fn void Adler32.update(Adler32* this, char[] data)
|
||||
fn void Adler32.update(&self, char[] data)
|
||||
{
|
||||
uint a = this.a;
|
||||
uint b = this.b;
|
||||
foreach (char x : data)
|
||||
{
|
||||
a = (a + x) % ADLER_CONST;
|
||||
b = (b + a) % ADLER_CONST;
|
||||
}
|
||||
*this = { a, b };
|
||||
uint a = self.a;
|
||||
uint b = self.b;
|
||||
foreach (char x : data)
|
||||
{
|
||||
a = (a + x) % ADLER_CONST;
|
||||
b = (b + a) % ADLER_CONST;
|
||||
}
|
||||
*self = { a, b };
|
||||
}
|
||||
|
||||
fn uint Adler32.final(Adler32* this)
|
||||
fn uint Adler32.final(&self)
|
||||
{
|
||||
return (this.b << 16) | this.a;
|
||||
return (self.b << 16) | self.a;
|
||||
}
|
||||
|
||||
fn uint encode(char[] data)
|
||||
{
|
||||
uint a = 1;
|
||||
uint b = 0;
|
||||
foreach (char x : data)
|
||||
{
|
||||
a = (a + x) % ADLER_CONST;
|
||||
b = (b + a) % ADLER_CONST;
|
||||
}
|
||||
return (b << 16) | a;
|
||||
uint a = 1;
|
||||
uint b = 0;
|
||||
foreach (char x : data)
|
||||
{
|
||||
a = (a + x) % ADLER_CONST;
|
||||
b = (b + a) % ADLER_CONST;
|
||||
}
|
||||
return (b << 16) | a;
|
||||
}
|
||||
@@ -5,41 +5,41 @@ module std::hash::crc32;
|
||||
|
||||
struct Crc32
|
||||
{
|
||||
uint result;
|
||||
uint result;
|
||||
}
|
||||
|
||||
fn void Crc32.init(Crc32* this, uint seed = 0)
|
||||
fn void Crc32.init(&self, uint seed = 0)
|
||||
{
|
||||
this.result = ~seed;
|
||||
self.result = ~seed;
|
||||
}
|
||||
|
||||
fn void Crc32.updatec(Crc32* this, char c)
|
||||
fn void Crc32.updatec(&self, char c)
|
||||
{
|
||||
this.result = (this.result >> 8) ^ CRC32_TABLE[(this.result ^ c) & 0xFF];
|
||||
self.result = (self.result >> 8) ^ CRC32_TABLE[(self.result ^ c) & 0xFF];
|
||||
}
|
||||
|
||||
fn void Crc32.update(Crc32* this, char[] data)
|
||||
fn void Crc32.update(&self, char[] data)
|
||||
{
|
||||
uint result = this.result;
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
|
||||
}
|
||||
this.result = result;
|
||||
uint result = self.result;
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
|
||||
}
|
||||
self.result = result;
|
||||
}
|
||||
|
||||
fn uint Crc32.final(Crc32* this)
|
||||
fn uint Crc32.final(&self)
|
||||
{
|
||||
return ~this.result;
|
||||
return ~self.result;
|
||||
}
|
||||
|
||||
fn uint encode(char[] data)
|
||||
{
|
||||
uint result = ~(uint)(0);
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
|
||||
}
|
||||
uint result = ~(uint)(0);
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
|
||||
}
|
||||
return ~result;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,107 +5,107 @@ module std::hash::crc64;
|
||||
|
||||
struct Crc64
|
||||
{
|
||||
ulong result;
|
||||
ulong result;
|
||||
}
|
||||
|
||||
fn void Crc64.init(Crc64* this, uint seed = 0)
|
||||
fn void Crc64.init(&self, uint seed = 0)
|
||||
{
|
||||
this.result = seed;
|
||||
self.result = seed;
|
||||
}
|
||||
|
||||
fn void Crc64.updatec(Crc64* this, char c)
|
||||
fn void Crc64.updatec(&self, char c)
|
||||
{
|
||||
this.result = (this.result << 8) ^ CRC64_TABLE[(char)((this.result >> 56) ^ c)];
|
||||
self.result = (self.result << 8) ^ CRC64_TABLE[(char)((self.result >> 56) ^ c)];
|
||||
}
|
||||
|
||||
fn void Crc64.update(Crc64* this, char[] data)
|
||||
fn void Crc64.update(&self, char[] data)
|
||||
{
|
||||
ulong result = this.result;
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
|
||||
}
|
||||
this.result = result;
|
||||
ulong result = self.result;
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
|
||||
}
|
||||
self.result = result;
|
||||
}
|
||||
|
||||
fn ulong Crc64.final(Crc64* this)
|
||||
fn ulong Crc64.final(&self)
|
||||
{
|
||||
return this.result;
|
||||
return self.result;
|
||||
}
|
||||
|
||||
fn ulong encode(char[] data)
|
||||
{
|
||||
ulong result = (ulong)(0);
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
|
||||
}
|
||||
ulong result = (ulong)(0);
|
||||
foreach (char x : data)
|
||||
{
|
||||
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const ulong[256] CRC64_TABLE @private = {
|
||||
0x0000000000000000, 0x42f0e1eba9ea3693, 0x85e1c3d753d46d26, 0xc711223cfa3e5bb5,
|
||||
0x493366450e42ecdf, 0x0bc387aea7a8da4c, 0xccd2a5925d9681f9, 0x8e224479f47cb76a,
|
||||
0x9266cc8a1c85d9be, 0xd0962d61b56fef2d, 0x17870f5d4f51b498, 0x5577eeb6e6bb820b,
|
||||
0xdb55aacf12c73561, 0x99a54b24bb2d03f2, 0x5eb4691841135847, 0x1c4488f3e8f96ed4,
|
||||
0x663d78ff90e185ef, 0x24cd9914390bb37c, 0xe3dcbb28c335e8c9, 0xa12c5ac36adfde5a,
|
||||
0x2f0e1eba9ea36930, 0x6dfeff5137495fa3, 0xaaefdd6dcd770416, 0xe81f3c86649d3285,
|
||||
0xf45bb4758c645c51, 0xb6ab559e258e6ac2, 0x71ba77a2dfb03177, 0x334a9649765a07e4,
|
||||
0xbd68d2308226b08e, 0xff9833db2bcc861d, 0x388911e7d1f2dda8, 0x7a79f00c7818eb3b,
|
||||
0xcc7af1ff21c30bde, 0x8e8a101488293d4d, 0x499b3228721766f8, 0x0b6bd3c3dbfd506b,
|
||||
0x854997ba2f81e701, 0xc7b97651866bd192, 0x00a8546d7c558a27, 0x4258b586d5bfbcb4,
|
||||
0x5e1c3d753d46d260, 0x1cecdc9e94ace4f3, 0xdbfdfea26e92bf46, 0x990d1f49c77889d5,
|
||||
0x172f5b3033043ebf, 0x55dfbadb9aee082c, 0x92ce98e760d05399, 0xd03e790cc93a650a,
|
||||
0xaa478900b1228e31, 0xe8b768eb18c8b8a2, 0x2fa64ad7e2f6e317, 0x6d56ab3c4b1cd584,
|
||||
0xe374ef45bf6062ee, 0xa1840eae168a547d, 0x66952c92ecb40fc8, 0x2465cd79455e395b,
|
||||
0x3821458aada7578f, 0x7ad1a461044d611c, 0xbdc0865dfe733aa9, 0xff3067b657990c3a,
|
||||
0x711223cfa3e5bb50, 0x33e2c2240a0f8dc3, 0xf4f3e018f031d676, 0xb60301f359dbe0e5,
|
||||
0xda050215ea6c212f, 0x98f5e3fe438617bc, 0x5fe4c1c2b9b84c09, 0x1d14202910527a9a,
|
||||
0x93366450e42ecdf0, 0xd1c685bb4dc4fb63, 0x16d7a787b7faa0d6, 0x5427466c1e109645,
|
||||
0x4863ce9ff6e9f891, 0x0a932f745f03ce02, 0xcd820d48a53d95b7, 0x8f72eca30cd7a324,
|
||||
0x0150a8daf8ab144e, 0x43a04931514122dd, 0x84b16b0dab7f7968, 0xc6418ae602954ffb,
|
||||
0xbc387aea7a8da4c0, 0xfec89b01d3679253, 0x39d9b93d2959c9e6, 0x7b2958d680b3ff75,
|
||||
0xf50b1caf74cf481f, 0xb7fbfd44dd257e8c, 0x70eadf78271b2539, 0x321a3e938ef113aa,
|
||||
0x2e5eb66066087d7e, 0x6cae578bcfe24bed, 0xabbf75b735dc1058, 0xe94f945c9c3626cb,
|
||||
0x676dd025684a91a1, 0x259d31cec1a0a732, 0xe28c13f23b9efc87, 0xa07cf2199274ca14,
|
||||
0x167ff3eacbaf2af1, 0x548f120162451c62, 0x939e303d987b47d7, 0xd16ed1d631917144,
|
||||
0x5f4c95afc5edc62e, 0x1dbc74446c07f0bd, 0xdaad56789639ab08, 0x985db7933fd39d9b,
|
||||
0x84193f60d72af34f, 0xc6e9de8b7ec0c5dc, 0x01f8fcb784fe9e69, 0x43081d5c2d14a8fa,
|
||||
0xcd2a5925d9681f90, 0x8fdab8ce70822903, 0x48cb9af28abc72b6, 0x0a3b7b1923564425,
|
||||
0x70428b155b4eaf1e, 0x32b26afef2a4998d, 0xf5a348c2089ac238, 0xb753a929a170f4ab,
|
||||
0x3971ed50550c43c1, 0x7b810cbbfce67552, 0xbc902e8706d82ee7, 0xfe60cf6caf321874,
|
||||
0xe224479f47cb76a0, 0xa0d4a674ee214033, 0x67c58448141f1b86, 0x253565a3bdf52d15,
|
||||
0xab1721da49899a7f, 0xe9e7c031e063acec, 0x2ef6e20d1a5df759, 0x6c0603e6b3b7c1ca,
|
||||
0xf6fae5c07d3274cd, 0xb40a042bd4d8425e, 0x731b26172ee619eb, 0x31ebc7fc870c2f78,
|
||||
0xbfc9838573709812, 0xfd39626eda9aae81, 0x3a28405220a4f534, 0x78d8a1b9894ec3a7,
|
||||
0x649c294a61b7ad73, 0x266cc8a1c85d9be0, 0xe17dea9d3263c055, 0xa38d0b769b89f6c6,
|
||||
0x2daf4f0f6ff541ac, 0x6f5faee4c61f773f, 0xa84e8cd83c212c8a, 0xeabe6d3395cb1a19,
|
||||
0x90c79d3fedd3f122, 0xd2377cd44439c7b1, 0x15265ee8be079c04, 0x57d6bf0317edaa97,
|
||||
0xd9f4fb7ae3911dfd, 0x9b041a914a7b2b6e, 0x5c1538adb04570db, 0x1ee5d94619af4648,
|
||||
0x02a151b5f156289c, 0x4051b05e58bc1e0f, 0x87409262a28245ba, 0xc5b073890b687329,
|
||||
0x4b9237f0ff14c443, 0x0962d61b56fef2d0, 0xce73f427acc0a965, 0x8c8315cc052a9ff6,
|
||||
0x3a80143f5cf17f13, 0x7870f5d4f51b4980, 0xbf61d7e80f251235, 0xfd913603a6cf24a6,
|
||||
0x73b3727a52b393cc, 0x31439391fb59a55f, 0xf652b1ad0167feea, 0xb4a25046a88dc879,
|
||||
0xa8e6d8b54074a6ad, 0xea16395ee99e903e, 0x2d071b6213a0cb8b, 0x6ff7fa89ba4afd18,
|
||||
0xe1d5bef04e364a72, 0xa3255f1be7dc7ce1, 0x64347d271de22754, 0x26c49cccb40811c7,
|
||||
0x5cbd6cc0cc10fafc, 0x1e4d8d2b65facc6f, 0xd95caf179fc497da, 0x9bac4efc362ea149,
|
||||
0x158e0a85c2521623, 0x577eeb6e6bb820b0, 0x906fc95291867b05, 0xd29f28b9386c4d96,
|
||||
0xcedba04ad0952342, 0x8c2b41a1797f15d1, 0x4b3a639d83414e64, 0x09ca82762aab78f7,
|
||||
0x87e8c60fded7cf9d, 0xc51827e4773df90e, 0x020905d88d03a2bb, 0x40f9e43324e99428,
|
||||
0x2cffe7d5975e55e2, 0x6e0f063e3eb46371, 0xa91e2402c48a38c4, 0xebeec5e96d600e57,
|
||||
0x65cc8190991cb93d, 0x273c607b30f68fae, 0xe02d4247cac8d41b, 0xa2dda3ac6322e288,
|
||||
0xbe992b5f8bdb8c5c, 0xfc69cab42231bacf, 0x3b78e888d80fe17a, 0x7988096371e5d7e9,
|
||||
0xf7aa4d1a85996083, 0xb55aacf12c735610, 0x724b8ecdd64d0da5, 0x30bb6f267fa73b36,
|
||||
0x4ac29f2a07bfd00d, 0x08327ec1ae55e69e, 0xcf235cfd546bbd2b, 0x8dd3bd16fd818bb8,
|
||||
0x03f1f96f09fd3cd2, 0x41011884a0170a41, 0x86103ab85a2951f4, 0xc4e0db53f3c36767,
|
||||
0xd8a453a01b3a09b3, 0x9a54b24bb2d03f20, 0x5d45907748ee6495, 0x1fb5719ce1045206,
|
||||
0x919735e51578e56c, 0xd367d40ebc92d3ff, 0x1476f63246ac884a, 0x568617d9ef46bed9,
|
||||
0xe085162ab69d5e3c, 0xa275f7c11f7768af, 0x6564d5fde549331a, 0x279434164ca30589,
|
||||
0xa9b6706fb8dfb2e3, 0xeb46918411358470, 0x2c57b3b8eb0bdfc5, 0x6ea7525342e1e956,
|
||||
0x72e3daa0aa188782, 0x30133b4b03f2b111, 0xf7021977f9cceaa4, 0xb5f2f89c5026dc37,
|
||||
0x3bd0bce5a45a6b5d, 0x79205d0e0db05dce, 0xbe317f32f78e067b, 0xfcc19ed95e6430e8,
|
||||
0x86b86ed5267cdbd3, 0xc4488f3e8f96ed40, 0x0359ad0275a8b6f5, 0x41a94ce9dc428066,
|
||||
0xcf8b0890283e370c, 0x8d7be97b81d4019f, 0x4a6acb477bea5a2a, 0x089a2aacd2006cb9,
|
||||
0x14dea25f3af9026d, 0x562e43b4931334fe, 0x913f6188692d6f4b, 0xd3cf8063c0c759d8,
|
||||
0x5dedc41a34bbeeb2, 0x1f1d25f19d51d821, 0xd80c07cd676f8394, 0x9afce626ce85b507,
|
||||
0x0000000000000000, 0x42f0e1eba9ea3693, 0x85e1c3d753d46d26, 0xc711223cfa3e5bb5,
|
||||
0x493366450e42ecdf, 0x0bc387aea7a8da4c, 0xccd2a5925d9681f9, 0x8e224479f47cb76a,
|
||||
0x9266cc8a1c85d9be, 0xd0962d61b56fef2d, 0x17870f5d4f51b498, 0x5577eeb6e6bb820b,
|
||||
0xdb55aacf12c73561, 0x99a54b24bb2d03f2, 0x5eb4691841135847, 0x1c4488f3e8f96ed4,
|
||||
0x663d78ff90e185ef, 0x24cd9914390bb37c, 0xe3dcbb28c335e8c9, 0xa12c5ac36adfde5a,
|
||||
0x2f0e1eba9ea36930, 0x6dfeff5137495fa3, 0xaaefdd6dcd770416, 0xe81f3c86649d3285,
|
||||
0xf45bb4758c645c51, 0xb6ab559e258e6ac2, 0x71ba77a2dfb03177, 0x334a9649765a07e4,
|
||||
0xbd68d2308226b08e, 0xff9833db2bcc861d, 0x388911e7d1f2dda8, 0x7a79f00c7818eb3b,
|
||||
0xcc7af1ff21c30bde, 0x8e8a101488293d4d, 0x499b3228721766f8, 0x0b6bd3c3dbfd506b,
|
||||
0x854997ba2f81e701, 0xc7b97651866bd192, 0x00a8546d7c558a27, 0x4258b586d5bfbcb4,
|
||||
0x5e1c3d753d46d260, 0x1cecdc9e94ace4f3, 0xdbfdfea26e92bf46, 0x990d1f49c77889d5,
|
||||
0x172f5b3033043ebf, 0x55dfbadb9aee082c, 0x92ce98e760d05399, 0xd03e790cc93a650a,
|
||||
0xaa478900b1228e31, 0xe8b768eb18c8b8a2, 0x2fa64ad7e2f6e317, 0x6d56ab3c4b1cd584,
|
||||
0xe374ef45bf6062ee, 0xa1840eae168a547d, 0x66952c92ecb40fc8, 0x2465cd79455e395b,
|
||||
0x3821458aada7578f, 0x7ad1a461044d611c, 0xbdc0865dfe733aa9, 0xff3067b657990c3a,
|
||||
0x711223cfa3e5bb50, 0x33e2c2240a0f8dc3, 0xf4f3e018f031d676, 0xb60301f359dbe0e5,
|
||||
0xda050215ea6c212f, 0x98f5e3fe438617bc, 0x5fe4c1c2b9b84c09, 0x1d14202910527a9a,
|
||||
0x93366450e42ecdf0, 0xd1c685bb4dc4fb63, 0x16d7a787b7faa0d6, 0x5427466c1e109645,
|
||||
0x4863ce9ff6e9f891, 0x0a932f745f03ce02, 0xcd820d48a53d95b7, 0x8f72eca30cd7a324,
|
||||
0x0150a8daf8ab144e, 0x43a04931514122dd, 0x84b16b0dab7f7968, 0xc6418ae602954ffb,
|
||||
0xbc387aea7a8da4c0, 0xfec89b01d3679253, 0x39d9b93d2959c9e6, 0x7b2958d680b3ff75,
|
||||
0xf50b1caf74cf481f, 0xb7fbfd44dd257e8c, 0x70eadf78271b2539, 0x321a3e938ef113aa,
|
||||
0x2e5eb66066087d7e, 0x6cae578bcfe24bed, 0xabbf75b735dc1058, 0xe94f945c9c3626cb,
|
||||
0x676dd025684a91a1, 0x259d31cec1a0a732, 0xe28c13f23b9efc87, 0xa07cf2199274ca14,
|
||||
0x167ff3eacbaf2af1, 0x548f120162451c62, 0x939e303d987b47d7, 0xd16ed1d631917144,
|
||||
0x5f4c95afc5edc62e, 0x1dbc74446c07f0bd, 0xdaad56789639ab08, 0x985db7933fd39d9b,
|
||||
0x84193f60d72af34f, 0xc6e9de8b7ec0c5dc, 0x01f8fcb784fe9e69, 0x43081d5c2d14a8fa,
|
||||
0xcd2a5925d9681f90, 0x8fdab8ce70822903, 0x48cb9af28abc72b6, 0x0a3b7b1923564425,
|
||||
0x70428b155b4eaf1e, 0x32b26afef2a4998d, 0xf5a348c2089ac238, 0xb753a929a170f4ab,
|
||||
0x3971ed50550c43c1, 0x7b810cbbfce67552, 0xbc902e8706d82ee7, 0xfe60cf6caf321874,
|
||||
0xe224479f47cb76a0, 0xa0d4a674ee214033, 0x67c58448141f1b86, 0x253565a3bdf52d15,
|
||||
0xab1721da49899a7f, 0xe9e7c031e063acec, 0x2ef6e20d1a5df759, 0x6c0603e6b3b7c1ca,
|
||||
0xf6fae5c07d3274cd, 0xb40a042bd4d8425e, 0x731b26172ee619eb, 0x31ebc7fc870c2f78,
|
||||
0xbfc9838573709812, 0xfd39626eda9aae81, 0x3a28405220a4f534, 0x78d8a1b9894ec3a7,
|
||||
0x649c294a61b7ad73, 0x266cc8a1c85d9be0, 0xe17dea9d3263c055, 0xa38d0b769b89f6c6,
|
||||
0x2daf4f0f6ff541ac, 0x6f5faee4c61f773f, 0xa84e8cd83c212c8a, 0xeabe6d3395cb1a19,
|
||||
0x90c79d3fedd3f122, 0xd2377cd44439c7b1, 0x15265ee8be079c04, 0x57d6bf0317edaa97,
|
||||
0xd9f4fb7ae3911dfd, 0x9b041a914a7b2b6e, 0x5c1538adb04570db, 0x1ee5d94619af4648,
|
||||
0x02a151b5f156289c, 0x4051b05e58bc1e0f, 0x87409262a28245ba, 0xc5b073890b687329,
|
||||
0x4b9237f0ff14c443, 0x0962d61b56fef2d0, 0xce73f427acc0a965, 0x8c8315cc052a9ff6,
|
||||
0x3a80143f5cf17f13, 0x7870f5d4f51b4980, 0xbf61d7e80f251235, 0xfd913603a6cf24a6,
|
||||
0x73b3727a52b393cc, 0x31439391fb59a55f, 0xf652b1ad0167feea, 0xb4a25046a88dc879,
|
||||
0xa8e6d8b54074a6ad, 0xea16395ee99e903e, 0x2d071b6213a0cb8b, 0x6ff7fa89ba4afd18,
|
||||
0xe1d5bef04e364a72, 0xa3255f1be7dc7ce1, 0x64347d271de22754, 0x26c49cccb40811c7,
|
||||
0x5cbd6cc0cc10fafc, 0x1e4d8d2b65facc6f, 0xd95caf179fc497da, 0x9bac4efc362ea149,
|
||||
0x158e0a85c2521623, 0x577eeb6e6bb820b0, 0x906fc95291867b05, 0xd29f28b9386c4d96,
|
||||
0xcedba04ad0952342, 0x8c2b41a1797f15d1, 0x4b3a639d83414e64, 0x09ca82762aab78f7,
|
||||
0x87e8c60fded7cf9d, 0xc51827e4773df90e, 0x020905d88d03a2bb, 0x40f9e43324e99428,
|
||||
0x2cffe7d5975e55e2, 0x6e0f063e3eb46371, 0xa91e2402c48a38c4, 0xebeec5e96d600e57,
|
||||
0x65cc8190991cb93d, 0x273c607b30f68fae, 0xe02d4247cac8d41b, 0xa2dda3ac6322e288,
|
||||
0xbe992b5f8bdb8c5c, 0xfc69cab42231bacf, 0x3b78e888d80fe17a, 0x7988096371e5d7e9,
|
||||
0xf7aa4d1a85996083, 0xb55aacf12c735610, 0x724b8ecdd64d0da5, 0x30bb6f267fa73b36,
|
||||
0x4ac29f2a07bfd00d, 0x08327ec1ae55e69e, 0xcf235cfd546bbd2b, 0x8dd3bd16fd818bb8,
|
||||
0x03f1f96f09fd3cd2, 0x41011884a0170a41, 0x86103ab85a2951f4, 0xc4e0db53f3c36767,
|
||||
0xd8a453a01b3a09b3, 0x9a54b24bb2d03f20, 0x5d45907748ee6495, 0x1fb5719ce1045206,
|
||||
0x919735e51578e56c, 0xd367d40ebc92d3ff, 0x1476f63246ac884a, 0x568617d9ef46bed9,
|
||||
0xe085162ab69d5e3c, 0xa275f7c11f7768af, 0x6564d5fde549331a, 0x279434164ca30589,
|
||||
0xa9b6706fb8dfb2e3, 0xeb46918411358470, 0x2c57b3b8eb0bdfc5, 0x6ea7525342e1e956,
|
||||
0x72e3daa0aa188782, 0x30133b4b03f2b111, 0xf7021977f9cceaa4, 0xb5f2f89c5026dc37,
|
||||
0x3bd0bce5a45a6b5d, 0x79205d0e0db05dce, 0xbe317f32f78e067b, 0xfcc19ed95e6430e8,
|
||||
0x86b86ed5267cdbd3, 0xc4488f3e8f96ed40, 0x0359ad0275a8b6f5, 0x41a94ce9dc428066,
|
||||
0xcf8b0890283e370c, 0x8d7be97b81d4019f, 0x4a6acb477bea5a2a, 0x089a2aacd2006cb9,
|
||||
0x14dea25f3af9026d, 0x562e43b4931334fe, 0x913f6188692d6f4b, 0xd3cf8063c0c759d8,
|
||||
0x5dedc41a34bbeeb2, 0x1f1d25f19d51d821, 0xd80c07cd676f8394, 0x9afce626ce85b507,
|
||||
};
|
||||
|
||||
@@ -3,39 +3,39 @@
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::hash::fnv32a;
|
||||
|
||||
def Fnv32a = distinct uint;
|
||||
distinct Fnv32a = uint;
|
||||
|
||||
const FNV32A_START @private = 0x811c9dc5;
|
||||
const FNV32A_MUL @private = 0x01000193;
|
||||
|
||||
macro void @update(uint &h, char x) @private => h = (h * FNV32A_MUL) ^ x;
|
||||
macro void @update(uint* &h, char x) @private => *h = (*h * FNV32A_MUL) ^ x;
|
||||
|
||||
fn void Fnv32a.init(Fnv32a* this)
|
||||
fn void Fnv32a.init(&self)
|
||||
{
|
||||
*this = FNV32A_START;
|
||||
*self = FNV32A_START;
|
||||
}
|
||||
|
||||
fn void Fnv32a.update(Fnv32a* this, char[] data)
|
||||
fn void Fnv32a.update(&self, char[] data)
|
||||
{
|
||||
uint h = (uint)*this;
|
||||
uint h = (uint)*self;
|
||||
foreach (char x : data)
|
||||
{
|
||||
@update(h, x);
|
||||
}
|
||||
*this = (Fnv32a)h;
|
||||
{
|
||||
@update(h, x);
|
||||
}
|
||||
*self = (Fnv32a)h;
|
||||
}
|
||||
|
||||
macro void Fnv32a.update_char(Fnv32a* this, char c)
|
||||
macro void Fnv32a.update_char(&self, char c)
|
||||
{
|
||||
@update(*this, x);
|
||||
@update(*self, x);
|
||||
}
|
||||
|
||||
fn uint encode(char[] data)
|
||||
{
|
||||
uint h = FNV32A_START;
|
||||
foreach (char x : data)
|
||||
{
|
||||
{
|
||||
@update(h, x);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
41
lib/std/hash/fnv64a.c3
Normal file
41
lib/std/hash/fnv64a.c3
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2021 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::hash::fnv64a;
|
||||
|
||||
distinct Fnv64a = ulong;
|
||||
|
||||
const FNV64A_START @private = 0xcbf29ce484222325;
|
||||
const FNV64A_MUL @private = 0x00000100000001b3;
|
||||
|
||||
macro void @update(ulong* &h, char x) @private => *h = (*h * FNV64A_MUL) ^ x;
|
||||
|
||||
fn void Fnv64a.init(&self)
|
||||
{
|
||||
*self = FNV64A_START;
|
||||
}
|
||||
|
||||
fn void Fnv64a.update(&self, char[] data)
|
||||
{
|
||||
ulong h = (ulong)*self;
|
||||
foreach (char x : data)
|
||||
{
|
||||
@update(h, x);
|
||||
}
|
||||
*self = (Fnv64a)h;
|
||||
}
|
||||
|
||||
macro void Fnv64a.update_char(&self, char c)
|
||||
{
|
||||
@update(*self, x);
|
||||
}
|
||||
|
||||
fn ulong encode(char[] data)
|
||||
{
|
||||
ulong h = FNV64A_START;
|
||||
foreach (char x : data)
|
||||
{
|
||||
@update(h, x);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
@@ -14,70 +14,69 @@ struct Sha1
|
||||
char[64] buffer;
|
||||
}
|
||||
|
||||
fn void Sha1.init(Sha1* this)
|
||||
fn void Sha1.init(&self)
|
||||
{
|
||||
// SHA1 initialization constants
|
||||
*this = {
|
||||
.state = {
|
||||
0x67452301,
|
||||
0xEFCDAB89,
|
||||
0x98BADCFE,
|
||||
0x10325476,
|
||||
0xC3D2E1F0
|
||||
}
|
||||
// SHA1 initialization constants
|
||||
*self = {
|
||||
.state = {
|
||||
0x67452301,
|
||||
0xEFCDAB89,
|
||||
0x98BADCFE,
|
||||
0x10325476,
|
||||
0xC3D2E1F0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] this
|
||||
* @param [in] data
|
||||
* @require data.len <= uint.max
|
||||
**/
|
||||
fn void Sha1.update(Sha1* this, char[] data)
|
||||
fn void Sha1.update(&self, char[] data)
|
||||
{
|
||||
uint j = this.count[0];
|
||||
uint j = self.count[0];
|
||||
uint len = data.len;
|
||||
if ((this.count[0] += len << 3) < j) this.count[1]++;
|
||||
this.count[1] += len >> 29;
|
||||
if ((self.count[0] += len << 3) < j) self.count[1]++;
|
||||
self.count[1] += len >> 29;
|
||||
j = (j >> 3) & 63;
|
||||
uint i;
|
||||
if (j + len > 63)
|
||||
{
|
||||
i = 64 - j;
|
||||
this.buffer[j..] = data[:i];
|
||||
sha1_transform(&this.state, &this.buffer);
|
||||
self.buffer[j..] = data[:i];
|
||||
sha1_transform(&self.state, &self.buffer);
|
||||
for (; i + 63 < len; i += 64)
|
||||
{
|
||||
sha1_transform(&this.state, &data[i]);
|
||||
sha1_transform(&self.state, &data[i]);
|
||||
}
|
||||
j = 0;
|
||||
}
|
||||
this.buffer[j:len - i] = data[i..];
|
||||
self.buffer[j:len - i] = data[i..];
|
||||
}
|
||||
|
||||
|
||||
fn char[20] Sha1.final(Sha1* this)
|
||||
fn char[20] Sha1.final(&self)
|
||||
{
|
||||
char[8] finalcount;
|
||||
for (uint i = 0; i < 8; i++)
|
||||
{
|
||||
finalcount[i] = (char)((this.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
|
||||
finalcount[i] = (char)((self.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
|
||||
}
|
||||
this.update(char[] { 0o200 });
|
||||
while ((this.count[0] & 504) != 448)
|
||||
self.update(char[] { 0o200 });
|
||||
while ((self.count[0] & 504) != 448)
|
||||
{
|
||||
this.update(char[] { 0 });
|
||||
self.update(char[] { 0 });
|
||||
}
|
||||
|
||||
this.update(&finalcount);
|
||||
self.update(&finalcount);
|
||||
char[20] digest;
|
||||
for (uint i = 0; i < 20; i++)
|
||||
{
|
||||
digest[i] = (char)((this.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF);
|
||||
digest[i] = (char)((self.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
// Clear mem
|
||||
mem::clear(this, Sha1.sizeof);
|
||||
mem::clear(self, Sha1.sizeof);
|
||||
finalcount = {};
|
||||
return digest;
|
||||
}
|
||||
@@ -96,42 +95,47 @@ macro @blk(&block, i) @local
|
||||
|
||||
macro @blk0(&block, i) @local
|
||||
{
|
||||
$if env::BIG_ENDIAN:
|
||||
return block.l[i];
|
||||
$else
|
||||
return block.l[i] = (block.l[i].rotl(24) & 0xFF00FF00)
|
||||
| (block.l[i].rotl(8) & 0x00FF00FF);
|
||||
$endif
|
||||
$if env::BIG_ENDIAN:
|
||||
return block.l[i];
|
||||
$else
|
||||
return block.l[i] = (block.l[i].rotl(24) & 0xFF00FF00)
|
||||
| (block.l[i].rotl(8) & 0x00FF00FF);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro @r0(&block, v, &w, x, y, &z, i) @local
|
||||
macro @r0(&block, v, &wref, x, y, &z, i) @local
|
||||
{
|
||||
z += ((w & (x ^ y)) ^ y) + @blk0(block, i) + 0x5A827999 + v.rotl(5);
|
||||
w = w.rotl(30);
|
||||
var w = *wref;
|
||||
*z += ((w & (x ^ y)) ^ y) + @blk0(*block, i) + 0x5A827999 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro @r1(&block, v, &w, x, y, &z, i) @local
|
||||
macro @r1(&block, v, &wref, x, y, &z, i) @local
|
||||
{
|
||||
z += ((w & (x ^ y)) ^ y) + @blk(block, i) + 0x5A827999 + v.rotl(5);
|
||||
w = w.rotl(30);
|
||||
var w = *wref;
|
||||
*z += ((w & (x ^ y)) ^ y) + @blk(*block, i) + 0x5A827999 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro @r2(&block, v, &w, x, y, &z, i) @local
|
||||
macro @r2(&block, v, &wref, x, y, &z, i) @local
|
||||
{
|
||||
z += (w ^ x ^ y) + @blk(block, i) + 0x6ED9EBA1 + v.rotl(5);
|
||||
w = w.rotl(30);
|
||||
var w = *wref;
|
||||
*z += (w ^ x ^ y) + @blk(*block, i) + 0x6ED9EBA1 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro @r3(&block, v, &w, x, y, &z, i) @local
|
||||
macro @r3(&block, v, &wref, x, y, &z, i) @local
|
||||
{
|
||||
z += (((w | x) &y) | (w & x)) + @blk(block, i) + 0x8F1BBCDC + v.rotl(5);
|
||||
w = w.rotl(30);
|
||||
var w = *wref;
|
||||
*z += (((w | x) & y) | (w & x)) + @blk(*block, i) + 0x8F1BBCDC + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro @r4(&block, v, &w, x, y, &z, i) @local
|
||||
macro @r4(&block, v, &wref, x, y, &z, i) @local
|
||||
{
|
||||
z += (w ^ x ^ y) + @blk(block, i) + 0xCA62C1D6 + v.rotl(5);
|
||||
w = w.rotl(30);
|
||||
var w = *wref;
|
||||
*z += (w ^ x ^ y) + @blk(*block, i) + 0xCA62C1D6 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
85
lib/std/io/bits.c3
Normal file
85
lib/std/io/bits.c3
Normal file
@@ -0,0 +1,85 @@
|
||||
module std::io;
|
||||
|
||||
struct BitReader
|
||||
{
|
||||
InStream* reader;
|
||||
uint bits;
|
||||
uint len;
|
||||
}
|
||||
|
||||
fn void BitReader.init(&self, InStream* byte_reader)
|
||||
{
|
||||
*self = { .reader = byte_reader };
|
||||
}
|
||||
|
||||
fn void BitReader.clear(&self) @inline
|
||||
{
|
||||
self.len = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require nbits <= 8
|
||||
* @require self.len + nbits <= uint.sizeof * 8
|
||||
**/
|
||||
fn char! BitReader.read_bits(&self, uint nbits)
|
||||
{
|
||||
uint bits = self.bits;
|
||||
if (self.len < nbits)
|
||||
{
|
||||
// New bits are pushed right.
|
||||
char c = self.reader.read_byte()!;
|
||||
bits <<= 8;
|
||||
bits |= c;
|
||||
self.bits = bits;
|
||||
self.len += 8;
|
||||
}
|
||||
self.len -= nbits;
|
||||
uint mask = (1 << nbits) - 1;
|
||||
return (char)((bits >> self.len) & mask);
|
||||
}
|
||||
|
||||
struct BitWriter
|
||||
{
|
||||
OutStream* writer;
|
||||
uint bits;
|
||||
uint len;
|
||||
}
|
||||
|
||||
fn void BitWriter.init(&self, OutStream* byte_writer)
|
||||
{
|
||||
*self = { .writer = byte_writer };
|
||||
}
|
||||
|
||||
fn void! BitWriter.flush(&self)
|
||||
{
|
||||
if (self.len == 0) return;
|
||||
uint bits = self.bits << (32 - self.len);
|
||||
uint n = (self.len + 7) / 8;
|
||||
char[4] buffer;
|
||||
bitorder::write(bits, &buffer, UIntBE);
|
||||
io::write_all(self.writer, buffer[:n])!;
|
||||
self.len = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require nbits <= 8
|
||||
**/
|
||||
fn void! BitWriter.write_bits(&self, uint bits, uint nbits)
|
||||
{
|
||||
if (nbits == 0) return;
|
||||
uint n = self.len + nbits;
|
||||
uint to_write = n / 8;
|
||||
uint left = n % 8;
|
||||
if (to_write > 0)
|
||||
{
|
||||
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])!;
|
||||
}
|
||||
self.bits <<= left;
|
||||
self.bits |= bits & ((1 << left) - 1);
|
||||
self.len = left;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module std::io::dir;
|
||||
import std::io::os;
|
||||
|
||||
147
lib/std/io/file.c3
Normal file
147
lib/std/io/file.c3
Normal file
@@ -0,0 +1,147 @@
|
||||
module std::io;
|
||||
import libc;
|
||||
|
||||
struct File (InStream, OutStream)
|
||||
{
|
||||
CFile file;
|
||||
}
|
||||
|
||||
module std::io::file;
|
||||
import libc;
|
||||
|
||||
fn File! open(String filename, String mode)
|
||||
{
|
||||
return from_handle(os::native_fopen(filename, mode));
|
||||
}
|
||||
|
||||
fn File! open_path(Path path, String mode)
|
||||
{
|
||||
return from_handle(os::native_fopen(path.str_view(), mode));
|
||||
}
|
||||
|
||||
fn File from_handle(CFile file)
|
||||
{
|
||||
return { .file = file };
|
||||
}
|
||||
|
||||
fn bool is_file(String path)
|
||||
{
|
||||
return os::native_is_file(path);
|
||||
}
|
||||
|
||||
fn usz! get_size(String path)
|
||||
{
|
||||
return os::native_file_size(path);
|
||||
}
|
||||
|
||||
fn void! delete(String filename) => os::native_remove(filename) @inline;
|
||||
|
||||
|
||||
/**
|
||||
* @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
|
||||
**/
|
||||
fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
|
||||
{
|
||||
os::native_fseek(self.file, offset, seek_mode)!;
|
||||
return os::native_ftell(self.file);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Implement later
|
||||
/**
|
||||
* @require self.file == null
|
||||
**/
|
||||
fn void! File.memopen(File* file, char[] data, String mode)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
file.file = libc::memopen(data.ptr, data.len, mode.zstr_tcopy(), file.file);
|
||||
// TODO errors
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @require self.file != null
|
||||
*/
|
||||
fn void! File.write_byte(&self, char c) @dynamic
|
||||
{
|
||||
if (!libc::fputc(c, self.file)) return IoError.EOF?;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] self
|
||||
*/
|
||||
fn void! File.close(&self) @inline @dynamic
|
||||
{
|
||||
if (self.file && libc::fclose(self.file))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ECONNRESET:
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID?;
|
||||
case errno::EINTR: return IoError.INTERRUPTED?;
|
||||
case errno::EDQUOT:
|
||||
case errno::EFAULT:
|
||||
case errno::EAGAIN:
|
||||
case errno::EFBIG:
|
||||
case errno::ENETDOWN:
|
||||
case errno::ENETUNREACH:
|
||||
case errno::ENOSPC:
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE?;
|
||||
default: return IoError.UNKNOWN_ERROR?;
|
||||
}
|
||||
}
|
||||
self.file = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require self.file
|
||||
*/
|
||||
fn bool File.eof(&self) @inline
|
||||
{
|
||||
return libc::feof(self.file) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [in] buffer
|
||||
*/
|
||||
fn usz! File.read(&self, char[] buffer) @dynamic
|
||||
{
|
||||
return os::native_fread(self.file, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 char! File.read_byte(&self) @dynamic
|
||||
{
|
||||
int c = libc::fgetc(self.file);
|
||||
if (c == -1) return IoError.EOF?;
|
||||
return (char)c;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require self.file `File must be initialized`
|
||||
*/
|
||||
fn void! File.flush(&self) @dynamic
|
||||
{
|
||||
libc::fflush(self.file);
|
||||
}
|
||||
461
lib/std/io/formatter.c3
Normal file
461
lib/std/io/formatter.c3
Normal file
@@ -0,0 +1,461 @@
|
||||
module std::io;
|
||||
import std::collections::map;
|
||||
import libc;
|
||||
|
||||
const int PRINTF_NTOA_BUFFER_SIZE = 256;
|
||||
|
||||
interface Printable
|
||||
{
|
||||
fn String to_new_string(Allocator *allocator) @optional;
|
||||
fn usz! to_format(Formatter* formatter) @optional;
|
||||
}
|
||||
|
||||
fault PrintFault
|
||||
{
|
||||
BUFFER_EXCEEDED,
|
||||
INTERNAL_BUFFER_EXCEEDED,
|
||||
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;
|
||||
|
||||
|
||||
|
||||
fn usz! Formatter.printf(&self, String format, args...)
|
||||
{
|
||||
return self.vprintf(format, args) @inline;
|
||||
}
|
||||
|
||||
struct Formatter
|
||||
{
|
||||
void *data;
|
||||
OutputFn out_fn;
|
||||
struct
|
||||
{
|
||||
PrintFlags flags;
|
||||
uint width;
|
||||
uint prec;
|
||||
usz idx;
|
||||
}
|
||||
}
|
||||
|
||||
bitstruct PrintFlags : uint
|
||||
{
|
||||
bool zeropad;
|
||||
bool left;
|
||||
bool plus;
|
||||
bool space;
|
||||
bool hash;
|
||||
bool uppercase;
|
||||
bool precision;
|
||||
}
|
||||
|
||||
fn void Formatter.init(&self, OutputFn out_fn, void* data = null)
|
||||
{
|
||||
*self = { .data = data, .out_fn = out_fn};
|
||||
}
|
||||
|
||||
fn usz! Formatter.out(&self, char c) @private
|
||||
{
|
||||
self.out_fn(self.data, c)!;
|
||||
return 1;
|
||||
}
|
||||
|
||||
fn usz! Formatter.print_with_function(&self, Printable* arg)
|
||||
{
|
||||
if (&arg.to_format)
|
||||
{
|
||||
PrintFlags old = self.flags;
|
||||
uint old_width = self.width;
|
||||
uint old_prec = self.prec;
|
||||
defer
|
||||
{
|
||||
self.flags = old;
|
||||
self.width = old_width;
|
||||
self.prec = old_prec;
|
||||
}
|
||||
return arg.to_format(self);
|
||||
}
|
||||
if (&arg.to_new_string)
|
||||
{
|
||||
PrintFlags old = self.flags;
|
||||
uint old_width = self.width;
|
||||
uint old_prec = self.prec;
|
||||
defer
|
||||
{
|
||||
self.flags = old;
|
||||
self.width = old_width;
|
||||
self.prec = old_prec;
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
return self.out_substr(arg.to_new_string(mem::temp()));
|
||||
};
|
||||
}
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
|
||||
fn usz! Formatter.out_str(&self, any* arg) @private
|
||||
{
|
||||
switch (arg.type.kindof)
|
||||
{
|
||||
case TYPEID:
|
||||
return self.out_substr("typeid");
|
||||
case VOID:
|
||||
return self.out_substr("void");
|
||||
case ANYFAULT:
|
||||
case FAULT:
|
||||
return self.out_substr((*(anyfault*)arg.ptr).nameof);
|
||||
case ANY:
|
||||
return self.out_str(*(any**)arg);
|
||||
case OPTIONAL:
|
||||
unreachable();
|
||||
case SIGNED_INT:
|
||||
case UNSIGNED_INT:
|
||||
PrintFlags flags = self.flags;
|
||||
uint width = self.width;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.flags = {};
|
||||
self.width = 0;
|
||||
return self.ntoa_any(arg, 10);
|
||||
case FLOAT:
|
||||
PrintFlags flags = self.flags;
|
||||
uint width = self.width;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.flags = {};
|
||||
self.width = 0;
|
||||
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 (catch err = n)
|
||||
{
|
||||
case SearchResult.MISSING:
|
||||
break;
|
||||
default:
|
||||
return err?;
|
||||
}
|
||||
else
|
||||
{
|
||||
return n;
|
||||
}
|
||||
switch (arg.type.kindof)
|
||||
{
|
||||
case ENUM:
|
||||
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_substr("<struct>");
|
||||
case UNION:
|
||||
return self.out_substr("<union>");
|
||||
case BITSTRUCT:
|
||||
return self.out_substr("<bitstruct>");
|
||||
case FUNC:
|
||||
return self.out_substr("<function>");
|
||||
case DISTINCT:
|
||||
if (arg.type == ZString.typeid)
|
||||
{
|
||||
return self.out_substr(((ZString*)arg).str_view());
|
||||
}
|
||||
if (arg.type == DString.typeid)
|
||||
{
|
||||
return self.out_substr(((DString*)arg).str_view());
|
||||
}
|
||||
return self.out_str(arg.as_inner());
|
||||
case POINTER:
|
||||
PrintFlags flags = self.flags;
|
||||
uint width = self.width;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.flags = {};
|
||||
self.width = 0;
|
||||
return self.ntoa_any(arg, 16);
|
||||
case ARRAY:
|
||||
// this is SomeType[*] so grab the "SomeType"
|
||||
PrintFlags flags = self.flags;
|
||||
uint width = self.width;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.flags = {};
|
||||
self.width = 0;
|
||||
typeid inner = arg.type.inner;
|
||||
usz size = inner.sizeof;
|
||||
usz alen = arg.type.len;
|
||||
// Pretend this is a String
|
||||
void* ptr = (void*)arg.ptr;
|
||||
usz len = self.out('[')!;
|
||||
for (usz i = 0; i < alen; i++)
|
||||
{
|
||||
if (i != 0) len += self.out_substr(", ")!;
|
||||
len += self.out_str(any_make(ptr, inner))!;
|
||||
ptr += size;
|
||||
}
|
||||
len += self.out(']')!;
|
||||
return len;
|
||||
case VECTOR:
|
||||
PrintFlags flags = self.flags;
|
||||
uint width = self.width;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.flags = {};
|
||||
self.width = 0;
|
||||
// this is SomeType[*] so grab the "SomeType"
|
||||
typeid inner = arg.type.inner;
|
||||
usz size = inner.sizeof;
|
||||
usz vlen = arg.type.len;
|
||||
// Pretend this is a String
|
||||
void* ptr = (void*)arg.ptr;
|
||||
usz len = self.out_substr("[<")!;
|
||||
for (usz i = 0; i < vlen; i++)
|
||||
{
|
||||
if (i != 0) len += self.out_substr(", ")!;
|
||||
len += self.out_str(any_make(ptr, inner))!;
|
||||
ptr += size;
|
||||
}
|
||||
len += self.out_substr(">]")!;
|
||||
return len;
|
||||
case SUBARRAY:
|
||||
// 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;
|
||||
defer
|
||||
{
|
||||
self.flags = flags;
|
||||
self.width = width;
|
||||
}
|
||||
self.flags = {};
|
||||
self.width = 0;
|
||||
usz size = inner.sizeof;
|
||||
// Pretend this is a String
|
||||
String* temp = (void*)arg.ptr;
|
||||
void* ptr = (void*)temp.ptr;
|
||||
usz slen = temp.len;
|
||||
usz len = self.out('[')!;
|
||||
for (usz i = 0; i < slen; i++)
|
||||
{
|
||||
if (i != 0) len += self.out_substr(", ")!;
|
||||
len += self.out_str(any_make(ptr, inner))!;
|
||||
ptr += size;
|
||||
}
|
||||
len += self.out(']')!;
|
||||
return len;
|
||||
default:
|
||||
}
|
||||
return self.out_substr("Invalid type");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
fn void! out_null_fn(void* data @unused, char c @unused) @private
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn usz! Formatter.vprintf(&self, String format, any*[] anys)
|
||||
{
|
||||
if (!self.out_fn)
|
||||
{
|
||||
// use null output function
|
||||
self.out_fn = &out_null_fn;
|
||||
}
|
||||
usz total_len;
|
||||
usz format_len = format.len;
|
||||
usz variant_index = 0;
|
||||
for (usz i = 0; i < format_len; i++)
|
||||
{
|
||||
// format specifier? %[flags][width][.precision][length]
|
||||
char c = format[i];
|
||||
if (c != '%')
|
||||
{
|
||||
// no
|
||||
total_len += self.out(c)!;
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
if (i >= format_len) return PrintFault.INVALID_FORMAT_STRING?;
|
||||
c = format[i];
|
||||
if (c == '%')
|
||||
{
|
||||
total_len += self.out(c)!;
|
||||
continue;
|
||||
}
|
||||
// evaluate flags
|
||||
self.flags = {};
|
||||
while FLAG_EVAL: (true)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '0': self.flags.zeropad = true;
|
||||
case '-': self.flags.left = true;
|
||||
case '+': self.flags.plus = true;
|
||||
case ' ': self.flags.space = true;
|
||||
case '#': self.flags.hash = true;
|
||||
default: break FLAG_EVAL;
|
||||
}
|
||||
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)!;
|
||||
c = format[i];
|
||||
if (w < 0)
|
||||
{
|
||||
self.flags.left = true;
|
||||
w = -w;
|
||||
}
|
||||
self.width = w;
|
||||
// evaluate precision field
|
||||
self.prec = 0;
|
||||
if (c == '.')
|
||||
{
|
||||
self.flags.precision = true;
|
||||
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) return PrintFault.MISSING_ARG?;
|
||||
any* current = anys[variant_index++];
|
||||
switch (c)
|
||||
{
|
||||
case 'd':
|
||||
base = 10;
|
||||
self.flags.hash = false;
|
||||
case 'X' :
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'x' :
|
||||
base = 16;
|
||||
case 'O':
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'o' :
|
||||
base = 8;
|
||||
case 'B':
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'b' :
|
||||
base = 2;
|
||||
case 'A':
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'a':
|
||||
total_len += self.atoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'F' :
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'f':
|
||||
total_len += self.ftoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'E':
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'e':
|
||||
total_len += self.etoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'G':
|
||||
self.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'g':
|
||||
total_len += self.gtoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'c':
|
||||
total_len += self.out_char(current)!;
|
||||
continue;
|
||||
case 's':
|
||||
if (self.flags.left)
|
||||
{
|
||||
usz len = self.out_str(current)!;
|
||||
total_len += len;
|
||||
total_len += self.pad(' ', self.width, len)!;
|
||||
continue;
|
||||
}
|
||||
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':
|
||||
self.flags.zeropad = true;
|
||||
self.flags.hash = true;
|
||||
base = 16;
|
||||
default:
|
||||
return PrintFault.INVALID_FORMAT_STRING?;
|
||||
}
|
||||
if (base != 10)
|
||||
{
|
||||
self.flags.plus = false;
|
||||
self.flags.space = false;
|
||||
}
|
||||
// ignore '0' flag when precision is given
|
||||
if (self.flags.precision) self.flags.zeropad = false;
|
||||
|
||||
bool is_neg;
|
||||
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
|
||||
return total_len;
|
||||
}
|
||||
|
||||
|
||||
fn usz! Formatter.print(&self, String str)
|
||||
{
|
||||
if (!self.out_fn)
|
||||
{
|
||||
// use null output function
|
||||
self.out_fn = &out_null_fn;
|
||||
}
|
||||
foreach (c : str) self.out(c)!;
|
||||
return self.idx;
|
||||
}
|
||||
@@ -3,30 +3,25 @@ module std::io;
|
||||
const char[16] XDIGITS_H = "0123456789ABCDEF";
|
||||
const char[16] XDIGITS_L = "0123456789abcdef";
|
||||
|
||||
fn void! Formatter.left_adjust(Formatter* this, usz len) @local
|
||||
fn usz! Formatter.adjust(&self, usz len) @local
|
||||
{
|
||||
if (!this.flags.left) return;
|
||||
for (usz l = len; l < this.width; l++) this.out(' ')!;
|
||||
if (!self.flags.left) return 0;
|
||||
return self.pad(' ', self.width, len);
|
||||
}
|
||||
|
||||
fn void! Formatter.right_adjust(Formatter* this, usz len) @local
|
||||
fn uint128! int_from_any(any* arg, bool *is_neg) @private
|
||||
{
|
||||
if (this.flags.left) return;
|
||||
for (usz l = len; l < this.width; l++) this.out(' ')!;
|
||||
}
|
||||
|
||||
|
||||
fn uint128! int_from_any(any arg, bool *is_neg) @private
|
||||
{
|
||||
*is_neg = false;
|
||||
if (arg.type.kindof == TypeKind.POINTER)
|
||||
switch (arg.type.kindof)
|
||||
{
|
||||
return (uint128)(uptr)*(void**)arg.ptr;
|
||||
case TypeKind.POINTER:
|
||||
*is_neg = false;
|
||||
return (uint128)(uptr)*(void**)arg.ptr;
|
||||
case TypeKind.DISTINCT:
|
||||
case TypeKind.ENUM:
|
||||
return int_from_any(arg.as_inner(), is_neg);
|
||||
default:
|
||||
}
|
||||
if (arg.type.kindof == TypeKind.DISTINCT)
|
||||
{
|
||||
return int_from_any(any { arg.ptr, arg.type.inner }, is_neg);
|
||||
}
|
||||
*is_neg = false;
|
||||
switch (arg)
|
||||
{
|
||||
case bool:
|
||||
@@ -44,8 +39,8 @@ fn uint128! int_from_any(any arg, bool *is_neg) @private
|
||||
long val = *arg;
|
||||
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
|
||||
case int128:
|
||||
int128 val = *arg;
|
||||
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
|
||||
int128 val = *arg;
|
||||
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
|
||||
case char:
|
||||
return *arg;
|
||||
case ushort:
|
||||
@@ -67,7 +62,7 @@ fn uint128! int_from_any(any arg, bool *is_neg) @private
|
||||
}
|
||||
}
|
||||
|
||||
fn FloatType! float_from_any(any arg) @private
|
||||
fn FloatType! float_from_any(any* arg) @private
|
||||
{
|
||||
$if env::F128_SUPPORT:
|
||||
if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr);
|
||||
@@ -77,7 +72,7 @@ fn FloatType! float_from_any(any arg) @private
|
||||
$endif
|
||||
if (arg.type.kindof == TypeKind.DISTINCT)
|
||||
{
|
||||
return float_from_any(any { arg.ptr, arg.type.inner });
|
||||
return float_from_any(arg.as_inner());
|
||||
}
|
||||
switch (arg)
|
||||
{
|
||||
@@ -92,7 +87,7 @@ fn FloatType! float_from_any(any arg) @private
|
||||
case long:
|
||||
return *arg;
|
||||
case int128:
|
||||
return *arg;
|
||||
return *arg;
|
||||
case char:
|
||||
return *arg;
|
||||
case ushort:
|
||||
@@ -102,7 +97,7 @@ fn FloatType! float_from_any(any arg) @private
|
||||
case ulong:
|
||||
return *arg;
|
||||
case uint128:
|
||||
return *arg;
|
||||
return *arg;
|
||||
case float:
|
||||
return (FloatType)*arg;
|
||||
case double:
|
||||
@@ -126,22 +121,21 @@ fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
|
||||
uint i = 0;
|
||||
usz len = *len_ptr;
|
||||
while (len < maxlen)
|
||||
{
|
||||
char c = buf[len];
|
||||
if (c < '0' || c > '9') break;
|
||||
i = i * 10 + c - '0';
|
||||
len++;
|
||||
}
|
||||
*len_ptr = len;
|
||||
return i;
|
||||
{
|
||||
char c = buf[len];
|
||||
if (!c.is_digit()) break;
|
||||
i = i * 10 + c - '0';
|
||||
len++;
|
||||
}
|
||||
*len_ptr = len;
|
||||
return i;
|
||||
}
|
||||
|
||||
fn void! Formatter.out_substr(Formatter *this, String str) @private
|
||||
fn usz! Formatter.out_substr(&self, String str) @private
|
||||
{
|
||||
usz l = conv::utf8_codepoints(str);
|
||||
uint prec = this.prec;
|
||||
if (this.flags.precision && l < prec) l = prec;
|
||||
this.right_adjust(' ')!;
|
||||
uint prec = self.prec;
|
||||
if (self.flags.precision && l < prec) l = prec;
|
||||
usz index = 0;
|
||||
usz chars = str.len;
|
||||
char* ptr = str.ptr;
|
||||
@@ -149,16 +143,18 @@ fn void! Formatter.out_substr(Formatter *this, String str) @private
|
||||
{
|
||||
char c = ptr[index];
|
||||
// Break if we have precision set and we ran out...
|
||||
if (c & 0xC0 != 0x80 && this.flags.precision && !prec--) break;
|
||||
this.out(c)!;
|
||||
index++;
|
||||
if (c & 0xC0 != 0x80 && self.flags.precision && !prec--) break;
|
||||
self.out(c)!;
|
||||
index++;
|
||||
}
|
||||
return this.left_adjust(l);
|
||||
return index;
|
||||
}
|
||||
|
||||
fn void! Formatter.pad(Formatter* this, char c, isz width, isz len) @inline
|
||||
fn usz! Formatter.pad(&self, char c, isz width, isz len) @inline
|
||||
{
|
||||
for (isz i = len; i < width; i++) this.out(c)!;
|
||||
isz delta = width - len;
|
||||
for (isz i = 0; i < delta; i++) self.out(c)!;
|
||||
return max(0, delta);
|
||||
}
|
||||
|
||||
fn char* fmt_u(uint128 x, char* s)
|
||||
@@ -168,9 +164,10 @@ fn char* fmt_u(uint128 x, char* s)
|
||||
return s;
|
||||
}
|
||||
|
||||
fn void! Formatter.out_chars(Formatter* this, char[] s)
|
||||
fn usz! Formatter.out_chars(&self, char[] s)
|
||||
{
|
||||
foreach (c : s) this.out(c)!;
|
||||
foreach (c : s) self.out(c)!;
|
||||
return s.len;
|
||||
}
|
||||
|
||||
enum FloatFormatting
|
||||
@@ -181,35 +178,37 @@ enum FloatFormatting
|
||||
HEX
|
||||
}
|
||||
|
||||
fn void! Formatter.etoa(Formatter* this, double y) => this.floatformat(EXPONENTIAL, y);
|
||||
fn void! Formatter.ftoa(Formatter* this, double y) => this.floatformat(FLOAT, y);
|
||||
fn void! Formatter.gtoa(Formatter* this, double y) => this.floatformat(ADAPTIVE, y);
|
||||
fn void! Formatter.atoa(Formatter* this, double y) => this.floatformat(HEX, y);
|
||||
fn usz! Formatter.etoa(&self, double y) => self.floatformat(EXPONENTIAL, y);
|
||||
fn usz! Formatter.ftoa(&self, double y) => self.floatformat(FLOAT, y);
|
||||
fn usz! Formatter.gtoa(&self, double y) => self.floatformat(ADAPTIVE, y);
|
||||
fn usz! Formatter.atoa(&self, double y) => self.floatformat(HEX, y);
|
||||
|
||||
fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, double y) @private
|
||||
fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @private
|
||||
{
|
||||
// This code is heavily based on musl's printf code
|
||||
const BUF_SIZE = (math::DOUBLE_MANT_DIG + 28) / 29 + 1
|
||||
+ (math::DOUBLE_MAX_EXP + math::DOUBLE_MANT_DIG + 28 + 8) / 9;
|
||||
uint[BUF_SIZE] big;
|
||||
bool is_neg = false;
|
||||
if (math::signbit(y))
|
||||
{
|
||||
is_neg = true;
|
||||
y = -y;
|
||||
}
|
||||
int pl = is_neg || this.flags.plus ? 1 : 0;
|
||||
// Print inf/nan
|
||||
bool is_neg = false;
|
||||
if (math::signbit(y))
|
||||
{
|
||||
is_neg = true;
|
||||
y = -y;
|
||||
}
|
||||
isz pl = is_neg || self.flags.plus ? 1 : 0;
|
||||
// Print inf/nan
|
||||
if (!math::is_finite(y))
|
||||
{
|
||||
usz len;
|
||||
// Add padding
|
||||
if (!this.flags.left) this.pad(' ', this.width, 3 + pl)!;
|
||||
String s = this.flags.uppercase ? "INF" : "inf";
|
||||
if (y != y) this.flags.uppercase ? "NAN" : "nan";
|
||||
if (pl) this.out(is_neg ? '-' : '+')!;
|
||||
this.out_chars(s)!;
|
||||
if (this.flags.left) this.pad(' ', this.width, 3 + pl)!;
|
||||
return;
|
||||
if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
|
||||
String s = self.flags.uppercase ? "INF" : "inf";
|
||||
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)!;
|
||||
return len;
|
||||
}
|
||||
// Rescale
|
||||
int e2;
|
||||
@@ -220,7 +219,7 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
char* ebuf = 12 + (char*)&ebuf0;
|
||||
char[9 + math::DOUBLE_MANT_DIG / 4] buf_array;
|
||||
char* buf = &buf_array;
|
||||
isz p = this.flags.precision ? this.prec : -1;
|
||||
isz p = self.flags.precision ? self.prec : -1;
|
||||
if (formatting == HEX)
|
||||
{
|
||||
double round = 8.0;
|
||||
@@ -229,50 +228,51 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
if (p > 0 && p < math::DOUBLE_MANT_DIG / 4 - 1)
|
||||
{
|
||||
int re = math::DOUBLE_MANT_DIG / 4 - 1 - (int)p;
|
||||
round *= 1 << (math::DOUBLE_MANT_DIG % 4);
|
||||
while (re--) round *= 16;
|
||||
if (is_neg)
|
||||
{
|
||||
y = -y;
|
||||
round *= 1 << (math::DOUBLE_MANT_DIG % 4);
|
||||
while (re--) round *= 16;
|
||||
if (is_neg)
|
||||
{
|
||||
y = -y;
|
||||
y -= round;
|
||||
y += round;
|
||||
y = -y;
|
||||
}
|
||||
else
|
||||
{
|
||||
y += round;
|
||||
y -= round;
|
||||
}
|
||||
y += round;
|
||||
y = -y;
|
||||
}
|
||||
else
|
||||
{
|
||||
y += round;
|
||||
y -= round;
|
||||
}
|
||||
}
|
||||
// Reverse print
|
||||
char* estr = fmt_u(e2 < 0 ? (int128)-e2 : (int128)e2, ebuf);
|
||||
if (estr == ebuf) *--estr = '0';
|
||||
*--estr = (e2 < 0 ? '-' : '+');
|
||||
*--estr = this.flags.uppercase ? 'P' : 'p';
|
||||
*--estr = self.flags.uppercase ? 'P' : 'p';
|
||||
char* s = buf;
|
||||
char* xdigits = this.flags.uppercase ? &XDIGITS_H : &XDIGITS_L;
|
||||
char* xdigits = self.flags.uppercase ? &XDIGITS_H : &XDIGITS_L;
|
||||
do
|
||||
{
|
||||
int x = (int)y;
|
||||
*s++ = xdigits[x];
|
||||
y = 16 * (y - x);
|
||||
if (s - buf == 1 && (y || p > 0 || this.flags.hash)) *s++ = '.';
|
||||
if (s - buf == 1 && (y || p > 0 || self.flags.hash)) *s++ = '.';
|
||||
} while (y);
|
||||
isz outlen = s - buf;
|
||||
isz explen = ebuf - estr;
|
||||
if (p > int.max - 2 - explen - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
usz len;
|
||||
usz l = p && outlen - 2 < p
|
||||
? p + 2 + explen
|
||||
: outlen + explen;
|
||||
if (!this.flags.left && !this.flags.zeropad) this.pad(' ', this.width, pl + l)!;
|
||||
if (is_neg || this.flags.plus) this.out(is_neg ? '-' : '+')!;
|
||||
this.out_chars(this.flags.uppercase ? "0X" : "0x")!;
|
||||
if (this.flags.zeropad) this.pad('0', this.width, pl + l)!;
|
||||
this.out_chars(buf[:outlen])!;
|
||||
this.pad('0', l - outlen - explen, 0)!;
|
||||
this.out_chars(estr[:explen])!;
|
||||
if (this.flags.left) this.pad(' ', this.width, pl + l)!;
|
||||
return;
|
||||
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
|
||||
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
|
||||
len += self.out_chars(self.flags.uppercase ? "0X" : "0x")!;
|
||||
if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
|
||||
len += self.out_chars(buf[:outlen])!;
|
||||
len += self.pad('0', l - outlen - explen, 0)!;
|
||||
len += self.out_chars(estr[:explen])!;
|
||||
if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;
|
||||
return len;
|
||||
}
|
||||
if (p < 0) p = 6;
|
||||
if (y)
|
||||
@@ -405,7 +405,7 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
formatting = EXPONENTIAL;
|
||||
p--;
|
||||
}
|
||||
if (!this.flags.hash)
|
||||
if (!self.flags.hash)
|
||||
{
|
||||
// Count trailing zeros in last place
|
||||
if (z > a && z[-1])
|
||||
@@ -427,8 +427,8 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
}
|
||||
}
|
||||
}
|
||||
if (p > int.max - 1 - (isz)(p || this.flags.hash)) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
int l = (int)(1 + p + (isz)(p || this.flags.hash));
|
||||
if (p > int.max - 1 - (isz)(p || self.flags.hash)) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
int l = (int)(1 + p + (isz)(p || self.flags.hash));
|
||||
char* estr @noinit;
|
||||
if (formatting == FLOAT)
|
||||
{
|
||||
@@ -440,14 +440,15 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
estr = fmt_u((uint128)(e < 0 ? -e : e), ebuf);
|
||||
while (ebuf - estr < 2) (--estr)[0] = '0';
|
||||
*--estr = (e < 0 ? '-' : '+');
|
||||
*--estr = this.flags.uppercase ? 'E' : 'e';
|
||||
*--estr = self.flags.uppercase ? 'E' : 'e';
|
||||
if (ebuf - estr > (isz)int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
l += (int)(ebuf - estr);
|
||||
}
|
||||
if (l > int.max - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
if (!this.flags.left && !this.flags.zeropad) this.pad(' ', this.width, pl + l)!;
|
||||
if (is_neg || this.flags.plus) this.out(is_neg ? '-' : '+')!;
|
||||
if (this.flags.zeropad) this.pad('0', this.width, pl + l)!;
|
||||
usz len;
|
||||
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
|
||||
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
|
||||
if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
|
||||
if (formatting == FLOAT)
|
||||
{
|
||||
if (a > r) a = r;
|
||||
@@ -462,57 +463,57 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
|
||||
case s == buf + 9:
|
||||
*--s = '0';
|
||||
}
|
||||
this.out_chars(s[:buf + 9 - s])!;
|
||||
len += self.out_chars(s[:buf + 9 - s])!;
|
||||
}
|
||||
if (p || this.flags.hash) this.out('.')!;
|
||||
if (p || self.flags.hash) len += self.out('.')!;
|
||||
for (; d < z && p > 0; d++, p -= 9)
|
||||
{
|
||||
char* s = fmt_u(*d, buf + 9);
|
||||
while (s > buf) *--s = '0';
|
||||
this.out_chars(s[:math::min((isz)9, p)])!;
|
||||
len += self.out_chars(s[:math::min((isz)9, p)])!;
|
||||
}
|
||||
this.pad('0', p + 9, 9)!;
|
||||
len += self.pad('0', p + 9, 9)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (z <= a) z = a + 1;
|
||||
for (uint* d = a; d < z && p >= 0; d++)
|
||||
{
|
||||
for (uint* d = a; d < z && p >= 0; d++)
|
||||
{
|
||||
char* s = fmt_u(*d, buf + 9);
|
||||
if (s == buf + 9) (--s)[0] = '0';
|
||||
if (d != a)
|
||||
{
|
||||
while (s > buf) (--s)[0] = '0';
|
||||
}
|
||||
else
|
||||
{
|
||||
this.out(s++[0])!;
|
||||
if (p > 0 || this.flags.hash) this.out('.')!;
|
||||
}
|
||||
this.out_chars(s[:math::min(buf + 9 - s, p)])!;
|
||||
p -= buf + 9 - s;
|
||||
}
|
||||
this.pad('0', p + 18, 18)!;
|
||||
this.out_chars(estr[:ebuf - estr])!;
|
||||
if (d != a)
|
||||
{
|
||||
while (s > buf) (--s)[0] = '0';
|
||||
}
|
||||
else
|
||||
{
|
||||
len += self.out(s++[0])!;
|
||||
if (p > 0 || self.flags.hash) len += self.out('.')!;
|
||||
}
|
||||
len += self.out_chars(s[:math::min(buf + 9 - s, p)])!;
|
||||
p -= buf + 9 - s;
|
||||
}
|
||||
len += self.pad('0', p + 18, 18)!;
|
||||
len += self.out_chars(estr[:ebuf - estr])!;
|
||||
}
|
||||
|
||||
if (this.flags.left) this.pad(' ', this.width, pl + l)!;
|
||||
if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;
|
||||
|
||||
return;
|
||||
return len;
|
||||
}
|
||||
|
||||
fn void! Formatter.ntoa(Formatter* this, uint128 value, bool negative, uint base) @private
|
||||
fn usz! Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
|
||||
{
|
||||
char[PRINTF_NTOA_BUFFER_SIZE] buf @noinit;
|
||||
usz len = 0;
|
||||
usz len;
|
||||
|
||||
// no hash for 0 values
|
||||
if (!value) this.flags.hash = false;
|
||||
if (!value) self.flags.hash = false;
|
||||
|
||||
// write if precision != 0 or value is != 0
|
||||
if (!this.flags.precision || value)
|
||||
if (!self.flags.precision || value)
|
||||
{
|
||||
char past_10 = (this.flags.uppercase ? 'A' : 'a') - 10;
|
||||
char past_10 = (self.flags.uppercase ? 'A' : 'a') - 10;
|
||||
do
|
||||
{
|
||||
if (len >= PRINTF_NTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
@@ -522,21 +523,21 @@ fn void! Formatter.ntoa(Formatter* this, uint128 value, bool negative, uint base
|
||||
}
|
||||
while (value);
|
||||
}
|
||||
return this.ntoa_format((String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
|
||||
return self.ntoa_format((String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
|
||||
}
|
||||
|
||||
fn void! Formatter.ntoa_format(Formatter* this, String buf, usz len, bool negative, uint base) @private
|
||||
fn usz! Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint base) @private
|
||||
{
|
||||
// pad leading zeros
|
||||
if (!this.flags.left)
|
||||
if (!self.flags.left)
|
||||
{
|
||||
if (this.width && this.flags.zeropad && (negative || this.flags.plus || this.flags.space)) this.width--;
|
||||
while (len < this.prec)
|
||||
if (self.width && self.flags.zeropad && (negative || self.flags.plus || self.flags.space)) self.width--;
|
||||
while (len < self.prec)
|
||||
{
|
||||
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
buf[len++] = '0';
|
||||
}
|
||||
while (this.flags.zeropad && len < this.width)
|
||||
while (self.flags.zeropad && len < self.width)
|
||||
{
|
||||
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
buf[len++] = '0';
|
||||
@@ -544,9 +545,9 @@ fn void! Formatter.ntoa_format(Formatter* this, String buf, usz len, bool negati
|
||||
}
|
||||
|
||||
// handle hash
|
||||
if (this.flags.hash && base != 10)
|
||||
if (self.flags.hash && base != 10)
|
||||
{
|
||||
if (!this.flags.precision && len && len == this.prec && len == this.width)
|
||||
if (!self.flags.precision && len && len == self.prec && len == self.width)
|
||||
{
|
||||
len--;
|
||||
if (len) len--;
|
||||
@@ -557,11 +558,11 @@ fn void! Formatter.ntoa_format(Formatter* this, String buf, usz len, bool negati
|
||||
switch (base)
|
||||
{
|
||||
case 16:
|
||||
buf[len++] = this.flags.uppercase ? 'X' : 'x';
|
||||
buf[len++] = self.flags.uppercase ? 'X' : 'x';
|
||||
case 8:
|
||||
buf[len++] = this.flags.uppercase ? 'O' : 'o';
|
||||
buf[len++] = self.flags.uppercase ? 'O' : 'o';
|
||||
case 2:
|
||||
buf[len++] = this.flags.uppercase ? 'B' : 'b';
|
||||
buf[len++] = self.flags.uppercase ? 'B' : 'b';
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
@@ -574,70 +575,71 @@ fn void! Formatter.ntoa_format(Formatter* this, String buf, usz len, bool negati
|
||||
case negative:
|
||||
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
buf[len++] = '-';
|
||||
case this.flags.plus:
|
||||
case self.flags.plus:
|
||||
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
buf[len++] = '+';
|
||||
case this.flags.space:
|
||||
case self.flags.space:
|
||||
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
|
||||
buf[len++] = ' ';
|
||||
}
|
||||
if (!len) return;
|
||||
return this.out_reverse(buf[:len]);
|
||||
if (len) self.out_reverse(buf[:len])!;
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
fn void! Formatter.ntoa_any(Formatter* this, any arg, uint base) @private
|
||||
fn usz! Formatter.ntoa_any(&self, any* arg, uint base) @private
|
||||
{
|
||||
bool is_neg;
|
||||
uint128 val = int_from_any(arg, &is_neg)!!;
|
||||
return this.ntoa(val, is_neg, base) @inline;
|
||||
return self.ntoa(val, is_neg, base) @inline;
|
||||
}
|
||||
|
||||
fn void! Formatter.out_char(Formatter* this, any arg) @private
|
||||
fn usz! Formatter.out_char(&self, any* arg) @private
|
||||
{
|
||||
usz len = 1;
|
||||
uint l = 1;
|
||||
// pre padding
|
||||
this.right_adjust(l)!;
|
||||
// char output
|
||||
Char32 c = types::any_to_int(arg, uint) ?? 0xFFFD;
|
||||
len += self.adjust(l)!;
|
||||
// char output
|
||||
Char32 c = types::any_to_int(arg, uint) ?? 0xFFFD;
|
||||
switch (true)
|
||||
{
|
||||
case c < 0x7f:
|
||||
this.out((char)c)!;
|
||||
self.out((char)c)!;
|
||||
case c < 0x7ff:
|
||||
this.out((char)(0xC0 | c >> 6))!;
|
||||
this.out((char)(0x80 | (c & 0x3F)))!;
|
||||
case c < 0xffff:
|
||||
this.out((char)(0xE0 | c >> 12))!;
|
||||
this.out((char)(0x80 | (c >> 6 & 0x3F)))!;
|
||||
this.out((char)(0x80 | (c & 0x3F)))!;
|
||||
default:
|
||||
this.out((char)(0xF0 | c >> 18))!;
|
||||
this.out((char)(0x80 | (c >> 12 & 0x3F)))!;
|
||||
this.out((char)(0x80 | (c >> 6 & 0x3F)))!;
|
||||
this.out((char)(0x80 | (c & 0x3F)))!;
|
||||
self.out((char)(0xC0 | c >> 6))!;
|
||||
self.out((char)(0x80 | (c & 0x3F)))!;
|
||||
case c < 0xffff:
|
||||
self.out((char)(0xE0 | c >> 12))!;
|
||||
self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
|
||||
self.out((char)(0x80 | (c & 0x3F)))!;
|
||||
default:
|
||||
self.out((char)(0xF0 | c >> 18))!;
|
||||
self.out((char)(0x80 | (c >> 12 & 0x3F)))!;
|
||||
self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
|
||||
self.out((char)(0x80 | (c & 0x3F)))!;
|
||||
}
|
||||
return this.left_adjust(l);
|
||||
len += self.adjust(l)!;
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
fn void! Formatter.out_reverse(Formatter* this, char[] buf) @private
|
||||
fn usz! Formatter.out_reverse(&self, char[] buf) @private
|
||||
{
|
||||
usz buffer_start_idx = this.idx;
|
||||
usz n;
|
||||
usz buffer_start_idx = self.idx;
|
||||
usz len = buf.len;
|
||||
// pad spaces up to given width
|
||||
if (!this.flags.left && !this.flags.zeropad)
|
||||
{
|
||||
for (usz i = len; i < this.width; i++)
|
||||
{
|
||||
this.out(' ')!;
|
||||
}
|
||||
}
|
||||
// reverse string
|
||||
while (len) this.out(buf[--len])!;
|
||||
// pad spaces up to given width
|
||||
if (!self.flags.zeropad)
|
||||
{
|
||||
n += self.adjust(len)!;
|
||||
}
|
||||
// reverse string
|
||||
while (len) n += self.out(buf[--len])!;
|
||||
|
||||
// append pad spaces up to given width
|
||||
return this.left_adjust(this.idx - buffer_start_idx);
|
||||
n += self.adjust(self.idx - buffer_start_idx)!;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn void! printf_advance_format(usz format_len, usz *index_ptr) @inline @private
|
||||
@@ -646,22 +648,22 @@ fn void! printf_advance_format(usz format_len, usz *index_ptr) @inline @private
|
||||
if (val >= format_len) return FormattingFault.UNTERMINATED_FORMAT?;
|
||||
}
|
||||
|
||||
fn any! next_any(any* args_ptr, usz args_len, usz* arg_index_ptr) @inline @private
|
||||
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,
|
||||
any** args_ptr, usz args_len, usz* args_index_ptr,
|
||||
char* format_ptr, usz format_len, usz* index_ptr) @inline @private
|
||||
{
|
||||
char c = format_ptr[*index_ptr];
|
||||
if (c >= '0' && c <= '9') return simple_atoi(format_ptr, format_len, index_ptr);
|
||||
if (c.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr);
|
||||
if (c != '*') return 0;
|
||||
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);
|
||||
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.INVALID_WIDTH_ARG?;
|
||||
}
|
||||
335
lib/std/io/io.c3
335
lib/std/io/io.c3
@@ -4,132 +4,287 @@
|
||||
module std::io;
|
||||
import libc;
|
||||
|
||||
struct File
|
||||
{
|
||||
CFile file;
|
||||
}
|
||||
|
||||
enum Seek
|
||||
{
|
||||
SET,
|
||||
CURSOR,
|
||||
END
|
||||
SET,
|
||||
CURSOR,
|
||||
END
|
||||
}
|
||||
|
||||
fault IoError
|
||||
{
|
||||
FILE_NOT_FOUND,
|
||||
FILE_NOT_VALID,
|
||||
INVALID_POSITION,
|
||||
OVERFLOW,
|
||||
FILE_IS_PIPE,
|
||||
FILE_EOF,
|
||||
INCOMPLETE_WRITE,
|
||||
BUSY,
|
||||
NO_PERMISSION,
|
||||
OUT_OF_SPACE,
|
||||
INVALID_PUSHBACK,
|
||||
EOF,
|
||||
CANNOT_READ_DIR,
|
||||
TOO_MANY_DESCRIPTORS,
|
||||
FILE_IS_DIR,
|
||||
READ_ONLY,
|
||||
FILE_NOT_DIR,
|
||||
SYMLINK_FAILED,
|
||||
ALREADY_EXISTS,
|
||||
NOT_SEEKABLE,
|
||||
NAME_TOO_LONG,
|
||||
WOULD_BLOCK,
|
||||
DIR_NOT_EMPTY,
|
||||
INTERRUPTED,
|
||||
GENERAL_ERROR,
|
||||
UNKNOWN_ERROR,
|
||||
UNSUPPORTED_OPERATION,
|
||||
ALREADY_EXISTS,
|
||||
BUSY,
|
||||
CANNOT_READ_DIR,
|
||||
DIR_NOT_EMPTY,
|
||||
EOF,
|
||||
FILE_CANNOT_DELETE,
|
||||
FILE_IS_DIR,
|
||||
FILE_IS_PIPE,
|
||||
FILE_NOT_DIR,
|
||||
FILE_NOT_FOUND,
|
||||
FILE_NOT_VALID,
|
||||
GENERAL_ERROR,
|
||||
ILLEGAL_ARGUMENT,
|
||||
INCOMPLETE_WRITE,
|
||||
INTERRUPTED,
|
||||
INVALID_POSITION,
|
||||
INVALID_PUSHBACK,
|
||||
NAME_TOO_LONG,
|
||||
NOT_SEEKABLE,
|
||||
NO_PERMISSION,
|
||||
OUT_OF_SPACE,
|
||||
OVERFLOW,
|
||||
READ_ONLY,
|
||||
SYMLINK_FAILED,
|
||||
TOO_MANY_DESCRIPTORS,
|
||||
UNEXPECTED_EOF,
|
||||
UNKNOWN_ERROR,
|
||||
UNSUPPORTED_OPERATION,
|
||||
WOULD_BLOCK,
|
||||
}
|
||||
|
||||
fn void putchar(char c) @inline
|
||||
|
||||
/**
|
||||
* @param stream
|
||||
* @require @is_instream(stream)
|
||||
**/
|
||||
macro String! readline(stream = io::stdin(), Allocator* allocator = mem::heap())
|
||||
{
|
||||
libc::putchar(c);
|
||||
bool $is_stream = @typeid(stream) == InStream*.typeid;
|
||||
$if $is_stream:
|
||||
$typeof(&stream.read_byte) func = &stream.read_byte;
|
||||
char val = func((void*)stream)!;
|
||||
$else
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
macro String! treadline(stream = io::stdin()) => readline(stream, mem::temp()) @inline;
|
||||
|
||||
/**
|
||||
* @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
|
||||
return fprintf(out, "%s", x);
|
||||
$endif
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn usz! fprintf(OutStream* out, String format, args...)
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putstream_fn, &out);
|
||||
return formatter.vprintf(format, args);
|
||||
}
|
||||
|
||||
fn usz! fprintfn(OutStream* out, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putstream_fn, &out);
|
||||
usz len = formatter.vprintf(format, args)!;
|
||||
out.write_byte('\n')!;
|
||||
if (&out.flush) out.flush()!;
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require @is_outstream(out) "The output must implement OutStream"
|
||||
*/
|
||||
macro usz! fprintn(out, x = "")
|
||||
{
|
||||
usz len = fprint(out, x)!;
|
||||
out.write_byte('\n')!;
|
||||
$switch
|
||||
$case @typeid(out) == OutStream*.typeid:
|
||||
if (&out.flush) out.flush()!;
|
||||
$case $defined(out.flush):
|
||||
out.flush()!;
|
||||
$endswitch
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
macro void print(x)
|
||||
{
|
||||
var $Type = $typeof(x);
|
||||
$switch ($Type)
|
||||
$case String:
|
||||
(void)stdout().print(x);
|
||||
$case ZString:
|
||||
(void)stdout().print(x.as_str());
|
||||
$case DString:
|
||||
(void)stdout().print(x.str());
|
||||
$default:
|
||||
$if @convertible(x, String):
|
||||
(void)stdout().print((String)x);
|
||||
$else
|
||||
(void)stdout().printf("%s", x);
|
||||
$endif
|
||||
$endswitch
|
||||
(void)fprint(io::stdout(), x);
|
||||
}
|
||||
|
||||
macro void printn(x = "")
|
||||
{
|
||||
var $Type = $typeof(x);
|
||||
$switch ($Type)
|
||||
$case String:
|
||||
(void)stdout().printn(x);
|
||||
$case ZString:
|
||||
(void)stdout().printn(x.as_str());
|
||||
$case DString:
|
||||
(void)stdout().printn(x.str());
|
||||
$default:
|
||||
$if @convertible(x, String):
|
||||
(void)stdout().printn((String)x);
|
||||
$else
|
||||
(void)stdout().printfn("%s", x);
|
||||
$endif
|
||||
$endswitch
|
||||
(void)fprintn(io::stdout(), x);
|
||||
}
|
||||
|
||||
fn File stdout()
|
||||
macro void eprint(x)
|
||||
{
|
||||
return { libc::stdout() };
|
||||
(void)fprint(io::stderr(), x);
|
||||
}
|
||||
|
||||
fn File stderr()
|
||||
macro void eprintn(x)
|
||||
{
|
||||
return { libc::stderr() };
|
||||
(void)fprintn(io::stderr(), x);
|
||||
}
|
||||
|
||||
fn File stdin()
|
||||
|
||||
fn void! out_putstream_fn(void* data, char c) @private
|
||||
{
|
||||
return { libc::stdin() };
|
||||
OutStream** stream = data;
|
||||
return (*stream).write_byte(c);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
error FileError
|
||||
fn void! out_putchar_fn(void* data @unused, char c) @private
|
||||
{
|
||||
ulong errno;
|
||||
libc::putchar(c);
|
||||
}
|
||||
|
||||
fn FileError errorFromErrno()
|
||||
fn usz! printf(String format, args...) @maydiscard
|
||||
{
|
||||
return FileError { };
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putchar_fn);
|
||||
return formatter.vprintf(format, args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
pubic fn void! File.clearerr(File *file) @inline
|
||||
fn usz! printfn(String format, args...) @maydiscard
|
||||
{
|
||||
clearerr(file->file);
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putchar_fn);
|
||||
usz len = formatter.vprintf(format, args)!;
|
||||
putchar('\n');
|
||||
io::stdout().flush()!;
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn void File.error(File *file) @inline
|
||||
fn usz! eprintf(String format, args...) @maydiscard
|
||||
{
|
||||
int err = ferror
|
||||
Formatter formatter;
|
||||
OutStream* stream = stderr();
|
||||
formatter.init(&out_putstream_fn, &stream);
|
||||
return formatter.vprintf(format, args);
|
||||
}
|
||||
|
||||
|
||||
fn usz! eprintfn(String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
OutStream* stream = stderr();
|
||||
formatter.init(&out_putstream_fn, &stream);
|
||||
usz len = formatter.vprintf(format, args)! + 1;
|
||||
stderr().write_byte('\n')!;
|
||||
stderr().flush()!;
|
||||
return len;
|
||||
}
|
||||
|
||||
fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
BufferData data = { .buffer = buffer };
|
||||
formatter.init(&out_buffer_fn, &data);
|
||||
usz size = formatter.vprintf(format, args)!;
|
||||
return buffer[:data.written];
|
||||
}
|
||||
|
||||
fn void! out_buffer_fn(void *data, char c) @private
|
||||
{
|
||||
BufferData *buffer_data = data;
|
||||
if (buffer_data.written >= buffer_data.buffer.len) return PrintFault.BUFFER_EXCEEDED?;
|
||||
buffer_data.buffer[buffer_data.written++] = c;
|
||||
}
|
||||
|
||||
|
||||
struct BufferData @private
|
||||
{
|
||||
char[] buffer;
|
||||
usz written;
|
||||
}
|
||||
|
||||
|
||||
module std::io @if (env::LIBC);
|
||||
import libc;
|
||||
|
||||
fn void putchar(char c) @inline
|
||||
{
|
||||
libc::putchar(c);
|
||||
}
|
||||
|
||||
fn File* stdout()
|
||||
{
|
||||
static File file;
|
||||
if (!file.file) file = file::from_handle(libc::stdout());
|
||||
return &file;
|
||||
}
|
||||
|
||||
fn File* stderr()
|
||||
{
|
||||
static File file;
|
||||
if (!file.file) file = file::from_handle(libc::stderr());
|
||||
return &file;
|
||||
}
|
||||
|
||||
fn File* stdin()
|
||||
{
|
||||
static File file;
|
||||
if (!file.file) file = file::from_handle(libc::stdin());
|
||||
return &file;
|
||||
}
|
||||
|
||||
module std::io @if(!env::LIBC);
|
||||
|
||||
File stdin_file;
|
||||
File stdout_file;
|
||||
File stderr_file;
|
||||
|
||||
fn void putchar(char c) @inline
|
||||
{
|
||||
(void)stdout_file.putc(c);
|
||||
}
|
||||
|
||||
fn File* stdout()
|
||||
{
|
||||
return &stdout_file;
|
||||
}
|
||||
|
||||
fn File* stderr()
|
||||
{
|
||||
return &stderr_file;
|
||||
}
|
||||
|
||||
fn File* stdin()
|
||||
{
|
||||
return &stdin_file;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
module std::io::file;
|
||||
import libc;
|
||||
|
||||
fn File! open(String filename, String mode)
|
||||
{
|
||||
return { .file = os::native_fopen(filename, mode) };
|
||||
}
|
||||
|
||||
fn File! open_path(Path path, String mode)
|
||||
{
|
||||
return { .file = os::native_fopen(path.as_str(), mode) };
|
||||
}
|
||||
|
||||
/**
|
||||
* @require file.file != null
|
||||
**/
|
||||
fn void! File.reopen(File* file, String filename, String mode)
|
||||
{
|
||||
file.file = os::native_freopen(file.file, filename, mode)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require file.file != null
|
||||
**/
|
||||
fn usz! File.seek(File file, isz offset, Seek seek_mode = Seek.SET)
|
||||
{
|
||||
os::native_fseek(file.file, offset, seek_mode)!;
|
||||
return os::native_ftell(file.file);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Implement later
|
||||
/**
|
||||
* @require file.file == null
|
||||
**/
|
||||
fn void! File.memopen(File* file, char[] data, String mode)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
file.file = libc::memopen(data.ptr, data.len, mode.zstr_tcopy(), file.file);
|
||||
// TODO errors
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @require file && file.file != null
|
||||
*/
|
||||
fn void! File.putc(File *file, char c)
|
||||
{
|
||||
if (!libc::fputc(c, file.file)) return IoError.FILE_EOF?;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require file != null
|
||||
*/
|
||||
fn void! File.close(File *file) @inline
|
||||
{
|
||||
if (file.file && libc::fclose(file.file))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ECONNRESET:
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID?;
|
||||
case errno::EINTR: return IoError.INTERRUPTED?;
|
||||
case errno::EDQUOT:
|
||||
case errno::EFAULT:
|
||||
case errno::EAGAIN:
|
||||
case errno::EFBIG:
|
||||
case errno::ENETDOWN:
|
||||
case errno::ENETUNREACH:
|
||||
case errno::ENOSPC:
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE?;
|
||||
default: return IoError.UNKNOWN_ERROR?;
|
||||
}
|
||||
}
|
||||
file.file = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require file && file.file
|
||||
*/
|
||||
fn bool File.eof(File* file) @inline
|
||||
{
|
||||
return libc::feof(file.file) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [in] buffer
|
||||
*/
|
||||
fn usz! File.read(File* file, char[] buffer)
|
||||
{
|
||||
return os::native_fread(file.file, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @param [&out] buffer
|
||||
* @require file.file `File must be initialized`
|
||||
*/
|
||||
fn usz! File.write(File file, char[] buffer)
|
||||
{
|
||||
return os::native_fwrite(file.file, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @require file.file `File must be initialized`
|
||||
*/
|
||||
fn usz! File.printn(File file, String string = "")
|
||||
{
|
||||
usz len = file.print(string)!;
|
||||
if (!libc::putc('\n', file.file)) return IoError.UNKNOWN_ERROR?;
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @require file.file `File must be initialized`
|
||||
*/
|
||||
fn usz! File.print(File file, String string)
|
||||
{
|
||||
usz len = string.len;
|
||||
if (len != file.write((char[])string)!) return IoError.UNKNOWN_ERROR?;
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @require file.file `File must be initialized`
|
||||
*/
|
||||
fn DString File.getline(File* file, Allocator* using = mem::heap())
|
||||
{
|
||||
DString s = dstring::new_with_capacity(120, using);
|
||||
while (!file.eof())
|
||||
{
|
||||
int c = libc::fgetc(file.file);
|
||||
if (c == -1) break;
|
||||
if (c == '\n') break;
|
||||
s.append_char((char)c);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @require file.file `File must be initialized`
|
||||
* @return "a zero terminated String (the pointer may be safely cast into a ZString)"
|
||||
*/
|
||||
fn String File.tgetline(File* file)
|
||||
{
|
||||
return file.getline(mem::temp()).zstr().as_str();
|
||||
}
|
||||
|
||||
fn char! File.getc(File* file)
|
||||
{
|
||||
int c = libc::fgetc(file.file);
|
||||
if (c == -1) return IoError.FILE_EOF?;
|
||||
return (char)c;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&in] file
|
||||
* @require file.file `File must be initialized`
|
||||
*/
|
||||
fn void File.flush(File* file)
|
||||
{
|
||||
libc::fflush(file.file);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
module std::io::file;
|
||||
import libc;
|
||||
|
||||
|
||||
|
||||
fn bool is_file(String path)
|
||||
{
|
||||
return os::native_is_file(path);
|
||||
}
|
||||
|
||||
fn usz! get_size(String path)
|
||||
{
|
||||
return os::native_file_size(path);
|
||||
}
|
||||
@@ -1,427 +0,0 @@
|
||||
module std::io;
|
||||
import std::collections::map;
|
||||
import libc;
|
||||
|
||||
const int PRINTF_NTOA_BUFFER_SIZE = 256;
|
||||
|
||||
fault PrintFault
|
||||
{
|
||||
BUFFER_EXCEEDED,
|
||||
INTERNAL_BUFFER_EXCEEDED,
|
||||
INVALID_FORMAT_STRING,
|
||||
MISSING_ARG,
|
||||
INVALID_ARGUMENT_TYPE,
|
||||
}
|
||||
|
||||
fault FormattingFault
|
||||
{
|
||||
UNTERMINATED_FORMAT,
|
||||
MISSING_ARG,
|
||||
INVALID_WIDTH_ARG,
|
||||
INVALID_FORMAT_TYPE,
|
||||
}
|
||||
|
||||
def OutputFn = fn void!(char c, void* buffer);
|
||||
def FloatType = double;
|
||||
|
||||
fn String any.to_string(void* value, Allocator *using) @interface;
|
||||
fn void! any.to_format(void* value, Formatter* formatter) @interface;
|
||||
|
||||
fn usz! printf(String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putchar_fn);
|
||||
return formatter.vprintf(format, args);
|
||||
}
|
||||
|
||||
fn usz! printfn(String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_putchar_fn);
|
||||
usz len = formatter.vprintf(format, args)!;
|
||||
putchar('\n');
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
BufferData data = { .buffer = buffer };
|
||||
formatter.init(&out_buffer_fn, &data);
|
||||
usz size = formatter.vprintf(format, args)!;
|
||||
return buffer[:data.written];
|
||||
}
|
||||
|
||||
fn usz! File.printf(File file, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_fputchar_fn, &file);
|
||||
return formatter.vprintf(format, args)!;
|
||||
}
|
||||
|
||||
fn usz! File.printfn(File file, String format, args...) @maydiscard
|
||||
{
|
||||
Formatter formatter;
|
||||
formatter.init(&out_fputchar_fn, &file);
|
||||
usz len = formatter.vprintf(format, args)!;
|
||||
file.putc('\n')!;
|
||||
file.flush();
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
fn usz! Formatter.printf(Formatter* this, String format, args...)
|
||||
{
|
||||
return this.vprintf(format, args) @inline;
|
||||
}
|
||||
|
||||
struct Formatter
|
||||
{
|
||||
void *data;
|
||||
OutputFn out_fn;
|
||||
struct
|
||||
{
|
||||
PrintFlags flags;
|
||||
uint width;
|
||||
uint prec;
|
||||
usz idx;
|
||||
}
|
||||
}
|
||||
|
||||
bitstruct PrintFlags : uint
|
||||
{
|
||||
bool zeropad : 0;
|
||||
bool left : 1;
|
||||
bool plus : 2;
|
||||
bool space : 3;
|
||||
bool hash : 4;
|
||||
bool uppercase : 5;
|
||||
bool precision : 6;
|
||||
}
|
||||
|
||||
fn void Formatter.init(Formatter* this, OutputFn out_fn, void* data = null)
|
||||
{
|
||||
*this = { .data = data, .out_fn = out_fn};
|
||||
}
|
||||
|
||||
fn void! Formatter.out(Formatter* this, char c) @private
|
||||
{
|
||||
this.out_fn(c, this.data)!;
|
||||
}
|
||||
|
||||
macro bool! Formatter.print_with_function(Formatter* this, any arg)
|
||||
{
|
||||
if (&arg.to_format)
|
||||
{
|
||||
PrintFlags old = this.flags;
|
||||
uint old_width = this.width;
|
||||
uint old_prec = this.prec;
|
||||
defer
|
||||
{
|
||||
this.flags = old;
|
||||
this.width = old_width;
|
||||
this.prec = old_prec;
|
||||
}
|
||||
arg.to_format(this)!;
|
||||
return true;
|
||||
}
|
||||
if (&arg.to_string)
|
||||
{
|
||||
PrintFlags old = this.flags;
|
||||
uint old_width = this.width;
|
||||
uint old_prec = this.prec;
|
||||
defer
|
||||
{
|
||||
this.flags = old;
|
||||
this.width = old_width;
|
||||
this.prec = old_prec;
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
this.out_substr(arg.to_string(mem::temp()))!;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn void! Formatter.out_str(Formatter* this, any arg) @private
|
||||
{
|
||||
switch (arg.type.kindof)
|
||||
{
|
||||
case TYPEID:
|
||||
return this.out_substr("typeid");
|
||||
case VOID:
|
||||
return this.out_substr("void");
|
||||
case ANYFAULT:
|
||||
case FAULT:
|
||||
return this.out_substr((*(anyfault*)arg.ptr).nameof);
|
||||
case ANY:
|
||||
return this.out_str(*(any*)arg);
|
||||
case ENUM:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr(arg.type.names[types::any_to_int(arg, usz)!!]);
|
||||
case STRUCT:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr("<struct>");
|
||||
case UNION:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr("<union>");
|
||||
case BITSTRUCT:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr("<bitstruct>");
|
||||
case FUNC:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr("<function>");
|
||||
case OPTIONAL:
|
||||
unreachable();
|
||||
case DISTINCT:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
if (arg.type == DString.typeid)
|
||||
{
|
||||
return this.out_substr(((DString*)arg).str());
|
||||
}
|
||||
return this.out_str(any { arg.ptr, arg.type.inner });
|
||||
case POINTER:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.ntoa_any(arg, 16);
|
||||
case SIGNED_INT:
|
||||
case UNSIGNED_INT:
|
||||
return this.ntoa_any(arg, 10);
|
||||
case FLOAT:
|
||||
return this.ftoa(float_from_any(arg)!!);
|
||||
case ARRAY:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
// this is SomeType[*] so grab the "SomeType"
|
||||
typeid inner = arg.type.inner;
|
||||
usz size = inner.sizeof;
|
||||
usz len = arg.type.len;
|
||||
// Pretend this is a String
|
||||
void* ptr = (void*)arg.ptr;
|
||||
this.out('[')!;
|
||||
for (usz i = 0; i < len; i++)
|
||||
{
|
||||
if (i != 0) this.out_substr(", ")!;
|
||||
this.out_str(any { ptr, inner })!;
|
||||
ptr += size;
|
||||
}
|
||||
return this.out(']');
|
||||
case VECTOR:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
// this is SomeType[*] so grab the "SomeType"
|
||||
typeid inner = arg.type.inner;
|
||||
usz size = inner.sizeof;
|
||||
usz len = arg.type.len;
|
||||
// Pretend this is a String
|
||||
void* ptr = (void*)arg.ptr;
|
||||
this.out_substr("[<")!;
|
||||
for (usz i = 0; i < len; i++)
|
||||
{
|
||||
if (i != 0) this.out_substr(", ")!;
|
||||
this.out_str(any { ptr, inner })!;
|
||||
ptr += size;
|
||||
}
|
||||
return this.out_substr(">]");
|
||||
case SUBARRAY:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
// this is SomeType[] so grab the "SomeType"
|
||||
typeid inner = arg.type.inner;
|
||||
if (inner == char.typeid)
|
||||
{
|
||||
return this.out_substr(*(String*)arg);
|
||||
}
|
||||
usz size = inner.sizeof;
|
||||
// Pretend this is a String
|
||||
String* temp = (void*)arg.ptr;
|
||||
void* ptr = (void*)temp.ptr;
|
||||
usz len = temp.len;
|
||||
this.out('[')!;
|
||||
for (usz i = 0; i < len; i++)
|
||||
{
|
||||
if (i != 0) this.out_substr(", ")!;
|
||||
this.out_str(any { ptr, inner })!;
|
||||
ptr += size;
|
||||
}
|
||||
this.out(']')!;
|
||||
case BOOL:
|
||||
return this.out_substr(*(bool*)arg.ptr ? "true" : "false");
|
||||
default:
|
||||
if (this.print_with_function(arg)!) return;
|
||||
return this.out_substr("Invalid type");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn void! out_buffer_fn(char c, void *data) @private
|
||||
{
|
||||
BufferData *buffer_data = data;
|
||||
if (buffer_data.written >= buffer_data.buffer.len) return PrintFault.BUFFER_EXCEEDED?;
|
||||
buffer_data.buffer[buffer_data.written++] = c;
|
||||
}
|
||||
|
||||
fn void! out_null_fn(char c @unused, void* data @unused) @private
|
||||
{
|
||||
}
|
||||
|
||||
fn void! out_putchar_fn(char c, void* data @unused) @private
|
||||
{
|
||||
libc::putchar(c);
|
||||
}
|
||||
|
||||
fn void! out_fputchar_fn(char c, void* data) @private
|
||||
{
|
||||
File* f = data;
|
||||
f.putc(c)!;
|
||||
}
|
||||
|
||||
struct BufferData @private
|
||||
{
|
||||
char[] buffer;
|
||||
usz written;
|
||||
}
|
||||
|
||||
|
||||
fn usz! Formatter.vprintf(Formatter* this, String format, any[] anys)
|
||||
{
|
||||
if (!this.out_fn)
|
||||
{
|
||||
// use null output function
|
||||
this.out_fn = &out_null_fn;
|
||||
}
|
||||
usz format_len = format.len;
|
||||
usz variant_index = 0;
|
||||
for (usz i = 0; i < format_len; i++)
|
||||
{
|
||||
// format specifier? %[flags][width][.precision][length]
|
||||
char c = format[i];
|
||||
if (c != '%')
|
||||
{
|
||||
// no
|
||||
this.out(c)!;
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
if (i >= format_len) return PrintFault.INVALID_FORMAT_STRING?;
|
||||
c = format[i];
|
||||
if (c == '%')
|
||||
{
|
||||
this.out(c)!;
|
||||
continue;
|
||||
}
|
||||
// evaluate flags
|
||||
this.flags = {};
|
||||
while FLAG_EVAL: (true)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '0': this.flags.zeropad = true;
|
||||
case '-': this.flags.left = true;
|
||||
case '+': this.flags.plus = true;
|
||||
case ' ': this.flags.space = true;
|
||||
case '#': this.flags.hash = true;
|
||||
default: break FLAG_EVAL;
|
||||
}
|
||||
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)!;
|
||||
c = format[i];
|
||||
if (w < 0)
|
||||
{
|
||||
this.flags.left = true;
|
||||
w = -w;
|
||||
}
|
||||
this.width = w;
|
||||
// evaluate precision field
|
||||
this.prec = 0;
|
||||
if (c == '.')
|
||||
{
|
||||
this.flags.precision = true;
|
||||
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)!;
|
||||
this.prec = prec < 0 ? 0 : prec;
|
||||
c = format[i];
|
||||
}
|
||||
|
||||
// evaluate specifier
|
||||
uint base = 0;
|
||||
if (variant_index >= anys.len) return PrintFault.MISSING_ARG?;
|
||||
any current = anys[variant_index++];
|
||||
switch (c)
|
||||
{
|
||||
case 'd':
|
||||
base = 10;
|
||||
this.flags.hash = false;
|
||||
case 'X' :
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'x' :
|
||||
base = 16;
|
||||
case 'O':
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'o' :
|
||||
base = 8;
|
||||
case 'B':
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'b' :
|
||||
base = 2;
|
||||
case 'A':
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'a':
|
||||
this.atoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'F' :
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'f':
|
||||
this.ftoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'E':
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'e':
|
||||
this.etoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'G':
|
||||
this.flags.uppercase = true;
|
||||
nextcase;
|
||||
case 'g':
|
||||
this.gtoa(float_from_any(current)!!)!;
|
||||
continue;
|
||||
case 'c':
|
||||
this.out_char(current)!;
|
||||
continue;
|
||||
case 's':
|
||||
this.out_str(current)!;
|
||||
continue;
|
||||
case 'p':
|
||||
this.flags.zeropad = true;
|
||||
this.flags.hash = true;
|
||||
base = 16;
|
||||
default:
|
||||
return PrintFault.INVALID_FORMAT_STRING?;
|
||||
}
|
||||
if (base != 10)
|
||||
{
|
||||
this.flags.plus = false;
|
||||
this.flags.space = false;
|
||||
}
|
||||
// ignore '0' flag when precision is given
|
||||
if (this.flags.precision) this.flags.zeropad = false;
|
||||
|
||||
bool is_neg;
|
||||
uint128 v = int_from_any(current, &is_neg)!!;
|
||||
|
||||
this.ntoa(v, is_neg, base)!;
|
||||
}
|
||||
// termination
|
||||
// out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
|
||||
|
||||
// return written chars without terminating \0
|
||||
return this.idx;
|
||||
}
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
module std::io;
|
||||
|
||||
def CloseStreamFn = fn void!(Stream*);
|
||||
def FlushStreamFn = fn void!(Stream*);
|
||||
def SeekStreamFn = fn usz!(Stream*, isz offset, Seek seek);
|
||||
def LenStreamFn = fn usz(Stream*);
|
||||
def AvailableStreamFn = fn usz(Stream*);
|
||||
def ReadStreamFn = fn usz!(Stream*, char[] bytes);
|
||||
def ReadFromStreamFn = fn usz!(Stream*, Stream*);
|
||||
def ReadByteStreamFn = fn char!(Stream*);
|
||||
def PushbackByteStreamFn = fn void!(Stream*);
|
||||
def WriteStreamFn = fn usz!(Stream*, char[] bytes);
|
||||
def WriteToStreamFn = fn usz!(Stream*, Stream* out);
|
||||
def WriteByteStreamFn = fn void!(Stream*, char c);
|
||||
def DestroyStreamFn = fn void!(Stream*);
|
||||
|
||||
struct StreamInterface
|
||||
{
|
||||
CloseStreamFn close_fn;
|
||||
FlushStreamFn flush_fn;
|
||||
SeekStreamFn seek_fn;
|
||||
LenStreamFn len_fn;
|
||||
AvailableStreamFn available_fn;
|
||||
ReadStreamFn read_fn;
|
||||
ReadFromStreamFn read_stream_fn;
|
||||
ReadByteStreamFn read_byte_fn;
|
||||
PushbackByteStreamFn pushback_byte_fn;
|
||||
WriteStreamFn write_fn;
|
||||
WriteToStreamFn write_stream_fn;
|
||||
WriteByteStreamFn write_byte_fn;
|
||||
DestroyStreamFn destroy_fn;
|
||||
}
|
||||
|
||||
struct Stream
|
||||
{
|
||||
StreamInterface *fns;
|
||||
void* data;
|
||||
}
|
||||
|
||||
fn bool Stream.supports_seek(Stream* s) @inline => (bool)s.fns.seek_fn;
|
||||
fn bool Stream.supports_available(Stream* s) @inline => s.fns.available_fn || s.fns.seek_fn;
|
||||
fn bool Stream.supports_len(Stream* s) @inline => s.fns.len_fn || s.fns.seek_fn;
|
||||
fn bool Stream.supports_read(Stream* s) @inline => s.fns.read_fn || s.fns.read_byte_fn;
|
||||
fn bool Stream.supports_read_from(Stream* s) @inline => (bool)s.fns.read_stream_fn;
|
||||
fn bool Stream.supports_write_to(Stream* s) @inline => (bool)s.fns.write_stream_fn;
|
||||
fn bool Stream.supports_pushback_byte(Stream* s) @inline => s.fns.pushback_byte_fn || s.fns.seek_fn;
|
||||
fn bool Stream.supports_write(Stream* s) @inline => s.fns.write_fn || s.fns.write_byte_fn;
|
||||
|
||||
fn void! Stream.destroy(Stream* s) @inline @maydiscard
|
||||
{
|
||||
if (s.fns.destroy_fn) return s.fns.destroy_fn(s);
|
||||
return s.close();
|
||||
}
|
||||
|
||||
fn void! Stream.close(Stream* s) @inline @maydiscard
|
||||
{
|
||||
if (CloseStreamFn func = s.fns.close_fn) return func(s);
|
||||
}
|
||||
|
||||
fn usz! Stream.seek(Stream* s, isz offset, Seek seek) @inline
|
||||
{
|
||||
if (SeekStreamFn func = s.fns.seek_fn) return func(s, offset, seek);
|
||||
return IoError.NOT_SEEKABLE?;
|
||||
}
|
||||
|
||||
fn usz! Stream.available(Stream* s) @inline
|
||||
{
|
||||
if (AvailableStreamFn func = s.fns.available_fn) return func(s);
|
||||
if (SeekStreamFn func = s.fns.seek_fn)
|
||||
{
|
||||
usz curr = func(s, 0, Seek.CURSOR)!;
|
||||
usz len = func(s, 0, Seek.END)!;
|
||||
func(s, curr, Seek.SET)!;
|
||||
return len - curr;
|
||||
}
|
||||
return IoError.NOT_SEEKABLE?;
|
||||
}
|
||||
|
||||
fn usz! Stream.read(Stream* s, char[] buffer)
|
||||
{
|
||||
if (ReadStreamFn func = s.fns.read_fn) return func(s, buffer);
|
||||
if (ReadByteStreamFn func = s.fns.read_byte_fn)
|
||||
{
|
||||
usz len = 0;
|
||||
foreach (&cptr : buffer)
|
||||
{
|
||||
char! c = func(s);
|
||||
if (catch err = c)
|
||||
{
|
||||
case IoError.EOF: return len;
|
||||
default: return err?;
|
||||
}
|
||||
*cptr = c;
|
||||
len++;
|
||||
}
|
||||
}
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn char! Stream.read_byte(Stream* s) @inline
|
||||
{
|
||||
if (ReadByteStreamFn func = s.fns.read_byte_fn) return func(s);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! Stream.write(Stream* s, char[] bytes) @inline
|
||||
{
|
||||
if (WriteStreamFn func = s.fns.write_fn) return func(s, bytes);
|
||||
if (WriteByteStreamFn func = s.fns.write_byte_fn)
|
||||
{
|
||||
foreach (c : bytes) func(s, c)!;
|
||||
return bytes.len;
|
||||
}
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn void! Stream.write_byte(Stream* s, char b) @inline
|
||||
{
|
||||
if (WriteByteStreamFn func = s.fns.write_byte_fn) return func(s, b);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! Stream.write_to(Stream* s, Stream* to) @inline
|
||||
{
|
||||
if (WriteToStreamFn func = s.fns.write_stream_fn) return func(s, to);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! Stream.read_from(Stream* s, Stream* from) @inline
|
||||
{
|
||||
if (ReadFromStreamFn func = s.fns.read_stream_fn) return func(s, from);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn void! Stream.flush(Stream* s) @inline @maydiscard
|
||||
{
|
||||
if (FlushStreamFn func = s.fns.flush_fn) return func(s);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! Stream.len(Stream* s) @inline
|
||||
{
|
||||
if (LenStreamFn func = s.fns.len_fn) return func(s);
|
||||
if (SeekStreamFn func = s.fns.seek_fn)
|
||||
{
|
||||
usz curr = func(s, 0, Seek.CURSOR)!;
|
||||
usz len = func(s, 0, Seek.END)!;
|
||||
func(s, curr, Seek.SET)!;
|
||||
return len;
|
||||
}
|
||||
return IoError.NOT_SEEKABLE?;
|
||||
}
|
||||
|
||||
fn void! Stream.pushback_byte(Stream* s) @inline
|
||||
{
|
||||
if (PushbackByteStreamFn func = s.fns.pushback_byte_fn) return func(s);
|
||||
if (SeekStreamFn func = s.fns.seek_fn)
|
||||
{
|
||||
func(s, -1, CURSOR)!;
|
||||
return;
|
||||
}
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn void! Stream.write_string(Stream* s, String str) @inline => (void)(s.write((char[])str)!);
|
||||
|
||||
fn usz! Stream.copy_to(Stream* s, Stream* dst, char[] buffer = {})
|
||||
{
|
||||
if (buffer.len) return copy_through_buffer(s, dst, buffer);
|
||||
if (WriteToStreamFn func = s.fns.write_stream_fn) return func(s, dst);
|
||||
if (ReadFromStreamFn func = dst.fns.read_stream_fn) return func(dst, s);
|
||||
$switch (env::MEMORY_ENV)
|
||||
$case NORMAL:
|
||||
@pool()
|
||||
{
|
||||
return copy_through_buffer(s, dst, tmalloc(char, 4096));
|
||||
};
|
||||
$case SMALL:
|
||||
@pool()
|
||||
{
|
||||
return copy_through_buffer(s, dst, tmalloc(char, 1024));
|
||||
};
|
||||
$case TINY:
|
||||
$case NONE:
|
||||
return copy_through_buffer(s, dst, &&(char[256]{}));
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro usz! copy_through_buffer(Stream* s, Stream* dst, char[] buffer) @local
|
||||
{
|
||||
usz total_copied;
|
||||
while (true)
|
||||
{
|
||||
usz! len = s.read(buffer);
|
||||
if (catch err = len)
|
||||
{
|
||||
case IoError.EOF: return total_copied;
|
||||
default: return err?;
|
||||
}
|
||||
if (!len) return total_copied;
|
||||
usz written = dst.write(buffer[:len])!;
|
||||
total_copied += len;
|
||||
if (written != len) return IoError.INCOMPLETE_WRITE?;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,30 @@
|
||||
module std::io::os;
|
||||
import libc;
|
||||
|
||||
|
||||
$switch
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_posix():
|
||||
|
||||
macro void! native_chdir(Path p)
|
||||
{
|
||||
if (posix::chdir(p.as_zstr()))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES: return IoError.NO_PERMISSION?;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
|
||||
case errno::ENOTDIR: return IoError.FILE_NOT_DIR?;
|
||||
case errno::ENOENT: return IoError.FILE_NOT_FOUND?;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
|
||||
default: return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_win32():
|
||||
|
||||
macro void! native_chdir(Path path)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
// TODO improve with better error handling.
|
||||
if (win32::win32_SetCurrentDirectoryW(path.as_str().to_temp_utf16()!!)) return;
|
||||
};
|
||||
return IoError.GENERAL_ERROR?;
|
||||
$switch
|
||||
$case env::POSIX:
|
||||
if (posix::chdir(path.as_zstr()))
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES: return IoError.NO_PERMISSION?;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
|
||||
case errno::ENOTDIR: return IoError.FILE_NOT_DIR?;
|
||||
case errno::ENOENT: return IoError.FILE_NOT_FOUND?;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
|
||||
default: return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
}
|
||||
$case env::WIN32:
|
||||
@pool()
|
||||
{
|
||||
// TODO improve with better error handling.
|
||||
if (win32::setCurrentDirectoryW(path.str_view().to_temp_utf16()!!)) return;
|
||||
};
|
||||
return IoError.GENERAL_ERROR?;
|
||||
$default:
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
$default:
|
||||
|
||||
fn void! native_chdir(Path path)
|
||||
{
|
||||
unreachable("'getcwd' not available");
|
||||
}
|
||||
|
||||
$endswitch
|
||||
@@ -1,187 +0,0 @@
|
||||
module std::io::os;
|
||||
import libc;
|
||||
|
||||
def FopenFn = fn void*!(String, String);
|
||||
def FreopenFn = fn void*!(void*, String, String);
|
||||
def FcloseFn = fn void!(void*);
|
||||
def FseekFn = fn void!(void*, isz, Seek);
|
||||
def FtellFn = fn usz!(void*);
|
||||
def FwriteFn = fn usz!(void*, char[] buffer);
|
||||
def FreadFn = fn usz!(void*, char[] buffer);
|
||||
|
||||
$if !$defined(native_fopen_fn):
|
||||
FopenFn native_fopen_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_fclose_fn):
|
||||
FcloseFn native_fclose_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_freopen_fn):
|
||||
FreopenFn native_freopen_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_fseek_fn):
|
||||
FseekFn native_fseek_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_ftell_fn):
|
||||
FtellFn native_ftell_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_fwrite_fn):
|
||||
FwriteFn native_fwrite_fn @weak;
|
||||
$endif
|
||||
$if !$defined(native_fread_fn):
|
||||
FreadFn native_fread_fn @weak;
|
||||
$endif
|
||||
|
||||
|
||||
/**
|
||||
* @require mode.len > 0
|
||||
* @require filename.len > 0
|
||||
**/
|
||||
fn void*! native_fopen(String filename, String mode) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_fopen_fn) return native_fopen_fn(filename, mode);
|
||||
unreachable("Tried to call fopen without support.");
|
||||
$else
|
||||
@pool()
|
||||
{
|
||||
$if env::os_is_win32():
|
||||
void* file = (CFile)_wfopen(filename.to_temp_utf16(), filename.to_temp_utf16())!;
|
||||
$else
|
||||
void* file = libc::fopen(filename.zstr_tcopy(), mode.zstr_tcopy());
|
||||
$endif
|
||||
return file ?: file_open_errno()?;
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @require mode.len > 0
|
||||
* @require filename.len > 0
|
||||
**/
|
||||
fn void*! native_freopen(void* file, String filename, String mode) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_freopen_fn) return native_freopen_fn(file, filename, mode);
|
||||
unreachable("Tried to call freopen without support.");
|
||||
$else
|
||||
@pool()
|
||||
{
|
||||
$if env::os_is_win32():
|
||||
file = _wfreopen(filename.to_temp_utf16(), mode.to_temp_utf16(), file)!;
|
||||
$else
|
||||
file = libc::freopen(filename.zstr_tcopy(), mode.zstr_tcopy(), file);
|
||||
$endif
|
||||
return file ?: file_open_errno()?;
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_fseek_fn) return native_fseek_fn(file, offset, seek_mode);
|
||||
unreachable("Tried to call fseek without support.");
|
||||
$else
|
||||
$if env::os_is_win32():
|
||||
bool success = _fseeki64(file, (long)offset, (int)seek_mode) == 0;
|
||||
$else
|
||||
bool success = libc::fseek(file, (SeekIndex)offset, (CInt)seek_mode) == 0;
|
||||
$endif
|
||||
if (!success) return file_seek_errno()?;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn usz! native_ftell(CFile file) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_ftell_fn) return native_ftell_fn(file);
|
||||
unreachable("Tried to call ftell without support.");
|
||||
$else
|
||||
$if env::os_is_win32():
|
||||
long index = _ftelli64(file);
|
||||
return index >= 0 ? index : file_seek_errno()?;
|
||||
$else
|
||||
SeekIndex index = libc::ftell(file);
|
||||
return index >= 0 ? index : file_seek_errno()?;
|
||||
$endif
|
||||
$endif
|
||||
}
|
||||
|
||||
fn usz! native_fwrite(CFile file, char[] buffer) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_fwrite_fn) return native_fwrite_fn(file, buffer);
|
||||
unreachable("Tried to call fwrite without support.");
|
||||
$else
|
||||
return libc::fwrite(buffer.ptr, 1, buffer.len, file);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn usz! native_fread(CFile file, char[] buffer) @inline
|
||||
{
|
||||
$if !env::COMPILER_LIBC_AVAILABLE:
|
||||
if (native_fread_fn) return native_fread_fn(file, buffer);
|
||||
unreachable("Tried to call fread without support.");
|
||||
$else
|
||||
return libc::fread(buffer.ptr, 1, buffer.len, file);
|
||||
$endif
|
||||
}
|
||||
|
||||
macro anyfault file_open_errno() @local
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES: return IoError.NO_PERMISSION;
|
||||
case errno::EDQUOT: return IoError.OUT_OF_SPACE;
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID;
|
||||
case errno::EEXIST: return IoError.ALREADY_EXISTS;
|
||||
case errno::EINTR: return IoError.INTERRUPTED;
|
||||
case errno::EFAULT: return IoError.GENERAL_ERROR;
|
||||
case errno::EISDIR: return IoError.FILE_IS_DIR;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED;
|
||||
case errno::EMFILE: return IoError.TOO_MANY_DESCRIPTORS;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG;
|
||||
case errno::ENFILE: return IoError.OUT_OF_SPACE;
|
||||
case errno::ENOTDIR: return IoError.FILE_NOT_DIR;
|
||||
case errno::ENOENT: return IoError.FILE_NOT_FOUND;
|
||||
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
|
||||
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
|
||||
case errno::EOVERFLOW: return IoError.OVERFLOW;
|
||||
case errno::EROFS: return IoError.READ_ONLY;
|
||||
case errno::EOPNOTSUPP: return IoError.UNSUPPORTED_OPERATION;
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE;
|
||||
case errno::EWOULDBLOCK: return IoError.WOULD_BLOCK;
|
||||
default: return IoError.UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
macro anyfault file_seek_errno() @local
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ESPIPE: return IoError.FILE_IS_PIPE;
|
||||
case errno::EPIPE: return IoError.FILE_IS_PIPE;
|
||||
case errno::EOVERFLOW: return IoError.OVERFLOW;
|
||||
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
|
||||
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE;
|
||||
case errno::EINVAL: return IoError.INVALID_POSITION;
|
||||
case errno::EINTR: return IoError.INTERRUPTED;
|
||||
case errno::EFBIG: return IoError.OUT_OF_SPACE;
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID;
|
||||
case errno::EAGAIN: return IoError.WOULD_BLOCK;
|
||||
default: return IoError.UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// Win functions
|
||||
$if env::os_is_win32():
|
||||
extern fn void* _wfopen(Char16*, Char16*) @local;
|
||||
extern fn void* _wfreopen(Char16*, Char16*, CFile) @local;
|
||||
extern fn int _fseeki64(CFile, long, int) @local;
|
||||
extern fn long _ftelli64(CFile) @local;
|
||||
$endif
|
||||
|
||||
$if env::os_is_posix():
|
||||
extern fn CInt access(ZString path, CInt mode);
|
||||
$endif
|
||||
129
lib/std/io/os/file_libc.c3
Normal file
129
lib/std/io/os/file_libc.c3
Normal file
@@ -0,0 +1,129 @@
|
||||
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()
|
||||
{
|
||||
$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)
|
||||
{
|
||||
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, (CInt)seek_mode)) return file_seek_errno()?;
|
||||
}
|
||||
|
||||
|
||||
fn usz! native_ftell(CFile file) @inline
|
||||
{
|
||||
long index = libc::ftell(file);
|
||||
return index >= 0 ? (usz)index : file_seek_errno()?;
|
||||
}
|
||||
|
||||
fn usz! native_fwrite(CFile file, char[] buffer) @inline
|
||||
{
|
||||
return libc::fwrite(buffer.ptr, 1, buffer.len, file);
|
||||
}
|
||||
|
||||
fn usz! native_fread(CFile file, char[] buffer) @inline
|
||||
{
|
||||
return libc::fread(buffer.ptr, 1, buffer.len, file);
|
||||
}
|
||||
|
||||
macro anyfault file_open_errno() @local
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES: return IoError.NO_PERMISSION;
|
||||
case errno::EDQUOT: return IoError.OUT_OF_SPACE;
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID;
|
||||
case errno::EEXIST: return IoError.ALREADY_EXISTS;
|
||||
case errno::EINTR: return IoError.INTERRUPTED;
|
||||
case errno::EFAULT: return IoError.GENERAL_ERROR;
|
||||
case errno::EISDIR: return IoError.FILE_IS_DIR;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED;
|
||||
case errno::EMFILE: return IoError.TOO_MANY_DESCRIPTORS;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG;
|
||||
case errno::ENFILE: return IoError.OUT_OF_SPACE;
|
||||
case errno::ENOTDIR: return IoError.FILE_NOT_DIR;
|
||||
case errno::ENOENT: return IoError.FILE_NOT_FOUND;
|
||||
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
|
||||
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
|
||||
case errno::EOVERFLOW: return IoError.OVERFLOW;
|
||||
case errno::EROFS: return IoError.READ_ONLY;
|
||||
case errno::EOPNOTSUPP: return IoError.UNSUPPORTED_OPERATION;
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE;
|
||||
case errno::EWOULDBLOCK: return IoError.WOULD_BLOCK;
|
||||
default: return IoError.UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
macro anyfault file_seek_errno() @local
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ESPIPE: return IoError.FILE_IS_PIPE;
|
||||
case errno::EPIPE: return IoError.FILE_IS_PIPE;
|
||||
case errno::EOVERFLOW: return IoError.OVERFLOW;
|
||||
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
|
||||
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
|
||||
case errno::EIO: return IoError.INCOMPLETE_WRITE;
|
||||
case errno::EINVAL: return IoError.INVALID_POSITION;
|
||||
case errno::EINTR: return IoError.INTERRUPTED;
|
||||
case errno::EFBIG: return IoError.OUT_OF_SPACE;
|
||||
case errno::EBADF: return IoError.FILE_NOT_VALID;
|
||||
case errno::EAGAIN: return IoError.WOULD_BLOCK;
|
||||
default: return IoError.UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
75
lib/std/io/os/file_nolibc.c3
Normal file
75
lib/std/io/os/file_nolibc.c3
Normal file
@@ -0,0 +1,75 @@
|
||||
module std::io::os @if(env::NO_LIBC);
|
||||
import libc;
|
||||
|
||||
def FopenFn = fn void*!(String, String);
|
||||
def FreopenFn = fn void*!(void*, String, String);
|
||||
def FcloseFn = fn void!(void*);
|
||||
def FseekFn = fn void!(void*, isz, Seek);
|
||||
def FtellFn = fn usz!(void*);
|
||||
def FwriteFn = fn usz!(void*, char[] buffer);
|
||||
def FreadFn = fn usz!(void*, char[] buffer);
|
||||
def RemoveFn = fn void!(String);
|
||||
|
||||
FopenFn native_fopen_fn @weak @if(!$defined(native_fopen_fn));
|
||||
FcloseFn native_fclose_fn @weak @if(!$defined(native_fclose_fn));
|
||||
FreopenFn native_freopen_fn @weak @if(!$defined(native_freopen_fn));
|
||||
FseekFn native_fseek_fn @weak @if(!$defined(native_fseek_fn));
|
||||
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));
|
||||
|
||||
/**
|
||||
* @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
|
||||
**/
|
||||
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
|
||||
**/
|
||||
fn void*! native_freopen(void* file, String filename, String mode) @inline
|
||||
{
|
||||
if (native_freopen_fn) return native_freopen_fn(file, filename, mode);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
|
||||
{
|
||||
if (native_fseek_fn) return native_fseek_fn(file, offset, seek_mode);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! native_ftell(CFile file) @inline
|
||||
{
|
||||
if (native_ftell_fn) return native_ftell_fn(file);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! native_fwrite(CFile file, char[] buffer) @inline
|
||||
{
|
||||
if (native_fwrite_fn) return native_fwrite_fn(file, buffer);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
|
||||
fn usz! native_fread(CFile file, char[] buffer) @inline
|
||||
{
|
||||
if (native_fread_fn) return native_fread_fn(file, buffer);
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
116
lib/std/io/os/fileinfo.c3
Normal file
116
lib/std/io/os/fileinfo.c3
Normal file
@@ -0,0 +1,116 @@
|
||||
module std::io::os;
|
||||
import libc;
|
||||
|
||||
fn void! native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
$if env::DARWIN || env::LINUX:
|
||||
int res = libc::stat(path.zstr_tcopy(), stat);
|
||||
$else
|
||||
unreachable("Stat unimplemented");
|
||||
int res = 0;
|
||||
$endif
|
||||
if (res != 0)
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EBADF:
|
||||
return IoError.FILE_NOT_VALID?;
|
||||
case errno::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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
File f = file::open(path, "r")!;
|
||||
defer (void)f.close();
|
||||
return f.seek(0, Seek.END)!;
|
||||
}
|
||||
|
||||
fn usz! native_file_size(String path) @if(env::DARWIN)
|
||||
{
|
||||
Stat stat;
|
||||
native_stat(&stat, path)!;
|
||||
return stat.st_size;
|
||||
}
|
||||
|
||||
fn bool native_file_or_dir_exists(String path)
|
||||
{
|
||||
$switch
|
||||
$case env::DARWIN:
|
||||
$case env::LINUX:
|
||||
Stat stat;
|
||||
return @ok(native_stat(&stat, path));
|
||||
$case env::WIN32:
|
||||
@pool()
|
||||
{
|
||||
return (bool)win32::pathFileExistsW(path.to_temp_utf16()) ?? false;
|
||||
};
|
||||
$case env::POSIX:
|
||||
@pool()
|
||||
{
|
||||
return posix::access(path.zstr_tcopy(), 0 /* F_OK */) != -1;
|
||||
};
|
||||
$default:
|
||||
unreachable("Not supported");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn bool native_is_file(String path)
|
||||
{
|
||||
$switch
|
||||
$case env::DARWIN:
|
||||
$case env::LINUX:
|
||||
Stat stat;
|
||||
return @ok(native_stat(&stat, path)) && stat.st_mode & libc::S_IFREG;
|
||||
$default:
|
||||
File! f = file::open(path, "r");
|
||||
defer (void)f.close();
|
||||
return @ok(f);
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn bool native_is_dir(String path)
|
||||
{
|
||||
$if env::DARWIN || env::LINUX:
|
||||
Stat stat;
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
module std::io::file::os;
|
||||
import libc;
|
||||
|
||||
$if env::os_is_darwin():
|
||||
|
||||
struct DarwinTimespec @private
|
||||
{
|
||||
long tv_sec;
|
||||
long tv_nsec;
|
||||
}
|
||||
struct Darwin64Stat @private
|
||||
{
|
||||
int st_dev;
|
||||
ushort st_mode;
|
||||
ushort st_nlink;
|
||||
ulong st_ino;
|
||||
uint st_uid;
|
||||
uint st_gid;
|
||||
int st_rdev;
|
||||
DarwinTimespec st_atimespec; // time of last access
|
||||
DarwinTimespec st_mtimespec; // time of last data modification
|
||||
DarwinTimespec st_ctimespec; // time of last status change
|
||||
DarwinTimespec st_birthtimespec; // time of file creation(birth)
|
||||
long st_size;
|
||||
long st_blocks;
|
||||
int st_blocksize;
|
||||
uint st_flags;
|
||||
uint st_gen;
|
||||
int st_lspare;
|
||||
long[2] st_qspare;
|
||||
}
|
||||
extern fn int _stat(ZString str, Darwin64Stat* stat) @extern("stat64");
|
||||
|
||||
const S_IFMT = 0o170000; // type of file mask
|
||||
const S_IFIFO = 0o010000; // named pipe (fifo)
|
||||
const S_IFCHR = 0o020000; // character special
|
||||
const S_IFDIR = 0o040000; // directory
|
||||
const S_IFBLK = 0o060000; // block special
|
||||
const S_IFREG = 0o100000; // regular
|
||||
const S_IFLNK = 0o120000; // symbolic link
|
||||
const S_IFSOCK = 0o140000; // socket
|
||||
|
||||
fn usz! native_file_size(String path)
|
||||
{
|
||||
Darwin64Stat stat;
|
||||
read_stat(&stat, path)!;
|
||||
return stat.st_size;
|
||||
}
|
||||
|
||||
fn bool native_file_or_dir_exists(String path)
|
||||
{
|
||||
Darwin64Stat stat;
|
||||
return @ok(read_stat(&stat, path));
|
||||
}
|
||||
|
||||
fn bool native_is_file(String path)
|
||||
{
|
||||
Darwin64Stat stat;
|
||||
return @ok(read_stat(&stat, path)) && stat.st_mode & S_IFREG;
|
||||
}
|
||||
|
||||
fn bool native_is_dir(String path)
|
||||
{
|
||||
Darwin64Stat stat;
|
||||
return @ok(read_stat(&stat, path)) && stat.st_mode & S_IFDIR;
|
||||
}
|
||||
|
||||
fn void! read_stat(Darwin64Stat* stat, String path) @local
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
int res = _stat(path.zstr_tcopy(), stat);
|
||||
if (res != 0)
|
||||
{
|
||||
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?;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
$endif
|
||||
@@ -1,181 +0,0 @@
|
||||
module std::io::file::os;
|
||||
|
||||
// native_temp_directory, for non Win32
|
||||
$if !env::os_is_win32():
|
||||
|
||||
fn Path! native_temp_directory(Allocator* using = mem::heap())
|
||||
{
|
||||
foreach (String env : { "TMPDIR", "TMP", "TEMP", "TEMPDIR" })
|
||||
{
|
||||
String tmpdir = env::get_var(env) ?? "";
|
||||
if (tmpdir) return path::new(tmpdir, using);
|
||||
}
|
||||
return path::new("/tmp", using);
|
||||
}
|
||||
|
||||
$if env::COMPILER_LIBC_AVAILABLE:
|
||||
|
||||
extern fn void* opendir(ZString);
|
||||
extern fn void closedir(void*);
|
||||
extern fn int remove(ZString);
|
||||
|
||||
const DT_UNKNOWN = 0;
|
||||
const DT_FIFO = 1;
|
||||
const DT_CHR = 2;
|
||||
const DT_DIR = 4;
|
||||
const DT_BLK = 6;
|
||||
const DT_REG = 8;
|
||||
const DT_LNK = 10;
|
||||
const DT_SOCK = 12;
|
||||
const DT_WHT = 14;
|
||||
|
||||
fn PathList! native_readdir(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator* using)
|
||||
{
|
||||
PathList list;
|
||||
list.init(.using = using);
|
||||
void* directory = opendir(dir.as_str() ? dir.as_zstr() : (ZString)".");
|
||||
defer if (directory) closedir(directory);
|
||||
if (!directory) return (path::is_dir(dir) ? IoError.CANNOT_READ_DIR : IoError.FILE_NOT_DIR)?;
|
||||
NativeDirentry* entry;
|
||||
while ((entry = readdir(directory)))
|
||||
{
|
||||
String name = ((ZString)&entry.name).as_str();
|
||||
if (!name || name == "." || name == "..") continue;
|
||||
if (entry.type == DT_LNK && no_symlinks) continue;
|
||||
if (entry.type == DT_DIR && no_dirs) continue;
|
||||
Path path = path::new(name.copy(using), using)!!;
|
||||
list.append(path);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require dir.as_str()
|
||||
**/
|
||||
fn void! native_rmtree(Path dir)
|
||||
{
|
||||
void* directory = opendir(dir.as_zstr());
|
||||
defer if (directory) closedir(directory);
|
||||
if (!directory) return path::is_dir(dir) ? IoError.CANNOT_READ_DIR? : IoError.FILE_NOT_DIR?;
|
||||
NativeDirentry* entry;
|
||||
while ((entry = readdir(directory)))
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
String name = ((ZString)&entry.name).as_str();
|
||||
if (!name || name == "." || name == "..") continue;
|
||||
Path new_path = dir.tappend(name)!;
|
||||
if (entry.type == DT_DIR)
|
||||
{
|
||||
native_rmtree(new_path)!;
|
||||
continue;
|
||||
}
|
||||
if (remove(new_path.as_zstr()))
|
||||
{
|
||||
// TODO improve
|
||||
return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
};
|
||||
}
|
||||
os::native_rmdir(dir)!;
|
||||
}
|
||||
|
||||
$endif
|
||||
|
||||
$endif
|
||||
|
||||
$if !env::os_is_darwin() && !env::os_is_win32():
|
||||
|
||||
fn usz! native_file_size(String path)
|
||||
{
|
||||
File f = file::open(path, "r")!;
|
||||
defer (void)f.close();
|
||||
return f.seek(0, Seek.END)!;
|
||||
}
|
||||
|
||||
$if env::os_is_posix() && env::COMPILER_LIBC_AVAILABLE:
|
||||
|
||||
fn bool native_file_or_dir_exists(String path)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
return os::access(path.zstr_tcopy(), 0 /* F_OK */) != -1;
|
||||
};
|
||||
}
|
||||
|
||||
fn bool native_is_file(String path)
|
||||
{
|
||||
File! f = file::open(path, "r");
|
||||
defer (void)f.close();
|
||||
return @ok(f);
|
||||
}
|
||||
|
||||
fn bool native_is_dir(String path)
|
||||
{
|
||||
return native_file_or_dir_exists(path) && !native_is_file(path);
|
||||
}
|
||||
|
||||
$else
|
||||
|
||||
fn bool native_file_or_dir_exists(String path)
|
||||
{
|
||||
unreachable("Tried to call file_or_dir_exists without support.");
|
||||
}
|
||||
|
||||
fn bool native_is_dir(String path)
|
||||
{
|
||||
unreachable("Tried to call is_dir without support.");
|
||||
}
|
||||
|
||||
fn bool native_is_file(String path)
|
||||
{
|
||||
unreachable("Tried to call is_file without support.");
|
||||
}
|
||||
|
||||
$endif
|
||||
|
||||
$endif
|
||||
|
||||
$switch (env::OS_TYPE)
|
||||
$case IOS:
|
||||
$case MACOS:
|
||||
$case TVOS:
|
||||
$case WATCHOS:
|
||||
|
||||
$if env::ARCH_TYPE == X86_64:
|
||||
extern fn NativeDirentry* readdir(void*) @extern("readdir$INODE64");
|
||||
$else
|
||||
extern fn NativeDirentry* readdir(void*) @extern("readdir");
|
||||
$endif
|
||||
|
||||
struct NativeDirentry
|
||||
{
|
||||
usz ino;
|
||||
usz seekoff;
|
||||
ushort reclen;
|
||||
ushort namelen;
|
||||
char type;
|
||||
char[1024] name;
|
||||
}
|
||||
$case LINUX:
|
||||
extern fn NativeDirentry* readdir(void*);
|
||||
struct NativeDirentry
|
||||
{
|
||||
usz ino;
|
||||
isz seekoff;
|
||||
ushort reclen;
|
||||
char type;
|
||||
char[*] name;
|
||||
}
|
||||
$default:
|
||||
// Fix this as we go along.
|
||||
extern fn NativeDirentry* readdir(void*);
|
||||
struct NativeDirentry
|
||||
{
|
||||
usz ino;
|
||||
isz seekoff;
|
||||
ushort reclen;
|
||||
char type;
|
||||
char[*] name;
|
||||
}
|
||||
$endswitch
|
||||
@@ -1,113 +0,0 @@
|
||||
module std::io::file::os;
|
||||
import std::os::win32;
|
||||
|
||||
$if env::os_is_win32():
|
||||
|
||||
const Win32_DWORD FILE_ATTRIBUTE_READONLY = 0x01;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_HIDDEN = 0x02;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_SYSTEM = 0x04;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_DIRECTORY = 0x10;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_ARCHIVE = 0x20;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_DEVICE = 0x40;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_NORMAL = 0x80;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_TEMPORARY = 0x100;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_SPARSE_FILE = 0x200;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_REPARSE_POINT = 0x400;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_COMPRESSED = 0x800;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_OFFLINE = 0x1000;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_ENCRYPTED = 0x4000;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x8000;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_VIRTUAL = 0x10000;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x20000;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_EA = 0x40000;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_PINNED = 0x80000;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_UNPINNED = 0x100000;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_RECALL_ON_OPEN = 0x40000;
|
||||
const Win32_DWORD FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS = 0x400000;
|
||||
|
||||
fn usz! native_file_size(String path)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
Char16[] path16 = path.to_temp_utf16()!;
|
||||
Win32_FILE_ATTRIBUTE_DATA data;
|
||||
win32::win32_GetFileAttributesExW(path16, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data);
|
||||
Win32_LARGE_INTEGER size;
|
||||
size.lowPart = data.nFileSizeLow;
|
||||
size.highPart = data.nFileSizeHigh;
|
||||
return (usz)size.quadPart;
|
||||
};
|
||||
}
|
||||
|
||||
fn bool native_file_or_dir_exists(String path)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
return (bool)win32::win32_PathFileExistsW(path.to_temp_utf16()) ?? false;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
fn bool native_is_file(String path)
|
||||
{
|
||||
File! f = file::open(path, "r");
|
||||
defer (void)f.close();
|
||||
return @ok(f);
|
||||
}
|
||||
|
||||
fn bool native_is_dir(String path)
|
||||
{
|
||||
return native_file_or_dir_exists(path) && !native_is_file(path);
|
||||
}
|
||||
|
||||
fn void! native_rmtree(Path path)
|
||||
{
|
||||
Win32_WIN32_FIND_DATAW find_data;
|
||||
|
||||
String s = path.as_str().tconcat("\\*");
|
||||
Win32_HANDLE find = win32::win32_FindFirstFileW(s.to_utf16(mem::temp()), &find_data)!;
|
||||
|
||||
if (find == win32::INVALID_HANDLE_VALUE) return IoError.CANNOT_READ_DIR?;
|
||||
|
||||
defer win32::win32_FindClose(find);
|
||||
do
|
||||
{
|
||||
String filename = string::from_zutf16(&find_data.cFileName, mem::temp())!;
|
||||
if (filename == "." || filename == "..") continue;
|
||||
Path file_path = path.tappend(filename)!;
|
||||
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
{
|
||||
native_rmtree(file_path)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
win32::win32_DeleteFileW(file_path.as_str().to_utf16(mem::temp()));
|
||||
}
|
||||
} while (win32::win32_FindNextFileW(find, &find_data) != 0);
|
||||
os::native_rmdir(path)!;
|
||||
}
|
||||
|
||||
fn Path! native_temp_directory(Allocator* using = mem::heap())
|
||||
{
|
||||
@stack_mem(256; Allocator* mem)
|
||||
{
|
||||
Win32_DWORD len = win32::win32_GetTempPathW(0, null);
|
||||
if (!len) return IoError.GENERAL_ERROR?;
|
||||
Char16[] buff = malloc(Char16, len + 1, .using = mem);
|
||||
if (!win32::win32_GetTempPathW(len, buff)) return IoError.GENERAL_ERROR?;
|
||||
return path::new(string::from_utf16(buff[:len], .using = mem), using);
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
}else if(method == file_size_methods::get_attributes){
|
||||
WIN32_FILE_ATTRIBUTE_DATA file_attr_data;
|
||||
if(GetFileAttributesEx(path, GetFileExInfoStandard, &file_attr_data)){
|
||||
file_size.LowPart = file_attr_data.nFileSizeLow;
|
||||
file_size.HighPart = file_attr_data.nFileSizeHigh;
|
||||
}
|
||||
}
|
||||
*/
|
||||
$endif
|
||||
@@ -1,51 +1,41 @@
|
||||
module std::io::os;
|
||||
import libc;
|
||||
|
||||
$switch
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_win32():
|
||||
|
||||
macro String! getcwd(Allocator* using = mem::heap())
|
||||
macro String! getcwd(Allocator* allocator = mem::heap())
|
||||
{
|
||||
const DEFAULT_BUFFER = 256;
|
||||
Char16[DEFAULT_BUFFER] buffer;
|
||||
Char16 *res = win32::_wgetcwd(&buffer, DEFAULT_BUFFER);
|
||||
bool free = false;
|
||||
defer if (free) libc::free(res);
|
||||
if (!res)
|
||||
{
|
||||
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR?;
|
||||
res = win32::_wgetcwd(null, 0);
|
||||
free = true;
|
||||
}
|
||||
Char16[] str16 = res[:win32::wcslen(res)];
|
||||
return string::from_utf16(str16, using);
|
||||
$switch
|
||||
$case env::WIN32:
|
||||
const DEFAULT_BUFFER = 256;
|
||||
Char16[DEFAULT_BUFFER] buffer;
|
||||
WString res = win32::_wgetcwd(&buffer, DEFAULT_BUFFER);
|
||||
bool free = false;
|
||||
defer if (free) libc::free(res);
|
||||
if (!res)
|
||||
{
|
||||
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR?;
|
||||
res = win32::_wgetcwd(null, 0);
|
||||
free = true;
|
||||
}
|
||||
Char16[] str16 = res[:win32::wcslen(res)];
|
||||
return string::new_from_utf16(str16, allocator);
|
||||
|
||||
$case env::POSIX:
|
||||
const usz DEFAULT_BUFFER = 256;
|
||||
char[DEFAULT_BUFFER] buffer;
|
||||
ZString res = posix::getcwd(&buffer, DEFAULT_BUFFER);
|
||||
bool free = false;
|
||||
if (!res)
|
||||
{
|
||||
// Improve error
|
||||
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR?;
|
||||
res = posix::getcwd(null, 0);
|
||||
free = true;
|
||||
}
|
||||
defer if (free) libc::free((void*)res);
|
||||
return res.copy(allocator);
|
||||
|
||||
$default:
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_posix():
|
||||
|
||||
macro String! getcwd(Allocator* using = mem::heap())
|
||||
{
|
||||
const usz DEFAULT_BUFFER = 256;
|
||||
char[DEFAULT_BUFFER] buffer;
|
||||
ZString res = posix::getcwd(&buffer, DEFAULT_BUFFER);
|
||||
bool free = false;
|
||||
if (!res)
|
||||
{
|
||||
// Improve error
|
||||
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR?;
|
||||
res = posix::getcwd(null, 0);
|
||||
free = true;
|
||||
}
|
||||
defer if (free) libc::free((void*)res);
|
||||
return res.copy(using);
|
||||
}
|
||||
|
||||
$default:
|
||||
|
||||
fn String! getcwd(Allocator* using = mem::heap())
|
||||
{
|
||||
unreachable("'getcwd' not available");
|
||||
}
|
||||
|
||||
$endswitch
|
||||
49
lib/std/io/os/ls.c3
Normal file
49
lib/std/io/os/ls.c3
Normal file
@@ -0,0 +1,49 @@
|
||||
module std::io::file::os @if(env::POSIX);
|
||||
|
||||
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator* allocator)
|
||||
{
|
||||
PathList list;
|
||||
list.init_new(.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)?;
|
||||
Posix_dirent* entry;
|
||||
while ((entry = posix::readdir(directory)))
|
||||
{
|
||||
String name = ((ZString)&entry.name).str_view();
|
||||
if (!name || name == "." || name == "..") continue;
|
||||
if (entry.d_type == posix::DT_LNK && no_symlinks) continue;
|
||||
if (entry.d_type == posix::DT_DIR && no_dirs) continue;
|
||||
Path path = path::new(name, allocator)!!;
|
||||
list.append(path);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
module std::io::os @if(env::WIN32);
|
||||
|
||||
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator* allocator)
|
||||
{
|
||||
PathList list;
|
||||
list.init_new(.allocator = allocator);
|
||||
|
||||
@pool(allocator)
|
||||
{
|
||||
WString result = dir.str_view().tconcat(`\*`).to_temp_wstring()!!;
|
||||
Win32_WIN32_FIND_DATAW find_data;
|
||||
Win32_HANDLE find = win32::findFirstFileW(result, &find_data);
|
||||
if (find == win32::INVALID_HANDLE_VALUE) return IoError.CANNOT_READ_DIR?;
|
||||
defer win32::findClose(find);
|
||||
do
|
||||
{
|
||||
if (no_dirs && (find_data.dwFileAttributes & win32::FILE_ATTRIBUTE_DIRECTORY)) continue;
|
||||
@pool(allocator)
|
||||
{
|
||||
String filename = string::temp_from_wstring((WString)&find_data.cFileName)!;
|
||||
if (filename == ".." || filename == ".") continue;
|
||||
list.append(path::new(filename, allocator)!);
|
||||
};
|
||||
} while (win32::findNextFileW(find, &find_data));
|
||||
return list;
|
||||
};
|
||||
}
|
||||
@@ -4,60 +4,47 @@ import std::io::path;
|
||||
import std::os::win32;
|
||||
import std::os::posix;
|
||||
|
||||
$switch
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_posix():
|
||||
|
||||
macro bool! native_mkdir(Path path, MkdirPermissions permissions)
|
||||
{
|
||||
if (!posix::mkdir(path.as_zstr(), permissions == NORMAL ? 0o777 : 0o700)) return true;
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES:
|
||||
case errno::EPERM:
|
||||
case errno::EROFS:
|
||||
case errno::EFAULT: return IoError.NO_PERMISSION?;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
|
||||
case errno::EDQUOT:
|
||||
case errno::ENOSPC: return IoError.OUT_OF_SPACE?;
|
||||
case errno::EISDIR:
|
||||
case errno::EEXIST: return false;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
|
||||
case errno::ENOTDIR: return IoError.FILE_NOT_FOUND?;
|
||||
default: return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
}
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_win32():
|
||||
|
||||
macro bool! native_mkdir(Path path, MkdirPermissions permissions)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
// TODO security attributes
|
||||
if (win32::win32_CreateDirectoryW(path.as_str().to_temp_utf16()!!, null)) return true;
|
||||
switch (win32::win32_GetLastError())
|
||||
{
|
||||
case win32::ERROR_ACCESS_DENIED:
|
||||
return IoError.NO_PERMISSION?;
|
||||
case win32::ERROR_DISK_FULL:
|
||||
return IoError.OUT_OF_SPACE?;
|
||||
case win32::ERROR_ALREADY_EXISTS:
|
||||
return false;
|
||||
case win32::ERROR_PATH_NOT_FOUND:
|
||||
return IoError.FILE_NOT_FOUND?;
|
||||
default:
|
||||
return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$default:
|
||||
|
||||
fn bool! native_mkdir(Path path, MkdirPermissions permissions)
|
||||
{
|
||||
unreachable("'mkdir' not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$endswitch
|
||||
$switch
|
||||
$case env::POSIX:
|
||||
if (!posix::mkdir(path.as_zstr(), permissions == NORMAL ? 0o777 : 0o700)) return true;
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EACCES:
|
||||
case errno::EPERM:
|
||||
case errno::EROFS:
|
||||
case errno::EFAULT: return IoError.NO_PERMISSION?;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
|
||||
case errno::EDQUOT:
|
||||
case errno::ENOSPC: return IoError.OUT_OF_SPACE?;
|
||||
case errno::EISDIR:
|
||||
case errno::EEXIST: return false;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
|
||||
case errno::ENOTDIR: return IoError.FILE_NOT_FOUND?;
|
||||
default: return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
$case env::WIN32:
|
||||
@pool()
|
||||
{
|
||||
// TODO security attributes
|
||||
if (win32::createDirectoryW(path.str_view().to_temp_utf16()!!, null)) return true;
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_ACCESS_DENIED:
|
||||
return IoError.NO_PERMISSION?;
|
||||
case win32::ERROR_DISK_FULL:
|
||||
return IoError.OUT_OF_SPACE?;
|
||||
case win32::ERROR_ALREADY_EXISTS:
|
||||
return false;
|
||||
case win32::ERROR_PATH_NOT_FOUND:
|
||||
return IoError.FILE_NOT_FOUND?;
|
||||
default:
|
||||
return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
};
|
||||
$default:
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
$endswitch
|
||||
}
|
||||
@@ -4,58 +4,45 @@ import std::io::path;
|
||||
import std::os::win32;
|
||||
import std::os::posix;
|
||||
|
||||
$switch
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_posix():
|
||||
|
||||
macro bool! native_rmdir(Path path)
|
||||
{
|
||||
if (!posix::rmdir(path.as_zstr())) return true;
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EBUSY: return IoError.BUSY?;
|
||||
case errno::EACCES:
|
||||
case errno::EPERM:
|
||||
case errno::EROFS:
|
||||
case errno::EFAULT: return IoError.NO_PERMISSION?;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
|
||||
case errno::ENOTDIR:
|
||||
case errno::ENOENT: return false;
|
||||
case errno::ENOTEMPTY: return IoError.DIR_NOT_EMPTY?;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
|
||||
default: return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
$switch
|
||||
$case env::POSIX:
|
||||
if (!posix::rmdir(path.as_zstr())) return true;
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EBUSY: return IoError.BUSY?;
|
||||
case errno::EACCES:
|
||||
case errno::EPERM:
|
||||
case errno::EROFS:
|
||||
case errno::EFAULT: return IoError.NO_PERMISSION?;
|
||||
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
|
||||
case errno::ENOTDIR:
|
||||
case errno::ENOENT: return false;
|
||||
case errno::ENOTEMPTY: return IoError.DIR_NOT_EMPTY?;
|
||||
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
|
||||
default: return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
$case env::WIN32:
|
||||
@pool()
|
||||
{
|
||||
if (win32::removeDirectoryW(path.str_view().to_temp_utf16()!!)) return true;
|
||||
switch (win32::getLastError())
|
||||
{
|
||||
case win32::ERROR_ACCESS_DENIED:
|
||||
return IoError.NO_PERMISSION?;
|
||||
case win32::ERROR_CURRENT_DIRECTORY:
|
||||
return IoError.BUSY?;
|
||||
case win32::ERROR_DIR_NOT_EMPTY:
|
||||
return IoError.DIR_NOT_EMPTY?;
|
||||
case win32::ERROR_DIRECTORY:
|
||||
case win32::ERROR_PATH_NOT_FOUND:
|
||||
return false;
|
||||
default:
|
||||
return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
};
|
||||
$default:
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_win32():
|
||||
|
||||
macro bool! native_rmdir(Path path)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
if (win32::win32_RemoveDirectoryW(path.as_str().to_temp_utf16()!!)) return true;
|
||||
switch (win32::win32_GetLastError())
|
||||
{
|
||||
case win32::ERROR_ACCESS_DENIED:
|
||||
return IoError.NO_PERMISSION?;
|
||||
case win32::ERROR_CURRENT_DIRECTORY:
|
||||
return IoError.BUSY?;
|
||||
case win32::ERROR_DIR_NOT_EMPTY:
|
||||
return IoError.DIR_NOT_EMPTY?;
|
||||
case win32::ERROR_DIRECTORY:
|
||||
case win32::ERROR_PATH_NOT_FOUND:
|
||||
return false;
|
||||
default:
|
||||
return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$default:
|
||||
|
||||
fn bool! native_rmdir(Path path)
|
||||
{
|
||||
unreachable("'rmdir' not available");
|
||||
}
|
||||
|
||||
$endswitch
|
||||
63
lib/std/io/os/rmtree.c3
Normal file
63
lib/std/io/os/rmtree.c3
Normal file
@@ -0,0 +1,63 @@
|
||||
module std::io::file::os @if(env::POSIX);
|
||||
import libc;
|
||||
|
||||
/**
|
||||
* @require dir.str_view()
|
||||
**/
|
||||
fn void! native_rmtree(Path dir)
|
||||
{
|
||||
DIRPtr directory = posix::opendir(dir.as_zstr());
|
||||
defer if (directory) posix::closedir(directory);
|
||||
if (!directory) return path::is_dir(dir) ? IoError.CANNOT_READ_DIR? : IoError.FILE_NOT_DIR?;
|
||||
Posix_dirent* entry;
|
||||
while ((entry = posix::readdir(directory)))
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
String name = ((ZString)&entry.name).str_view();
|
||||
if (!name || name == "." || name == "..") continue;
|
||||
Path new_path = dir.tappend(name)!;
|
||||
if (entry.d_type == posix::DT_DIR)
|
||||
{
|
||||
native_rmtree(new_path)!;
|
||||
continue;
|
||||
}
|
||||
if (libc::remove(new_path.as_zstr()))
|
||||
{
|
||||
// TODO improve
|
||||
return IoError.GENERAL_ERROR?;
|
||||
}
|
||||
};
|
||||
}
|
||||
os::native_rmdir(dir)!;
|
||||
}
|
||||
|
||||
module std::io::os @if(env::WIN32);
|
||||
|
||||
fn void! native_rmtree(Path path)
|
||||
{
|
||||
Win32_WIN32_FIND_DATAW find_data;
|
||||
String s = path.str_view().tconcat("\\*");
|
||||
Win32_HANDLE find = win32::findFirstFileW(s.to_temp_utf16(), &find_data)!;
|
||||
|
||||
if (find == win32::INVALID_HANDLE_VALUE) return IoError.CANNOT_READ_DIR?;
|
||||
defer win32::findClose(find);
|
||||
do
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
String filename = string::new_from_wstring((WString)&find_data.cFileName, mem::temp())!;
|
||||
if (filename == "." || filename == "..") continue;
|
||||
Path file_path = path.tappend(filename)!;
|
||||
if (find_data.dwFileAttributes & win32::FILE_ATTRIBUTE_DIRECTORY)
|
||||
{
|
||||
native_rmtree(file_path)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
win32::deleteFileW(file_path.str_view().to_temp_wstring()!!);
|
||||
}
|
||||
};
|
||||
} while (win32::findNextFileW(find, &find_data) != 0);
|
||||
os::native_rmdir(path)!;
|
||||
}
|
||||
30
lib/std/io/os/temp_directory.c3
Normal file
30
lib/std/io/os/temp_directory.c3
Normal file
@@ -0,0 +1,30 @@
|
||||
module std::io::os @if(env::LIBC);
|
||||
|
||||
fn Path! native_temp_directory(Allocator* allocator = mem::heap()) @if(!env::WIN32)
|
||||
{
|
||||
foreach (String env : { "TMPDIR", "TMP", "TEMP", "TEMPDIR" })
|
||||
{
|
||||
String tmpdir = env::get_var(env) ?? "";
|
||||
if (tmpdir) return path::new(tmpdir, allocator);
|
||||
}
|
||||
return path::new("/tmp", allocator);
|
||||
}
|
||||
|
||||
fn Path! native_temp_directory(Allocator* allocator = mem::heap()) @if(env::WIN32)
|
||||
{
|
||||
@pool(allocator)
|
||||
{
|
||||
Win32_DWORD len = win32::getTempPathW(0, null);
|
||||
if (!len) return IoError.GENERAL_ERROR?;
|
||||
Char16[] buff = mem::temp_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);
|
||||
|
||||
macro Path! native_temp_directory(Allocator* allocator = mem::heap())
|
||||
{
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
module std::io::path;
|
||||
import std::collections::list;
|
||||
|
||||
const PathEnv DEFAULT_PATH_ENV = env::os_is_win32() ? PathEnv.WIN32 : PathEnv.POSIX;
|
||||
const PathEnv DEFAULT_PATH_ENV = env::WIN32 ? PathEnv.WIN32 : PathEnv.POSIX;
|
||||
const char PREFERRED_SEPARATOR_WIN32 = '\\';
|
||||
const char PREFERRED_SEPARATOR_POSIX = '/';
|
||||
const char PREFERRED_SEPARATOR = env::os_is_win32() ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX;
|
||||
const char PREFERRED_SEPARATOR = env::WIN32 ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX;
|
||||
|
||||
def PathList = List<Path>;
|
||||
def PathList = List(<Path>);
|
||||
|
||||
fault PathResult
|
||||
{
|
||||
@@ -14,7 +14,7 @@ fault PathResult
|
||||
NO_PARENT,
|
||||
}
|
||||
|
||||
struct Path
|
||||
struct Path (Printable)
|
||||
{
|
||||
String path_string;
|
||||
PathEnv env;
|
||||
@@ -26,22 +26,22 @@ enum PathEnv
|
||||
POSIX
|
||||
}
|
||||
|
||||
|
||||
fn Path! getcwd(Allocator* using = mem::heap())
|
||||
fn Path! getcwd(Allocator* allocator = mem::heap())
|
||||
{
|
||||
@stack_mem(256; Allocator* mem)
|
||||
@pool(allocator)
|
||||
{
|
||||
return new(os::getcwd(mem), using);
|
||||
return new(os::getcwd(mem::temp()), allocator);
|
||||
};
|
||||
}
|
||||
|
||||
fn bool is_dir(Path path) => os::native_is_dir(path.as_str());
|
||||
fn bool is_file(Path path) => os::native_is_file(path.as_str());
|
||||
fn usz! file_size(Path path) => os::native_file_size(path.as_str());
|
||||
fn bool exists(Path path) => os::native_file_or_dir_exists(path.as_str());
|
||||
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! tgetcwd() => getcwd(mem::temp()) @inline;
|
||||
fn void! chdir(Path path) => os::native_chdir(path) @inline;
|
||||
fn Path! temp_directory(Allocator* using = mem::heap()) => os::native_temp_directory(using);
|
||||
fn Path! temp_directory(Allocator* allocator = mem::heap()) => os::native_temp_directory(allocator);
|
||||
fn void! delete(Path path) => os::native_remove(path.str_view()) @inline;
|
||||
|
||||
macro bool is_separator(char c, PathEnv path_env = DEFAULT_PATH_ENV)
|
||||
{
|
||||
@@ -58,9 +58,13 @@ macro bool is_win32_separator(char c)
|
||||
return c == '/' || c == '\\';
|
||||
}
|
||||
|
||||
fn Path[]! ls(Path path)
|
||||
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator* allocator = mem::heap())
|
||||
{
|
||||
unreachable();
|
||||
$if $defined(os::native_ls):
|
||||
return os::native_ls(dir, no_dirs, no_symlinks, mask, allocator);
|
||||
$else
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
$endif
|
||||
}
|
||||
|
||||
enum MkdirPermissions
|
||||
@@ -70,8 +74,6 @@ enum MkdirPermissions
|
||||
USER_AND_ADMIN
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
|
||||
{
|
||||
if (!path.path_string.len) return PathResult.INVALID_PATH?;
|
||||
@@ -96,89 +98,129 @@ fn bool! rmdir(Path path)
|
||||
fn void! rmtree(Path path)
|
||||
{
|
||||
if (!path.path_string.len) return PathResult.INVALID_PATH?;
|
||||
$if $defined(os::native_rmtree):
|
||||
return os::native_rmtree(path);
|
||||
$else
|
||||
assert(false, "rmtree is not available");
|
||||
$endif
|
||||
$if $defined(os::native_rmtree):
|
||||
return os::native_rmtree(path);
|
||||
$else
|
||||
return IoError.UNSUPPORTED_OPERATION?;
|
||||
$endif
|
||||
}
|
||||
|
||||
fn Path! new(String path, Allocator* using = mem::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
|
||||
fn Path! new(String path, Allocator* allocator = mem::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
|
||||
{
|
||||
return { normalize(path.copy(using), path_env), path_env };
|
||||
return { normalize(path.copy(allocator), path_env), path_env };
|
||||
}
|
||||
|
||||
fn Path! new_windows(String path, Allocator* using = mem::heap())
|
||||
fn Path! temp_new(String path, PathEnv path_env = DEFAULT_PATH_ENV)
|
||||
{
|
||||
return new(path, using, WIN32);
|
||||
return new(path, mem::temp(), path_env);
|
||||
}
|
||||
|
||||
fn Path! new_posix(String path, Allocator* using = mem::heap())
|
||||
fn Path! new_win32_wstring(WString path, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return new(path, using, POSIX);
|
||||
@pool(allocator)
|
||||
{
|
||||
return path::new(string::temp_from_wstring(path)!, .allocator = allocator);
|
||||
};
|
||||
}
|
||||
|
||||
fn bool Path.equals(Path p1, Path p2)
|
||||
fn Path! new_windows(String path, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return p1.env == p2.env && p1.path_string == p2.path_string;
|
||||
return new(path, allocator, WIN32);
|
||||
}
|
||||
|
||||
fn Path! new_posix(String path, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return new(path, allocator, POSIX);
|
||||
}
|
||||
|
||||
fn bool Path.equals(self, Path p2)
|
||||
{
|
||||
return self.env == p2.env && self.path_string == p2.path_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the string to the current path.
|
||||
*
|
||||
* @param [in] path
|
||||
* @param [in] filename
|
||||
* @ensure return.path_string.len >= path.path_string.len
|
||||
**/
|
||||
fn Path! Path.append(Path path, String filename, Allocator* using = mem::heap())
|
||||
fn Path! Path.append(self, String filename, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (!path.path_string.len) return new(filename, using, path.env)!;
|
||||
assert(!is_separator(path.path_string[^1], path.env));
|
||||
if (!self.path_string.len) return new(filename, allocator, self.env)!;
|
||||
assert(!is_separator(self.path_string[^1], self.env));
|
||||
|
||||
@stack_mem(256; Allocator* mem)
|
||||
@pool(allocator)
|
||||
{
|
||||
DString dstr = dstring::new_with_capacity(path.path_string.len + 1 + filename.len, .using = mem);
|
||||
dstr.append(path.path_string);
|
||||
DString dstr = dstring::temp_with_capacity(self.path_string.len + 1 + filename.len);
|
||||
dstr.append(self.path_string);
|
||||
dstr.append(PREFERRED_SEPARATOR);
|
||||
dstr.append(filename);
|
||||
return { normalize(dstr.copy_str(using), path.env), path.env };
|
||||
return { normalize(dstr.copy_str(allocator), self.env), self.env };
|
||||
};
|
||||
}
|
||||
|
||||
fn Path! Path.tappend(Path path, String filename) => path.append(filename, mem::temp());
|
||||
fn Path! Path.tappend(self, String filename) => self.append(filename, mem::temp());
|
||||
|
||||
fn usz Path.start_of_base_name(Path path) @local
|
||||
fn usz Path.start_of_base_name(self) @local
|
||||
{
|
||||
String path_str = path.path_string;
|
||||
String path_str = self.path_string;
|
||||
if (!path_str.len) return 0;
|
||||
if (path.env == PathEnv.WIN32)
|
||||
if (self.env == PathEnv.WIN32)
|
||||
{
|
||||
return path_str.rindex_of(`\`) + 1 ?? volume_name_len(path_str, path.env)!!;
|
||||
return path_str.rindex_of_char('\\') + 1 ?? volume_name_len(path_str, self.env)!!;
|
||||
}
|
||||
return path_str.rindex_of("/") + 1 ?? 0;
|
||||
return path_str.rindex_of_char('/') + 1 ?? 0;
|
||||
}
|
||||
|
||||
fn String Path.basename(Path path)
|
||||
fn bool! Path.is_absolute(self)
|
||||
{
|
||||
usz basename_start = path.start_of_base_name();
|
||||
String path_str = path.path_string;
|
||||
String path_str = self.str_view();
|
||||
if (!path_str.len) return false;
|
||||
usz path_start = volume_name_len(path_str, self.env)!;
|
||||
return path_start < path_str.len && is_separator(path_str[path_start], self.env);
|
||||
}
|
||||
|
||||
fn Path! Path.absolute(self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
String path_str = self.str_view();
|
||||
if (!path_str.len) path_str = ".";
|
||||
if (path_str == ".")
|
||||
{
|
||||
String cwd = os::getcwd(mem::temp())!;
|
||||
return new(cwd, allocator, self.env);
|
||||
}
|
||||
switch (self.env)
|
||||
{
|
||||
case WIN32:
|
||||
usz path_start = volume_name_len(path_str, self.env)!;
|
||||
if (path_start > 0) return self;
|
||||
case POSIX:
|
||||
if (path_str[0] == PREFERRED_SEPARATOR_POSIX) return self;
|
||||
}
|
||||
String cwd = os::getcwd(mem::temp())!;
|
||||
return Path{ cwd, self.env }.append(path_str, allocator)!;
|
||||
}
|
||||
|
||||
fn String Path.basename(self)
|
||||
{
|
||||
usz basename_start = self.start_of_base_name();
|
||||
String path_str = self.path_string;
|
||||
if (basename_start == path_str.len) return "";
|
||||
return path_str[basename_start..];
|
||||
}
|
||||
|
||||
fn String Path.dirname(Path path)
|
||||
fn String Path.dirname(self)
|
||||
{
|
||||
usz basename_start = path.start_of_base_name();
|
||||
String path_str = path.path_string;
|
||||
usz basename_start = self.start_of_base_name();
|
||||
String path_str = self.path_string;
|
||||
if (basename_start == 0) return "";
|
||||
usz start = volume_name_len(path_str, path.env)!!;
|
||||
usz start = volume_name_len(path_str, self.env)!!;
|
||||
if (basename_start <= start + 1) return path_str[:basename_start];
|
||||
return path_str[:basename_start - 1];
|
||||
}
|
||||
|
||||
fn String! Path.extension(Path path)
|
||||
fn String! Path.extension(self)
|
||||
{
|
||||
String basename = path.basename();
|
||||
String basename = self.basename();
|
||||
usz index = basename.rindex_of(".")!;
|
||||
// Plain ".foo" does not have an
|
||||
if (index == 0) return SearchResult.MISSING?;
|
||||
@@ -186,11 +228,11 @@ fn String! Path.extension(Path path)
|
||||
return basename[index + 1..];
|
||||
}
|
||||
|
||||
fn String Path.volume_name(Path path)
|
||||
fn String Path.volume_name(self)
|
||||
{
|
||||
usz len = volume_name_len(path.as_str(), path.env)!!;
|
||||
usz len = volume_name_len(self.str_view(), self.env)!!;
|
||||
if (!len) return "";
|
||||
return path.path_string[:len];
|
||||
return self.path_string[:len];
|
||||
}
|
||||
|
||||
fn usz! volume_name_len(String path, PathEnv path_env) @local
|
||||
@@ -198,38 +240,38 @@ fn usz! volume_name_len(String path, PathEnv path_env) @local
|
||||
usz len = path.len;
|
||||
if (len < 2 || path_env != PathEnv.WIN32) return 0;
|
||||
switch (path[0])
|
||||
{
|
||||
case '\\':
|
||||
// "\\" paths.. must be longer than 2
|
||||
if (len == 2) return 0;
|
||||
{
|
||||
case '\\':
|
||||
// "\\" paths.. must be longer than 2
|
||||
if (len == 2) return 0;
|
||||
int count = 1;
|
||||
while (count < len && path[count] == '\\') count++;
|
||||
// Not 2 => folded paths
|
||||
if (count != 2) return 0;
|
||||
// Check that we have a name followed by '/'
|
||||
for (usz i = 2; i < len; i++)
|
||||
{
|
||||
char c = path[i];
|
||||
if (is_win32_separator(c)) return i;
|
||||
if (is_reserved_win32_path_char(c)) return PathResult.INVALID_PATH?;
|
||||
}
|
||||
return PathResult.INVALID_PATH?;
|
||||
case 'A'..'Z':
|
||||
case 'a'..'z':
|
||||
return path[1] == ':' ? 2 : 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
{
|
||||
char c = path[i];
|
||||
if (is_win32_separator(c)) return i;
|
||||
if (is_reserved_win32_path_char(c)) return PathResult.INVALID_PATH?;
|
||||
}
|
||||
return PathResult.INVALID_PATH?;
|
||||
case 'A'..'Z':
|
||||
case 'a'..'z':
|
||||
return path[1] == ':' ? 2 : 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn Path! Path.parent(Path path)
|
||||
fn Path! Path.parent(self)
|
||||
{
|
||||
if (path.path_string.len == 1 && is_separator(path.path_string[0], path.env)) return PathResult.NO_PARENT?;
|
||||
foreach_r(i, c : path.path_string)
|
||||
if (self.path_string.len == 1 && is_separator(self.path_string[0], self.env)) return PathResult.NO_PARENT?;
|
||||
foreach_r(i, c : self.path_string)
|
||||
{
|
||||
if (is_separator(c, path.env))
|
||||
if (is_separator(c, self.env))
|
||||
{
|
||||
return { path.path_string[:i], path.env };
|
||||
return { self.path_string[:i], self.env };
|
||||
}
|
||||
}
|
||||
return PathResult.NO_PARENT?;
|
||||
@@ -237,8 +279,8 @@ fn Path! Path.parent(Path path)
|
||||
|
||||
fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
|
||||
{
|
||||
if (!path_str.len) return path_str;
|
||||
usz path_start = volume_name_len(path_str, path_env)!;
|
||||
if (!path_str.len) return "";
|
||||
usz path_start = volume_name_len(path_str, path_env)!;
|
||||
usz path_len = path_str.len;
|
||||
if (path_start == path_len) return path_str;
|
||||
char path_separator = path_env == PathEnv.WIN32 ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX;
|
||||
@@ -278,13 +320,20 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
|
||||
// Get the number of dots until next separator, expecting 1 or 2
|
||||
bool is_last = i == path_len - 1;
|
||||
int dots = 1;
|
||||
if (!is_last && path_str[i + 1] == '.')
|
||||
if (!is_last)
|
||||
{
|
||||
dots = 2;
|
||||
is_last = i == path_len - 2;
|
||||
if (!is_last && !is_separator(path_str[i + 2], path_env))
|
||||
char next = path_str[i + 1];
|
||||
switch
|
||||
{
|
||||
dots = 0;
|
||||
case next == '.':
|
||||
dots = 2;
|
||||
is_last = i == path_len - 2;
|
||||
if (!is_last && !is_separator(path_str[i + 2], path_env))
|
||||
{
|
||||
dots = 0;
|
||||
}
|
||||
case !is_separator(next, path_env):
|
||||
dots = 0;
|
||||
}
|
||||
}
|
||||
switch (dots)
|
||||
@@ -323,7 +372,7 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
|
||||
}
|
||||
// Reading, we go from /../abc to /../abc
|
||||
// ^ ^
|
||||
i += 2;
|
||||
i += 2;
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
@@ -339,16 +388,16 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
|
||||
return path_str[:len];
|
||||
}
|
||||
|
||||
fn ZString Path.as_zstr(Path path) => (ZString)path.path_string.ptr;
|
||||
fn ZString Path.as_zstr(self) => (ZString)self.path_string.ptr;
|
||||
|
||||
fn String Path.root_directory(Path path)
|
||||
fn String Path.root_directory(self)
|
||||
{
|
||||
String path_str = path.as_str();
|
||||
String path_str = self.str_view();
|
||||
usz len = path_str.len;
|
||||
if (!len) return "";
|
||||
if (path.env == PathEnv.WIN32)
|
||||
if (self.env == PathEnv.WIN32)
|
||||
{
|
||||
usz root_len = volume_name_len(path_str, path.env)!!;
|
||||
usz root_len = volume_name_len(path_str, self.env)!!;
|
||||
if (root_len == len || !is_win32_separator(path_str[root_len])) return "";
|
||||
return path_str[root_len..root_len];
|
||||
}
|
||||
@@ -363,24 +412,57 @@ fn String Path.root_directory(Path path)
|
||||
return path_str;
|
||||
}
|
||||
|
||||
def PathWalker = fn bool! (Path, bool is_dir, void*);
|
||||
|
||||
fn String Path.as_str(Path path)
|
||||
/*
|
||||
* Walk the path recursively. PathWalker is run on every file and
|
||||
* directory found. Return true to abort the walk.
|
||||
*/
|
||||
fn bool! Path.walk(self, PathWalker w, void* data)
|
||||
{
|
||||
return path.path_string;
|
||||
const PATH_MAX = 512;
|
||||
@stack_mem(PATH_MAX; 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.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;
|
||||
}
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
fn String Path.str_view(self) @inline
|
||||
{
|
||||
return self.path_string;
|
||||
}
|
||||
|
||||
|
||||
fn bool Path.has_suffix(Path path, String str)
|
||||
fn bool Path.has_suffix(self, String str)
|
||||
{
|
||||
return path.as_str().ends_with(str);
|
||||
return self.str_view().ends_with(str);
|
||||
}
|
||||
|
||||
fn void Path.free(self)
|
||||
{
|
||||
free(self.path_string.ptr);
|
||||
}
|
||||
|
||||
|
||||
fn void Path.free(Path path)
|
||||
fn usz! Path.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
free(path.path_string.ptr);
|
||||
return formatter.print(self.str_view());
|
||||
}
|
||||
|
||||
fn String Path.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
|
||||
{
|
||||
return self.str_view().copy(allocator);
|
||||
}
|
||||
|
||||
const bool[256] RESERVED_PATH_CHAR_POSIX = {
|
||||
[0] = true,
|
||||
@@ -409,5 +491,4 @@ macro bool is_reserved_path_char(char c, PathEnv path_env = DEFAULT_PATH_ENV)
|
||||
return path_env == PathEnv.WIN32
|
||||
? RESERVED_PATH_CHAR_WIN32[c]
|
||||
: RESERVED_PATH_CHAR_POSIX[c];
|
||||
}
|
||||
|
||||
}
|
||||
233
lib/std/io/stream.c3
Normal file
233
lib/std/io/stream.c3
Normal file
@@ -0,0 +1,233 @@
|
||||
module std::io;
|
||||
|
||||
interface InStream
|
||||
{
|
||||
fn void! close() @optional;
|
||||
fn usz! seek(isz offset, Seek seek) @optional;
|
||||
fn usz len() @optional;
|
||||
fn usz! available() @optional;
|
||||
fn usz! read(char[] buffer);
|
||||
fn char! read_byte();
|
||||
fn usz! write_to(OutStream* out) @optional;
|
||||
fn void! pushback_byte() @optional;
|
||||
}
|
||||
|
||||
|
||||
interface OutStream
|
||||
{
|
||||
fn void! destroy() @optional;
|
||||
fn void! close() @optional;
|
||||
fn void! flush() @optional;
|
||||
fn usz! write(char[] bytes);
|
||||
fn void! write_byte(char c);
|
||||
fn usz! read_to(InStream* in) @optional;
|
||||
}
|
||||
|
||||
fn usz! available(InStream* s)
|
||||
{
|
||||
if (&s.available) return s.available();
|
||||
if (&s.seek)
|
||||
{
|
||||
usz curr = s.seek(0, Seek.CURSOR)!;
|
||||
usz len = s.seek(0, Seek.END)!;
|
||||
s.seek(curr, Seek.SET)!;
|
||||
return len - curr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro bool @is_instream(#expr)
|
||||
{
|
||||
return $assignable(#expr, InStream*);
|
||||
}
|
||||
|
||||
macro bool @is_outstream(#expr)
|
||||
{
|
||||
return $assignable(#expr, OutStream*);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
macro usz! write_any(stream, any* ref)
|
||||
{
|
||||
return write_all(stream, ((char*)ref)[:ref.type.sizeof]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require @is_instream(stream)
|
||||
*/
|
||||
macro usz! read_all(stream, char[] buffer)
|
||||
{
|
||||
if (buffer.len == 0) return 0;
|
||||
usz n = stream.read(buffer)!;
|
||||
if (n != buffer.len) return IoError.UNEXPECTED_EOF?;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require @is_outstream(stream)
|
||||
*/
|
||||
macro usz! write_all(stream, char[] buffer)
|
||||
{
|
||||
if (buffer.len == 0) return 0;
|
||||
usz n = stream.write(buffer)!;
|
||||
if (n != buffer.len) return IoError.INCOMPLETE_WRITE?;
|
||||
return n;
|
||||
}
|
||||
|
||||
macro usz! @read_using_read_byte(&s, char[] buffer)
|
||||
{
|
||||
usz len = 0;
|
||||
foreach (&cptr : buffer)
|
||||
{
|
||||
char! c = s.read_byte();
|
||||
if (catch err = c)
|
||||
{
|
||||
case IoError.EOF: return len;
|
||||
default: return err?;
|
||||
}
|
||||
*cptr = c;
|
||||
len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
macro void! @write_byte_using_write(&s, char c)
|
||||
{
|
||||
char[1] buff = { c };
|
||||
(*s).write(&buff)!;
|
||||
}
|
||||
|
||||
|
||||
macro char! @read_byte_using_read(&s)
|
||||
{
|
||||
char[1] buffer;
|
||||
usz read = (*s).read(&buffer)!;
|
||||
if (read != 1) return IoError.EOF?;
|
||||
return buffer[0];
|
||||
}
|
||||
|
||||
def ReadByteFn = fn char!();
|
||||
|
||||
|
||||
macro usz! @write_using_write_byte(&s, char[] bytes)
|
||||
{
|
||||
foreach (c : bytes) s.write_byte(self, c)!;
|
||||
return bytes.len;
|
||||
}
|
||||
|
||||
macro void! @pushback_using_seek(&s)
|
||||
{
|
||||
s.seek(-1, CURSOR)!;
|
||||
}
|
||||
|
||||
fn usz! copy_to(InStream* in, OutStream* dst, char[] buffer = {})
|
||||
{
|
||||
if (buffer.len) return copy_through_buffer(in, dst, buffer);
|
||||
if (&in.write_to) return in.write_to(dst);
|
||||
if (&dst.read_to) return dst.read_to(in);
|
||||
$switch (env::MEMORY_ENV)
|
||||
$case NORMAL:
|
||||
@pool()
|
||||
{
|
||||
return copy_through_buffer(in, dst, mem::temp_array(char, 4096));
|
||||
};
|
||||
$case SMALL:
|
||||
@pool()
|
||||
{
|
||||
return copy_through_buffer(in, dst, mem::temp_array(char, 1024));
|
||||
};
|
||||
$case TINY:
|
||||
$case NONE:
|
||||
return copy_through_buffer(in, dst, &&(char[256]{}));
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro usz! copy_through_buffer(InStream *in, OutStream* dst, char[] buffer) @local
|
||||
{
|
||||
usz total_copied;
|
||||
while (true)
|
||||
{
|
||||
usz! len = in.read(buffer);
|
||||
if (catch err = len)
|
||||
{
|
||||
case IoError.EOF: return total_copied;
|
||||
default: return err?;
|
||||
}
|
||||
if (!len) return total_copied;
|
||||
usz written = dst.write(buffer[:len])!;
|
||||
total_copied += len;
|
||||
if (written != len) return IoError.INCOMPLETE_WRITE?;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
**/
|
||||
macro usz! read_varint(stream, x_ptr)
|
||||
{
|
||||
var $Type = $typefrom($typeof(x_ptr).inner);
|
||||
const MAX = MAX_VARS[$Type.sizeof];
|
||||
$Type x;
|
||||
uint shift;
|
||||
usz n;
|
||||
for (usz i = 0; i < MAX; i++)
|
||||
{
|
||||
char! c = stream.read_byte();
|
||||
if (catch err = c)
|
||||
{
|
||||
case IoError.EOF:
|
||||
return IoError.UNEXPECTED_EOF?;
|
||||
default:
|
||||
return err?;
|
||||
}
|
||||
n++;
|
||||
if (c & 0x80 == 0)
|
||||
{
|
||||
if (i + 1 == MAX && c > 1) break;
|
||||
x |= c << shift;
|
||||
$if $Type.kindof == SIGNED_INT:
|
||||
x = x & 1 == 0 ? x >> 1 : ~(x >> 1);
|
||||
$endif
|
||||
*x_ptr = x;
|
||||
return n;
|
||||
}
|
||||
x |= (c & 0x7F) << shift;
|
||||
shift += 7;
|
||||
}
|
||||
return MathError.OVERFLOW?;
|
||||
}
|
||||
/**
|
||||
* @require @is_outstream(stream)
|
||||
* @require @typekind(x).is_int()
|
||||
**/
|
||||
macro usz! write_varint(stream, x)
|
||||
{
|
||||
var $Type = $typeof(x);
|
||||
const MAX = MAX_VARS[$Type.sizeof];
|
||||
char[MAX] buffer @noinit;
|
||||
usz i;
|
||||
while (x >= 0x80)
|
||||
{
|
||||
buffer[i] = (char)(x | 0x80);
|
||||
x >>= 7;
|
||||
i++;
|
||||
}
|
||||
buffer[i] = (char)x;
|
||||
return write_all(stream, buffer[:i + 1]);
|
||||
}
|
||||
133
lib/std/io/stream/buffer.c3
Normal file
133
lib/std/io/stream/buffer.c3
Normal file
@@ -0,0 +1,133 @@
|
||||
module std::io;
|
||||
|
||||
struct ReadBuffer (InStream)
|
||||
{
|
||||
InStream* wrapped_stream;
|
||||
char[] bytes;
|
||||
usz read_idx;
|
||||
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"
|
||||
**/
|
||||
fn ReadBuffer* ReadBuffer.init(&self, InStream* wrapped_stream, char[] bytes)
|
||||
{
|
||||
*self = { .wrapped_stream = wrapped_stream, .bytes = bytes };
|
||||
return self;
|
||||
}
|
||||
fn String ReadBuffer.str_view(&self) @inline
|
||||
{
|
||||
return (String)self.bytes[self.read_idx:self.write_idx - self.read_idx];
|
||||
}
|
||||
|
||||
fn void! ReadBuffer.close(&self) @dynamic
|
||||
{
|
||||
if (&self.wrapped_stream.close) self.wrapped_stream.close()!;
|
||||
}
|
||||
|
||||
fn usz! ReadBuffer.read(&self, char[] bytes) @dynamic
|
||||
{
|
||||
if (self.read_idx == self.write_idx)
|
||||
{
|
||||
if (self.read_idx == 0 && bytes.len >= self.bytes.len)
|
||||
{
|
||||
// Read directly into the input buffer.
|
||||
return self.wrapped_stream.read(bytes)!;
|
||||
}
|
||||
self.refill()!;
|
||||
}
|
||||
usz n = min(self.write_idx - self.read_idx, bytes.len);
|
||||
bytes[:n] = self.bytes[self.read_idx:n];
|
||||
self.read_idx += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn char! ReadBuffer.read_byte(&self) @dynamic
|
||||
{
|
||||
if (self.read_idx == self.write_idx) self.refill()!;
|
||||
if (self.read_idx == self.write_idx) return IoError.EOF?;
|
||||
char c = self.bytes[self.read_idx];
|
||||
self.read_idx++;
|
||||
return c;
|
||||
}
|
||||
|
||||
fn void! ReadBuffer.refill(&self) @local @inline
|
||||
{
|
||||
self.read_idx = 0;
|
||||
self.write_idx = self.wrapped_stream.read(self.bytes)!;
|
||||
}
|
||||
|
||||
struct WriteBuffer (OutStream)
|
||||
{
|
||||
OutStream* wrapped_stream;
|
||||
char[] bytes;
|
||||
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"
|
||||
**/
|
||||
fn WriteBuffer* WriteBuffer.init(&self, OutStream* wrapped_stream, char[] bytes)
|
||||
{
|
||||
*self = { .wrapped_stream = wrapped_stream, .bytes = bytes };
|
||||
return self;
|
||||
}
|
||||
|
||||
fn String WriteBuffer.str_view(&self) @inline
|
||||
{
|
||||
return (String)self.bytes[:self.index];
|
||||
}
|
||||
|
||||
fn void! WriteBuffer.close(&self) @dynamic
|
||||
{
|
||||
if (&self.wrapped_stream.close) return self.wrapped_stream.close();
|
||||
}
|
||||
|
||||
fn void! WriteBuffer.flush(&self) @dynamic
|
||||
{
|
||||
self.write_pending()!;
|
||||
if (&self.wrapped_stream.flush) self.wrapped_stream.flush()!;
|
||||
}
|
||||
|
||||
fn usz! WriteBuffer.write(&self, char[] bytes) @dynamic
|
||||
{
|
||||
usz n = self.bytes.len - self.index;
|
||||
if (bytes.len < n)
|
||||
{
|
||||
// Enough room in the buffer.
|
||||
self.bytes[self.index:bytes.len] = bytes[..];
|
||||
self.index += bytes.len;
|
||||
return bytes.len;
|
||||
}
|
||||
self.write_pending()!;
|
||||
if (bytes.len >= self.bytes.len)
|
||||
{
|
||||
// Write directly to the stream.
|
||||
return self.wrapped_stream.write(bytes);
|
||||
}
|
||||
// Buffer the data.
|
||||
self.bytes[:bytes.len] = bytes[..];
|
||||
self.index = bytes.len;
|
||||
return bytes.len;
|
||||
}
|
||||
|
||||
fn void! WriteBuffer.write_byte(&self, char c) @dynamic
|
||||
{
|
||||
usz n = self.bytes.len - self.index;
|
||||
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?;
|
||||
}
|
||||
148
lib/std/io/stream/bytebuffer.c3
Normal file
148
lib/std/io/stream/bytebuffer.c3
Normal file
@@ -0,0 +1,148 @@
|
||||
module std::io;
|
||||
import std::math;
|
||||
|
||||
struct ByteBuffer (InStream, OutStream)
|
||||
{
|
||||
Allocator* allocator;
|
||||
usz max_read;
|
||||
char[] bytes;
|
||||
usz read_idx;
|
||||
usz write_idx;
|
||||
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_new(&self, usz max_read, usz initial_capacity = 16, Allocator* allocator = mem::heap())
|
||||
{
|
||||
*self = { .allocator = allocator, .max_read = max_read };
|
||||
initial_capacity = max(initial_capacity, 16);
|
||||
self.grow(initial_capacity)!;
|
||||
return self;
|
||||
}
|
||||
|
||||
fn ByteBuffer*! ByteBuffer.init_temp(&self, usz max_read, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init_new(max_read, initial_capacity, mem::temp());
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
fn void ByteBuffer.free(&self)
|
||||
{
|
||||
if (self.allocator) self.allocator.free(self.bytes);
|
||||
*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)!;
|
||||
self.bytes[self.write_idx:bytes.len] = bytes[..];
|
||||
self.write_idx += bytes.len;
|
||||
return bytes.len;
|
||||
}
|
||||
|
||||
fn void! ByteBuffer.write_byte(&self, char c) @dynamic
|
||||
{
|
||||
usz cap = self.bytes.len - self.write_idx;
|
||||
if (cap == 0) self.grow(1)!;
|
||||
self.bytes[self.write_idx] = c;
|
||||
self.write_idx++;
|
||||
}
|
||||
|
||||
fn usz! ByteBuffer.read(&self, char[] bytes) @dynamic
|
||||
{
|
||||
usz readable = self.write_idx - self.read_idx;
|
||||
if (readable == 0)
|
||||
{
|
||||
self.has_last = false;
|
||||
return IoError.EOF?;
|
||||
}
|
||||
usz n = min(readable, bytes.len);
|
||||
bytes[:n] = self.bytes[self.read_idx:n];
|
||||
self.read_idx += n;
|
||||
self.has_last = n > 0;
|
||||
self.shrink();
|
||||
return n;
|
||||
}
|
||||
|
||||
fn char! ByteBuffer.read_byte(&self) @dynamic
|
||||
{
|
||||
usz readable = self.write_idx - self.read_idx;
|
||||
if (readable == 0)
|
||||
{
|
||||
self.has_last = false;
|
||||
return IoError.EOF?;
|
||||
}
|
||||
char c = self.bytes[self.read_idx];
|
||||
self.read_idx++;
|
||||
self.has_last = true;
|
||||
self.shrink();
|
||||
return c;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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?;
|
||||
assert(self.read_idx > 0);
|
||||
self.read_idx--;
|
||||
self.has_last = false;
|
||||
}
|
||||
|
||||
fn usz! ByteBuffer.seek(&self, isz offset, Seek seek) @dynamic
|
||||
{
|
||||
switch (seek)
|
||||
{
|
||||
case SET:
|
||||
if (offset < 0 || offset > self.write_idx) return IoError.INVALID_POSITION?;
|
||||
self.read_idx = offset;
|
||||
return offset;
|
||||
case CURSOR:
|
||||
if ((offset < 0 && self.read_idx < -offset) ||
|
||||
(offset > 0 && self.read_idx + offset > self.write_idx)) return IoError.INVALID_POSITION?;
|
||||
self.read_idx += offset;
|
||||
case END:
|
||||
if (offset < 0 || offset > self.write_idx) return IoError.INVALID_POSITION?;
|
||||
self.read_idx = self.write_idx - offset;
|
||||
}
|
||||
return self.read_idx;
|
||||
}
|
||||
|
||||
fn usz! ByteBuffer.available(&self) @inline @dynamic
|
||||
{
|
||||
return self.write_idx - self.read_idx;
|
||||
}
|
||||
|
||||
fn void! ByteBuffer.grow(&self, usz n)
|
||||
{
|
||||
n = math::next_power_of_2(n);
|
||||
char* p = self.allocator.realloc_aligned(self.bytes, n, .alignment = char.alignof)!;
|
||||
self.bytes = p[:n];
|
||||
}
|
||||
|
||||
macro ByteBuffer.shrink(&self)
|
||||
{
|
||||
if (self.read_idx >= self.max_read)
|
||||
{
|
||||
// Drop the read data besides the last byte (for pushback_byte).
|
||||
usz readable = self.write_idx - self.read_idx;
|
||||
self.bytes[:1 + readable] = self.bytes[self.read_idx - 1:1 + readable];
|
||||
self.write_idx = 1 + readable;
|
||||
self.read_idx = 1;
|
||||
}
|
||||
}
|
||||
@@ -1,80 +1,68 @@
|
||||
module std::io;
|
||||
import std::math;
|
||||
|
||||
struct ByteReader
|
||||
struct ByteReader (InStream)
|
||||
{
|
||||
char[] bytes;
|
||||
usz index;
|
||||
}
|
||||
|
||||
fn void ByteReader.init(ByteReader* reader, char[] bytes)
|
||||
fn usz ByteReader.len(&self) @dynamic
|
||||
{
|
||||
*reader = { .bytes = bytes };
|
||||
return self.bytes.len;
|
||||
}
|
||||
|
||||
fn Stream ByteReader.as_stream(ByteReader* reader)
|
||||
fn ByteReader* ByteReader.init(&self, char[] bytes)
|
||||
{
|
||||
return { .fns = &bytereader_interface, .data = reader };
|
||||
*self = { .bytes = bytes };
|
||||
return self;
|
||||
}
|
||||
|
||||
fn usz! ByteReader.read(ByteReader* reader, char[] bytes)
|
||||
fn usz! ByteReader.read(&self, char[] bytes) @dynamic
|
||||
{
|
||||
if (reader.index >= reader.bytes.len) return IoError.EOF?;
|
||||
usz len = math::min(reader.bytes.len - reader.index, bytes.len);
|
||||
if (self.index >= self.bytes.len) return IoError.EOF?;
|
||||
usz len = min(self.bytes.len - self.index, bytes.len);
|
||||
if (len == 0) return 0;
|
||||
mem::copy(bytes.ptr, &reader.bytes[reader.index], len);
|
||||
reader.index += len;
|
||||
mem::copy(bytes.ptr, &self.bytes[self.index], len);
|
||||
self.index += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
fn char! ByteReader.read_byte(ByteReader* reader)
|
||||
fn char! ByteReader.read_byte(&self) @dynamic
|
||||
{
|
||||
if (reader.index >= reader.bytes.len) return IoError.EOF?;
|
||||
return reader.bytes[reader.index++];
|
||||
if (self.index >= self.bytes.len) return IoError.EOF?;
|
||||
return self.bytes[self.index++];
|
||||
}
|
||||
|
||||
fn void! ByteReader.pushback_byte(ByteReader* reader)
|
||||
fn void! ByteReader.pushback_byte(&self) @dynamic
|
||||
{
|
||||
if (!reader.index) return IoError.INVALID_PUSHBACK?;
|
||||
reader.index--;
|
||||
if (!self.index) return IoError.INVALID_PUSHBACK?;
|
||||
self.index--;
|
||||
}
|
||||
|
||||
fn usz! ByteReader.seek(ByteReader* reader, isz offset, Seek seek)
|
||||
fn usz! ByteReader.seek(&self, isz offset, Seek seek) @dynamic
|
||||
{
|
||||
isz new_index;
|
||||
switch (seek)
|
||||
{
|
||||
case SET: new_index = offset;
|
||||
case CURSOR: new_index = reader.index + offset;
|
||||
case END: new_index = reader.bytes.len + offset;
|
||||
case CURSOR: new_index = self.index + offset;
|
||||
case END: new_index = self.bytes.len + offset;
|
||||
}
|
||||
if (new_index < 0) return IoError.INVALID_POSITION?;
|
||||
reader.index = new_index;
|
||||
self.index = new_index;
|
||||
return new_index;
|
||||
}
|
||||
|
||||
fn usz! ByteReader.write_stream(ByteReader* reader, Stream* writer)
|
||||
fn usz! ByteReader.write_to(&self, OutStream* writer) @dynamic
|
||||
{
|
||||
if (reader.index >= reader.bytes.len) return 0;
|
||||
usz written = writer.write(reader.bytes[reader.index..])!;
|
||||
reader.index += written;
|
||||
assert(reader.index <= reader.bytes.len);
|
||||
if (self.index >= self.bytes.len) return 0;
|
||||
usz written = writer.write(self.bytes[self.index..])!;
|
||||
self.index += written;
|
||||
assert(self.index <= self.bytes.len);
|
||||
return written;
|
||||
}
|
||||
|
||||
fn usz ByteReader.available(ByteReader* reader)
|
||||
fn usz! ByteReader.available(&self) @inline @dynamic
|
||||
{
|
||||
return math::max((isz)0, (isz)reader.bytes.len - reader.index);
|
||||
}
|
||||
|
||||
StreamInterface bytereader_interface = {
|
||||
.len_fn = fn (s) => ((ByteReader*)s.data).bytes.len,
|
||||
.read_fn = fn (s, char[] bytes) => ((ByteReader*)s.data).read(bytes) @inline,
|
||||
.read_byte_fn = fn (s) => ((ByteReader*)s.data).read_byte() @inline,
|
||||
.pushback_byte_fn = fn (s) => ((ByteReader*)s.data).pushback_byte() @inline,
|
||||
.seek_fn = fn (s, offset, seek) => ((ByteReader*)s.data).seek(offset, seek) @inline,
|
||||
.write_stream_fn = fn (s, writer) => ((ByteReader*)s.data).write_stream(writer) @inline,
|
||||
.available_fn = fn (s) => ((ByteReader*)s.data).available() @inline,
|
||||
};
|
||||
|
||||
|
||||
return max(0, self.bytes.len - self.index);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
module std::io;
|
||||
|
||||
struct ByteWriter
|
||||
struct ByteWriter (OutStream)
|
||||
{
|
||||
char[] bytes;
|
||||
usz index;
|
||||
@@ -8,112 +8,105 @@ struct ByteWriter
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] writer
|
||||
* @param [&in] using
|
||||
* @require writer.bytes.len == 0 "Init may not run on on already initialized data"
|
||||
* @ensure using != null, index == 0
|
||||
* @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 void ByteWriter.init(ByteWriter* writer, Allocator* using = mem::heap())
|
||||
fn ByteWriter* ByteWriter.init_new(&self, Allocator* allocator = mem::heap())
|
||||
{
|
||||
*writer = { .bytes = {}, .allocator = using };
|
||||
}
|
||||
|
||||
fn void ByteWriter.init_buffer(ByteWriter* writer, char[] data)
|
||||
{
|
||||
*writer = { .bytes = data, .allocator = null };
|
||||
*self = { .bytes = {}, .allocator = allocator };
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] writer
|
||||
* @require writer.bytes.len == 0 "Init may not run on on already initialized data"
|
||||
* @param [&inout] self
|
||||
* @require self.bytes.len == 0 "Init may not run on on already initialized data"
|
||||
* @ensure self.index == 0
|
||||
**/
|
||||
fn void ByteWriter.tinit(ByteWriter* writer)
|
||||
fn ByteWriter* ByteWriter.init_temp(&self)
|
||||
{
|
||||
*writer = { .bytes = {}, .allocator = mem::temp() };
|
||||
return self.init_new(mem::temp());
|
||||
}
|
||||
|
||||
fn Stream ByteWriter.as_stream(ByteWriter* writer)
|
||||
fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
|
||||
{
|
||||
return { .fns = &bytewriter_interface, .data = writer };
|
||||
*self = { .bytes = data, .allocator = null };
|
||||
return self;
|
||||
}
|
||||
|
||||
fn void ByteWriter.destroy(ByteWriter* writer)
|
||||
fn void! ByteWriter.destroy(&self) @dynamic
|
||||
{
|
||||
if (!writer.allocator) return;
|
||||
if (void* ptr = writer.bytes.ptr) free(ptr, .using = writer.allocator);
|
||||
*writer = { };
|
||||
if (!self.allocator) return;
|
||||
if (void* ptr = self.bytes.ptr) self.allocator.free(ptr);
|
||||
*self = { };
|
||||
}
|
||||
|
||||
fn String ByteWriter.as_str(ByteWriter* writer)
|
||||
fn String ByteWriter.str_view(&self) @inline
|
||||
{
|
||||
return (String)writer.bytes[:writer.index];
|
||||
return (String)self.bytes[:self.index];
|
||||
}
|
||||
|
||||
fn void! ByteWriter.ensure_capacity(ByteWriter* writer, usz len) @inline
|
||||
fn void! ByteWriter.ensure_capacity(&self, usz len) @inline
|
||||
{
|
||||
if (writer.bytes.len > len) return;
|
||||
if (!writer.allocator) return IoError.OUT_OF_SPACE?;
|
||||
if (self.bytes.len > len) return;
|
||||
if (!self.allocator) return IoError.OUT_OF_SPACE?;
|
||||
if (len < 16) len = 16;
|
||||
usz new_capacity = math::next_power_of_2(len);
|
||||
char* new_ptr = realloc_checked(writer.bytes.ptr, new_capacity, .using = writer.allocator)!;
|
||||
writer.bytes = new_ptr[:new_capacity];
|
||||
char* new_ptr = self.allocator.realloc_checked(self.bytes.ptr, new_capacity)!;
|
||||
self.bytes = new_ptr[:new_capacity];
|
||||
}
|
||||
|
||||
fn usz! ByteWriter.write(ByteWriter* writer, char[] bytes)
|
||||
fn usz! ByteWriter.write(&self, char[] bytes) @dynamic
|
||||
{
|
||||
writer.ensure_capacity(writer.index + bytes.len)!;
|
||||
mem::copy(&writer.bytes[writer.index], bytes.ptr, bytes.len);
|
||||
writer.index += bytes.len;
|
||||
self.ensure_capacity(self.index + bytes.len)!;
|
||||
mem::copy(&self.bytes[self.index], bytes.ptr, bytes.len);
|
||||
self.index += bytes.len;
|
||||
return bytes.len;
|
||||
}
|
||||
|
||||
fn void! ByteWriter.write_byte(ByteWriter* writer, char c)
|
||||
fn void! ByteWriter.write_byte(&self, char c) @dynamic
|
||||
{
|
||||
writer.ensure_capacity(writer.index + 1)!;
|
||||
writer.bytes[writer.index++] = c;
|
||||
self.ensure_capacity(self.index + 1)!;
|
||||
self.bytes[self.index++] = c;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [&inout] writer
|
||||
* @param [&inout] reader
|
||||
* @param [&inout] self
|
||||
* @param reader
|
||||
**/
|
||||
fn usz! ByteWriter.read_from(ByteWriter* writer, Stream* reader)
|
||||
fn usz! ByteWriter.read_from(&self, InStream* reader) @dynamic
|
||||
{
|
||||
if (reader.supports_available())
|
||||
usz start_index = self.index;
|
||||
if (&reader.available)
|
||||
{
|
||||
usz total_read = 0;
|
||||
while (usz available = reader.available()!)
|
||||
{
|
||||
writer.ensure_capacity(writer.index + available)!;
|
||||
usz len = reader.read(writer.bytes[writer.index..])!;
|
||||
total_read += len;
|
||||
writer.index += len;
|
||||
self.ensure_capacity(self.index + available)!;
|
||||
usz read = reader.read(self.bytes[self.index..])!;
|
||||
self.index += read;
|
||||
}
|
||||
return total_read;
|
||||
return self.index - start_index;
|
||||
}
|
||||
if (self.bytes.len == 0)
|
||||
{
|
||||
self.ensure_capacity(16)!;
|
||||
}
|
||||
usz total_read = 0;
|
||||
while (true)
|
||||
{
|
||||
// See how much we can read.
|
||||
usz len_to_read = writer.bytes.len - writer.index;
|
||||
usz len_to_read = self.bytes.len - self.index;
|
||||
// Less than 16 bytes? Double the capacity
|
||||
if (len_to_read < 16)
|
||||
{
|
||||
writer.ensure_capacity(writer.bytes.len * 2)!;
|
||||
self.ensure_capacity(self.bytes.len * 2)!;
|
||||
len_to_read = self.bytes.len - self.index;
|
||||
}
|
||||
// Read into the rest of the buffer
|
||||
usz read = reader.read(writer.bytes[writer.index..])!;
|
||||
writer.index += read;
|
||||
usz read = reader.read(self.bytes[self.index..])!;
|
||||
self.index += read;
|
||||
// Ok, we reached the end.
|
||||
if (read < len_to_read) return total_read;
|
||||
if (read < len_to_read) return self.index - start_index;
|
||||
// Otherwise go another round
|
||||
}
|
||||
}
|
||||
|
||||
StreamInterface bytewriter_interface = {
|
||||
.destroy_fn = fn (s) => ((ByteWriter*)s.data).destroy(),
|
||||
.len_fn = fn (s) => ((ByteWriter*)s.data).bytes.len,
|
||||
.write_fn = fn (s, char[] bytes) => ((ByteWriter*)s.data).write(bytes),
|
||||
.write_byte_fn = fn (s, char c) => ((ByteWriter*)s.data).write_byte(c),
|
||||
.read_stream_fn = fn (s, reader) => ((ByteWriter*)s.data).read_from(reader),
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
module std::io;
|
||||
|
||||
fn Stream DString.as_stream(DString* dstring)
|
||||
{
|
||||
return { .fns = &dstring_interface, .data = dstring };
|
||||
}
|
||||
|
||||
StreamInterface dstring_interface = {
|
||||
.destroy_fn = fn (s) => ((DString*)s.data).free(),
|
||||
.len_fn = fn (s) => ((DString*)s.data).len(),
|
||||
.write_fn = fn (s, char[] bytes) { ((DString*)s.data).append_chars((String)bytes); return bytes.len; },
|
||||
.write_byte_fn = fn (s, char c) => ((DString*)s.data).append_char(c),
|
||||
.read_stream_fn = fn (s, reader) => ((DString*)s.data).read_from_stream(reader),
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
module std::io;
|
||||
|
||||
fn Stream File.as_stream(File* file)
|
||||
{
|
||||
return { .fns = &filestream_interface, .data = file };
|
||||
}
|
||||
|
||||
StreamInterface filestream_interface = {
|
||||
.close_fn = fn (s) => ((File*)s.data).close(),
|
||||
.seek_fn = fn (s, offset, seek) => ((File*)s.data).seek(offset, seek) @inline,
|
||||
.read_fn = fn (s, char[] bytes) => ((File*)s.data).read(bytes) @inline,
|
||||
.write_fn = fn (s, char[] bytes) => ((File*)s.data).write(bytes) @inline,
|
||||
.write_byte_fn = fn (s, char c) => ((File*)s.data).putc(c) @inline,
|
||||
.read_byte_fn = fn (s) => ((File*)s.data).getc() @inline,
|
||||
.flush_fn = fn (s) => ((File*)s.data).flush() @inline,
|
||||
};
|
||||
|
||||
|
||||
44
lib/std/io/stream/limitreader.c3
Normal file
44
lib/std/io/stream/limitreader.c3
Normal file
@@ -0,0 +1,44 @@
|
||||
module std::io;
|
||||
|
||||
struct LimitReader (InStream)
|
||||
{
|
||||
InStream* wrapped_stream;
|
||||
usz limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 };
|
||||
return self;
|
||||
}
|
||||
|
||||
fn void! LimitReader.close(&self) @dynamic
|
||||
{
|
||||
if (&self.wrapped_stream.close) return self.wrapped_stream.close();
|
||||
}
|
||||
|
||||
|
||||
fn usz! LimitReader.read(&self, char[] bytes) @dynamic
|
||||
{
|
||||
if (self.limit == 0) return IoError.EOF?;
|
||||
usz m = min(bytes.len, self.limit);
|
||||
usz n = self.wrapped_stream.read(bytes[:m])!;
|
||||
self.limit -= n;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn char! LimitReader.read_byte(&self) @dynamic
|
||||
{
|
||||
if (self.limit == 0) return IoError.EOF?;
|
||||
defer try self.limit--;
|
||||
return self.wrapped_stream.read_byte();
|
||||
}
|
||||
|
||||
fn usz! LimitReader.available(&self) @inline @dynamic
|
||||
{
|
||||
return self.limit;
|
||||
}
|
||||
124
lib/std/io/stream/scanner.c3
Normal file
124
lib/std/io/stream/scanner.c3
Normal file
@@ -0,0 +1,124 @@
|
||||
module std::io;
|
||||
|
||||
struct Scanner (InStream)
|
||||
{
|
||||
InStream* wrapped_stream;
|
||||
char[] buf;
|
||||
usz pattern_idx;
|
||||
usz read_idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scanner provides a way to read delimited data (with newlines as the default).
|
||||
* The supplied buffer must be at least as large as the expected data length
|
||||
* including its pattern.
|
||||
*
|
||||
* @param [&in] stream "The stream to read data from."
|
||||
* @require buffer.len > 0 "Non-empty buffer required."
|
||||
**/
|
||||
fn void Scanner.init(&self, InStream* stream, char[] buffer)
|
||||
{
|
||||
*self = { .wrapped_stream = stream, .buf = buffer };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return and clear any remaining unscanned data.
|
||||
**/
|
||||
fn char[] Scanner.flush(&self) @dynamic
|
||||
{
|
||||
assert(self.read_idx >= self.pattern_idx);
|
||||
usz n = self.read_idx - self.pattern_idx;
|
||||
char[] buf = self.buf[self.pattern_idx:n];
|
||||
self.pattern_idx = 0;
|
||||
self.read_idx = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
fn void! Scanner.close(&self) @dynamic
|
||||
{
|
||||
if (&self.wrapped_stream.close) return self.wrapped_stream.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the stream for the next split character and return data up to the match.
|
||||
* @require pattern.len > 0 "Non-empty pattern required."
|
||||
* @require self.buf.len > pattern.len "Pattern too large."
|
||||
**/
|
||||
fn char[]! Scanner.scan(&self, String pattern = "\n")
|
||||
{
|
||||
if (self.read_idx == 0)
|
||||
{
|
||||
// First read.
|
||||
self.read_idx = self.refill(self.buf)!;
|
||||
self.pattern_idx = 0;
|
||||
}
|
||||
assert(self.read_idx >= self.pattern_idx);
|
||||
usz n = self.read_idx - self.pattern_idx;
|
||||
char[] buf = self.buf[self.pattern_idx:n];
|
||||
if (try i = self.find(buf, pattern))
|
||||
{
|
||||
self.pattern_idx += i + pattern.len;
|
||||
return buf[:i];
|
||||
}
|
||||
if (self.pattern_idx == 0 || self.read_idx < self.buf.len)
|
||||
{
|
||||
// Split pattern not found with maximized search, abort.
|
||||
// Split pattern not found and already read as much as possible.
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
// Split pattern not found: maximize the search and try one more time.
|
||||
self.buf[:n] = buf[..];
|
||||
self.pattern_idx = 0;
|
||||
|
||||
buf = self.buf[n..];
|
||||
usz p = self.refill(buf)!;
|
||||
self.read_idx = n + p;
|
||||
|
||||
buf = buf[:p];
|
||||
usz i = self.find(buf, pattern)!;
|
||||
self.pattern_idx = n + i + pattern.len;
|
||||
|
||||
return self.buf[:n + i];
|
||||
}
|
||||
|
||||
macro usz! Scanner.find(&self, buf, pattern) @private
|
||||
{
|
||||
return ((String)buf).index_of(pattern);
|
||||
}
|
||||
|
||||
macro usz! Scanner.refill(&self, buf) @private
|
||||
{
|
||||
usz! n = self.wrapped_stream.read(buf);
|
||||
if (catch err = n)
|
||||
{
|
||||
case IoError.EOF:
|
||||
return SearchResult.MISSING?;
|
||||
default:
|
||||
return err?;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
fn usz! Scanner.read(&self, char[] bytes) @dynamic
|
||||
{
|
||||
usz n;
|
||||
if (self.pattern_idx < self.read_idx)
|
||||
{
|
||||
n = min(bytes.len, self.read_idx - self.pattern_idx);
|
||||
bytes[:n] = self.buf[self.pattern_idx:n];
|
||||
self.pattern_idx += n;
|
||||
bytes = bytes[n..];
|
||||
}
|
||||
n += self.wrapped_stream.read(bytes)!;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn char! Scanner.read_byte(&self) @dynamic
|
||||
{
|
||||
if (self.pattern_idx < self.read_idx)
|
||||
{
|
||||
return self.buf[self.pattern_idx++];
|
||||
}
|
||||
return self.wrapped_stream.read_byte();
|
||||
}
|
||||
@@ -11,14 +11,14 @@ const int RAND_MAX = 0x7fffffff;
|
||||
|
||||
struct DivResult
|
||||
{
|
||||
int quot;
|
||||
int rem;
|
||||
CInt quot;
|
||||
CInt rem;
|
||||
}
|
||||
|
||||
struct LongDivResult
|
||||
{
|
||||
long quot;
|
||||
long rem;
|
||||
CLong quot;
|
||||
CLong rem;
|
||||
}
|
||||
|
||||
fn Errno errno()
|
||||
@@ -31,73 +31,200 @@ fn void errno_set(Errno e)
|
||||
os::errno_set((int)e);
|
||||
}
|
||||
|
||||
distinct Errno = inline CInt;
|
||||
def TerminateFunction = fn void();
|
||||
def CompareFunction = fn int(void*, void*);
|
||||
def JmpBuf = uptr[$$JMP_BUF_SIZE];
|
||||
def Fd = CInt;
|
||||
def Fpos_t = long; // TODO make sure fpos is correct on all targets.
|
||||
def SignalFunction = fn void(CInt);
|
||||
|
||||
$if env::COMPILER_LIBC_AVAILABLE:
|
||||
const CInt SIGHUP = 1;
|
||||
const CInt SIGINT = 2;
|
||||
const CInt SIGQUIT = 3;
|
||||
const CInt SIGILL = 4;
|
||||
const CInt SIGTRAP = 5;
|
||||
const CInt SIGABTR = 6;
|
||||
const CInt SIGBUS = BSD_FLAVOR_SIG ? 10 : 7; // Or Mips
|
||||
const CInt SIGFPE = 8;
|
||||
const CInt SIGKILL = 9;
|
||||
const CInt SIGSEGV = 11;
|
||||
const CInt SIGSYS = BSD_FLAVOR_SIG ? 12 : 31;
|
||||
const CInt SIGPIPE = 13;
|
||||
const CInt SIGALRM = 14;
|
||||
const CInt SIGTERM = 15;
|
||||
const CInt SIGURG = BSD_FLAVOR_SIG ? 16 : 23;
|
||||
const CInt SIGSTOP = BSD_FLAVOR_SIG ? 17 : 19;
|
||||
const CInt SIGTSTP = BSD_FLAVOR_SIG ? 18 : 20;
|
||||
const CInt SIGCONT = BSD_FLAVOR_SIG ? 19 : 18;
|
||||
const CInt SIGCHLD = BSD_FLAVOR_SIG ? 20 : 17;
|
||||
|
||||
const bool BSD_FLAVOR_SIG @local = env::OPENBSD || env::DARWIN || env::FREEBSD || env::NETBSD;
|
||||
|
||||
def Time_t = $typefrom(env::WIN32 ? long.typeid : CLong.typeid);
|
||||
def Off_t = $typefrom(env::WIN32 ? int.typeid : usz.typeid);
|
||||
|
||||
struct Timespec
|
||||
{
|
||||
Time_t tv_sec;
|
||||
CLong tv_nsec;
|
||||
}
|
||||
|
||||
module libc @if(env::LIBC);
|
||||
|
||||
extern fn void abort();
|
||||
extern fn CInt abs(CInt n);
|
||||
extern fn ZString asctime(Tm* timeptr);
|
||||
extern fn ZString asctime_r(Tm* timeptr, char* buf);
|
||||
extern fn CInt atexit(TerminateFunction func);
|
||||
extern fn double atof(char* str);
|
||||
extern fn int atoi(char* str);
|
||||
extern fn CLongLong atoll(char* str);
|
||||
extern fn double strtod(char* str, char** endptr);
|
||||
extern fn CLong strtol(char* str, char** endptr, int base);
|
||||
extern fn CULong stroul(char* str, char** endptr, int base);
|
||||
extern fn void abort();
|
||||
extern fn void atexit(TerminateFunction f);
|
||||
extern fn void exit(int status);
|
||||
extern fn ZString getenv(ZString name);
|
||||
extern fn int setenv(ZString name, ZString value, int overwrite);
|
||||
extern fn int unsetenv(ZString name);
|
||||
extern fn int system(char* str);
|
||||
extern fn void bsearch(void* key, void *base, usz items, usz size, CompareFunction compare);
|
||||
extern fn void qsort(void* base, usz items, usz size, CompareFunction compare);
|
||||
extern fn DivResult div(int numer, int denom);
|
||||
extern fn long labs(long x);
|
||||
extern fn LongDivResult ldiv(long number, long denom);
|
||||
extern fn int rand();
|
||||
extern fn void srand(uint seed);
|
||||
|
||||
extern fn void* calloc(usz count, usz size);
|
||||
extern fn void clearerr(CFile stream);
|
||||
extern fn Clock_t clock();
|
||||
extern fn CInt close(CInt fd) @if(!env::WIN32);
|
||||
extern fn double difftime(Time_t time1, Time_t time2) @if(!env::WIN32);
|
||||
extern fn DivResult div(CInt numer, CInt denom);
|
||||
extern fn void exit(CInt status);
|
||||
extern fn CInt fclose(CFile stream);
|
||||
extern fn CFile fdopen(CInt fd, ZString mode) @if(!env::WIN32);
|
||||
extern fn CInt feof(CFile stream);
|
||||
extern fn CInt ferror(CFile stream);
|
||||
extern fn CInt fflush(CFile stream);
|
||||
extern fn CInt fgetc(CFile stream);
|
||||
extern fn ZString fgets(char* string, CInt n, CFile stream);
|
||||
extern fn CInt fgetpos(CFile stream, Fpos_t* pos);
|
||||
extern fn Fd fileno(CFile stream) @if(!env::WIN32);
|
||||
extern fn CFile fopen(ZString filename, ZString mode);
|
||||
extern fn CInt fprintf(CFile stream, ZString format, ...);
|
||||
extern fn CInt fputc(CInt c, CFile stream);
|
||||
extern fn CInt fputs(ZString string, CFile stream);
|
||||
extern fn usz fread(void* ptr, usz size, usz nmemb, CFile stream);
|
||||
extern fn void* free(void*);
|
||||
extern fn CFile freopen(ZString filename, ZString mode, CFile stream);
|
||||
extern fn CInt fscanf(CFile stream, ZString format, ...);
|
||||
extern fn CInt fseek(CFile stream, SeekIndex offset, CInt whence) @if(!env::WIN32);
|
||||
extern fn CInt fsetpos(CFile stream, Fpos_t* pos);
|
||||
extern fn SeekIndex ftell(CFile stream) @if(!env::WIN32);
|
||||
extern fn usz fwrite(void* ptr, usz size, usz nmemb, CFile stream);
|
||||
extern fn CInt getc(CFile stream);
|
||||
extern fn CInt getchar();
|
||||
extern fn ZString getenv(ZString name);
|
||||
extern fn ZString gets(char* buffer);
|
||||
extern fn Tm* gmtime(Time_t* timer);
|
||||
extern fn Tm* gmtime_r(Time_t *timer, Tm* buf) @if(!env::WIN32);
|
||||
extern fn CLong labs(CLong x);
|
||||
extern fn LongDivResult ldiv(CLong number, CLong denom);
|
||||
extern fn Tm* localtime(Time_t* timer);
|
||||
extern fn Tm* localtime_r(Time_t* timer, Tm* result) @if(!env::WIN32);
|
||||
extern fn void longjmp(JmpBuf* buffer, CInt value);
|
||||
$if env::os_is_win32():
|
||||
// TODO win32 aarch64
|
||||
extern fn CInt _setjmp(void* frameptr, JmpBuf* buffer);
|
||||
macro CInt setjmp(JmpBuf* buffer) => _setjmp($$frameaddress(), buffer);
|
||||
$else
|
||||
extern fn CInt setjmp(JmpBuf* buffer);
|
||||
$endif
|
||||
// MB functions omitted
|
||||
|
||||
// string
|
||||
extern fn void* memchr(void* str, int c, usz n);
|
||||
extern fn int memcmp(void* str1, void* str2, usz n);
|
||||
extern fn void* malloc(usz size);
|
||||
extern fn void* memchr(void* str, CInt c, usz n);
|
||||
extern fn CInt memcmp(void* buf1, void* buf2, usz count);
|
||||
extern fn void* memcpy(void* dest, void* src, usz n);
|
||||
extern fn void* memmove(void* dest, void* src, usz n);
|
||||
extern fn void* memset(void* dest, CInt value, usz n);
|
||||
extern fn char* strcat(char* dest, char* src);
|
||||
extern fn char* strncat(char* dest, char* src, usz n);
|
||||
extern fn char* strchr(char* str, int c);
|
||||
extern fn int strcmp(char* str1, char* str2);
|
||||
extern fn int strncmp(char* str1, char* str2, usz n);
|
||||
extern fn int strcoll(char* str1, char* str2);
|
||||
extern fn char* strcpy(char* dst, char* src);
|
||||
extern fn char* strncpy(char* dst, char* src, usz n);
|
||||
extern fn usz strcspn(char* str1, char* str2);
|
||||
extern fn char* strerror(int errn);
|
||||
extern fn usz strlen(char* str);
|
||||
extern fn char* strpbrk(char* str1, char* str2);
|
||||
extern fn usz strspn(char* str1, char* str2);
|
||||
extern fn char* strstr(char* haystack, char* needle);
|
||||
extern fn char* strtok(char* str, char* delim);
|
||||
extern fn usz strxfrm(char* dest, char* src, usz n);
|
||||
|
||||
// malloc
|
||||
extern fn void* malloc(usz size);
|
||||
extern fn void* calloc(usz count, usz size);
|
||||
extern fn void* free(void*);
|
||||
extern fn Time_t* mktime(Tm* time) @if(!env::WIN32);
|
||||
extern fn void perror(ZString string);
|
||||
extern fn CInt printf(ZString format, ...);
|
||||
extern fn CInt putc(CInt c, CFile stream);
|
||||
extern fn CInt putchar(CInt c);
|
||||
extern fn CInt puts(ZString str);
|
||||
extern fn void qsort(void* base, usz items, usz size, CompareFunction compare);
|
||||
extern fn CInt raise(CInt signal);
|
||||
extern fn CInt rand();
|
||||
extern fn isz read(Fd fd, void* buf, usz nbyte) @if(!env::WIN32);
|
||||
extern fn void* realloc(void* ptr, usz size);
|
||||
extern fn CInt remove(ZString filename);
|
||||
extern fn CInt rename(ZString old_name, ZString new_name);
|
||||
extern fn void rewind(CFile stream);
|
||||
extern fn CInt scanf(ZString format, ...);
|
||||
extern fn void setbuf(CFile stream, char* buffer);
|
||||
extern fn int setenv(ZString name, ZString value, CInt overwrite);
|
||||
extern fn CInt setjmp(JmpBuf* buffer) @if(!env::WIN32);
|
||||
extern fn void setvbuf(CFile stream, char* buf, CInt type, usz size);
|
||||
extern fn SignalFunction signal(CInt sig, SignalFunction function);
|
||||
extern fn CInt snprintf(char* buffer, usz size, ZString format, ...);
|
||||
extern fn CInt sprintf(char* buffer, ZString format, ...);
|
||||
extern fn void srand(uint seed);
|
||||
extern fn CInt sscanf(char* buffer, ZString format, ...);
|
||||
extern fn ZString strcat(ZString dest, ZString src);
|
||||
extern fn char* strchr(char* str, CInt c);
|
||||
extern fn CInt strcmp(ZString str1, ZString str2);
|
||||
extern fn CInt strcoll(ZString str1, ZString str2);
|
||||
extern fn usz strcspn(ZString str1, ZString str2);
|
||||
extern fn ZString strcpy(ZString dst, ZString src);
|
||||
extern fn ZString strerror(CInt errn);
|
||||
extern fn usz strftime(char* dest, usz maxsize, ZString format, Tm* timeptr);
|
||||
extern fn usz strlen(ZString str);
|
||||
extern fn ZString strncat(char* dest, char* src, usz n);
|
||||
extern fn CInt strncmp(char* str1, char* str2, usz n);
|
||||
extern fn char* strncpy(char* dst, char* src, usz n);
|
||||
extern fn CULong stroul(char* str, char** endptr, int base);
|
||||
extern fn char* strpbrk(ZString str1, ZString str2);
|
||||
extern fn usz strspn(ZString str1, ZString str2);
|
||||
extern fn ZString strptime(char* buf, ZString format, Tm* tm);
|
||||
extern fn char* strrchr(ZString str, CInt c);
|
||||
extern fn char* strstr(ZString haystack, ZString needle);
|
||||
extern fn double strtod(char* str, char** endptr);
|
||||
extern fn float strtof(char* str, char** endptr);
|
||||
extern fn ZString strtok(ZString str, ZString delim);
|
||||
extern fn CLong strtol(char* str, char** endptr, CInt base);
|
||||
extern fn CULong strtul(char* str, char** endptr, CInt base);
|
||||
extern fn usz strxfrm(char* dest, ZString src, usz n);
|
||||
extern fn CInt system(ZString str);
|
||||
extern fn Time_t timegm(Tm *timeptr) @if(!env::WIN32);
|
||||
extern fn ZString tmpnam(ZString str);
|
||||
extern fn CInt ungetc(CInt c, CFile stream);
|
||||
extern fn CInt unsetenv(ZString name);
|
||||
extern fn isz write(Fd fd, void* buffer, usz count) @if(!env::WIN32);
|
||||
|
||||
$else
|
||||
extern fn CFile fmemopen(void* ptr, usz size, ZString mode);
|
||||
extern fn isz getline(char** linep, usz* linecapp, CFile stream);
|
||||
extern fn int timespec_get(TimeSpec* ts, int base);
|
||||
extern fn int nanosleep(TimeSpec* req, TimeSpec* remaining);
|
||||
extern fn ZString ctime(Time_t *timer);
|
||||
extern fn Time_t time(Time_t *timer);
|
||||
|
||||
const CInt STDIN_FD = 0;
|
||||
const CInt STDOUT_FD = 1;
|
||||
const CInt STDERR_FD = 2;
|
||||
|
||||
module libc @if(env::LINUX);
|
||||
extern CFile __stdin @extern("stdin");
|
||||
extern CFile __stdout @extern("stdout");
|
||||
extern CFile __stderr @extern("stderr");
|
||||
extern fn usz malloc_usable_size(void* ptr);
|
||||
macro usz malloc_size(void* ptr) => malloc_usable_size(ptr);
|
||||
extern fn void* aligned_alloc(usz align, usz size);
|
||||
macro CFile stdin() => __stdin;
|
||||
macro CFile stdout() => __stdout;
|
||||
macro CFile stderr() => __stderr;
|
||||
|
||||
module libc @if(env::DARWIN);
|
||||
extern CFile __stdinp;
|
||||
extern CFile __stdoutp;
|
||||
extern CFile __stderrp;
|
||||
extern fn usz malloc_size(void* ptr);
|
||||
extern fn void* aligned_alloc(usz align, usz size);
|
||||
macro CFile stdin() => __stdinp;
|
||||
macro CFile stdout() => __stdoutp;
|
||||
macro CFile stderr() => __stderrp;
|
||||
|
||||
module libc @if(env::WIN32);
|
||||
macro usz malloc_size(void* ptr) => _msize(ptr);
|
||||
macro CFile stdin() => __acrt_iob_func(STDIN_FD);
|
||||
macro CFile stdout() => __acrt_iob_func(STDOUT_FD);
|
||||
macro CFile stderr() => __acrt_iob_func(STDERR_FD);
|
||||
|
||||
module libc @if(env::LIBC && !env::WIN32 && !env::LINUX && !env::DARWIN);
|
||||
macro CFile stdin() { return (CFile*)(uptr)STDIN_FD; }
|
||||
macro CFile stdout() { return (CFile*)(uptr)STDOUT_FD; }
|
||||
macro CFile stderr() { return (CFile*)(uptr)STDERR_FD; }
|
||||
|
||||
module libc @if(!env::LIBC);
|
||||
|
||||
fn void longjmp(JmpBuf* buffer, CInt value) @weak @extern("longjmp") @nostrip
|
||||
{
|
||||
@@ -144,110 +271,6 @@ fn void* memset(void* dest, CInt value, usz n) @weak @extern("memset") @nostrip
|
||||
return dest;
|
||||
}
|
||||
|
||||
$endif
|
||||
|
||||
// stdio
|
||||
|
||||
def Fpos = long;
|
||||
def CFile = void*;
|
||||
|
||||
$switch
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == LINUX:
|
||||
extern CFile __stdin @extern("stdin");
|
||||
extern CFile __stdout @extern("stdout");
|
||||
extern CFile __stderr @extern("stderr");
|
||||
extern fn usz malloc_usable_size(void* ptr);
|
||||
macro usz malloc_size(void* ptr) { return malloc_usable_size(ptr); }
|
||||
extern fn void* aligned_alloc(usz align, usz size);
|
||||
macro CFile stdin() { return __stdin; }
|
||||
macro CFile stdout() { return __stdout; }
|
||||
macro CFile stderr() { return __stderr; }
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_darwin():
|
||||
extern CFile __stdinp;
|
||||
extern CFile __stdoutp;
|
||||
extern CFile __stderrp;
|
||||
extern fn usz malloc_size(void* ptr);
|
||||
extern fn void* aligned_alloc(usz align, usz size);
|
||||
macro CFile stdin() { return __stdinp; }
|
||||
macro CFile stdout() { return __stdoutp; }
|
||||
macro CFile stderr() { return __stderrp; }
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::os_is_win32():
|
||||
extern fn CFile __acrt_iob_func(CInt c);
|
||||
extern fn usz _msize(void* ptr);
|
||||
macro usz malloc_size(void* ptr) { return _msize(ptr); }
|
||||
macro CFile stdin() { return __acrt_iob_func(0); }
|
||||
macro CFile stdout() { return __acrt_iob_func(1); }
|
||||
macro CFile stderr() { return __acrt_iob_func(2); }
|
||||
$default:
|
||||
macro CFile stdin() { return (CFile*)(uptr)0; }
|
||||
macro CFile stdout() { return (CFile*)(uptr)1; }
|
||||
macro CFile stderr() { return (CFile*)(uptr)2; }
|
||||
$endswitch
|
||||
|
||||
const HAS_MALLOC_SIZE =
|
||||
env::OS_TYPE == LINUX
|
||||
|| env::os_is_win32()
|
||||
|| env::os_is_darwin();
|
||||
|
||||
// The following needs to be set per arch+os
|
||||
// For now I have simply pulled the defaults from MacOS
|
||||
const int SEEK_SET = 0;
|
||||
const int SEEK_CUR = 1;
|
||||
const int SEEK_END = 2;
|
||||
const int _IOFBF = 0; // Fully buffered
|
||||
const int _IOLBF = 1; // Line buffered
|
||||
const int _IONBF = 2; // Unbuffered
|
||||
const int BUFSIZ = 1024;
|
||||
const int EOF = -1;
|
||||
const int FOPEN_MAX = 20;
|
||||
const int FILENAME_MAX = 1024;
|
||||
|
||||
def Errno = distinct CInt;
|
||||
def SeekIndex = CLong;
|
||||
|
||||
$if env::COMPILER_LIBC_AVAILABLE:
|
||||
|
||||
extern fn int fclose(CFile stream);
|
||||
extern fn void clearerr(CFile stream);
|
||||
extern fn int feof(CFile stream);
|
||||
extern fn int ferror(CFile stream);
|
||||
extern fn int fflush(CFile stream);
|
||||
extern fn int fgetpos(CFile stream, Fpos* pos);
|
||||
extern fn CFile fopen(ZString filename, ZString mode);
|
||||
extern fn usz fread(void* ptr, usz size, usz nmemb, CFile stream);
|
||||
extern fn CFile freopen(ZString filename, ZString mode, CFile stream);
|
||||
extern fn CFile fmemopen(void* ptr, usz size, ZString mode);
|
||||
extern fn int fseek(CFile stream, SeekIndex offset, int whence);
|
||||
extern fn int fsetpos(CFile stream, Fpos* pos);
|
||||
extern fn SeekIndex ftell(CFile stream);
|
||||
extern fn usz fwrite(void* ptr, usz size, usz nmemb, CFile stream);
|
||||
extern fn int remove(char* filename);
|
||||
extern fn int rename(char* old_name, char* new_name);
|
||||
extern fn void rewind(CFile stream);
|
||||
extern fn void setbuf(CFile stream, char* buffer);
|
||||
extern fn void setvbuf(CFile stream, char* buffer, int mode, usz size);
|
||||
extern fn CFile tmpnam(char* str);
|
||||
extern fn int fprintf(CFile stream, char* format, ...);
|
||||
extern fn int printf(char* format, ...);
|
||||
extern fn int sprintf(char* str, char* format, ...);
|
||||
extern fn int snprintf(char* str, usz size, char* format, ...);
|
||||
extern fn int fscanf(CFile stream, char* format, ...);
|
||||
extern fn int scanf(char* format, ...);
|
||||
extern fn int sscanf(char* str, char* format, ...);
|
||||
extern fn int fgetc(CFile stream);
|
||||
extern fn char* fgets(char* str, int n, CFile stream);
|
||||
extern fn int fputc(int c, CFile stream);
|
||||
extern fn int getc(CFile stream);
|
||||
extern fn int getchar();
|
||||
extern fn int putc(int c, CFile stream);
|
||||
extern fn int putchar(int c);
|
||||
extern fn int puts(char* str);
|
||||
extern fn int ungetc(int c, CFile stream);
|
||||
extern fn void perror(char* str);
|
||||
extern fn isz getline(char** linep, usz* linecapp, CFile stream);
|
||||
|
||||
$else
|
||||
|
||||
fn int fseek(CFile stream, SeekIndex offset, int whence) @weak @extern("fseek") @nostrip
|
||||
{
|
||||
unreachable("'fseek' not available.");
|
||||
@@ -315,212 +338,235 @@ fn int puts(ZString str) @weak @extern("puts") @nostrip
|
||||
unreachable("'puts' not available.");
|
||||
}
|
||||
|
||||
$endif
|
||||
module libc;
|
||||
|
||||
// stdio
|
||||
|
||||
def CFile = void*;
|
||||
|
||||
|
||||
const HAS_MALLOC_SIZE = env::LINUX || env::WIN32 || env::DARWIN;
|
||||
|
||||
// The following needs to be set per arch+os
|
||||
// For now I have simply pulled the defaults from MacOS
|
||||
const int SEEK_SET = 0;
|
||||
const int SEEK_CUR = 1;
|
||||
const int SEEK_END = 2;
|
||||
const int _IOFBF = 0; // Fully buffered
|
||||
const int _IOLBF = 1; // Line buffered
|
||||
const int _IONBF = 2; // Unbuffered
|
||||
const int BUFSIZ = 1024;
|
||||
const int EOF = -1;
|
||||
const int FOPEN_MAX = 20;
|
||||
const int FILENAME_MAX = 1024;
|
||||
|
||||
const S_IFMT = 0o170000; // type of file mask
|
||||
const S_IFIFO = 0o010000; // named pipe (fifo)
|
||||
const S_IFCHR = 0o020000; // character special
|
||||
const S_IFDIR = 0o040000; // directory
|
||||
const S_IFBLK = 0o060000; // block special
|
||||
const S_IFREG = 0o100000; // regular
|
||||
const S_IFLNK = 0o120000; // symbolic link
|
||||
const S_IFSOCK = 0o140000; // socket
|
||||
const S_ISUID = 0o004000; // Set user id on execution
|
||||
const S_ISGID = 0o002000; // Set group id on execution
|
||||
const S_ISVTX = 0o001000; // Save swapped text even after use
|
||||
const S_IRUSR = 0o000400; // Read permission, owner
|
||||
const S_IWUSR = 0o000200; // Write permission, owner
|
||||
const S_IXUSR = 0o000100; // Execute/search permission, owner
|
||||
|
||||
def SeekIndex = CLong;
|
||||
|
||||
// vsprintf vprintf not supported
|
||||
|
||||
// time.h
|
||||
|
||||
struct TmCommon @private
|
||||
{
|
||||
int tm_sec; /* seconds after the minute [0-60] */
|
||||
int tm_min; /* minutes after the hour [0-59] */
|
||||
int tm_hour; /* hours since midnight [0-23] */
|
||||
int tm_mday; /* day of the month [1-31] */
|
||||
int tm_mon; /* months since January [0-11] */
|
||||
int tm_year; /* years since 1900 */
|
||||
int tm_wday; /* days since Sunday [0-6] */
|
||||
int tm_yday; /* days since January 1 [0-365] */
|
||||
int tm_isdst; /* Daylight Savings Time flag */
|
||||
}
|
||||
|
||||
|
||||
$switch (env::OS_TYPE)
|
||||
|
||||
$case WIN32:
|
||||
|
||||
def Tm = TmCommon;
|
||||
|
||||
$case WASI:
|
||||
|
||||
def TimeOffset = int;
|
||||
struct Tm
|
||||
{
|
||||
inline TmCommon common;
|
||||
TimeOffset tm_gmtoff; /* offset from UTC in seconds */
|
||||
char *tm_zone; /* timezone abbreviation */
|
||||
int tm_nsec;
|
||||
CInt tm_sec; // seconds after the minute [0-60]
|
||||
CInt tm_min; // minutes after the hour [0-59]
|
||||
CInt tm_hour; // hours since midnight [0-23]
|
||||
CInt tm_mday; // day of the month [1-31]
|
||||
CInt tm_mon; // months since January [0-11]
|
||||
CInt tm_year; // years since 1900
|
||||
CInt tm_wday; // days since Sunday [0-6]
|
||||
CInt tm_yday; // days since January 1 [0-365]
|
||||
CInt tm_isdst; // Daylight Savings Time flag
|
||||
TimeOffset tm_gmtoff @if(!env::WIN32); /* offset from UTC in seconds */
|
||||
char *tm_zone @if(!env::WIN32); /* timezone abbreviation */
|
||||
CInt tm_nsec @if(env::WASI);
|
||||
}
|
||||
|
||||
$case MACOS:
|
||||
$case IOS:
|
||||
$case TVOS:
|
||||
$case WATCHOS:
|
||||
$case OPENBSD:
|
||||
$case FREEBSD:
|
||||
$default:
|
||||
|
||||
def TimeOffset = CLong;
|
||||
struct Tm
|
||||
{
|
||||
inline TmCommon common;
|
||||
TimeOffset tm_gmtoff; /* offset from UTC in seconds */
|
||||
char *tm_zone; /* timezone abbreviation */
|
||||
}
|
||||
|
||||
$endswitch
|
||||
|
||||
|
||||
$if env::os_is_win32():
|
||||
|
||||
struct TimeSpec
|
||||
{
|
||||
Time_t s;
|
||||
ulong ns;
|
||||
ulong ns @if(env::WIN32);
|
||||
CLong ns @if(!env::WIN32);
|
||||
}
|
||||
|
||||
def Time_t = long;
|
||||
def Clock_t = ulong;
|
||||
|
||||
$else
|
||||
def Clock_t = int @if(env::WIN32);
|
||||
def Clock_t = CULong @if(!env::WIN32);
|
||||
|
||||
struct TimeSpec
|
||||
{
|
||||
Time_t s;
|
||||
CLong ns;
|
||||
}
|
||||
|
||||
def Time_t = CLong;
|
||||
def Clock_t = CULong;
|
||||
|
||||
$endif
|
||||
def TimeOffset = int @if(env::WASI) ;
|
||||
def TimeOffset = CLong @if(!env::WASI) ;
|
||||
|
||||
const int TIME_UTC = 1;
|
||||
|
||||
extern fn int timespec_get(TimeSpec* ts, int base);
|
||||
extern fn int nanosleep(TimeSpec* req, TimeSpec* remaining);
|
||||
|
||||
// Likely wrong, must be per platform.
|
||||
const CLOCKS_PER_SEC = 1000000;
|
||||
|
||||
|
||||
extern fn ZString asctime(Tm *timeptr);
|
||||
extern fn Clock_t clock();
|
||||
extern fn ZString ctime(Time_t *timer);
|
||||
extern fn double difftime(Time_t time1, Time_t time2);
|
||||
extern fn Tm* gmtime(Time_t *timer);
|
||||
|
||||
extern fn Tm* localtime(Time_t *timer);
|
||||
|
||||
$if env::os_is_win32():
|
||||
extern fn Tm* _gmtime64_s(Tm* buf, Time_t *timer);
|
||||
extern fn Tm* _localtime64_s(Tm* buf, Time_t *timer);
|
||||
extern fn void _get_timezone(CLong *timezone);
|
||||
|
||||
macro Tm* gmtime_r(Time_t *timer, Tm* buf) => _gmtime64_s(buf, timer);
|
||||
macro Tm* localtime_r(Time_t *timer, Tm* buf) => _localtime64_s(buf, timer);
|
||||
|
||||
extern fn Time_t mktime(Tm *timeptr) @extern("_mktime64");
|
||||
extern fn Time_t timegm(Tm *timeptr) @extern("_mkgmtime64");
|
||||
$else
|
||||
extern fn Tm* gmtime_r(Time_t *timer, Tm* buf);
|
||||
extern fn Tm* localtime_r(Time_t *timer, Tm* buf);
|
||||
extern fn Time_t mktime(Tm *timeptr);
|
||||
extern fn Time_t timegm(Tm *timeptr);
|
||||
$endif
|
||||
|
||||
extern fn usz strftime(char* str, usz maxsize, char* format, Tm *timeptr);
|
||||
extern fn Time_t time(Time_t *timer);
|
||||
|
||||
// signal
|
||||
def SignalFunction = fn void(int);
|
||||
extern fn SignalFunction signal(int sig, SignalFunction function);
|
||||
// Incomplete
|
||||
|
||||
|
||||
module libc::errno;
|
||||
|
||||
const Errno OK = 0;
|
||||
const Errno EPERM = 1; // Operation not permitted
|
||||
const Errno ENOENT = 2; // No such file or directory
|
||||
const Errno ESRCH = 3; // No such process
|
||||
const Errno EINTR = 4; // Interrupted system call
|
||||
const Errno EIO = 5; // I/O error
|
||||
const Errno ENXIO = 6; // No such device or address
|
||||
const Errno E2BIG = 7; // Argument list too long
|
||||
const Errno ENOEXEC = 8; // Exec format error
|
||||
const Errno EBADF = 9; // Bad file number
|
||||
const Errno ECHILD = 10; // No child processes
|
||||
const Errno OK = 0;
|
||||
const Errno EPERM = 1; // Operation not permitted
|
||||
const Errno ENOENT = 2; // No such file or directory
|
||||
const Errno ESRCH = 3; // No such process
|
||||
const Errno EINTR = 4; // Interrupted system call
|
||||
const Errno EIO = 5; // I/O error
|
||||
const Errno ENXIO = 6; // No such device or address
|
||||
const Errno E2BIG = 7; // Argument list too long
|
||||
const Errno ENOEXEC = 8; // Exec format error
|
||||
const Errno EBADF = 9; // Bad file number
|
||||
const Errno ECHILD = 10; // No child processes
|
||||
|
||||
$if env::os_is_darwin():
|
||||
const Errno EAGAIN = 35; // Try again Macos
|
||||
$else
|
||||
const Errno EAGAIN = 11; // Try again
|
||||
$endif
|
||||
|
||||
const Errno ENOMEM = 12; // Out of memory
|
||||
const Errno EACCES = 13; // Permission denied
|
||||
const Errno EFAULT = 14; // Bad address
|
||||
const Errno ENOTBLK = 15; // Block device required, not on Win32
|
||||
const Errno EBUSY = 16; // Device or resource busy
|
||||
const Errno EEXIST = 17; // File exists
|
||||
const Errno EXDEV = 18; // Cross-device link
|
||||
const Errno ENODEV = 19; // No such device
|
||||
const Errno ENOTDIR = 20; // Not a directory
|
||||
const Errno EISDIR = 21; // Is a directory
|
||||
const Errno EINVAL = 22; // Invalid argument
|
||||
const Errno ENFILE = 23; // File table overflow
|
||||
const Errno EMFILE = 24; // Too many open files
|
||||
const Errno ENOTTY = 25; // Not a typewriter
|
||||
const Errno ETXTBSY = 26; // Text file busy, not on Win32
|
||||
const Errno EFBIG = 27; // File too large
|
||||
const Errno ENOSPC = 28; // No space left on device
|
||||
const Errno ESPIPE = 29; // Illegal seek
|
||||
const Errno EROFS = 30; // Read-only file system
|
||||
const Errno EMLINK = 31; // Too many links
|
||||
const Errno EPIPE = 32; // Broken pipe
|
||||
const Errno EDOM = 33; // Math argument out of domain of func
|
||||
const Errno ERANGE = 34; // Math result not representable
|
||||
const Errno EAGAIN @if(env::DARWIN) = 35; // Try again Macos
|
||||
const Errno EAGAIN @if(!env::DARWIN) = 11; // Try again
|
||||
|
||||
$switch (env::OS_TYPE)
|
||||
const Errno ENOMEM = 12; // Out of memory
|
||||
const Errno EACCES = 13; // Permission denied
|
||||
const Errno EFAULT = 14; // Bad address
|
||||
const Errno ENOTBLK = 15; // Block device required, not on Win32
|
||||
const Errno EBUSY = 16; // Device or resource busy
|
||||
const Errno EEXIST = 17; // File exists
|
||||
const Errno EXDEV = 18; // Cross-device link
|
||||
const Errno ENODEV = 19; // No such device
|
||||
const Errno ENOTDIR = 20; // Not a directory
|
||||
const Errno EISDIR = 21; // Is a directory
|
||||
const Errno EINVAL = 22; // Invalid argument
|
||||
const Errno ENFILE = 23; // File table overflow
|
||||
const Errno EMFILE = 24; // Too many open files
|
||||
const Errno ENOTTY = 25; // Not a typewriter
|
||||
const Errno ETXTBSY = 26; // Text file busy, not on Win32
|
||||
const Errno EFBIG = 27; // File too large
|
||||
const Errno ENOSPC = 28; // No space left on device
|
||||
const Errno ESPIPE = 29; // Illegal seek
|
||||
const Errno EROFS = 30; // Read-only file system
|
||||
const Errno EMLINK = 31; // Too many links
|
||||
const Errno EPIPE = 32; // Broken pipe
|
||||
const Errno EDOM = 33; // Math argument out of domain of func
|
||||
const Errno ERANGE = 34; // Math result not representable
|
||||
|
||||
$case MACOS:
|
||||
const Errno EDEADLK = 11; // Resource deadlock would occur MacOS
|
||||
const Errno ENAMETOOLONG = 63; // File name too long MacOS
|
||||
const Errno ELOOP = 62; // Too many symbolic links encountered
|
||||
const Errno EOVERFLOW = 84; // Value too large for defined data type Macos
|
||||
const Errno ECONNRESET = 54; // Connection reset by peer Macos
|
||||
const Errno ENETDOWN = 50; // Network is down MacOS
|
||||
const Errno ENETUNREACH = 51; // Network is unreachable MacOS
|
||||
const Errno ENETRESET = 52; // Network dropped connection because of reset MacOS
|
||||
const Errno EOPNOTSUPP = 45; // Operation not supported on transport endpoint
|
||||
const Errno ENOTEMPTY = 66; // Directory not empty
|
||||
// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/intro.2.html
|
||||
module libc::errno @if(env::DARWIN);
|
||||
const Errno EWOULDBLOCK = EAGAIN; // Operation would block
|
||||
const Errno EDEADLK = 11; // Resource deadlock would occur
|
||||
const Errno EINPROGRESS = 36; // Operation now in progress
|
||||
const Errno EALREADY = 37; // Operation already in progress
|
||||
const Errno ENOTSOCK = 38; // Socket operation on non-socket
|
||||
const Errno EDESTADDRREQ = 39; // Destination address required
|
||||
const Errno EMSGSIZE = 40; // Message too long
|
||||
const Errno EPROTOTYPE = 41; // Protocol wrong type for socket
|
||||
const Errno ENOPROTOOPT = 42; // Protocol not available
|
||||
const Errno EPROTONOSUPPORT = 43; // Protocol not supported
|
||||
const Errno ESOCKTNOSUPPORT = 44; // Socket type not supported
|
||||
const Errno ENOTSUP = 45; // Not supported
|
||||
const Errno EPFNOSUPPORT = 46; // Protocol family not supported
|
||||
const Errno EAFNOSUPPORT = 47; // Address family not supported by protocol family
|
||||
const Errno EADDRINUSE = 48; // Address already in use
|
||||
const Errno EADDRNOTAVAIL = 49; // Cannot assign requested address
|
||||
const Errno ENETDOWN = 50; // Network is down
|
||||
const Errno ENETUNREACH = 51; // Network is unreachable
|
||||
const Errno ENETRESET = 52; // Network dropped connection on reset
|
||||
const Errno ECONNABORTED = 53; // Software caused connection abort
|
||||
const Errno ECONNRESET = 54; // Connection reset by peer
|
||||
const Errno ENOBUFS = 55; // No buffer space available
|
||||
const Errno EISCONN = 56; // Socket is already connected
|
||||
const Errno ENOTCONN = 57; // Socket is not connected
|
||||
const Errno ESHUTDOWN = 58; // Cannot send after socket shutdown
|
||||
const Errno ETIMEDOUT = 60; // Operation timed out
|
||||
const Errno ECONNREFUSED = 61; // Connection refused
|
||||
const Errno ELOOP = 62; // Too many levels of symbolic links
|
||||
const Errno ENAMETOOLONG = 63; // File name too long
|
||||
const Errno EHOSTDOWN = 64; // Host is down
|
||||
const Errno EHOSTUNREACH = 65; // No route to host
|
||||
const Errno ENOTEMPTY = 66; // Directory not empty
|
||||
const Errno EPROCLIM = 67; // Too many processes
|
||||
const Errno EUSERS = 68; // Too many users
|
||||
const Errno EDQUOT = 69; // Disc quota exceeded
|
||||
const Errno ESTALE = 70; // Stale NFS file handle
|
||||
const Errno EBADRPC = 72; // RPC struct is bad
|
||||
const Errno ERPCMISMATCH = 73; // RPC version wrong
|
||||
const Errno EPROGUNAVAIL = 74; // RPC prog. not avail
|
||||
const Errno EPROGMISMATCH = 75; // Program version wrong
|
||||
const Errno EPROCUNAVAIL = 76; // Bad procedure for program
|
||||
const Errno ENOLCK = 77; // No locks available
|
||||
const Errno ENOSYS = 78; // Function not implemented
|
||||
const Errno EFTYPE = 79; // Inappropriate file type or format
|
||||
const Errno EAUTH = 80; // Authentication error
|
||||
const Errno ENEEDAUTH = 81; // Need authenticator
|
||||
const Errno EPWROFF = 82; // Device power is off
|
||||
const Errno EDEVERR = 83; // Device error
|
||||
const Errno EOVERFLOW = 84; // Value too large to be stored in data type
|
||||
const Errno EBADEXEC = 85; // Bad executable (or shared library)
|
||||
const Errno EBADARCH = 86; // Bad CPU type in executable
|
||||
const Errno ESHLIBVERS = 87; // Shared library version mismatch
|
||||
const Errno EBADMACHO = 88; // Malformed Mach-o file
|
||||
const Errno ECANCELED = 89; // Operation canceled
|
||||
const Errno EIDRM = 90; // Identifier removed
|
||||
const Errno ENOMSG = 91; // No message of desired type
|
||||
const Errno EILSEQ = 92; // Illegal byte sequence
|
||||
const Errno ENOATTR = 93; // Attribute not found
|
||||
const Errno EBADMSG = 94; // Bad message
|
||||
const Errno EMULTIHOP = 95; // Reserved
|
||||
const Errno ENODATA = 96; // No message available
|
||||
const Errno ENOLINK = 97; // Reserved
|
||||
const Errno ENOSR = 98; // No STREAM resources
|
||||
const Errno ENOSTR = 99; // Not a STREAM
|
||||
const Errno EPROTO = 100; // Protocol error
|
||||
const Errno ETIME = 101; // STREAM ioctl() timeout
|
||||
const Errno EOPNOTSUPP = 102; // Operation not supported on socket
|
||||
|
||||
$case WIN32:
|
||||
const Errno EDEADLK = 36; // Resource deadlock would occur Win32
|
||||
const Errno ENAMETOOLONG = 38; // File name too long Win32
|
||||
const Errno ELOOP = 114; // Too many symbolic links encountered
|
||||
const Errno EOVERFLOW = 132; // Value too large for defined data type
|
||||
const Errno ENETDOWN = 116; // Network is down
|
||||
const Errno ECONNRESET = 108; // Connection reset by peer
|
||||
const Errno ENETUNREACH = 118; // Network is unreachable
|
||||
const Errno ENETRESET = 117; // Network dropped connection because of reset
|
||||
const Errno EOPNOTSUPP = 130; // Operation not supported on transport endpoint
|
||||
const Errno ENOTEMPTY = 41; // Directory not empty
|
||||
|
||||
$default:
|
||||
const Errno EDEADLK = 35; // Resource deadlock would occur Linux (others?)
|
||||
const Errno ENAMETOOLONG = 36; // File name too long Linux (others?)
|
||||
const Errno ELOOP = 40; // Too many symbolic links encountered
|
||||
const Errno EOVERFLOW = 75; // Value too large for defined data type
|
||||
const Errno ENETDOWN = 100; // Network is down
|
||||
const Errno ECONNRESET = 104; // Connection reset by peer
|
||||
const Errno ENETUNREACH = 101; // Network is unreachable
|
||||
const Errno ENETRESET = 102; // Network dropped connection because of reset
|
||||
const Errno EOPNOTSUPP = 95; // Operation not supported on transport endpoint
|
||||
const Errno ENOTEMPTY = 39; // Directory not empty
|
||||
|
||||
$endswitch
|
||||
module libc::errno @if(env::WIN32);
|
||||
const Errno EDEADLK = 36; // Resource deadlock would occur Win32
|
||||
const Errno ENAMETOOLONG = 38; // File name too long Win32
|
||||
const Errno ENOTEMPTY = 41; // Directory not empty
|
||||
const Errno ELOOP = 114; // Too many symbolic links encountered
|
||||
const Errno EOVERFLOW = 132; // Value too large for defined data type
|
||||
const Errno ENETDOWN = 116; // Network is down
|
||||
const Errno ECONNRESET = 108; // Connection reset by peer
|
||||
const Errno ENETUNREACH = 118; // Network is unreachable
|
||||
const Errno ENETRESET = 117; // Network dropped connection because of reset
|
||||
const Errno EOPNOTSUPP = 130; // Operation not supported on transport endpoint
|
||||
const Errno ETIMEDOUT = 138; // Connection timed out
|
||||
const Errno EALREADY = 103; // Operation already in progress
|
||||
const Errno EINPROGRESS = 112; // Operation now in progress Win32
|
||||
const Errno EDQUOT = -122; // Quota exceeded, not in Win32
|
||||
const Errno EWOULDBLOCK = 140; // Operation would block
|
||||
|
||||
module libc::errno @if(!env::WIN32 && !env::DARWIN);
|
||||
const Errno EDEADLK = 35; // Resource deadlock would occur Linux (others?)
|
||||
const Errno ENAMETOOLONG = 36; // File name too long Linux (others?)
|
||||
const Errno ENOTEMPTY = 39; // Directory not empty
|
||||
const Errno ELOOP = 40; // Too many symbolic links encountered
|
||||
const Errno EWOULDBLOCK = EAGAIN; // Operation would block
|
||||
const Errno EOVERFLOW = 75; // Value too large for defined data type
|
||||
const Errno ENOTSOCK = 88; // Socket operation on non-socket
|
||||
const Errno EOPNOTSUPP = 95; // Operation not supported on transport endpoint
|
||||
const Errno EADDRINUSE = 98; // Address already in use
|
||||
const Errno EADDRNOTAVAIL = 99; // Cannot assign requested address
|
||||
const Errno ENETDOWN = 100; // Network is down
|
||||
const Errno ENETUNREACH = 101; // Network is unreachable
|
||||
const Errno ENETRESET = 102; // Network dropped connection because of reset
|
||||
const Errno ECONNRESET = 104; // Connection reset by peer
|
||||
const Errno EISCONN = 106; // Socket is already connected
|
||||
const Errno ETIMEDOUT = 110; // Connection timed out
|
||||
const Errno ECONNREFUSED = 111; // Connection refused
|
||||
const Errno EALREADY = 114; // Operation already in progress
|
||||
const Errno EINPROGRESS = 115; // Operation now in progress
|
||||
const Errno EDQUOT = 122; // Quota exceeded
|
||||
|
||||
|
||||
/*
|
||||
@@ -582,7 +628,6 @@ const Errno ESOCKTNOSUPPORT = 94; /* Socket type not supported */
|
||||
const Errno EPFNOSUPPORT = 96; /* Protocol family not supported */
|
||||
const Errno EAFNOSUPPORT = 97; /* Address family not supported by protocol */
|
||||
const Errno EADDRINUSE = 98; /* Address already in use */
|
||||
const Errno EADDRNOTAVAIL = 99; /* Cannot assign requested address */
|
||||
const Errno ECONNABORTED = 103; /* Software caused connection abort */
|
||||
const Errno ENOBUFS = 105; /* No buffer space available */
|
||||
const Errno EISCONN = 106; /* Transport endpoint is already connected */
|
||||
@@ -594,29 +639,6 @@ const Errno EHOSTDOWN = 112; /* Host is down */
|
||||
const Errno EHOSTUNREACH = 113; /* No route to host */
|
||||
*/
|
||||
|
||||
$switch (env::OS_TYPE)
|
||||
|
||||
$case MACOS:
|
||||
const Errno ETIMEDOUT = 60; // Connection timed out
|
||||
const Errno EINPROGRESS = 36; // Operation now in progress MacOS
|
||||
const Errno EALREADY = 37; // Operation already in progress MacOS
|
||||
const Errno EDQUOT = 69; // Quota exceeded, MacOS
|
||||
const Errno EWOULDBLOCK = 35; // Operation would block
|
||||
|
||||
$case WIN32:
|
||||
const Errno ETIMEDOUT = 138; // Connection timed out
|
||||
const Errno EALREADY = 103; // Operation already in progress
|
||||
const Errno EINPROGRESS = 112; // Operation now in progress Win32
|
||||
const Errno EDQUOT = -122; // Quota exceeded, not in Win32
|
||||
const Errno EWOULDBLOCK = 140; // Operation would block
|
||||
|
||||
$default:
|
||||
const Errno ETIMEDOUT = 110; // Connection timed out
|
||||
const Errno EALREADY = 114; // Operation already in progress
|
||||
const Errno EINPROGRESS = 115; // Operation now in progress
|
||||
const Errno EDQUOT = 122; // Quota exceeded
|
||||
const Errno EWOULDBLOCK = 41; // Operation would block
|
||||
$endswitch
|
||||
|
||||
/*
|
||||
const Errno ESTALE = 116; /* Stale NFS file handle */
|
||||
|
||||
22
lib/std/libc/libc_extra.c3
Normal file
22
lib/std/libc/libc_extra.c3
Normal file
@@ -0,0 +1,22 @@
|
||||
module libc;
|
||||
import std::time;
|
||||
|
||||
/**
|
||||
* @require self >= 0
|
||||
**/
|
||||
fn TimeSpec NanoDuration.to_timespec(self) @inline
|
||||
{
|
||||
CLong ns = (CLong)(self % 1000_000_000);
|
||||
Time_t sec = (Time_t)(self / 1000_000_000);
|
||||
return { .s = sec, .ns = ns };
|
||||
}
|
||||
|
||||
/**
|
||||
* @require self >= 0
|
||||
**/
|
||||
fn TimeSpec Duration.to_timespec(self) @inline
|
||||
{
|
||||
CLong ns = (CLong)(1000 * (self % time::SEC));
|
||||
Time_t sec = (Time_t)(self / time::SEC);
|
||||
return { .s = sec, .ns = ns };
|
||||
}
|
||||
35
lib/std/libc/os/darwin.c3
Normal file
35
lib/std/libc/os/darwin.c3
Normal file
@@ -0,0 +1,35 @@
|
||||
module libc @if(env::DARWIN);
|
||||
|
||||
def Dev_t = int;
|
||||
def Mode_t = ushort;
|
||||
def Nlink_t = ushort;
|
||||
def Blkcnt_t = long;
|
||||
def Blksize_t = int;
|
||||
def Ino_t = ulong;
|
||||
|
||||
struct Stat
|
||||
{
|
||||
Dev_t st_dev;
|
||||
Mode_t st_mode;
|
||||
Nlink_t st_nlink;
|
||||
Ino_t st_ino;
|
||||
Uid_t st_uid;
|
||||
Gid_t st_gid;
|
||||
Dev_t st_rdev;
|
||||
|
||||
Timespec st_atimespec; // time of last access
|
||||
Timespec st_mtimespec; // time of last data modification
|
||||
Timespec st_ctimespec; // time of last status change
|
||||
Timespec st_birthtimespec; // time of file creation(birth)
|
||||
Off_t st_size; // file size, in bytes
|
||||
Blkcnt_t st_blocks; // blocks allocated for file
|
||||
Blksize_t st_blocksize; // optimal blocksize for I/O
|
||||
uint st_flags; // user defined flags for file
|
||||
uint st_gen; // file generation number
|
||||
int st_lspare; // RESERVED
|
||||
long[2] st_qspare; // RESERVED
|
||||
}
|
||||
|
||||
extern fn int stat(ZString str, Stat* stat) @extern("stat64");
|
||||
|
||||
extern fn CInt sysctl(CInt *name, CUInt namelen, void *oldp, usz *oldlenp, void *newp, usz newlen);
|
||||
@@ -1,38 +1,28 @@
|
||||
module libc::os;
|
||||
|
||||
// Linux
|
||||
extern fn int* __errno_location() @if(env::LINUX);
|
||||
macro int errno() @if(env::LINUX) => *__errno_location();
|
||||
macro void errno_set(int err) @if(env::LINUX) => *(__errno_location()) = err;
|
||||
|
||||
$switch
|
||||
// Darwin
|
||||
extern fn int* __error() @if(env::DARWIN);
|
||||
macro int errno() @if(env::DARWIN) => *__error();
|
||||
macro void errno_set(int err) @if(env::DARWIN) => *(__error()) = err;
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == LINUX:
|
||||
|
||||
extern fn int* __errno_location();
|
||||
macro int errno() => *__errno_location();
|
||||
macro void errno_set(int err) => *(__errno_location()) = err;
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == MACOS:
|
||||
|
||||
extern fn int* __error();
|
||||
macro int errno() => *__error();
|
||||
macro void errno_set(int err) => *(__error()) = err;
|
||||
|
||||
$case env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == WIN32:
|
||||
|
||||
macro int errno()
|
||||
// Win32
|
||||
macro int errno() @if(env::WIN32)
|
||||
{
|
||||
int holder;
|
||||
_get_errno(&holder);
|
||||
return holder;
|
||||
}
|
||||
macro void errno_set(int err) @if(env::WIN32) => _set_errno(err);
|
||||
extern fn void _get_errno(int* result) @if(env::WIN32);
|
||||
extern fn void _set_errno(int err) @if(env::WIN32);
|
||||
|
||||
macro void errno_set(int err) => _set_errno(err);
|
||||
|
||||
extern fn void _get_errno(int* result);
|
||||
extern fn void _set_errno(int err);
|
||||
|
||||
$default:
|
||||
|
||||
tlocal int _errno_c3 = 0;
|
||||
fn void errno_set(int err) => _errno_c3 = err;
|
||||
fn int errno() => _errno_c3;
|
||||
|
||||
$endswitch
|
||||
// Default
|
||||
const ERRNO_DEFAULT @local = !env::LINUX && !env::DARWIN && !env::WIN32;
|
||||
tlocal int _errno_c3 @if(ERRNO_DEFAULT) = 0;
|
||||
fn void errno_set(int err) @if(ERRNO_DEFAULT) => _errno_c3 = err;
|
||||
fn int errno() @if(ERRNO_DEFAULT) => _errno_c3;
|
||||
62
lib/std/libc/os/linux.c3
Normal file
62
lib/std/libc/os/linux.c3
Normal file
@@ -0,0 +1,62 @@
|
||||
module libc @if(env::LINUX);
|
||||
|
||||
// Checked for x86_64, this is notoriously incorrect when comparing with Rust code etc
|
||||
|
||||
def Blksize_t = $typefrom(env::X86_64 ? long.typeid : CInt.typeid);
|
||||
def Nlink_t = $typefrom(env::X86_64 ? ulong.typeid : CUInt.typeid);
|
||||
def Blkcnt_t = long;
|
||||
def Ino_t = ulong;
|
||||
def Dev_t = ulong;
|
||||
def Mode_t = uint;
|
||||
def Ino64_t = ulong;
|
||||
def Blkcnt64_t = long;
|
||||
|
||||
struct Stat @if(env::X86_64)
|
||||
{
|
||||
Dev_t st_dev;
|
||||
Ino_t st_ino;
|
||||
Nlink_t st_nlink;
|
||||
Mode_t st_mode;
|
||||
Uid_t st_uid;
|
||||
Gid_t st_gid;
|
||||
CInt __pad0;
|
||||
Dev_t st_rdev;
|
||||
Off_t st_size;
|
||||
Blksize_t st_blksize;
|
||||
Blkcnt_t st_blocks;
|
||||
Time_t st_atime;
|
||||
long st_atime_nsec;
|
||||
Time_t st_mtime;
|
||||
long st_mtime_nsec;
|
||||
Time_t st_ctime;
|
||||
long st_ctime_nsec;
|
||||
long[3] __unused;
|
||||
}
|
||||
|
||||
struct Stat @if(!env::X86_64)
|
||||
{
|
||||
Dev_t st_dev;
|
||||
Ino_t st_ino;
|
||||
Mode_t st_mode;
|
||||
Nlink_t st_nlink;
|
||||
Uid_t st_uid;
|
||||
Gid_t st_gid;
|
||||
Dev_t st_rdev;
|
||||
CInt __pad1;
|
||||
Off_t st_size;
|
||||
Blksize_t st_blksize;
|
||||
CInt __pad2;
|
||||
Blkcnt_t st_blocks;
|
||||
Time_t st_atime;
|
||||
long st_atime_nsec;
|
||||
Time_t st_mtime;
|
||||
long st_mtime_nsec;
|
||||
Time_t st_ctime;
|
||||
long st_ctime_nsec;
|
||||
CInt[2] __unused;
|
||||
}
|
||||
|
||||
extern fn CInt stat(ZString path, Stat* stat);
|
||||
|
||||
extern fn CInt get_nprocs();
|
||||
extern fn CInt get_nprocs_conf();
|
||||
45
lib/std/libc/os/posix.c3
Normal file
45
lib/std/libc/os/posix.c3
Normal file
@@ -0,0 +1,45 @@
|
||||
module libc @if(env::POSIX);
|
||||
|
||||
def Pid_t = int;
|
||||
def Uid_t = uint;
|
||||
def Gid_t = uint;
|
||||
|
||||
const CUInt SA_ONSTACK = env::LINUX ? 0x08000000 : 0x0001;
|
||||
const CUInt SA_RESTART = env::LINUX ? 0x10000000 : 0x0002;
|
||||
const CUInt SA_RESETHAND = env::LINUX ? 0x80000000 : 0x0004;
|
||||
const CUInt SA_SIGINFO = env::LINUX ? 0x00000004 : 0x0040;
|
||||
|
||||
def Sigset_t = uint @if(!env::LINUX);
|
||||
def Sigset_t = ulong[16] @if(env::LINUX);
|
||||
def SigActionFunction = fn void(CInt, void*, void*);
|
||||
|
||||
struct Sigaction
|
||||
{
|
||||
union
|
||||
{
|
||||
SignalFunction sa_handler;
|
||||
SigActionFunction sa_sigaction;
|
||||
}
|
||||
CInt sa_flags @if(env::FREEBSD);
|
||||
Sigset_t sa_mask; // 128
|
||||
CInt sa_flags @if(!env::FREEBSD);
|
||||
void* sa_restorer @if(env::LINUX);
|
||||
}
|
||||
|
||||
struct Stack_t
|
||||
{
|
||||
void* ss_sp;
|
||||
struct @if(!env::LINUX)
|
||||
{
|
||||
usz ss_size;
|
||||
CInt ss_flags;
|
||||
}
|
||||
struct @if(env::LINUX)
|
||||
{
|
||||
CInt ss_flags;
|
||||
usz ss_size;
|
||||
}
|
||||
}
|
||||
|
||||
extern fn CInt sigaltstack(Stack_t* ss, Stack_t* old_ss);
|
||||
extern fn CInt sigaction(CInt signum, Sigaction *action, Sigaction *oldaction);
|
||||
51
lib/std/libc/os/win32.c3
Normal file
51
lib/std/libc/os/win32.c3
Normal file
@@ -0,0 +1,51 @@
|
||||
module libc @if(env::WIN32);
|
||||
|
||||
|
||||
extern fn CFile __acrt_iob_func(CInt c);
|
||||
extern fn CInt _close(Fd fd); def close = _close;
|
||||
extern fn double _difftime64(Time_t time1, Time_t time2); def difftime = _difftime64;
|
||||
extern fn CFile _fdopen(Fd fd, ZString mode); def fdopen = _fdopen;
|
||||
extern fn CInt _fileno(CFile stream); def fileno = _fileno;
|
||||
extern fn CInt _fseeki64(CFile, long, int); def fseek = _fseeki64;
|
||||
extern fn CLong _ftelli64(CFile); def ftell = _ftelli64;
|
||||
extern fn Errno _get_timezone(CLong *timezone);
|
||||
extern fn Tm* _gmtime64_s(Tm* buf, Time_t *timer);
|
||||
extern fn Tm* _localtime64_s(Tm* buf, Time_t *timer);
|
||||
extern fn Time_t _mkgmtime64(Tm* timeptr); def timegm = _mkgmtime64;
|
||||
extern fn Time_t _mktime64(Tm *timeptr); def mktime = _mktime64;
|
||||
extern fn usz _msize(void* ptr);
|
||||
extern fn CInt _read(Fd fd, void* buffer, CUInt buffer_size);
|
||||
extern fn CInt _setjmp(void* frameptr, JmpBuf* buffer) @if(env::WIN32);
|
||||
extern fn CFile _wfopen(WString, WString);
|
||||
extern fn CFile _wfreopen(WString, WString, CFile);
|
||||
extern fn CInt _write(Fd fd, void* buffer, CUInt count);
|
||||
extern fn CInt _wremove(WString);
|
||||
|
||||
struct SystemInfo
|
||||
{
|
||||
union {
|
||||
uint dwOemId;
|
||||
struct {
|
||||
ushort wProcessorArchitecture;
|
||||
ushort wReserved;
|
||||
}
|
||||
}
|
||||
uint dwPageSize;
|
||||
void* lpMinimumApplicationAddress;
|
||||
void* lpMaximumApplicationAddress;
|
||||
usz dwActiveProcessorMask;
|
||||
uint dwNumberOfProcessors;
|
||||
uint dwProcessorType;
|
||||
uint dwAllocationGranularity;
|
||||
ushort wProcessorLevel;
|
||||
ushort wProcessorRevision;
|
||||
}
|
||||
|
||||
extern fn CInt get_system_info(SystemInfo*) @extern("GetSystemInfo");
|
||||
|
||||
// Aliases to simplify libc use
|
||||
macro Tm* localtime_r(Time_t* timer, Tm* buf) => _localtime64_s(buf, timer);
|
||||
macro CInt setjmp(JmpBuf* buffer) => _setjmp($$frameaddress(0), buffer);
|
||||
macro Tm* gmtime_r(Time_t* timer, Tm* buf) => _gmtime64_s(buf, timer);
|
||||
macro isz read(Fd fd, void* buffer, usz buffer_size) => _read(fd, buffer, (CUInt)buffer_size);
|
||||
macro isz write(Fd fd, void* buffer, usz count) => _write(fd, buffer, (CUInt)count);
|
||||
@@ -80,38 +80,43 @@ enum RoundingMode : int
|
||||
TOWARD_NEG_INFINITY
|
||||
}
|
||||
|
||||
fault MatrixError
|
||||
fault MathError
|
||||
{
|
||||
MATRIX_INVERSE_DOESNT_EXIST,
|
||||
OVERFLOW,
|
||||
}
|
||||
|
||||
def Complexf = Complex<float>;
|
||||
def Complex = Complex<double>;
|
||||
def complexf_identity = complex::identity<float>;
|
||||
def complex_identity = complex::identity<double>;
|
||||
fault MatrixError
|
||||
{
|
||||
MATRIX_INVERSE_DOESNT_EXIST,
|
||||
}
|
||||
|
||||
def Quaternionf = Quaternion<float>;
|
||||
def Quaternion = Quaternion<double>;
|
||||
def quaternionf_identity = quaternion::identity<float>;
|
||||
def quaternion_identity = quaternion::identity<double>;
|
||||
def Complexf = Complex(<float>);
|
||||
def Complex = Complex(<double>);
|
||||
def complexf_identity = complex::identity(<float>);
|
||||
def complex_identity = complex::identity(<double>);
|
||||
|
||||
def Matrix2f = Matrix2x2<float>;
|
||||
def Matrix2 = Matrix2x2<double>;
|
||||
def Matrix3f = Matrix3x3<float>;
|
||||
def Matrix3 = Matrix3x3<double>;
|
||||
def Matrix4f = Matrix4x4<float>;
|
||||
def Matrix4 = Matrix4x4<double>;
|
||||
def matrix4_ortho = matrix::ortho<double>;
|
||||
def matrix4_perspective = matrix::perspective<double>;
|
||||
def matrix4f_ortho = matrix::ortho<float>;
|
||||
def matrix4f_perspective = matrix::perspective<float>;
|
||||
def Quaternionf = Quaternion(<float>);
|
||||
def Quaternion = Quaternion(<double>);
|
||||
def quaternionf_identity = quaternion::identity(<float>);
|
||||
def quaternion_identity = quaternion::identity(<double>);
|
||||
|
||||
def MATRIX2_IDENTITY = matrix::IDENTITY2<double>;
|
||||
def MATRIX2F_IDENTITY = matrix::IDENTITY2<float>;
|
||||
def MATRIX3_IDENTITY = matrix::IDENTITY3<double>;
|
||||
def MATRIX3F_IDENTITY = matrix::IDENTITY3<float>;
|
||||
def MATRIX4_IDENTITY = matrix::IDENTITY4<double>;
|
||||
def MATRIX4F_IDENTITY = matrix::IDENTITY4<float>;
|
||||
def Matrix2f = Matrix2x2(<float>);
|
||||
def Matrix2 = Matrix2x2(<double>);
|
||||
def Matrix3f = Matrix3x3(<float>);
|
||||
def Matrix3 = Matrix3x3(<double>);
|
||||
def Matrix4f = Matrix4x4(<float>);
|
||||
def Matrix4 = Matrix4x4(<double>);
|
||||
def matrix4_ortho = matrix::ortho(<double>);
|
||||
def matrix4_perspective = matrix::perspective(<double>);
|
||||
def matrix4f_ortho = matrix::ortho(<float>);
|
||||
def matrix4f_perspective = matrix::perspective(<float>);
|
||||
|
||||
def MATRIX2_IDENTITY = matrix::IDENTITY2(<double>);
|
||||
def MATRIX2F_IDENTITY = matrix::IDENTITY2(<float>);
|
||||
def MATRIX3_IDENTITY = matrix::IDENTITY3(<double>);
|
||||
def MATRIX3F_IDENTITY = matrix::IDENTITY3(<float>);
|
||||
def MATRIX4_IDENTITY = matrix::IDENTITY4(<double>);
|
||||
def MATRIX4F_IDENTITY = matrix::IDENTITY4(<float>);
|
||||
|
||||
/**
|
||||
* @require types::is_numerical($typeof(x)) `The input must be a numerical value or numerical vector`
|
||||
@@ -131,10 +136,9 @@ macro sign(x)
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @require values::@is_int(x) || values::@is_float(x) "Expected an integer or floating point value"
|
||||
* @checked x + y
|
||||
* @require values::@is_int(y) || values::@is_float(y) "Expected an integer or floating point value"
|
||||
**/
|
||||
macro atan2(x, y)
|
||||
{
|
||||
@@ -147,12 +151,12 @@ macro atan2(x, y)
|
||||
|
||||
/**
|
||||
* @require values::@is_int(x) || values::@is_float(x) "Expected an integer or floating point value"
|
||||
* @checked (*y)[0] = x, y.len
|
||||
* @require y.len == 2
|
||||
* @require $and(@typekind(y) == ARRAY || @typekind(y) == VECTOR, y.len == 2)
|
||||
* @require $assignable(x, $typeof(y[0]))
|
||||
**/
|
||||
macro sincos(x, y)
|
||||
{
|
||||
$if $typeof(y[0]).typeid == float.typeid:
|
||||
$if @typeid(y[0]) == float.typeid:
|
||||
return _sincosf(x, y);
|
||||
$else
|
||||
return _sincos(x, y);
|
||||
@@ -161,11 +165,10 @@ macro sincos(x, y)
|
||||
|
||||
/**
|
||||
* @require values::@is_int(x) || values::@is_float(x) "Expected an integer or floating point value"
|
||||
* @checked x
|
||||
**/
|
||||
macro atan(x)
|
||||
{
|
||||
$if $typeof(x).typeid == float.typeid:
|
||||
$if @typeid(x) == float.typeid:
|
||||
return _atanf(x);
|
||||
$else
|
||||
return _atan(x);
|
||||
@@ -177,7 +180,7 @@ macro atan(x)
|
||||
**/
|
||||
macro atanh(x)
|
||||
{
|
||||
$if $typeof(x).typeid == float.typeid:
|
||||
$if @typeid(x) == float.typeid:
|
||||
return _atanhf(x);
|
||||
$else
|
||||
return _atanh(x);
|
||||
@@ -189,7 +192,7 @@ macro atanh(x)
|
||||
**/
|
||||
macro acos(x)
|
||||
{
|
||||
$if $typeof(x).typeid == float.typeid:
|
||||
$if @typeid(x) == float.typeid:
|
||||
return _acosf(x);
|
||||
$else
|
||||
return _acos(x);
|
||||
@@ -201,7 +204,7 @@ macro acos(x)
|
||||
**/
|
||||
macro acosh(x)
|
||||
{
|
||||
$if $typeof(x).typeid == float.typeid:
|
||||
$if @typeid(x) == float.typeid:
|
||||
return _acoshf(x);
|
||||
$else
|
||||
return _acosh(x);
|
||||
@@ -213,7 +216,7 @@ macro acosh(x)
|
||||
**/
|
||||
macro asin(x)
|
||||
{
|
||||
$if $typeof(x).typeid == float.typeid:
|
||||
$if @typeid(x) == float.typeid:
|
||||
return _asinf(x);
|
||||
$else
|
||||
return _asin(x);
|
||||
@@ -225,7 +228,7 @@ macro asin(x)
|
||||
**/
|
||||
macro asinh(x)
|
||||
{
|
||||
$if $typeof(x).typeid == float.typeid:
|
||||
$if @typeid(x) == float.typeid:
|
||||
return _asinhf(x);
|
||||
$else
|
||||
return _asinh(x);
|
||||
@@ -246,14 +249,14 @@ macro ceil(x) => $$ceil(x);
|
||||
* @return "lower if x < lower, upper if x > upper, otherwise return x."
|
||||
*
|
||||
* @require types::is_numerical($typeof(x)) `The input must be a numerical value or numerical vector`
|
||||
* @checked $typeof(x) z = lower `The lower bound must be convertable to the value type.`
|
||||
* @checked $typeof(x) z = upper `The upper bound must be convertable to the value type.`
|
||||
* @require values::@assign_to(lower, x) `The lower bound must be convertable to the value type.`
|
||||
* @require values::@assign_to(upper, x) `The upper bound must be convertable to the value type.`
|
||||
**/
|
||||
macro clamp(x, lower, upper) => $$max(($typeof(x))lower, $$min(x, ($typeof(x))upper));
|
||||
|
||||
/**
|
||||
* @require values::@is_promotable_to_floatlike(mag) `The input must be a number value or float vector`
|
||||
* @require @convertable(sgn, $typeof(values::promote_int(mag)))
|
||||
* @require values::@assign_to(sgn, values::promote_int(mag))
|
||||
**/
|
||||
macro copysign(mag, sgn) => $$copysign(values::promote_int(mag), ($typeof(values::promote_int(mag)))sgn);
|
||||
|
||||
@@ -306,8 +309,8 @@ macro floor(x) => $$floor(values::promote_int(x));
|
||||
* @require values::@is_promotable_to_floatlike(a) `The input must be a number or float vector`
|
||||
* @require values::@is_promotable_to_floatlike(b) `The input must be a number or float vector`
|
||||
* @require values::@is_promotable_to_floatlike(c) `The input must be a number or float vector`
|
||||
* @require types::@is_same_vector_type(a, b) `The input types must be equal`
|
||||
* @require types::@is_same_vector_type(a, c) `The input types must match`
|
||||
* @require values::@is_same_vector_type(a, b) `The input types must be equal`
|
||||
* @require values::@is_same_vector_type(a, c) `The input types must match`
|
||||
**/
|
||||
macro fma(a, b, c) => $$fma(a, b, c);
|
||||
|
||||
@@ -315,14 +318,20 @@ macro fma(a, b, c) => $$fma(a, b, c);
|
||||
/**
|
||||
* @require values::@is_promotable_to_floatlike(x) `The input must be a number or a float vector`
|
||||
* @require values::@is_promotable_to_floatlike(y) `The input must be a number or a float vector`
|
||||
* @require types::@is_same_vector_type(x, y) `The input types must match`
|
||||
* @require values::@is_same_vector_type(x, y) `The input types must match`
|
||||
**/
|
||||
macro hypot(x, y) => sqrt(sqr(x) + sqr(y));
|
||||
|
||||
/**
|
||||
* @require values::@is_promotable_to_floatlike(x) `The input must be a number or a float vector`
|
||||
**/
|
||||
macro log(x) => $$log(values::promote_int(x));
|
||||
macro ln(x) => $$log(values::promote_int(x));
|
||||
|
||||
/**
|
||||
* @require values::@is_promotable_to_floatlike(x) `The input must be a number or a float vector`
|
||||
* @require values::@is_promotable_to_floatlike(base) `The base must be a number or a float vector`
|
||||
**/
|
||||
macro log(x, base) => $$log(values::promote_int(x)) / $$log(values::promote_int(base));
|
||||
|
||||
/**
|
||||
* @require values::@is_promotable_to_floatlike(x) `The input must be a number or a float vector`
|
||||
@@ -359,13 +368,13 @@ macro min(x, y, ...)
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return $$min(x, y);
|
||||
$else
|
||||
var m = $$min(x, y);
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
m = $$min(m, $vaarg($i));
|
||||
$endfor
|
||||
return m;
|
||||
$endif
|
||||
$else
|
||||
var m = $$min(x, y);
|
||||
$for (var $i = 0; $i < $vacount; $i++)
|
||||
m = $$min(m, $vaarg($i));
|
||||
$endfor
|
||||
return m;
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,15 +390,15 @@ macro nearbyint(x) => $$nearbyint(x);
|
||||
|
||||
/**
|
||||
* @require values::@is_promotable_to_floatlike(x) `The input must be a number or a float vector`
|
||||
* @require values::@convertable_to(exp, x) || values::@is_int(exp) `The input must be an integer, castable to the type of x`
|
||||
* @require $assignable(exp, $typeof(x)) || values::@is_int(exp) `The input must be an integer, castable to the type of x`
|
||||
**/
|
||||
macro pow(x, exp)
|
||||
{
|
||||
$if types::is_floatlike($typeof(exp)):
|
||||
$if types::is_floatlike($typeof(exp)):
|
||||
return $$pow(x, ($typeof(x))exp);
|
||||
$else
|
||||
$else
|
||||
return $$pow_int(x, exp);
|
||||
$endif
|
||||
$endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -471,14 +480,14 @@ macro sqrt(x) => $$sqrt(values::promote_int(x));
|
||||
macro tan(x)
|
||||
{
|
||||
var $Type = $typeof(x);
|
||||
$switch
|
||||
$case types::is_vector($Type):
|
||||
return $$sin(x) / $$cos(x);
|
||||
$case $Type.typeid == float.typeid:
|
||||
return _tanf(x);
|
||||
$default:
|
||||
return _tan(x);
|
||||
$endswitch
|
||||
$switch
|
||||
$case types::is_vector($Type):
|
||||
return $$sin(x) / $$cos(x);
|
||||
$case $Type.typeid == float.typeid:
|
||||
return _tanf(x);
|
||||
$default:
|
||||
return _tan(x);
|
||||
$endswitch
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,11 +496,11 @@ $endswitch
|
||||
macro bool is_finite(x)
|
||||
{
|
||||
$switch ($typeof(x))
|
||||
$case float:
|
||||
$case float16:
|
||||
return bitcast((float)x, uint) & 0x7fffffff < 0x7f800000;
|
||||
$default:
|
||||
return bitcast((double)x, ulong) & (~0u64 >> 1) < 0x7ffu64 << 52;
|
||||
$case float:
|
||||
$case float16:
|
||||
return bitcast((float)x, uint) & 0x7fffffff < 0x7f800000;
|
||||
$default:
|
||||
return bitcast((double)x, ulong) & (~0u64 >> 1) < 0x7ffu64 << 52;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
@@ -546,6 +555,23 @@ macro normalize(x) @private
|
||||
return x * (1 / len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a mask to select values from either "then" or "else" vectors.
|
||||
*
|
||||
* @param mask "The mask to use for the select, 'true' will pick the then_value, 'false' the else_value"
|
||||
* @param then_value "The vector to get elements from where the mask is 'true'"
|
||||
* @param else_value "The vector to get elements from where the mask is 'false'"
|
||||
* @require values::@is_vector(then_value) && values::@is_vector(else_value) "'Then' and 'else' must be vectors."
|
||||
* @require values::@is_same_type(then_value, else_value) "'Then' and 'else' vectors must be of the same type."
|
||||
* @require then_value.len == mask.len "Mask and selected vectors must be of the same width."
|
||||
*
|
||||
* @return "a vector of the same type as then/else"
|
||||
**/
|
||||
macro select(bool[<*>] mask, then_value, else_value)
|
||||
{
|
||||
return $$select(mask, then_value, else_value);
|
||||
}
|
||||
|
||||
macro float float.ceil(float x) => $$ceil(x);
|
||||
macro float float.clamp(float x, float lower, float upper) => $$max(lower, $$min(x, upper));
|
||||
macro float float.copysign(float mag, float sgn) => $$copysign(mag, sgn);
|
||||
@@ -799,54 +825,124 @@ macro char char.sat_add(char x, char y) => $$sat_add(x, y);
|
||||
macro char char.sat_sub(char x, char y) => $$sat_sub(x, y);
|
||||
macro char char.sat_mul(char x, char y) => $$sat_mul(x, y);
|
||||
macro char char.sat_shl(char x, char y) => $$sat_shl(x, y);
|
||||
macro char! char.overflow_add(char x, char y) => overflow_add_helper(x, y);
|
||||
macro char! char.overflow_sub(char x, char y) => overflow_sub_helper(x, y);
|
||||
macro char! char.overflow_mul(char x, char y) => overflow_mul_helper(x, y);
|
||||
|
||||
|
||||
macro ichar ichar.sat_add(ichar x, ichar y) => $$sat_add(x, y);
|
||||
macro ichar ichar.sat_sub(ichar x, ichar y) => $$sat_sub(x, y);
|
||||
macro ichar ichar.sat_mul(ichar x, ichar y) => $$sat_mul(x, y);
|
||||
macro ichar ichar.sat_shl(ichar x, ichar y) => $$sat_shl(x, y);
|
||||
macro ichar! ichar.overflow_add(ichar x, ichar y) => overflow_add_helper(x, y);
|
||||
macro ichar! ichar.overflow_sub(ichar x, ichar y) => overflow_sub_helper(x, y);
|
||||
macro ichar! ichar.overflow_mul(ichar x, ichar y) => overflow_mul_helper(x, y);
|
||||
|
||||
macro ushort ushort.sat_add(ushort x, ushort y) => $$sat_add(x, y);
|
||||
macro ushort ushort.sat_sub(ushort x, ushort y) => $$sat_sub(x, y);
|
||||
macro ushort ushort.sat_mul(ushort x, ushort y) => $$sat_mul(x, y);
|
||||
macro ushort ushort.sat_shl(ushort x, ushort y) => $$sat_shl(x, y);
|
||||
macro ushort! ushort.overflow_add(ushort x, ushort y) => overflow_add_helper(x, y);
|
||||
macro ushort! ushort.overflow_sub(ushort x, ushort y) => overflow_sub_helper(x, y);
|
||||
macro ushort! ushort.overflow_mul(ushort x, ushort y) => overflow_mul_helper(x, y);
|
||||
|
||||
macro short short.sat_add(short x, short y) => $$sat_add(x, y);
|
||||
macro short short.sat_sub(short x, short y) => $$sat_sub(x, y);
|
||||
macro short short.sat_mul(short x, short y) => $$sat_mul(x, y);
|
||||
macro short short.sat_shl(short x, short y) => $$sat_shl(x, y);
|
||||
macro short! short.overflow_add(short x, short y) => overflow_add_helper(x, y);
|
||||
macro short! short.overflow_sub(short x, short y) => overflow_sub_helper(x, y);
|
||||
macro short! short.overflow_mul(short x, short y) => overflow_mul_helper(x, y);
|
||||
|
||||
macro uint uint.sat_add(uint x, uint y) => $$sat_add(x, y);
|
||||
macro uint uint.sat_sub(uint x, uint y) => $$sat_sub(x, y);
|
||||
macro uint uint.sat_mul(uint x, uint y) => $$sat_mul(x, y);
|
||||
macro uint uint.sat_shl(uint x, uint y) => $$sat_shl(x, y);
|
||||
macro uint! uint.overflow_add(uint x, uint y) => overflow_add_helper(x, y);
|
||||
macro uint! uint.overflow_sub(uint x, uint y) => overflow_sub_helper(x, y);
|
||||
macro uint! uint.overflow_mul(uint x, uint y) => overflow_mul_helper(x, y);
|
||||
|
||||
macro int int.sat_add(int x, int y) => $$sat_add(x, y);
|
||||
macro int int.sat_sub(int x, int y) => $$sat_sub(x, y);
|
||||
macro int int.sat_mul(int x, int y) => $$sat_mul(x, y);
|
||||
macro int int.sat_shl(int x, int y) => $$sat_shl(x, y);
|
||||
macro int! int.overflow_add(int x, int y) => overflow_add_helper(x, y);
|
||||
macro int! int.overflow_sub(int x, int y) => overflow_sub_helper(x, y);
|
||||
macro int! int.overflow_mul(int x, int y) => overflow_mul_helper(x, y);
|
||||
|
||||
macro ulong ulong.sat_add(ulong x, ulong y) => $$sat_add(x, y);
|
||||
macro ulong ulong.sat_sub(ulong x, ulong y) => $$sat_sub(x, y);
|
||||
macro ulong ulong.sat_mul(ulong x, ulong y) => $$sat_mul(x, y);
|
||||
macro ulong ulong.sat_shl(ulong x, ulong y) => $$sat_shl(x, y);
|
||||
macro ulong! ulong.overflow_add(ulong x, ulong y) => overflow_add_helper(x, y);
|
||||
macro ulong! ulong.overflow_sub(ulong x, ulong y) => overflow_sub_helper(x, y);
|
||||
macro ulong! ulong.overflow_mul(ulong x, ulong y) => overflow_mul_helper(x, y);
|
||||
|
||||
macro long long.sat_add(long x, long y) => $$sat_add(x, y);
|
||||
macro long long.sat_sub(long x, long y) => $$sat_sub(x, y);
|
||||
macro long long.sat_mul(long x, long y) => $$sat_mul(x, y);
|
||||
macro long long.sat_shl(long x, long y) => $$sat_shl(x, y);
|
||||
macro long! long.overflow_add(long x, long y) => overflow_add_helper(x, y);
|
||||
macro long! long.overflow_sub(long x, long y) => overflow_sub_helper(x, y);
|
||||
macro long! long.overflow_mul(long x, long y) => overflow_mul_helper(x, y);
|
||||
|
||||
macro uint128 uint128.sat_add(uint128 x, uint128 y) => $$sat_add(x, y);
|
||||
macro uint128 uint128.sat_sub(uint128 x, uint128 y) => $$sat_sub(x, y);
|
||||
macro uint128 uint128.sat_mul(uint128 x, uint128 y) => $$sat_mul(x, y);
|
||||
macro uint128 uint128.sat_shl(uint128 x, uint128 y) => $$sat_shl(x, y);
|
||||
macro uint128! uint128.overflow_add(uint128 x, uint128 y) => overflow_add_helper(x, y);
|
||||
macro uint128! uint128.overflow_sub(uint128 x, uint128 y) => overflow_sub_helper(x, y);
|
||||
macro uint128! uint128.overflow_mul(uint128 x, uint128 y) => overflow_mul_helper(x, y);
|
||||
|
||||
macro int128 int128.sat_add(int128 x, int128 y) => $$sat_add(x, y);
|
||||
macro int128 int128.sat_sub(int128 x, int128 y) => $$sat_sub(x, y);
|
||||
macro int128 int128.sat_mul(int128 x, int128 y) => $$sat_mul(x, y);
|
||||
macro int128 int128.sat_shl(int128 x, int128 y) => $$sat_shl(x, y);
|
||||
macro int128! int128.overflow_add(int128 x, int128 y) => overflow_add_helper(x, y);
|
||||
macro int128! int128.overflow_sub(int128 x, int128 y) => overflow_sub_helper(x, y);
|
||||
macro int128! int128.overflow_mul(int128 x, int128 y) => overflow_mul_helper(x, y);
|
||||
/**
|
||||
* @require values::@is_int(x) `The input must be an integer`
|
||||
**/
|
||||
macro bool is_odd(x) => (bool)(x & 1);
|
||||
|
||||
/**
|
||||
* @checked x & 1
|
||||
* @require values::@is_int(x) `The input must be an integer`
|
||||
**/
|
||||
macro bool is_even(x) => !is_odd(x);
|
||||
|
||||
macro bool char.is_even(char x) => is_even(x);
|
||||
macro bool char.is_odd(char x) => is_odd(x);
|
||||
|
||||
macro bool ichar.is_even(ichar x) => is_even(x);
|
||||
macro bool ichar.is_odd(ichar x) => is_odd(x);
|
||||
|
||||
macro bool ushort.is_even(ushort x) => is_even(x);
|
||||
macro bool ushort.is_odd(ushort x) => is_odd(x);
|
||||
|
||||
macro bool short.is_even(short x) => is_even(x);
|
||||
macro bool short.is_odd(short x) => is_odd(x);
|
||||
|
||||
macro bool uint.is_even(uint x) => is_even(x);
|
||||
macro bool uint.is_odd(uint x) => is_odd(x);
|
||||
|
||||
macro bool int.is_even(int x) => is_even(x);
|
||||
macro bool int.is_odd(int x) => is_odd(x);
|
||||
|
||||
macro bool ulong.is_even(ulong x) => is_even(x);
|
||||
macro bool ulong.is_odd(ulong x) => is_odd(x);
|
||||
|
||||
macro bool long.is_even(long x) => is_even(x);
|
||||
macro bool long.is_odd(long x) => is_odd(x);
|
||||
|
||||
macro bool uint128.is_even(uint128 x) => is_even(x);
|
||||
macro bool uint128.is_odd(uint128 x) => is_odd(x);
|
||||
|
||||
macro bool int128.is_even(int128 x) => is_even(x);
|
||||
macro bool int128.is_odd(int128 x) => is_odd(x);
|
||||
|
||||
/**
|
||||
* @require types::is_underlying_int($typeof(x)) : `is_power_of_2 may only be used on integer types`
|
||||
*/
|
||||
macro bool is_power_of_2(x)
|
||||
{
|
||||
@@ -866,7 +962,7 @@ macro equals_vec(v1, v2) @private
|
||||
var abs_diff = math::abs(v1 - v2);
|
||||
var abs_v1 = math::abs(v1);
|
||||
var abs_v2 = math::abs(v2);
|
||||
$typeof(abs_v2) eps = 1;
|
||||
$typeof(abs_v2) eps = 1;
|
||||
return abs_diff.comp_le(FLOAT_EPSILON * math::max(abs_v1, abs_v2, eps)).and();
|
||||
}
|
||||
|
||||
@@ -947,4 +1043,25 @@ fn float _frexpf(float x, int* e)
|
||||
i |= 0x3f000000u32;
|
||||
return bitcast(i, float);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro overflow_add_helper(x, y) @local
|
||||
{
|
||||
$typeof(x) res @noinit;
|
||||
if ($$overflow_add(x, y, &res)) return MathError.OVERFLOW?;
|
||||
return res;
|
||||
}
|
||||
|
||||
macro overflow_sub_helper(x, y) @local
|
||||
{
|
||||
$typeof(x) res @noinit;
|
||||
if ($$overflow_sub(x, y, &res)) return MathError.OVERFLOW?;
|
||||
return res;
|
||||
}
|
||||
|
||||
macro overflow_mul_helper(x, y) @local
|
||||
{
|
||||
$typeof(x) res @noinit;
|
||||
if ($$overflow_mul(x, y, &res)) return MathError.OVERFLOW?;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
module std::math::complex<Real>;
|
||||
module std::math::complex(<Real>);
|
||||
|
||||
union Complex
|
||||
{
|
||||
struct
|
||||
{
|
||||
Real r, c;
|
||||
}
|
||||
{
|
||||
Real r, c;
|
||||
}
|
||||
Real[<2>] v;
|
||||
}
|
||||
|
||||
|
||||
macro Complex identity() => { 1, 0 };
|
||||
macro Complex Complex.add(Complex a, Complex b) => Complex { .v = a.v + b.v };
|
||||
macro Complex Complex.add_each(Complex a, Real b) => Complex { .v = a.v + b };
|
||||
macro Complex Complex.sub(Complex a, Complex b) => Complex { .v = a.v - b.v };
|
||||
macro Complex Complex.sub_each(Complex a, Real b) => Complex { .v = a.v - b };
|
||||
macro Complex Complex.scale(Complex a, Real s) => Complex { .v = a.v * s };
|
||||
macro Complex Complex.mul(Complex a, Complex b) => { a.r * b.r - a.c * b.c, a.r * b.c + b.r * a.c };
|
||||
macro Complex Complex.div(Complex a, Complex b)
|
||||
macro Complex Complex.add(self, Complex b) => Complex { .v = self.v + b.v };
|
||||
macro Complex Complex.add_each(self, Real b) => Complex { .v = self.v + b };
|
||||
macro Complex Complex.sub(self, Complex b) => Complex { .v = self.v - b.v };
|
||||
macro Complex Complex.sub_each(self, Real b) => Complex { .v = self.v - b };
|
||||
macro Complex Complex.scale(self, Real s) => Complex { .v = self.v * s };
|
||||
macro Complex Complex.mul(self, Complex b) => { self.r * b.r - self.c * b.c, self.r * b.c + b.r * self.c };
|
||||
macro Complex Complex.div(self, Complex b)
|
||||
{
|
||||
Real div = b.v.dot(b.v);
|
||||
return Complex{ (a.r * b.r + a.c * b.c) / div, (a.c * b.r - a.r * b.c) / div };
|
||||
return Complex{ (self.r * b.r + self.c * b.c) / div, (self.c * b.r - self.r * b.c) / div };
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module std::math::matrix<Real>;
|
||||
module std::math::matrix(<Real>);
|
||||
|
||||
struct Matrix2x2
|
||||
{
|
||||
@@ -42,62 +42,62 @@ struct Matrix4x4
|
||||
}
|
||||
}
|
||||
|
||||
fn Real[<2>] Matrix2x2.apply(Matrix2x2* mat, Real[<2>] vec)
|
||||
fn Real[<2>] Matrix2x2.apply(&self, Real[<2>] vec)
|
||||
{
|
||||
return Real[<2>] {
|
||||
mat.m00 * vec[0] + mat.m01 * vec[1],
|
||||
mat.m10 * vec[0] + mat.m11 * vec[1],
|
||||
return {
|
||||
self.m00 * vec[0] + self.m01 * vec[1],
|
||||
self.m10 * vec[0] + self.m11 * vec[1],
|
||||
};
|
||||
}
|
||||
|
||||
fn Real[<3>] Matrix3x3.apply(Matrix3x3* mat, Real[<3>] vec)
|
||||
fn Real[<3>] Matrix3x3.apply(&self, Real[<3>] vec)
|
||||
{
|
||||
return Real[<3>] {
|
||||
mat.m00 * vec[0] + mat.m01 * vec[1] + mat.m02 * vec[2],
|
||||
mat.m10 * vec[0] + mat.m11 * vec[1] + mat.m12 * vec[2],
|
||||
mat.m20 * vec[0] + mat.m21 * vec[1] + mat.m22 * vec[2],
|
||||
return {
|
||||
self.m00 * vec[0] + self.m01 * vec[1] + self.m02 * vec[2],
|
||||
self.m10 * vec[0] + self.m11 * vec[1] + self.m12 * vec[2],
|
||||
self.m20 * vec[0] + self.m21 * vec[1] + self.m22 * vec[2],
|
||||
};
|
||||
}
|
||||
|
||||
fn Real[<4>] Matrix4x4.apply(Matrix4x4* mat, Real[<4>] vec)
|
||||
fn Real[<4>] Matrix4x4.apply(&self, Real[<4>] vec)
|
||||
{
|
||||
return Real[<4>] {
|
||||
mat.m00 * vec[0] + mat.m01 * vec[1] + mat.m02 * vec[2] + mat.m03 * vec[3],
|
||||
mat.m10 * vec[0] + mat.m11 * vec[1] + mat.m12 * vec[2] + mat.m13 * vec[3],
|
||||
mat.m20 * vec[0] + mat.m21 * vec[1] + mat.m22 * vec[2] + mat.m23 * vec[3],
|
||||
mat.m30 * vec[0] + mat.m31 * vec[1] + mat.m32 * vec[2] + mat.m33 * vec[3],
|
||||
return {
|
||||
self.m00 * vec[0] + self.m01 * vec[1] + self.m02 * vec[2] + self.m03 * vec[3],
|
||||
self.m10 * vec[0] + self.m11 * vec[1] + self.m12 * vec[2] + self.m13 * vec[3],
|
||||
self.m20 * vec[0] + self.m21 * vec[1] + self.m22 * vec[2] + self.m23 * vec[3],
|
||||
self.m30 * vec[0] + self.m31 * vec[1] + self.m32 * vec[2] + self.m33 * vec[3],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
fn Matrix2x2 Matrix2x2.mul(Matrix2x2* a, Matrix2x2 b)
|
||||
fn Matrix2x2 Matrix2x2.mul(&self, Matrix2x2 b)
|
||||
{
|
||||
return Matrix2x2 {
|
||||
a.m00 * b.m00 + a.m01 * b.m10, a.m00 * b.m01 + a.m01 * b.m11,
|
||||
a.m10 * b.m00 + a.m11 * b.m10, a.m10 * b.m01 + a.m11 * b.m11,
|
||||
return {
|
||||
self.m00 * b.m00 + self.m01 * b.m10, self.m00 * b.m01 + self.m01 * b.m11,
|
||||
self.m10 * b.m00 + self.m11 * b.m10, self.m10 * b.m01 + self.m11 * b.m11,
|
||||
};
|
||||
}
|
||||
|
||||
fn Matrix3x3 Matrix3x3.mul(Matrix3x3* a, Matrix3x3 b)
|
||||
fn Matrix3x3 Matrix3x3.mul(&self, Matrix3x3 b)
|
||||
{
|
||||
return Matrix3x3 {
|
||||
a.m00 * b.m00 + a.m01 * b.m10 + a.m02 * b.m20,
|
||||
a.m00 * b.m01 + a.m01 * b.m11 + a.m02 * b.m21,
|
||||
a.m00 * b.m02 + a.m01 * b.m12 + a.m02 * b.m22,
|
||||
return {
|
||||
self.m00 * b.m00 + self.m01 * b.m10 + self.m02 * b.m20,
|
||||
self.m00 * b.m01 + self.m01 * b.m11 + self.m02 * b.m21,
|
||||
self.m00 * b.m02 + self.m01 * b.m12 + self.m02 * b.m22,
|
||||
|
||||
a.m10 * b.m00 + a.m11 * b.m10 + a.m12 * b.m20,
|
||||
a.m10 * b.m01 + a.m11 * b.m11 + a.m12 * b.m21,
|
||||
a.m10 * b.m02 + a.m11 * b.m12 + a.m12 * b.m22,
|
||||
self.m10 * b.m00 + self.m11 * b.m10 + self.m12 * b.m20,
|
||||
self.m10 * b.m01 + self.m11 * b.m11 + self.m12 * b.m21,
|
||||
self.m10 * b.m02 + self.m11 * b.m12 + self.m12 * b.m22,
|
||||
|
||||
a.m20 * b.m00 + a.m21 * b.m10 + a.m22 * b.m20,
|
||||
a.m20 * b.m01 + a.m21 * b.m11 + a.m22 * b.m21,
|
||||
a.m20 * b.m02 + a.m21 * b.m12 + a.m22 * b.m22,
|
||||
self.m20 * b.m00 + self.m21 * b.m10 + self.m22 * b.m20,
|
||||
self.m20 * b.m01 + self.m21 * b.m11 + self.m22 * b.m21,
|
||||
self.m20 * b.m02 + self.m21 * b.m12 + self.m22 * b.m22,
|
||||
};
|
||||
}
|
||||
|
||||
fn Matrix4x4 Matrix4x4.mul(Matrix4x4* a, Matrix4x4 b)
|
||||
{
|
||||
return Matrix4x4 {
|
||||
return {
|
||||
a.m00 * b.m00 + a.m01 * b.m10 + a.m02 * b.m20 + a.m03 * b.m30,
|
||||
a.m00 * b.m01 + a.m01 * b.m11 + a.m02 * b.m21 + a.m03 * b.m31,
|
||||
a.m00 * b.m02 + a.m01 * b.m12 + a.m02 * b.m22 + a.m03 * b.m32,
|
||||
@@ -120,196 +120,196 @@ fn Matrix4x4 Matrix4x4.mul(Matrix4x4* a, Matrix4x4 b)
|
||||
};
|
||||
}
|
||||
|
||||
fn Matrix2x2 Matrix2x2.component_mul(Matrix2x2* mat, Real s) => matrix_component_mul(mat, s);
|
||||
fn Matrix3x3 Matrix3x3.component_mul(Matrix3x3* mat, Real s) => matrix_component_mul(mat, s);
|
||||
fn Matrix4x4 Matrix4x4.component_mul(Matrix4x4* mat, Real s) => matrix_component_mul(mat, s);
|
||||
fn Matrix2x2 Matrix2x2.component_mul(&self, Real s) => matrix_component_mul(self, s);
|
||||
fn Matrix3x3 Matrix3x3.component_mul(&self, Real s) => matrix_component_mul(self, s);
|
||||
fn Matrix4x4 Matrix4x4.component_mul(&self, Real s) => matrix_component_mul(self, s);
|
||||
|
||||
fn Matrix2x2 Matrix2x2.add(Matrix2x2* mat, Matrix2x2 mat2) => matrix_add(mat, mat2);
|
||||
fn Matrix3x3 Matrix3x3.add(Matrix3x3* mat, Matrix3x3 mat2) => matrix_add(mat, mat2);
|
||||
fn Matrix4x4 Matrix4x4.add(Matrix4x4* mat, Matrix4x4 mat2) => matrix_add(mat, mat2);
|
||||
fn Matrix2x2 Matrix2x2.add(&self, Matrix2x2 mat2) => matrix_add(self, mat2);
|
||||
fn Matrix3x3 Matrix3x3.add(&self, Matrix3x3 mat2) => matrix_add(self, mat2);
|
||||
fn Matrix4x4 Matrix4x4.add(&self, Matrix4x4 mat2) => matrix_add(self, mat2);
|
||||
|
||||
fn Matrix2x2 Matrix2x2.sub(Matrix2x2* mat, Matrix2x2 mat2) => matrix_sub(mat, mat2);
|
||||
fn Matrix3x3 Matrix3x3.sub(Matrix3x3* mat, Matrix3x3 mat2) => matrix_sub(mat, mat2);
|
||||
fn Matrix4x4 Matrix4x4.sub(Matrix4x4* mat, Matrix4x4 mat2) => matrix_sub(mat, mat2);
|
||||
fn Matrix2x2 Matrix2x2.sub(&self, Matrix2x2 mat2) => matrix_sub(self, mat2);
|
||||
fn Matrix3x3 Matrix3x3.sub(&self, Matrix3x3 mat2) => matrix_sub(self, mat2);
|
||||
fn Matrix4x4 Matrix4x4.sub(&self, Matrix4x4 mat2) => matrix_sub(self, mat2);
|
||||
|
||||
|
||||
|
||||
fn Matrix2x2 Matrix2x2.transpose(Matrix2x2* mat)
|
||||
fn Matrix2x2 Matrix2x2.transpose(&self)
|
||||
{
|
||||
return Matrix2x2 {
|
||||
mat.m00, mat.m10,
|
||||
mat.m01, mat.m11
|
||||
return {
|
||||
self.m00, self.m10,
|
||||
self.m01, self.m11
|
||||
};
|
||||
}
|
||||
|
||||
fn Matrix3x3 Matrix3x3.transpose(Matrix3x3* mat)
|
||||
fn Matrix3x3 Matrix3x3.transpose(&self)
|
||||
{
|
||||
return Matrix3x3 {
|
||||
mat.m00, mat.m10, mat.m20,
|
||||
mat.m01, mat.m11, mat.m21,
|
||||
mat.m02, mat.m12, mat.m22,
|
||||
return {
|
||||
self.m00, self.m10, self.m20,
|
||||
self.m01, self.m11, self.m21,
|
||||
self.m02, self.m12, self.m22,
|
||||
};
|
||||
}
|
||||
|
||||
fn Matrix4x4 Matrix4x4.transpose(Matrix4x4* mat)
|
||||
fn Matrix4x4 Matrix4x4.transpose(&self)
|
||||
{
|
||||
return Matrix4x4 {
|
||||
mat.m00, mat.m10, mat.m20, mat.m30,
|
||||
mat.m01, mat.m11, mat.m21, mat.m31,
|
||||
mat.m02, mat.m12, mat.m22, mat.m32,
|
||||
mat.m03, mat.m13, mat.m23, mat.m33,
|
||||
return {
|
||||
self.m00, self.m10, self.m20, self.m30,
|
||||
self.m01, self.m11, self.m21, self.m31,
|
||||
self.m02, self.m12, self.m22, self.m32,
|
||||
self.m03, self.m13, self.m23, self.m33,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
fn Real Matrix2x2.determinant(Matrix2x2* mat)
|
||||
fn Real Matrix2x2.determinant(&self)
|
||||
{
|
||||
return mat.m00 * mat.m11 - mat.m01 * mat.m10;
|
||||
return self.m00 * self.m11 - self.m01 * self.m10;
|
||||
}
|
||||
|
||||
fn Real Matrix3x3.determinant(Matrix3x3* mat)
|
||||
fn Real Matrix3x3.determinant(&self)
|
||||
{
|
||||
return
|
||||
mat.m00 * (mat.m11 * mat.m22 - mat.m21 * mat.m12) -
|
||||
mat.m01 * (mat.m10 * mat.m22 - mat.m20 * mat.m12) +
|
||||
mat.m02 * (mat.m10 * mat.m21 - mat.m20 * mat.m11);
|
||||
self.m00 * (self.m11 * self.m22 - self.m21 * self.m12) -
|
||||
self.m01 * (self.m10 * self.m22 - self.m20 * self.m12) +
|
||||
self.m02 * (self.m10 * self.m21 - self.m20 * self.m11);
|
||||
}
|
||||
|
||||
fn Real Matrix4x4.determinant(Matrix4x4* mat)
|
||||
fn Real Matrix4x4.determinant(&self)
|
||||
{
|
||||
return
|
||||
mat.m00 * (mat.m11 * (mat.m22 * mat.m33 - mat.m32 * mat.m23) -
|
||||
mat.m12 * (mat.m21 * mat.m33 - mat.m31 * mat.m23) +
|
||||
mat.m13 * (mat.m21 * mat.m32 - mat.m31 * mat.m22) ) -
|
||||
mat.m01 * (mat.m10 * (mat.m22 * mat.m33 - mat.m32 * mat.m23) -
|
||||
mat.m12 * (mat.m20 * mat.m33 - mat.m30 * mat.m23) +
|
||||
mat.m13 * (mat.m20 * mat.m32 - mat.m30 * mat.m22) ) +
|
||||
mat.m02 * (mat.m10 * (mat.m21 * mat.m33 - mat.m31 * mat.m23) -
|
||||
mat.m11 * (mat.m20 * mat.m33 - mat.m30 * mat.m23) +
|
||||
mat.m13 * (mat.m20 * mat.m31 - mat.m30 * mat.m21) ) -
|
||||
mat.m03 * (mat.m10 * (mat.m21 * mat.m32 - mat.m31 * mat.m22) -
|
||||
mat.m11 * (mat.m20 * mat.m32 - mat.m30 * mat.m22) +
|
||||
mat.m12 * (mat.m20 * mat.m31 - mat.m30 * mat.m21) );
|
||||
self.m00 * (self.m11 * (self.m22 * self.m33 - self.m32 * self.m23) -
|
||||
self.m12 * (self.m21 * self.m33 - self.m31 * self.m23) +
|
||||
self.m13 * (self.m21 * self.m32 - self.m31 * self.m22) ) -
|
||||
self.m01 * (self.m10 * (self.m22 * self.m33 - self.m32 * self.m23) -
|
||||
self.m12 * (self.m20 * self.m33 - self.m30 * self.m23) +
|
||||
self.m13 * (self.m20 * self.m32 - self.m30 * self.m22) ) +
|
||||
self.m02 * (self.m10 * (self.m21 * self.m33 - self.m31 * self.m23) -
|
||||
self.m11 * (self.m20 * self.m33 - self.m30 * self.m23) +
|
||||
self.m13 * (self.m20 * self.m31 - self.m30 * self.m21) ) -
|
||||
self.m03 * (self.m10 * (self.m21 * self.m32 - self.m31 * self.m22) -
|
||||
self.m11 * (self.m20 * self.m32 - self.m30 * self.m22) +
|
||||
self.m12 * (self.m20 * self.m31 - self.m30 * self.m21) );
|
||||
}
|
||||
|
||||
|
||||
fn Matrix2x2 Matrix2x2.adjoint(Matrix2x2* mat)
|
||||
fn Matrix2x2 Matrix2x2.adjoint(&self)
|
||||
{
|
||||
return Matrix2x2 { mat.m00, -mat.m01, -mat.m10, mat.m11 };
|
||||
return { self.m00, -self.m01, -self.m10, self.m11 };
|
||||
}
|
||||
|
||||
fn Matrix3x3 Matrix3x3.adjoint(Matrix3x3* mat)
|
||||
fn Matrix3x3 Matrix3x3.adjoint(&self)
|
||||
{
|
||||
return Matrix3x3 {
|
||||
(mat.m11 * mat.m22 - mat.m21 * mat.m12),
|
||||
-(mat.m10 * mat.m22 - mat.m20 * mat.m12),
|
||||
(mat.m10 * mat.m21 - mat.m20 * mat.m11),
|
||||
return {
|
||||
(self.m11 * self.m22 - self.m21 * self.m12),
|
||||
-(self.m10 * self.m22 - self.m20 * self.m12),
|
||||
(self.m10 * self.m21 - self.m20 * self.m11),
|
||||
|
||||
-(mat.m01 * mat.m22 - mat.m21 * mat.m02),
|
||||
(mat.m00 * mat.m22 - mat.m20 * mat.m02),
|
||||
-(mat.m00 * mat.m21 - mat.m20 * mat.m01),
|
||||
-(self.m01 * self.m22 - self.m21 * self.m02),
|
||||
(self.m00 * self.m22 - self.m20 * self.m02),
|
||||
-(self.m00 * self.m21 - self.m20 * self.m01),
|
||||
|
||||
(mat.m01 * mat.m12 - mat.m11 * mat.m02),
|
||||
-(mat.m00 * mat.m12 - mat.m10 * mat.m02),
|
||||
(mat.m00 * mat.m11 - mat.m10 * mat.m01),
|
||||
(self.m01 * self.m12 - self.m11 * self.m02),
|
||||
-(self.m00 * self.m12 - self.m10 * self.m02),
|
||||
(self.m00 * self.m11 - self.m10 * self.m01),
|
||||
};
|
||||
}
|
||||
|
||||
fn Matrix4x4 Matrix4x4.adjoint(Matrix4x4* mat)
|
||||
fn Matrix4x4 Matrix4x4.adjoint(&self)
|
||||
{
|
||||
return Matrix4x4 {
|
||||
(mat.m11 * (mat.m22 * mat.m33 - mat.m32 * mat.m23) -
|
||||
mat.m12 * (mat.m21 * mat.m33 - mat.m31 * mat.m23) +
|
||||
mat.m13 * (mat.m21 * mat.m32 - mat.m31 * mat.m22)),
|
||||
-(mat.m10 * (mat.m22 * mat.m33 - mat.m32 * mat.m23) -
|
||||
mat.m12 * (mat.m20 * mat.m33 - mat.m30 * mat.m23) +
|
||||
mat.m13 * (mat.m20 * mat.m32 - mat.m30 * mat.m22)),
|
||||
(mat.m10 * (mat.m21 * mat.m33 - mat.m31 * mat.m23) -
|
||||
mat.m11 * (mat.m20 * mat.m33 - mat.m30 * mat.m23) +
|
||||
mat.m13 * (mat.m20 * mat.m31 - mat.m30 * mat.m21)),
|
||||
-(mat.m10 * (mat.m21 * mat.m32 - mat.m31 * mat.m22) -
|
||||
mat.m11 * (mat.m20 * mat.m32 - mat.m30 * mat.m22) +
|
||||
mat.m12 * (mat.m20 * mat.m31 - mat.m30 * mat.m21)),
|
||||
return {
|
||||
(self.m11 * (self.m22 * self.m33 - self.m32 * self.m23) -
|
||||
self.m12 * (self.m21 * self.m33 - self.m31 * self.m23) +
|
||||
self.m13 * (self.m21 * self.m32 - self.m31 * self.m22)),
|
||||
-(self.m10 * (self.m22 * self.m33 - self.m32 * self.m23) -
|
||||
self.m12 * (self.m20 * self.m33 - self.m30 * self.m23) +
|
||||
self.m13 * (self.m20 * self.m32 - self.m30 * self.m22)),
|
||||
(self.m10 * (self.m21 * self.m33 - self.m31 * self.m23) -
|
||||
self.m11 * (self.m20 * self.m33 - self.m30 * self.m23) +
|
||||
self.m13 * (self.m20 * self.m31 - self.m30 * self.m21)),
|
||||
-(self.m10 * (self.m21 * self.m32 - self.m31 * self.m22) -
|
||||
self.m11 * (self.m20 * self.m32 - self.m30 * self.m22) +
|
||||
self.m12 * (self.m20 * self.m31 - self.m30 * self.m21)),
|
||||
|
||||
-(mat.m01 * (mat.m22 * mat.m33 - mat.m32 * mat.m23) -
|
||||
mat.m02 * (mat.m21 * mat.m33 - mat.m31 * mat.m23) +
|
||||
mat.m03 * (mat.m21 * mat.m32 - mat.m31 * mat.m22)),
|
||||
(mat.m00 * (mat.m22 * mat.m33 - mat.m32 * mat.m23) -
|
||||
mat.m02 * (mat.m20 * mat.m33 - mat.m30 * mat.m23) +
|
||||
mat.m03 * (mat.m20 * mat.m32 - mat.m30 * mat.m22)),
|
||||
-(mat.m00 * (mat.m21 * mat.m33 - mat.m31 * mat.m23) -
|
||||
mat.m01 * (mat.m20 * mat.m33 - mat.m30 * mat.m23) +
|
||||
mat.m03 * (mat.m20 * mat.m31 - mat.m30 * mat.m21)),
|
||||
(mat.m00 * (mat.m21 * mat.m32 - mat.m31 * mat.m22) -
|
||||
mat.m01 * (mat.m20 * mat.m32 - mat.m30 * mat.m22) +
|
||||
mat.m02 * (mat.m20 * mat.m31 - mat.m30 * mat.m21)),
|
||||
-(self.m01 * (self.m22 * self.m33 - self.m32 * self.m23) -
|
||||
self.m02 * (self.m21 * self.m33 - self.m31 * self.m23) +
|
||||
self.m03 * (self.m21 * self.m32 - self.m31 * self.m22)),
|
||||
(self.m00 * (self.m22 * self.m33 - self.m32 * self.m23) -
|
||||
self.m02 * (self.m20 * self.m33 - self.m30 * self.m23) +
|
||||
self.m03 * (self.m20 * self.m32 - self.m30 * self.m22)),
|
||||
-(self.m00 * (self.m21 * self.m33 - self.m31 * self.m23) -
|
||||
self.m01 * (self.m20 * self.m33 - self.m30 * self.m23) +
|
||||
self.m03 * (self.m20 * self.m31 - self.m30 * self.m21)),
|
||||
(self.m00 * (self.m21 * self.m32 - self.m31 * self.m22) -
|
||||
self.m01 * (self.m20 * self.m32 - self.m30 * self.m22) +
|
||||
self.m02 * (self.m20 * self.m31 - self.m30 * self.m21)),
|
||||
|
||||
(mat.m01 * (mat.m12 * mat.m33 - mat.m32 * mat.m13) -
|
||||
mat.m02 * (mat.m11 * mat.m33 - mat.m31 * mat.m13) +
|
||||
mat.m03 * (mat.m11 * mat.m32 - mat.m31 * mat.m12)),
|
||||
-(mat.m00 * (mat.m12 * mat.m33 - mat.m32 * mat.m13) -
|
||||
mat.m02 * (mat.m10 * mat.m33 - mat.m30 * mat.m13) +
|
||||
mat.m03 * (mat.m10 * mat.m32 - mat.m30 * mat.m12)),
|
||||
(mat.m00 * (mat.m11 * mat.m33 - mat.m31 * mat.m13) -
|
||||
mat.m01 * (mat.m10 * mat.m33 - mat.m30 * mat.m13) +
|
||||
mat.m03 * (mat.m10 * mat.m31 - mat.m30 * mat.m11)),
|
||||
-(mat.m00 * (mat.m11 * mat.m32 - mat.m31 * mat.m12) -
|
||||
mat.m01 * (mat.m10 * mat.m32 - mat.m30 * mat.m12) +
|
||||
mat.m02 * (mat.m10 * mat.m31 - mat.m30 * mat.m11)),
|
||||
(self.m01 * (self.m12 * self.m33 - self.m32 * self.m13) -
|
||||
self.m02 * (self.m11 * self.m33 - self.m31 * self.m13) +
|
||||
self.m03 * (self.m11 * self.m32 - self.m31 * self.m12)),
|
||||
-(self.m00 * (self.m12 * self.m33 - self.m32 * self.m13) -
|
||||
self.m02 * (self.m10 * self.m33 - self.m30 * self.m13) +
|
||||
self.m03 * (self.m10 * self.m32 - self.m30 * self.m12)),
|
||||
(self.m00 * (self.m11 * self.m33 - self.m31 * self.m13) -
|
||||
self.m01 * (self.m10 * self.m33 - self.m30 * self.m13) +
|
||||
self.m03 * (self.m10 * self.m31 - self.m30 * self.m11)),
|
||||
-(self.m00 * (self.m11 * self.m32 - self.m31 * self.m12) -
|
||||
self.m01 * (self.m10 * self.m32 - self.m30 * self.m12) +
|
||||
self.m02 * (self.m10 * self.m31 - self.m30 * self.m11)),
|
||||
|
||||
-(mat.m01 * (mat.m12 * mat.m23 - mat.m22 * mat.m13) -
|
||||
mat.m02 * (mat.m11 * mat.m23 - mat.m21 * mat.m13) +
|
||||
mat.m03 * (mat.m11 * mat.m22 - mat.m21 * mat.m12)),
|
||||
(mat.m00 * (mat.m12 * mat.m23 - mat.m22 * mat.m13) -
|
||||
mat.m02 * (mat.m10 * mat.m23 - mat.m20 * mat.m13) +
|
||||
mat.m03 * (mat.m10 * mat.m22 - mat.m20 * mat.m12)),
|
||||
-(mat.m00 * (mat.m11 * mat.m23 - mat.m21 * mat.m13) -
|
||||
mat.m01 * (mat.m10 * mat.m23 - mat.m20 * mat.m13) +
|
||||
mat.m03 * (mat.m10 * mat.m21 - mat.m20 * mat.m11)),
|
||||
(mat.m00 * (mat.m11 * mat.m22 - mat.m21 * mat.m12) -
|
||||
mat.m01 * (mat.m10 * mat.m22 - mat.m20 * mat.m12) +
|
||||
mat.m02 * (mat.m10 * mat.m21 - mat.m20 * mat.m11)),
|
||||
-(self.m01 * (self.m12 * self.m23 - self.m22 * self.m13) -
|
||||
self.m02 * (self.m11 * self.m23 - self.m21 * self.m13) +
|
||||
self.m03 * (self.m11 * self.m22 - self.m21 * self.m12)),
|
||||
(self.m00 * (self.m12 * self.m23 - self.m22 * self.m13) -
|
||||
self.m02 * (self.m10 * self.m23 - self.m20 * self.m13) +
|
||||
self.m03 * (self.m10 * self.m22 - self.m20 * self.m12)),
|
||||
-(self.m00 * (self.m11 * self.m23 - self.m21 * self.m13) -
|
||||
self.m01 * (self.m10 * self.m23 - self.m20 * self.m13) +
|
||||
self.m03 * (self.m10 * self.m21 - self.m20 * self.m11)),
|
||||
(self.m00 * (self.m11 * self.m22 - self.m21 * self.m12) -
|
||||
self.m01 * (self.m10 * self.m22 - self.m20 * self.m12) +
|
||||
self.m02 * (self.m10 * self.m21 - self.m20 * self.m11)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
fn Matrix2x2! Matrix2x2.inverse(Matrix2x2* m)
|
||||
fn Matrix2x2! Matrix2x2.inverse(&self)
|
||||
{
|
||||
Real det = m.determinant();
|
||||
Real det = self.determinant();
|
||||
if (det == 0) return MatrixError.MATRIX_INVERSE_DOESNT_EXIST?;
|
||||
Matrix2x2 adj = m.adjoint();
|
||||
Matrix2x2 adj = self.adjoint();
|
||||
return adj.component_mul(1 / det).transpose();
|
||||
}
|
||||
|
||||
fn Matrix3x3! Matrix3x3.inverse(Matrix3x3* m)
|
||||
fn Matrix3x3! Matrix3x3.inverse(&self)
|
||||
{
|
||||
Real det = m.determinant();
|
||||
Real det = self.determinant();
|
||||
if (det == 0) return MatrixError.MATRIX_INVERSE_DOESNT_EXIST?;
|
||||
Matrix3x3 adj = m.adjoint();
|
||||
Matrix3x3 adj = self.adjoint();
|
||||
return adj.component_mul(1 / det).transpose();
|
||||
}
|
||||
|
||||
fn Matrix4x4! Matrix4x4.inverse(Matrix4x4* m)
|
||||
fn Matrix4x4! Matrix4x4.inverse(&self)
|
||||
{
|
||||
Real det = m.determinant();
|
||||
Real det = self.determinant();
|
||||
if (det == 0) return MatrixError.MATRIX_INVERSE_DOESNT_EXIST?;
|
||||
Matrix4x4 adj = m.adjoint();
|
||||
Matrix4x4 adj = self.adjoint();
|
||||
return adj.component_mul(1 / det).transpose();
|
||||
}
|
||||
|
||||
|
||||
fn Matrix3x3 Matrix3x3.translate(Matrix3x3* m, Real[<2>] v)
|
||||
fn Matrix3x3 Matrix3x3.translate(&self, Real[<2>] v)
|
||||
{
|
||||
return m.mul(Matrix3x3 {
|
||||
return self.mul({
|
||||
1, 0, v[0],
|
||||
0, 1, v[1],
|
||||
0, 0, 1,
|
||||
});
|
||||
}
|
||||
|
||||
fn Matrix4x4 Matrix4x4.translate(Matrix4x4* m, Real[<3>] v)
|
||||
fn Matrix4x4 Matrix4x4.translate(&self, Real[<3>] v)
|
||||
{
|
||||
return m.mul(Matrix4x4 {
|
||||
return self.mul({
|
||||
1, 0, 0, v[0],
|
||||
0, 1, 0, v[1],
|
||||
0, 0, 1, v[2],
|
||||
@@ -318,9 +318,9 @@ fn Matrix4x4 Matrix4x4.translate(Matrix4x4* m, Real[<3>] v)
|
||||
}
|
||||
|
||||
// r in radians
|
||||
fn Matrix3x3 Matrix3x3.rotate(Matrix3x3* m, Real r)
|
||||
fn Matrix3x3 Matrix3x3.rotate(&self, Real r)
|
||||
{
|
||||
return m.mul(Matrix3x3 {
|
||||
return self.mul({
|
||||
math::cos(r), -math::sin(r), 0,
|
||||
math::sin(r), math::cos(r), 0,
|
||||
0, 0, 1,
|
||||
@@ -328,9 +328,9 @@ fn Matrix3x3 Matrix3x3.rotate(Matrix3x3* m, Real r)
|
||||
}
|
||||
|
||||
// r in radians
|
||||
fn Matrix4x4 Matrix4x4.rotate_z(Matrix4x4* m, Real r)
|
||||
fn Matrix4x4 Matrix4x4.rotate_z(&self, Real r)
|
||||
{
|
||||
return m.mul(Matrix4x4 {
|
||||
return self.mul({
|
||||
math::cos(r), -math::sin(r), 0, 0,
|
||||
math::sin(r), math::cos(r), 0, 0,
|
||||
0, 0, 1, 0,
|
||||
@@ -339,9 +339,9 @@ fn Matrix4x4 Matrix4x4.rotate_z(Matrix4x4* m, Real r)
|
||||
}
|
||||
|
||||
// r in radians
|
||||
fn Matrix4x4 Matrix4x4.rotate_y(Matrix4x4* m, Real r)
|
||||
fn Matrix4x4 Matrix4x4.rotate_y(&self, Real r)
|
||||
{
|
||||
return m.mul(Matrix4x4 {
|
||||
return self.mul({
|
||||
math::cos(r), 0, -math::sin(r), 0,
|
||||
0, 1, 0, 0,
|
||||
math::sin(r), 0, math::cos(r), 0,
|
||||
@@ -350,9 +350,9 @@ fn Matrix4x4 Matrix4x4.rotate_y(Matrix4x4* m, Real r)
|
||||
}
|
||||
|
||||
// r in radians
|
||||
fn Matrix4x4 Matrix4x4.rotate_x(Matrix4x4* m, Real r)
|
||||
fn Matrix4x4 Matrix4x4.rotate_x(&self, Real r)
|
||||
{
|
||||
return m.mul(Matrix4x4 {
|
||||
return self.mul({
|
||||
1, 0, 0, 0,
|
||||
0, math::cos(r), -math::sin(r), 0,
|
||||
0, math::sin(r), math::cos(r), 0,
|
||||
@@ -361,22 +361,22 @@ fn Matrix4x4 Matrix4x4.rotate_x(Matrix4x4* m, Real r)
|
||||
}
|
||||
|
||||
|
||||
fn Matrix3x3 Matrix3x3.scale(Matrix3x3* m, Real[<2>] v)
|
||||
fn Matrix3x3 Matrix3x3.scale(&self, Real[<2>] v)
|
||||
{
|
||||
return m.mul(Matrix3x3 {
|
||||
return self.mul({
|
||||
v[0], 0, 0,
|
||||
0, v[1], 0,
|
||||
0, 0, 1,
|
||||
});
|
||||
}
|
||||
|
||||
fn Real Matrix2x2.trace(Matrix2x2* m) => m.m00 + m.m11;
|
||||
fn Real Matrix3x3.trace(Matrix3x3* m) => m.m00 + m.m11 + m.m22;
|
||||
fn Real Matrix4x4.trace(Matrix4x4* m) => m.m00 + m.m11 + m.m22 + m.m33;
|
||||
fn Real Matrix2x2.trace(&self) => self.m00 + self.m11;
|
||||
fn Real Matrix3x3.trace(&self) => self.m00 + self.m11 + self.m22;
|
||||
fn Real Matrix4x4.trace(&self) => self.m00 + self.m11 + self.m22 + self.m33;
|
||||
|
||||
fn Matrix4x4 Matrix4x4.scale(Matrix4x4* m, Real[<3>] v)
|
||||
fn Matrix4x4 Matrix4x4.scale(&self, Real[<3>] v)
|
||||
{
|
||||
return m.mul(Matrix4x4 {
|
||||
return self.mul({
|
||||
v[0], 0, 0, 0,
|
||||
0, v[1], 0, 0,
|
||||
0, 0, v[2], 0,
|
||||
@@ -386,29 +386,29 @@ fn Matrix4x4 Matrix4x4.scale(Matrix4x4* m, Real[<3>] v)
|
||||
|
||||
fn Matrix4x4 ortho(Real left, Real right, Real top, Real bottom, Real near, Real far)
|
||||
{
|
||||
Real width = right - left;
|
||||
Real height = top - bottom;
|
||||
Real depth = far - near;
|
||||
return Matrix4x4 {
|
||||
2 / width, 0, 0, 0,
|
||||
0, 2 / height, 0, 0,
|
||||
0, 0, -2 / depth, 0,
|
||||
-(right + left) / width, -(top + bottom) / height, -(far + near) / depth, 1
|
||||
};
|
||||
Real width = right - left;
|
||||
Real height = top - bottom;
|
||||
Real depth = far - near;
|
||||
return {
|
||||
2 / width, 0, 0, 0,
|
||||
0, 2 / height, 0, 0,
|
||||
0, 0, -2 / depth, 0,
|
||||
-(right + left) / width, -(top + bottom) / height, -(far + near) / depth, 1
|
||||
};
|
||||
}
|
||||
|
||||
// fov in radians
|
||||
fn Matrix4x4 perspective(Real fov, Real aspect_ratio, Real near, Real far)
|
||||
{
|
||||
Real top = (math::sin(fov / 2) / math::cos(fov / 2)) * near;
|
||||
Real right = top * aspect_ratio;
|
||||
Real depth = far - near;
|
||||
return Matrix4x4 {
|
||||
1 / right, 0, 0, 0,
|
||||
0, 1 / top, 0, 0,
|
||||
0, 0, -2 / depth, 0,
|
||||
0, 0, -(far + near) / depth, 1,
|
||||
};
|
||||
Real f = (Real)math::tan(math::PI * 0.5 - 0.5 * fov);
|
||||
Real rangeInv = (Real)1.0 / (near - far);
|
||||
|
||||
return {
|
||||
f / aspect_ratio, 0, 0, 0,
|
||||
0, f, 0, 0,
|
||||
0, 0, (near + far) * rangeInv, -1,
|
||||
0, 0, near * far * rangeInv * 2, 0,
|
||||
};
|
||||
}
|
||||
|
||||
const Matrix2x2 IDENTITY2 = { .m = { [0] = 1, [3] = 1 } };
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user