Compare commits

..

2 Commits
v0.6.5 ... test

Author SHA1 Message Date
Christoffer Lerno
2bd66dcf18 Revert malloc reduced init. 2023-11-22 11:16:36 +01:00
Christoffer Lerno
3e31d20603 Test 2023-11-22 11:16:01 +01:00
884 changed files with 35336 additions and 63955 deletions

View File

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

2
.gitattributes vendored
View File

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

14
.github/FUNDING.yml vendored
View File

@@ -1,14 +0,0 @@
# These are supported funding model platforms
github: [c3lang]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: c3lang
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -2,16 +2,13 @@ name: CI
on:
push:
branches: [ master, dev, ci_testing, experiments ]
branches: [ master, dev, ci_testing ]
pull_request:
branches: [ master, dev ]
branches: [ master ]
env:
LLVM_RELEASE_VERSION_WINDOWS: 18
LLVM_RELEASE_VERSION_MAC: 17
LLVM_RELEASE_VERSION_LINUX: 17
LLVM_RELEASE_VERSION_UBUNTU20: 17
LLVM_DEV_VERSION: 20
LLVM_RELEASE_VERSION: 16
jobs:
build-msvc:
@@ -36,50 +33,33 @@ jobs:
- name: Compile and run some examples
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe compile-run -L C:\ --print-linking examples\hello_world_many.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\time.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\fannkuch-redux.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\hello_world_many.c3
..\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-run examples\args.c3 -- foo -bar "baz baz"
..\build\${{ matrix.build_type }}\c3c.exe compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run msvc_stack.c3
- name: Build testproject
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
cd resources/testproject
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32
..\..\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: |
cd resources/testproject
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv build hello_world_win32_lib
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
..\..\..\build\${{ matrix.build_type }}\c3c.exe dynamic-lib add.c3
..\..\..\build\${{ matrix.build_type }}\c3c.exe compile-run test.c3 -l ./add.lib
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log build hello_world_win32_lib
- name: Vendor-fetch
run: |
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
- name: Try raylib5
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_arkanoid.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_snake.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_tetris.c3
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib
- name: run compiler tests
run: |
@@ -91,18 +71,11 @@ jobs:
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
- name: Test python script
run: |
py msvc_build_libraries.py --accept-license
dir msvc_sdk
- name: upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: c3-windows-${{ matrix.build_type }}
path: |
build\${{ matrix.build_type }}\c3c.exe
build\${{ matrix.build_type }}\c3c_rt
path: build\${{ matrix.build_type }}\c3c.exe
build-msys2-mingw:
runs-on: windows-latest
@@ -126,8 +99,8 @@ jobs:
install: git binutils mingw-w64-x86_64-clang mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
- shell: msys2 {0}
run: |
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-19.1.4-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-19.1.4-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_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
@@ -136,27 +109,26 @@ jobs:
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile-run --print-linking examples/hello_world_many.c3
../build/c3c compile-run --print-linking examples/time.c3
../build/c3c compile-run --print-linking examples/fannkuch-redux.c3
../build/c3c compile-run --print-linking examples/contextfree/boolerr.c3
../build/c3c compile-run --print-linking examples/load_world.c3
../build/c3c compile-run --print-linking examples/args.c3 -- foo -bar "baz baz"
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
../build/c3c compile-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 --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
../../build/c3c run --debug-log
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib5
./build/c3c vendor-fetch raylib
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib --cc cc -vvv
../../build/c3c build hello_world_lib --debug-log
- name: run compiler tests
run: |
@@ -198,17 +170,16 @@ jobs:
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
../build/c3c compile --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
../../build/c3c run --debug-log
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib -vvv
../../build/c3c build hello_world_lib --debug-log
- name: run compiler tests
run: |
@@ -222,7 +193,7 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20]
llvm_version: [15, 16, 17, 18]
steps:
- uses: actions/checkout@v4
@@ -233,29 +204,24 @@ 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}}" < 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
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
else
if [[ "${{matrix.llvm_version}}" < "${{env.LLVM_DEV_VERSION}}" ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
sudo apt-get update
sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
sudo apt-get install -y -t llvm-toolchain-focal libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
fi
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
sudo apt-get install -y -t llvm-toolchain-focal libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
fi
- name: CMake
if: matrix.llvm_version < 18 || matrix.llvm_version == env.LLVM_DEV_VERSION
run: |
cmake -B build \
-G Ninja \
@@ -268,20 +234,6 @@ jobs:
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: CMake18
if: matrix.llvm_version >= 18 && matrix.llvm_version != env.LLVM_DEV_VERSION
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
- name: Compile and run some examples
run: |
@@ -293,9 +245,9 @@ jobs:
../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile examples/levenshtein.c3
../build/c3c compile examples/load_world.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3
@@ -304,8 +256,8 @@ jobs:
../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
@@ -313,18 +265,8 @@ jobs:
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run --system-linker=no linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
../../../build/c3c dynamic-lib add.c3
mv add.so libadd.so
cc test.c -L. -ladd -Wl,-rpath=.
./a.out
../../../build/c3c compile-run test.c3 -L . -l add -z -Wl,-rpath=.
- name: Compile run unit tests
run: |
@@ -334,33 +276,12 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
- name: Test WASM
run: |
cd resources/testfragments
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
- name: Install QEMU and Risc-V toolchain
run: |
sudo apt-get install opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf
- name: Compile and run Baremetal Risc-V Example
run: |
cd resources/examples/embedded/riscv-qemu
make C3C_PATH=../../../../build/ run
../../build/c3c run --debug-log
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin
- name: Init a library & a project
run: |
./build/c3c init-lib mylib
ls mylib.c3l
./build/c3c init myproject
ls myproject
../../build/c3c run --debug-log --system-linker=no
- name: run compiler tests
run: |
@@ -368,17 +289,17 @@ jobs:
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
if: matrix.llvm_version == 16
run: |
mkdir c3
cp -r lib c3
cp msvc_build_libraries.py c3
cp build/c3c c3
tar czf c3-linux-${{matrix.build_type}}.tar.gz c3
mkdir linux
cp -r lib linux
cp msvc_build_libraries.py linux
cp build/c3c linux
tar czf c3-linux-${{matrix.build_type}}.tar.gz linux
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
uses: actions/upload-artifact@v4
if: matrix.llvm_version == 16
uses: actions/upload-artifact@v3
with:
name: c3-linux-${{matrix.build_type}}
path: c3-linux-${{matrix.build_type}}.tar.gz
@@ -390,7 +311,8 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20]
llvm_version: [16]
steps:
- uses: actions/checkout@v4
- name: Install common deps
@@ -400,17 +322,16 @@ jobs:
- name: Install Clang ${{matrix.llvm_version}}
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
if [[ "${{matrix.llvm_version}}" < "${{env.LLVM_DEV_VERSION}}" ]]; then
if [[ "${{matrix.llvm_version}}" < 17 ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
fi
sudo apt-get update
sudo apt-get update
sudo apt-get install -y clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
sudo apt-get install -y libpolly-${{matrix.llvm_version}}-dev
- name: CMake Old
if: matrix.llvm_version < 18 || matrix.llvm_version == env.LLVM_DEV_VERSION
- name: CMake
run: |
cmake -B build \
-G Ninja \
@@ -423,26 +344,13 @@ jobs:
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: CMake
if: matrix.llvm_version >= 18 && matrix.llvm_version != env.LLVM_DEV_VERSION
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile examples/gameoflife.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/levenshtein.c3
../build/c3c compile examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/spectralnorm.c3
@@ -452,8 +360,8 @@ jobs:
../build/c3c compile-run examples/nbodies.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/contextfree/dynscope.c3
../build/c3c compile-run examples/contextfree/multi.c3
../build/c3c compile-run examples/contextfree/cleanup.c3
../build/c3c compile-run examples/contextfree/multi.c3
../build/c3c compile-run examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
@@ -464,9 +372,8 @@ jobs:
../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 --linker=builtin linux_stack.c3
../build/c3c compile-run --system-linker=no linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile run unit tests
run: |
@@ -476,12 +383,12 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
../../build/c3c run --debug-log
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin
../../build/c3c run --debug-log --system-linker=no
- name: run compiler tests
run: |
@@ -489,108 +396,20 @@ jobs:
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
if: matrix.llvm_version == 16
run: |
mkdir c3
cp -r lib c3
cp msvc_build_libraries.py c3
cp build/c3c c3
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz c3
mkdir linux
cp -r lib linux
cp msvc_build_libraries.py linux
cp build/c3c linux
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz linux
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
uses: actions/upload-artifact@v4
if: matrix.llvm_version == 16
uses: actions/upload-artifact@v3
with:
name: c3-ubuntu-20-${{matrix.build_type}}
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
build-with-docker:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ubuntu_version: [20.04, 22.04]
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20]
steps:
- uses: actions/checkout@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Make script executable
run: chmod +x ./build-with-docker.sh
- name: Run build
run: |
LLVM_VERSION=${{ matrix.llvm_version }} UBUNTU_VERSION=${{ matrix.ubuntu_version }} CMAKE_BUILD_TYPE=${{ matrix.build_type }} ./build-with-docker.sh
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile examples/base64.c3
../build/c3c compile examples/binarydigits.c3
../build/c3c compile examples/brainfk.c3
../build/c3c compile examples/factorial_macro.c3
../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile examples/load_world.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
- name: Test WASM
run: |
cd resources/testfragments
../../build/c3c compile --reloc=none --target wasm32 -g0 --no-entry -Os wasm4.c3
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin
- name: Init a library & a project
run: |
./build/c3c init-lib mylib
ls mylib.c3l
./build/c3c init myproject
ls myproject
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c test_suite/
build-mac:
runs-on: macos-latest
@@ -599,30 +418,24 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18]
llvm_version: [15, 16]
steps:
- uses: actions/checkout@v4
- name: Download LLVM
run: |
brew install llvm@${{ matrix.llvm_version }} ninja curl
echo "/opt/homebrew/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
echo "/usr/local/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
TMP_PATH=$(xcrun --show-sdk-path)/user/include
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
- name: CMake
if: matrix.llvm_version < 18
run: |
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
- name: CMake18
if: matrix.llvm_version >= 18
run: |
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}}.1 -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib5
./build/c3c vendor-fetch raylib
- name: Compile and run some examples
run: |
@@ -633,14 +446,6 @@ jobs:
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run -O5 examples/load_world.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile and run dynlib-test
run: |
cd resources/examples/dynlib-test
../../../build/c3c dynamic-lib add.c3
../../../build/c3c compile-run test.c3 -l ./add.dylib
- name: Compile run unit tests
run: |
@@ -650,17 +455,17 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv
../../build/c3c run --debug-log
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin
../../build/c3c run --debug-log --system-linker=no
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib -vvv
../../build/c3c build hello_world_lib --debug-log
- name: run compiler tests
run: |
@@ -668,7 +473,7 @@ jobs:
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
run: |
mkdir macos
cp -r lib macos
@@ -677,8 +482,8 @@ jobs:
zip -r c3-macos-${{matrix.build_type}}.zip macos
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
uses: actions/upload-artifact@v4
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
uses: actions/upload-artifact@v3
with:
name: c3-macos-${{matrix.build_type}}
path: c3-macos-${{matrix.build_type}}.zip
@@ -686,7 +491,7 @@ jobs:
release:
runs-on: ubuntu-latest
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu20]
needs: [build-msvc, build-linux, build-mac]
if: github.ref == 'refs/heads/master'
steps:
@@ -713,22 +518,18 @@ jobs:
sha: context.sha
})
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
- run: cp -r lib c3-windows-Release
- run: cp -r lib c3-windows-Debug
- run: cp msvc_build_libraries.py c3-windows-Release
- run: cp msvc_build_libraries.py c3-windows-Debug
- run: zip -r c3-windows.zip c3-windows-Release
- run: zip -r c3-windows-debug.zip c3-windows-Debug
- run: mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux-Release/c3-linux.tar.gz
- run: mv c3-linux-Debug/c3-linux-Debug.tar.gz c3-linux-Debug/c3-linux-debug.tar.gz
- run: mv c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
- run: mv c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
- run: mv c3-macos-Release/c3-macos-Release.zip c3-macos-Release/c3-macos.zip
- run: mv c3-macos-Debug/c3-macos-Debug.zip c3-macos-Debug/c3-macos-debug.zip
- run: cp install_win_reqs.bat c3-windows-Release
- run: cp install_win_reqs.bat c3-windows-Debug
- run: zip -r c3-windows-Release.zip c3-windows-Release
- run: zip -r c3-windows-Debug.zip c3-windows-Debug
- id: create_release
uses: softprops/action-gh-release@v2
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -736,12 +537,84 @@ jobs:
release_name: latest
draft: false
prerelease: true
files: |
c3-windows.zip
c3-windows-debug.zip
c3-linux-Release/c3-linux.tar.gz
c3-linux-Debug/c3-linux-debug.tar.gz
c3-ubuntu-20-Release/c3-ubuntu-20.tar.gz
c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
c3-macos-Release/c3-macos.zip
c3-macos-Debug/c3-macos-debug.zip
- name: upload windows
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-windows-Release.zip
asset_name: c3-windows.zip
asset_content_type: application/zip
- name: upload windows debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-windows-Debug.zip
asset_name: c3-windows-debug.zip
asset_content_type: application/zip
- name: upload linux
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-linux-Release/c3-linux-Release.tar.gz
asset_name: c3-linux.tar.gz
asset_content_type: application/gzip
- name: upload linux debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-linux-Debug/c3-linux-Debug.tar.gz
asset_name: c3-linux-debug.tar.gz
asset_content_type: application/gzip
- name: upload ubuntu 20
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz
asset_name: c3-ubuntu-20.tar.gz
asset_content_type: application/gzip
- name: upload ubuntu 20 debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz
asset_name: c3-ubuntu-20-debug.tar.gz
asset_content_type: application/gzip
- name: upload macos
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-macos-Release/c3-macos-Release.zip
asset_name: c3-macos.zip
asset_content_type: application/zip
- name: upload macos debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-macos-Debug/c3-macos-Debug.zip
asset_name: c3-macos-debug.zip
asset_content_type: application/zip

12
.gitignore vendored
View File

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

View File

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

View File

@@ -10,8 +10,7 @@ Precompiled binaries for the following operating systems are available:
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-mac-with-precompiled-binaries).
- MacOS x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-mac-with-precompiled-binaries).
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
@@ -33,10 +32,10 @@ whole new language.
### Example code
The following code shows [generic modules](https://c3-lang.org/references/docs/generics/) (more examples can be found at https://c3-lang.org/references/docs/examples/).
The following code shows [generic modules](http://www.c3-lang.org/generics/) (more examples can be found at http://www.c3-lang.org/examples/).
```cpp
module stack (<Type>);
```c++
module stack <Type>;
// Above: the parameterized type is applied to the entire module.
struct Stack
@@ -55,7 +54,7 @@ fn void Stack.push(Stack* this, Type element)
if (this.capacity == this.size)
{
this.capacity *= 2;
if (this.capacity < 16) this.capacity = 16;
if (this.capacity < 16) this.capacity = 16;
this.elems = realloc(this.elems, Type.sizeof * this.capacity);
}
this.elems[this.size++] = element;
@@ -123,7 +122,7 @@ fn void main()
- No mandatory header files
- New semantic macro system
- Module based name spacing
- Slices
- Subarrays (slices)
- Compile time reflection
- Enhanced compile time execution
- Generics based on generic modules
@@ -138,9 +137,9 @@ fn void main()
### Current status
The current stable version of the compiler is **version 0.6.4**.
The current stable version of the compiler is **version 0.5**.
The upcoming 0.6.x releases will focus on expanding the standard library.
The upcoming 0.6 release will focus on expanding the standard library.
Follow the issues [here](https://github.com/c3lang/c3c/issues).
If you have suggestions on how to improve the language, either [file an issue](https://github.com/c3lang/c3c/issues)
@@ -206,32 +205,17 @@ More platforms will be supported in the future.
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on Ubuntu with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20-debug.tar.gz))
2. Unpack executable and standard lib.
3. Run `./c3c`.
#### Installing on MacOS with precompiled binaries
#### Installing on Mac with precompiled binaries
1. Make sure you have XCode with command line tools installed.
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-macos-debug.zip))
3. Unzip executable and standard lib.
4. Run `./c3c`.
(*Note that there is a known issue with debug symbol generation on MacOS 13, see [issue #1086](https://github.com/c3lang/c3c/issues/1086))
#### Installing on Arch Linux
Arch includes c3c in the official 'extra' repo. It can be easily installed the usual way:
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
```sh
sudo pacman -S c3c
# or paru -S c3c
# or yay -S c3c
# or aura -A c3c
```
There is also an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
Due to some issues with the LLVM packaged for Arch Linux, the AUR package will download and use LLVM 16 for Ubuntu-23.04 to compile the c3c compiler.
You can use your AUR package manager:
```sh
@@ -267,7 +251,7 @@ A `c3c` executable will be found under `bin/`.
#### Installing on OS X using Homebrew
2. Install CMake: `brew install cmake`
3. Install LLVM 17+: `brew install llvm`
3. Install LLVM 15: `brew install llvm`
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
5. Enter the C3C directory `cd c3c`.
6. Create a build directory `mkdir build`
@@ -303,7 +287,7 @@ called `hello_world` or `hello_world.exe`depending on platform.
#### Compiling on Windows
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++"
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++" (there is also `c3c/resources/install_win_reqs.bat` to automate this)
2. Install CMake
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
@@ -318,41 +302,27 @@ You can try it out by running some sample code: `c3c.exe compile ../resources/ex
*Note that if you run into linking issues when building, make sure that you are using the latest version of VS17.*
#### Compiling on Ubuntu 24.04 LTS
#### Compiling on Ubuntu 20.10
1. Make sure you have a C compiler that handles C11 and a C++ compiler, such as GCC or Clang. Git also needs to be installed.
2. Install LLVM 18 `sudo apt-get install cmake git clang zlib1g zlib1g-dev libllvm18 llvm llvm-dev llvm-runtime liblld-dev liblld-18 libpolly-18-dev`
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build`
6. Change directory to the build directory `cd build`
7. Set up CMake build: `cmake ..`
8. Build: `cmake --build .`
2. Install CMake: `sudo apt install cmake`
3. Install LLVM 15 (or greater: C3C supports LLVM 15-17): `sudo apt-get install clang-15 zlib1g zlib1g-dev libllvm15 llvm-15 llvm-15-dev llvm-15-runtime liblld-15-dev liblld-15`
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
5. Enter the C3C directory `cd c3c`.
6. Create a build directory `mkdir build`
7. Change directory to the build directory `cd build`
8. Set up CMake build: `cmake ..`
9. Build: `cmake --build .`
You should now have a `c3c` executable.
You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
#### Compiling on Void Linux
1. As root, ensure that all project dependencies are installed: `xbps-install git cmake llvm17 llvm17-devel lld17-devel libcurl-devel ncurses-devel zlib-devel libzstd-devel libxml2-devel`
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone instead: `git clone https://github.com/c3lang/c3c.git --depth=1`
3. Enter the directory: `cd c3c`
4. Create a build directory: `mkdir build`
5. Enter the build directory: `cd build`
6. Create the CMake build cache: `cmake ..`
7. Build: `cmake --build .`
Your c3c executable should have compiled properly. You may want to test it: `./c3c compile ../resources/examples/hash.c3`
For a sytem-wide installation, run the following as root: `cmake --install .`
#### Compiling on other Linux / Unix variants
1. Install CMake.
2. Install or compile LLVM and LLD *libraries* (version 17+ or higher)
2. Install or compile LLVM and LLD *libraries* (version 15+ or higher)
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build`
@@ -382,9 +352,3 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
3. Run tests and see that they pass. (Recommended settings: `c3c compile-test -O0 test/unit`.
- in this example `test/unit/` is the relative path to the test directory, so adjust as required)
4. Make a pull request for the new tests.
## Thank yous
A huge **THANK YOU** goes out to all contributors and sponsors.
A special thank you to sponsors [Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad) for going the extra mile.

View File

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

View File

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

552
c3c.1
View File

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

View File

@@ -1,49 +1,16 @@
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION}
ARG LLVM_VERSION=18
ENV LLVM_DEV_VERSION=20
ARG UBUNTU_VERSION
FROM ubuntu:$UBUNTU_VERSION
ARG CMAKE_VERSION=3.20
ARG DEPS
RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ && \
wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-$CMAKE_VERSION-linux-x86_64.sh && \
mkdir -p /opt/cmake && \
sh cmake-${CMAKE_VERSION}-linux-x86_64.sh --prefix=/opt/cmake --skip-license && \
rm cmake-${CMAKE_VERSION}-linux-x86_64.sh && \
ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake
RUN export DEBIAN_FRONTEND=noninteractive && export TERM=xterm && apt-get update && apt-get install -y build-essential cmake zlib1g zlib1g-dev \
$DEPS && \
rm -rf /var/lib/apt/lists/*
RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \
if [ "${LLVM_VERSION}" -lt 18 ]; then \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev libmlir-${LLVM_VERSION} \
libmlir-${LLVM_VERSION}-dev mlir-${LLVM_VERSION}-tools; \
elif [ "${LLVM_VERSION}" -lt "${LLVM_DEV_VERSION}" ]; then \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} clang++-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
else \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
fi && \
rm -rf /var/lib/apt/lists/*
ARG GID=1000
ARG UID=1000
RUN groupadd -g 1337 c3c && \
useradd -m -u 1337 -g c3c c3c
RUN groupadd -o --gid=$GID c3c && useradd --gid=$GID --uid=$GID --create-home --shell /bin/bash c3c
# Add cmake to PATH for user c3c
USER c3c
ENV PATH="/opt/cmake/bin:${PATH}"
WORKDIR /home/c3c

61
flake.lock generated
View File

@@ -1,61 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1730958623,
"narHash": "sha256-JwQZIGSYnRNOgDDoIgqKITrPVil+RMWHsZH1eE1VGN0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "85f7e662eda4fa3a995556527c87b2524b691933",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

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

View File

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

17
install_win_reqs.bat Normal file
View File

@@ -0,0 +1,17 @@
@echo off
set DOWNLOAD_URL=https://aka.ms/vs/17/release
mkdir tmp 2> NUL
if not exist "tmp\vs_buildtools.exe" (
bitsadmin /transfer /download /priority foreground %DOWNLOAD_URL%/vs_buildtools.exe %CD%\tmp\vs_buildtools.exe
)
echo Preparing Build Tools, please wait...
tmp\vs_BuildTools.exe --quiet --wait --layout tmp\ --add Microsoft.VisualStudio.Component.Windows10SDK.19041
echo Installing Build Tools, please wait...
tmp\vs_BuildTools.exe --quiet --wait --noweb --add Microsoft.VisualStudio.Component.Windows10SDK.19041
REM rmdir tmp /s /q

View File

@@ -53,9 +53,9 @@ fn bool char.is_blank(char c) => is_blank_m(c);
fn bool char.is_cntrl(char c) => is_cntrl_m(c);
fn char char.to_lower(char c) => (char)to_lower_m(c);
fn char char.to_upper(char c) => (char)to_upper_m(c);
<*
@require c.is_xdigit()
*>
/**
* @require c.is_xdigit()
**/
fn char char.from_hex(char c) => c.is_digit() ? c - '0' : 10 + (c | 0x20) - 'a';
fn bool uint.in_range(uint c, uint start, uint len) => in_range_m(c, start, len);

View File

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

View File

@@ -9,7 +9,7 @@ macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $succe
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: unreachable("Unrecognized failure ordering");
default: assert(false, "Unrecognized failure ordering");
}
return 0;
}
@@ -23,12 +23,12 @@ macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, succes
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: unreachable("Unrecognized success ordering");
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
fn CInt __atomic_compare_exchange(CInt size, any* ptr, any* expected, any* desired, CInt success, CInt failure) @extern("__atomic_compare_exchange") @export
{
switch (size)
{
@@ -57,7 +57,7 @@ fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired,
nextcase;
$endif
default:
unreachable("Unsuported size (%d) for atomic_compare_exchange", size);
assert(false, "Unsuported size (%d) for atomic_compare_exchange", size);
}
return 0;
}

View File

@@ -1,13 +1,13 @@
module std::bits;
<*
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
*>
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro reverse(i) => $$bitreverse(i);
<*
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
*>
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro bswap(i) @builtin => $$bswap(i);
macro uint[<*>].popcount(self) => $$popcount(self);

View File

@@ -1,503 +0,0 @@
// Copyright (c) 2024 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::anylist;
import std::io,std::math;
def AnyPredicate = fn bool(any value);
def AnyTest = fn bool(any type, any context);
struct AnyList (Printable)
{
usz size;
usz capacity;
Allocator allocator;
any* entries;
}
<*
Use `init` for to use a custom allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = null)
{
return self.init(allocator ?: allocator::heap(), initial_capacity) @inline;
}
<*
@param [&inout] allocator "The allocator to use"
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
{
self.allocator = allocator;
self.size = 0;
if (initial_capacity > 0)
{
initial_capacity = math::next_power_of_2(initial_capacity);
self.entries = allocator::alloc_array(allocator, any, initial_capacity);
}
else
{
self.entries = null;
}
self.capacity = initial_capacity;
return self;
}
<*
Initialize the list using the temp allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16)
{
return self.init(allocator::temp(), initial_capacity) @inline;
}
fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
case 0:
return formatter.print("[]")!;
case 1:
return formatter.printf("[%s]", self.entries[0])!;
default:
usz n = formatter.print("[")!;
foreach (i, element : self.entries[:self.size])
{
if (i != 0) formatter.print(", ")!;
n += formatter.printf("%s", element)!;
}
n += formatter.print("]")!;
return n;
}
}
fn String AnyList.to_new_string(&self, Allocator allocator = null) @dynamic
{
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
}
fn String AnyList.to_string(&self, Allocator allocator) @dynamic
{
return string::format("%s", *self, allocator: allocator);
}
fn String AnyList.to_tstring(&self) => string::tformat("%s", *self);
<*
Push an element on the list by cloning it.
*>
macro void AnyList.push(&self, element)
{
if (!self.allocator) self.allocator = allocator::heap();
self.append_internal(allocator::clone(self.allocator, element));
}
fn void AnyList.append_internal(&self, any element) @local
{
self.ensure_capacity();
self.entries[self.size++] = element;
}
<*
Free a retained element removed using *_retained.
*>
fn void AnyList.free_element(&self, any element) @inline
{
allocator::free(self.allocator, element.ptr);
}
<*
Pop a value who's type is known. If the type is incorrect, this
will still pop the element.
@return! CastResult.TYPE_MISMATCH, IteratorResult.NO_MORE_ELEMENT
*>
macro AnyList.pop(&self, $Type)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return *anycast(self.entries[--self.size], $Type);
}
<*
Pop the last value and allocate the copy using the given allocator.
@return! IteratorResult.NO_MORE_ELEMENT
*>
fn any! AnyList.copy_pop(&self, Allocator allocator = allocator::heap())
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return allocator::clone_any(allocator, self.entries[--self.size]);
}
<*
Pop the last value and allocate the copy using the given allocator.
@return! IteratorResult.NO_MORE_ELEMENT
@deprecated `use copy_pop`
*>
fn any! AnyList.new_pop(&self, Allocator allocator = allocator::heap())
{
return self.copy_pop(allocator);
}
<*
Pop the last value and allocate the copy using the temp allocator
@return! IteratorResult.NO_MORE_ELEMENT
@deprecated `use tcopy_pop`
*>
fn any! AnyList.temp_pop(&self) => self.copy_pop(allocator::temp());
<*
Pop the last value and allocate the copy using the temp allocator
@return! IteratorResult.NO_MORE_ELEMENT
*>
fn any! AnyList.tcopy_pop(&self) => self.copy_pop(allocator::temp());
<*
Pop the last value. It must later be released using list.free_element()
@return! IteratorResult.NO_MORE_ELEMENT
*>
fn any! AnyList.pop_retained(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
fn void AnyList.clear(&self)
{
for (usz i = 0; i < self.size; i++)
{
self.free_element(self.entries[i]);
}
self.size = 0;
}
<*
Same as pop() but pops the first value instead.
*>
macro AnyList.pop_first(&self, $Type)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return *anycast(self.entries[0], $Type);
}
<*
Same as pop_retained() but pops the first value instead.
*>
fn any! AnyList.pop_first_retained(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
<*
Same as new_pop() but pops the first value instead.
@deprecated `use copy_pop_first`
*>
fn any! AnyList.new_pop_first(&self, Allocator allocator = allocator::heap())
{
return self.copy_pop_first(allocator) @inline;
}
<*
Same as new_pop() but pops the first value instead.
*>
fn any! AnyList.copy_pop_first(&self, Allocator allocator = allocator::heap())
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
defer self.remove_at(0);
return allocator::clone_any(allocator, self.entries[0]);
}
<*
Same as temp_pop() but pops the first value instead.
*>
fn any! AnyList.tcopy_pop_first(&self) => self.copy_pop_first(allocator::temp());
<*
Same as temp_pop() but pops the first value instead.
@deprecated `use tcopy_pop_first`
*>
fn any! AnyList.temp_pop_first(&self) => self.new_pop_first(allocator::temp());
<*
@require index < self.size
*>
fn void AnyList.remove_at(&self, usz index)
{
if (!--self.size || index == self.size) return;
self.free_element(self.entries[index]);
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
}
fn void AnyList.add_all(&self, AnyList* other_list)
{
if (!other_list.size) return;
self.reserve(other_list.size);
foreach (value : other_list)
{
self.entries[self.size++] = allocator::clone_any(self.allocator, value);
}
}
<*
Reverse the elements in a list.
*>
fn void AnyList.reverse(&self)
{
if (self.size < 2) return;
usz half = self.size / 2U;
usz end = self.size - 1;
for (usz i = 0; i < half; i++)
{
self.swap(i, end - i);
}
}
fn any[] AnyList.array_view(&self)
{
return self.entries[:self.size];
}
<*
Push an element to the front of the list.
*>
macro void AnyList.push_front(&self, type)
{
self.insert_at(0, type);
}
<*
@require index < self.size
*>
macro void AnyList.insert_at(&self, usz index, type) @local
{
any value = allocator::copy(self.allocator, type);
self.insert_at_internal(self, index, value);
}
<*
@require index < self.size
*>
fn void AnyList.insert_at_internal(&self, usz index, any value) @local
{
self.ensure_capacity();
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.size++;
self.entries[index] = value;
}
<*
@require self.size > 0
*>
fn void AnyList.remove_last(&self)
{
self.free_element(self.entries[--self.size]);
}
<*
@require self.size > 0
*>
fn void AnyList.remove_first(&self)
{
self.remove_at(0);
}
macro AnyList.first(&self, $Type)
{
return *anycast(self.first_any(), $Type);
}
fn any! AnyList.first_any(&self) @inline
{
return self.size ? self.entries[0] : IteratorResult.NO_MORE_ELEMENT?;
}
macro AnyList.last(&self, $Type)
{
return *anycast(self.last_any(), $Type);
}
fn any! AnyList.last_any(&self) @inline
{
return self.size ? self.entries[self.size - 1] : IteratorResult.NO_MORE_ELEMENT?;
}
fn bool AnyList.is_empty(&self) @inline
{
return !self.size;
}
fn usz AnyList.len(&self) @operator(len) @inline
{
return self.size;
}
<*
@require index < self.size "Index out of range"
*>
macro AnyList.get(&self, usz index, $Type)
{
return *anycast(self.entries[index], $Type);
}
<*
@require index < self.size "Index out of range"
*>
fn any AnyList.get_any(&self, usz index) @inline
{
return self.entries[index];
}
fn void AnyList.free(&self)
{
if (!self.allocator) return;
self.clear();
allocator::free(self.allocator, self.entries);
self.capacity = 0;
self.entries = null;
}
fn void AnyList.swap(&self, usz i, usz j)
{
any temp = self.entries[i];
self.entries[i] = self.entries[j];
self.entries[j] = temp;
}
<*
@param filter "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
fn usz AnyList.remove_if(&self, AnyPredicate filter)
{
return self._remove_if(filter, false);
}
<*
@param selection "The function to determine if it should be kept or not"
@return "the number of deleted elements"
*>
fn usz AnyList.retain_if(&self, AnyPredicate selection)
{
return self._remove_if(selection, true);
}
macro usz AnyList._remove_if(&self, AnyPredicate filter, bool $invert) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && filter(&self.entries[i - 1])) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$endif
}
return size - self.size;
}
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
{
return self._remove_using_test(filter, false, context);
}
fn usz AnyList.retain_using_test(&self, AnyTest filter, any context)
{
return self._remove_using_test(filter, true, context);
}
macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$endif
}
return size - self.size;
}
<*
Reserve at least min_capacity
*>
fn void AnyList.reserve(&self, usz min_capacity)
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = allocator::heap();
min_capacity = math::next_power_of_2(min_capacity);
self.entries = allocator::realloc(self.allocator, self.entries, any.sizeof * min_capacity);
self.capacity = min_capacity;
}
macro any AnyList.@item_at(&self, usz index) @operator([])
{
return self.entries[index];
}
<*
@require index <= self.size "Index out of range"
*>
macro void AnyList.set(&self, usz index, value)
{
if (index == self.size)
{
self.push(value);
return;
}
self.free_element(self.entries[index]);
self.entries[index] = allocator::copy(self.allocator, value);
}
fn void AnyList.ensure_capacity(&self, usz added = 1) @inline @private
{
usz new_size = self.size + added;
if (self.capacity >= new_size) return;
assert(new_size < usz.max / 2U);
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
while (new_capacity < new_size) new_capacity *= 2U;
self.reserve(new_capacity);
}

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,13 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enumset"
*>
/**
* @require Enum.kindof == TypeKind.ENUM : "Only enums maybe be used with an enumset"
**/
module std::collections::enumset(<Enum>);
import std::io;
def EnumSetType = $typefrom(private::type_for_enum_elements(Enum.elements)) @private;
def EnumSetType = $typefrom(private::type_for_enum_elements(Enum.elements)) @private ;
const IS_CHAR_ARRAY = Enum.elements > 128;
distinct EnumSet (Printable) = EnumSetType;
@@ -141,14 +140,9 @@ fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
return n;
}
fn String EnumSet.to_new_string(&set, Allocator allocator = allocator::heap()) @dynamic
fn String EnumSet.to_new_string(&set, Allocator* allocator = mem::heap()) @dynamic
{
return string::format("%s", *set, allocator: allocator);
}
fn String EnumSet.to_string(&set, Allocator allocator) @dynamic
{
return string::format("%s", *set, allocator: allocator);
return string::new_format("%s", *set, .allocator = allocator);
}
fn String EnumSet.to_tstring(&set) @dynamic

View File

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

View File

@@ -1,10 +1,8 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::linkedlist(<Type>);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
struct Node @private
{
Node *next;
@@ -14,51 +12,51 @@ struct Node @private
struct LinkedList
{
Allocator allocator;
Allocator *allocator;
usz size;
Node *_first;
Node *_last;
}
<*
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
@return "the initialized list"
*>
fn LinkedList* LinkedList.init(&self, Allocator allocator)
fn void LinkedList.push(&self, Type value)
{
self.link_first(value);
}
fn void LinkedList.push_last(&self, Type value)
{
self.link_last(value);
}
/**
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
* @return "the initialized list"
**/
fn LinkedList* LinkedList.init_new(&self, Allocator* allocator = mem::heap())
{
*self = { .allocator = allocator };
return self;
}
<*
@return "the initialized list"
*>
fn LinkedList* LinkedList.new_init(&self)
fn LinkedList* LinkedList.init_temp(&self)
{
return self.init(allocator::heap()) @inline;
return self.init_new(mem::temp()) @inline;
}
fn LinkedList* LinkedList.temp_init(&self)
{
return self.init(allocator::temp()) @inline;
}
<*
@require self.allocator
*>
/**
* @require self.allocator
**/
macro void LinkedList.free_node(&self, Node* node) @private
{
allocator::free(self.allocator, node);
self.allocator.free(node);
}
macro Node* LinkedList.alloc_node(&self) @private
{
if (!self.allocator) self.allocator = allocator::heap();
return allocator::alloc(self.allocator, Node);
if (!self.allocator) self.allocator = mem::heap();
return self.allocator.new(Node);
}
fn void LinkedList.push_front(&self, Type value)
fn void LinkedList.link_first(&self, Type value) @private
{
Node *first = self._first;
Node *new_node = self.alloc_node();
@@ -75,7 +73,7 @@ fn void LinkedList.push_front(&self, Type value)
self.size++;
}
fn void LinkedList.push(&self, Type value)
fn void LinkedList.link_last(&self, Type value) @private
{
Node *last = self._last;
Node *new_node = self.alloc_node();
@@ -124,9 +122,9 @@ fn void LinkedList.clear(&self)
fn usz LinkedList.len(&self) @inline => self.size;
<*
@require index < self.size
*>
/**
* @require index < self.size
**/
macro Node* LinkedList.node_at_index(&self, usz index)
{
if (index * 2 >= self.size)
@@ -140,48 +138,48 @@ macro Node* LinkedList.node_at_index(&self, usz index)
while (index--) node = node.next;
return node;
}
<*
@require index < self.size
*>
/**
* @require index < self.size
**/
fn Type LinkedList.get(&self, usz index)
{
return self.node_at_index(index).value;
}
<*
@require index < self.size
*>
/**
* @require index < self.size
**/
fn void LinkedList.set(&self, usz index, Type element)
{
self.node_at_index(index).value = element;
}
<*
@require index < self.size
*>
fn void LinkedList.remove_at(&self, usz index)
/**
* @require index < self.size
**/
fn void LinkedList.remove(&self, usz index)
{
self.unlink(self.node_at_index(index));
}
<*
@require index <= self.size
*>
fn void LinkedList.insert_at(&self, usz index, Type element)
/**
* @require index <= self.size
**/
fn void LinkedList.insert(&self, usz index, Type element)
{
switch (index)
{
case 0:
self.push_front(element);
case self.size:
self.push(element);
case self.size:
self.push_last(element);
default:
self.link_before(self.node_at_index(index), element);
}
}
<*
@require succ != null
*>
/**
* @require succ != null
**/
fn void LinkedList.link_before(&self, Node *succ, Type value) @private
{
Node* pred = succ.prev;
@@ -199,9 +197,9 @@ fn void LinkedList.link_before(&self, Node *succ, Type value) @private
self.size++;
}
<*
@require self._first
*>
/**
* @require self._first
**/
fn void LinkedList.unlink_first(&self) @private
{
Node* f = self._first;
@@ -219,58 +217,7 @@ fn void LinkedList.unlink_first(&self) @private
self.size--;
}
fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
usz start = self.size;
Node* node = self._first;
while (node)
{
switch
{
case equals(node.value, t):
Node* next = node.next;
self.unlink(node);
node = next;
default:
node = node.next;
}
}
return start - self.size;
}
fn Type! LinkedList.pop(&self)
{
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
defer self.unlink_last();
return self._last.value;
}
fn bool LinkedList.is_empty(&self)
{
return !self._first;
}
fn Type! LinkedList.pop_front(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
defer self.unlink_first();
return self._first.value;
}
fn void! LinkedList.remove_last(&self) @maydiscard
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
self.unlink_last();
}
fn void! LinkedList.remove_first(&self) @maydiscard
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
self.unlink_first();
}
fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
fn bool LinkedList.remove_value(&self, Type t)
{
for (Node* node = self._first; node != null; node = node.next)
{
@@ -283,7 +230,7 @@ fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
return false;
}
fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
fn bool LinkedList.remove_last_value(&self, Type t)
{
for (Node* node = self._last; node != null; node = node.prev)
{
@@ -295,9 +242,29 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
}
return false;
}
<*
@require self._last
*>
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();
}
/**
* @require self._last
**/
fn void LinkedList.unlink_last(&self) @inline @private
{
Node* l = self._last;
@@ -315,9 +282,9 @@ fn void LinkedList.unlink_last(&self) @inline @private
self.size--;
}
<*
@require x != null
*>
/**
* @require x != null
**/
fn void LinkedList.unlink(&self, Node* x) @private
{
Node* next = x.next;

View File

@@ -1,83 +1,63 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::list(<Type>);
import std::io, std::math, std::collections::list_common;
import std::io;
import std::math;
def ElementPredicate = fn bool(Type *type);
def ElementTest = fn bool(Type *type, any context);
def ElementTest = fn bool(Type *type, any* context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
struct List (Printable)
{
usz size;
usz capacity;
Allocator allocator;
Allocator *allocator;
Type *entries;
}
<*
@param initial_capacity "The initial capacity to reserve"
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
*>
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
/**
* @param initial_capacity "The initial capacity to reserve"
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
**/
fn List* List.init_new(&self, usz initial_capacity = 16, Allocator* allocator = mem::heap())
{
self.allocator = allocator;
self.size = 0;
self.capacity = 0;
self.entries = null;
self.reserve(initial_capacity);
if (initial_capacity > 0)
{
initial_capacity = math::next_power_of_2(initial_capacity);
self.entries = allocator.alloc_aligned(Type.sizeof * initial_capacity, .alignment = Type[1].alignof)!!;
}
else
{
self.entries = null;
}
self.capacity = initial_capacity;
return self;
}
<*
Initialize the list using the temp allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn List* List.temp_init(&self, usz initial_capacity = 16)
/**
* 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 self.new_init(initial_capacity, allocator::temp()) @inline;
return self.init_new(initial_capacity, mem::temp()) @inline;
}
<*
Initialize a new list with an array.
@param [in] values `The values to initialize the list with.`
@require self.size == 0 "The List must be empty"
*>
fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = allocator::heap())
{
self.new_init(values.len, allocator) @inline;
self.add_array(values) @inline;
return self;
}
<*
Initialize a temporary list with an array.
@param [in] values `The values to initialize the list with.`
@require self.size == 0 "The List must be empty"
*>
fn List* List.temp_init_with_array(&self, Type[] values)
{
self.temp_init(values.len) @inline;
self.add_array(values) @inline;
return self;
}
<*
@require self.capacity == 0 "The List must not be allocated"
*>
fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allocator::heap())
/**
* @require self.size == 0 "The List must be empty"
**/
fn void List.init_wrapping_array(&self, Type[] types, Allocator* allocator = mem::heap())
{
self.allocator = allocator;
self.size = types.len;
self.capacity = types.len;
self.entries = types.ptr;
self.set_size(types.len);
}
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
@@ -100,9 +80,9 @@ fn usz! List.to_format(&self, Formatter* formatter) @dynamic
}
}
fn String List.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String List.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
{
return string::format("%s", *self, allocator: allocator);
return string::new_format("%s", *self, .allocator = allocator);
}
fn String List.to_tstring(&self)
@@ -112,82 +92,86 @@ fn String List.to_tstring(&self)
fn void List.push(&self, Type element) @inline
{
self.reserve(1);
self.entries[self.set_size(self.size + 1)] = element;
self.append(element);
}
fn Type! List.pop(&self)
fn void List.append(&self, Type element)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.set_size(self.size - 1);
return self.entries[self.size - 1];
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.set_size(0);
self.size = 0;
}
fn Type! List.pop_first(&self)
/**
* @require self.size > 0
**/
fn Type List.pop_first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
Type value = self.entries[0];
self.remove_at(0);
return value;
}
<*
@require index < self.size `Removed element out of bounds`
*>
/**
* @require index < self.size
**/
fn void List.remove_at(&self, usz index)
{
self.set_size(self.size - 1);
if (!self.size || index == self.size) return;
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
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;
self.reserve(other_list.size);
usz index = self.set_size(self.size + other_list.size);
foreach (&value : other_list)
{
self.entries[index++] = *value;
self.entries[self.size++] = *value;
}
}
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
fn Type[] List.to_new_aligned_array(&self, Allocator allocator = allocator::heap())
fn Type[] List.to_new_array(&self, Allocator* allocator = mem::heap())
{
return list_common::list_to_new_aligned_array(Type, self, allocator);
}
<*
@require !type_is_overaligned() : "This function is not available on overaligned types"
*>
macro Type[] List.to_new_array(&self, Allocator allocator = allocator::heap())
{
return list_common::list_to_new_array(Type, self, allocator);
if (!self.size) return Type[] {};
Type[] result = allocator.new_array(Type, self.size);
result[..] = self.entries[:self.size];
return result;
}
fn Type[] List.to_tarray(&self)
{
$if type_is_overaligned():
return self.to_new_aligned_array(allocator::temp());
$else
return self.to_new_array(allocator::temp());
$endif;
return self.to_new_array(mem::temp());
}
<*
Reverse the elements in a list.
*>
/**
* Reverse the elements in a list.
**/
fn void List.reverse(&self)
{
list_common::list_reverse(self);
if (self.size < 2) return;
usz half = self.size / 2U;
usz end = self.size - 1;
for (usz i = 0; i < half; i++)
{
@swap(self.entries[i], self.entries[end - i]);
}
}
fn Type[] List.array_view(&self)
@@ -195,18 +179,14 @@ fn Type[] List.array_view(&self)
return self.entries[:self.size];
}
<*
Add the values of an array to this list.
@param [in] array
@ensure self.size >= array.len
*>
fn void List.add_array(&self, Type[] array)
{
if (!array.len) return;
self.reserve(array.len);
usz index = self.set_size(self.size + array.len);
self.entries[index : array.len] = array[..];
foreach (&value : array)
{
self.entries[self.size++] = *value;
}
}
fn void List.push_front(&self, Type type) @inline
@@ -214,50 +194,52 @@ fn void List.push_front(&self, Type type) @inline
self.insert_at(0, type);
}
<*
@require index <= self.size `Insert was out of bounds`
*>
/**
* @require index < self.size
**/
fn void List.insert_at(&self, usz index, Type type)
{
self.reserve(1);
self.ensure_capacity();
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.set_size(self.size + 1);
self.size++;
self.entries[index] = type;
}
<*
@require index < self.size
*>
/**
* @require index < self.size
**/
fn void List.set_at(&self, usz index, Type type)
{
self.entries[index] = type;
}
fn void! List.remove_last(&self) @maydiscard
/**
* @require self.size > 0
**/
fn void List.remove_last(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.set_size(self.size - 1);
self.size--;
}
fn void! List.remove_first(&self) @maydiscard
/**
* @require self.size > 0
**/
fn void List.remove_first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.remove_at(0);
}
fn Type! List.first(&self)
fn Type* List.first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[0];
return self.size ? &self.entries[0] : null;
}
fn Type! List.last(&self)
fn Type* List.last(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[self.size - 1];
return self.size ? &self.entries[self.size - 1] : null;
}
fn bool List.is_empty(&self) @inline
@@ -265,19 +247,11 @@ fn bool List.is_empty(&self) @inline
return !self.size;
}
fn usz List.byte_size(&self) @inline
{
return Type.sizeof * self.size;
}
fn usz List.len(&self) @operator(len) @inline
{
return self.size;
}
<*
@require index < self.size `Access out of bounds`
*>
fn Type List.get(&self, usz index) @inline
{
return self.entries[index];
@@ -285,110 +259,125 @@ fn Type List.get(&self, usz index) @inline
fn void List.free(&self)
{
if (!self.allocator || !self.capacity) return;
self.pre_free(); // Remove sanitizer annotation
$if type_is_overaligned():
allocator::free_aligned(self.allocator, self.entries);
$else
allocator::free(self.allocator, self.entries);
$endif;
if (!self.allocator) return;
self.allocator.free_aligned(self.entries);
self.capacity = 0;
self.size = 0;
self.entries = null;
}
<*
@require i < self.size && j < self.size `Access out of bounds`
*>
fn void List.swap(&self, usz i, usz j)
{
@swap(self.entries[i], self.entries[j]);
}
<*
@param filter "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
/**
* @param filter "The function to determine if it should be removed or not"
* @return "the number of deleted elements"
**/
fn usz List.remove_if(&self, ElementPredicate filter)
{
return list_common::list_remove_if(self, filter, false);
return self._remove_if(filter, false);
}
<*
@param selection "The function to determine if it should be kept or not"
@return "the number of deleted elements"
*>
/**
* @param selection "The function to determine if it should be kept or not"
* @return "the number of deleted elements"
**/
fn usz List.retain_if(&self, ElementPredicate selection)
{
return list_common::list_remove_if(self, selection, true);
return self._remove_if(selection, true);
}
fn usz List.remove_using_test(&self, ElementTest filter, any context)
macro usz List._remove_if(&self, ElementPredicate filter, bool $invert) @local
{
usz old_size = self.size;
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
usz size = self.size;
for (usz i = size, 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 list_common::list_remove_using_test(self, filter, false, context);
return size - self.size;
}
fn usz List.retain_using_test(&self, ElementTest filter, any context)
fn usz List.remove_using_test(&self, ElementTest filter, any* context)
{
usz old_size = self.size;
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
}
return list_common::list_remove_using_test(self, filter, true, context);
return self._remove_using_test(filter, false, context);
}
fn void List.ensure_capacity(&self, usz min_capacity) @local
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(&self, usz min_capacity)
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = allocator::heap();
self.pre_free(); // Remove sanitizer annotation
if (!self.allocator) self.allocator = mem::heap();
min_capacity = math::next_power_of_2(min_capacity);
$if type_is_overaligned():
self.entries = allocator::realloc_aligned(self.allocator, self.entries, Type.sizeof * min_capacity, alignment: Type[1].alignof)!!;
$else
self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity);
$endif;
self.entries = self.allocator.realloc_aligned(self.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof) ?? null;
self.capacity = min_capacity;
self.post_alloc(); // Add sanitizer annotation
}
<*
@require index < self.size `Access out of bounds`
*>
macro Type List.@item_at(&self, usz index) @operator([])
{
return self.entries[index];
}
<*
@require index < self.size `Access out of bounds`
*>
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
{
return &self.entries[index];
}
<*
@require index < self.size `Access out of bounds`
*>
fn void List.set(&self, usz index, Type value) @operator([]=)
{
self.entries[index] = value;
}
fn void List.reserve(&self, usz added)
fn void List.ensure_capacity(&self, usz added = 1) @inline @private
{
usz new_size = self.size + added;
if (self.capacity >= new_size) return;
@@ -396,40 +385,7 @@ fn void List.reserve(&self, usz added)
assert(new_size < usz.max / 2U);
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
while (new_capacity < new_size) new_capacity *= 2U;
self.ensure_capacity(new_capacity);
}
fn void List._update_size_change(&self,usz old_size, usz new_size)
{
if (old_size == new_size) return;
sanitizer::annotate_contiguous_container(self.entries,
&self.entries[self.capacity],
&self.entries[old_size],
&self.entries[new_size]);
}
<*
@require new_size == 0 || self.capacity != 0
*>
fn usz List.set_size(&self, usz new_size) @inline @private
{
usz old_size = self.size;
self._update_size_change(old_size, new_size);
self.size = new_size;
return old_size;
}
macro void List.pre_free(&self) @private
{
if (!self.capacity) return;
self._update_size_change(self.size, self.capacity);
}
<*
@require self.capacity
*>
macro void List.post_alloc(&self) @private
{
self._update_size_change(self.capacity, self.size);
self.reserve(new_capacity);
}
// Functions for equatable types
@@ -463,13 +419,13 @@ fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
return true;
}
<*
Check for presence of a value in a list.
@param [&in] self "the list to find elements in"
@param value "The value to search for"
@return "True if the value is found, false otherwise"
*>
/**
* Check for presence of a value in a list.
*
* @param [&in] self "the list to find elements in"
* @param value "The value to search for"
* @return "True if the value is found, false otherwise"
**/
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
@@ -479,55 +435,37 @@ fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
return false;
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
return @ok(self.remove_at(self.rindex_of(value)));
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
/**
* @param [&inout] self "The list to remove elements from"
* @param value "The value to remove"
* @return "the number of deleted elements."
**/
fn usz List.remove(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
return @ok(self.remove_at(self.index_of(value)));
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "the number of deleted elements."
*>
fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
usz old_size = self.size;
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (!equals(self.entries[i - 1], value)) continue;
for (usz j = i; j < size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return list_common::list_remove_item(self, value);
return size - self.size;
}
fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
fn void List.remove_all(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
{
if (!other_list.size) return;
usz old_size = self.size;
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
}
foreach (v : other_list) self.remove_item(v);
foreach (v : other_list) self.remove(v);
}
<*
@param [&in] self
@return "The number non-null values in the list"
*>
/**
* @param [&in] self
* @return "The number non-null values in the list"
**/
fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
{
usz vals = 0;
@@ -537,42 +475,15 @@ fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
{
usz old_size = self.size;
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (self.entries[i - 1]) continue;
for (usz j = i; j < size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return list_common::list_compact(self);
}
// --> Deprecated
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_last_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_last_item(value) @inline;
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_first_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_first_item(value) @inline;
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "the number of deleted elements."
*>
fn usz List.remove_all_matches(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_item(value) @inline;
return size - self.size;
}

View File

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

View File

@@ -10,155 +10,86 @@ const float DEFAULT_LOAD_FACTOR = 0.75;
const VALUE_IS_EQUATABLE = Value.is_eq;
const bool COPY_KEYS = types::implements_copy(Key);
distinct Map = void*;
struct MapImpl
struct HashMap
{
Entry*[] table;
Allocator allocator;
Allocator* allocator;
uint count; // Number of elements
uint threshold; // Resize limit
float load_factor;
}
<*
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map new(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
/**
* @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"
**/
fn HashMap* HashMap.init_new(&map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator* allocator = mem::heap())
{
MapImpl* map = allocator::alloc(allocator, MapImpl);
_init(map, capacity, load_factor, allocator);
return (Map)map;
}
<*
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map temp(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
MapImpl* map = mem::temp_alloc(MapImpl);
_init(map, capacity, load_factor, allocator::temp());
return (Map)map;
}
<*
@param [&inout] allocator "The allocator to use"
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro Map new_init_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
Map map = new(capacity, load_factor, allocator);
$for (var $i = 0; $i < $vacount; $i += 2)
map.set($vaarg[$i], $vaarg[$i+1]);
$endfor
capacity = math::next_power_of_2(capacity);
map.allocator = allocator;
map.load_factor = load_factor;
map.threshold = (uint)(capacity * load_factor);
map.table = allocator.new_zero_array(Entry*, capacity);
return map;
}
<*
@param [in] keys "Array of keys for the Map entries"
@param [in] values "Array of values for the Map entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map new_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
/**
* @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"
**/
fn HashMap* HashMap.init_temp(&map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
assert(keys.len == values.len);
Map map = new(capacity, load_factor, allocator);
for (usz i = 0; i < keys.len; i++)
{
map.set(keys[i], values[i]);
}
return map;
return map.init_new(capacity, load_factor, mem::temp());
}
<*
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro Map temp_new_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
/**
* Has this hash map been initialized yet?
*
* @param [&in] map "The hash map we are testing"
* @return "Returns true if it has been initialized, false otherwise"
**/
fn bool HashMap.is_initialized(&map)
{
Map map = temp(capacity, load_factor);
$for (var $i = 0; $i < $vacount; $i += 2)
map.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return map;
return (bool)map.allocator;
}
<*
@param [in] keys "The keys for the HashMap entries"
@param [in] values "The values for the HashMap entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map temp_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
/**
* @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())
{
assert(keys.len == values.len);
Map map = temp(capacity, load_factor);
for (usz i = 0; i < keys.len; i++)
{
map.set(keys[i], values[i]);
}
return map;
self.init_new(other_map.table.len, other_map.load_factor, allocator);
self.put_all_for_create(other_map);
return self;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn Map new_from_map(Map other_map, Allocator allocator = null)
/**
* @param [&in] other_map "The map to copy from."
**/
fn HashMap* HashMap.init_temp_from_map(&map, HashMap* other_map)
{
MapImpl* other_map_impl = (MapImpl*)other_map;
if (!other_map_impl)
{
if (allocator) return new(allocator: allocator);
return null;
}
MapImpl* map = (MapImpl*)new(other_map_impl.table.len, other_map_impl.load_factor, allocator ?: allocator::heap());
if (!other_map_impl.count) return (Map)map;
foreach (Entry *e : other_map_impl.table)
{
if (!e) continue;
map._put_for_create(e.key, e.value);
}
return (Map)map;
return map.init_new_from_map(other_map, mem::temp()) @inline;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn Map temp_from_map(Map other_map)
fn bool HashMap.is_empty(&map) @inline
{
return new_from_map(other_map, allocator::temp());
return !map.count;
}
fn bool Map.is_empty(map) @inline
fn usz HashMap.len(&map) @inline
{
return !map || !((MapImpl*)map).count;
return map.count;
}
fn usz Map.len(map) @inline
fn Value*! HashMap.get_ref(&map, Key key)
{
return map ? ((MapImpl*)map).count : 0;
}
fn Value*! Map.get_ref(self, Key key)
{
MapImpl *map = (MapImpl*)self;
if (!map || !map.count) return SearchResult.MISSING?;
if (!map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
@@ -167,26 +98,23 @@ fn Value*! Map.get_ref(self, Key key)
return SearchResult.MISSING?;
}
fn Entry*! Map.get_entry(map, Key key)
fn Entry*! HashMap.get_entry(&map, Key key)
{
MapImpl *map_impl = (MapImpl*)map;
if (!map_impl || !map_impl.count) return SearchResult.MISSING?;
if (!map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map_impl.table[index_for(hash, map_impl.table.len)]; e != null; e = e.next)
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return SearchResult.MISSING?;
}
<*
Get the value or update and
@require $assignable(#expr, Value)
*>
macro Value Map.@get_or_set(&self, Key key, Value #expr)
/**
* Get the value or update and
**/
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
{
MapImpl *map = (MapImpl*)*self;
if (!map || !map.count)
if (!map.count)
{
Value val = #expr;
map.set(key, val);
@@ -203,27 +131,23 @@ macro Value Map.@get_or_set(&self, Key key, Value #expr)
return val;
}
fn Value! Map.get(map, Key key) @operator([])
fn Value! HashMap.get(&map, Key key) @operator([])
{
return *map.get_ref(key) @inline;
}
fn bool Map.has_key(map, Key key)
fn bool HashMap.has_key(&map, Key key)
{
return @ok(map.get_ref(key));
}
macro Value Map.set_value_return(&map, Key key, Value value) @operator([]=)
{
map.set(key, value);
return value;
}
fn bool Map.set(&self, Key key, Value value)
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
if (!*self) *self = new();
MapImpl* map = (MapImpl*)*self;
if (!map.allocator)
{
map.init_new();
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
@@ -234,19 +158,18 @@ fn bool Map.set(&self, Key key, Value value)
return true;
}
}
map._add_entry(hash, key, value, index);
map.add_entry(hash, key, value, index);
return false;
}
fn void! Map.remove(map, Key key) @maydiscard
fn void! HashMap.remove(&map, Key key) @maydiscard
{
if (!map || !((MapImpl*)map)._remove_entry_for_key(key)) return SearchResult.MISSING?;
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
}
fn void Map.clear(self)
fn void HashMap.clear(&map)
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return;
if (!map.count) return;
foreach (Entry** &entry_ref : map.table)
{
Entry* entry = *entry_ref;
@@ -256,35 +179,32 @@ fn void Map.clear(self)
{
Entry *to_delete = next;
next = next.next;
map._free_entry(to_delete);
map.free_entry(to_delete);
}
map._free_entry(entry);
map.free_entry(entry);
*entry_ref = null;
}
map.count = 0;
}
fn void Map.free(self)
fn void HashMap.free(&map)
{
if (!self) return;
MapImpl* map = (MapImpl*)self;
self.clear();
map._free_internal(map.table.ptr);
if (!map.allocator) return;
map.clear();
map.free_internal(map.table.ptr);
map.table = {};
allocator::free(map.allocator, map);
}
fn Key[] Map.temp_keys_list(map)
fn Key[] HashMap.key_tlist(&map)
{
return map.new_keys_list(allocator::temp()) @inline;
return map.key_new_list(mem::temp()) @inline;
}
fn Key[] Map.new_keys_list(self, Allocator allocator = allocator::heap())
fn Key[] HashMap.key_new_list(&map, Allocator* allocator = mem::heap())
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return {};
if (!map.count) return {};
Key[] list = allocator::alloc_array(allocator, Key, map.count);
Key[] list = allocator.new_array(Key, map.count);
usz index = 0;
foreach (Entry* entry : map.table)
{
@@ -297,37 +217,37 @@ fn Key[] Map.new_keys_list(self, Allocator allocator = allocator::heap())
return list;
}
macro Map.@each(map; @body(key, value))
macro HashMap.@each(map; @body(key, value))
{
map.@each_entry(; Entry* entry) {
@body(entry.key, entry.value);
};
}
macro Map.@each_entry(self; @body(entry))
macro HashMap.@each_entry(map; @body(entry))
{
MapImpl *map = (MapImpl*)self;
if (!map || !map.count) return;
foreach (Entry* entry : map.table)
if (map.count)
{
while (entry)
foreach (Entry* entry : map.table)
{
@body(entry);
entry = entry.next;
while (entry)
{
@body(entry);
entry = entry.next;
}
}
}
}
fn Value[] Map.temp_values_list(map)
fn Value[] HashMap.value_tlist(&map)
{
return map.new_values_list(allocator::temp()) @inline;
return map.value_new_list(mem::temp()) @inline;
}
fn Value[] Map.new_values_list(self, Allocator allocator = allocator::heap())
fn Value[] HashMap.value_new_list(&map, Allocator* allocator = mem::heap())
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, map.count);
if (!map.count) return {};
Value[] list = allocator.new_array(Value, map.count);
usz index = 0;
foreach (Entry* entry : map.table)
{
@@ -340,10 +260,9 @@ fn Value[] Map.new_values_list(self, Allocator allocator = allocator::heap())
return list;
}
fn bool Map.has_value(self, Value v) @if(VALUE_IS_EQUATABLE)
fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return false;
if (!map.count) return false;
foreach (Entry* entry : map.table)
{
while (entry)
@@ -357,20 +276,21 @@ fn bool Map.has_value(self, Value v) @if(VALUE_IS_EQUATABLE)
// --- private methods
fn void MapImpl._add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
{
Entry* entry = map.allocator.new(Entry);
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
*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);
map.resize(map.table.len * 2);
}
}
fn void MapImpl._resize(&map, uint new_capacity) @private
fn void HashMap.resize(&map, uint new_capacity) @private
{
Entry*[] old_table = map.table;
uint old_capacity = old_table.len;
@@ -379,10 +299,10 @@ fn void MapImpl._resize(&map, uint new_capacity) @private
map.threshold = uint.max;
return;
}
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
map._transfer(new_table);
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);
map.free_internal(old_table.ptr);
map.threshold = (uint)(new_capacity * map.load_factor);
}
@@ -397,7 +317,7 @@ macro uint index_for(uint hash, uint capacity) @private
return hash & (capacity - 1);
}
fn void MapImpl._transfer(&map, Entry*[] new_table) @private
fn void HashMap.transfer(&map, Entry*[] new_table) @private
{
Entry*[] src = map.table;
uint new_capacity = new_table.len;
@@ -416,18 +336,17 @@ fn void MapImpl._transfer(&map, Entry*[] new_table) @private
}
}
fn void _init(MapImpl* impl, uint capacity, float load_factor, Allocator allocator) @private
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
{
capacity = math::next_power_of_2(capacity);
*impl = {
.allocator = allocator,
.load_factor = load_factor,
.threshold = (uint)(capacity * load_factor),
.table = allocator::new_array(allocator, Entry*, capacity)
};
if (!other_map.count) return;
foreach (Entry *e : other_map.table)
{
if (!e) continue;
map.put_for_create(e.key, e.value);
}
}
fn void MapImpl._put_for_create(&map, Key key, Value value) @private
fn void HashMap.put_for_create(&map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
@@ -439,17 +358,16 @@ fn void MapImpl._put_for_create(&map, Key key, Value value) @private
return;
}
}
map._create_entry(hash, key, value, i);
map.create_entry(hash, key, value, i);
}
fn void MapImpl._free_internal(&map, void* ptr) @inline @private
fn void HashMap.free_internal(&map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
map.allocator.free(ptr);
}
fn bool MapImpl._remove_entry_for_key(&map, Key key) @private
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
{
if (!map.count) return false;
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
Entry* prev = map.table[i];
@@ -468,7 +386,7 @@ fn bool MapImpl._remove_entry_for_key(&map, Key key) @private
{
prev.next = next;
}
map._free_entry(e);
map.free_entry(e);
return true;
}
prev = e;
@@ -477,23 +395,24 @@ fn bool MapImpl._remove_entry_for_key(&map, Key key) @private
return false;
}
fn void MapImpl._create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
{
Entry *e = map.table[bucket_index];
Entry* entry = map.allocator.new(Entry);
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
*entry = { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] };
map.table[bucket_index] = entry;
map.count++;
}
fn void MapImpl._free_entry(&self, Entry *entry) @local
fn void HashMap.free_entry(&self, Entry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
self.allocator.free(entry.key);
$endif
self._free_internal(entry);
self.free_internal(entry);
}
struct Entry
@@ -502,4 +421,4 @@ struct Entry
Key key;
Value value;
Entry* next;
}
}

View File

@@ -2,7 +2,9 @@
// 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::object;
import std::collections::map, std::collections::list, std::io;
import std::collections::map;
import std::collections::list;
import std::io;
const Object TRUE_OBJECT = { .b = true, .type = bool.typeid };
const Object FALSE_OBJECT = { .b = false, .type = bool.typeid };
@@ -11,7 +13,7 @@ const Object NULL_OBJECT = { .type = void*.typeid };
struct Object (Printable)
{
typeid type;
Allocator allocator;
Allocator* allocator;
union
{
uint128 i;
@@ -48,9 +50,9 @@ fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
return n;
case ObjectInternalMap:
usz n = formatter.printf("{")!;
@stack_mem(1024; Allocator mem)
@pool()
{
foreach (i, key : self.map.copy_keys(mem))
foreach (i, key : self.map.key_tlist())
{
if (i > 0) n += formatter.printf(",")!;
n += formatter.printf(`"%s":`, key)!;
@@ -63,11 +65,11 @@ fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
switch (self.type.kindof)
{
case SIGNED_INT:
return formatter.printf("%d", (int128)self.i)!;
return formatter.printf("%d", self.i)!;
case UNSIGNED_INT:
return formatter.printf("%d", (uint128)self.i)!;
case FLOAT:
return formatter.printf("%g", self.f)!;
return formatter.printf("%d", self.f)!;
case ENUM:
return formatter.printf("%d", self.i)!;
default:
@@ -76,9 +78,11 @@ fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
}
}
fn Object* new_obj(Allocator allocator)
fn Object* new_obj(Allocator* allocator)
{
return allocator::new(allocator, Object, { .allocator = allocator, .type = void.typeid });
Object* o = allocator.new(Object);
*o = { .allocator = allocator, .type = void.typeid };
return o;
}
fn Object* new_null()
@@ -86,24 +90,32 @@ fn Object* new_null()
return &NULL_OBJECT;
}
fn Object* new_int(int128 i, Allocator allocator)
fn Object* new_int(int128 i, Allocator* allocator)
{
return allocator::new(allocator, Object, { .i = i, .allocator = allocator, .type = int128.typeid });
Object* o = allocator.new(Object);
*o = { .i = i, .allocator = allocator, .type = int128.typeid };
return o;
}
macro Object* new_enum(e, Allocator allocator)
macro Object* new_enum(e, Allocator* allocator)
{
return allocator::new(allocator, Object, { .i = (int128)e, .allocator = allocator, .type = @typeid(e) });
Object* o = allocator.new(Object);
*o = { .i = (int128)e, .allocator = allocator, .type = @typeid(e) };
return o;
}
fn Object* new_float(double f, Allocator allocator)
fn Object* new_float(double f, Allocator* allocator)
{
return allocator::new(allocator, Object, { .f = f, .allocator = allocator, .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 allocator)
fn Object* new_string(String s, Allocator* allocator)
{
return allocator::new(allocator, Object, { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid });
Object* o = allocator.new(Object);
*o = { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid };
return o;
}
@@ -119,7 +131,7 @@ fn void Object.free(&self)
case void:
break;
case String:
allocator::free(self.allocator, self.s);
self.allocator.free(self.s);
case ObjectInternalList:
foreach (ol : self.array)
{
@@ -128,13 +140,13 @@ fn void Object.free(&self)
self.array.free();
case ObjectInternalMap:
self.map.@each_entry(; ObjectInternalMapEntry* entry) {
self.allocator.free(entry.key);
entry.value.free();
};
self.map.free();
default:
break;
}
if (self.allocator) allocator::free(self.allocator, self);
if (self.allocator) self.allocator.free(self);
}
fn bool Object.is_null(&self) @inline => self == &NULL_OBJECT;
@@ -148,47 +160,49 @@ fn bool Object.is_int(&self) @inline => self.type == int128.typeid;
fn bool Object.is_keyable(&self) => self.is_empty() || self.is_map();
fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array();
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn void Object.init_map_if_needed(&self) @private
{
if (self.is_empty())
{
self.type = ObjectInternalMap.typeid;
self.map.new_init(allocator: self.allocator);
self.map.init_new(.allocator = self.allocator);
}
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn void Object.init_array_if_needed(&self) @private
{
if (self.is_empty())
{
self.type = ObjectInternalList.typeid;
self.array.new_init(allocator: self.allocator);
self.array.init_new(.allocator = self.allocator);
}
}
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn void Object.set_object(&self, String key, Object* new_object) @private
{
self.init_map_if_needed();
ObjectInternalMapEntry*! entry = self.map.get_entry(key);
defer
{
(void)self.allocator.free(entry.key);
(void)entry.value.free();
}
self.map.set(key, new_object);
self.map.set(key.copy(self.map.allocator), new_object);
}
macro Object* Object.object_from_value(&self, value) @private
{
var $Type = $typeof(value);
$switch
$case types::is_int($Type):
return new_int(value, self.allocator);
@@ -218,9 +232,9 @@ macro Object* Object.set(&self, String key, value)
return val;
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
macro Object* Object.set_at(&self, usz index, String key, value)
{
Object* val = self.object_from_value(value);
@@ -228,72 +242,72 @@ macro Object* Object.set_at(&self, usz index, String key, value)
return val;
}
<*
@require self.is_indexable()
@ensure return != null
*>
macro Object* Object.push(&self, value)
/**
* @require self.is_indexable()
* @ensure return != null
**/
macro Object* Object.append(&self, value)
{
Object* val = self.object_from_value(value);
self.push_object(val);
self.append_object(val);
return val;
}
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn Object*! Object.get(&self, String key) => self.is_empty() ? SearchResult.MISSING? : self.map.get(key);
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn Object* Object.get_at(&self, usz index)
{
return self.array.get(index);
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn usz Object.get_len(&self)
{
return self.array.len();
}
<*
@require self.is_indexable()
*>
fn void Object.push_object(&self, Object* to_append)
/**
* @require self.is_indexable()
**/
fn void Object.append_object(&self, Object* to_append)
{
self.init_array_if_needed();
self.array.push(to_append);
self.array.append(to_append);
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn void Object.set_object_at(&self, usz index, Object* to_set)
{
self.init_array_if_needed();
while (self.array.len() < index)
{
self.array.push(&NULL_OBJECT);
self.array.append(&NULL_OBJECT);
}
if (self.array.len() == index)
{
self.array.push(to_set);
self.array.append(to_set);
return;
}
self.array.get(index).free();
self.array.set_at(index, to_set);
}
<*
@require $Type.kindof.is_int() "Expected an integer type."
*>
/**
* @require $Type.kindof.is_int() "Expected an integer type."
**/
macro get_integer_value(Object* value, $Type)
{
if (value.is_float())
@@ -313,19 +327,19 @@ macro get_integer_value(Object* value, $Type)
}
<*
@require self.is_indexable()
@require $Type.kindof.is_int() : "Expected an integer type"
*>
/**
* @require self.is_indexable()
* @require $Type.kindof.is_int() : "Expected an integer type"
**/
macro Object.get_integer_at(&self, $Type, usz index) @private
{
return get_integer_value(self.get_at(index), $Type);
}
<*
@require self.is_keyable()
@require $Type.kindof.is_int() : "Expected an integer type"
*>
/**
* @require self.is_keyable()
* @require $Type.kindof.is_int() : "Expected an integer type"
**/
macro Object.get_integer(&self, $Type, String key) @private
{
return get_integer_value(self.get(key), $Type);
@@ -355,9 +369,9 @@ fn uint! Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index
fn ulong! Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index);
fn uint128! Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn String! Object.get_string(&self, String key)
{
Object* value = self.get(key)!;
@@ -365,9 +379,9 @@ fn String! Object.get_string(&self, String key)
return value.s;
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn String! Object.get_string_at(&self, usz index)
{
Object* value = self.get_at(index);
@@ -375,9 +389,9 @@ fn String! Object.get_string_at(&self, usz index)
return value.s;
}
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
macro String! Object.get_enum(&self, $EnumType, String key)
{
Object value = self.get(key)!;
@@ -385,9 +399,9 @@ macro String! Object.get_enum(&self, $EnumType, String key)
return ($EnumType)value.i;
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
macro String! Object.get_enum_at(&self, $EnumType, usz index)
{
Object value = self.get_at(index);
@@ -395,9 +409,9 @@ macro String! Object.get_enum_at(&self, $EnumType, usz index)
return ($EnumType)value.i;
}
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn bool! Object.get_bool(&self, String key)
{
Object* value = self.get(key)!;
@@ -406,9 +420,9 @@ fn bool! Object.get_bool(&self, String key)
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn bool! Object.get_bool_at(&self, usz index)
{
Object* value = self.get_at(index);
@@ -416,9 +430,9 @@ fn bool! Object.get_bool_at(&self, usz index)
return value.b;
}
<*
@require self.is_keyable()
*>
/**
* @require self.is_keyable()
**/
fn double! Object.get_float(&self, String key)
{
Object* value = self.get(key)!;
@@ -435,9 +449,9 @@ fn double! Object.get_float(&self, String key)
}
}
<*
@require self.is_indexable()
*>
/**
* @require self.is_indexable()
**/
fn double! Object.get_float_at(&self, usz index)
{
Object* value = self.get_at(index);

View File

@@ -27,7 +27,7 @@ distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);
module std::collections::priorityqueue::private(<Type, MAX>);
import std::collections::list, std::io;
import std::collections::list;
def Heap = List(<Type>);
@@ -36,17 +36,16 @@ struct PrivatePriorityQueue (Printable)
Heap heap;
}
fn void PrivatePriorityQueue.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @inline
fn void PrivatePriorityQueue.init_new(&self, usz initial_capacity = 16, Allocator* allocator = mem::heap()) @inline
{
self.heap.new_init(initial_capacity, allocator);
self.heap.init_new(initial_capacity, allocator);
}
fn void PrivatePriorityQueue.temp_init(&self, usz initial_capacity = 16) @inline
fn void PrivatePriorityQueue.init_temp(&self, usz initial_capacity = 16) @inline
{
self.heap.new_init(initial_capacity, allocator::temp()) @inline;
self.heap.init_new(initial_capacity, mem::temp()) @inline;
}
fn void PrivatePriorityQueue.push(&self, Type element)
{
self.heap.push(element);
@@ -67,49 +66,37 @@ fn void PrivatePriorityQueue.push(&self, Type element)
}
}
fn void PrivatePriorityQueue.remove_at(&self, usz index)
{
if (index == 0)
{
self.pop()!!;
return;
}
self.heap.remove_at(index);
}
<*
@require self != null
*>
/**
* @require self != null
*/
fn Type! PrivatePriorityQueue.pop(&self)
{
usz i = 0;
usz len = self.heap.len();
if (!len) return IteratorResult.NO_MORE_ELEMENT?;
usz new_count = len - 1;
self.heap.swap(0, new_count);
while OUTER: ((2 * i + 1) < new_count)
usz newCount = len - 1;
self.heap.swap(0, newCount);
while ((2 * i + 1) < newCount)
{
usz j = 2 * i + 1;
Type left = self.heap[j];
Type item = self.heap[i];
switch
Type itemj = self.heap[j];
if ((j + 1) < newCount)
{
case j + 1 < new_count:
Type right = self.heap[j + 1];
$if MAX:
if (!greater(right, left)) nextcase;
if (!greater(right, item)) break OUTER;
$else
if (!greater(left, right)) nextcase;
if (!greater(item, right)) break OUTER;
$endif
j++;
default:
$if MAX:
if (!greater(left, item)) break OUTER;
$else
if (!greater(item, left)) break OUTER;
$endif
Type nextj = self.heap[j + 1];
$if MAX:
bool ok = greater(nextj, itemj);
$else
bool ok = less(nextj, itemj);
$endif
if (ok) j++;
}
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;
}
@@ -117,7 +104,7 @@ fn Type! PrivatePriorityQueue.pop(&self)
return self.heap.pop();
}
fn Type! PrivatePriorityQueue.first(&self)
fn Type! PrivatePriorityQueue.peek(&self)
{
if (!self.len()) return IteratorResult.NO_MORE_ELEMENT?;
return self.heap.get(0);
@@ -138,10 +125,10 @@ fn bool PrivatePriorityQueue.is_empty(&self)
return self.heap.is_empty();
}
<*
@require index < self.len()
*>
fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
/**
* @require index < self.len()
*/
fn Type PrivatePriorityQueue.peek_at(&self, usz index) @operator([])
{
return self.heap[index];
}
@@ -151,7 +138,7 @@ fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
return self.heap.to_format(formatter);
}
fn String PrivatePriorityQueue.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String PrivatePriorityQueue.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
{
return self.heap.to_new_string(allocator);
}

View File

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

View File

@@ -1,6 +1,3 @@
<*
@require values::@is_int(SIZE) &&& SIZE > 0 "The size must be positive integer"
*>
module std::collections::ringbuffer(<Type, SIZE>);
struct RingBuffer
@@ -15,7 +12,7 @@ fn void RingBuffer.init(&self) @inline
*self = {};
}
fn void RingBuffer.push(&self, Type c)
fn void RingBuffer.putc(&self, Type c)
{
if (self.written < SIZE)
{
@@ -29,7 +26,7 @@ fn void RingBuffer.push(&self, Type c)
}
}
fn Type RingBuffer.get(&self, usz index) @operator([])
fn Type RingBuffer.getc(&self, usz index)
{
index %= SIZE;
usz avail = SIZE - self.head;
@@ -40,7 +37,7 @@ fn Type RingBuffer.get(&self, usz index) @operator([])
return self.buf[index - avail];
}
fn Type! RingBuffer.pop(&self)
fn Type! RingBuffer.popc(&self)
{
switch
{
@@ -55,7 +52,7 @@ fn Type! RingBuffer.pop(&self)
}
}
fn usz RingBuffer.read(&self, usz index, Type[] buffer)
fn usz RingBuffer.get(&self, usz index, Type[] buffer)
{
index %= SIZE;
if (self.written < SIZE)
@@ -90,7 +87,7 @@ fn usz RingBuffer.read(&self, usz index, Type[] buffer)
return n1 + n2;
}
fn void RingBuffer.write(&self, Type[] buffer)
fn void RingBuffer.push(&self, Type[] buffer)
{
usz i;
while (self.written < SIZE && i < buffer.len)

View File

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

View File

@@ -2,7 +2,6 @@
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator;
import std::math;
struct ArenaAllocator (Allocator)
{
@@ -10,14 +9,13 @@ struct ArenaAllocator (Allocator)
usz used;
}
<*
Initialize a memory arena for use using the provided bytes.
*>
fn ArenaAllocator* ArenaAllocator.init(&self, char[] data)
/**
* Initialize a memory arena for use using the provided bytes.
**/
fn void ArenaAllocator.init(&self, char[] data)
{
self.data = data;
self.used = 0;
return self;
}
fn void ArenaAllocator.clear(&self)
@@ -31,11 +29,9 @@ struct ArenaAllocatorHeader @local
char[*] data;
}
<*
@require ptr != null
*>
fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
{
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.
@@ -44,40 +40,53 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
self.used -= header.size + ArenaAllocatorHeader.sizeof;
}
}
fn usz ArenaAllocator.mark(&self) @dynamic => self.used;
fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark;
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require size > 0
*>
fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
/**
* @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
**/
fn void*! ArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{
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 = self.data.ptr;
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof;
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz end = (usz)(mem - self.data.ptr) + size;
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 - self.data.ptr) + size - offset;
if (end > total_len) return AllocationFailure.OUT_OF_MEMORY?;
self.used = end;
void* mem = aligned_pointer_to_offset - offset;
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
header.size = size;
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require old_pointer != null
@require size > 0
*>
fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
/**
* @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
**/
fn void*! ArenaAllocator.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);
}
alignment = alignment_for_allocation(alignment);
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
usz total_len = self.data.len;
@@ -85,7 +94,7 @@ fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
usz old_size = header.size;
// Do last allocation and alignment match?
if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer + offset, alignment))
{
if (old_size >= size)
{
@@ -101,7 +110,7 @@ fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
return old_pointer;
}
// Otherwise just allocate new memory.
void* mem = self.acquire(size, NO_ZERO, alignment)!;
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;
}

View File

@@ -1,22 +1,21 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// 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 std::math;
struct DynamicArenaAllocator (Allocator)
{
Allocator backing_allocator;
Allocator* backing_allocator;
DynamicArenaPage* page;
DynamicArenaPage* unused_page;
usz page_size;
}
<*
@param [&inout] allocator
@require page_size >= 128
*>
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator allocator)
/**
* @param [&inout] allocator
* @require page_size >= 128
**/
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator* allocator)
{
self.page = null;
self.unused_page = null;
@@ -30,16 +29,14 @@ fn void DynamicArenaAllocator.free(&self)
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
allocator::free(self.backing_allocator, page.memory);
allocator::free(self.backing_allocator, page);
self.backing_allocator.free(page);
page = next_page;
}
page = self.unused_page;
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
allocator::free(self.backing_allocator, page.memory);
allocator::free(self.backing_allocator, page);
self.backing_allocator.free(page);
page = next_page;
}
self.page = null;
@@ -60,12 +57,12 @@ struct DynamicArenaChunk @local
usz size;
}
<*
@require ptr
@require self.page `tried to free pointer on invalid allocator`
*>
/**
* @require self.page `tried to free pointer on invalid allocator`
*/
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
{
if (!ptr) return;
DynamicArenaPage* current_page = self.page;
if (ptr == current_page.current_stack_ptr)
{
@@ -74,13 +71,20 @@ fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
current_page.current_stack_ptr = null;
}
<*
@require size > 0 `Resize doesn't support zeroing`
@require old_pointer != null `Resize doesn't handle null pointers`
@require self.page `tried to realloc pointer on invalid allocator`
*>
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
/**
* @require self.page `tried to realloc pointer on invalid allocator`
*/
fn void*! DynamicArenaAllocator.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);
}
DynamicArenaPage* current_page = self.page;
alignment = alignment_for_allocation(alignment);
usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
@@ -104,7 +108,7 @@ fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a
current_page.used += add_size;
return old_pointer;
}
void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
void* new_mem = self.acquire(size, false, alignment, offset)!;
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
return new_mem;
}
@@ -126,25 +130,25 @@ fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
self.page = page;
}
<*
@require math::is_power_of_2(alignment)
@require size > 0
*>
fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @local
/**
* @require math::is_power_of_2(alignment)
* @require size > 0
*/
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(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + alignment, alignment));
assert(page_size > size + DynamicArenaChunk.sizeof);
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + offset, alignment) - offset);
// Grab the page without alignment (we do it ourselves)
void* mem = allocator::malloc_try(self.backing_allocator, page_size)!;
DynamicArenaPage*! page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
void* mem = self.backing_allocator.alloc_checked(page_size)!;
DynamicArenaPage*! page = self.backing_allocator.new(DynamicArenaPage);
if (catch err = page)
{
allocator::free(self.backing_allocator, mem);
self.backing_allocator.free(mem);
return err?;
}
page.memory = mem;
void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment);
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;
@@ -156,12 +160,12 @@ fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @loca
return mem_start;
}
<*
@require size > 0 `acquire expects size > 0`
@require !alignment || math::is_power_of_2(alignment)
*>
fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
/**
* @require !alignment || math::is_power_of_2(alignment)
*/
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 = self.page;
void* ptr = {|
@@ -171,14 +175,14 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
self.unused_page = page.prev_arena;
page.prev_arena = null;
}
if (!page) return self._alloc_new(size, alignment);
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
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))
{
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
new_used = start + size - page.memory;
if (page.total >= new_used)
{
@@ -188,7 +192,7 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
break ALLOCATE_NEW;
}
}
return self._alloc_new(size, alignment);
return self._alloc_new(size, alignment, offset);
}
page.used = new_used;
assert(start + size == page.memory + page.used);
@@ -197,6 +201,6 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
chunk.size = size;
return mem;
|}!;
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
if (clear) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
return ptr;
}

View File

@@ -1,9 +1,9 @@
// Copyright (c) 2021-2024 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;
import std::math;
struct SimpleHeapAllocator (Allocator)
{
@@ -11,29 +11,39 @@ struct SimpleHeapAllocator (Allocator)
Header* free_list;
}
<*
@require allocator "An underlying memory provider must be given"
@require !self.free_list "The allocator may not be already initialized"
*>
/**
* @require allocator "An underlying memory provider must be given"
* @require !self.free_list "The allocator may not be already initialized"
**/
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
{
self.alloc_fn = allocator;
self.free_list = null;
}
fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*! SimpleHeapAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{
if (init_type == ZERO)
if (!size) return null;
if (clear)
{
return alignment > 0 ? @aligned_alloc(self._calloc, size, alignment) : self._calloc(size);
return alignment > 0 ? @aligned_calloc(self._calloc, size, alignment, offset) : self._calloc(size);
}
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(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) @dynamic
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)
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment, offset)
: self._realloc(old_pointer, size);
}
@@ -49,9 +59,9 @@ fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dyn
}
}
<*
@require old_pointer && bytes > 0
*>
/**
* @require old_pointer && bytes > 0
**/
fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
{
// Find the block header.
@@ -98,7 +108,7 @@ fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
return current + 1;
case current.size > aligned_bytes:
Header* unallocated = (Header*)((char*)current + aligned_bytes + Header.sizeof);
unallocated.size = current.size - aligned_bytes - Header.sizeof;
unallocated.size = current.size - aligned_bytes;
unallocated.next = current.next;
if (current == self.free_list)
{

View File

@@ -1,147 +1,49 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// 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 @if(env::LIBC);
import std::io;
module std::core::mem::allocator;
import libc;
const LibcAllocator LIBC_ALLOCATOR = {};
distinct LibcAllocator (Allocator, Printable) = uptr;
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
fn usz! LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
module std::core::mem::allocator @if(env::POSIX);
import std::os;
import libc;
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
distinct LibcAllocator (Allocator) = uptr;
fn void*! LibcAllocator.acquire(&self, usz bytes, bool clear, usz alignment, usz offset) @dynamic
{
if (init_type == ZERO)
assert(alignment != 0 || offset == 0);
if (clear)
{
void* data @noinit;
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
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 @noinit;
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
}
else
{
if (!(data = libc::malloc(bytes))) return AllocationFailure.OUT_OF_MEMORY?;
}
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
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) @dynamic
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment, usz offset) @dynamic
{
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
void* new_ptr;
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return AllocationFailure.OUT_OF_MEMORY?;
$switch
$case env::DARWIN:
usz old_usable_size = darwin::malloc_size(old_ptr);
$case env::LINUX:
usz old_usable_size = linux::malloc_usable_size(old_ptr);
$default:
usz old_usable_size = new_bytes;
$endswitch
usz copy_size = new_bytes < old_usable_size ? new_bytes : old_usable_size;
mem::copy(new_ptr, old_ptr, copy_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
libc::free(old_ptr);
return new_ptr;
}
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
libc::free(old_ptr);
}
module std::core::mem::allocator @if(env::WIN32);
import std::os::win32;
import libc;
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
if (init_type == ZERO)
assert(alignment != 0 || offset == 0);
if (!new_bytes)
{
if (alignment > 0)
{
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
}
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
self.release(old_ptr, alignment > 0);
return null;
}
if (!old_ptr)
{
return self.acquire(new_bytes, true, alignment, offset);
}
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
}
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (alignment)
{
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: 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)
{
win32::_aligned_free(old_ptr);
return;
}
libc::free(old_ptr);
}
module std::core::mem::allocator @if(!env::WIN32 && !env::POSIX && env::LIBC);
import libc;
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
if (init_type == ZERO)
{
void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1);
return data ?: AllocationFailure.OUT_OF_MEMORY?;
}
else
{
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
}
}
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (alignment)
{
void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, 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?;

View File

@@ -2,7 +2,7 @@ module std::core::mem::allocator;
struct OnStackAllocator (Allocator)
{
Allocator backing_allocator;
Allocator* backing_allocator;
char[] data;
usz used;
OnStackAllocatorExtraChunk* chunk;
@@ -16,12 +16,11 @@ struct OnStackAllocatorExtraChunk @local
void* data;
}
<*
Initialize a memory arena for use using the provided bytes.
@param [&inout] allocator
*>
fn void OnStackAllocator.init(&self, char[] data, Allocator allocator)
/**
* @param [&inout] allocator
* Initialize a memory arena for use using the provided bytes.
**/
fn void OnStackAllocator.init(&self, char[] data, Allocator* allocator)
{
self.data = data;
self.backing_allocator = allocator;
@@ -35,15 +34,15 @@ fn void OnStackAllocator.free(&self)
{
if (chunk.is_aligned)
{
allocator::free_aligned(self.backing_allocator, chunk.data);
self.backing_allocator.free_aligned(chunk.data);
}
else
{
allocator::free(self.backing_allocator, chunk.data);
self.backing_allocator.free(chunk.data);
}
void* old = chunk;
chunk = chunk.prev;
allocator::free(self.backing_allocator, old);
self.backing_allocator.free(old);
}
self.chunk = null;
self.used = 0;
@@ -55,11 +54,9 @@ struct OnStackAllocatorHeader
char[*] data;
}
<*
@require old_pointer
*>
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{
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);
@@ -79,7 +76,7 @@ fn void on_stack_allocator_remove_chunk(OnStackAllocator* a, void* ptr) @local
if (chunk.data == ptr)
{
*addr = chunk.prev;
allocator::free(a.backing_allocator, chunk);
a.backing_allocator.free(chunk);
return;
}
addr = &chunk.prev;
@@ -99,51 +96,58 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a
return null;
}
<*
@require size > 0
@require old_pointer != null
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
*>
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
/**
* @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, OnStackAllocatorExtraChunk.alignof) == offset
**/
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
{
if (!allocation_in_stack_mem(self, old_pointer))
{
OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(self, old_pointer);
assert(chunk, "Tried to realloc pointer not belonging to the allocator");
return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment)!;
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 = self.acquire(size, NO_ZERO, alignment)!;
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;
}
<*
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require size > 0
*>
fn void*! OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
/**
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
* @require offset <= size && offset >= 0
* @require offset == 0 || alignment > 0
* @require mem::aligned_offset(offset, OnStackAllocatorHeader.alignof) == offset
**/
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 = self.data.len;
void* start_mem = self.data.ptr;
void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof ;
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz end = (usz)(mem - self.data.ptr) + size;
Allocator backing_allocator = self.backing_allocator;
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 - self.data.ptr) + size - offset;
Allocator* backing_allocator = self.backing_allocator;
if (end > total_len)
{
OnStackAllocatorExtraChunk* chunk = allocator::alloc_try(backing_allocator, OnStackAllocatorExtraChunk)!;
defer catch allocator::free(backing_allocator, chunk);
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, init_type, aligned ? alignment : 0)!;
return chunk.data = backing_allocator.acquire(size, clear, aligned ? alignment : 0, offset)!;
}
self.used = end;
void *mem = aligned_pointer_to_offset - offset;
OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader.sizeof;
header.size = size;
return mem;

View File

@@ -1,5 +1,5 @@
module std::core::mem::allocator;
import std::io, std::math;
import std::io;
struct TempAllocatorChunk @local
{
@@ -9,7 +9,7 @@ struct TempAllocatorChunk @local
struct TempAllocator (Allocator)
{
Allocator backing_allocator;
Allocator* backing_allocator;
TempAllocatorPage* last_page;
usz used;
usz capacity;
@@ -32,12 +32,12 @@ struct TempAllocatorPage
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_allocator(usz size, Allocator allocator)
/**
* @require size >= 16
**/
fn TempAllocator*! new_temp(usz size, Allocator* allocator)
{
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
TempAllocator* temp = allocator.alloc_checked(TempAllocator.sizeof + size)!;
temp.last_page = null;
temp.backing_allocator = allocator;
temp.used = 0;
@@ -45,13 +45,6 @@ fn TempAllocator*! new_temp_allocator(usz size, Allocator allocator)
return temp;
}
fn void TempAllocator.destroy(&self)
{
self.reset(0);
if (self.last_page) (void)self._free_page(self.last_page);
allocator::free(self.backing_allocator, self);
}
fn usz TempAllocator.mark(&self) @dynamic => self.used;
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
@@ -60,7 +53,6 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
if (old_pointer + old_size == &self.data[self.used])
{
self.used -= old_size;
asan::poison_memory_region(&self.data[self.used], old_size);
}
}
fn void TempAllocator.reset(&self, usz mark) @dynamic
@@ -68,35 +60,22 @@ fn void TempAllocator.reset(&self, usz mark) @dynamic
TempAllocatorPage *last_page = self.last_page;
while (last_page && last_page.mark > mark)
{
self.used = last_page.mark;
TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
}
self.last_page = last_page;
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
if (!last_page)
{
usz cleaned = self.used - mark;
if (cleaned > 0)
{
$if env::COMPILER_SAFE_MODE:
self.data[mark : cleaned] = 0xAA;
$endif
asan::poison_memory_region(&self.data[mark], cleaned);
}
}
$endif
self.used = mark;
}
fn void! TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
if (page.is_aligned()) return self.backing_allocator.free_aligned(mem);
return self.backing_allocator.free(mem);
}
fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @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;
@@ -111,36 +90,53 @@ fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size,
*pointer_to_prev = page.prev_page;
usz page_size = page.pagesize();
// Clear on size > original size.
void* data = self.acquire(size, NO_ZERO, alignment)!;
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);
self.backing_allocator.release(real_pointer, page.is_aligned());
if (page.is_aligned())
{
self.backing_allocator.free_aligned(real_pointer);
}
else
{
self.backing_allocator.free(real_pointer);
}
return data;
}
fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
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(self.last_page, "Realloc of non temp pointer");
// First grab the page
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
return self._realloc_page(page, size, alignment);
return self._realloc_page(page, size, alignment, offset);
}
TempAllocatorChunk* data = self.acquire(size, NO_ZERO, alignment)!;
// TODO optimize last allocation
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 size > 0
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
*>
fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
**/
fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{
if (!size) return null;
alignment = alignment_for_allocation(alignment);
void* start_mem = &self.data;
void* starting_ptr = start_mem + self.used;
@@ -148,18 +144,17 @@ fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
void* mem = aligned_header_start + TempAllocatorChunk.sizeof;
if (alignment > TempAllocatorChunk.alignof)
{
mem = mem::aligned_pointer(mem, alignment);
mem = mem::aligned_pointer(mem + offset, alignment) - offset;
}
usz new_usage = (usz)(mem - start_mem) + size;
// Arena allocation, simple!
// Arena alignment, simple!
if (new_usage <= self.capacity)
{
asan::unpoison_memory_region(starting_ptr, new_usage - self.used);
TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof;
chunk_start.size = size;
self.used = new_usage;
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
@@ -167,20 +162,19 @@ fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
TempAllocatorPage* page;
// We have something we need to align.
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
if (alignment > mem::DEFAULT_MEM_ALIGNMENT || offset)
{
// This is actually simpler, since it will create the offset for us.
usz total_alloc_size = mem::aligned_offset(TempAllocatorPage.sizeof + size, alignment);
if (init_type == ZERO)
usz total_alloc_size = TempAllocatorPage.sizeof + size;
if (clear)
{
mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
page = self.backing_allocator.calloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!;
}
else
{
mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
page = self.backing_allocator.alloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!;
}
page = (TempAllocatorPage*)mem - 1;
page.start = mem;
page.start = page;
page.size = size | PAGE_IS_ALIGNED;
}
else
@@ -188,7 +182,7 @@ fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
// 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 = self.backing_allocator.acquire(total_alloc_size, init_type, 0)!;
void* alloc = self.backing_allocator.acquire(total_alloc_size, clear, 0, 0)!;
// Find the page.
page = alloc + padded_header_size - TempAllocatorPage.sizeof;

View File

@@ -1,9 +1,10 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// 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 std::collections, std::io, std::os::backtrace;
import std::collections::map;
import std::collections::list;
const MAX_BACKTRACE = 16;
struct Allocation
@@ -20,35 +21,35 @@ def AllocMap = HashMap(<uptr, Allocation>);
// is not compatible with allocators that uses mark()
struct TrackingAllocator (Allocator)
{
Allocator inner_allocator;
Allocator* inner_allocator;
AllocMap map;
usz mem_total;
usz allocs_total;
}
<*
Initialize a tracking allocator to wrap (and track) another allocator.
@param [&inout] allocator "The allocator to track"
*>
fn void TrackingAllocator.init(&self, Allocator allocator)
/**
* Initialize a tracking allocator to wrap (and track) another allocator.
*
* @param [&inout] allocator "The allocator to track"
**/
fn void TrackingAllocator.init(&self, Allocator* allocator)
{
*self = { .inner_allocator = allocator };
self.map.new_init(allocator: allocator);
self.map.init_new(.allocator = allocator);
}
<*
Free this tracking allocator.
*>
/**
* Free this tracking allocator.
**/
fn void TrackingAllocator.free(&self)
{
self.map.free();
*self = {};
}
<*
@return "the total allocated memory not yet freed."
*>
/**
* @return "the total allocated memory not yet freed."
**/
fn usz TrackingAllocator.allocated(&self)
{
usz allocated = 0;
@@ -59,54 +60,67 @@ fn usz TrackingAllocator.allocated(&self)
return allocated;
}
<*
@return "the total memory allocated (freed or not)."
*>
/**
* @return "the total memory allocated (freed or not)."
**/
fn usz TrackingAllocator.total_allocated(&self) => self.mem_total;
<*
@return "the total number of allocations (freed or not)."
*>
/**
* @return "the total number of allocations (freed or not)."
**/
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator* allocator)
{
return self.map.value_tlist();
}
<*
@return "the number of non-freed allocations."
*>
/**
* @return "the number of non-freed allocations."
**/
fn usz TrackingAllocator.allocation_count(&self) => self.map.count;
fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*! TrackingAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{
void* data = self.inner_allocator.acquire(size, init_type, alignment)!;
void* data = self.inner_allocator.acquire(size, clear, alignment, offset)!;
self.allocs_total++;
void*[MAX_BACKTRACE] bt;
backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
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.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
{
void* data = self.inner_allocator.resize(old_pointer, size, alignment)!;
self.map.remove((uptr)old_pointer);
void*[MAX_BACKTRACE] bt;
backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
self.allocs_total++;
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 (catch self.map.remove((uptr)old_pointer))
if (old_pointer)
{
unreachable("Attempt to release untracked pointer %p, this is likely a bug.", 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);
}
@@ -118,7 +132,7 @@ fn void TrackingAllocator.clear(&self)
fn void TrackingAllocator.print_report(&self) => self.fprint_report(io::stdout())!!;
fn void! TrackingAllocator.fprint_report(&self, OutStream out)
fn void! TrackingAllocator.fprint_report(&self, OutStream* out)
{
usz total = 0;
@@ -132,14 +146,14 @@ fn void! TrackingAllocator.fprint_report(&self, OutStream out)
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, "===============================")!;
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
@@ -154,7 +168,7 @@ fn void! TrackingAllocator.fprint_report(&self, OutStream out)
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
if (allocation.backtrace[3])
{
trace = backtrace::symbolize_backtrace(allocation.backtrace[3:1], allocator::temp()).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
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,
@@ -169,48 +183,48 @@ fn void! TrackingAllocator.fprint_report(&self, OutStream out)
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::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;
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 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)], allocator::temp())!;
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
foreach (trace : list)
{
if (trace.has_file())
{
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
continue;
}
if (trace.is_unknown())
{
io::fprintfn(out, " ??? (in unknown)");
continue;
}
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);
}
}
}
}
}
};
}

View File

@@ -1,12 +1,11 @@
module std::core::array;
import std::core::array::slice;
<*
@param [in] array
@param [in] element
@return "the first index of the element"
@return! SearchResult.MISSING
*>
/**
* @param [in] array
* @param [in] element
* @return "the first index of the element"
* @return! SearchResult.MISSING
**/
macro index_of(array, element)
{
foreach (i, &e : array)
@@ -16,25 +15,12 @@ macro index_of(array, element)
return SearchResult.MISSING?;
}
<*
@require @typekind(array) == VECTOR || @typekind(array) == ARRAY
@require @typekind(array[0]) == VECTOR || @typekind(array[0]) == ARRAY
*>
macro slice2d(array, x = 0, xlen = 0, y = 0, ylen = 0)
{
if (xlen < 1) xlen = $typeof(array[0]).len + xlen;
if (ylen < 1) ylen = $typeof(array).len + ylen;
var $ElementType = $typeof(array[0][0]);
return Slice2d(<$ElementType>) { ($ElementType*)&array, $typeof(array[0]).len, y, ylen, x, xlen };
}
<*
@param [in] array
@param [in] element
@return "the last index of the element"
@return! SearchResult.MISSING
*>
/**
* @param [in] array
* @param [in] element
* @return "the last index of the element"
* @return! SearchResult.MISSING
**/
macro rindex_of(array, element)
{
foreach_r (i, &e : array)
@@ -44,21 +30,21 @@ macro rindex_of(array, element)
return SearchResult.MISSING?;
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
@param [in] arr1
@param [in] arr2
@param [&inout] allocator "The allocator to use, default is the heap allocator"
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len
*>
macro concat(arr1, arr2, Allocator allocator) @nodiscard
/**
* Concatenate two arrays or subarrays, returning a subarray containing the concatenation of them.
*
* @param [in] arr1
* @param [in] arr2
* @param [&inout] allocator "The allocator to use, default is the heap allocator"
* @require @typekind(arr1) == 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_new(arr1, arr2, Allocator* allocator = mem::heap())
{
var $Type = $typeof(arr1[0]);
$Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len);
$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);
@@ -69,94 +55,16 @@ macro concat(arr1, arr2, Allocator allocator) @nodiscard
}
return result;
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
@param [in] arr1
@param [in] arr2
@param [&inout] allocator "The allocator to use, default is the heap allocator"
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len
*>
macro concat_new(arr1, arr2, Allocator allocator = allocator::heap()) @nodiscard
{
return concat(arr1, arr2, allocator);
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them,
allocated using the temp allocator.
@param [in] arr1
@param [in] arr2
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len
*>
macro tconcat(arr1, arr2) @nodiscard => concat(arr1, arr2, allocator::temp());
module std::core::array::slice(<Type>);
struct Slice2d
{
Type* ptr;
usz inner_len;
usz ystart;
usz ylen;
usz xstart;
usz xlen;
}
fn usz Slice2d.len(&self) @operator(len)
{
return self.ylen;
}
fn usz Slice2d.count(&self)
{
return self.ylen * self.xlen;
}
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
{
foreach (y, line : *self)
{
foreach (x, val : line)
{
@body({ x, y }, val);
}
}
}
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
{
foreach (y, line : *self)
{
foreach (x, &val : line)
{
@body({ x, y }, val);
}
}
}
<*
@require idy >= 0 && idy < self.ylen
*>
macro Type[] Slice2d.get(self, usz idy) @operator([])
{
return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen];
}
<*
@require y >= 0 && y < self.ylen
@require x >= 0 && x < self.xlen
*>
fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0)
{
if (xlen < 1) xlen = self.xlen + xlen;
if (ylen < 1) ylen = self.ylen + ylen;
return { self.ptr, self.inner_len, y + self.ystart, ylen, x + self.xstart, xlen };
}
/**
* Concatenate two arrays or subarrays, returning a subarray containing the concatenation of them,
* allocated using the temp allocator.
*
* @param [in] arr1
* @param [in] arr2
* @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 tconcat(arr1, arr2) => concat(arr1, arr2, mem::temp());

View File

@@ -87,10 +87,10 @@ bitstruct UInt128LE : uint128 @littleendian
uint128 val : 0..127;
}
<*
@require is_array_or_slice_of_char(bytes) "argument must be an array, a pointer to an array or a slice of char"
@require is_bitorder($Type) "type must be a bitorder integer"
*>
/**
* @require is_array_or_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;
@@ -103,10 +103,10 @@ macro read(bytes, $Type)
return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
}
<*
@require is_arrayptr_or_slice_of_char(bytes) "argument must be a pointer to an array or a slice of char"
@require is_bitorder($Type) "type must be a bitorder integer"
*>
/**
* @require is_arrayptr_or_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;
@@ -144,7 +144,7 @@ macro is_bitorder($Type)
$endswitch
}
macro bool is_array_or_slice_of_char(bytes)
macro bool is_array_or_sub_of_char(bytes)
{
$switch (@typekind(bytes))
$case POINTER:
@@ -154,7 +154,7 @@ macro bool is_array_or_slice_of_char(bytes)
return $Inner2.typeid == char.typeid;
$endif
$case ARRAY:
$case SLICE:
$case SUBARRAY:
var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid;
$default:
@@ -162,7 +162,7 @@ macro bool is_array_or_slice_of_char(bytes)
$endswitch
}
macro bool is_arrayptr_or_slice_of_char(bytes)
macro bool is_arrayptr_or_sub_of_char(bytes)
{
$switch (@typekind(bytes))
$case POINTER:
@@ -171,7 +171,7 @@ macro bool is_arrayptr_or_slice_of_char(bytes)
var $Inner2 = $typefrom($Inner.inner);
return $Inner2.typeid == char.typeid;
$endif
$case SLICE:
$case SUBARRAY:
var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid;
$default:

View File

@@ -1,32 +1,40 @@
// Copyright (c) 2021-2024 Christoffer Lerno and contributors. All rights reserved.
// Copyright (c) 2021-2022 Christoffer Lerno and contributors. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::builtin;
import libc, std::hash, std::io, std::os::backtrace;
import libc;
import std::hash;
<*
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
*>
fault IteratorResult { NO_MORE_ELEMENT }
/**
* Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
**/
fault IteratorResult
{
NO_MORE_ELEMENT
}
<*
Use `SearchResult` when trying to return a value from some collection but the element is missing.
*>
fault SearchResult { MISSING }
/**
* Use `SearchResult` when trying to return a value from some collection but the element is missing.
**/
fault SearchResult
{
MISSING
}
<*
Use `CastResult` when an attempt at conversion fails.
*>
fault CastResult { TYPE_MISMATCH }
/**
* Use `CastResult` when an attempt at conversion fails.
**/
fault CastResult
{
TYPE_MISMATCH
}
def VoidFn = fn void();
<*
Stores a variable on the stack, then restores it at the end of the
macro scope.
@param variable `the variable to store and restore`
*>
/**
* Stores a variable on the stack, then restores it at the end of the
* macro scope.
*
* @param variable `the variable to store and restore`
**/
macro void @scope(&variable; @body) @builtin
{
var temp = *variable;
@@ -34,10 +42,10 @@ macro void @scope(&variable; @body) @builtin
@body();
}
<*
Swap two variables
@require $assignable(*b, $typeof(*a)) && $assignable(*a, $typeof(*b))
*>
/**
* Swap two variables
* @require $assignable(*b, $typeof(*a)) && $assignable(*a, $typeof(*b))
**/
macro void @swap(&a, &b) @builtin
{
var temp = *a;
@@ -45,16 +53,16 @@ macro void @swap(&a, &b) @builtin
*b = temp;
}
<*
Convert an `any` type to a type, returning an failure if there is a type mismatch.
@param v `the any to convert to the given type.`
@param $Type `the type to convert to`
@return `The any.ptr converted to its type.`
@ensure @typeis(return, $Type*)
@return! CastResult.TYPE_MISMATCH
*>
macro anycast(any v, $Type) @builtin
/**
* Convert an `any` type to a type, returning an failure if there is a type mismatch.
*
* @param v `the any to convert to the given type.`
* @param $Type `the type to convert to`
* @return `The any.ptr converted to its type.`
* @ensure @typeis(return, $Type*)
* @return! CastResult.TYPE_MISMATCH
**/
macro anycast(any* v, $Type) @builtin
{
if (v.type != $Type.typeid) return CastResult.TYPE_MISMATCH?;
return ($Type*)v.ptr;
@@ -67,27 +75,26 @@ fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIV
void*[256] buffer;
void*[] backtraces = backtrace::capture_current(&buffer);
backtraces_to_ignore++;
BacktraceList! backtrace = backtrace::symbolize_backtrace(backtraces, allocator::temp());
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("'");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
{
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
{
io::eprintfn(" in ???%s", inline_suffix);
{
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]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
{
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;
};
@@ -104,29 +111,13 @@ fn void default_panic(String message, String file, String function, uint line) @
$$trap();
}
macro void abort(String string = "Unrecoverable error reached", ...) @builtin @noreturn
{
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
$$trap();
}
bool in_panic @local = false;
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
{
if (in_panic)
{
io::eprintn("Panic inside of panic.");
return;
}
in_panic = true;
$if $defined(io::stderr):
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintfn("', in %s (%s:%d)", function, file, line);
$endif
in_panic = false;
$$trap();
}
@@ -136,54 +127,36 @@ PanicFn panic = &default_panic;
fn void panicf(String fmt, String file, String function, uint line, args...)
{
if (in_panic)
{
io::eprint("Panic inside of panic: ");
io::eprintn(fmt);
return;
}
in_panic = true;
@stack_mem(512; Allocator allocator)
@stack_mem(512; Allocator* allocator)
{
DString s;
s.new_init(allocator: allocator);
s.init_new(.allocator = allocator);
s.appendf(fmt, ...args);
in_panic = false;
panic(s.str_view(), file, function, line);
};
}
<*
Marks the path as unreachable. This will panic in safe mode, and in fast will simply be assumed
never happens.
@param [in] string "The panic message or format string"
*>
/**
* Marks the path as unreachable. This will panic in safe mode, and in fast will simply be assumed
* never happens.
* @param [in] string "The panic message or format string"
**/
macro void unreachable(String string = "Unreachable statement reached.", ...) @builtin @noreturn
{
$if env::COMPILER_SAFE_MODE:
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
$endif;
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat());
$$unreachable();
}
<*
Marks the path as unsupported, this is similar to unreachable.
@param [in] string "The error message"
*>
/**
* Marks the path as unsupported, this is similar to unreachable.
* @param [in] string "The error message"
**/
macro void unsupported(String string = "Unsupported function invoked") @builtin @noreturn
{
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat());
$$unreachable();
}
<*
Unconditionally break into an attached debugger when reached.
*>
macro void breakpoint() @builtin
{
$$breakpoint();
}
macro any_make(void* ptr, typeid type) @builtin
{
return $$any_make(ptr, type);
@@ -199,13 +172,13 @@ macro any.as_inner(&self)
return $$any_make(self.ptr, self.type.inner);
}
<*
@param expr "the expression to cast"
@param $Type "the type to cast to"
@require $sizeof(expr) == $Type.sizeof "Cannot bitcast between types of different size."
@ensure @typeis(return, $Type)
*>
/**
* @param expr "the expression to cast"
* @param $Type "the type to cast to"
*
* @require $sizeof(expr) == $Type.sizeof "Cannot bitcast between types of different size."
* @ensure @typeis(return, $Type)
**/
macro bitcast(expr, $Type) @builtin
{
$if $Type.alignof <= $alignof(expr):
@@ -217,13 +190,13 @@ macro bitcast(expr, $Type) @builtin
$endif
}
<*
@param $Type `The type of the enum`
@param [in] enum_name `The name of the enum to search for`
@require $Type.kindof == ENUM `Only enums may be used`
@ensure @typeis(return, $Type)
@return! SearchResult.MISSING
*>
/**
* @param $Type `The type of the enum`
* @param [in] enum_name `The name of the enum to search for`
* @require $Type.kindof == ENUM `Only enums may be used`
* @ensure @typeis(return, $Type)
* @return! SearchResult.MISSING
**/
macro enum_by_name($Type, String enum_name) @builtin
{
typeid x = $Type.typeid;
@@ -234,65 +207,65 @@ macro enum_by_name($Type, String enum_name) @builtin
return SearchResult.MISSING?;
}
<*
Mark an expression as likely to be true
@param #value "expression to be marked likely"
@param $probability "in the range 0 - 1"
@require $probability >= 0 && $probability <= 1.0
*>
/**
* Mark an expression as likely to be true
*
* @param #value "expression to be marked likely"
* @param $probability "in the range 0 - 1"
* @require $probability >= 0 && $probability <= 1.0
**/
macro bool @likely(bool #value, $probability = 1.0) @builtin
{
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value;
$case $probability == 1.0:
$case $probability == 1.0:
return $$expect(#value, true);
$default:
$default:
return $$expect_with_probability(#value, true, $probability);
$endswitch
$endswitch
}
<*
Mark an expression as unlikely to be true
@param #value "expression to be marked unlikely"
@param $probability "in the range 0 - 1"
@require $probability >= 0 && $probability <= 1.0
*>
/**
* Mark an expression as unlikely to be true
*
* @param #value "expression to be marked unlikely"
* @param $probability "in the range 0 - 1"
* @require $probability >= 0 && $probability <= 1.0
**/
macro bool @unlikely(bool #value, $probability = 1.0) @builtin
{
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value;
$case $probability == 1.0:
$case $probability == 1.0:
return $$expect(#value, false);
$default:
$default:
return $$expect_with_probability(#value, false, $probability);
$endswitch
$endswitch
}
<*
@require values::@is_int(#value) || values::@is_bool(#value)
@require $assignable(expected, $typeof(#value))
@require $probability >= 0 && $probability <= 1.0
*>
/**
* @require values::@is_int(#value) || values::@is_bool(#value)
* @require $assignable(expected, $typeof(#value))
* @require $probability >= 0 && $probability <= 1.0
**/
macro @expect(#value, expected, $probability = 1.0) @builtin
{
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value == expected;
$case $probability == 1.0:
$case $probability == 1.0:
return $$expect(#value, ($typeof(#value))expected);
$default:
$default:
return $$expect_with_probability(#value, expected, $probability);
$endswitch
$endswitch
}
<*
Locality for prefetch, levels 0 - 3, corresponding
to "extremely local" to "no locality"
*>
/**
* Locality for prefetch, levels 0 - 3, corresponding
* to "extremely local" to "no locality"
**/
enum PrefetchLocality
{
NO_LOCALITY,
@@ -301,13 +274,13 @@ enum PrefetchLocality
VERY_NEAR,
}
<*
Prefetch a pointer.
/**
* Prefetch a pointer.
@param [in] ptr `Pointer to prefetch`
@param $locality `Locality ranging from none to extremely local`
@param $write `Prefetch for write, otherwise prefetch for read.`
*>
* @param [in] ptr `Pointer to prefetch`
* @param $locality `Locality ranging from none to extremely local`
* @param $write `Prefetch for write, otherwise prefetch for read.`
**/
macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
{
$if !env::BUILTIN_PREFETCH_IS_DISABLED:
@@ -317,31 +290,20 @@ macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write =
macro swizzle(v, ...) @builtin
{
return $$swizzle(v, $vasplat);
return $$swizzle(v, $vasplat());
}
macro swizzle2(v, v2, ...) @builtin
{
return $$swizzle2(v, v2, $vasplat);
return $$swizzle2(v, v2, $vasplat());
}
<*
Return the excuse in the Optional if it is Empty, otherwise
return a null fault.
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
*>
macro anyfault @catch(#expr) @builtin
{
if (catch f = #expr) return f;
return anyfault {};
}
<*
Check if an Optional expression holds a value or is empty, returning true
if it has a value.
@require @typekind(#expr) == OPTIONAL : `@ok expects an Optional value`
*>
macro bool @ok(#expr) @builtin
{
if (catch #expr) return false;
@@ -353,11 +315,6 @@ macro char[] @as_char_view(&value) @builtin
return ((char*)value)[:$sizeof(*value)];
}
macro isz @str_find(String $string, String $needle) @builtin => $$str_find($string, $needle);
macro String @str_upper(String $str) @builtin => $$str_upper($str);
macro String @str_lower(String $str) @builtin => $$str_lower($str);
macro uint @str_hash(String $str) @builtin => $$str_hash($str);
macro uint int.hash(int i) => i;
macro uint uint.hash(uint i) => i;
macro uint short.hash(short s) => s;
@@ -374,15 +331,10 @@ macro uint String.hash(String c) => (uint)fnv32a::encode(c);
macro uint char[].hash(char[] c) => (uint)fnv32a::encode(c);
macro uint void*.hash(void* ptr) => ((ulong)(uptr)ptr).hash();
distinct EmptySlot = void*;
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
macro @is_empty_macro_slot(#arg) @builtin => @typeis(#arg, EmptySlot);
macro @is_valid_macro_slot(#arg) @builtin => !@typeis(#arg, EmptySlot);
const MAX_FRAMEADDRESS = 128;
<*
@require n >= 0
*>
/**
* @require n >= 0
**/
macro void* get_frameaddress(int n)
{
if (n > MAX_FRAMEADDRESS) return null;
@@ -521,9 +473,9 @@ macro void* get_frameaddress(int n)
}
}
<*
@require n >= 0
*>
/**
* @require n >= 0
**/
macro void* get_returnaddress(int n)
{
if (n > MAX_FRAMEADDRESS) return null;
@@ -663,7 +615,7 @@ macro void* get_returnaddress(int n)
}
module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
import libc, std::io;
import libc;
fn void sig_panic(String message)
{
@@ -711,7 +663,7 @@ fn void install_signal_handler(CInt signal, SignalFunction func) @local
}
// Clean this up
fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE)
fn void install_signal_handlers() @init(101) @local
{
install_signal_handler(libc::SIGBUS, &sig_bus_error);
install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);

View File

@@ -1,11 +1,11 @@
// Copyright (c) 2021-2024 Christoffer Lerno and contributors. All rights reserved.
// Copyright (c) 2021-2022 Christoffer Lerno and contributors. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::builtin;
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro less(a, b) @builtin
{
$switch
@@ -18,9 +18,9 @@ macro less(a, b) @builtin
$endswitch
}
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro less_eq(a, b) @builtin
{
$switch
@@ -33,9 +33,9 @@ macro less_eq(a, b) @builtin
$endswitch
}
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro greater(a, b) @builtin
{
$switch
@@ -48,9 +48,9 @@ macro greater(a, b) @builtin
$endswitch
}
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro int compare_to(a, b) @builtin
{
$switch
@@ -62,9 +62,9 @@ macro int compare_to(a, b) @builtin
return (int)(a > b) - (int)(a < b);
$endswitch
}
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro greater_eq(a, b) @builtin
{
$switch
@@ -77,9 +77,9 @@ macro greater_eq(a, b) @builtin
$endswitch
}
<*
@require types::@equatable_value(a) && types::@equatable_value(b) `values must be equatable`
*>
/**
* @require types::is_equatable_value(a) && types::is_equatable_value(b) `values must be equatable`
**/
macro bool equals(a, b) @builtin
{
$switch
@@ -97,13 +97,13 @@ macro bool equals(a, b) @builtin
macro min(x, ...) @builtin
{
$if $vacount == 1:
return less(x, $vaarg[0]) ? x : $vaarg[0];
return less(x, $vaarg(0)) ? x : $vaarg(0);
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
if (less($vaarg[$i], result))
if (less($vaarg($i), result))
{
result = $vaarg[$i];
result = $vaarg($i);
}
$endfor
return result;
@@ -113,13 +113,13 @@ macro min(x, ...) @builtin
macro max(x, ...) @builtin
{
$if $vacount == 1:
return greater(x, $vaarg[0]) ? x : $vaarg[0];
return greater(x, $vaarg(0)) ? x : $vaarg(0);
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
if (greater($vaarg[$i], result))
if (greater($vaarg($i), result))
{
result = $vaarg[$i];
result = $vaarg($i);
}
$endfor
return result;

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// 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::cinterop;
@@ -29,12 +29,6 @@ def CUChar = char;
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
enum CBool : CInt
{
FALSE,
TRUE
}
// Helper macros
macro typeid signed_int_from_bitsize(usz $bitsize) @private
{

View File

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

View File

@@ -5,36 +5,36 @@ distinct DString (OutStream) = void*;
const usz MIN_CAPACITY @private = 16;
<*
@require !self.data() "String already initialized"
*>
fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap())
/**
* @require !self.data() "String already initialized"
**/
fn DString DString.init_new(&self, usz capacity = MIN_CAPACITY, Allocator* allocator = mem::heap())
{
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
StringData* data = allocator.new(StringData, .end_padding = capacity);
data.allocator = allocator;
data.len = 0;
data.capacity = capacity;
return *self = (DString)data;
}
<*
@require !self.data() "String already initialized"
*>
fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY)
/**
* @require !self.data() "String already initialized"
**/
fn DString DString.init_temp(&self, usz capacity = MIN_CAPACITY)
{
self.new_init(capacity, allocator::temp()) @inline;
self.init_new(capacity, mem::temp()) @inline;
return *self;
}
fn DString new_with_capacity(usz capacity, Allocator allocator = allocator::heap())
fn DString new_with_capacity(usz capacity, Allocator* allocator = mem::heap())
{
return DString{}.new_init(capacity, allocator);
return DString{}.init_new(capacity, allocator);
}
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, allocator::temp()) @inline;
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, mem::temp()) @inline;
fn DString new(String c = "", Allocator allocator = allocator::heap())
fn DString new(String c = "", Allocator* allocator = mem::heap())
{
usz len = c.len;
StringData* data = (StringData*)new_with_capacity(len, allocator);
@@ -46,68 +46,18 @@ fn DString new(String c = "", Allocator allocator = allocator::heap())
return (DString)data;
}
fn DString temp_new(String s = "") => new(s, allocator::temp()) @inline;
fn DString temp_new(String s = "") => new(s, mem::temp()) @inline;
fn void DString.replace_char(self, char ch, char replacement)
{
StringData* data = self.data();
foreach (&c : data.chars[:data.len])
{
if (*c == ch) *c = replacement;
}
}
fn void DString.replace(&self, String needle, String replacement)
{
StringData* data = self.data();
usz needle_len = needle.len;
if (!data || data.len < needle_len) return;
usz replace_len = replacement.len;
if (needle_len == 1 && replace_len == 1)
{
self.replace_char(needle[0], replacement[0]);
return;
}
@pool(data.allocator) {
String str = self.tcopy_str();
self.clear();
usz len = str.len;
usz match = 0;
foreach (i, c : str)
{
if (c == needle[match])
{
match++;
if (match == needle_len)
{
self.append_chars(replacement);
match = 0;
continue;
}
continue;
}
if (match > 0)
{
self.append_chars(str[i - match:match]);
match = 0;
}
self.append_char(c);
}
if (match > 0) self.append_chars(str[^match:match]);
};
}
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
fn DString DString.new_concat(self, DString b, Allocator* allocator = mem::heap())
{
DString string;
string.new_init(self.len() + b.len(), allocator);
string.init_new(self.len() + b.len(), allocator);
string.append(self);
string.append(b);
return string;
}
fn DString DString.temp_concat(self, DString b) => self.new_concat(b, allocator::temp());
fn DString DString.new_tconcat(self, DString b) => self.new_concat(b, mem::temp());
fn ZString DString.zstr_view(&self)
{
@@ -132,15 +82,15 @@ fn usz DString.capacity(self)
return self.data().capacity;
}
fn usz DString.len(&self) @dynamic @operator(len)
fn usz DString.len(&self) @dynamic
{
if (!*self) return 0;
return self.data().len;
}
<*
@require new_size <= self.len()
*>
/**
* @require new_size <= self.len()
*/
fn void DString.chop(self, usz new_size)
{
if (!self) return;
@@ -154,39 +104,19 @@ fn String DString.str_view(self)
return (String)data.chars[:data.len];
}
<*
@require index < self.len()
@require self.data() "Empty string"
*>
fn char DString.char_at(self, usz index) @operator([])
{
return self.data().chars[index];
}
<*
@require index < self.len()
@require self.data() "Empty string"
*>
fn char* DString.char_ref(&self, usz index) @operator(&[])
{
return &self.data().chars[index];
}
fn usz DString.append_utf32(&self, Char32[] chars)
fn void DString.append_utf32(&self, Char32[] chars)
{
self.reserve(chars.len);
usz end = self.len();
foreach (Char32 c : chars)
{
self.append_char32(c);
}
return self.data().len - end;
}
<*
@require index < self.len()
*>
fn void DString.set(self, usz index, char c) @operator([]=)
/**
* @require index < self.len()
**/
fn void DString.set(self, usz index, char c)
{
self.data().chars[index] = c;
}
@@ -202,10 +132,10 @@ fn void DString.append_repeat(&self, char c, usz times)
}
}
<*
@require c <= 0x10ffff
*>
fn usz DString.append_char32(&self, Char32 c)
/**
* @require c <= 0x10ffff
*/
fn void DString.append_char32(&self, Char32 c)
{
char[4] buffer @noinit;
char* p = &buffer;
@@ -214,12 +144,11 @@ fn usz DString.append_char32(&self, Char32 c)
StringData* data = self.data();
data.chars[data.len:n] = buffer[:n];
data.len += n;
return n;
}
fn DString DString.tcopy(&self) => self.copy(allocator::temp());
fn DString DString.tcopy(&self) => self.copy(mem::temp());
fn DString DString.copy(self, Allocator allocator = null)
fn DString DString.copy(self, Allocator* allocator = null)
{
if (!self)
{
@@ -227,32 +156,32 @@ fn DString DString.copy(self, Allocator allocator = null)
return (DString)null;
}
StringData* data = self.data();
if (!allocator) allocator = allocator::heap();
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(self, Allocator allocator = allocator::heap())
fn ZString DString.copy_zstr(self, Allocator* allocator = mem::heap())
{
usz str_len = self.len();
if (!str_len)
{
return (ZString)allocator::calloc(allocator, 1);
return (ZString)allocator.calloc(1);
}
char* zstr = allocator::malloc(allocator, str_len + 1);
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(self, Allocator allocator = allocator::heap())
fn String DString.copy_str(self, Allocator* allocator = mem::heap())
{
return (String)self.copy_zstr(allocator)[:self.len()];
}
fn String DString.tcopy_str(self) => self.copy_str(allocator::temp()) @inline;
fn String DString.tcopy_str(self) => self.copy_str(mem::temp()) @inline;
fn bool DString.equals(self, DString other_string)
{
@@ -275,7 +204,7 @@ fn void DString.free(&self)
if (!*self) return;
StringData* data = self.data();
if (!data) return;
allocator::free(data.allocator, data);
data.allocator.free(data);
*self = (DString)null;
}
@@ -311,9 +240,9 @@ fn void DString.append_chars(&self, String str)
data.len += other_len;
}
fn Char32[] DString.copy_utf32(&self, Allocator allocator = allocator::heap())
fn Char32[] DString.copy_utf32(&self, Allocator* allocator = mem::heap())
{
return self.str_view().to_utf32(allocator) @inline!!;
return self.str_view().to_new_utf32(allocator) @inline!!;
}
fn void DString.append_string(&self, DString str)
@@ -351,37 +280,6 @@ fn void DString.append_char(&self, char c)
data.chars[data.len++] = c;
}
<*
@require start < self.len()
@require end < self.len()
@require end >= start "End must be same or equal to the start"
*>
fn void DString.delete_range(&self, usz start, usz end)
{
self.delete(start, end - start + 1);
}
<*
@require start < self.len()
@require start + len <= self.len()
*>
fn void DString.delete(&self, usz start, usz len = 1)
{
if (!len) return;
StringData* data = self.data();
usz new_len = data.len - len;
if (new_len == 0)
{
data.len = 0;
return;
}
usz len_after = data.len - start - len;
if (len_after > 0)
{
data.chars[start:len_after] = data.chars[start + len:len_after];
}
data.len = new_len;
}
macro void DString.append(&self, value)
{
@@ -408,10 +306,7 @@ macro void DString.append(&self, value)
$endswitch
}
<*
@require index <= self.len()
*>
fn void DString.insert_chars_at(&self, usz index, String s)
fn void DString.insert_at(&self, usz index, String s)
{
if (s.len == 0) return;
self.reserve(s.len);
@@ -443,128 +338,23 @@ fn void DString.insert_chars_at(&self, usz index, String s)
}
}
<*
@require index <= self.len()
*>
fn void DString.insert_string_at(&self, usz index, DString str)
{
StringData* other = str.data();
if (!other) return;
self.insert_at(index, str.str_view());
}
<*
@require index <= self.len()
*>
fn void DString.insert_char_at(&self, usz index, char c)
{
self.reserve(1);
StringData* data = self.data();
char* start = &data.chars[index];
mem::move(start + 1, start, self.len() - index);
data.chars[index] = c;
data.len++;
}
<*
@require index <= self.len()
*>
fn usz DString.insert_char32_at(&self, usz index, Char32 c)
{
char[4] buffer @noinit;
char* p = &buffer;
usz n = conv::char32_to_utf8_unsafe(c, &p);
self.reserve(n);
StringData* data = self.data();
char* start = &data.chars[index];
mem::move(start + n, start, self.len() - index);
data.chars[index:n] = buffer[:n];
data.len += n;
return n;
}
<*
@require index <= self.len()
*>
fn usz DString.insert_utf32_at(&self, usz index, Char32[] chars)
{
usz n = conv::utf8len_for_utf32(chars);
self.reserve(n);
StringData* data = self.data();
char* start = &data.chars[index];
mem::move(start + n, start, self.len() - index);
char[4] buffer @noinit;
foreach(c : chars)
{
char* p = &buffer;
usz m = conv::char32_to_utf8_unsafe(c, &p);
data.chars[index:m] = buffer[:m];
index += m;
}
data.len += n;
return n;
}
macro void DString.insert_at(&self, usz index, value)
{
var $Type = $typeof(value);
$switch ($Type)
$case char:
$case ichar:
self.insert_char_at(index, value);
$case DString:
self.insert_string_at(index, value);
$case String:
self.insert_chars_at(index, value);
$case Char32:
self.insert_char32_at(index, value);
$default:
$switch
$case $defined((Char32)value):
self.insert_char32_at(index, (Char32)value);
$case $defined((String)value):
self.insert_chars_at(index, (String)value);
$default:
$error "Unsupported type for insert";
$endswitch
$endswitch
}
fn usz! DString.appendf(&self, String format, args...) @maydiscard
{
if (!self.data()) self.new_init(format.len + 20);
@pool(self.data().allocator)
{
Formatter formatter;
formatter.init(&out_string_append_fn, self);
return formatter.vprintf(format, args);
};
Formatter formatter;
formatter.init(&out_string_append_fn, self);
return formatter.vprintf(format, args);
}
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
{
if (!self.data()) self.new_init(format.len + 20);
@pool(self.data().allocator)
{
Formatter formatter;
formatter.init(&out_string_append_fn, self);
usz len = formatter.vprintf(format, args)!;
self.append('\n');
return len + 1;
};
Formatter formatter;
formatter.init(&out_string_append_fn, self);
usz len = formatter.vprintf(format, args)!;
self.append('\n');
return len + 1;
}
fn DString new_join(String[] s, String joiner, Allocator allocator = allocator::heap())
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;
@@ -588,19 +378,6 @@ fn void! out_string_append_fn(void* data, char c) @private
s.append_char(c);
}
fn void DString.reverse(self)
{
StringData *data = self.data();
if (!data) return;
isz mid = data.len / 2;
for (isz i = 0; i < mid; i++)
{
char temp = data.chars[i];
isz reverse_index = data.len - 1 - i;
data.chars[i] = data.chars[reverse_index];
data.chars[reverse_index] = temp;
}
}
fn StringData* DString.data(self) @inline @private
{
@@ -621,10 +398,10 @@ fn void DString.reserve(&self, usz addition)
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
while (new_capacity < len) new_capacity *= 2;
data.capacity = new_capacity;
*self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity);
*self = (DString)data.allocator.realloc(data, StringData.sizeof + new_capacity);
}
fn usz! DString.read_from_stream(&self, InStream reader)
fn usz! DString.read_from_stream(&self, InStream* reader)
{
if (&reader.available)
{
@@ -656,7 +433,7 @@ fn usz! DString.read_from_stream(&self, InStream reader)
struct StringData @private
{
Allocator allocator;
Allocator* allocator;
usz len;
usz capacity;
char[*] chars;

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// 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::env;
@@ -112,15 +112,10 @@ enum ArchType
WASM64, // WebAssembly with 64-bit pointers
RSCRIPT32, // 32-bit RenderScript
RSCRIPT64, // 64-bit RenderScript
XTENSA, // Xtensa
}
const String COMPILER_BUILD_HASH = $$BUILD_HASH;
const String COMPILER_BUILD_DATE = $$BUILD_DATE;
const OsType OS_TYPE = (OsType)$$OS_TYPE;
const ArchType ARCH_TYPE = (ArchType)$$ARCH_TYPE;
const bool ARCH_32_BIT = $$REGISTER_SIZE == 32;
const bool ARCH_64_BIT = $$REGISTER_SIZE == 64;
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
const CompilerOptLevel COMPILER_OPT_LEVEL = (CompilerOptLevel)$$COMPILER_OPT_LEVEL;
@@ -128,17 +123,14 @@ 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 REGISTER_SIZE = $$REGISTER_SIZE;
const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE;
const bool DEBUG_SYMBOLS = $$DEBUG_SYMBOLS;
const bool BACKTRACE = $$BACKTRACE;
const usz LLVM_VERSION = $$LLVM_VERSION;
const bool BENCHMARKING = $$BENCHMARKING;
const bool TESTING = $$TESTING;
const MemoryEnvironment MEMORY_ENV = (MemoryEnvironment)$$MEMORY_ENVIRONMENT;
const bool TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING);
const bool X86_64 = ARCH_TYPE == X86_64;
const bool X86 = ARCH_TYPE == X86;
const bool AARCH64 = ARCH_TYPE == AARCH64;
const bool NATIVE_STACKTRACE = LINUX || DARWIN || WIN32;
const bool LINUX = LIBC && OS_TYPE == LINUX;
@@ -148,15 +140,10 @@ const bool POSIX = LIBC && os_is_posix();
const bool OPENBSD = LIBC && OS_TYPE == OPENBSD;
const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
const bool NETBSD = LIBC && OS_TYPE == NETBSD;
const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD;
const bool WASI = LIBC && OS_TYPE == WASI;
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
macro bool os_is_darwin() @const
macro bool os_is_darwin()
{
$switch (OS_TYPE)
$case IOS:
@@ -169,7 +156,7 @@ macro bool os_is_darwin() @const
$endswitch
}
macro bool os_is_posix() @const
macro bool os_is_posix()
{
$switch (OS_TYPE)
$case IOS:

File diff suppressed because it is too large Load Diff

View File

@@ -10,324 +10,78 @@ struct TrackingEnv
uint line;
}
enum AllocInitType
{
NO_ZERO,
ZERO
}
interface Allocator
{
fn void reset(usz mark) @optional;
fn usz mark() @optional;
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require size > 0
*>
fn void*! acquire(usz size, AllocInitType init_type, usz alignment = 0);
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require ptr != null
@require new_size > 0
*>
fn void*! resize(void* ptr, usz new_size, usz alignment = 0);
<*
@require ptr != null
*>
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);
}
def MemoryAllocFn = fn char[]!(usz);
fault AllocationFailure
{
OUT_OF_MEMORY,
CHUNK_TOO_LARGE,
}
fn usz alignment_for_allocation(usz alignment) @inline @private
{
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
}
macro void* malloc(Allocator allocator, usz size) @nodiscard
{
return malloc_try(allocator, size)!!;
}
macro void*! malloc_try(Allocator allocator, usz size) @nodiscard
{
if (!size) return null;
$if env::TESTING:
char* data = allocator.acquire(size, NO_ZERO)!;
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
return data;
$else
return allocator.acquire(size, NO_ZERO);
$endif
}
macro void* calloc(Allocator allocator, usz size) @nodiscard
{
return calloc_try(allocator, size)!!;
}
macro void*! calloc_try(Allocator allocator, usz size) @nodiscard
{
if (!size) return null;
return allocator.acquire(size, ZERO);
}
macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard
{
return realloc_try(allocator, ptr, new_size)!!;
}
macro void*! realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
{
if (!new_size)
{
free(allocator, ptr);
return null;
}
if (!ptr) return allocator.acquire(new_size, NO_ZERO);
return allocator.resize(ptr, new_size);
}
macro void free(Allocator allocator, void* ptr)
{
if (!ptr) return;
$if env::TESTING:
((char*)ptr)[0] = 0xBA;
$endif
allocator.release(ptr, false);
}
macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
{
if (!size) return null;
$if env::TESTING:
char* data = allocator.acquire(size, NO_ZERO, alignment)!;
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
return data;
$else
return allocator.acquire(size, NO_ZERO, alignment);
$endif
}
macro void*! calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
{
if (!size) return null;
return allocator.acquire(size, ZERO, alignment);
}
macro void*! realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
{
if (!new_size)
{
free_aligned(allocator, ptr);
return null;
}
if (!ptr)
{
return malloc_aligned(allocator, new_size, alignment);
}
return allocator.resize(ptr, new_size, alignment);
}
macro void free_aligned(Allocator allocator, void* ptr)
{
if (!ptr) return;
$if env::TESTING:
((char*)ptr)[0] = 0xBA;
$endif
allocator.release(ptr, aligned: true);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new(Allocator allocator, $Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc(allocator, $Type.sizeof);
$else
$Type* val = malloc(allocator, $Type.sizeof);
*val = $vaexpr[0];
return val;
$endif
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new_try(Allocator allocator, $Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc_try(allocator, $Type.sizeof);
$else
$Type* val = malloc_try(allocator, $Type.sizeof)!;
*val = $vaexpr[0];
return val;
$endif
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new_aligned($Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc_aligned(allocator, $Type.sizeof, $Type.alignof);
$else
$Type* val = malloc_aligned(allocator, $Type.sizeof, $Type.alignof);
*val = $vaexpr[0];
return val;
$endif
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT
*>
macro new_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
{
return ($Type*)calloc_try(allocator, $Type.sizeof + padding);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro alloc(Allocator allocator, $Type) @nodiscard
{
return ($Type*)malloc(allocator, $Type.sizeof);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro alloc_try(Allocator allocator, $Type) @nodiscard
{
return ($Type*)malloc_try(allocator, $Type.sizeof);
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
*>
macro alloc_aligned(Allocator allocator, $Type) @nodiscard
{
return ($Type*)malloc_aligned(allocator, $Type.sizeof, $Type.alignof);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT
*>
macro alloc_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
{
return ($Type*)malloc_try(allocator, $Type.sizeof + padding);
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
*>
macro new_array(Allocator allocator, $Type, usz elements) @nodiscard
{
return new_array_try(allocator, $Type, elements)!!;
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
*>
macro new_array_try(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)calloc_try(allocator, $Type.sizeof * elements))[:elements];
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
*>
macro new_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)calloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*>
macro alloc_array(Allocator allocator, $Type, usz elements) @nodiscard
{
return alloc_array_try(allocator, $Type, elements)!!;
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
*>
macro alloc_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)malloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*>
macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
}
macro clone(Allocator allocator, value) @nodiscard
{
return new(allocator, $typeof(value), value);
}
fn any clone_any(Allocator allocator, any value) @nodiscard
{
usz size = value.type.sizeof;
void* data = malloc(allocator, size);
mem::copy(data, value.ptr, size);
return any_make(data, value.type);
}
<*
@require bytes > 0
@require alignment > 0
@require bytes <= isz.max
*>
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
{
if (alignment < void*.alignof) alignment = void*.alignof;
usz header = AlignedBlock.sizeof + alignment;
usz alignsize = bytes + header;
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
void* data = #alloc_fn(alignsize)!;
$else
void* data = #alloc_fn(alignsize);
$endif
void* mem = mem::aligned_pointer(data + AlignedBlock.sizeof, alignment);
AlignedBlock* desc = (AlignedBlock*)mem - 1;
assert(mem > data);
*desc = { bytes, data };
return mem;
}
struct AlignedBlock
{
usz len;
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;
@@ -338,117 +92,133 @@ macro void! @aligned_free(#free_fn, void* old_pointer)
$endif
}
<*
@require bytes > 0
@require alignment > 0
*>
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
def MemoryAllocFn = fn char[]!(usz);
fault AllocationFailure
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!;
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
$if @typekind(#free_fn(data_start)) == OPTIONAL:
#free_fn(data_start)!;
$else
#free_fn(data_start);
$endif
return new_data;
OUT_OF_MEMORY,
CHUNK_TOO_LARGE,
}
// All allocators
tlocal Allocator thread_allocator @private = base_allocator();
Allocator temp_base_allocator @private = base_allocator();
tlocal TempAllocator* thread_temp_allocator @private = null;
tlocal TempAllocator*[2] temp_allocator_pair @private;
macro Allocator base_allocator() @private
fn usz alignment_for_allocation(usz alignment) @inline @private
{
$if env::LIBC:
return &allocator::LIBC_ALLOCATOR;
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 &allocator::NULL_ALLOCATOR;
return self.acquire(size, false, 0, 0);
$endif
}
macro TempAllocator* create_default_sized_temp_allocator(Allocator allocator) @local
macro void*! Allocator.calloc_checked(&self, usz size)
{
$switch (env::MEMORY_ENV)
$case NORMAL:
return new_temp_allocator(1024 * 256, allocator)!!;
$case SMALL:
return new_temp_allocator(1024 * 16, allocator)!!;
$case TINY:
return new_temp_allocator(1024 * 2, allocator)!!;
$case NONE:
unreachable("Temp allocator must explicitly created when memory-env is set to 'none'.");
$endswitch
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 heap() => thread_allocator;
macro TempAllocator* temp()
macro Allocator.new_array(&self, $Type, usz size, usz end_padding = 0)
{
if (!thread_temp_allocator)
{
init_default_temp_allocators();
}
return thread_temp_allocator;
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size]!!;
}
fn void init_default_temp_allocators() @private
macro Allocator.new_array_checked(&self, $Type, usz size, usz end_padding = 0)
{
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];
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size];
}
fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::LIBC)
macro Allocator.new_zero_array(&self, $Type, usz size, usz end_padding = 0)
{
destroy_temp_allocators();
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size]!!;
}
<*
Call this to destroy any memory used by the temp allocators. This will invalidate all temp memory.
*>
fn void destroy_temp_allocators()
macro Allocator.new_zero_array_checked(&self, $Type, usz size, usz end_padding = 0)
{
if (!thread_temp_allocator) return;
temp_allocator_pair[0].destroy();
temp_allocator_pair[1].destroy();
temp_allocator_pair[..] = null;
thread_temp_allocator = null;
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size];
}
fn TempAllocator* temp_allocator_next() @private
macro Allocator.new(&self, $Type, usz end_padding = 0) @nodiscard
{
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];
return ($Type*)self.alloc_checked($Type.sizeof + end_padding)!!;
}
const NullAllocator NULL_ALLOCATOR = {};
distinct NullAllocator (Allocator) = uptr;
fn void*! NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
macro Allocator.new_checked(&self, $Type, usz end_padding = 0) @nodiscard
{
return AllocationFailure.OUT_OF_MEMORY?;
return ($Type*)self.alloc_checked($Type.sizeof + end_padding);
}
fn void*! NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
macro Allocator.new_clear(&self, $Type, usz end_padding = 0) @nodiscard
{
return AllocationFailure.OUT_OF_MEMORY?;
return ($Type*)self.calloc_checked($Type.sizeof + end_padding)!!;
}
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
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);
}

View File

@@ -1,268 +0,0 @@
module std::core::cpudetect @if(env::X86 || env::X86_64);
struct CpuId
{
uint eax, ebx, ecx, edx;
}
fn CpuId x86_cpuid(uint eax, uint ecx = 0)
{
int edx;
int ebx;
asm
{
movl $eax, eax;
movl $ecx, ecx;
cpuid;
movl eax, $eax;
movl ebx, $ebx;
movl ecx, $ecx;
movl edx, $edx;
}
return { eax, ebx, ecx, edx };
}
enum X86Feature
{
ADX,
AES,
AMX_AVX512,
AMX_FP8,
AMX_MOVRS,
AMX_TF32,
AMX_TRANSPOSE,
AMX_BF16,
AMX_COMPLEX,
AMX_FP16,
AMX_INT8,
AMX_TILE,
APXF,
AVX,
AVX10_1_256,
AVX10_1_512,
AVX10_2_256,
AVX10_2_512,
AVX2,
AVX5124FMAPS,
AVX5124VNNIW,
AVX512BF16,
AVX512BITALG,
AVX512BW,
AVX512CD,
AVX512DQ,
AVX512ER,
AVX512F,
AVX512FP16,
AVX512IFMA,
AVX512PF,
AVX512VBMI,
AVX512VBMI2,
AVX512VL,
AVX512VNNI,
AVX512VP2INTERSECT,
AVX512VPOPCNTDQ,
AVXIFMA,
AVXNECONVERT,
AVXVNNI,
AVXVNNIINT16,
AVXVNNIINT8,
BMI,
BMI2,
CLDEMOTE,
CLFLUSHOPT,
CLWB,
CLZERO,
CMOV,
CMPCCXADD,
CMPXCHG16B,
CX8,
ENQCMD,
F16C,
FMA,
FMA4,
FSGSBASE,
FXSR,
GFNI,
HRESET,
INVPCID,
KL,
LWP,
LZCNT,
MMX,
MOVBE,
MOVDIR64B,
MOVDIRI,
MOVRS,
MWAITX,
PCLMUL,
PCONFIG,
PKU,
POPCNT,
PREFETCHI,
PREFETCHWT1,
PRFCHW,
PTWRITE,
RAOINT,
RDPID,
RDPRU,
RDRND,
RDSEED,
RTM,
SAHF,
SERIALIZE,
SGX,
SHA,
SHA512,
SHSTK,
SM3,
SM4,
SSE,
SSE2,
SSE3,
SSE4_1,
SSE4_2,
SSE4_A,
SSSE3,
TBM,
TSXLDTRK,
UINTR,
USERMSR,
VAES,
VPCLMULQDQ,
WAITPKG,
WBNOINVD,
WIDEKL,
X87,
XOP,
XSAVE,
XSAVEC,
XSAVEOPT,
XSAVES,
}
uint128 x86_features;
fn void add_feature_if_bit(X86Feature feature, uint register, int bit)
{
if (register & 1U << bit) x86_features |= 1u128 << feature.ordinal;
}
fn void x86_initialize_cpu_features()
{
uint max_level = x86_cpuid(0).eax;
CpuId feat = x86_cpuid(1);
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : CpuId {};
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : CpuId {};
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : CpuId {};
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : CpuId {};
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : CpuId {};
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : CpuId {};
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : CpuId {};
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : CpuId {};
add_feature_if_bit(ADX, leaf7.ebx, 19);
add_feature_if_bit(AES, feat.ecx, 25);
add_feature_if_bit(AMX_BF16, leaf7.edx, 22);
add_feature_if_bit(AMX_COMPLEX, leaf7s1.edx, 8);
add_feature_if_bit(AMX_FP16, leaf7s1.eax, 21);
add_feature_if_bit(AMX_INT8, leaf7.edx, 25);
add_feature_if_bit(AMX_TILE, leaf7.edx, 24);
add_feature_if_bit(APXF, leaf7s1.edx, 21);
add_feature_if_bit(AVX, feat.ecx, 28);
add_feature_if_bit(AVX10_1_256, leaf7s1.edx, 19);
add_feature_if_bit(AVX10_1_512, leaf_24.ebx, 18);
add_feature_if_bit(AVX2, leaf7.ebx, 5);
add_feature_if_bit(AVX5124FMAPS, leaf7.edx, 3);
add_feature_if_bit(AVX5124VNNIW, leaf7.edx, 2);
add_feature_if_bit(AVX512BF16, leaf7s1.eax, 5);
add_feature_if_bit(AVX512BITALG, leaf7.ecx, 12);
add_feature_if_bit(AVX512BW, leaf7.ebx, 30);
add_feature_if_bit(AVX512CD, leaf7.ebx, 28);
add_feature_if_bit(AVX512DQ, leaf7.ebx, 17);
add_feature_if_bit(AVX512ER, leaf7.ebx, 27);
add_feature_if_bit(AVX512F, leaf7.ebx, 16);
add_feature_if_bit(AVX512FP16, leaf7.edx, 23);
add_feature_if_bit(AVX512IFMA, leaf7.ebx, 21);
add_feature_if_bit(AVX512PF, leaf7.ebx, 26);
add_feature_if_bit(AVX512VBMI, leaf7.ecx, 1);
add_feature_if_bit(AVX512VBMI2, leaf7.ecx, 6);
add_feature_if_bit(AVX512VL, leaf7.ebx, 31);
add_feature_if_bit(AVX512VNNI, leaf7.ecx, 11);
add_feature_if_bit(AVX512VP2INTERSECT, leaf7.edx, 8);
add_feature_if_bit(AVX512VPOPCNTDQ, leaf7.ecx, 14);
add_feature_if_bit(AVXIFMA, leaf7s1.eax, 23);
add_feature_if_bit(AVXNECONVERT, leaf7s1.edx, 5);
add_feature_if_bit(AVXVNNI, leaf7s1.eax, 4);
add_feature_if_bit(AVXVNNIINT16, leaf7s1.edx, 10);
add_feature_if_bit(AVXVNNIINT8, leaf7s1.edx, 4);
add_feature_if_bit(BMI, leaf7.ebx, 3);
add_feature_if_bit(BMI2, leaf7.ebx, 8);
add_feature_if_bit(CLDEMOTE, leaf7.ecx, 25);
add_feature_if_bit(CLFLUSHOPT, leaf7.ebx, 23);
add_feature_if_bit(CLWB, leaf7.ebx, 24);
add_feature_if_bit(CLZERO, ext8.ecx, 0);
add_feature_if_bit(CMOV, feat.edx, 15);
add_feature_if_bit(CMPCCXADD, leaf7s1.eax, 7);
add_feature_if_bit(CMPXCHG16B, feat.ecx, 12);
add_feature_if_bit(CX8, feat.edx, 8);
add_feature_if_bit(ENQCMD, leaf7.ecx, 29);
add_feature_if_bit(F16C, feat.ecx, 29);
add_feature_if_bit(FMA, feat.ecx, 12);
add_feature_if_bit(FMA4, ext1.ecx, 16);
add_feature_if_bit(FSGSBASE, leaf7.ebx, 0);
add_feature_if_bit(FXSR, feat.edx, 24);
add_feature_if_bit(GFNI, leaf7.ecx, 8);
add_feature_if_bit(HRESET, leaf7s1.eax, 22);
add_feature_if_bit(INVPCID, leaf7.ebx, 10);
add_feature_if_bit(KL, leaf7.ecx, 23);
add_feature_if_bit(LWP, ext1.ecx, 15);
add_feature_if_bit(LZCNT, ext1.ecx, 5);
add_feature_if_bit(MMX, feat.edx, 23);
add_feature_if_bit(MOVBE, feat.ecx, 22);
add_feature_if_bit(MOVDIR64B, leaf7.ecx, 28);
add_feature_if_bit(MOVDIRI, leaf7.ecx, 27);
add_feature_if_bit(MWAITX, ext1.ecx, 29);
add_feature_if_bit(PCLMUL, feat.ecx, 1);
add_feature_if_bit(PCONFIG, leaf7.edx, 18);
add_feature_if_bit(PKU, leaf7.ecx, 4);
add_feature_if_bit(POPCNT, feat.ecx, 23);
add_feature_if_bit(PREFETCHI, leaf7s1.edx, 14);
add_feature_if_bit(PREFETCHWT1, leaf7.ecx, 0);
add_feature_if_bit(PRFCHW, ext1.ecx, 8);
add_feature_if_bit(PTWRITE, leaf_14.ebx, 4);
add_feature_if_bit(RAOINT, leaf7s1.eax, 3);
add_feature_if_bit(RDPID, leaf7.ecx, 22);
add_feature_if_bit(RDPRU, ext8.ecx, 4);
add_feature_if_bit(RDRND, feat.ecx, 30);
add_feature_if_bit(RDSEED, leaf7.ebx, 18);
add_feature_if_bit(RTM, leaf7.ebx, 11);
add_feature_if_bit(SAHF, ext1.ecx, 0);
add_feature_if_bit(SERIALIZE, leaf7.edx, 14);
add_feature_if_bit(SGX, leaf7.ebx, 2);
add_feature_if_bit(SHA, leaf7.ebx, 29);
add_feature_if_bit(SHA512, leaf7s1.eax, 0);
add_feature_if_bit(SHSTK, leaf7.ecx, 7);
add_feature_if_bit(SM3, leaf7s1.eax, 1);
add_feature_if_bit(SM4, leaf7s1.eax, 2);
add_feature_if_bit(SSE, feat.edx, 25);
add_feature_if_bit(SSE2, feat.edx, 26);
add_feature_if_bit(SSE3, feat.ecx, 0);
add_feature_if_bit(SSE4_1, feat.ecx, 19);
add_feature_if_bit(SSE4_2, feat.ecx, 20);
add_feature_if_bit(SSE4_A, ext1.ecx, 6);
add_feature_if_bit(SSSE3, feat.ecx, 9);
add_feature_if_bit(TBM, ext1.ecx, 21);
add_feature_if_bit(TSXLDTRK, leaf7.edx, 16);
add_feature_if_bit(UINTR, leaf7.edx, 5);
add_feature_if_bit(USERMSR, leaf7s1.edx, 15);
add_feature_if_bit(VAES, leaf7.ecx, 9);
add_feature_if_bit(VPCLMULQDQ, leaf7.ecx, 10);
add_feature_if_bit(WAITPKG, leaf7.ecx, 5);
add_feature_if_bit(WBNOINVD, ext8.ecx, 9);
add_feature_if_bit(WIDEKL, leaf_19.ebx, 2);
add_feature_if_bit(X87, feat.edx, 0);
add_feature_if_bit(XOP, ext1.ecx, 11);
add_feature_if_bit(XSAVE, feat.ecx, 26);
add_feature_if_bit(XSAVEC, leaf_d.eax, 1);
add_feature_if_bit(XSAVEOPT, leaf_d.eax, 0);
add_feature_if_bit(XSAVES, leaf_d.eax, 3);
}

View File

@@ -1,254 +0,0 @@
module std::core::machoruntime @if(env::DARWIN) @private;
struct SegmentCommand64
{
uint cmd;
uint cmdsize;
char[16] segname;
ulong vmaddr;
ulong vmsize;
ulong fileoff;
ulong filesize;
uint maxprot;
uint initprot;
uint nsects;
uint flags;
}
struct LoadCommand
{
uint cmd;
uint cmdsize;
}
struct Section64
{
char[16] sectname;
char[16] segname;
ulong addr;
ulong size;
uint offset;
uint align;
uint reloff;
uint nreloc;
uint flags;
uint reserved1;
uint reserved2;
uint reserved3;
}
struct MachHeader
{
uint magic;
uint cputype;
uint cpusubtype;
uint filetype;
uint ncmds;
uint sizeofcmds;
uint flags;
}
struct MachHeader64
{
inline MachHeader header;
uint reserved;
}
const LC_SEGMENT_64 = 0x19;
fault MachoSearch
{
NOT_FOUND
}
fn bool name_cmp(char* a, char[16]* b)
{
for (usz i = 0; i < 16; i++)
{
if (a[i] != (*b)[i]) return false;
if (a[i] == '\0') return true;
}
return false;
}
fn SegmentCommand64*! find_segment(MachHeader* header, char* segname)
{
LoadCommand* command = (void*)header + MachHeader64.sizeof;
for (uint i = 0; i < header.ncmds; i++)
{
if (command.cmd == LC_SEGMENT_64)
{
SegmentCommand64* segment = (SegmentCommand64*)command;
if (name_cmp(segname, &segment.segname)) return segment;
}
command = (void*)command + command.cmdsize;
}
return MachoSearch.NOT_FOUND?;
}
fn Section64*! find_section(SegmentCommand64* command, char* sectname)
{
Section64* section = (void*)command + SegmentCommand64.sizeof;
for (uint i = 0; i < command.nsects; i++)
{
if (name_cmp(sectname, &section.sectname)) return section;
section++;
}
return MachoSearch.NOT_FOUND?;
}
macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type)
{
Section64*! section = find_section(find_segment(header, segname), sectname);
if (catch section)
{
return $Type[] {};
}
$Type* ptr = (void*)header + section.offset;
return ptr[:section.size / $Type.sizeof];
}
def DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide);
extern fn void _dyld_register_func_for_add_image(DyldCallback);
struct DlInfo
{
char* dli_fname;
void* dli_fbase;
char* dli_sname;
void* dli_saddr;
}
extern fn void printf(char*, ...);
extern fn int dladdr(MachHeader* mh, DlInfo* dlinfo);
extern fn void* realloc(void* ptr, usz size);
extern fn void* malloc(usz size);
extern fn void free(void* ptr);
def CallbackFn = fn void();
struct Callback
{
uint priority;
CallbackFn xtor;
Callback* next;
}
struct DynamicMethod
{
void* fn_ptr;
char* sel;
union
{
DynamicMethod* next;
TypeId* type;
}
}
enum StartupState
{
NOT_STARTED,
INIT,
RUN_CTORS,
READ_DYLIB,
RUN_DYLIB_CTORS,
RUN_DTORS,
SHUTDOWN
}
StartupState runtime_state = NOT_STARTED;
Callback* ctor_first;
Callback* dtor_first;
fn void runtime_startup() @public @export("__c3_runtime_startup")
{
if (runtime_state != NOT_STARTED) return;
runtime_state = INIT;
_dyld_register_func_for_add_image(&dl_reg_callback);
assert(runtime_state == INIT);
runtime_state = RUN_CTORS;
Callback* ctor = ctor_first;
while (ctor)
{
ctor.xtor();
ctor = ctor.next;
}
assert(runtime_state == RUN_CTORS);
runtime_state = READ_DYLIB;
ctor_first = null;
}
fn void runtime_finalize() @public @export("__c3_runtime_finalize")
{
if (runtime_state != READ_DYLIB) return;
runtime_state = RUN_DTORS;
Callback* dtor = dtor_first;
while (dtor)
{
dtor.xtor();
dtor = dtor.next;
}
assert(runtime_state == RUN_DTORS);
runtime_state = SHUTDOWN;
}
fn void append_xxlizer(Callback** ref, Callback* cb)
{
while (Callback* current = *ref, current)
{
if (current.priority > cb.priority)
{
cb.next = current;
break;
}
ref = &current.next;
}
*ref = cb;
}
struct TypeId
{
char type;
TypeId* parentof;
DynamicMethod* dtable;
usz sizeof;
TypeId* inner;
usz len;
typeid[*] additional;
}
fn void dl_reg_callback(MachHeader* mh, isz vmaddr_slide)
{
usz size = 0;
assert(runtime_state == INIT || runtime_state == READ_DYLIB, "State was %s", runtime_state);
foreach (&dm : find_segment_section_body(mh, "__DATA", "__c3_dynamic", DynamicMethod))
{
TypeId* type = dm.type;
dm.next = type.dtable;
type.dtable = dm;
DynamicMethod* m = dm;
while (m)
{
m = m.next;
}
}
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3dtor", Callback))
{
append_xxlizer(&dtor_first, cb);
}
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3ctor", Callback))
{
append_xxlizer(&ctor_first, cb);
}
if (runtime_state != READ_DYLIB) return;
runtime_state = RUN_DYLIB_CTORS;
Callback* ctor = ctor_first;
ctor_first = null;
while (ctor)
{
ctor.xtor();
ctor = ctor.next;
}
assert(runtime_state == RUN_DYLIB_CTORS);
runtime_state = READ_DYLIB;
}

View File

@@ -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 = mem::alloc_array(String, argc);
String[] list = mem::new_array(String, argc);
for (int i = 0; i < argc; i++)
{
char* arg = argv[i];
@@ -68,7 +68,7 @@ macro String[] win_command_line_to_strings(ushort* cmd_line) @private
macro String[] wargs_strings(int argc, Char16** argv) @private
{
String[] list = mem::alloc_array(String, argc);
String[] list = mem::new_array(String, argc);
for (int i = 0; i < argc; i++)
{
Char16* arg = argv[i];
@@ -84,19 +84,19 @@ macro void release_wargs(String[] list) @private
free(list.ptr);
}
macro int @win_to_err_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_err_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd)
{
if (catch #m()) return 1;
return 0;
}
macro int @win_to_int_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd) => #m();
macro int @win_to_void_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_int_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) => #m();
macro int @win_to_void_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd)
{
#m();
return 0;
}
macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_err_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
@@ -104,14 +104,14 @@ macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd
return 0;
}
macro int @win_to_int_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_int_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return #m(args);
}
macro int @win_to_void_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_void_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
@@ -119,26 +119,26 @@ macro int @win_to_void_main_args(#m, void* handle, void* prev_handle, Char16* cm
return 0;
}
macro int @win_to_err_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_err_main(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
if (catch #m(handle, prev_handle, args, show_cmd)) return 1;
if (catch #m(handle, args, show_cmd)) return 1;
return 0;
}
macro int @win_to_int_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_int_main(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return #m(handle, prev_handle, args, show_cmd);
return #m(handle, args, show_cmd);
}
macro int @win_to_void_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @win_to_void_main(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
#m(handle, prev_handle, args, show_cmd);
#m(handle, args, show_cmd);
return 0;
}

View File

@@ -2,21 +2,15 @@
// 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, std::time, std::io, std::sort;
import libc;
struct ReflectedParam (Printable) @if(!$defined(ReflectedParam))
{
String name;
typeid type;
}
struct AnyRaw
struct AnyStruct
{
void* ptr;
typeid type;
}
struct SliceRaw
struct SubArrayStruct
{
void* ptr;
usz len;
@@ -30,11 +24,11 @@ struct BenchmarkUnit
BenchmarkFn func;
}
fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator = allocator::heap())
fn BenchmarkUnit[] benchmark_collection_create(Allocator* allocator = mem::heap())
{
BenchmarkFn[] fns = $$BENCHMARK_FNS;
String[] names = $$BENCHMARK_NAMES;
BenchmarkUnit[] benchmarks = allocator::alloc_array(allocator, BenchmarkUnit, names.len);
BenchmarkUnit[] benchmarks = allocator.new_array(BenchmarkUnit, names.len);
foreach (i, benchmark : fns)
{
benchmarks[i] = { names[i], fns[i] };
@@ -136,7 +130,7 @@ fn bool default_benchmark_runner()
{
@pool()
{
return run_benchmarks(benchmark_collection_create(allocator::temp()));
return run_benchmarks(benchmark_collection_create(mem::temp()));
};
}
@@ -148,11 +142,11 @@ struct TestUnit
TestFn func;
}
fn TestUnit[] test_collection_create(Allocator allocator = allocator::heap())
fn TestUnit[] test_collection_create(Allocator* allocator = mem::heap())
{
TestFn[] fns = $$TEST_FNS;
String[] names = $$TEST_NAMES;
TestUnit[] tests = allocator::alloc_array(allocator, TestUnit, names.len);
TestUnit[] tests = allocator.new_array(TestUnit, names.len);
foreach (i, test : fns)
{
tests[i] = { names[i], fns[i] };
@@ -221,7 +215,6 @@ fn bool run_tests(TestUnit[] tests)
name.appendf("Testing %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
(void)io::stdout().flush();
if (libc::setjmp(&context.buf) == 0)
{
if (catch err = unit.func())
@@ -243,7 +236,7 @@ fn bool default_test_runner()
{
@pool()
{
return run_tests(test_collection_create(allocator::temp()));
return run_tests(test_collection_create(mem::temp()));
};
}

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
module std::core::string;
import std::ascii;
import std::io;
distinct String @if(!$defined(String)) = inline char[];
distinct ZString = inline char*;
distinct WString = inline Char16*;
def Char32 = uint;
@@ -33,89 +31,35 @@ fault NumberConversion
FLOAT_OUT_OF_RANGE,
}
<*
Return a temporary ZString created using the formatting function.
@param [in] fmt `The formatting string`
*>
fn ZString tformat_zstr(String fmt, args...)
macro String tformat(String fmt, ...)
{
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
str.appendf(fmt, ...args);
return str.zstr_view();
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.str_view();
}
<*
Return a new String created using the formatting function.
@param [inout] allocator `The allocator to use`
@param [in] fmt `The formatting string`
*>
fn String format(String fmt, args..., Allocator allocator)
macro String new_format(String fmt, ..., Allocator* allocator = mem::heap())
{
@pool(allocator)
{
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
str.appendf(fmt, ...args);
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.copy_str(allocator);
};
}
<*
Return a heap allocated String created using the formatting function.
@param [in] fmt `The formatting string`
*>
fn String new_format(String fmt, args..., Allocator allocator = null) => format(fmt, ...args, allocator: allocator ?: allocator::heap());
<*
Return a temporary String created using the formatting function.
@param [in] fmt `The formatting string`
*>
fn String tformat(String fmt, args...)
{
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
str.appendf(fmt, ...args);
return str.str_view();
}
<*
Return a new ZString created using the formatting function.
@param [in] fmt `The formatting string`
@param [inout] allocator `The allocator to use`
*>
fn ZString new_format_zstr(String fmt, args..., Allocator allocator = allocator::heap())
{
@pool(allocator)
{
DString str = dstring::temp_with_capacity(fmt.len + args.len * 8);
str.appendf(fmt, ...args);
return str.copy_zstr(allocator);
};
}
<*
Check if a character is in a set.
@param c `the character to check`
@param [in] set `The formatting string`
@pure
@return `True if a character is in the set`
*>
macro bool char_in_set(char c, String set)
{
foreach (ch : set) if (ch == c) return true;
return false;
}
fn String join_new(String[] s, String joiner, Allocator allocator = allocator::heap())
fn String join_new(String[] s, String joiner, Allocator* allocator = mem::heap())
{
if (!s)
{
return (String)allocator::new_array(allocator, char, 2)[:0];
return (String)allocator.new_zero_array(char, 2)[:0];
}
usz total_size = joiner.len * s.len;
@@ -136,14 +80,10 @@ fn String join_new(String[] s, String joiner, Allocator allocator = allocator::h
};
}
<*
Remove characters from the front and end of a string.
@param [in] string `The string to trim`
@param [in] to_trim `The set of characters to trim, defaults to whitespace`
@pure
@return `a substring of the string passed in`
*>
/**
* @param [in] string
* @param [in] to_trim
**/
fn String String.trim(string, String to_trim = "\t\n\r ")
{
usz start = 0;
@@ -155,14 +95,10 @@ fn String String.trim(string, String to_trim = "\t\n\r ")
return string[start..end];
}
<*
Check if the String starts with the needle.
@param [in] string
@param [in] needle
@pure
@return `'true' if the string starts with the needle`
*>
/**
* @param [in] string
* @param [in] needle
**/
fn bool String.starts_with(string, String needle)
{
if (needle.len > string.len) return false;
@@ -170,14 +106,10 @@ fn bool String.starts_with(string, String needle)
return string[:needle.len] == needle;
}
<*
Check if the String ends with the needle.
@param [in] string
@param [in] needle
@pure
@return `'true' if the string ends with the needle`
*>
/**
* @param [in] string
* @param [in] needle
**/
fn bool String.ends_with(string, String needle)
{
if (needle.len > string.len) return false;
@@ -185,28 +117,24 @@ fn bool String.ends_with(string, String needle)
return string[^needle.len..] == needle;
}
<*
Strip the front of the string if the prefix exists.
@param [in] string
@param [in] needle
@pure
@return `the substring with the prefix removed`
*>
/**
* Strip the front of the string if the prefix exists.
*
* @param [in] string
* @param [in] needle
**/
fn String String.strip(string, String needle)
{
if (!needle.len || !string.starts_with(needle)) return string;
return string[needle.len..];
}
<*
Strip the end of the string if the suffix exists.
@param [in] string
@param [in] needle
@pure
@return `the substring with the suffix removed`
*>
/**
* Strip the end of the string if the suffix exists.
*
* @param [in] string
* @param [in] needle
**/
fn String String.strip_end(string, String needle)
{
if (!needle.len || !string.ends_with(needle)) return string;
@@ -214,21 +142,22 @@ fn String String.strip_end(string, String needle)
return string[:(string.len - needle.len)];
}
<*
Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
@param [in] s
@param [in] needle
@param [&inout] allocator "The allocator to use for the String[]"
@param max "Max number of elements, 0 means no limit, defaults to 0"
@require needle.len > 0 "The needle must be at least 1 character long"
@ensure return.len > 0
*>
fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = allocator::heap())
/**
* Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
*
* @param [in] s
* @param [in] needle
* @param [&inout] allocator "The allocator, defaults to the heap allocator"
* @param max "Max number of elements, 0 means no limit, defaults to 0"
* @require needle.len > 0 "The needle must be at least 1 character long"
* @ensure return.len > 0
**/
fn String[] String.split(s, String needle, usz max = 0, Allocator* allocator = mem::heap())
{
usz capacity = 16;
usz i = 0;
String* holder = allocator::alloc_array(allocator, String, capacity);
String* holder = allocator.new_array(String, capacity);
bool no_more = false;
while (!no_more)
{
@@ -247,58 +176,40 @@ fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = al
if (i == capacity)
{
capacity *= 2;
holder = allocator::realloc(allocator, holder, String.sizeof * capacity);
holder = allocator.realloc(holder, String.sizeof * capacity);
}
holder[i++] = res;
}
return holder[:i];
}
<*
Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }, using the heap allocator
to store the parts.
/**
* This function is identical to String.split, but implicitly uses the
* temporary allocator.
*
* @param [in] s
* @param [in] needle
* @param max "Max number of elements, 0 means no limit, defaults to 0"
**/
fn String[] String.tsplit(s, String needle, usz max = 0)
{
return s.split(needle, max, mem::temp()) @inline;
}
@param [in] s
@param [in] needle
@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.new_split(s, String needle, usz max = 0) => s.split(needle, max, allocator::heap()) @inline;
<*
This function is identical to String.split, but implicitly uses the
temporary allocator.
@param [in] s
@param [in] needle
@param max "Max number of elements, 0 means no limit, defaults to 0"
*>
fn String[] String.tsplit(s, String needle, usz max = 0) => s.split(needle, max, allocator::temp()) @inline;
<*
Check if a substring is found in the string.
@param [in] s
@param [in] needle "The string to look for."
@pure
@return "true if the string contains the substring, false otherwise"
*>
fn bool String.contains(s, String needle)
{
return @ok(s.index_of(needle));
}
<*
Find the index of the first incidence of a string.
@param [in] s
@param needle "The character to look for"
@pure
@ensure return < s.len
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found"
*>
/**
* Find the index of the first incidence of a string.
*
* @param [in] s
* @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)
@@ -308,38 +219,15 @@ fn usz! String.index_of_char(s, char needle)
return SearchResult.MISSING?;
}
<*
Find the index of the first incidence of a character.
@param [in] s
@param needle "The character to look for"
@param start_index "The index to start with, may exceed max index."
@pure
@ensure return < s.len
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found starting from the start_index"
*>
fn usz! String.index_of_char_from(s, char needle, usz start_index)
{
usz len = s.len;
if (len <= start_index) return SearchResult.MISSING?;
for (usz i = start_index; i < len; i++)
{
if (s[i] == needle) return i;
}
return SearchResult.MISSING?;
}
<*
Find the index of the first incidence of a character starting from the end.
@param [in] s
@param needle "the character to find"
@pure
@ensure return < s.len
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found"
*>
/**
* Find the index of the first incidence of a 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)
@@ -349,17 +237,17 @@ fn usz! String.rindex_of_char(s, char needle)
return SearchResult.MISSING?;
}
<*
Find the index of the first incidence of a string.
@param [in] s
@param [in] needle
@pure
@ensure return < s.len
@require needle.len > 0 : "The needle must be len 1 or more"
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found"
*>
/**
* Find the index of the first incidence of a string.
*
* @param [in] s
* @param [in] needle
* @pure
* @ensure return < s.len
* @require needle.len > 0 : "The needle must be len 1 or more"
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.index_of(s, String needle)
{
usz needed = needle.len;
@@ -374,17 +262,17 @@ fn usz! String.index_of(s, String needle)
return SearchResult.MISSING?;
}
<*
Find the index of the last incidence of a string.
@param [in] s
@param [in] needle
@pure
@ensure return < s.len
@require needle.len > 0 "The needle must be len 1 or more"
@return "the index of the needle"
@return! SearchResult.MISSING "if the needle cannot be found"
*>
/**
* Find the index of the last incidence of a string.
*
* @param [in] s
* @param [in] needle
* @pure
* @ensure return < s.len
* @require needle.len > 0 "The needle must be len 1 or more"
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.rindex_of(s, String needle)
{
usz needed = needle.len;
@@ -424,19 +312,19 @@ fn usz ZString.len(str)
}
fn ZString String.zstr_copy(s, Allocator allocator = allocator::heap())
fn ZString String.zstr_copy(s, Allocator* allocator = mem::heap())
{
usz len = s.len;
char* str = allocator::malloc(allocator, len + 1);
char* str = allocator.alloc(len + 1);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (ZString)str;
}
fn String String.concat(s1, String s2, Allocator allocator = allocator::heap())
fn String String.concat(s1, String s2, Allocator* allocator = mem::heap())
{
usz full_len = s1.len + s2.len;
char* str = allocator::malloc(allocator, full_len + 1);
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);
@@ -444,169 +332,144 @@ fn String String.concat(s1, String s2, Allocator allocator = allocator::heap())
return (String)str[:full_len];
}
fn String String.tconcat(s1, String s2) => s1.concat(s2, allocator::temp());
fn String String.tconcat(s1, String s2) => s1.concat(s2, mem::temp());
fn ZString String.zstr_tcopy(s) => s.zstr_copy(allocator::temp()) @inline;
fn ZString String.zstr_tcopy(s) => s.zstr_copy(mem::temp()) @inline;
fn String String.copy(s, Allocator allocator = allocator::heap())
fn String String.copy(s, Allocator* allocator = mem::heap())
{
usz len = s.len;
char* str = allocator::malloc(allocator, len + 1);
char* str = allocator.alloc(len + 1);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (String)str[:len];
}
fn void String.free(&s, Allocator allocator = allocator::heap())
fn void String.free(&s, Allocator* allocator = mem::heap())
{
if (!s.len) return;
allocator::free(allocator, s.ptr);
allocator.free(s.ptr);
*s = "";
}
fn String String.tcopy(s) => s.copy(allocator::temp()) @inline;
fn String String.tcopy(s) => s.copy(mem::temp()) @inline;
fn String ZString.copy(z, Allocator allocator = allocator::heap())
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(allocator::temp()) @inline;
return z.str_view().copy(mem::temp()) @inline;
}
<*
Convert an UTF-8 string to UTF-16
@return "The UTF-16 string as a slice, allocated using the given allocator"
@return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
@return! AllocationFailure "If allocation of the string fails"
*>
fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::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_new_utf16(s, Allocator* allocator = mem::heap())
{
usz len16 = conv::utf16len_for_utf8(s);
Char16* data = allocator::alloc_array_try(allocator, Char16, len16 + 1)!;
Char16* data = allocator.new_array_checked(Char16, len16 + 1)!;
conv::utf8to16_unsafe(s, data)!;
data[len16] = 0;
return data[:len16];
}
<*
Convert an UTF-8 string to UTF-16
@return "The UTF-16 string as a slice, allocated using the given allocator"
@return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
@return! AllocationFailure "If allocation of the string fails"
*>
/**
* Convert an UTF-8 string to UTF-16
* @return "The UTF-16 string as a slice, allocated using the given allocator"
* @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
* @return! AllocationFailure "If allocation of the string fails"
**/
fn Char16[]! String.to_temp_utf16(s)
{
return s.to_new_utf16(allocator::temp());
return s.to_new_utf16(mem::temp());
}
fn WString! String.to_wstring(s, Allocator allocator)
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) => s.to_wstring(allocator::temp());
fn WString! String.to_new_wstring(s) => s.to_wstring(allocator::heap());
fn WString! String.to_temp_wstring(s)
{
return (WString)s.to_temp_utf16().ptr;
}
fn Char32[]! String.to_utf32(s, Allocator allocator)
fn Char32[]! String.to_new_utf32(s, Allocator* allocator = mem::heap())
{
usz codepoints = conv::utf8_codepoints(s);
Char32* data = allocator::alloc_array_try(allocator, Char32, codepoints + 1)!;
Char32* data = allocator.new_array(Char32, codepoints + 1);
conv::utf8to32_unsafe(s, data)!;
data[codepoints] = 0;
return data[:codepoints];
}
fn Char32[]! String.to_new_utf32(s) => s.to_utf32(allocator::heap()) @inline;
fn Char32[]! String.to_temp_utf32(s) => s.to_utf32(allocator::temp()) @inline;
<*
Convert a string to ASCII lower case.
@param [inout] s
@pure
*>
fn void String.convert_ascii_to_lower(s)
fn Char32[]! String.to_temp_utf32(s)
{
foreach (&c : s) if (c.is_upper() @pure) *c += 'a' - 'A';
return s.to_new_utf32(mem::temp());
}
fn String String.new_ascii_to_lower(s, Allocator allocator = allocator::heap())
fn void String.convert_ascii_to_lower(s)
{
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 String String.temp_ascii_to_lower(s)
fn String String.temp_ascii_to_lower(s, Allocator* allocator = mem::heap())
{
return s.new_ascii_to_lower(allocator::temp());
return s.new_ascii_to_lower(mem::temp());
}
<*
Convert a string to ASCII upper case.
@param [inout] s
@pure
*>
fn void String.convert_ascii_to_upper(s)
{
foreach (&c : s) if (c.is_lower() @pure) *c -= 'a' - 'A';
foreach (&c : s) if (c.is_lower()) *c -= 'a' - 'A';
}
<*
Returns a string converted to ASCII upper case.
@param [in] s
@param [inout] allocator
@return `a new String converted to ASCII upper case.`
*>
fn String String.new_ascii_to_upper(s, Allocator allocator = allocator::heap())
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 StringIterator String.iterator(s)
{
return { s, 0 };
}
<*
@param [in] s
@return `a temporary String converted to ASCII upper case.`
*>
fn String String.temp_ascii_to_upper(s)
{
return s.new_ascii_to_upper(allocator::temp());
return s.new_ascii_to_upper(mem::temp());
}
fn String! new_from_utf32(Char32[] utf32, Allocator allocator = allocator::heap())
fn String! new_from_utf32(Char32[] utf32, Allocator* allocator = mem::heap())
{
usz len = conv::utf8len_for_utf32(utf32);
char* data = allocator::malloc_try(allocator, len + 1)!;
defer catch allocator::free(allocator, data);
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! new_from_utf16(Char16[] utf16, Allocator allocator = allocator::heap())
fn String! new_from_utf16(Char16[] utf16, Allocator* allocator = mem::heap())
{
usz len = conv::utf8len_for_utf16(utf16);
char* data = allocator::malloc_try(allocator, len + 1)!;
defer catch allocator::free(allocator, data);
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! new_from_wstring(WString wstring, Allocator allocator = allocator::heap())
fn String! new_from_wstring(WString wstring, Allocator* allocator = mem::heap())
{
usz utf16_len;
while (wstring[utf16_len] != 0) utf16_len++;
@@ -614,8 +477,8 @@ fn String! new_from_wstring(WString wstring, Allocator allocator = allocator::he
return new_from_utf16(utf16, allocator);
}
fn String! temp_from_wstring(WString wstring) => new_from_wstring(wstring, allocator::temp()) @inline;
fn String! temp_from_utf16(Char16[] utf16) => new_from_utf16(utf16, allocator::temp()) @inline;
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)
{
@@ -627,11 +490,7 @@ fn usz String.utf8_codepoints(s)
return len;
}
<*
@require (base <= 10 && base > 1) || base == 16 : "Unsupported base"
*>
macro String.to_integer(string, $Type, int base = 10)
macro String.to_integer(string, $Type)
{
usz len = string.len;
usz index = 0;
@@ -651,8 +510,8 @@ macro String.to_integer(string, $Type, int base = 10)
break;
}
if (len == index) return NumberConversion.MALFORMED_INTEGER?;
$Type base_used = ($Type)base;
if (string[index] == '0' && base == 10)
$Type base = 10;
if (string[index] == '0')
{
index++;
if (index == len) return ($Type)0;
@@ -660,15 +519,15 @@ macro String.to_integer(string, $Type, int base = 10)
{
case 'x':
case 'X':
base_used = 16;
base = 16;
index++;
case 'b':
case 'B':
base_used = 2;
base = 2;
index++;
case 'o':
case 'O':
base_used = 8;
base = 8;
index++;
default:
break;
@@ -680,21 +539,21 @@ macro String.to_integer(string, $Type, int base = 10)
{
char c = {|
char ch = string[index++];
if (base_used != 16 || ch < 'A') return (char)(ch - '0');
if (ch <= 'F') return (char)(ch - 'A' + 10);
if (base != 16 || ch < 'A') return (char)(ch - '0');
if (ch <= 'F') return (char)(ch - 'A');
if (ch < 'a') return NumberConversion.MALFORMED_INTEGER?;
if (ch > 'f') return NumberConversion.MALFORMED_INTEGER?;
return (char)(ch - 'a' + 10);
return (char)(ch - 'a');
|}!;
if (c >= base_used) return NumberConversion.MALFORMED_INTEGER?;
if (c >= base) return NumberConversion.MALFORMED_INTEGER?;
value = {|
if (is_negative)
{
$Type new_value = value * base_used - c;
$Type new_value = value * base - c;
if (new_value > value) return NumberConversion.INTEGER_OVERFLOW?;
return new_value;
}
$Type new_value = value * base_used + c;
$Type new_value = value * base + c;
if (new_value < value) return NumberConversion.INTEGER_OVERFLOW?;
return new_value;
|}!;
@@ -702,63 +561,17 @@ macro String.to_integer(string, $Type, int base = 10)
return value;
}
fn int128! String.to_int128(s, int base = 10) => s.to_integer(int128, base);
fn long! String.to_long(s, int base = 10) => s.to_integer(long, base);
fn int! String.to_int(s, int base = 10) => s.to_integer(int, base);
fn short! String.to_short(s, int base = 10) => s.to_integer(short, base);
fn ichar! String.to_ichar(s, int base = 10) => s.to_integer(ichar, base);
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 uint128! String.to_uint128(s, int base = 10) => s.to_integer(uint128, base);
fn ulong! String.to_ulong(s, int base = 10) => s.to_integer(ulong, base);
fn uint! String.to_uint(s, int base = 10) => s.to_integer(uint, base);
fn ushort! String.to_ushort(s, int base = 10) => s.to_integer(ushort, base);
fn char! String.to_uchar(s, int base = 10) => s.to_integer(char, base);
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 double! String.to_double(s) => s.to_real(double);
fn float! String.to_float(s) => s.to_real(float);
fn Splitter String.splitter(self, String split)
{
return Splitter { self, split, 0 };
}
struct Splitter
{
String string;
String split;
usz current;
}
fn void Splitter.reset(&self)
{
self.current = 0;
}
fn String! Splitter.next(&self)
{
usz len = self.string.len;
usz current = self.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
String remaining = self.string[current..];
usz! next = remaining.index_of(self.split);
if (try next)
{
defer self.current = current + next + self.split.len;
return remaining[:next];
}
self.current = len;
return remaining;
}
macro String new_struct_to_str(x, Allocator allocator = allocator::heap())
{
DString s;
@stack_mem(512; Allocator mem)
{
s.new_init(allocator: mem);
io::fprint(&s, x)!!;
};
return s.copy_str(allocator);
}
macro String temp_struct_to_str(x) => new_struct_to_str(x, allocator::temp());

View File

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

View File

@@ -31,9 +31,9 @@ const MASK = KMAX - 1;
const B1B_DIG = 2;
const uint[2] B1B_MAX = { 9007199, 254740991 };
<*
@require chars.len > 0
*>
/**
* @require chars.len > 0
**/
macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
{
uint[KMAX] x;
@@ -266,7 +266,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
y *= sign;
bool denormal;
// Limit precision for denormal results
/* Limit precision for denormal results */
uint bits = $bits;
if (bits > math::DOUBLE_MANT_DIG + e2 - $emin)
{

View File

@@ -8,10 +8,10 @@ fault ConversionResult
VALUE_OUT_OF_RANGE,
VALUE_OUT_OF_UNSIGNED_RANGE,
}
<*
@require $Type.kindof.is_int() || $Type.kindof == TypeKind.ENUM "Argument was not an integer"
*>
macro any_to_int(any v, $Type)
/**
* @require $Type.kindof.is_int() || $Type.kindof == TypeKind.ENUM "Argument was not an integer"
**/
macro any_to_int(any* v, $Type)
{
typeid any_type = v.type;
TypeKind kind = any_type.kindof;
@@ -108,10 +108,10 @@ fn bool TypeKind.is_int(kind) @inline
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
}
macro bool is_slice_convertable($Type)
macro bool is_subarray_convertable($Type)
{
$switch ($Type.kindof)
$case SLICE:
$case SUBARRAY:
return true;
$case POINTER:
return $Type.inner.kindof == TypeKind.ARRAY;
@@ -120,51 +120,10 @@ macro bool is_slice_convertable($Type)
$endswitch
}
macro bool is_bool($Type) @const => $Type.kindof == TypeKind.BOOL;
macro bool is_int($Type) @const => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT;
macro bool is_bool($Type) => $Type.kindof == TypeKind.BOOL;
macro bool is_int($Type) => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT;
<*
@require is_numerical($Type) "Expected a numerical type"
*>
macro bool is_signed($Type) @const
{
$switch (inner_kind($Type))
$case SIGNED_INT:
$case FLOAT:
return true;
$case VECTOR:
return is_signed($typefrom($Type.inner));
$default:
return false;
$endswitch
}
<*
@require is_numerical($Type) "Expected a numerical type"
*>
macro bool is_unsigned($Type) @const
{
$switch (inner_kind($Type))
$case UNSIGNED_INT:
return true;
$case VECTOR:
return is_unsigned($typefrom($Type.inner));
$default:
return false;
$endswitch
}
macro bool is_indexable($Type) @const
{
return $defined($Type{}[0]);
}
macro bool is_ref_indexable($Type) @const
{
return $defined(&$Type{}[0]);
}
macro bool is_intlike($Type) @const
macro bool is_intlike($Type)
{
$switch ($Type.kindof)
$case SIGNED_INT:
@@ -177,7 +136,7 @@ macro bool is_intlike($Type) @const
$endswitch
}
macro bool is_underlying_int($Type) @const
macro bool is_underlying_int($Type)
{
$switch ($Type.kindof)
$case SIGNED_INT:
@@ -190,9 +149,9 @@ macro bool is_underlying_int($Type) @const
$endswitch
}
macro bool is_float($Type) @const => $Type.kindof == TypeKind.FLOAT;
macro bool is_float($Type) => $Type.kindof == TypeKind.FLOAT;
macro bool is_floatlike($Type) @const
macro bool is_floatlike($Type)
{
$switch ($Type.kindof)
$case FLOAT:
@@ -204,45 +163,40 @@ macro bool is_floatlike($Type) @const
$endswitch
}
macro bool is_vector($Type) @const
macro bool is_vector($Type)
{
return $Type.kindof == TypeKind.VECTOR;
}
macro typeid inner_type($Type) @const
macro TypeKind inner_kind($Type)
{
$if $Type.kindof == TypeKind.DISTINCT:
return inner_type($typefrom($Type.inner));
return inner_kind($typefrom($Type.inner));
$else
return $Type.typeid;
return $Type.kindof;
$endif
}
macro TypeKind inner_kind($Type) @const
{
return inner_type($Type).kindof;
}
macro bool is_same($TypeA, $TypeB) @const
macro bool is_same($TypeA, $TypeB)
{
return $TypeA.typeid == $TypeB.typeid;
}
macro bool @has_same(#a, #b, ...) @const
macro bool @has_same(#a, #b, ...)
{
var $type_a = @typeid(#a);
$if $type_a != @typeid(#b):
return false;
$endif
$for (var $i = 0; $i < $vacount; $i++)
$if @typeid($vaexpr[$i]) != $type_a:
$if @typeid($vaexpr($i)) != $type_a:
return false;
$endif
$endfor
return true;
}
macro bool may_load_atomic($Type) @const
macro bool may_load_atomic($Type)
{
$switch ($Type.kindof)
$case SIGNED_INT:
@@ -257,12 +211,12 @@ macro bool may_load_atomic($Type) @const
$endswitch
}
macro lower_to_atomic_compatible_type($Type) @const
macro lower_to_atomic_compatible_type($Type)
{
$switch ($Type.kindof)
$case SIGNED_INT:
$case SIGNED_INT:
$case UNSIGNED_INT:
return $Type.typeid;
return $Type.typeid;
$case DISTINCT:
return lower_to_atomic_compatible_type($Type.inner);
$case FLOAT:
@@ -283,10 +237,10 @@ macro lower_to_atomic_compatible_type($Type) @const
$endswitch
}
macro bool is_promotable_to_floatlike($Type) @const => types::is_floatlike($Type) || types::is_int($Type);
macro bool is_promotable_to_float($Type) @const => types::is_float($Type) || types::is_int($Type);
macro bool is_promotable_to_floatlike($Type) => types::is_floatlike($Type) || types::is_int($Type);
macro bool is_promotable_to_float($Type) => types::is_float($Type) || types::is_int($Type);
macro bool is_same_vector_type($Type1, $Type2) @const
macro bool is_same_vector_type($Type1, $Type2)
{
$if $Type1.kindof != TypeKind.VECTOR:
return $Type2.kindof != TypeKind.VECTOR;
@@ -295,7 +249,7 @@ macro bool is_same_vector_type($Type1, $Type2) @const
$endif
}
macro bool is_equatable_type($Type) @const
macro bool is_equatable_type($Type)
{
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):
return true;
@@ -304,34 +258,20 @@ macro bool is_equatable_type($Type) @const
$endif
}
<*
Checks if a type implements the copy protocol.
*>
macro bool implements_copy($Type) @const
/**
* Checks if a type implements the copy protocol.
**/
macro bool implements_copy($Type)
{
return $defined($Type.copy) && $defined($Type.free);
}
macro bool is_equatable_value(value) @deprecated
macro bool is_equatable_value(value)
{
return is_equatable_type($typeof(value));
}
macro bool @equatable_value(#value) @const
{
return is_equatable_type($typeof(#value));
}
macro bool @comparable_value(#value) @const
{
$if $defined(#value.less) || $defined(#value.compare_to):
return true;
$else
return $typeof(#value).is_ordered;
$endif
}
macro bool is_comparable_value(value) @deprecated
macro bool is_comparable_value(value)
{
$if $defined(value.less) || $defined(value.compare_to):
return true;
@@ -358,11 +298,10 @@ enum TypeKind : char
FUNC,
OPTIONAL,
ARRAY,
SLICE,
SUBARRAY,
VECTOR,
DISTINCT,
POINTER,
INTERFACE,
}
struct TypeEnum

View File

@@ -1,21 +1,21 @@
module std::core::values;
macro typeid @typeid(#value) @const @builtin => $typeof(#value).typeid;
macro TypeKind @typekind(#value) @const @builtin => $typeof(#value).kindof;
macro bool @typeis(#value, $Type) @const @builtin => $typeof(#value).typeid == $Type.typeid;
<*
Return true if two values have the same type before any conversions.
*>
macro bool @is_same_type(#value1, #value2) @const => $typeof(#value1).typeid == $typeof(#value2).typeid;
macro bool @is_bool(#value) @const => types::is_bool($typeof(#value));
macro bool @is_int(#value) @const => types::is_int($typeof(#value));
macro bool @is_floatlike(#value) @const => types::is_floatlike($typeof(#value));
macro bool @is_float(#value) @const => types::is_float($typeof(#value));
macro bool @is_promotable_to_floatlike(#value) @const => types::is_promotable_to_floatlike($typeof(#value));
macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_float($typeof(#value));
macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2));
macro typeid @typeid(#value) @builtin => $typeof(#value).typeid;
macro TypeKind @typekind(#value) @builtin => $typeof(#value).kindof;
macro bool @typeis(#value, $Type) @builtin => $typeof(#value).typeid == $Type.typeid;
/**
* Return true if two values have the same type before any conversions.
**/
macro bool @is_same_type(#value1, #value2) => $typeof(#value1).typeid == $typeof(#value2).typeid;
macro bool @is_bool(#value) => types::is_bool($typeof(#value));
macro bool @is_int(#value) => types::is_int($typeof(#value));
macro bool @is_floatlike(#value) => types::is_floatlike($typeof(#value));
macro bool @is_float(#value) => types::is_float($typeof(#value));
macro bool @is_promotable_to_floatlike(#value) => types::is_promotable_to_floatlike($typeof(#value));
macro bool @is_promotable_to_float(#value) => types::is_promotable_to_float($typeof(#value));
macro bool @is_vector(#value) => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) => $assignable(#value1, $typeof(#value2));
macro promote_int(x)
{
@@ -26,21 +26,5 @@ macro promote_int(x)
$endif
}
macro promote_int_same(x, y)
{
$if @is_int(x):
$switch
$case @is_vector(y) &&& $typeof(y).inner == float.typeid:
return (float)x;
$case $typeof(y).typeid == float.typeid:
return (float)x;
$default:
return (double)x;
$endswitch
$else
return x;
$endif
}
macro TypeKind @inner_kind(#value) @const => types::inner_kind($typeof(#value));
macro TypeKind @inner_kind(#value) => types::inner_kind($typeof(#value));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,29 +5,13 @@
// Implementation was off Steve Reid's SHA-1 C implementation
module std::hash::sha1;
import std::hash::hmac;
import std::bits;
const BLOCK_BYTES = 64;
const HASH_BYTES = 20;
struct Sha1
{
uint[5] state;
uint[2] count;
char[BLOCK_BYTES] buffer;
}
def HmacSha1 = Hmac(<Sha1, HASH_BYTES, BLOCK_BYTES>);
def hmac = hmac::hash(<Sha1, HASH_BYTES, BLOCK_BYTES>);
def pbkdf2 = hmac::pbkdf2(<Sha1, HASH_BYTES, BLOCK_BYTES>);
fn char[HASH_BYTES] hash(char[] data)
{
Sha1 sha1 @noinit;
sha1.init();
sha1.update(data);
return sha1.final();
char[64] buffer;
}
fn void Sha1.init(&self)
@@ -44,10 +28,10 @@ fn void Sha1.init(&self)
};
}
<*
@param [in] data
@require data.len <= uint.max
*>
/**
* @param [in] data
* @require data.len <= uint.max
**/
fn void Sha1.update(&self, char[] data)
{
uint j = self.count[0];
@@ -71,7 +55,7 @@ fn void Sha1.update(&self, char[] data)
}
fn char[HASH_BYTES] Sha1.final(&self)
fn char[20] Sha1.final(&self)
{
char[8] finalcount;
for (uint i = 0; i < 8; i++)
@@ -85,21 +69,21 @@ fn char[HASH_BYTES] Sha1.final(&self)
}
self.update(&finalcount);
char[HASH_BYTES] digest;
for (uint i = 0; i < HASH_BYTES; i++)
char[20] digest;
for (uint i = 0; i < 20; i++)
{
digest[i] = (char)((self.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF);
}
// Clear mem
*self = {};
mem::clear(self, Sha1.sizeof);
finalcount = {};
return digest;
}
union Long16 @local
{
char[BLOCK_BYTES] c;
char[64] c;
uint[16] l;
}
@@ -154,10 +138,10 @@ macro @r4(&block, v, &wref, x, y, &z, i) @local
*wref = w.rotl(30);
}
<*
@param [&inout] state
@param [&in] buffer
*>
/**
* @param [&inout] state
* @param [&in] buffer
**/
fn void sha1_transform(uint* state, char* buffer) @local
{
Long16 block;

View File

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

View File

@@ -2,12 +2,12 @@ module std::io;
struct BitReader
{
InStream reader;
InStream* reader;
uint bits;
uint len;
}
fn void BitReader.init(&self, InStream byte_reader)
fn void BitReader.init(&self, InStream* byte_reader)
{
*self = { .reader = byte_reader };
}
@@ -17,10 +17,10 @@ fn void BitReader.clear(&self) @inline
self.len = 0;
}
<*
@require nbits <= 8
@require self.len + nbits <= uint.sizeof * 8
*>
/**
* @require nbits <= 8
* @require self.len + nbits <= uint.sizeof * 8
**/
fn char! BitReader.read_bits(&self, uint nbits)
{
uint bits = self.bits;
@@ -40,12 +40,12 @@ fn char! BitReader.read_bits(&self, uint nbits)
struct BitWriter
{
OutStream writer;
OutStream* writer;
uint bits;
uint len;
}
fn void BitWriter.init(&self, OutStream byte_writer)
fn void BitWriter.init(&self, OutStream* byte_writer)
{
*self = { .writer = byte_writer };
}
@@ -61,9 +61,9 @@ fn void! BitWriter.flush(&self)
self.len = 0;
}
<*
@require nbits <= 8
*>
/**
* @require nbits <= 8
**/
fn void! BitWriter.write_bits(&self, uint bits, uint nbits)
{
if (nbits == 0) return;

View File

@@ -7,7 +7,7 @@ struct File (InStream, OutStream)
}
module std::io::file;
import libc, std::io::path, std::io::os;
import libc;
fn File! open(String filename, String mode)
{
@@ -37,17 +37,17 @@ fn usz! get_size(String path)
fn void! delete(String filename) => os::native_remove(filename) @inline;
<*
@require self.file != null
*>
/**
* @require self.file != null
**/
fn void! File.reopen(&self, String filename, String mode)
{
self.file = os::native_freopen(self.file, filename, mode)!;
}
<*
@require self.file != null
*>
/**
* @require self.file != null
**/
fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
{
os::native_fseek(self.file, offset, seek_mode)!;
@@ -57,9 +57,9 @@ fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
/*
Implement later
<*
@require self.file == null
*>
/**
* @require self.file == null
**/
fn void! File.memopen(File* file, char[] data, String mode)
{
@pool()
@@ -71,17 +71,17 @@ fn void! File.memopen(File* file, char[] data, String mode)
*/
<*
@require self.file != null
*>
/**
* @require self.file != null
*/
fn void! File.write_byte(&self, char c) @dynamic
{
return os::native_fputc(c, self.file);
if (!libc::fputc(c, self.file)) return IoError.EOF?;
}
<*
@param [&inout] self
*>
/**
* @param [&inout] self
*/
fn void! File.close(&self) @inline @dynamic
{
if (self.file && libc::fclose(self.file))
@@ -105,26 +105,26 @@ fn void! File.close(&self) @inline @dynamic
self.file = null;
}
<*
@require self.file
*>
/**
* @require self.file
*/
fn bool File.eof(&self) @inline
{
return libc::feof(self.file) != 0;
}
<*
@param [in] buffer
*>
/**
* @param [in] buffer
*/
fn usz! File.read(&self, char[] buffer) @dynamic
{
return os::native_fread(self.file, buffer);
}
<*
@param [out] buffer
@require self.file `File must be initialized`
*>
/**
* @param [out] buffer
* @require self.file `File must be initialized`
*/
fn usz! File.write(&self, char[] buffer) @dynamic
{
return os::native_fwrite(self.file, buffer);
@@ -138,64 +138,9 @@ fn char! File.read_byte(&self) @dynamic
return (char)c;
}
<*
Load up to buffer.len characters. Returns IoError.OVERFLOW if the file is longer
than the buffer.
@param filename "The path to the file to read"
@param [in] buffer "The buffer to read to"
*>
fn char[]! load_buffer(String filename, char[] buffer)
{
File file = open(filename, "rb")!;
defer (void)file.close();
usz len = file.seek(0, END)!;
if (len > buffer.len) return IoError.OVERFLOW?;
file.seek(0, SET)!;
usz read = 0;
while (read < len)
{
read += file.read(buffer[read:len - read])!;
}
return buffer[:len];
}
fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
{
File file = open(filename, "rb")!;
defer (void)file.close();
usz len = file.seek(0, END)!;
file.seek(0, SET)!;
char* data = allocator::malloc_try(allocator, len)!;
defer catch allocator::free(allocator, data);
usz read = 0;
while (read < len)
{
read += file.read(data[read:len - read])!;
}
return data[:len];
}
fn char[]! load_temp(String filename)
{
return load_new(filename, allocator::temp());
}
fn void! save(String filename, char[] data)
{
File file = open(filename, "wb")!;
defer (void)file.close();
while (data.len)
{
usz written = file.write(data)!;
data = data[written..];
}
}
<*
@require self.file `File must be initialized`
*>
/**
* @require self.file `File must be initialized`
*/
fn void! File.flush(&self) @dynamic
{
libc::fflush(self.file);

View File

@@ -6,8 +6,7 @@ const int PRINTF_NTOA_BUFFER_SIZE = 256;
interface Printable
{
fn String to_string(Allocator allocator) @optional;
fn String to_new_string(Allocator allocator) @optional @deprecated("Use to_string");
fn String to_new_string(Allocator *allocator) @optional;
fn usz! to_format(Formatter* formatter) @optional;
}
@@ -15,53 +14,23 @@ fault PrintFault
{
BUFFER_EXCEEDED,
INTERNAL_BUFFER_EXCEEDED,
INVALID_FORMAT,
NOT_ENOUGH_ARGUMENTS,
INVALID_ARGUMENT,
INVALID_FORMAT_STRING,
MISSING_ARG,
INVALID_ARGUMENT_TYPE,
}
fault FormattingFault
{
UNTERMINATED_FORMAT,
MISSING_ARG,
INVALID_WIDTH_ARG,
INVALID_FORMAT_TYPE,
}
def OutputFn = fn void!(void* buffer, char c);
def FloatType = double;
macro bool is_struct_with_default_print($Type)
{
return $Type.kindof == STRUCT
&&& !$defined($Type.to_format)
&&& !$defined($Type.to_new_string)
&&& !$defined($Type.to_string);
}
<*
Introspect a struct and print it to a formatter
@require @typekind(value) == STRUCT `This macro is only valid on macros`
*>
macro usz! struct_to_format(value, Formatter* f, bool $force_dump)
{
var $Type = $typeof(value);
usz total = f.print("{ ")!;
$foreach ($i, $member : $Type.membersof)
$if $i > 0:
total += f.print(", ")!;
$endif
$if $member.nameof != "":
total += f.printf("%s: ", $member.nameof)!;
$endif
$if ($force_dump &&& $member.typeid.kindof == STRUCT) |||
is_struct_with_default_print($typefrom($member.typeid)):
total += struct_to_format($member.get(value), f, $force_dump)!;
$else
total += f.printf("%s", $member.get(value))!;
$endif
$endforeach
return total + f.print(" }");
}
fn usz! ReflectedParam.to_format(&self, Formatter* f) @dynamic
{
return f.printf("[Parameter '%s']", self.name);
}
fn usz! Formatter.printf(&self, String format, args...)
{
@@ -78,7 +47,6 @@ struct Formatter
uint width;
uint prec;
usz idx;
anyfault first_fault;
}
}
@@ -100,16 +68,11 @@ fn void Formatter.init(&self, OutputFn out_fn, void* data = null)
fn usz! Formatter.out(&self, char c) @private
{
if (catch err = self.out_fn(self.data, c))
{
if (self.first_fault) return self.first_fault?;
self.first_fault = err;
return err?;
}
self.out_fn(self.data, c)!;
return 1;
}
fn usz! Formatter.print_with_function(&self, Printable arg)
fn usz! Formatter.print_with_function(&self, Printable* arg)
{
if (&arg.to_format)
{
@@ -122,10 +85,9 @@ fn usz! Formatter.print_with_function(&self, Printable arg)
self.width = old_width;
self.prec = old_prec;
}
if (!arg) return self.out_substr("(null)");
return arg.to_format(self);
}
if (&arg.to_string)
if (&arg.to_new_string)
{
PrintFlags old = self.flags;
uint old_width = self.width;
@@ -136,17 +98,16 @@ fn usz! Formatter.print_with_function(&self, Printable arg)
self.width = old_width;
self.prec = old_prec;
}
if (!arg) return self.out_substr("(null)");
@stack_mem(1024; Allocator mem)
@pool()
{
return self.out_substr(arg.to_string(mem));
return self.out_substr(arg.to_new_string(mem::temp()));
};
}
return SearchResult.MISSING?;
}
fn usz! Formatter.out_str(&self, any arg) @private
fn usz! Formatter.out_str(&self, any* arg) @private
{
switch (arg.type.kindof)
{
@@ -157,9 +118,8 @@ fn usz! Formatter.out_str(&self, any arg) @private
case ANYFAULT:
case FAULT:
return self.out_substr((*(anyfault*)arg.ptr).nameof);
case INTERFACE:
case ANY:
return self.out_str(*(any*)arg);
return self.out_str(*(any**)arg);
case OPTIONAL:
unreachable();
case SIGNED_INT:
@@ -173,7 +133,7 @@ fn usz! Formatter.out_str(&self, any arg) @private
}
self.flags = {};
self.width = 0;
return self.ntoa_any(arg, 10) ?? self.out_substr("<INVALID>");
return self.ntoa_any(arg, 10);
case FLOAT:
PrintFlags flags = self.flags;
uint width = self.width;
@@ -184,14 +144,23 @@ fn usz! Formatter.out_str(&self, any arg) @private
}
self.flags = {};
self.width = 0;
return self.ftoa(float_from_any(arg)) ?? self.out_substr("ERR");
return self.ftoa(float_from_any(arg)!!);
case BOOL:
return self.out_substr(*(bool*)arg.ptr ? "true" : "false");
default:
}
usz! n = self.print_with_function((Printable)arg);
if (try n) return n;
if (@catch(n) != SearchResult.MISSING) n!;
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:
@@ -207,29 +176,16 @@ fn usz! Formatter.out_str(&self, any arg) @private
case FUNC:
return self.out_substr("<function>");
case DISTINCT:
if (arg.type == String.typeid)
{
return self.out_substr(*(String*)arg);
}
if (arg.type == ZString.typeid)
{
return self.out_substr(*(ZString*)arg ? ((ZString*)arg).str_view() : "(null)");
return self.out_substr(((ZString*)arg).str_view());
}
if (arg.type == DString.typeid)
{
return self.out_substr(*(DString*)arg ? ((DString*)arg).str_view() : "(null)");
return self.out_substr(((DString*)arg).str_view());
}
return self.out_str(arg.as_inner());
case POINTER:
typeid inner = arg.type.inner;
void** pointer = arg.ptr;
if (arg.type.inner != void.typeid)
{
any deref = any_make(*pointer, inner);
n = self.print_with_function((Printable)deref);
if (try n) return n;
if (@catch(n) != SearchResult.MISSING) n!;
}
PrintFlags flags = self.flags;
uint width = self.width;
defer
@@ -237,8 +193,8 @@ fn usz! Formatter.out_str(&self, any arg) @private
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
self.out_substr("0x")!;
return self.ntoa_any(arg, 16);
case ARRAY:
// this is SomeType[*] so grab the "SomeType"
@@ -290,9 +246,13 @@ fn usz! Formatter.out_str(&self, any arg) @private
}
len += self.out_substr(">]")!;
return len;
case SLICE:
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;
@@ -317,9 +277,6 @@ fn usz! Formatter.out_str(&self, any arg) @private
}
len += self.out(']')!;
return len;
case ANY:
case INTERFACE:
unreachable("Already handled");
default:
}
return self.out_substr("Invalid type");
@@ -332,31 +289,10 @@ fn void! out_null_fn(void* data @unused, char c @unused) @private
{
}
macro usz! @report_fault(Formatter* f, $fault)
{
(void)f.out_substr($fault);
return PrintFault.INVALID_FORMAT?;
}
macro usz! @wrap_bad(Formatter* f, #action)
{
usz! len = #action;
if (catch err = len)
{
case PrintFault.BUFFER_EXCEEDED:
case PrintFault.INTERNAL_BUFFER_EXCEEDED:
return f.first_err(err)?;
default:
err = f.first_err(PrintFault.INVALID_ARGUMENT);
f.out_substr("<INVALID>")!;
return err?;
}
return len;
}
fn usz! Formatter.vprintf(&self, String format, any[] anys)
fn usz! Formatter.vprintf(&self, String format, any*[] anys)
{
self.first_fault = {};
if (!self.out_fn)
{
// use null output function
@@ -376,7 +312,7 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
continue;
}
i++;
if (i >= format_len) return @report_fault(self, "%ERR");
if (i >= format_len) return PrintFault.INVALID_FORMAT_STRING?;
c = format[i];
if (c == '%')
{
@@ -396,12 +332,11 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
case '#': self.flags.hash = true;
default: break FLAG_EVAL;
}
if (++i >= format_len) return @report_fault(self, "%ERR");
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING?;
c = format[i];
}
// evaluate width field
int! w = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
if (catch w) return @report_fault(self, "%ERR");
int w = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i)!;
c = format[i];
if (w < 0)
{
@@ -414,22 +349,16 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
if (c == '.')
{
self.flags.precision = true;
if (++i >= format_len) return @report_fault(self, "<BAD FORMAT>");
int! prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
if (catch prec) return @report_fault(self, "<BAD FORMAT>");
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING?;
int prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i)!;
self.prec = prec < 0 ? 0 : prec;
c = format[i];
}
// evaluate specifier
uint base = 0;
if (variant_index >= anys.len)
{
self.first_err(PrintFault.NOT_ENOUGH_ARGUMENTS);
total_len += self.out_substr("<MISSING>")!;
continue;
}
any current = anys[variant_index++];
if (variant_index >= anys.len) return PrintFault.MISSING_ARG?;
any* current = anys[variant_index++];
switch (c)
{
case 'd':
@@ -454,25 +383,25 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
self.flags.uppercase = true;
nextcase;
case 'a':
total_len += @wrap_bad(self, self.atoa(float_from_any(current)))!;
total_len += self.atoa(float_from_any(current)!!)!;
continue;
case 'F' :
self.flags.uppercase = true;
nextcase;
case 'f':
total_len += @wrap_bad(self, self.ftoa(float_from_any(current)))!;
total_len += self.ftoa(float_from_any(current)!!)!;
continue;
case 'E':
self.flags.uppercase = true;
nextcase;
case 'e':
total_len += @wrap_bad(self, self.etoa(float_from_any(current)))!;
total_len += self.etoa(float_from_any(current)!!)!;
continue;
case 'G':
self.flags.uppercase = true;
nextcase;
case 'g':
total_len += @wrap_bad(self, self.gtoa(float_from_any(current)))!;
total_len += self.gtoa(float_from_any(current)!!)!;
continue;
case 'c':
total_len += self.out_char(current)!;
@@ -497,9 +426,7 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
self.flags.hash = true;
base = 16;
default:
self.first_err(PrintFault.INVALID_FORMAT);
total_len += self.out_substr("<BAD FORMAT>")!;
continue;
return PrintFault.INVALID_FORMAT_STRING?;
}
if (base != 10)
{
@@ -510,13 +437,14 @@ fn usz! Formatter.vprintf(&self, String format, any[] anys)
if (self.flags.precision) self.flags.zeropad = false;
bool is_neg;
total_len += @wrap_bad(self, self.ntoa(int_from_any(current, &is_neg), is_neg, base))!;
uint128 v = int_from_any(current, &is_neg)!!;
total_len += self.ntoa(v, is_neg, base)!;
}
// termination
// out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
// return written chars without terminating \0
if (self.first_fault) return self.first_fault?;
return total_len;
}
@@ -530,4 +458,4 @@ fn usz! Formatter.print(&self, String str)
}
foreach (c : str) self.out(c)!;
return self.idx;
}
}

View File

@@ -1,28 +1,15 @@
module std::io;
import std::math;
const char[16] XDIGITS_H = "0123456789ABCDEF";
const char[16] XDIGITS_L = "0123456789abcdef";
fault FormattingFault
{
BAD_FORMAT
}
macro Formatter.first_err(&self, anyfault f)
{
if (self.first_fault) return self.first_fault;
self.first_fault = f;
return f;
}
fn usz! Formatter.adjust(&self, usz len) @local
{
if (!self.flags.left) return 0;
return self.pad(' ', self.width, len);
}
fn uint128! int_from_any(any arg, bool *is_neg) @private
fn uint128! int_from_any(any* arg, bool *is_neg) @private
{
switch (arg.type.kindof)
{
@@ -33,7 +20,6 @@ fn uint128! int_from_any(any arg, bool *is_neg) @private
case TypeKind.ENUM:
return int_from_any(arg.as_inner(), is_neg);
default:
break;
}
*is_neg = false;
switch (arg)
@@ -72,15 +58,18 @@ fn uint128! int_from_any(any arg, bool *is_neg) @private
double d = *arg;
return (uint128)((*is_neg = d < 0) ? -d : d);
default:
return FormattingFault.BAD_FORMAT?;
return PrintFault.INVALID_ARGUMENT_TYPE?;
}
}
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);
$endif
$if env::F16_SUPPORT:
if (arg.type == float16.typeid) return *((float16*)arg.ptr);
$endif
if (arg.type.kindof == TypeKind.DISTINCT)
{
return float_from_any(arg.as_inner());
@@ -114,19 +103,19 @@ fn FloatType! float_from_any(any arg) @private
case double:
return (FloatType)*arg;
default:
return FormattingFault.BAD_FORMAT?;
return PrintFault.INVALID_ARGUMENT_TYPE?;
}
}
<*
Read a simple integer value, typically for formatting.
@param [inout] len_ptr "the length remaining."
@param [in] buf "the buf to read from."
@param maxlen "the maximum len that can be read."
@return "The result of the atoi."
*>
/**
* Read a simple integer value, typically for formatting.
*
* @param [inout] len_ptr "the length remaining."
* @param [in] buf "the buf to read from."
* @param maxlen "the maximum len that can be read."
* @return "The result of the atoi."
**/
fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
{
uint i = 0;
@@ -214,7 +203,7 @@ fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
// Add padding
if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
String s = self.flags.uppercase ? "INF" : "inf";
if (math::is_nan(y)) s = self.flags.uppercase ? "NAN" : "nan";
if (y != y) s = self.flags.uppercase ? "NAN" : "nan";
len += s.len;
if (pl) len += self.out(is_neg ? '-' : '+')!;
len += self.out_chars(s)!;
@@ -598,13 +587,14 @@ fn usz! Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
}
fn usz! Formatter.ntoa_any(&self, any arg, uint base) @private
fn usz! Formatter.ntoa_any(&self, any* arg, uint base) @private
{
bool is_neg;
return self.ntoa(int_from_any(arg, &is_neg)!!, is_neg, base) @inline;
uint128 val = int_from_any(arg, &is_neg)!!;
return self.ntoa(val, is_neg, base) @inline;
}
fn usz! Formatter.out_char(&self, any arg) @private
fn usz! Formatter.out_char(&self, any* arg) @private
{
usz len = 1;
uint l = 1;
@@ -640,31 +630,40 @@ fn usz! Formatter.out_reverse(&self, char[] buf) @private
usz buffer_start_idx = self.idx;
usz len = buf.len;
// pad spaces up to given width
if (!self.flags.zeropad && !self.flags.left)
if (!self.flags.zeropad)
{
n += self.pad(' ', self.width, len)!;
n += self.adjust(len)!;
}
// reverse string
while (len) n += self.out(buf[--len])!;
// append pad spaces up to given width
n += self.adjust(n)!;
n += self.adjust(self.idx - buffer_start_idx)!;
return n;
}
fn void! printf_advance_format(usz format_len, usz *index_ptr) @inline @private
{
usz val = ++(*index_ptr);
if (val >= format_len) return FormattingFault.UNTERMINATED_FORMAT?;
}
fn any*! next_any(any** args_ptr, usz args_len, usz* arg_index_ptr) @inline @private
{
if (*arg_index_ptr >= args_len) return FormattingFault.MISSING_ARG?;
return args_ptr[(*arg_index_ptr)++];
}
fn int! printf_parse_format_field(
any* args_ptr, usz args_len, usz* args_index_ptr,
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.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr);
if (c != '*') return 0;
usz len = ++(*index_ptr);
if (len >= format_len) return FormattingFault.BAD_FORMAT?;
if (*args_index_ptr >= args_len) return FormattingFault.BAD_FORMAT?;
any val = args_ptr[(*args_index_ptr)++];
if (!val.type.kindof.is_int()) return FormattingFault.BAD_FORMAT?;
printf_advance_format(format_len, index_ptr)!;
any* val = next_any(args_ptr, args_len, args_index_ptr)!;
if (!val.type.kindof.is_int()) return FormattingFault.INVALID_WIDTH_ARG?;
uint! intval = types::any_to_int(val, int);
return intval ?? FormattingFault.BAD_FORMAT?;
return intval ?? FormattingFault.INVALID_WIDTH_ARG?;
}

View File

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

View File

@@ -1,5 +1,5 @@
module std::io::os;
import std::io::path, libc, std::os;
import libc;
macro void! native_chdir(Path path)
{

View File

@@ -1,10 +1,10 @@
module std::io::os @if(env::LIBC);
import libc;
<*
@require mode.len > 0
@require filename.len > 0
*>
/**
* @require mode.len > 0
* @require filename.len > 0
**/
fn void*! native_fopen(String filename, String mode) @inline
{
@pool()
@@ -41,10 +41,10 @@ fn void! native_remove(String filename)
};
}
<*
@require mode.len > 0
@require filename.len > 0
*>
/**
* @require mode.len > 0
* @require filename.len > 0
**/
fn void*! native_freopen(void* file, String filename, String mode) @inline
{
@pool()
@@ -75,11 +75,6 @@ fn usz! native_fwrite(CFile file, char[] buffer) @inline
return libc::fwrite(buffer.ptr, 1, buffer.len, file);
}
fn void! native_fputc(CInt c, CFile stream) @inline
{
if (!libc::fputc(c, stream)) return IoError.EOF?;
}
fn usz! native_fread(CFile file, char[] buffer) @inline
{
return libc::fread(buffer.ptr, 1, buffer.len, file);

View File

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

View File

@@ -1,11 +1,11 @@
module std::io::os;
import libc, std::os, std::io;
import libc;
fn void! native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX || env::BSD_FAMILY)
fn void! native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX)
{
@pool()
{
$if env::DARWIN || env::LINUX || env::BSD_FAMILY:
$if env::DARWIN || env::LINUX:
int res = libc::stat(path.zstr_tcopy(), stat);
$else
unreachable("Stat unimplemented");
@@ -71,9 +71,6 @@ fn bool native_file_or_dir_exists(String path)
{
$switch
$case env::DARWIN:
$case env::FREEBSD:
$case env::NETBSD:
$case env::OPENBSD:
$case env::LINUX:
Stat stat;
return @ok(native_stat(&stat, path));
@@ -96,12 +93,9 @@ fn bool native_is_file(String path)
{
$switch
$case env::DARWIN:
$case env::FREEBSD:
$case env::NETBSD:
$case env::OPENBSD:
$case env::LINUX:
Stat stat;
return @ok(native_stat(&stat, path)) && libc_S_ISTYPE(stat.st_mode, libc::S_IFREG);
return @ok(native_stat(&stat, path)) && stat.st_mode & libc::S_IFREG;
$default:
File! f = file::open(path, "r");
defer (void)f.close();
@@ -111,9 +105,9 @@ fn bool native_is_file(String path)
fn bool native_is_dir(String path)
{
$if env::DARWIN || env::LINUX || env::BSD_FAMILY:
$if env::DARWIN || env::LINUX:
Stat stat;
return @ok(native_stat(&stat, path)) && libc_S_ISTYPE(stat.st_mode, libc::S_IFDIR);
return @ok(native_stat(&stat, path)) && stat.st_mode & libc::S_IFDIR;
$else
return native_file_or_dir_exists(path) && !native_is_file(path);
$endif

View File

@@ -1,7 +1,7 @@
module std::io::os;
import libc, std::os;
import libc;
macro String! getcwd(Allocator allocator = allocator::heap())
macro String! getcwd(Allocator* allocator = mem::heap())
{
$switch
$case env::WIN32:

View File

@@ -1,10 +1,9 @@
module std::io::os @if(env::POSIX);
import std::io, std::os;
module std::io::file::os @if(env::POSIX);
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator* allocator)
{
PathList list;
list.new_init(allocator: allocator);
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)?;
@@ -16,18 +15,17 @@ fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Al
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.push(path);
list.append(path);
}
return list;
}
module std::io::os @if(env::WIN32);
import std::time, std::os, std::io;
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator* allocator)
{
PathList list;
list.new_init(allocator: allocator);
list.init_new(.allocator = allocator);
@pool(allocator)
{
@@ -43,7 +41,7 @@ fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Al
{
String filename = string::temp_from_wstring((WString)&find_data.cFileName)!;
if (filename == ".." || filename == ".") continue;
list.push(path::new(filename, allocator)!);
list.append(path::new(filename, allocator)!);
};
} while (win32::findNextFileW(find, &find_data));
return list;

View File

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

View File

@@ -1,7 +1,6 @@
module std::io::os @if(env::LIBC);
import std::io::path, std::os;
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(!env::WIN32)
fn Path! native_temp_directory(Allocator* allocator = mem::heap()) @if(!env::WIN32)
{
foreach (String env : { "TMPDIR", "TMP", "TEMP", "TEMPDIR" })
{
@@ -11,22 +10,21 @@ fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(!env
return path::new("/tmp", allocator);
}
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(env::WIN32)
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_alloc_array(Char16, len + (usz)1);
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);
import std::io::path;
macro Path! native_temp_directory(Allocator allocator = allocator::heap())
macro Path! native_temp_directory(Allocator* allocator = mem::heap())
{
return IoError.UNSUPPORTED_OPERATION?;
}

View File

@@ -1,6 +1,5 @@
module std::io::path;
import std::collections::list, std::io::os;
import std::os::win32;
import std::collections::list;
const PathEnv DEFAULT_PATH_ENV = env::WIN32 ? PathEnv.WIN32 : PathEnv.POSIX;
const char PREFERRED_SEPARATOR_WIN32 = '\\';
@@ -15,9 +14,7 @@ fault PathResult
NO_PARENT,
}
def Path = PathImp;
struct PathImp (Printable)
struct Path (Printable)
{
String path_string;
PathEnv env;
@@ -29,19 +26,11 @@ enum PathEnv
POSIX
}
fn Path! new_cwd(Allocator allocator = allocator::heap())
fn Path! getcwd(Allocator* allocator = mem::heap())
{
@pool(allocator)
{
return new(os::getcwd(allocator::temp()), allocator);
};
}
fn Path! getcwd(Allocator allocator = allocator::heap()) @deprecated("Use new_cwd()")
{
@pool(allocator)
{
return new(os::getcwd(allocator::temp()), allocator);
return new(os::getcwd(mem::temp()), allocator);
};
}
@@ -49,10 +38,9 @@ fn bool is_dir(Path path) => os::native_is_dir(path.str_view());
fn bool is_file(Path path) => os::native_is_file(path.str_view());
fn usz! file_size(Path path) => os::native_file_size(path.str_view());
fn bool exists(Path path) => os::native_file_or_dir_exists(path.str_view());
fn Path! temp_cwd() => new_cwd(allocator::temp()) @inline;
fn Path! tgetcwd() @deprecated("Use temp_cwd()") => new_cwd(allocator::temp()) @inline;
fn Path! tgetcwd() => getcwd(mem::temp()) @inline;
fn void! chdir(Path path) => os::native_chdir(path) @inline;
fn Path! temp_directory(Allocator allocator = allocator::heap()) => os::native_temp_directory(allocator);
fn 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)
@@ -70,17 +58,7 @@ macro bool is_win32_separator(char c)
return c == '/' || c == '\\';
}
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap()) @deprecated("use new_ls")
{
return new_ls(dir, no_dirs, no_symlinks, mask, allocator);
}
fn PathList! temp_ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "")
{
return new_ls(dir, no_dirs, no_symlinks, mask, allocator::temp()) @inline;
}
fn PathList! new_ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap())
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator* allocator = mem::heap())
{
$if $defined(os::native_ls):
return os::native_ls(dir, no_dirs, no_symlinks, mask, allocator);
@@ -96,13 +74,6 @@ enum MkdirPermissions
USER_AND_ADMIN
}
<*
Create a directory on a given path, optionally recursive.
@param path `The path to create`
@param recursive `If directories in between should be created if they're missing, defaults to false`
@param permissions `The permissions to set on the directory`
*>
fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
@@ -118,22 +89,12 @@ fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions =
return os::native_mkdir(path, permissions);
}
<*
Tries to delete directory, which must be empty.
@param path `The path to delete`
@return `true if there was a directory to delete, false otherwise`
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
fn bool! rmdir(Path path)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
return os::native_rmdir(path);
}
<*
Like [rmdir] but deletes a directory even if it contains items.
*>
fn void! rmtree(Path path)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
@@ -144,40 +105,30 @@ fn void! rmtree(Path path)
$endif
}
<*
Creates a new path.
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
fn Path! new(String path, Allocator allocator = allocator::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
fn Path! new(String path, Allocator* allocator = mem::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
{
return { normalize(path.copy(allocator), path_env), path_env };
}
<*
Creates a new path using the temp allocator.
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
fn Path! temp_new(String path, PathEnv path_env = DEFAULT_PATH_ENV)
{
return new(path, allocator::temp(), path_env);
return new(path, mem::temp(), path_env);
}
fn Path! new_win32_wstring(WString path, Allocator allocator = allocator::heap())
fn Path! new_win32_wstring(WString path, Allocator* allocator = mem::heap())
{
@pool(allocator)
{
return path::new(string::temp_from_wstring(path)!, allocator: allocator);
return path::new(string::temp_from_wstring(path)!, .allocator = allocator);
};
}
fn Path! new_windows(String path, Allocator allocator = allocator::heap())
fn Path! new_windows(String path, Allocator* allocator = mem::heap())
{
return new(path, allocator, WIN32);
}
fn Path! new_posix(String path, Allocator allocator = allocator::heap())
fn Path! new_posix(String path, Allocator* allocator = mem::heap())
{
return new(path, allocator, POSIX);
}
@@ -187,17 +138,12 @@ fn bool Path.equals(self, Path p2)
return self.env == p2.env && self.path_string == p2.path_string;
}
fn Path! Path.append(self, String filename, Allocator allocator = allocator::heap()) @deprecated("Use path.new_append(...)")
{
return self.new_append(filename, allocator) @inline;
}
<*
Append the string to the current path.
@param [in] filename
*>
fn Path! Path.new_append(self, String filename, Allocator allocator = allocator::heap())
/**
* Append the string to the current path.
*
* @param [in] filename
**/
fn Path! Path.append(self, String filename, Allocator* allocator = mem::heap())
{
if (!self.path_string.len) return new(filename, allocator, self.env)!;
assert(!is_separator(self.path_string[^1], self.env));
@@ -212,9 +158,7 @@ fn Path! Path.new_append(self, String filename, Allocator allocator = allocator:
};
}
fn Path! Path.temp_append(self, String filename) => self.new_append(filename, allocator::temp());
fn Path! Path.tappend(self, String filename) @deprecated("Use path.temp_append(...)") => self.new_append(filename, allocator::temp());
fn Path! Path.tappend(self, String filename) => self.append(filename, mem::temp());
fn usz Path.start_of_base_name(self) @local
{
@@ -222,19 +166,7 @@ fn usz Path.start_of_base_name(self) @local
if (!path_str.len) return 0;
if (self.env == PathEnv.WIN32)
{
if (try index = path_str.rindex_of_char('\\'))
{
// c:\ style path, we're done!
if (path_str[0] != '\\') return index + 1;
// Handle \\server\foo
// Find the \ before "foo"
usz last_index = 2 + path_str[2..].index_of_char('\\')!!;
// If they don't match, we're done
assert(last_index <= index, "Invalid normalized, path %d vs %s in %s", last_index, index, path_str);
if (last_index != index) return index + 1;
// Otherwise just default to the volume length.
}
return volume_name_len(path_str, self.env)!!;
return path_str.rindex_of_char('\\') + 1 ?? volume_name_len(path_str, self.env)!!;
}
return path_str.rindex_of_char('/') + 1 ?? 0;
}
@@ -244,44 +176,28 @@ fn bool! Path.is_absolute(self)
String path_str = self.str_view();
if (!path_str.len) return false;
usz path_start = volume_name_len(path_str, self.env)!;
if (path_start > 0 && path_str[0] == '\\') return true;
return path_start < path_str.len && is_separator(path_str[path_start], self.env);
}
fn Path! Path.absolute(self, Allocator allocator = allocator::heap()) @deprecated("Use path.new_absolute()")
{
return self.new_absolute(allocator) @inline;
}
<*
@require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
*>
fn Path! Path.new_absolute(self, Allocator allocator = allocator::heap())
fn Path! Path.absolute(self, Allocator* allocator = mem::heap())
{
String path_str = self.str_view();
if (!path_str.len) return PathResult.INVALID_PATH?;
if (self.is_absolute()!) return new(path_str, allocator, self.env);
if (!path_str.len) path_str = ".";
if (path_str == ".")
{
@pool(allocator)
{
String cwd = os::getcwd(allocator::temp())!;
return new(cwd, allocator, self.env);
};
String cwd = os::getcwd(mem::temp())!;
return new(cwd, allocator, self.env);
}
$if DEFAULT_PATH_ENV == WIN32:
@pool(allocator)
{
const usz BUFFER_LEN = 4096;
WString buffer = (WString)mem::temp_alloc_array(Char16, BUFFER_LEN);
buffer = win32::_wfullpath(buffer, path_str.to_temp_wstring()!, BUFFER_LEN);
if (!buffer) return PathResult.INVALID_PATH?;
return { string::new_from_wstring(buffer, allocator), WIN32 };
};
$else
String cwd = os::getcwd(allocator::temp())!;
return Path { cwd, self.env }.new_append(path_str, allocator)!;
$endif
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)
@@ -292,40 +208,16 @@ fn String Path.basename(self)
return path_str[basename_start..];
}
fn String Path.dirname(self)
{
usz basename_start = self.start_of_base_name();
String path_str = self.path_string;
if (basename_start == 0) return ".";
if (basename_start == 0) return "";
usz start = volume_name_len(path_str, self.env)!!;
if (basename_start <= start + 1)
{
if (self.env == WIN32 && basename_start > start && path_str[0..1] == `\\`)
{
return path_str[:basename_start - 1];
}
return path_str[:basename_start];
}
if (basename_start <= start + 1) return path_str[:basename_start];
return path_str[:basename_start - 1];
}
<*
Test if the path has the given extension, so given the path /foo/bar.c3
this would be true matching the extension "c3"
@param [in] extension `The extension name (not including the leading '.')`
@require extension.len > 0 : `The extension cannot be empty`
@return `true if the extension matches`
*>
fn bool Path.has_extension(self, String extension)
{
String basename = self.basename();
if (basename.len <= extension.len) return false;
if (basename[^extension.len + 1] != '.') return false;
return basename[^extension.len..] == extension;
}
fn String! Path.extension(self)
{
String basename = self.basename();
@@ -356,20 +248,13 @@ fn usz! volume_name_len(String path, PathEnv path_env) @local
while (count < len && path[count] == '\\') count++;
// Not 2 => folded paths
if (count != 2) return 0;
// Check that we have a name followed by '\'
isz base_found = 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))
{
if (base_found) return i;
base_found = i;
continue;
}
if (is_win32_separator(c)) return i;
if (is_reserved_win32_path_char(c)) return PathResult.INVALID_PATH?;
}
if (base_found > 0 && base_found + 1 < len) return len;
return PathResult.INVALID_PATH?;
case 'A'..'Z':
case 'a'..'z':
@@ -379,13 +264,6 @@ fn usz! volume_name_len(String path, PathEnv path_env) @local
}
}
<*
Get the path of the parent. This does not allocate, but returns a slice
of the path itself.
@return `The parent of the path as a non-allocated path`
@return! PathResult.NO_PARENT `if this path does not have a parent`
*>
fn Path! Path.parent(self)
{
if (self.path_string.len == 1 && is_separator(self.path_string[0], self.env)) return PathResult.NO_PARENT?;
@@ -403,10 +281,6 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
{
if (!path_str.len) return "";
usz path_start = volume_name_len(path_str, path_env)!;
if (path_start > 0 && path_env == PathEnv.WIN32)
{
for (usz i = 0; i < path_start; i++) if (path_str[i] == '/') path_str[i] = '\\';
}
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;
@@ -510,9 +384,7 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
len++;
}
if (len > path_start + 1 && is_separator(path_str[len - 1], path_env)) len--;
if (path_str.len > len) path_str.ptr[len] = 0;
// Empty path after normalization -> "."
if (!len) return ".";
path_str.ptr[len] = 0;
return path_str[:len];
}
@@ -523,7 +395,6 @@ fn String Path.root_directory(self)
String path_str = self.str_view();
usz len = path_str.len;
if (!len) return "";
if (path_str == ".") return ".";
if (self.env == PathEnv.WIN32)
{
usz root_len = volume_name_len(path_str, self.env)!!;
@@ -543,22 +414,21 @@ fn String Path.root_directory(self)
def PathWalker = fn bool! (Path, bool is_dir, void*);
<*
Walk the path recursively. PathWalker is run on every file and
directory found. Return true to abort the walk.
@require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
*>
/*
* Walk the path recursively. PathWalker is run on every file and
* directory found. Return true to abort the walk.
*/
fn bool! Path.walk(self, PathWalker w, void* data)
{
const PATH_MAX = 512;
@stack_mem(PATH_MAX; Allocator allocator)
@stack_mem(PATH_MAX; Allocator* allocator)
{
Path abs = self.new_absolute(allocator)!;
PathList files = new_ls(abs, allocator: allocator)!;
Path abs = self.absolute(allocator)!;
PathList files = ls(abs, .allocator = allocator)!;
foreach (f : files)
{
if (f.str_view() == "." || f.str_view() == "..") continue;
f = abs.new_append(f.str_view(), allocator)!;
f = abs.append(f.str_view(), allocator)!;
bool is_directory = is_dir(f);
if (w(f, is_directory, data)!) return true;
if (is_directory && f.walk(w, data)!) return true;
@@ -578,11 +448,6 @@ fn bool Path.has_suffix(self, String str)
return self.str_view().ends_with(str);
}
fn void Path.free_with_allocator(self, Allocator allocator)
{
allocator::free(allocator, self.path_string.ptr);
}
fn void Path.free(self)
{
free(self.path_string.ptr);
@@ -594,7 +459,7 @@ fn usz! Path.to_format(&self, Formatter* formatter) @dynamic
return formatter.print(self.str_view());
}
fn String Path.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String Path.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
{
return self.str_view().copy(allocator);
}

View File

@@ -1,5 +1,4 @@
module std::io;
import std::math;
interface InStream
{
@@ -9,7 +8,7 @@ interface InStream
fn usz! available() @optional;
fn usz! read(char[] buffer);
fn char! read_byte();
fn usz! write_to(OutStream out) @optional;
fn usz! write_to(OutStream* out) @optional;
fn void! pushback_byte() @optional;
}
@@ -21,10 +20,10 @@ interface OutStream
fn void! flush() @optional;
fn usz! write(char[] bytes);
fn void! write_byte(char c);
fn usz! read_to(InStream in) @optional;
fn usz! read_to(InStream* in) @optional;
}
fn usz! available(InStream s)
fn usz! available(InStream* s)
{
if (&s.available) return s.available();
if (&s.seek)
@@ -39,36 +38,36 @@ fn usz! available(InStream s)
macro bool @is_instream(#expr)
{
return $assignable(#expr, InStream);
return $assignable(#expr, InStream*);
}
macro bool @is_outstream(#expr)
{
return $assignable(#expr, OutStream);
return $assignable(#expr, OutStream*);
}
<*
@param [&out] ref
@require @is_instream(stream)
*>
macro usz! read_any(stream, any ref)
/**
* @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)
/**
* @param [&in] ref "the object to write."
* @require @is_outstream(stream)
* @ensure return == ref.type.sizeof
*/
macro usz! write_any(stream, any* ref)
{
return write_all(stream, ((char*)ref)[:ref.type.sizeof]);
}
<*
@require @is_instream(stream)
*>
/**
* @require @is_instream(stream)
*/
macro usz! read_all(stream, char[] buffer)
{
if (buffer.len == 0) return 0;
@@ -77,25 +76,9 @@ macro usz! read_all(stream, char[] buffer)
return n;
}
<*
@require @is_instream(stream)
*>
macro char[]! read_new_fully(stream, Allocator allocator = allocator::heap())
{
usz len = available(stream)!;
char* data = allocator::malloc_try(allocator, len)!;
defer catch allocator::free(allocator, data);
usz read = 0;
while (read < len)
{
read += stream.read(data[read:len - read])!;
}
return data[:len];
}
<*
@require @is_outstream(stream)
*>
/**
* @require @is_outstream(stream)
*/
macro usz! write_all(stream, char[] buffer)
{
if (buffer.len == 0) return 0;
@@ -150,23 +133,29 @@ macro void! @pushback_using_seek(&s)
s.seek(-1, CURSOR)!;
}
fn usz! copy_to(InStream in, OutStream dst, char[] buffer = {})
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:
return copy_through_buffer(in, dst, &&char[4096]{});
@pool()
{
return copy_through_buffer(in, dst, mem::temp_array(char, 4096));
};
$case SMALL:
return copy_through_buffer(in, dst, &&char[1024]{});
@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
macro usz! copy_through_buffer(InStream *in, OutStream* dst, char[] buffer) @local
{
usz total_copied;
while (true)
@@ -186,10 +175,10 @@ macro usz! copy_through_buffer(InStream in, OutStream dst, char[] buffer) @local
const char[*] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 };
<*
@require @is_instream(stream)
@require @typekind(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
*>
/**
* @require @is_instream(stream)
* @require @typekind(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
**/
macro usz! read_varint(stream, x_ptr)
{
var $Type = $typefrom($typeof(x_ptr).inner);
@@ -223,10 +212,10 @@ macro usz! read_varint(stream, x_ptr)
}
return MathError.OVERFLOW?;
}
<*
@require @is_outstream(stream)
@require @typekind(x).is_int()
*>
/**
* @require @is_outstream(stream)
* @require @typekind(x).is_int()
**/
macro usz! write_varint(stream, x)
{
var $Type = $typeof(x);
@@ -241,205 +230,4 @@ macro usz! write_varint(stream, x)
}
buffer[i] = (char)x;
return write_all(stream, buffer[:i + 1]);
}
<*
@require @is_instream(stream)
*>
macro ushort! read_be_ushort(stream)
{
char hi_byte = stream.read_byte()!;
char lo_byte = stream.read_byte()!;
return (ushort)(hi_byte << 8 | lo_byte);
}
<*
@require @is_instream(stream)
*>
macro short! read_be_short(stream)
{
return read_be_ushort(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_short(stream, ushort s)
{
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_instream(stream)
*>
macro uint! read_be_uint(stream)
{
uint val = stream.read_byte()! << 24;
val += stream.read_byte()! << 16;
val += stream.read_byte()! << 8;
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro int! read_be_int(stream)
{
return read_be_uint(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_int(stream, uint s)
{
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_instream(stream)
*>
macro ulong! read_be_ulong(stream)
{
ulong val = (ulong)stream.read_byte()! << 56;
val += (ulong)stream.read_byte()! << 48;
val += (ulong)stream.read_byte()! << 40;
val += (ulong)stream.read_byte()! << 32;
val += (ulong)stream.read_byte()! << 24;
val += (ulong)stream.read_byte()! << 16;
val += (ulong)stream.read_byte()! << 8;
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro long! read_be_long(stream)
{
return read_be_ulong(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_long(stream, ulong s)
{
stream.write_byte((char)(s >> 56))!;
stream.write_byte((char)(s >> 48))!;
stream.write_byte((char)(s >> 40))!;
stream.write_byte((char)(s >> 32))!;
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_instream(stream)
*>
macro uint128! read_be_uint128(stream)
{
uint128 val = (uint128)stream.read_byte()! << 120;
val += (uint128)stream.read_byte()! << 112;
val += (uint128)stream.read_byte()! << 104;
val += (uint128)stream.read_byte()! << 96;
val += (uint128)stream.read_byte()! << 88;
val += (uint128)stream.read_byte()! << 80;
val += (uint128)stream.read_byte()! << 72;
val += (uint128)stream.read_byte()! << 64;
val += (uint128)stream.read_byte()! << 56;
val += (uint128)stream.read_byte()! << 48;
val += (uint128)stream.read_byte()! << 40;
val += (uint128)stream.read_byte()! << 32;
val += (uint128)stream.read_byte()! << 24;
val += (uint128)stream.read_byte()! << 16;
val += (uint128)stream.read_byte()! << 8;
return val + stream.read_byte()!;
}
<*
@require @is_instream(stream)
*>
macro int128! read_be_int128(stream)
{
return read_be_uint128(stream);
}
<*
@require @is_outstream(stream)
*>
macro void! write_be_int128(stream, uint128 s)
{
stream.write_byte((char)(s >> 120))!;
stream.write_byte((char)(s >> 112))!;
stream.write_byte((char)(s >> 104))!;
stream.write_byte((char)(s >> 96))!;
stream.write_byte((char)(s >> 88))!;
stream.write_byte((char)(s >> 80))!;
stream.write_byte((char)(s >> 72))!;
stream.write_byte((char)(s >> 64))!;
stream.write_byte((char)(s >> 56))!;
stream.write_byte((char)(s >> 48))!;
stream.write_byte((char)(s >> 40))!;
stream.write_byte((char)(s >> 32))!;
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 16))!;
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
}
<*
@require @is_outstream(stream)
@require data.len < 256 "Data exceeded 255"
*>
macro usz! write_tiny_bytearray(stream, char[] data)
{
stream.write_byte((char)data.len)!;
return stream.write(data) + 1;
}
<*
@require @is_instream(stream)
*>
macro char[]! read_tiny_bytearray(stream, Allocator allocator)
{
int len = stream.read_byte()!;
if (!len) return {};
char[] data = allocator::alloc_array(allocator, char, len);
io::read_all(stream, data)!;
return data;
}
<*
@require @is_outstream(stream)
@require data.len < 0x1000 "Data exceeded 65535"
*>
macro usz! write_short_bytearray(stream, char[] data)
{
io::write_be_short(stream, (ushort)data.len)!;
return stream.write(data) + 2;
}
<*
@require @is_instream(stream)
*>
macro char[]! read_short_bytearray(stream, Allocator allocator)
{
int len = io::read_be_ushort(stream)!;
if (!len) return {};
char[] data = allocator::alloc_array(allocator, char, len);
io::read_all(stream, data)!;
return data;
}
<*
Wrap bytes for reading using io functions.
*>
fn ByteReader wrap_bytes(char[] bytes)
{
return { bytes, 0 };
}

View File

@@ -2,19 +2,19 @@ module std::io;
struct ReadBuffer (InStream)
{
InStream wrapped_stream;
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)
/**
* 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;
@@ -63,18 +63,18 @@ fn void! ReadBuffer.refill(&self) @local @inline
struct WriteBuffer (OutStream)
{
OutStream wrapped_stream;
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)
/**
* 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;
@@ -121,16 +121,13 @@ fn usz! WriteBuffer.write(&self, char[] bytes) @dynamic
fn void! WriteBuffer.write_byte(&self, char c) @dynamic
{
usz n = self.bytes.len - self.index;
if (n == 0)
{
self.write_pending()!;
}
self.bytes[self.index] = c;
self.index += 1;
if (n == 0) self.write_pending()!;
self.bytes[0] = c;
self.index = 1;
}
fn void! WriteBuffer.write_pending(&self) @local
{
self.index -= self.wrapped_stream.write(self.bytes[:self.index])!;
if (self.index != 0) return IoError.INCOMPLETE_WRITE?;
}
}

View File

@@ -3,7 +3,7 @@ import std::math;
struct ByteBuffer (InStream, OutStream)
{
Allocator allocator;
Allocator* allocator;
usz max_read;
char[] bytes;
usz read_idx;
@@ -11,12 +11,12 @@ struct ByteBuffer (InStream, OutStream)
bool has_last;
}
<*
ByteBuffer provides a streamable read/write buffer.
max_read defines how many bytes might be kept before its internal buffer is shrinked.
@require self.bytes.len == 0 "Buffer already initialized."
*>
fn ByteBuffer*! ByteBuffer.new_init(&self, usz max_read, usz initial_capacity = 16, Allocator allocator = allocator::heap())
/**
* 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);
@@ -24,15 +24,15 @@ fn ByteBuffer*! ByteBuffer.new_init(&self, usz max_read, usz initial_capacity =
return self;
}
fn ByteBuffer*! ByteBuffer.temp_init(&self, usz max_read, usz initial_capacity = 16)
fn ByteBuffer*! ByteBuffer.init_temp(&self, usz max_read, usz initial_capacity = 16)
{
return self.new_init(max_read, initial_capacity, allocator::temp());
return self.init_new(max_read, initial_capacity, mem::temp());
}
<*
@require buf.len > 0
@require self.bytes.len == 0 "Buffer already initialized."
*>
/**
* @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 };
@@ -41,7 +41,7 @@ fn ByteBuffer*! ByteBuffer.init_with_buffer(&self, char[] buf)
fn void ByteBuffer.free(&self)
{
if (self.allocator) allocator::free(self.allocator, self.bytes);
if (self.allocator) self.allocator.free(self.bytes);
*self = {};
}
@@ -93,9 +93,9 @@ fn char! ByteBuffer.read_byte(&self) @dynamic
return c;
}
<*
Only the last byte of a successful read can be pushed back.
*>
/*
* Only the last byte of a successful read can be pushed back.
*/
fn void! ByteBuffer.pushback_byte(&self) @dynamic
{
if (!self.has_last) return IoError.EOF?;
@@ -131,7 +131,7 @@ fn usz! ByteBuffer.available(&self) @inline @dynamic
fn void! ByteBuffer.grow(&self, usz n)
{
n = math::next_power_of_2(n);
char* p = allocator::realloc_aligned(self.allocator, self.bytes, n, alignment: char.alignof)!;
char* p = self.allocator.realloc_aligned(self.bytes, n, .alignment = char.alignof)!;
self.bytes = p[:n];
}

View File

@@ -53,7 +53,7 @@ fn usz! ByteReader.seek(&self, isz offset, Seek seek) @dynamic
return new_index;
}
fn usz! ByteReader.write_to(&self, OutStream writer) @dynamic
fn usz! ByteReader.write_to(&self, OutStream* writer) @dynamic
{
if (self.index >= self.bytes.len) return 0;
usz written = writer.write(self.bytes[self.index..])!;

View File

@@ -1,33 +1,32 @@
module std::io;
import std::math;
struct ByteWriter (OutStream)
{
char[] bytes;
usz index;
Allocator allocator;
Allocator* allocator;
}
<*
@param [&inout] self
@param [&inout] allocator
@require self.bytes.len == 0 "Init may not run on already initialized data"
@ensure (bool)allocator, self.index == 0
*>
fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap())
/**
* @param [&inout] self
* @param [&inout] allocator
* @require self.bytes.len == 0 "Init may not run on on already initialized data"
* @ensure (bool)allocator, self.index == 0
**/
fn ByteWriter* ByteWriter.init_new(&self, Allocator* allocator = mem::heap())
{
*self = { .bytes = {}, .allocator = allocator };
return self;
}
<*
@param [&inout] self
@require self.bytes.len == 0 "Init may not run on already initialized data"
@ensure self.index == 0
*>
fn ByteWriter* ByteWriter.temp_init(&self)
/**
* @param [&inout] self
* @require self.bytes.len == 0 "Init may not run on on already initialized data"
* @ensure self.index == 0
**/
fn ByteWriter* ByteWriter.init_temp(&self)
{
return self.new_init(allocator::temp()) @inline;
return self.init_new(mem::temp());
}
fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
@@ -39,7 +38,7 @@ fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
fn void! ByteWriter.destroy(&self) @dynamic
{
if (!self.allocator) return;
if (void* ptr = self.bytes.ptr) allocator::free(self.allocator, ptr);
if (void* ptr = self.bytes.ptr) self.allocator.free(ptr);
*self = { };
}
@@ -54,7 +53,7 @@ fn void! ByteWriter.ensure_capacity(&self, usz len) @inline
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 = allocator::realloc_try(self.allocator, self.bytes.ptr, new_capacity)!;
char* new_ptr = self.allocator.realloc_checked(self.bytes.ptr, new_capacity)!;
self.bytes = new_ptr[:new_capacity];
}
@@ -72,11 +71,11 @@ fn void! ByteWriter.write_byte(&self, char c) @dynamic
self.bytes[self.index++] = c;
}
<*
@param [&inout] self
@param reader
*>
fn usz! ByteWriter.read_from(&self, InStream reader) @dynamic
/**
* @param [&inout] self
* @param reader
**/
fn usz! ByteWriter.read_from(&self, InStream* reader) @dynamic
{
usz start_index = self.index;
if (&reader.available)

View File

@@ -2,15 +2,15 @@ module std::io;
struct LimitReader (InStream)
{
InStream wrapped_stream;
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)
/**
* @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;

View File

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

View File

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

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