Merge tag 'dev-2025-07'

This commit is contained in:
ed
2025-07-12 22:38:51 -04:00
220 changed files with 37115 additions and 4278 deletions
+56 -56
View File
@@ -30,13 +30,13 @@ jobs:
gmake -C vendor/stb/src gmake -C vendor/stb/src
gmake -C vendor/cgltf/src gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64 ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64 ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64
./odin check vendor/sdl3 -vet -strict-style -disallow-do -target:netbsd_amd64 -no-entry-point ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64 -no-entry-point
./odin check vendor/sdl3 -vet -strict-style -disallow-do -target:netbsd_arm64 -no-entry-point ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64 -no-entry-point
./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true ./odin test tests/core/speed.odin -file -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true ./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
(cd tests/issues; ./run.sh) (cd tests/issues; ./run.sh)
./odin check tests/benchmark -vet -strict-style -no-entry-point ./odin check tests/benchmark -vet -strict-style -no-entry-point
@@ -63,11 +63,11 @@ jobs:
gmake -C vendor/stb/src gmake -C vendor/stb/src
gmake -C vendor/cgltf/src gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64 ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
./odin check vendor/sdl3 -vet -strict-style -disallow-do -target:freebsd_amd64 -no-entry-point ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64 -no-entry-point
./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true ./odin test tests/core/speed.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true ./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
(cd tests/issues; ./run.sh) (cd tests/issues; ./run.sh)
./odin check tests/benchmark -vet -strict-style -no-entry-point ./odin check tests/benchmark -vet -strict-style -no-entry-point
ci: ci:
@@ -75,7 +75,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
# MacOS 13 runs on Intel, 14 runs on ARM # MacOS 13 runs on Intel, 14 runs on ARM
os: [macos-13, macos-14, ubuntu-latest] os: [macos-14, ubuntu-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test
timeout-minutes: 15 timeout-minutes: 15
@@ -123,17 +123,17 @@ jobs:
- name: Odin run -debug - name: Odin run -debug
run: ./odin run examples/demo -debug run: ./odin run examples/demo -debug
- name: Odin check examples/all - name: Odin check examples/all
run: ./odin check examples/all -strict-style -vet -disallow-do run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
- name: Odin check vendor/sdl3 - name: Odin check examples/all/sdl3
run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
- name: Normal Core library tests - name: Normal Core library tests
run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address run: ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized Core library tests - name: Optimized Core library tests
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests - name: Vendor library tests
run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address run: ./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Internals tests - name: Internals tests
run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address run: ./odin test tests/internal -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: GitHub Issue tests - name: GitHub Issue tests
run: | run: |
cd tests/issues cd tests/issues
@@ -141,37 +141,43 @@ jobs:
- name: Run demo on WASI WASM32 - name: Run demo on WASI WASM32
run: | run: |
./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo ./odin build examples/demo -target:wasi_wasm32 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -out:demo
wasmtime ./demo.wasm wasmtime ./demo.wasm
if: matrix.os == 'macos-14' if: matrix.os == 'macos-14'
- name: Check benchmarks - name: Check benchmarks
run: ./odin check tests/benchmark -vet -strict-style -no-entry-point run: ./odin check tests/benchmark -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
- name: Odin check examples/all for Linux i386 - name: Odin check examples/all for Linux i386
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386 run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386
- name: Odin check examples/all for Linux arm64 - name: Odin check examples/all for Linux arm64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64 run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm64
- name: Odin check examples/all for FreeBSD amd64 - name: Odin check examples/all for FreeBSD amd64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64 run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
- name: Odin check examples/all for OpenBSD amd64 - name: Odin check examples/all for OpenBSD amd64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64 run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64
- name: Odin check examples/all for js_wasm32
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:js_wasm32
- name: Odin check examples/all for js_wasm64p32
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:js_wasm64p32
- name: Odin check vendor/sdl3 for Linux i386 - name: Odin check examples/all/sdl3 for Linux i386
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386 run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:linux_i386
- name: Odin check vendor/sdl3 for Linux arm64 - name: Odin check examples/all/sdl3 for Linux arm64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64 run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:linux_arm64
- name: Odin check vendor/sdl3 for FreeBSD amd64 - name: Odin check examples/all/sdl3 for FreeBSD amd64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64 run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:freebsd_amd64
- name: Odin check vendor/sdl3 for OpenBSD amd64 - name: Odin check examples/all/sdl3 for OpenBSD amd64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64 run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:openbsd_amd64
build_windows: build_windows:
name: Windows Build, Check, and Test name: Windows Build, Check, and Test
@@ -202,38 +208,38 @@ jobs:
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin run examples/demo -debug -vet -strict-style -disallow-do odin run examples/demo -debug -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
- name: Odin check examples/all - name: Odin check examples/all
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check examples/all -vet -strict-style -disallow-do odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
- name: Odin check vendor/sdl3 - name: Odin check examples/all/sdl3
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
- name: Core library tests - name: Core library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized core library tests - name: Optimized core library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests - name: Vendor library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
copy vendor\lua\5.4\windows\*.dll . copy vendor\lua\5.4\windows\*.dll .
odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Odin internals tests - name: Odin internals tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address odin test tests/internal -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Check issues - name: Check issues
shell: cmd shell: cmd
run: | run: |
@@ -251,12 +257,6 @@ jobs:
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\documentation cd tests\documentation
call build.bat call build.bat
- name: core:math/big tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\core\math\big
call build.bat
- name: Odin check examples/all for Windows 32bits - name: Odin check examples/all for Windows 32bits
shell: cmd shell: cmd
run: | run: |
@@ -293,25 +293,25 @@ jobs:
make -C vendor/miniaudio/src make -C vendor/miniaudio/src
- name: Odin check examples/all - name: Odin check examples/all
run: ./odin check examples/all -target:linux_riscv64 -vet -strict-style -disallow-do run: ./odin check examples/all -target:linux_riscv64 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
- name: Odin check vendor/sdl3 - name: Odin check examples/all/sdl3
run: ./odin check vendor/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point run: ./odin check examples/all/sdl3 -target:linux_riscv64 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
- name: Install riscv64 toolchain and qemu - name: Install riscv64 toolchain and qemu
run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross
- name: Odin run - name: Odin run
run: ./odin run examples/demo -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath run: ./odin run examples/demo -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
- name: Odin run -debug - name: Odin run -debug
run: ./odin run examples/demo -debug -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath run: ./odin run examples/demo -debug -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
- name: Normal Core library tests - name: Normal Core library tests
run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath run: ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
- name: Optimized Core library tests - name: Optimized Core library tests
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
- name: Internals tests - name: Internals tests
run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath run: ./odin test tests/internal -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
+60
View File
@@ -0,0 +1,60 @@
name: Test Coverage
on: [push, pull_request, workflow_dispatch]
jobs:
build_linux_amd64:
runs-on: ubuntu-latest
name: Linux AMD64 Test Coverage
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Download LLVM (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
- name: Install kcov
run: |
sudo apt-get update
sudo apt-get install binutils-dev build-essential cmake libssl-dev libcurl4-openssl-dev libelf-dev libstdc++-12-dev zlib1g-dev libdw-dev libiberty-dev
git clone https://github.com/SimonKagstrom/kcov.git
mkdir kcov/build
cd kcov/build
cmake ..
sudo make
sudo make install
cd ../..
kcov --version
- name: Build Odin
run: ./build_odin.sh release
- name: Odin report
run: ./odin report
- name: Normal Core library tests
run: |
./odin build tests/core/normal.odin -build-mode:test -debug -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_amd64
mkdir kcov-out
kcov --exclude-path=tests,/usr kcov-out ./normal.bin .
- name: Optimized Core library tests
run: |
./odin build tests/core/speed.odin -build-mode:test -debug -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_amd64
kcov --exclude-path=tests,/usr kcov-out ./speed.bin .
- name: Internals tests
run: |
./odin build tests/internal -build-mode:test -debug -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_amd64
kcov --exclude-path=tests,/usr kcov-out ./internal .
- uses: codecov/codecov-action@v5
with:
name: Ubuntu Coverage # optional
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true # optional (default = false
directory: kcov-out/kcov-merged
+1
View File
@@ -277,6 +277,7 @@ odin
*.bin *.bin
demo.bin demo.bin
libLLVM*.so* libLLVM*.so*
*.a
# shared collection # shared collection
shared/ shared/
+1
View File
@@ -169,6 +169,7 @@ type_is_union :: proc($T: typeid) -> bool ---
type_is_enum :: proc($T: typeid) -> bool --- type_is_enum :: proc($T: typeid) -> bool ---
type_is_proc :: proc($T: typeid) -> bool --- type_is_proc :: proc($T: typeid) -> bool ---
type_is_bit_set :: proc($T: typeid) -> bool --- type_is_bit_set :: proc($T: typeid) -> bool ---
type_is_bit_field :: proc($T: typeid) -> bool ---
type_is_simd_vector :: proc($T: typeid) -> bool --- type_is_simd_vector :: proc($T: typeid) -> bool ---
type_is_matrix :: proc($T: typeid) -> bool --- type_is_matrix :: proc($T: typeid) -> bool ---
+11 -2
View File
@@ -67,7 +67,7 @@ init_global_temporary_allocator :: proc(size: int, backup_allocator := context.a
// Prefer the procedure group `copy`. // Prefer the procedure group `copy`.
@builtin @builtin
copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int { copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
n := max(0, min(len(dst), len(src))) n := min(len(dst), len(src))
if n > 0 { if n > 0 {
intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(E)) intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(E))
} }
@@ -80,7 +80,7 @@ copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
// Prefer the procedure group `copy`. // Prefer the procedure group `copy`.
@builtin @builtin
copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int { copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int {
n := max(0, min(len(dst), len(src))) n := min(len(dst), len(src))
if n > 0 { if n > 0 {
intrinsics.mem_copy(raw_data(dst), raw_data(src), n) intrinsics.mem_copy(raw_data(dst), raw_data(src), n)
} }
@@ -649,6 +649,9 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i
@builtin @builtin
inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil { if array == nil {
return return
} }
@@ -667,6 +670,9 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcas
@builtin @builtin
inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil { if array == nil {
return return
} }
@@ -690,6 +696,9 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca
@builtin @builtin
inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil { if array == nil {
return return
} }
@@ -1,7 +1,7 @@
package runtime package runtime
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer" // import "base:sanitizer"
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE) DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
@@ -44,7 +44,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
block.base = ([^]byte)(uintptr(block) + base_offset) block.base = ([^]byte)(uintptr(block) + base_offset)
block.capacity = uint(end - uintptr(block.base)) block.capacity = uint(end - uintptr(block.base))
sanitizer.address_poison(block.base, block.capacity) // sanitizer.address_poison(block.base, block.capacity)
// Should be zeroed // Should be zeroed
assert(block.used == 0) assert(block.used == 0)
@@ -55,7 +55,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) { memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
if block_to_free != nil { if block_to_free != nil {
allocator := block_to_free.allocator allocator := block_to_free.allocator
sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity) // sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity)
mem_free(block_to_free, allocator, loc) mem_free(block_to_free, allocator, loc)
} }
} }
@@ -87,7 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
return return
} }
data = block.base[block.used+alignment_offset:][:min_size] data = block.base[block.used+alignment_offset:][:min_size]
sanitizer.address_unpoison(block.base[block.used:block.used+size]) // sanitizer.address_unpoison(block.base[block.used:block.used+size])
block.used += size block.used += size
return return
} }
@@ -169,7 +169,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil { if arena.curr_block != nil {
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used) intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
arena.curr_block.used = 0 arena.curr_block.used = 0
sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity) // sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
} }
arena.total_used = 0 arena.total_used = 0
} }
@@ -234,7 +234,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
// grow data in-place, adjusting next allocation // grow data in-place, adjusting next allocation
block.used = uint(new_end) block.used = uint(new_end)
data = block.base[start:new_end] data = block.base[start:new_end]
sanitizer.address_unpoison(data) // sanitizer.address_unpoison(data)
return return
} }
} }
@@ -308,7 +308,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
amount_to_zero := block.used-temp.used amount_to_zero := block.used-temp.used
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
sanitizer.address_poison(block.base[temp.used:block.capacity]) // sanitizer.address_poison(block.base[temp.used:block.capacity])
block.used = temp.used block.used = temp.used
arena.total_used -= amount_to_zero arena.total_used -= amount_to_zero
} }
+74
View File
@@ -0,0 +1,74 @@
#+no-instrumentation
package sanitizer
@(private="file")
MSAN_ENABLED :: .Memory in ODIN_SANITIZER_FLAGS
@(private="file")
@(default_calling_convention="system")
foreign {
__msan_unpoison :: proc(addr: rawptr, size: uint) ---
}
/*
Marks a slice as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access any
address within the slice as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
when MSAN_ENABLED {
__msan_unpoison(raw_data(region), size_of(E) * len(region))
}
}
/*
Marks a pointer as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access memory
within the region the pointer points to as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
when MSAN_ENABLED {
__msan_unpoison(ptr, size_of(T))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access memory
within this range as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
when MSAN_ENABLED {
__msan_unpoison(ptr, uint(len))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access memory
within this range as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
when MSAN_ENABLED {
__msan_unpoison(ptr, len)
}
}
memory_unpoison :: proc {
memory_unpoison_slice,
memory_unpoison_ptr,
memory_unpoison_rawptr,
memory_unpoison_rawptr_uint,
}
+75
View File
@@ -0,0 +1,75 @@
@echo off
if "%1" == "" (
echo Checking darwin_amd64 - expect vendor:cgltf panic
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_amd64
echo Checking darwin_arm64 - expect vendor:cgltf panic
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_arm64
echo Checking linux_i386
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386
echo Checking linux_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_amd64
echo Checking linux_arm64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm64
echo Checking linux_arm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm32
echo Checking linux_riscv64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_riscv64
echo Checking windows_i386
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_i386
echo Checking windows_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_amd64
echo Checking freebsd_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
echo Checking freebsd_arm64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_arm64
echo Checking netbsd_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
echo Checking netbsd_arm64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64
echo Checking openbsd_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64
)
if "%1" == "freestanding" (
echo Checking freestanding_wasm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
echo Checking freestanding_wasm64p32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
echo Checking freestanding_amd64_sysv
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_sysv
echo Checking freestanding_amd64_win64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_win64
echo Checking freestanding_arm64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm64
echo Checking freestanding_arm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm32
echo Checking freestanding_riscv64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_riscv64
)
if "%1" == "rare" (
echo Checking essence_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:essence_amd64
echo Checking freebsd_i386
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
echo Checking haiku_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:haiku_amd64
)
if "%1" == "wasm" (
echo Checking freestanding_wasm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
echo Checking freestanding_wasm64p32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
echo Checking wasi_wasm64p32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm64p32
echo Checking wasi_wasm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm32
echo Checking js_wasm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm32
echo Checking orca_wasm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:orca_wasm32
echo Checking js_wasm64p32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm64p32
)
Executable
+78
View File
@@ -0,0 +1,78 @@
#!/bin/sh
case $1 in
freestanding)
echo Checking freestanding_wasm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
echo Checking freestanding_wasm64p32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
echo Checking freestanding_amd64_sysv
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_sysv
echo Checking freestanding_amd64_win64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_win64
echo Checking freestanding_arm64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm64
echo Checking freestanding_arm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm32
echo Checking freestanding_riscv64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_riscv64
;;
rare)
echo Checking essence_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:essence_amd64
echo Checking freebsd_i386
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
echo Checking haiku_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:haiku_amd64
;;
wasm)
echo Checking freestanding_wasm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
echo Checking freestanding_wasm64p32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
echo Checking wasi_wasm64p32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm64p32
echo Checking wasi_wasm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm32
echo Checking js_wasm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm32
echo Checking orca_wasm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:orca_wasm32
echo Checking js_wasm64p32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm64p32
;;
*)
echo Checking darwin_amd64 - expect vendor:cgltf panic
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_amd64
echo Checking darwin_arm64 - expect vendor:cgltf panic
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_arm64
echo Checking linux_i386
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386
echo Checking linux_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_amd64
echo Checking linux_arm64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm64
echo Checking linux_arm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm32
echo Checking linux_riscv64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_riscv64
echo Checking windows_i386
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_i386
echo Checking windows_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_amd64
echo Checking freebsd_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
echo Checking freebsd_arm64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_arm64
echo Checking netbsd_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
echo Checking netbsd_arm64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64
echo Checking openbsd_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64
;;
esac
+1
View File
@@ -0,0 +1 @@
comment: false
-3
View File
@@ -139,9 +139,6 @@ Context_Memory_Input :: struct #packed {
} }
when size_of(rawptr) == 8 { when size_of(rawptr) == 8 {
#assert(size_of(Context_Memory_Input) == 64) #assert(size_of(Context_Memory_Input) == 64)
} else {
// e.g. `-target:windows_i386`
#assert(size_of(Context_Memory_Input) == 52)
} }
Context_Stream_Input :: struct #packed { Context_Stream_Input :: struct #packed {
+282 -58
View File
@@ -4,7 +4,13 @@ import "base:builtin"
import "base:runtime" import "base:runtime"
_ :: runtime _ :: runtime
// Dynamically resizable double-ended queue/ring-buffer /*
`Queue` is a dynamically resizable double-ended queue/ring-buffer.
Being double-ended means that either end may be pushed onto or popped from
across the same block of memory, in any order, thus providing both stack and
queue-like behaviors in the same data structure.
*/
Queue :: struct($T: typeid) { Queue :: struct($T: typeid) {
data: [dynamic]T, data: [dynamic]T,
len: uint, len: uint,
@@ -13,18 +19,31 @@ Queue :: struct($T: typeid) {
DEFAULT_CAPACITY :: 16 DEFAULT_CAPACITY :: 16
// Procedure to initialize a queue /*
Initialize a `Queue` with a starting `capacity` and an `allocator`.
*/
init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> runtime.Allocator_Error { init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> runtime.Allocator_Error {
if q.data.allocator.procedure == nil {
q.data.allocator = allocator
}
clear(q) clear(q)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
data = nil,
len = 0,
cap = 0,
allocator = allocator,
}
return reserve(q, capacity) return reserve(q, capacity)
} }
// Procedure to initialize a queue from a fixed backing slice. /*
// The contents of the `backing` will be overwritten as items are pushed onto the `Queue`. Initialize a `Queue` from a fixed `backing` slice into which modifications are
// Any previous contents are not available. made directly.
The contents of the `backing` will be overwritten as items are pushed onto the
`Queue`. Any previous contents will not be available through the API but are
not explicitly zeroed either.
Note that procedures which need space to work (`push_back`, ...) will fail if
the backing slice runs out of space.
*/
init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
clear(q) clear(q)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
@@ -36,8 +55,14 @@ init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
return true return true
} }
// Procedure to initialize a queue from a fixed backing slice. /*
// Existing contents are preserved and available on the queue. Initialize a `Queue` from a fixed `backing` slice into which modifications are
made directly.
The contents of the queue will start out with all of the elements in `backing`,
effectively creating a full queue from the slice. As such, no procedures will
be able to add more elements to the queue until some are taken off.
*/
init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
clear(q) clear(q)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
@@ -50,84 +75,200 @@ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
return true return true
} }
// Procedure to destroy a queue /*
Delete memory that has been dynamically allocated from a `Queue` that was setup with `init`.
Note that this procedure should not be used on queues setup with
`init_from_slice` or `init_with_contents`, as neither of those procedures keep
track of the allocator state of the underlying `backing` slice.
*/
destroy :: proc(q: ^$Q/Queue($T)) { destroy :: proc(q: ^$Q/Queue($T)) {
delete(q.data) delete(q.data)
} }
// The length of the queue /*
Return the length of the queue.
*/
len :: proc(q: $Q/Queue($T)) -> int { len :: proc(q: $Q/Queue($T)) -> int {
return int(q.len) return int(q.len)
} }
// The current capacity of the queue /*
Return the capacity of the queue.
*/
cap :: proc(q: $Q/Queue($T)) -> int { cap :: proc(q: $Q/Queue($T)) -> int {
return builtin.len(q.data) return builtin.len(q.data)
} }
// Remaining space in the queue (cap-len) /*
Return the remaining space in the queue.
This will be `cap() - len()`.
*/
space :: proc(q: $Q/Queue($T)) -> int { space :: proc(q: $Q/Queue($T)) -> int {
return builtin.len(q.data) - int(q.len) return builtin.len(q.data) - int(q.len)
} }
// Reserve enough space for at least the specified capacity /*
Reserve enough space in the queue for at least the specified capacity.
This may return an error if allocation failed.
*/
reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error { reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error {
if capacity > space(q^) { if capacity > space(q^) {
return _grow(q, uint(capacity)) return _grow(q, uint(capacity))
} }
return nil return nil
} }
/*
Shrink a queue's dynamically allocated array.
This has no effect if the queue was initialized with a backing slice.
*/
shrink :: proc(q: ^$Q/Queue($T), temp_allocator := context.temp_allocator, loc := #caller_location) {
if q.data.allocator.procedure == runtime.nil_allocator_proc {
return
}
if q.len > 0 && q.offset > 0 {
// Make the array contiguous again.
buffer := make([]T, q.len, temp_allocator)
defer delete(buffer, temp_allocator)
right := uint(builtin.len(q.data)) - q.offset
copy(buffer[:], q.data[q.offset:])
copy(buffer[right:], q.data[:q.offset])
copy(q.data[:], buffer[:])
q.offset = 0
}
builtin.shrink(&q.data, q.len, loc)
}
/*
Get the element at index `i`.
This will raise a bounds checking error if `i` is an invalid index.
*/
get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T { get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T {
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data)) runtime.bounds_check_error_loc(loc, i, int(q.len))
idx := (uint(i)+q.offset)%builtin.len(q.data) idx := (uint(i)+q.offset)%builtin.len(q.data)
return q.data[idx] return q.data[idx]
} }
front :: proc(q: ^$Q/Queue($T)) -> T { /*
return q.data[q.offset] Get a pointer to the element at index `i`.
}
front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
return &q.data[q.offset]
}
back :: proc(q: ^$Q/Queue($T)) -> T { This will raise a bounds checking error if `i` is an invalid index.
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) */
return q.data[idx] get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T {
} runtime.bounds_check_error_loc(loc, i, int(q.len))
back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) idx := (uint(i)+q.offset)%builtin.len(q.data)
return &q.data[idx] return &q.data[idx]
} }
/*
Set the element at index `i` to `val`.
This will raise a bounds checking error if `i` is an invalid index.
*/
set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) { set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) {
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data)) runtime.bounds_check_error_loc(loc, i, int(q.len))
idx := (uint(i)+q.offset)%builtin.len(q.data) idx := (uint(i)+q.offset)%builtin.len(q.data)
q.data[idx] = val q.data[idx] = val
} }
get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T {
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data)) /*
Get the element at the front of the queue.
idx := (uint(i)+q.offset)%builtin.len(q.data)
This will raise a bounds checking error if the queue is empty.
*/
front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
return q.data[q.offset]
}
/*
Get a pointer to the element at the front of the queue.
This will raise a bounds checking error if the queue is empty.
*/
front_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
return &q.data[q.offset]
}
/*
Get the element at the back of the queue.
This will raise a bounds checking error if the queue is empty.
*/
back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
return q.data[idx]
}
/*
Get a pointer to the element at the back of the queue.
This will raise a bounds checking error if the queue is empty.
*/
back_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
return &q.data[idx] return &q.data[idx]
} }
@(deprecated="Use `front_ptr` instead")
peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
runtime.bounds_check_error_loc(loc, 0, builtin.len(q.data)) return front_ptr(q, loc)
idx := q.offset%builtin.len(q.data)
return &q.data[idx]
} }
@(deprecated="Use `back_ptr` instead")
peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
runtime.bounds_check_error_loc(loc, int(q.len - 1), builtin.len(q.data)) return back_ptr(q, loc)
idx := (uint(q.len - 1)+q.offset)%builtin.len(q.data)
return &q.data[idx]
} }
// Push an element to the back of the queue /*
Push an element to the back of the queue.
If there is no more space left and allocation fails to get more, this will
return false with an `Allocator_Error`.
Example:
import "base:runtime"
import "core:container/queue"
// This demonstrates typical queue behavior (First-In First-Out).
main :: proc() {
q: queue.Queue(int)
queue.init(&q)
queue.push_back(&q, 1)
queue.push_back(&q, 2)
queue.push_back(&q, 3)
// q.data is now [1, 2, 3, ...]
assert(queue.pop_front(&q) == 1)
assert(queue.pop_front(&q) == 2)
assert(queue.pop_front(&q) == 3)
}
*/
push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) { push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
if space(q^) == 0 { if space(q^) == 0 {
_grow(q) or_return _grow(q) or_return
@@ -138,27 +279,78 @@ push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocato
return true, nil return true, nil
} }
// Push an element to the front of the queue /*
Push an element to the front of the queue.
If there is no more space left and allocation fails to get more, this will
return false with an `Allocator_Error`.
Example:
import "base:runtime"
import "core:container/queue"
// This demonstrates stack behavior (First-In Last-Out).
main :: proc() {
q: queue.Queue(int)
queue.init(&q)
queue.push_back(&q, 1)
queue.push_back(&q, 2)
queue.push_back(&q, 3)
// q.data is now [1, 2, 3, ...]
assert(queue.pop_back(&q) == 3)
assert(queue.pop_back(&q) == 2)
assert(queue.pop_back(&q) == 1)
}
*/
push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) { push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
if space(q^) == 0 { if space(q^) == 0 {
_grow(q) or_return _grow(q) or_return
} }
q.offset = uint(q.offset - 1 + builtin.len(q.data)) % builtin.len(q.data) q.offset = uint(q.offset - 1 + builtin.len(q.data)) % builtin.len(q.data)
q.len += 1 q.len += 1
q.data[q.offset] = elem q.data[q.offset] = elem
return true, nil return true, nil
} }
/*
Pop an element from the back of the queue.
// Pop an element from the back of the queue This will raise a bounds checking error if the queue is empty.
Example:
import "base:runtime"
import "core:container/queue"
// This demonstrates stack behavior (First-In Last-Out) at the far end of the data array.
main :: proc() {
q: queue.Queue(int)
queue.init(&q)
queue.push_front(&q, 1)
queue.push_front(&q, 2)
queue.push_front(&q, 3)
// q.data is now [..., 3, 2, 1]
log.infof("%#v", q)
assert(queue.pop_front(&q) == 3)
assert(queue.pop_front(&q) == 2)
assert(queue.pop_front(&q) == 1)
}
*/
pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
assert(condition=q.len > 0, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
q.len -= 1 q.len -= 1
idx := (q.offset+uint(q.len))%builtin.len(q.data) idx := (q.offset+uint(q.len))%builtin.len(q.data)
elem = q.data[idx] elem = q.data[idx]
return return
} }
// Safely pop an element from the back of the queue
/*
Pop an element from the back of the queue if one exists and return true.
Otherwise, return a nil element and false.
*/
pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
if q.len > 0 { if q.len > 0 {
q.len -= 1 q.len -= 1
@@ -169,15 +361,25 @@ pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
return return
} }
// Pop an element from the front of the queue /*
Pop an element from the front of the queue
This will raise a bounds checking error if the queue is empty.
*/
pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
assert(condition=q.len > 0, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
elem = q.data[q.offset] elem = q.data[q.offset]
q.offset = (q.offset+1)%builtin.len(q.data) q.offset = (q.offset+1)%builtin.len(q.data)
q.len -= 1 q.len -= 1
return return
} }
// Safely pop an element from the front of the queue
/*
Pop an element from the front of the queue if one exists and return true.
Otherwise, return a nil element and false.
*/
pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
if q.len > 0 { if q.len > 0 {
elem = q.data[q.offset] elem = q.data[q.offset]
@@ -188,13 +390,18 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
return return
} }
// Push multiple elements to the back of the queue /*
Push many elements at once to the back of the queue.
If there is not enough space left and allocation fails to get more, this will
return false with an `Allocator_Error`.
*/
push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) { push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) {
n := uint(builtin.len(elems)) n := uint(builtin.len(elems))
if space(q^) < int(n) { if space(q^) < int(n) {
_grow(q, q.len + n) or_return _grow(q, q.len + n) or_return
} }
sz := uint(builtin.len(q.data)) sz := uint(builtin.len(q.data))
insert_from := (q.offset + q.len) % sz insert_from := (q.offset + q.len) % sz
insert_to := n insert_to := n
@@ -207,19 +414,31 @@ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime
return true, nil return true, nil
} }
// Consume `n` elements from the front of the queue /*
Consume `n` elements from the back of the queue.
This will raise a bounds checking error if the queue does not have enough elements.
*/
consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) { consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
assert(condition=int(q.len) >= n, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc)
}
if n > 0 { if n > 0 {
nu := uint(n) nu := uint(n)
q.offset = (q.offset + nu) % builtin.len(q.data) q.offset = (q.offset + nu) % builtin.len(q.data)
q.len -= nu q.len -= nu
} }
} }
// Consume `n` elements from the back of the queue /*
Consume `n` elements from the back of the queue.
This will raise a bounds checking error if the queue does not have enough elements.
*/
consume_back :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) { consume_back :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
assert(condition=int(q.len) >= n, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc)
}
if n > 0 { if n > 0 {
q.len -= uint(n) q.len -= uint(n)
} }
@@ -231,9 +450,14 @@ append_elem :: push_back
append_elems :: push_back_elems append_elems :: push_back_elems
push :: proc{push_back, push_back_elems} push :: proc{push_back, push_back_elems}
append :: proc{push_back, push_back_elems} append :: proc{push_back, push_back_elems}
enqueue :: push_back
dequeue :: pop_front
// Clear the contents of the queue /*
Reset the queue's length and offset to zero, letting it write new elements over
old memory, in effect clearing the accessible contents.
*/
clear :: proc(q: ^$Q/Queue($T)) { clear :: proc(q: ^$Q/Queue($T)) {
q.len = 0 q.len = 0
q.offset = 0 q.offset = 0
+23
View File
@@ -0,0 +1,23 @@
#+build haiku
#+private
package dynlib
import "base:runtime"
_LIBRARY_FILE_EXTENSION :: ""
_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) {
return nil, false
}
_unload_library :: proc(library: Library) -> bool {
return false
}
_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) {
return nil, false
}
_last_error :: proc() -> string {
return ""
}
+3 -3
View File
@@ -385,17 +385,17 @@ to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> i
// which we want for the diagnostic format. // which we want for the diagnostic format.
case f16: case f16:
buf: [64]byte buf: [64]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16)) str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16))
if str[0] == '+' && str != "+Inf" { str = str[1:] } if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return io.write_string(w, str) or_return
case f32: case f32:
buf: [128]byte buf: [128]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32)) str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32))
if str[0] == '+' && str != "+Inf" { str = str[1:] } if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return io.write_string(w, str) or_return
case f64: case f64:
buf: [256]byte buf: [256]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64)) str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64))
if str[0] == '+' && str != "+Inf" { str = str[1:] } if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return io.write_string(w, str) or_return
+36
View File
@@ -612,6 +612,42 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
case: case:
panic("unknown bit_size size") panic("unknown bit_size size")
} }
case runtime.Type_Info_Matrix:
count := info.column_count * info.elem_stride
err_conv(_encode_u64(e, u64(count), .Array)) or_return
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
for i in 0..<count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
}
return
}
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
for i in 0..<count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
}
return
case runtime.Type_Info_Simd_Vector:
err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
}
return
}
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
}
return
} }
return _unsupported(v.id, nil) return _unsupported(v.id, nil)
+25
View File
@@ -591,6 +591,31 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
if out_of_space { return _unsupported(v, hdr) } if out_of_space { return _unsupported(v, hdr) }
return return
case reflect.Type_Info_Matrix:
count := t.column_count * t.elem_stride
length, _ := err_conv(_decode_len_container(d, add)) or_return
if length > count {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case reflect.Type_Info_Simd_Vector:
length, _ := err_conv(_decode_len_container(d, add)) or_return
if length > t.count {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case: return _unsupported(v, hdr) case: return _unsupported(v, hdr)
} }
} }
+4 -4
View File
@@ -108,13 +108,13 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) { if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) {
switch i in a { switch i in a {
case u8, u16, u32, u64, u128: case u8, u16, u32, u64, u128:
s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix }) s = strconv.write_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
case: case:
s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil) s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
} }
} else { } else {
s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil) s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
} }
io.write_string(w, s) or_return io.write_string(w, s) or_return
@@ -286,7 +286,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
case runtime.Type_Info_Integer: case runtime.Type_Info_Integer:
buf: [40]byte buf: [40]byte
u := cast_any_int_to_u128(ka) u := cast_any_int_to_u128(ka)
name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil) name = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil)
opt_write_key(w, opt, name) or_return opt_write_key(w, opt, name) or_return
case: return .Unsupported_Type case: return .Unsupported_Type
+2 -1
View File
@@ -175,7 +175,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
data = bytes.clone(data) data = bytes.clone(data)
} }
t := &Tokenizer{} t := new(Tokenizer)
init(t, string(data), path, error_handler) init(t, string(data), path, error_handler)
doc = new(Document) doc = new(Document)
@@ -403,6 +403,7 @@ destroy :: proc(doc: ^Document) {
} }
delete(doc.strings_to_free) delete(doc.strings_to_free)
free(doc.tokenizer)
free(doc) free(doc)
} }
+2 -2
View File
@@ -19,7 +19,7 @@ SUBTAG_NAME :: "name"
SUBTAG_POS :: "pos" SUBTAG_POS :: "pos"
SUBTAG_REQUIRED :: "required" SUBTAG_REQUIRED :: "required"
SUBTAG_HIDDEN :: "hidden" SUBTAG_HIDDEN :: "hidden"
SUBTAG_VARIADIC :: "variadic" SUBTAG_MANIFOLD :: "manifold"
SUBTAG_FILE :: "file" SUBTAG_FILE :: "file"
SUBTAG_PERMS :: "perms" SUBTAG_PERMS :: "perms"
SUBTAG_INDISTINCT :: "indistinct" SUBTAG_INDISTINCT :: "indistinct"
@@ -28,7 +28,7 @@ TAG_USAGE :: "usage"
UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>" UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>"
INTERNAL_VARIADIC_FLAG :: "varg" INTERNAL_OVERFLOW_FLAG :: #config(ODIN_CORE_FLAGS_OVERFLOW_FLAG, "overflow")
RESERVED_HELP_FLAG :: "help" RESERVED_HELP_FLAG :: "help"
RESERVED_HELP_FLAG_SHORT :: "h" RESERVED_HELP_FLAG_SHORT :: "h"
+16 -4
View File
@@ -20,6 +20,17 @@ The format is similar to the Odin binary's way of handling compiler flags.
-<map>:<key>=<value> set map[key] to value -<map>:<key>=<value> set map[key] to value
Unhandled Arguments:
All unhandled positional arguments are placed into the `overflow` field on a
struct, if it exists. In UNIX-style parsing, the existence of a `--` on the
command line will also pass all arguments afterwards into this field.
If desired, the name of the field may be changed from `overflow` to any string
by setting the `ODIN_CORE_FLAGS_OVERFLOW_FLAG` compile-time config option with
`-define:ODIN_CORE_FLAGS_OVERFLOW_FLAG=<name>`.
Struct Tags: Struct Tags:
Users of the `core:encoding/json` package may be familiar with using tags to Users of the `core:encoding/json` package may be familiar with using tags to
@@ -32,7 +43,7 @@ Under the `args` tag, there are the following subtags:
- `pos=N`: place positional argument `N` into this flag. - `pos=N`: place positional argument `N` into this flag.
- `hidden`: hide this flag from the usage documentation. - `hidden`: hide this flag from the usage documentation.
- `required`: cause verification to fail if this argument is not set. - `required`: cause verification to fail if this argument is not set.
- `variadic`: take all remaining arguments when set, UNIX-style only. - `manifold=N`: take several arguments at once, UNIX-style only.
- `file`: for `os.Handle` types, file open mode. - `file`: for `os.Handle` types, file open mode.
- `perms`: for `os.Handle` types, file open permissions. - `perms`: for `os.Handle` types, file open permissions.
- `indistinct`: allow the setting of distinct types by their base type. - `indistinct`: allow the setting of distinct types by their base type.
@@ -47,8 +58,9 @@ you want to require 3 and only 3 arguments in a dynamic array, you would
specify `required=3<4`. specify `required=3<4`.
`variadic` may be given a number (`variadic=N`) above 1 to limit how many extra `manifold` may be given a number (`manifold=N`) above 1 to limit how many extra
arguments it consumes. arguments it consumes at once. If this number is not specified, it will take as
many arguments as can be converted to the underlying element type.
`file` determines the file open mode for an `os.Handle`. `file` determines the file open mode for an `os.Handle`.
@@ -160,7 +172,7 @@ at parse time.
--flag --flag
--flag=argument --flag=argument
--flag argument --flag argument
--flag argument repeating-argument --flag argument (manifold-argument)
`-flag` may also be substituted for `--flag`. `-flag` may also be substituted for `--flag`.
+1 -1
View File
@@ -4,7 +4,7 @@ import "core:os"
Parse_Error_Reason :: enum { Parse_Error_Reason :: enum {
None, None,
// An extra positional argument was given, and there is no `varg` field. // An extra positional argument was given, and there is no `overflow` field.
Extra_Positional, Extra_Positional,
// The underlying type does not support the string value it is being set to. // The underlying type does not support the string value it is being set to.
Bad_Value, Bad_Value,
+3 -3
View File
@@ -107,14 +107,14 @@ main :: proc() {
// assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`, // assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`,
// (Variadic) Only available in UNIX style: // (Manifold) Only available in UNIX style:
// bots: [dynamic]string `args:"variadic=2,required"`, // bots: [dynamic]string `args:"manifold=2,required"`,
verbose: bool `usage:"Show verbose output."`, verbose: bool `usage:"Show verbose output."`,
debug: bool `args:"hidden" usage:"print debug info"`, debug: bool `args:"hidden" usage:"print debug info"`,
varg: [dynamic]string `usage:"Any extra arguments go here."`, overflow: [dynamic]string `usage:"Any extra arguments go here."`,
} }
opt: Options opt: Options
+4 -4
View File
@@ -33,9 +33,9 @@ push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: stri
field, index, has_pos_assigned := get_field_by_pos(model, pos) field, index, has_pos_assigned := get_field_by_pos(model, pos)
if !has_pos_assigned { if !has_pos_assigned {
when intrinsics.type_has_field(T, INTERNAL_VARIADIC_FLAG) { when intrinsics.type_has_field(T, INTERNAL_OVERFLOW_FLAG) {
// Add it to the fallback array. // Add it to the fallback array.
field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG) field = reflect.struct_field_by_name(T, INTERNAL_OVERFLOW_FLAG)
} else { } else {
return Parse_Error { return Parse_Error {
.Extra_Positional, .Extra_Positional,
@@ -117,8 +117,8 @@ set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args
case runtime.Type_Info_Dynamic_Array: case runtime.Type_Info_Dynamic_Array:
future_args = 1 future_args = 1
if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok { if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic { if length, is_manifold := get_struct_subtag(tag, SUBTAG_MANIFOLD); is_manifold {
// Variadic arrays may specify how many arguments they consume at once. // Manifold arrays may specify how many arguments they consume at once.
// Otherwise, they take everything that's left. // Otherwise, they take everything that's left.
if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok { if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
future_args = cast(int)value future_args = cast(int)value
+1 -1
View File
@@ -95,7 +95,7 @@ parse_one_unix_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (
// `--`, and only `--`. // `--`, and only `--`.
// Everything from now on will be treated as an argument. // Everything from now on will be treated as an argument.
future_args = max(int) future_args = max(int)
current_flag = INTERNAL_VARIADIC_FLAG current_flag = INTERNAL_OVERFLOW_FLAG
return return
} }
} }
+13 -8
View File
@@ -59,7 +59,8 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
} }
} }
if pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS); has_pos { pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS)
if has_pos {
#partial switch specific_type_info in field.type.variant { #partial switch specific_type_info in field.type.variant {
case runtime.Type_Info_Map: case runtime.Type_Info_Map:
fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.", fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.",
@@ -79,7 +80,7 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.", fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.",
model_type, field.name, loc = loc) model_type, field.name, loc = loc)
fmt.assertf(field.name != INTERNAL_VARIADIC_FLAG, "%T.%s is defined as required. This is disallowed.", fmt.assertf(field.name != INTERNAL_OVERFLOW_FLAG, "%T.%s is defined as required. This is disallowed.",
model_type, field.name, loc = loc) model_type, field.name, loc = loc)
if len(requirement) > 0 { if len(requirement) > 0 {
@@ -109,24 +110,28 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
} }
} }
if length, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic { if length, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
fmt.assertf(!has_pos,
"%T.%s has both `%s` and `%s` defined. This is disallowed.\n\tSuggestion: Use a dynamic array field named `%s` to accept unspecified positional arguments.",
model_type, field.name, SUBTAG_POS, SUBTAG_MANIFOLD, INTERNAL_OVERFLOW_FLAG, loc = loc)
if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok { if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok {
fmt.assertf(value > 0, fmt.assertf(value > 0,
"%T.%s has `%s` set to %i. It must be greater than zero.", "%T.%s has `%s` set to %i. It must be greater than zero.",
model_type, field.name, value, SUBTAG_VARIADIC, loc = loc) model_type, field.name, value, SUBTAG_MANIFOLD, loc = loc)
fmt.assertf(value != 1, fmt.assertf(value != 1,
"%T.%s has `%s` set to 1. This has no effect.", "%T.%s has `%s` set to 1. This is equivalent to not defining `%s`.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc) model_type, field.name, SUBTAG_MANIFOLD, SUBTAG_MANIFOLD, loc = loc)
} }
#partial switch specific_type_info in field.type.variant { #partial switch specific_type_info in field.type.variant {
case runtime.Type_Info_Dynamic_Array: case runtime.Type_Info_Dynamic_Array:
fmt.assertf(style != .Odin, fmt.assertf(style != .Odin,
"%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.", "%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc) model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
case: case:
fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.", fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc) model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
} }
} }
+2 -2
View File
@@ -6,7 +6,7 @@ package flags
Parsing_Style :: enum { Parsing_Style :: enum {
// Odin-style: `-flag`, `-flag:option`, `-map:key=value` // Odin-style: `-flag`, `-flag:option`, `-map:key=value`
Odin, Odin,
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument` // UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument (manifold-argument)`
Unix, Unix,
} }
@@ -61,7 +61,7 @@ parse :: proc(
} }
case .Unix: case .Unix:
// Support for `-flag argument (repeating-argument ...)` // Support for `-flag argument (manifold-argument ...)`
future_args: int future_args: int
current_flag: string current_flag: string
+15 -15
View File
@@ -30,18 +30,18 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
is_positional: bool, is_positional: bool,
is_required: bool, is_required: bool,
is_boolean: bool, is_boolean: bool,
is_variadic: bool, is_manifold: bool,
variadic_length: int, manifold_length: int,
} }
// //
// POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ... // POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ...
// //
sort_flags :: proc(i, j: Flag) -> slice.Ordering { sort_flags :: proc(i, j: Flag) -> slice.Ordering {
// `varg` goes to the end. // `overflow` goes to the end.
if i.name == INTERNAL_VARIADIC_FLAG { if i.name == INTERNAL_OVERFLOW_FLAG {
return .Greater return .Greater
} else if j.name == INTERNAL_VARIADIC_FLAG { } else if j.name == INTERNAL_OVERFLOW_FLAG {
return .Less return .Less
} }
@@ -120,10 +120,10 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
flag.is_required = true flag.is_required = true
flag.required_min, flag.required_max, _ = parse_requirements(requirement) flag.required_min, flag.required_max, _ = parse_requirements(requirement)
} }
if length_str, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic { if length_str, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
flag.is_variadic = true flag.is_manifold = true
if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok { if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok {
flag.variadic_length = cast(int)length flag.manifold_length = cast(int)length
} }
} }
} }
@@ -147,15 +147,15 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
case runtime.Type_Info_Dynamic_Array: case runtime.Type_Info_Dynamic_Array:
requirement_spec := describe_array_requirements(flag) requirement_spec := describe_array_requirements(flag)
if flag.is_variadic || flag.name == INTERNAL_VARIADIC_FLAG { if flag.is_manifold || flag.name == INTERNAL_OVERFLOW_FLAG {
if flag.variadic_length == 0 { if flag.manifold_length == 0 {
flag.type_description = fmt.tprintf("<%v, ...>%s", flag.type_description = fmt.tprintf("<%v, ...>%s",
specific_type_info.elem.id, specific_type_info.elem.id,
requirement_spec) requirement_spec)
} else { } else {
flag.type_description = fmt.tprintf("<%v, %i at once>%s", flag.type_description = fmt.tprintf("<%v, %i at once>%s",
specific_type_info.elem.id, specific_type_info.elem.id,
flag.variadic_length, flag.manifold_length,
requirement_spec) requirement_spec)
} }
} else { } else {
@@ -177,7 +177,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
} }
} }
if flag.name == INTERNAL_VARIADIC_FLAG { if flag.name == INTERNAL_OVERFLOW_FLAG {
flag.full_length = len(flag.type_description) flag.full_length = len(flag.type_description)
} else if flag.is_boolean { } else if flag.is_boolean {
flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description) flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description)
@@ -201,13 +201,13 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
strings.write_string(&builder, program) strings.write_string(&builder, program)
for flag in visible_flags { for flag in visible_flags {
if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_VARIADIC_FLAG) { if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_OVERFLOW_FLAG) {
continue continue
} }
strings.write_byte(&builder, ' ') strings.write_byte(&builder, ' ')
if flag.name == INTERNAL_VARIADIC_FLAG { if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, "...") strings.write_string(&builder, "...")
continue continue
} }
@@ -252,7 +252,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
strings.write_byte(&builder, '\t') strings.write_byte(&builder, '\t')
if flag.name == INTERNAL_VARIADIC_FLAG { if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, flag.type_description) strings.write_string(&builder, flag.type_description)
} else { } else {
strings.write_string(&builder, flag_prefix) strings.write_string(&builder, flag_prefix)
+4 -4
View File
@@ -1122,7 +1122,7 @@ _fmt_int :: proc(fi: ^Info, u: u64, base: int, is_signed: bool, bit_size: int, d
flags: strconv.Int_Flags flags: strconv.Int_Flags
if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} } if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
if fi.plus { flags += {.Plus} } if fi.plus { flags += {.Plus} }
s := strconv.append_bits(buf[start:], u, base, is_signed, bit_size, digits, flags) s := strconv.write_bits(buf[start:], u, base, is_signed, bit_size, digits, flags)
prev_zero := fi.zero prev_zero := fi.zero
defer fi.zero = prev_zero defer fi.zero = prev_zero
fi.zero = false fi.zero = false
@@ -1207,7 +1207,7 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i
flags: strconv.Int_Flags flags: strconv.Int_Flags
if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} } if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
if fi.plus { flags += {.Plus} } if fi.plus { flags += {.Plus} }
s := strconv.append_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags) s := strconv.write_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags)
if fi.hash && fi.zero && fi.indent == 0 { if fi.hash && fi.zero && fi.indent == 0 {
c: byte = 0 c: byte = 0
@@ -1272,7 +1272,7 @@ _fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: st
} }
buf: [256]byte buf: [256]byte
str := strconv.append_float(buf[:], amt, 'f', prec, 64) str := strconv.write_float(buf[:], amt, 'f', prec, 64)
// Add the unit at the end. // Add the unit at the end.
copy(buf[len(str):], units[off:off+unit_len]) copy(buf[len(str):], units[off:off+unit_len])
@@ -1424,7 +1424,7 @@ _fmt_float_as :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune, float_fmt: b
buf: [386]byte buf: [386]byte
// Can return "NaN", "+Inf", "-Inf", "+<value>", "-<value>". // Can return "NaN", "+Inf", "-Inf", "+<value>", "-<value>".
str := strconv.append_float(buf[:], v, float_fmt, prec, bit_size) str := strconv.write_float(buf[:], v, float_fmt, prec, bit_size)
if !fi.plus { if !fi.plus {
// Strip sign from "+<value>" but not "+Inf". // Strip sign from "+<value>" but not "+Inf".
+8 -8
View File
@@ -22,12 +22,12 @@ write_ptr_at :: proc(w: Writer_At, p: rawptr, byte_size: int, offset: i64, n_wri
write_u64 :: proc(w: Writer, i: u64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { write_u64 :: proc(w: Writer, i: u64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [32]byte buf: [32]byte
s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil) s := strconv.write_bits(buf[:], i, base, false, 64, strconv.digits, nil)
return write_string(w, s, n_written) return write_string(w, s, n_written)
} }
write_i64 :: proc(w: Writer, i: i64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { write_i64 :: proc(w: Writer, i: i64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [32]byte buf: [32]byte
s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil) s := strconv.write_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
return write_string(w, s, n_written) return write_string(w, s, n_written)
} }
@@ -40,18 +40,18 @@ write_int :: proc(w: Writer, i: int, base: int = 10, n_written: ^int = nil) -> (
write_u128 :: proc(w: Writer, i: u128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { write_u128 :: proc(w: Writer, i: u128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [39]byte buf: [39]byte
s := strconv.append_bits_128(buf[:], i, base, false, 128, strconv.digits, nil) s := strconv.write_bits_128(buf[:], i, base, false, 128, strconv.digits, nil)
return write_string(w, s, n_written) return write_string(w, s, n_written)
} }
write_i128 :: proc(w: Writer, i: i128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { write_i128 :: proc(w: Writer, i: i128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [40]byte buf: [40]byte
s := strconv.append_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil) s := strconv.write_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil)
return write_string(w, s, n_written) return write_string(w, s, n_written)
} }
write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: Error) { write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [386]byte buf: [386]byte
str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val)) str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
s := buf[:len(str)+1] s := buf[:len(str)+1]
if s[1] == '+' || s[1] == '-' { if s[1] == '+' || s[1] == '-' {
s = s[1:] s = s[1:]
@@ -67,7 +67,7 @@ write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: E
write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: Error) { write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [386]byte buf: [386]byte
str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val)) str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
s := buf[:len(str)+1] s := buf[:len(str)+1]
if s[1] == '+' || s[1] == '-' { if s[1] == '+' || s[1] == '-' {
s = s[1:] s = s[1:]
@@ -83,7 +83,7 @@ write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: E
write_f64 :: proc(w: Writer, val: f64, n_written: ^int = nil) -> (n: int, err: Error) { write_f64 :: proc(w: Writer, val: f64, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [386]byte buf: [386]byte
str := strconv.append_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val)) str := strconv.write_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val))
s := buf[:len(str)+1] s := buf[:len(str)+1]
if s[1] == '+' || s[1] == '-' { if s[1] == '+' || s[1] == '-' {
s = s[1:] s = s[1:]
@@ -130,7 +130,7 @@ write_encoded_rune :: proc(w: Writer, r: rune, write_quote := true, n_written: ^
write_string(w, `\x`, &n) or_return write_string(w, `\x`, &n) or_return
buf: [2]byte buf: [2]byte
s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil) s := strconv.write_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil)
switch len(s) { switch len(s) {
case 0: case 0:
write_string(w, "00", &n) or_return write_string(w, "00", &n) or_return
+2 -3
View File
@@ -160,6 +160,5 @@ destroy :: proc {
int_destroy :: proc(integers: ..^Int) int_destroy :: proc(integers: ..^Int)
*/ */
int_destroy, int_destroy,
} internal_rat_destroy,
}
+2 -2
View File
@@ -1660,13 +1660,13 @@ internal_int_sqrt :: proc(dest, src: ^Int, allocator := context.allocator) -> (e
if internal_gte(y, x) { if internal_gte(y, x) {
internal_swap(dest, x) internal_swap(dest, x)
return nil return internal_clamp(dest)
} }
internal_swap(x, y) internal_swap(x, y)
} }
internal_swap(dest, x) internal_swap(dest, x)
return err return internal_clamp(dest)
} }
internal_sqrt :: proc { internal_int_sqrt, } internal_sqrt :: proc { internal_int_sqrt, }
+1 -1
View File
@@ -1370,8 +1370,8 @@ _private_int_div_recursive :: proc(quotient, remainder, a, b: ^Int, allocator :=
/* /*
Slower bit-bang division... also smaller. Slower bit-bang division... also smaller.
Prefer `_int_div_school` for speed.
*/ */
@(deprecated="Use `_int_div_school`, it's 3.5x faster.")
_private_int_div_small :: proc(quotient, remainder, numerator, denominator: ^Int) -> (err: Error) { _private_int_div_small :: proc(quotient, remainder, numerator, denominator: ^Int) -> (err: Error) {
ta, tb, tq, q := &Int{}, &Int{}, &Int{}, &Int{} ta, tb, tq, q := &Int{}, &Int{}, &Int{}, &Int{}
+1 -1
View File
@@ -310,7 +310,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
res.sign = sign res.sign = sign
} }
return nil return internal_clamp(res)
} }
+5 -3
View File
@@ -157,6 +157,8 @@ internal_rat_norm :: proc(z: ^Rat, allocator := context.allocator) -> (err: Erro
z.b.sign = .Zero_or_Positive z.b.sign = .Zero_or_Positive
f := &Int{} f := &Int{}
defer internal_int_destroy(f)
internal_int_gcd(f, &z.a, &z.b) or_return internal_int_gcd(f, &z.a, &z.b) or_return
if !internal_int_equals_digit(f, 1) { if !internal_int_equals_digit(f, 1) {
f.sign = .Zero_or_Positive f.sign = .Zero_or_Positive
@@ -378,9 +380,6 @@ internal_rat_to_float :: proc($T: typeid, z: ^Rat, allocator := context.allocato
} }
has_sign := a.sign != b.sign has_sign := a.sign != b.sign
defer if has_sign {
f = -builtin.abs(f)
}
exp := alen - blen exp := alen - blen
a2, b2 := &Int{}, &Int{} a2, b2 := &Int{}, &Int{}
@@ -440,6 +439,9 @@ internal_rat_to_float :: proc($T: typeid, z: ^Rat, allocator := context.allocato
if math.is_inf(f, 0) { if math.is_inf(f, 0) {
exact = false exact = false
} }
if has_sign {
f = -builtin.abs(f)
}
return return
} }
+10 -5
View File
@@ -103,7 +103,7 @@ round :: proc(x: $T/Fixed($Backing, $Fraction_Width)) -> Backing {
} }
@(require_results) @(require_results)
append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string { write :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
Integer_Width :: 8*size_of(Backing) - Fraction_Width Integer_Width :: 8*size_of(Backing) - Fraction_Width
x := x x := x
@@ -124,16 +124,16 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
when size_of(Backing) < 16 { when size_of(Backing) < 16 {
T :: u64 T :: u64
append_uint :: strconv.append_uint write_uint :: strconv.write_uint
} else { } else {
T :: u128 T :: u128
append_uint :: strconv.append_u128 write_uint :: strconv.write_u128
} }
integer := T(x.i) >> Fraction_Width integer := T(x.i) >> Fraction_Width
fraction := T(x.i) & (1<<Fraction_Width - 1) fraction := T(x.i) & (1<<Fraction_Width - 1)
s := append_uint(buf[i:], integer, 10) s := write_uint(buf[i:], integer, 10)
i += len(s) i += len(s)
if fraction != 0 { if fraction != 0 {
buf[i] = '.' buf[i] = '.'
@@ -155,7 +155,7 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
@(require_results) @(require_results)
to_string :: proc(x: $T/Fixed($Backing, $Fraction_Width), allocator := context.allocator) -> string { to_string :: proc(x: $T/Fixed($Backing, $Fraction_Width), allocator := context.allocator) -> string {
buf: [48]byte buf: [48]byte
s := append(buf[:], x) s := write(buf[:], x)
str := make([]byte, len(s), allocator) str := make([]byte, len(s), allocator)
copy(str, s) copy(str, s)
return string(str) return string(str)
@@ -294,3 +294,8 @@ _power_of_two_table := [129]string{
"85070591730234615865843651857942052864", "85070591730234615865843651857942052864",
"170141183460469231731687303715884105728", "170141183460469231731687303715884105728",
} }
@(deprecated="Use write instead")
append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
return write(dst, x)
}
+412 -248
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -1,7 +1,7 @@
package mem package mem
import "base:runtime" import "base:runtime"
import "base:sanitizer" // import "base:sanitizer"
/* /*
Rollback stack default block size. Rollback stack default block size.
@@ -134,7 +134,7 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
stack.head.next_block = nil stack.head.next_block = nil
stack.head.last_alloc = nil stack.head.last_alloc = nil
stack.head.offset = 0 stack.head.offset = 0
sanitizer.address_poison(stack.head.buffer) // sanitizer.address_poison(stack.head.buffer)
} }
/* /*
@@ -241,7 +241,7 @@ rb_alloc_bytes_non_zeroed :: proc(
block.offset = cast(uintptr)len(block.buffer) block.offset = cast(uintptr)len(block.buffer)
} }
res := ptr[:size] res := ptr[:size]
sanitizer.address_unpoison(res) // sanitizer.address_unpoison(res)
return res, nil return res, nil
} }
return nil, .Out_Of_Memory return nil, .Out_Of_Memory
@@ -338,7 +338,7 @@ rb_resize_bytes_non_zeroed :: proc(
block.offset += cast(uintptr)size - cast(uintptr)old_size block.offset += cast(uintptr)size - cast(uintptr)old_size
} }
res := (ptr)[:size] res := (ptr)[:size]
sanitizer.address_unpoison(res) // sanitizer.address_unpoison(res)
#no_bounds_check return res, nil #no_bounds_check return res, nil
} }
} }
+15 -14
View File
@@ -10,7 +10,7 @@
package mem_tlsf package mem_tlsf
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer" // import "base:sanitizer"
import "base:runtime" import "base:runtime"
// log2 of number of linear subdivisions of block sizes. // log2 of number of linear subdivisions of block sizes.
@@ -210,7 +210,7 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
return nil, .Out_Of_Memory return nil, .Out_Of_Memory
} }
sanitizer.address_poison(new_pool_buf) // sanitizer.address_poison(new_pool_buf)
// Allocate a new link in the `control.pool` tracking structure. // Allocate a new link in the `control.pool` tracking structure.
new_pool := new_clone(Pool{ new_pool := new_clone(Pool{
@@ -257,7 +257,7 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
return block_prepare_used(control, block, adjust) return block_prepare_used(control, block, adjust)
} }
@(private, require_results, no_sanitize_address) @(private, require_results)
alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
res, err = alloc_bytes_non_zeroed(control, size, align) res, err = alloc_bytes_non_zeroed(control, size, align)
if err == nil { if err == nil {
@@ -267,6 +267,7 @@ alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byt
} }
@(no_sanitize_address)
free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) { free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
assert(control != nil) assert(control != nil)
// `size` is currently ignored // `size` is currently ignored
@@ -276,7 +277,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
block := block_from_ptr(ptr) block := block_from_ptr(ptr)
assert(!block_is_free(block), "block already marked as free") // double free assert(!block_is_free(block), "block already marked as free") // double free
sanitizer.address_poison(ptr, block.size) // sanitizer.address_poison(ptr, block.size)
block_mark_as_free(block) block_mark_as_free(block)
block = block_merge_prev(control, block) block = block_merge_prev(control, block)
block = block_merge_next(control, block) block = block_merge_next(control, block)
@@ -320,7 +321,7 @@ resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, align
block_trim_used(control, block, adjust) block_trim_used(control, block, adjust)
res = ([^]byte)(ptr)[:new_size] res = ([^]byte)(ptr)[:new_size]
sanitizer.address_unpoison(res) // sanitizer.address_unpoison(res)
if min_size < new_size { if min_size < new_size {
to_zero := ([^]byte)(ptr)[min_size:new_size] to_zero := ([^]byte)(ptr)[min_size:new_size]
@@ -483,19 +484,19 @@ block_mark_as_used :: proc(block: ^Block_Header) {
block_set_used(block) block_set_used(block)
} }
@(private, require_results, no_sanitize_address) @(private, require_results)
align_up :: proc(x, align: uint) -> (aligned: uint) { align_up :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
return (x + (align - 1)) &~ (align - 1) return (x + (align - 1)) &~ (align - 1)
} }
@(private, require_results, no_sanitize_address) @(private, require_results)
align_down :: proc(x, align: uint) -> (aligned: uint) { align_down :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
return x - (x & (align - 1)) return x - (x & (align - 1))
} }
@(private, require_results, no_sanitize_address) @(private, require_results)
align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) { align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
align_mask := uintptr(align) - 1 align_mask := uintptr(align) - 1
@@ -505,7 +506,7 @@ align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
} }
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
@(private, require_results, no_sanitize_address) @(private, require_results)
adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) { adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
if size == 0 { if size == 0 {
return 0 return 0
@@ -519,7 +520,7 @@ adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
} }
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
@(private, require_results, no_sanitize_address) @(private, require_results)
adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) { adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
if size == 0 { if size == 0 {
return 0, nil return 0, nil
@@ -537,7 +538,7 @@ adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err:
// TLSF utility functions. In most cases these are direct translations of // TLSF utility functions. In most cases these are direct translations of
// the documentation in the research paper. // the documentation in the research paper.
@(optimization_mode="favor_size", private, require_results, no_sanitize_address) @(optimization_mode="favor_size", private, require_results)
mapping_insert :: proc(size: uint) -> (fl, sl: i32) { mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
if size < SMALL_BLOCK_SIZE { if size < SMALL_BLOCK_SIZE {
// Store small blocks in first list. // Store small blocks in first list.
@@ -550,7 +551,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
return return
} }
@(optimization_mode="favor_size", private, require_results, no_sanitize_address) @(optimization_mode="favor_size", private, require_results)
mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
rounded = size rounded = size
if size >= SMALL_BLOCK_SIZE { if size >= SMALL_BLOCK_SIZE {
@@ -561,7 +562,7 @@ mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
} }
// This version rounds up to the next block size (for allocations) // This version rounds up to the next block size (for allocations)
@(optimization_mode="favor_size", private, require_results, no_sanitize_address) @(optimization_mode="favor_size", private, require_results)
mapping_search :: proc(size: uint) -> (fl, sl: i32) { mapping_search :: proc(size: uint) -> (fl, sl: i32) {
return mapping_insert(mapping_round(size)) return mapping_insert(mapping_round(size))
} }
@@ -788,7 +789,7 @@ block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint
block_trim_free(control, block, size) block_trim_free(control, block, size)
block_mark_as_used(block) block_mark_as_used(block)
res = ([^]byte)(block_to_ptr(block))[:size] res = ([^]byte)(block_to_ptr(block))[:size]
sanitizer.address_unpoison(res) // sanitizer.address_unpoison(res)
} }
return return
} }
+12 -12
View File
@@ -3,7 +3,7 @@ package mem_virtual
import "core:mem" import "core:mem"
import "core:sync" import "core:sync"
import "base:sanitizer" // import "base:sanitizer"
Arena_Kind :: enum uint { Arena_Kind :: enum uint {
Growing = 0, // Chained memory blocks (singly linked list). Growing = 0, // Chained memory blocks (singly linked list).
@@ -55,7 +55,7 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
if arena.minimum_block_size == 0 { if arena.minimum_block_size == 0 {
arena.minimum_block_size = reserved arena.minimum_block_size = reserved
} }
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return return
} }
@@ -68,7 +68,7 @@ arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_R
arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return
arena.total_used = 0 arena.total_used = 0
arena.total_reserved = arena.curr_block.reserved arena.total_reserved = arena.curr_block.reserved
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return return
} }
@@ -82,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
arena.kind = .Buffer arena.kind = .Buffer
sanitizer.address_poison(buffer[:]) // sanitizer.address_poison(buffer[:])
block_base := raw_data(buffer) block_base := raw_data(buffer)
block := (^Memory_Block)(block_base) block := (^Memory_Block)(block_base)
@@ -163,7 +163,7 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
arena.total_used = arena.curr_block.used arena.total_used = arena.curr_block.used
} }
sanitizer.address_unpoison(data) // sanitizer.address_unpoison(data)
return return
} }
@@ -182,7 +182,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos]) mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos])
} }
arena.total_used = arena.curr_block.used arena.total_used = arena.curr_block.used
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return true return true
} else if pos == 0 { } else if pos == 0 {
arena.total_used = 0 arena.total_used = 0
@@ -200,7 +200,7 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat
arena.total_reserved -= free_block.reserved arena.total_reserved -= free_block.reserved
arena.curr_block = free_block.prev arena.curr_block = free_block.prev
sanitizer.address_poison(free_block.base[:free_block.committed]) // sanitizer.address_poison(free_block.base[:free_block.committed])
memory_block_dealloc(free_block) memory_block_dealloc(free_block)
} }
} }
@@ -219,9 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil { if arena.curr_block != nil {
curr_block_used := int(arena.curr_block.used) curr_block_used := int(arena.curr_block.used)
arena.curr_block.used = 0 arena.curr_block.used = 0
sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used]) // sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used])
mem.zero(arena.curr_block.base, curr_block_used) mem.zero(arena.curr_block.base, curr_block_used)
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
} }
arena.total_used = 0 arena.total_used = 0
case .Static, .Buffer: case .Static, .Buffer:
@@ -349,7 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
if size < old_size { if size < old_size {
// shrink data in-place // shrink data in-place
data = old_data[:size] data = old_data[:size]
sanitizer.address_poison(old_data[size:old_size]) // sanitizer.address_poison(old_data[size:old_size])
return return
} }
@@ -363,7 +363,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return _ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
arena.total_used += block.used - prev_used arena.total_used += block.used - prev_used
data = block.base[start:new_end] data = block.base[start:new_end]
sanitizer.address_unpoison(data) // sanitizer.address_unpoison(data)
return return
} }
} }
@@ -374,7 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
return return
} }
copy(new_memory, old_data[:old_size]) copy(new_memory, old_data[:old_size])
sanitizer.address_poison(old_data[:old_size]) // sanitizer.address_poison(old_data[:old_size])
return new_memory, nil return new_memory, nil
case .Query_Features: case .Query_Features:
set := (^mem.Allocator_Mode_Set)(old_memory) set := (^mem.Allocator_Mode_Set)(old_memory)
+5 -5
View File
@@ -2,7 +2,7 @@ package mem_virtual
import "core:mem" import "core:mem"
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer" // import "base:sanitizer"
import "base:runtime" import "base:runtime"
_ :: runtime _ :: runtime
@@ -22,7 +22,7 @@ reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Erro
@(no_sanitize_address) @(no_sanitize_address)
commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
sanitizer.address_unpoison(data, size) // sanitizer.address_unpoison(data, size)
return _commit(data, size) return _commit(data, size)
} }
@@ -35,13 +35,13 @@ reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: All
@(no_sanitize_address) @(no_sanitize_address)
decommit :: proc "contextless" (data: rawptr, size: uint) { decommit :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_poison(data, size) // sanitizer.address_poison(data, size)
_decommit(data, size) _decommit(data, size)
} }
@(no_sanitize_address) @(no_sanitize_address)
release :: proc "contextless" (data: rawptr, size: uint) { release :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_unpoison(data, size) // sanitizer.address_unpoison(data, size)
_release(data, size) _release(data, size)
} }
@@ -179,7 +179,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
data = block.base[block.used+alignment_offset:][:min_size] data = block.base[block.used+alignment_offset:][:min_size]
block.used += size block.used += size
sanitizer.address_unpoison(data) // sanitizer.address_unpoison(data)
return return
} }
+4
View File
@@ -64,6 +64,7 @@ Network_Error :: union #shared_nil {
UDP_Recv_Error, UDP_Recv_Error,
Shutdown_Error, Shutdown_Error,
Interfaces_Error, Interfaces_Error,
Socket_Info_Error,
Socket_Option_Error, Socket_Option_Error,
Set_Blocking_Error, Set_Blocking_Error,
Parse_Endpoint_Error, Parse_Endpoint_Error,
@@ -260,6 +261,9 @@ DNS_Configuration :: struct {
resolv_conf: string, resolv_conf: string,
hosts_file: string, hosts_file: string,
resolv_conf_buf: [128]u8,
hosts_file_buf: [128]u8,
// TODO: Allow loading these up with `reload_configuration()` call or the like, // TODO: Allow loading these up with `reload_configuration()` call or the like,
// so we don't have to do it each call. // so we don't have to do it each call.
name_servers: []Endpoint, name_servers: []Endpoint,
+29 -47
View File
@@ -22,69 +22,43 @@ package net
Haesbaert: Security fixes Haesbaert: Security fixes
*/ */
@(require) import "base:runtime"
import "core:mem" import "core:mem"
import "core:strings" import "core:strings"
import "core:time" import "core:time"
import "core:os" import "core:os"
import "core:math/rand" import "core:math/rand"
/* @(require) import "core:sync"
Default configuration for DNS resolution.
*/ dns_config_initialized: sync.Once
when ODIN_OS == .Windows { when ODIN_OS == .Windows {
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ dns_configuration := DNS_Configuration{
resolv_conf = "", resolv_conf = "",
hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts", hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts",
} }
} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { } else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ dns_configuration := DNS_Configuration{
resolv_conf = "/etc/resolv.conf", resolv_conf = "/etc/resolv.conf",
hosts_file = "/etc/hosts", hosts_file = "/etc/hosts",
} }
} else { } else {
#panic("Please add a configuration for this OS.") #panic("Please add a configuration for this OS.")
} }
@(init) /*
Replaces environment placeholders in `dns_configuration`. Only necessary on Windows.
Is automatically called, once, by `get_dns_records_*`.
*/
@(private)
init_dns_configuration :: proc() { init_dns_configuration :: proc() {
/* when ODIN_OS == .Windows {
Resolve %ENVIRONMENT% placeholders in their paths. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
*/ val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator)
dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf) copy(dns_configuration.hosts_file_buf[:], val)
dns_configuration.hosts_file, _ = replace_environment_path(dns_configuration.hosts_file) dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)])
}
@(fini, private)
destroy_dns_configuration :: proc() {
delete(dns_configuration.resolv_conf)
dns_configuration.resolv_conf = ""
delete(dns_configuration.hosts_file)
dns_configuration.hosts_file = ""
}
dns_configuration := DEFAULT_DNS_CONFIGURATION
// Always allocates for consistency.
replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) {
// Nothing to replace. Return a clone of the original.
if strings.count(path, "%") != 2 {
return strings.clone(path, allocator), true
} }
left := strings.index(path, "%") + 1
assert(left > 0 && left <= len(path)) // should be covered by there being two %
right := strings.index(path[left:], "%") + 1
assert(right > 0 && right <= len(path)) // should be covered by there being two %
env_key := path[left: right]
env_val := os.get_env(env_key, allocator)
defer delete(env_val)
res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1, allocator)
return res, true
} }
/* /*
Resolves a hostname to exactly one IP4 and IP6 endpoint. Resolves a hostname to exactly one IP4 and IP6 endpoint.
It's then up to you which one you use. It's then up to you which one you use.
@@ -204,6 +178,9 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net
See `destroy_records`. See `destroy_records`.
*/ */
get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
when ODIN_OS == .Windows {
sync.once_do(&dns_config_initialized, init_dns_configuration)
}
return _get_dns_records_os(hostname, type, allocator) return _get_dns_records_os(hostname, type, allocator)
} }
@@ -219,6 +196,9 @@ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocat
See `destroy_records`. See `destroy_records`.
*/ */
get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
when ODIN_OS == .Windows {
sync.once_do(&dns_config_initialized, init_dns_configuration)
}
context.allocator = allocator context.allocator = allocator
if type != .SRV { if type != .SRV {
@@ -440,6 +420,8 @@ load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (
splits := strings.fields(line) splits := strings.fields(line)
defer delete(splits) defer delete(splits)
(len(splits) >= 2) or_continue
ip_str := splits[0] ip_str := splits[0]
addr := parse_address(ip_str) addr := parse_address(ip_str)
if addr == nil { if addr == nil {
@@ -886,4 +868,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator
xid = hdr.id xid = hdr.id
return _records[:], xid, true return _records[:], xid, true
} }
+1 -1
View File
@@ -79,4 +79,4 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator :
} }
return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:]) return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:])
} }
+17
View File
@@ -246,6 +246,23 @@ Shutdown_Error :: enum i32 {
Unknown, Unknown,
} }
Socket_Info_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Socket is invalid or not connected, or the manner given is invalid.
Invalid_Argument,
// The socket is valid, but unsupported by this opperation.
Unsupported_Socket,
// Connection was closed/aborted/shutdown.
Connection_Closed,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Socket_Option_Error :: enum i32 { Socket_Option_Error :: enum i32 {
None, None,
// No network connection, or the network stack is not initialized. // No network connection, or the network stack is not initialized.
+17
View File
@@ -226,6 +226,23 @@ _shutdown_error :: proc() -> Shutdown_Error {
} }
} }
_socket_info_error :: proc() -> Socket_Info_Error {
#partial switch posix.errno() {
case .EBADF, .ENOTSOCK:
return .Invalid_Argument
case .ENOTCONN:
return .Network_Unreachable
case .EOPNOTSUPP:
return .Unsupported_Socket
case .EINVAL:
return .Connection_Closed
case .ENOBUFS:
return .Insufficient_Resources
case:
return .Unknown
}
}
_socket_option_error :: proc() -> Socket_Option_Error { _socket_option_error :: proc() -> Socket_Option_Error {
#partial switch posix.errno() { #partial switch posix.errno() {
case .ENOBUFS: case .ENOBUFS:
+18
View File
@@ -255,6 +255,24 @@ _shutdown_error :: proc(errno: freebsd.Errno) -> Shutdown_Error {
} }
} }
_socket_info_error :: proc(errno: freebsd.Errno) -> Socket_Info_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTSOCK, .EINVAL, .EFAULT:
return .Invalid_Argument
case .ENOTCONN:
return .Network_Unreachable
case .ECONNRESET:
return .Connection_Closed
case .ENOBUFS:
return .Insufficient_Resources
case:
return .Unknown
}
}
_socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error { _socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error {
assert(errno != nil) assert(errno != nil)
_last_error = errno _last_error = errno
+16
View File
@@ -258,6 +258,22 @@ _shutdown_error :: proc(errno: linux.Errno) -> Shutdown_Error {
} }
} }
_socket_info_error :: proc(errno: linux.Errno) -> Socket_Info_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT, .EINVAL:
return .Invalid_Argument
case .ENOTCONN:
return .Network_Unreachable
case .ENOBUFS:
return .Insufficient_Resources
case:
return .Unknown
}
}
_socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error { _socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error {
assert(errno != nil) assert(errno != nil)
_last_error = errno _last_error = errno
+7
View File
@@ -18,3 +18,10 @@ _last_platform_error_string :: proc() -> string {
_set_last_platform_error :: proc(err: i32) { _set_last_platform_error :: proc(err: i32) {
_last_error = err _last_error = err
} }
Parse_Endpoint_Error :: enum u32 {
None = 0,
Bad_Port = 1,
Bad_Address,
Bad_Hostname,
}
+11
View File
@@ -234,6 +234,17 @@ _shutdown_error :: proc() -> Shutdown_Error {
} }
} }
_socket_info_error :: proc() -> Socket_Info_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEINVAL:
return .Invalid_Argument
case .WSANOTINITIALISED, .WSAENETDOWN, .WSAENOTCONN:
return .Network_Unreachable
case:
return .Unknown
}
}
_socket_option_error :: proc() -> Socket_Option_Error { _socket_option_error :: proc() -> Socket_Option_Error {
#partial switch win.System_Error(win.WSAGetLastError()) { #partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAENETDOWN, .WSANOTINITIALISED: case .WSAENETDOWN, .WSANOTINITIALISED:
+8 -1
View File
@@ -174,10 +174,17 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC
/* /*
Returns the endpoint that the given socket is listening / bound on. Returns the endpoint that the given socket is listening / bound on.
*/ */
bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Listen_Error) { bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Socket_Info_Error) {
return _bound_endpoint(socket) return _bound_endpoint(socket)
} }
/*
Returns the endpoint that the given socket is connected to. (Peer's endpoint)
*/
peer_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Socket_Info_Error) {
return _peer_endpoint(socket)
}
accept_tcp :: proc(socket: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) { accept_tcp :: proc(socket: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
return _accept_tcp(socket, options) return _accept_tcp(socket, options)
} }
+15 -2
View File
@@ -137,11 +137,24 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
addr: posix.sockaddr_storage addr: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr)) addr_len := posix.socklen_t(size_of(addr))
if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK { if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK {
err = _listen_error() err = _socket_info_error()
return
}
ep = _sockaddr_to_endpoint(&addr)
return
}
@(private)
_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
addr: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr))
if posix.getpeername(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK {
err = _socket_info_error()
return return
} }
+16 -2
View File
@@ -140,12 +140,26 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
sockaddr: freebsd.Socket_Address_Storage sockaddr: freebsd.Socket_Address_Storage
errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr) errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr)
if errno != nil { if errno != nil {
err = _listen_error(errno) err = _socket_info_error(errno)
return
}
ep = _sockaddr_to_endpoint(&sockaddr)
return
}
@(private)
_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
sockaddr: freebsd.Socket_Address_Storage
errno := freebsd.getpeername(cast(Fd)any_socket_to_socket(sock), &sockaddr)
if errno != nil {
err = _socket_info_error(errno)
return return
} }
+15 -2
View File
@@ -218,11 +218,24 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
addr: linux.Sock_Addr_Any addr: linux.Sock_Addr_Any
errno := linux.getsockname(_unwrap_os_socket(sock), &addr) errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
if errno != .NONE { if errno != .NONE {
err = _listen_error(errno) err = _socket_info_error(errno)
return
}
ep = _wrap_os_addr(addr)
return
}
@(private)
_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
addr: linux.Sock_Addr_Any
errno := linux.getpeername(_unwrap_os_socket(sock), &addr)
if errno != .NONE {
err = _socket_info_error(errno)
return return
} }
+16 -2
View File
@@ -177,11 +177,25 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
sockaddr: win.SOCKADDR_STORAGE_LH sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr)) sockaddrlen := c.int(size_of(sockaddr))
if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR { if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR {
err = _listen_error() err = _socket_info_error()
return
}
ep = _sockaddr_to_endpoint(&sockaddr)
return
}
@(private)
_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr))
res := win.getpeername(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen)
if res < 0 {
err = _socket_info_error()
return return
} }
+1 -1
View File
@@ -125,7 +125,7 @@ percent_encode :: proc(s: string, allocator := context.allocator) -> string {
bytes, n := utf8.encode_rune(ch) bytes, n := utf8.encode_rune(ch)
for byte in bytes[:n] { for byte in bytes[:n] {
buf: [2]u8 = --- buf: [2]u8 = ---
t := strconv.append_int(buf[:], i64(byte), 16) t := strconv.write_int(buf[:], i64(byte), 16)
strings.write_rune(&b, '%') strings.write_rune(&b, '%')
strings.write_string(&b, t) strings.write_string(&b, t)
} }
+10 -5
View File
@@ -2307,6 +2307,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
open := expect_token(p, .Open_Paren) open := expect_token(p, .Open_Paren)
p.expr_level += 1 p.expr_level += 1
expr := parse_expr(p, false) expr := parse_expr(p, false)
skip_possible_newline(p)
p.expr_level -= 1 p.expr_level -= 1
close := expect_token(p, .Close_Paren) close := expect_token(p, .Close_Paren)
@@ -2922,6 +2923,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
fields: [dynamic]^ast.Bit_Field_Field fields: [dynamic]^ast.Bit_Field_Field
for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF { for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
docs := p.lead_comment
name := parse_ident(p) name := parse_ident(p)
expect_token(p, .Colon) expect_token(p, .Colon)
type := parse_type(p) type := parse_type(p)
@@ -2932,6 +2935,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
if p.curr_tok.kind == .String { if p.curr_tok.kind == .String {
tag = expect_token(p, .String) tag = expect_token(p, .String)
} }
ok := allow_token(p, .Comma)
field := ast.new(ast.Bit_Field_Field, name.pos, bit_size) field := ast.new(ast.Bit_Field_Field, name.pos, bit_size)
@@ -2939,10 +2943,14 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
field.type = type field.type = type
field.bit_size = bit_size field.bit_size = bit_size
field.tag = tag field.tag = tag
field.docs = docs
field.comments = p.line_comment
append(&fields, field) append(&fields, field)
allow_token(p, .Comma) or_break if !ok {
break
}
} }
close := expect_closing_brace_of_field_list(p) close := expect_closing_brace_of_field_list(p)
@@ -3526,6 +3534,7 @@ parse_binary_expr :: proc(p: ^Parser, lhs: bool, prec_in: int) -> ^ast.Expr {
case .When: case .When:
x := expr x := expr
cond := parse_expr(p, lhs) cond := parse_expr(p, lhs)
skip_possible_newline(p)
else_tok := expect_token(p, .Else) else_tok := expect_token(p, .Else)
y := parse_expr(p, lhs) y := parse_expr(p, lhs)
te := ast.new(ast.Ternary_When_Expr, expr.pos, end_pos(p.prev_tok)) te := ast.new(ast.Ternary_When_Expr, expr.pos, end_pos(p.prev_tok))
@@ -3780,10 +3789,6 @@ parse_import_decl :: proc(p: ^Parser, kind := Import_Decl_Kind.Standard) -> ^ast
import_name.pos = p.curr_tok.pos import_name.pos = p.curr_tok.pos
} }
if !is_using && is_blank_ident(import_name) {
error(p, import_name.pos, "illegal import name: '_'")
}
path := expect_token_after(p, .String, "import") path := expect_token_after(p, .String, "import")
decl := ast.new(ast.Import_Decl, tok.pos, end_pos(path)) decl := ast.new(ast.Import_Decl, tok.pos, end_pos(path))
+39 -2
View File
@@ -8,7 +8,7 @@ import "base:runtime"
// Otherwise the returned value will be empty and the boolean will be false // Otherwise the returned value will be empty and the boolean will be false
// NOTE: the value will be allocated with the supplied allocator // NOTE: the value will be allocated with the supplied allocator
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
if key == "" { if key == "" {
return return
} }
@@ -29,17 +29,54 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
return return
} }
// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
// Note that it is limited to environment names and values of 512 utf-16 values each
// due to the necessary utf-8 <> utf-16 conversion.
@(require_results)
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
key_buf: [513]u16
wkey := win32.utf8_to_wstring(key_buf[:], key)
if wkey == nil {
return "", .Buffer_Full
}
n2 := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n2 == 0 {
return "", .Env_Var_Not_Found
}
val_buf: [513]u16
n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:])))
if n2 == 0 {
return "", .Env_Var_Not_Found
} else if int(n2) > len(buf) {
return "", .Buffer_Full
}
value = win32.utf16_to_utf8(buf, val_buf[:n2])
return value, nil
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
// get_env retrieves the value of the environment variable named by the key // get_env retrieves the value of the environment variable named by the key
// It returns the value, which will be empty if the variable is not present // It returns the value, which will be empty if the variable is not present
// To distinguish between an empty value and an unset value, use lookup_env // To distinguish between an empty value and an unset value, use lookup_env
// NOTE: the value will be allocated with the supplied allocator // NOTE: the value will be allocated with the supplied allocator
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
// set_env sets the value of the environment variable named by the key // set_env sets the value of the environment variable named by the key
set_env :: proc(key, value: string) -> Error { set_env :: proc(key, value: string) -> Error {
k := win32.utf8_to_wstring(key) k := win32.utf8_to_wstring(key)
+4
View File
@@ -35,6 +35,9 @@ General_Error :: enum u32 {
File_Is_Pipe, File_Is_Pipe,
Not_Dir, Not_Dir,
// Environment variable not found.
Env_Var_Not_Found,
} }
@@ -82,6 +85,7 @@ error_string :: proc "contextless" (ferr: Error) -> string {
case .Pattern_Has_Separator: return "pattern has separator" case .Pattern_Has_Separator: return "pattern has separator"
case .File_Is_Pipe: return "file is pipe" case .File_Is_Pipe: return "file is pipe"
case .Not_Dir: return "file is not directory" case .Not_Dir: return "file is not directory"
case .Env_Var_Not_Found: return "environment variable not found"
} }
case io.Error: case io.Error:
switch e { switch e {
+54 -1
View File
@@ -4,6 +4,7 @@ import "base:intrinsics"
import "base:runtime" import "base:runtime"
import "core:io" import "core:io"
import "core:strconv" import "core:strconv"
import "core:strings"
import "core:unicode/utf8" import "core:unicode/utf8"
@@ -57,7 +58,7 @@ write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) {
if r < 32 { if r < 32 {
if wrap(write_string(f, "\\x"), &n, &err) { return } if wrap(write_string(f, "\\x"), &n, &err) { return }
b: [2]byte b: [2]byte
s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
switch len(s) { switch len(s) {
case 0: if wrap(write_string(f, "00"), &n, &err) { return } case 0: if wrap(write_string(f, "00"), &n, &err) { return }
case 1: if wrap(write_rune(f, '0'), &n, &err) { return } case 1: if wrap(write_rune(f, '0'), &n, &err) { return }
@@ -210,3 +211,55 @@ heap_free :: runtime.heap_free
processor_core_count :: proc() -> int { processor_core_count :: proc() -> int {
return _processor_core_count() return _processor_core_count()
} }
// Always allocates for consistency.
replace_environment_placeholders :: proc(path: string, allocator := context.allocator) -> (res: string) {
path := path
sb: strings.Builder
strings.builder_init_none(&sb, allocator)
for len(path) > 0 {
switch path[0] {
case '%': // Windows
when ODIN_OS == .Windows {
for r, i in path[1:] {
if r == '%' {
env_key := path[1:i+1]
env_val := get_env(env_key, context.temp_allocator)
strings.write_string(&sb, env_val)
path = path[i+1:] // % is part of key, so skip 1 character extra
}
}
} else {
strings.write_rune(&sb, rune(path[0]))
}
case '$': // Posix
when ODIN_OS != .Windows {
env_key := ""
dollar_loop: for r, i in path[1:] {
switch r {
case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident
case:
env_key = path[1:i+1]
break dollar_loop
}
}
if len(env_key) > 0 {
env_val := get_env(env_key, context.temp_allocator)
strings.write_string(&sb, env_val)
path = path[len(env_key):]
}
} else {
strings.write_rune(&sb, rune(path[0]))
}
case:
strings.write_rune(&sb, rune(path[0]))
}
path = path[1:]
}
return strings.to_string(sb)
}
+79 -5
View File
@@ -1,26 +1,49 @@
package os2 package os2
import "base:runtime" import "base:runtime"
import "core:strings"
// get_env retrieves the value of the environment variable named by the key // `get_env` retrieves the value of the environment variable named by the key
// It returns the value, which will be empty if the variable is not present // It returns the value, which will be empty if the variable is not present
// To distinguish between an empty value and an unset value, use lookup_env // To distinguish between an empty value and an unset value, use lookup_env
// NOTE: the value will be allocated with the supplied allocator // NOTE: the value will be allocated with the supplied allocator
@(require_results) @(require_results)
get_env :: proc(key: string, allocator: runtime.Allocator) -> string { get_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> string {
value, _ := lookup_env(key, allocator) value, _ := lookup_env(key, allocator)
return value return value
} }
// lookup_env gets the value of the environment variable named by the key // `get_env` retrieves the value of the environment variable named by the key
// It returns the value, which will be empty if the variable is not present
// To distinguish between an empty value and an unset value, use lookup_env
// NOTE: this version takes a backing buffer for the string value
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> string {
value, _ := lookup_env(buf, key)
return value
}
get_env :: proc{get_env_alloc, get_env_buf}
// `lookup_env` gets the value of the environment variable named by the key
// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
// Otherwise the returned value will be empty and the boolean will be false // Otherwise the returned value will be empty and the boolean will be false
// NOTE: the value will be allocated with the supplied allocator // NOTE: the value will be allocated with the supplied allocator
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
return _lookup_env(key, allocator) return _lookup_env_alloc(key, allocator)
} }
// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
// Note that it is limited to environment names and values of 512 utf-16 values each
// due to the necessary utf-8 <> utf-16 conversion.
@(require_results)
lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
return _lookup_env_buf(buf, key)
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buf}
// set_env sets the value of the environment variable named by the key // set_env sets the value of the environment variable named by the key
// Returns Error on failure // Returns Error on failure
set_env :: proc(key, value: string) -> Error { set_env :: proc(key, value: string) -> Error {
@@ -45,4 +68,55 @@ environ :: proc(allocator: runtime.Allocator) -> ([]string, Error) {
return _environ(allocator) return _environ(allocator)
} }
// Always allocates for consistency.
replace_environment_placeholders :: proc(path: string, allocator: runtime.Allocator) -> (res: string) {
path := path
sb: strings.Builder
strings.builder_init_none(&sb, allocator)
for len(path) > 0 {
switch path[0] {
case '%': // Windows
when ODIN_OS == .Windows {
for r, i in path[1:] {
if r == '%' {
env_key := path[1:i+1]
env_val := get_env(env_key, context.temp_allocator)
strings.write_string(&sb, env_val)
path = path[i+1:] // % is part of key, so skip 1 character extra
}
}
} else {
strings.write_rune(&sb, rune(path[0]))
}
case '$': // Posix
when ODIN_OS != .Windows {
env_key := ""
dollar_loop: for r, i in path[1:] {
switch r {
case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident
case:
env_key = path[1:i+1]
break dollar_loop
}
}
if len(env_key) > 0 {
env_val := get_env(env_key, context.temp_allocator)
strings.write_string(&sb, env_val)
path = path[len(env_key):]
}
} else {
strings.write_rune(&sb, rune(path[0]))
}
case:
strings.write_rune(&sb, rune(path[0]))
}
path = path[1:]
}
return strings.to_string(sb)
}
+18 -1
View File
@@ -41,7 +41,7 @@ _lookup :: proc(key: string) -> (value: string, idx: int) {
return "", -1 return "", -1
} }
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
_build_env() _build_env()
} }
@@ -53,6 +53,23 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return return
} }
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
_build_env()
}
if v, idx := _lookup(key); idx != -1 {
if len(buf) >= len(v) {
copy(buf, v)
return string(buf[:len(v)]), nil
}
return "", .Buffer_Full
}
return "", .Env_Var_Not_Found
}
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
_set_env :: proc(key, v_new: string) -> Error { _set_env :: proc(key, v_new: string) -> Error {
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
_build_env() _build_env()
+31 -1
View File
@@ -7,7 +7,7 @@ import "base:runtime"
import "core:strings" import "core:strings"
import "core:sys/posix" import "core:sys/posix"
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
if key == "" { if key == "" {
return return
} }
@@ -26,6 +26,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return return
} }
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) {
if key == "" {
return
}
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
cval := posix.getenv(cstring(raw_data(buf)))
if cval == nil {
return
}
if value = string(cval); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
_set_env :: proc(key, value: string) -> (err: Error) { _set_env :: proc(key, value: string) -> (err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({}) temp_allocator := TEMP_ALLOCATOR_GUARD({})
+29 -1
View File
@@ -67,7 +67,7 @@ delete_string_if_not_original :: proc(str: string) {
} }
@(require_results) @(require_results)
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
if err := build_env(); err != nil { if err := build_env(); err != nil {
return return
} }
@@ -79,6 +79,34 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return return
} }
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) {
if key == "" {
return
}
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
sync.shared_guard(&g_env_mutex)
val, ok := g_env[key]
if !ok {
return "", .Env_Var_Not_Found
} else {
if len(val) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, val)
return string(buf[:len(val)]), nil
}
}
}
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
@(require_results) @(require_results)
_set_env :: proc(key, value: string) -> (err: Error) { _set_env :: proc(key, value: string) -> (err: Error) {
build_env() or_return build_env() or_return
+31 -1
View File
@@ -4,7 +4,7 @@ package os2
import win32 "core:sys/windows" import win32 "core:sys/windows"
import "base:runtime" import "base:runtime"
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
if key == "" { if key == "" {
return return
} }
@@ -36,6 +36,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return return
} }
// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
// Note that it is limited to environment names and values of 512 utf-16 values each
// due to the necessary utf-8 <> utf-16 conversion.
@(require_results)
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
key_buf: [513]u16
wkey := win32.utf8_to_wstring(key_buf[:], key)
if wkey == nil {
return "", .Buffer_Full
}
n2 := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n2 == 0 {
return "", .Env_Var_Not_Found
}
val_buf: [513]u16
n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:])))
if n2 == 0 {
return "", .Env_Var_Not_Found
} else if int(n2) > len(buf) {
return "", .Buffer_Full
}
value = win32.utf16_to_utf8(buf, val_buf[:n2])
return value, nil
}
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
_set_env :: proc(key, value: string) -> Error { _set_env :: proc(key, value: string) -> Error {
temp_allocator := TEMP_ALLOCATOR_GUARD({}) temp_allocator := TEMP_ALLOCATOR_GUARD({})
k := win32_utf8_to_wstring(key, temp_allocator) or_return k := win32_utf8_to_wstring(key, temp_allocator) or_return
+19 -14
View File
@@ -27,6 +27,9 @@ General_Error :: enum u32 {
Pattern_Has_Separator, Pattern_Has_Separator,
No_HOME_Variable,
Env_Var_Not_Found,
Unsupported, Unsupported,
} }
@@ -59,20 +62,22 @@ error_string :: proc(ferr: Error) -> string {
case General_Error: case General_Error:
switch e { switch e {
case .None: return "" case .None: return ""
case .Permission_Denied: return "permission denied" case .Permission_Denied: return "permission denied"
case .Exist: return "file already exists" case .Exist: return "file already exists"
case .Not_Exist: return "file does not exist" case .Not_Exist: return "file does not exist"
case .Closed: return "file already closed" case .Closed: return "file already closed"
case .Timeout: return "i/o timeout" case .Timeout: return "i/o timeout"
case .Broken_Pipe: return "Broken pipe" case .Broken_Pipe: return "Broken pipe"
case .No_Size: return "file has no definite size" case .No_Size: return "file has no definite size"
case .Invalid_File: return "invalid file" case .Invalid_File: return "invalid file"
case .Invalid_Dir: return "invalid directory" case .Invalid_Dir: return "invalid directory"
case .Invalid_Path: return "invalid path" case .Invalid_Path: return "invalid path"
case .Invalid_Callback: return "invalid callback" case .Invalid_Callback: return "invalid callback"
case .Invalid_Command: return "invalid command" case .Invalid_Command: return "invalid command"
case .Unsupported: return "unsupported" case .Unsupported: return "unsupported"
case .Pattern_Has_Separator: return "pattern has separator" case .Pattern_Has_Separator: return "pattern has separator"
case .No_HOME_Variable: return "no $HOME variable"
case .Env_Var_Not_Found: return "environment variable not found"
} }
case io.Error: case io.Error:
switch e { switch e {
+1
View File
@@ -269,6 +269,7 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error
return return
} }
@(no_sanitize_memory)
_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
// TODO: Identify 0-sized "pseudo" files and return No_Size. This would // TODO: Identify 0-sized "pseudo" files and return No_Size. This would
// eliminate the need for the _read_entire_pseudo_file procs. // eliminate the need for the _read_entire_pseudo_file procs.
+1 -1
View File
@@ -59,7 +59,7 @@ write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
if r < 32 { if r < 32 {
if wrap(write_string(f, "\\x"), &n, &err) { return } if wrap(write_string(f, "\\x"), &n, &err) { return }
b: [2]byte b: [2]byte
s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
switch len(s) { switch len(s) {
case 0: if wrap(write_string(f, "00"), &n, &err) { return } case 0: if wrap(write_string(f, "00"), &n, &err) { return }
case 1: if wrap(write_rune(f, '0'), &n, &err) { return } case 1: if wrap(write_rune(f, '0'), &n, &err) { return }
+141 -71
View File
@@ -2,78 +2,148 @@ package os2
import "base:runtime" import "base:runtime"
@(require_results) // ```
user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { // Windows: C:\Users\Alice
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) // macOS: /Users/Alice
// Linux: /home/alice
#partial switch ODIN_OS { // ```
case .Windows:
dir = get_env("LocalAppData", temp_allocator)
if dir != "" {
dir = clone_string(dir, temp_allocator) or_return
}
case .Darwin:
dir = get_env("HOME", temp_allocator)
if dir != "" {
dir = concatenate({dir, "/Library/Caches"}, temp_allocator) or_return
}
case: // All other UNIX systems
dir = get_env("XDG_CACHE_HOME", allocator)
if dir == "" {
dir = get_env("HOME", temp_allocator)
if dir == "" {
return
}
dir = concatenate({dir, "/.cache"}, temp_allocator) or_return
}
}
if dir == "" {
err = .Invalid_Path
}
return
}
@(require_results)
user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
#partial switch ODIN_OS {
case .Windows:
dir = get_env("AppData", temp_allocator)
if dir != "" {
dir = clone_string(dir, allocator) or_return
}
case .Darwin:
dir = get_env("HOME", temp_allocator)
if dir != "" {
dir = concatenate({dir, "/.config"}, allocator) or_return
}
case: // All other UNIX systems
dir = get_env("XDG_CONFIG_HOME", allocator)
if dir == "" {
dir = get_env("HOME", temp_allocator)
if dir == "" {
return
}
dir = concatenate({dir, "/.config"}, allocator) or_return
}
}
if dir == "" {
err = .Invalid_Path
}
return
}
@(require_results) @(require_results)
user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
env := "HOME" return _user_home_dir(allocator)
#partial switch ODIN_OS {
case .Windows:
env = "USERPROFILE"
}
if v := get_env(env, allocator); v != "" {
return v, nil
}
return "", .Invalid_Path
} }
// Files that applications can regenerate/refetch at a loss of speed, e.g. shader caches
//
// Sometimes deleted for system maintenance
//
// ```
// Windows: C:\Users\Alice\AppData\Local
// macOS: /Users/Alice/Library/Caches
// Linux: /home/alice/.cache
// ```
@(require_results)
user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_cache_dir(allocator)
}
// User-hidden application data
//
// ```
// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`)
// macOS: /Users/Alice/Library/Application Support
// Linux: /home/alice/.local/share
// ```
//
// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network*
@(require_results)
user_data_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) {
return _user_data_dir(allocator, roaming)
}
// Non-essential application data, e.g. history, ui layout state
//
// ```
// Windows: C:\Users\Alice\AppData\Local
// macOS: /Users/Alice/Library/Application Support
// Linux: /home/alice/.local/state
// ```
@(require_results)
user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_state_dir(allocator)
}
// Application log files
//
// ```
// Windows: C:\Users\Alice\AppData\Local
// macOS: /Users/Alice/Library/Logs
// Linux: /home/alice/.local/state
// ```
@(require_results)
user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_log_dir(allocator)
}
// Application settings/preferences
//
// ```
// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`)
// macOS: /Users/Alice/Library/Application Support
// Linux: /home/alice/.config
// ```
//
// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network*
@(require_results)
user_config_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) {
return _user_config_dir(allocator, roaming)
}
// ```
// Windows: C:\Users\Alice\Music
// macOS: /Users/Alice/Music
// Linux: /home/alice/Music
// ```
@(require_results)
user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_music_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Desktop
// macOS: /Users/Alice/Desktop
// Linux: /home/alice/Desktop
// ```
@(require_results)
user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_desktop_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Documents
// macOS: /Users/Alice/Documents
// Linux: /home/alice/Documents
// ```
@(require_results)
user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_documents_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Downloads
// macOS: /Users/Alice/Downloads
// Linux: /home/alice/Downloads
// ```
@(require_results)
user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_downloads_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Pictures
// macOS: /Users/Alice/Pictures
// Linux: /home/alice/Pictures
// ```
@(require_results)
user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_pictures_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Public
// macOS: /Users/Alice/Public
// Linux: /home/alice/Public
// ```
@(require_results)
user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_public_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Videos
// macOS: /Users/Alice/Movies
// Linux: /home/alice/Videos
// ```
@(require_results)
user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_videos_dir(allocator)
}
+175
View File
@@ -0,0 +1,175 @@
#+build !windows
package os2
import "base:runtime"
import "core:encoding/ini"
import "core:strings"
_user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Library/Caches", allocator)
case: // Unix
return _xdg_lookup("XDG_CACHE_HOME", "/.cache", allocator)
}
}
_user_config_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Library/Application Support", allocator)
case: // Unix
return _xdg_lookup("XDG_CONFIG_HOME", "/.config", allocator)
}
}
_user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Library/Application Support", allocator)
case: // Unix
return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator)
}
}
_user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Library/Logs", allocator)
case: // Unix
return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator)
}
}
_user_data_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Library/Application Support", allocator)
case: // Unix
return _xdg_lookup("XDG_DATA_HOME", "/.local/share", allocator)
}
}
_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Music", allocator)
case: // Unix
return _xdg_lookup("XDG_MUSIC_DIR", "/Music", allocator)
}
}
_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Desktop", allocator)
case: // Unix
return _xdg_lookup("XDG_DESKTOP_DIR", "/Desktop", allocator)
}
}
_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Documents", allocator)
case: // Unix
return _xdg_lookup("XDG_DOCUMENTS_DIR", "/Documents", allocator)
}
}
_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Downloads", allocator)
case: // Unix
return _xdg_lookup("XDG_DOWNLOAD_DIR", "/Downloads", allocator)
}
}
_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Pictures", allocator)
case: // Unix
return _xdg_lookup("XDG_PICTURES_DIR", "/Pictures", allocator)
}
}
_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Public", allocator)
case: // Unix
return _xdg_lookup("XDG_PUBLICSHARE_DIR", "/Public", allocator)
}
}
_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Movies", allocator)
case: // Unix
return _xdg_lookup("XDG_VIDEOS_DIR", "/Videos", allocator)
}
}
_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
if v := get_env("HOME", allocator); v != "" {
return v, nil
}
err = .No_HOME_Variable
return
}
_xdg_lookup :: proc(xdg_key: string, fallback_suffix: string, allocator: runtime.Allocator) -> (dir: string, err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
if xdg_key == "" { // Darwin doesn't have XDG paths.
dir = get_env("HOME", temp_allocator)
if dir == "" {
err = .No_HOME_Variable
return
}
return concatenate({dir, fallback_suffix}, allocator)
} else {
if strings.ends_with(xdg_key, "_DIR") {
dir = _xdg_user_dirs_lookup(xdg_key, allocator) or_return
} else {
dir = get_env(xdg_key, allocator)
}
if dir == "" {
dir = get_env("HOME", temp_allocator)
if dir == "" {
err = .No_HOME_Variable
return
}
dir = concatenate({dir, fallback_suffix}, allocator) or_return
}
return
}
}
// If `<config-dir>/user-dirs.dirs` doesn't exist, or `xdg_key` can't be found there: returns `""`
_xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) -> (dir: string, err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
config_dir := user_config_dir(temp_allocator) or_return
user_dirs_path := concatenate({config_dir, "/user-dirs.dirs"}, temp_allocator) or_return
content := read_entire_file(user_dirs_path, temp_allocator) or_return
it := ini.Iterator{
section = "",
_src = string(content),
options = ini.Options{
comment = "#",
key_lower_case = false,
},
}
for k, v in ini.iterate(&it) {
if k == xdg_key {
return replace_environment_placeholders(v, allocator), nil
}
}
return
}
+79
View File
@@ -0,0 +1,79 @@
package os2
import "base:runtime"
@(require) import win32 "core:sys/windows"
_local_appdata :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_LocalAppData
return _get_known_folder_path(&guid, allocator)
}
_local_appdata_or_roaming :: proc(allocator: runtime.Allocator, roaming: bool) -> (dir: string, err: Error) {
guid := win32.FOLDERID_LocalAppData
if roaming {
guid = win32.FOLDERID_RoamingAppData
}
return _get_known_folder_path(&guid, allocator)
}
_user_config_dir :: _local_appdata_or_roaming
_user_data_dir :: _local_appdata_or_roaming
_user_state_dir :: _local_appdata
_user_log_dir :: _local_appdata
_user_cache_dir :: _local_appdata
_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Profile
return _get_known_folder_path(&guid, allocator)
}
_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Music
return _get_known_folder_path(&guid, allocator)
}
_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Desktop
return _get_known_folder_path(&guid, allocator)
}
_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Documents
return _get_known_folder_path(&guid, allocator)
}
_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Downloads
return _get_known_folder_path(&guid, allocator)
}
_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Pictures
return _get_known_folder_path(&guid, allocator)
}
_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Public
return _get_known_folder_path(&guid, allocator)
}
_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Videos
return _get_known_folder_path(&guid, allocator)
}
_get_known_folder_path :: proc(rfid: win32.REFKNOWNFOLDERID, allocator: runtime.Allocator) -> (dir: string, err: Error) {
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
// See also `known_folders.odin` in `core:sys/windows` for the GUIDs.
path_w: win32.LPWSTR
res := win32.SHGetKnownFolderPath(rfid, 0, nil, &path_w)
defer win32.CoTaskMemFree(path_w)
if res != 0 {
return "", .Invalid_Path
}
dir, _ = win32.wstring_to_utf8(path_w, -1, allocator)
return
}
+37 -3
View File
@@ -1055,9 +1055,10 @@ flush :: proc(fd: Handle) -> Error {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str) cstr := _unix_getenv(path_str)
if cstr == nil { if cstr == nil {
return "", false return "", false
@@ -1066,11 +1067,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
set_env :: proc(key, value: string) -> Error { set_env :: proc(key, value: string) -> Error {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
key_cstring := strings.clone_to_cstring(key, context.temp_allocator) key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1196,7 +1225,7 @@ _processor_core_count :: proc() -> int {
return 1 return 1
} }
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for _, i in res { for _, i in res {
@@ -1205,6 +1234,11 @@ _alloc_command_line_arguments :: proc() -> []string {
return res return res
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
result := _unix_socket(c.int(domain), c.int(type), c.int(protocol)) result := _unix_socket(c.int(domain), c.int(type), c.int(protocol))
if result < 0 { if result < 0 {
+40 -7
View File
@@ -662,7 +662,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
return File_Time(modified), nil return File_Time(modified), nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) { _stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -674,7 +674,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) { _lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -688,7 +688,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
s: OS_Stat = --- s: OS_Stat = ---
result := _unix_fstat(fd, &s) result := _unix_fstat(fd, &s)
@@ -827,10 +827,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str) cstr := _unix_getenv(path_str)
if cstr == nil { if cstr == nil {
return "", false return "", false
@@ -839,11 +839,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
@(require_results) @(require_results)
get_current_directory :: proc(allocator := context.allocator) -> string { get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator context.allocator = allocator
@@ -936,7 +964,7 @@ _processor_core_count :: proc() -> int {
} }
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ { for arg, i in runtime.args__ {
@@ -944,3 +972,8 @@ _alloc_command_line_arguments :: proc() -> []string {
} }
return res return res
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
+41 -6
View File
@@ -316,7 +316,7 @@ file_size :: proc(fd: Handle) -> (i64, Error) {
// "Argv" arguments converted to Odin strings // "Argv" arguments converted to Odin strings
args := _alloc_command_line_arguments() args := _alloc_command_line_arguments()
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ { for arg, i in runtime.args__ {
@@ -325,7 +325,12 @@ _alloc_command_line_arguments :: proc() -> []string {
return res return res
} }
@(private, require_results) @(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
@(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) { _stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -339,7 +344,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) { _lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -353,7 +358,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized // deliberately uninitialized
s: OS_Stat = --- s: OS_Stat = ---
@@ -463,9 +468,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str) cstr := _unix_getenv(path_str)
if cstr == nil { if cstr == nil {
return "", false return "", false
@@ -474,11 +480,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
@(private, require_results) @(private, require_results)
_processor_core_count :: proc() -> int { _processor_core_count :: proc() -> int {
info: haiku.system_info info: haiku.system_info
+24
View File
@@ -249,3 +249,27 @@ exit :: proc "contextless" (code: int) -> ! {
current_thread_id :: proc "contextless" () -> int { current_thread_id :: proc "contextless" () -> int {
return 0 return 0
} }
@(require_results)
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
return "", false
}
@(require_results)
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
return "", .Env_Var_Not_Found
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator)
return
}
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
+40 -7
View File
@@ -674,7 +674,7 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
return i64(res), nil return i64(res), nil
} }
@(require_results) @(require_results, no_sanitize_memory)
file_size :: proc(fd: Handle) -> (i64, Error) { file_size :: proc(fd: Handle) -> (i64, Error) {
// deliberately uninitialized; the syscall fills this buffer for us // deliberately uninitialized; the syscall fills this buffer for us
s: OS_Stat = --- s: OS_Stat = ---
@@ -794,7 +794,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
return File_Time(modified), nil return File_Time(modified), nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) { _stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -808,7 +808,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) { _lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -822,7 +822,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized; the syscall fills this buffer for us // deliberately uninitialized; the syscall fills this buffer for us
s: OS_Stat = --- s: OS_Stat = ---
@@ -946,7 +946,7 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
@@ -958,11 +958,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
set_env :: proc(key, value: string) -> Error { set_env :: proc(key, value: string) -> Error {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
key_cstring := strings.clone_to_cstring(key, context.temp_allocator) key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1069,7 +1097,7 @@ _processor_core_count :: proc() -> int {
return int(_unix_get_nprocs()) return int(_unix_get_nprocs())
} }
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ { for arg, i in runtime.args__ {
@@ -1078,6 +1106,11 @@ _alloc_command_line_arguments :: proc() -> []string {
return res return res
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
@(require_results) @(require_results)
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
result := unix.sys_socket(domain, type, protocol) result := unix.sys_socket(domain, type, protocol)
+40 -7
View File
@@ -724,7 +724,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
return File_Time(modified), nil return File_Time(modified), nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) { _stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -736,7 +736,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) { _lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -750,7 +750,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
s: OS_Stat = --- s: OS_Stat = ---
result := _unix_fstat(fd, &s) result := _unix_fstat(fd, &s)
@@ -874,10 +874,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str) cstr := _unix_getenv(path_str)
if cstr == nil { if cstr == nil {
return "", false return "", false
@@ -886,11 +886,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
@(require_results) @(require_results)
get_current_directory :: proc(allocator := context.allocator) -> string { get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator context.allocator = allocator
@@ -986,7 +1014,7 @@ _processor_core_count :: proc() -> int {
return 1 return 1
} }
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ { for arg, i in runtime.args__ {
@@ -994,3 +1022,8 @@ _alloc_command_line_arguments :: proc() -> []string {
} }
return res return res
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
+40 -6
View File
@@ -639,7 +639,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
return File_Time(modified), nil return File_Time(modified), nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) { _stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -653,7 +653,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) { _lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -667,7 +667,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized // deliberately uninitialized
s: OS_Stat = --- s: OS_Stat = ---
@@ -787,9 +787,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str) cstr := _unix_getenv(path_str)
if cstr == nil { if cstr == nil {
return "", false return "", false
@@ -798,11 +799,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
@(require_results) @(require_results)
get_current_directory :: proc(allocator := context.allocator) -> string { get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator context.allocator = allocator
@@ -885,7 +914,7 @@ _processor_core_count :: proc() -> int {
return int(_sysconf(_SC_NPROCESSORS_ONLN)) return int(_sysconf(_SC_NPROCESSORS_ONLN))
} }
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ { for arg, i in runtime.args__ {
@@ -893,3 +922,8 @@ _alloc_command_line_arguments :: proc() -> []string {
} }
return res return res
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
+30 -1
View File
@@ -27,7 +27,7 @@ stderr: Handle = 2
args := _alloc_command_line_arguments() args := _alloc_command_line_arguments()
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> (args: []string) { _alloc_command_line_arguments :: proc() -> (args: []string) {
args = make([]string, len(runtime.args__)) args = make([]string, len(runtime.args__))
for &arg, i in args { for &arg, i in args {
@@ -36,6 +36,11 @@ _alloc_command_line_arguments :: proc() -> (args: []string) {
return return
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
// WASI works with "preopened" directories, the environment retrieves directories // WASI works with "preopened" directories, the environment retrieves directories
// (for example with `wasmtime --dir=. module.wasm`) and those given directories // (for example with `wasmtime --dir=. module.wasm`) and those given directories
// are the only ones accessible by the application. // are the only ones accessible by the application.
@@ -239,3 +244,27 @@ exit :: proc "contextless" (code: int) -> ! {
runtime._cleanup_runtime_contextless() runtime._cleanup_runtime_contextless()
wasi.proc_exit(wasi.exitcode_t(code)) wasi.proc_exit(wasi.exitcode_t(code))
} }
@(require_results)
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
return "", false
}
@(require_results)
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
return "", .Env_Var_Not_Found
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator)
return
}
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
+9 -1
View File
@@ -193,7 +193,7 @@ current_thread_id :: proc "contextless" () -> int {
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
arg_count: i32 arg_count: i32
arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count) arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count)
@@ -215,6 +215,14 @@ _alloc_command_line_arguments :: proc() -> []string {
return arg_list return arg_list
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
for s in args {
delete(s)
}
delete(args)
}
/* /*
Windows 11 (preview) has the same major and minor version numbers Windows 11 (preview) has the same major and minor version numbers
as Windows 10: 10 and 0 respectively. as Windows 10: 10 and 0 respectively.
+36
View File
@@ -0,0 +1,36 @@
package filepath
import "base:runtime"
import "core:strings"
SEPARATOR :: '/'
SEPARATOR_STRING :: `/`
LIST_SEPARATOR :: ':'
is_reserved_name :: proc(path: string) -> bool {
return false
}
is_abs :: proc(path: string) -> bool {
return strings.has_prefix(path, "/")
}
abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
if is_abs(path) {
return strings.clone(string(path), allocator), true
}
return path, false
}
join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error {
for e, i in elems {
if e != "" {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return
return clean(p, allocator)
}
}
return "", nil
}
+19
View File
@@ -387,6 +387,25 @@ has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_c
return false return false
} }
/*
return the suffix length common between slices `a` and `b`.
slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1, 2, 3, 4}) -> 4
slice.suffix_length([]u8{1, 2, 3, 4}, []u8{3, 4}) -> 2
slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1}) -> 0
slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1, 3, 5}) -> 0
slice.suffix_length([]u8{3, 4, 5}, []u8{3, 5}) -> 1
*/
@(require_results)
suffix_length :: proc(a, b: $T/[]$E) -> (n: int) where intrinsics.type_is_comparable(E) {
len_a, len_b := len(a), len(b)
_len := builtin.min(len_a, len_b)
#no_bounds_check for i := 1; i <= _len && a[len_a - i] == b[len_b - i]; i += 1 {
n += 1
}
return
}
@(require_results) @(require_results)
has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) { has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) {
-46
View File
@@ -30,14 +30,6 @@ sort :: proc(it: Interface) {
_quick_sort(it, 0, n, max_depth(n)) _quick_sort(it, 0, n, max_depth(n))
} }
@(deprecated="use slice.sort")
slice :: proc(array: $T/[]$E) where ORD(E) {
_slice.sort(array)
// s := array;
// sort(slice_interface(&s));
}
slice_interface :: proc(s: ^$T/[]$E) -> Interface where ORD(E) { slice_interface :: proc(s: ^$T/[]$E) -> Interface where ORD(E) {
return Interface{ return Interface{
collection = rawptr(s), collection = rawptr(s),
@@ -80,31 +72,6 @@ reverse_sort :: proc(it: Interface) {
sort(reverse_interface(&it)) sort(reverse_interface(&it))
} }
@(deprecated="use slice.reverse")
reverse_slice :: proc(array: $T/[]$E) where ORD(E) {
_slice.reverse(array)
/*
s := array;
sort(Interface{
collection = rawptr(&s),
len = proc(it: Interface) -> int {
s := (^T)(it.collection);
return len(s^);
},
less = proc(it: Interface, i, j: int) -> bool {
s := (^T)(it.collection);
return s[j] < s[i]; // manual set up
},
swap = proc(it: Interface, i, j: int) {
s := (^T)(it.collection);
s[i], s[j] = s[j], s[i];
},
});
*/
}
is_sorted :: proc(it: Interface) -> bool { is_sorted :: proc(it: Interface) -> bool {
n := it->len() n := it->len()
for i := n-1; i > 0; i -= 1 { for i := n-1; i > 0; i -= 1 {
@@ -294,11 +261,6 @@ _insertion_sort :: proc(it: Interface, a, b: int) {
} }
} }
// @(deprecated="use sort.sort or slice.sort_by")
bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
assert(f != nil) assert(f != nil)
count := len(array) count := len(array)
@@ -327,7 +289,6 @@ bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
} }
} }
// @(deprecated="use sort.sort_slice or slice.sort")
bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
count := len(array) count := len(array)
@@ -355,7 +316,6 @@ bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
} }
} }
// @(deprecated="use sort.sort or slice.sort_by")
quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
assert(f != nil) assert(f != nil)
a := array a := array
@@ -384,7 +344,6 @@ quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
quick_sort_proc(a[i:n], f) quick_sort_proc(a[i:n], f)
} }
// @(deprecated="use sort.sort_slice or slice.sort")
quick_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { quick_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
a := array a := array
n := len(a) n := len(a)
@@ -420,7 +379,6 @@ _log2 :: proc(x: int) -> int {
return res return res
} }
// @(deprecated="use sort.sort or slice.sort_by")
merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
merge :: proc(a: A, start, mid, end: int, f: proc(T, T) -> int) { merge :: proc(a: A, start, mid, end: int, f: proc(T, T) -> int) {
s, m := start, mid s, m := start, mid
@@ -462,7 +420,6 @@ merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
internal_sort(array, 0, len(array)-1, f) internal_sort(array, 0, len(array)-1, f)
} }
// @(deprecated="use sort.sort_slice or slice.sort")
merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
merge :: proc(a: A, start, mid, end: int) { merge :: proc(a: A, start, mid, end: int) {
s, m := start, mid s, m := start, mid
@@ -504,8 +461,6 @@ merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
internal_sort(array, 0, len(array)-1) internal_sort(array, 0, len(array)-1)
} }
// @(deprecated="use sort.sort or slice.sort_by")
heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
sift_proc :: proc(a: A, pi: int, n: int, f: proc(T, T) -> int) #no_bounds_check { sift_proc :: proc(a: A, pi: int, n: int, f: proc(T, T) -> int) #no_bounds_check {
p := pi p := pi
@@ -540,7 +495,6 @@ heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
} }
} }
// @(deprecated="use sort.sort_slice or slice.sort")
heap_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { heap_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
sift :: proc(a: A, pi: int, n: int) #no_bounds_check { sift :: proc(a: A, pi: int, n: int) #no_bounds_check {
p := pi p := pi
+15 -15
View File
@@ -12,11 +12,11 @@ Decimal :: struct {
Sets a Decimal from a given string `s`. The string is expected to represent a float. Stores parsed number in the given Decimal structure. Sets a Decimal from a given string `s`. The string is expected to represent a float. Stores parsed number in the given Decimal structure.
If parsing fails, the Decimal will be left in an undefined state. If parsing fails, the Decimal will be left in an undefined state.
**Inputs** **Inputs**
- d: Pointer to a Decimal struct where the parsed result will be stored - d: Pointer to a Decimal struct where the parsed result will be stored
- s: The input string representing the floating-point number - s: The input string representing the floating-point number
**Returns** **Returns**
- ok: A boolean indicating whether the parsing was successful - ok: A boolean indicating whether the parsing was successful
*/ */
set :: proc(d: ^Decimal, s: string) -> (ok: bool) { set :: proc(d: ^Decimal, s: string) -> (ok: bool) {
@@ -104,11 +104,11 @@ set :: proc(d: ^Decimal, s: string) -> (ok: bool) {
/* /*
Converts a Decimal to a string representation, using the provided buffer as storage. Converts a Decimal to a string representation, using the provided buffer as storage.
**Inputs** **Inputs**
- buf: A byte slice buffer to hold the resulting string - buf: A byte slice buffer to hold the resulting string
- a: The struct to be converted to a string - a: The struct to be converted to a string
**Returns** **Returns**
- A string representation of the Decimal - A string representation of the Decimal
*/ */
decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string { decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string {
@@ -150,7 +150,7 @@ decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string {
/* /*
Trims trailing zeros in the given Decimal, updating the count and decimal_point values as needed. Trims trailing zeros in the given Decimal, updating the count and decimal_point values as needed.
**Inputs** **Inputs**
- a: Pointer to the Decimal struct to be trimmed - a: Pointer to the Decimal struct to be trimmed
*/ */
trim :: proc(a: ^Decimal) { trim :: proc(a: ^Decimal) {
@@ -166,7 +166,7 @@ Converts a given u64 integer `idx` to its Decimal representation in the provided
**Used for internal Decimal Operations.** **Used for internal Decimal Operations.**
**Inputs** **Inputs**
- a: Where the result will be stored - a: Where the result will be stored
- idx: The value to be assigned to the Decimal - idx: The value to be assigned to the Decimal
*/ */
@@ -190,11 +190,11 @@ assign :: proc(a: ^Decimal, idx: u64) {
trim(a) trim(a)
} }
/* /*
Shifts the Decimal value to the right by k positions. Shifts the Decimal value to the right by k positions.
**Used for internal Decimal Operations.** **Used for internal Decimal Operations.**
**Inputs** **Inputs**
- a: The Decimal struct to be shifted - a: The Decimal struct to be shifted
- k: The number of positions to shift right - k: The number of positions to shift right
*/ */
@@ -344,7 +344,7 @@ Shifts the decimal of the input value to the left by `k` places
WARNING: asserts `k < 61` WARNING: asserts `k < 61`
**Inputs** **Inputs**
- a: The Decimal to be modified - a: The Decimal to be modified
- k: The number of places to shift the decimal to the left - k: The number of places to shift the decimal to the left
*/ */
@@ -405,7 +405,7 @@ shift_left :: proc(a: ^Decimal, k: uint) #no_bounds_check {
/* /*
Shifts the decimal of the input value by the specified number of places Shifts the decimal of the input value by the specified number of places
**Inputs** **Inputs**
- a: The Decimal to be modified - a: The Decimal to be modified
- i: The number of places to shift the decimal (positive for left shift, negative for right shift) - i: The number of places to shift the decimal (positive for left shift, negative for right shift)
*/ */
@@ -435,7 +435,7 @@ shift :: proc(a: ^Decimal, i: int) {
/* /*
Determines if the Decimal can be rounded up at the given digit index Determines if the Decimal can be rounded up at the given digit index
**Inputs** **Inputs**
- a: The Decimal to check - a: The Decimal to check
- nd: The digit index to consider for rounding up - nd: The digit index to consider for rounding up
@@ -455,7 +455,7 @@ can_round_up :: proc(a: ^Decimal, nd: int) -> bool {
/* /*
Rounds the Decimal at the given digit index Rounds the Decimal at the given digit index
**Inputs** **Inputs**
- a: The Decimal to be modified - a: The Decimal to be modified
- nd: The digit index to round - nd: The digit index to round
*/ */
@@ -470,7 +470,7 @@ round :: proc(a: ^Decimal, nd: int) {
/* /*
Rounds the Decimal up at the given digit index Rounds the Decimal up at the given digit index
**Inputs** **Inputs**
- a: The Decimal to be modified - a: The Decimal to be modified
- nd: The digit index to round up - nd: The digit index to round up
*/ */
@@ -493,7 +493,7 @@ round_up :: proc(a: ^Decimal, nd: int) {
/* /*
Rounds down the decimal value to the specified number of decimal places Rounds down the decimal value to the specified number of decimal places
**Inputs** **Inputs**
- a: The Decimal value to be rounded down - a: The Decimal value to be rounded down
- nd: The number of decimal places to round down to - nd: The number of decimal places to round down to
@@ -522,7 +522,7 @@ round_down :: proc(a: ^Decimal, nd: int) {
/* /*
Extracts the rounded integer part of a decimal value Extracts the rounded integer part of a decimal value
**Inputs** **Inputs**
- a: A pointer to the Decimal value to extract the rounded integer part from - a: A pointer to the Decimal value to extract the rounded integer part from
WARNING: There are no guarantees about overflow. WARNING: There are no guarantees about overflow.
+38
View File
@@ -0,0 +1,38 @@
package strconv
// (2025-06-05) These procedures are to be removed at a later release.
@(deprecated="Use write_bits instead")
append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
return write_bits(buf, x, base, is_signed, bit_size, digits, flags)
}
@(deprecated="Use write_bits_128 instead")
append_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
return write_bits_128(buf, x, base, is_signed, bit_size, digits, flags)
}
@(deprecated="Use write_bool instead")
append_bool :: proc(buf: []byte, b: bool) -> string {
return write_bool(buf, b)
}
@(deprecated="Use write_uint instead")
append_uint :: proc(buf: []byte, u: u64, base: int) -> string {
return write_uint(buf, u, base)
}
@(deprecated="Use write_int instead")
append_int :: proc(buf: []byte, i: i64, base: int) -> string {
return write_int(buf, i, base)
}
@(deprecated="Use write_u128 instead")
append_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
return write_u128(buf, u, base)
}
@(deprecated="Use write_float instead")
append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string {
return write_float(buf, f, fmt, prec, bit_size)
}
+7 -7
View File
@@ -23,7 +23,7 @@ _f64_info := Float_Info{52, 11, -1023}
/* /*
Converts a floating-point number to a string with the specified format and precision. Converts a floating-point number to a string with the specified format and precision.
**Inputs** **Inputs**
buf: A byte slice to store the resulting string buf: A byte slice to store the resulting string
val: The floating-point value to be converted val: The floating-point value to be converted
@@ -40,7 +40,7 @@ Example:
bit_size := 64 bit_size := 64
result := strconv.generic_ftoa(buf[:], val, fmt, precision, bit_size) -> "3.14" result := strconv.generic_ftoa(buf[:], val, fmt, precision, bit_size) -> "3.14"
**Returns** **Returns**
- A byte slice containing the formatted string - A byte slice containing the formatted string
*/ */
generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, precision, bit_size: int) -> []byte { generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, precision, bit_size: int) -> []byte {
@@ -122,7 +122,7 @@ generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, precision, bit_size: int)
/* /*
Converts a decimal floating-point number into a byte buffer with the given format Converts a decimal floating-point number into a byte buffer with the given format
**Inputs** **Inputs**
- buf: The byte buffer to store the formatted number - buf: The byte buffer to store the formatted number
- shortest: If true, generates the shortest representation of the number - shortest: If true, generates the shortest representation of the number
- neg: If true, the number is negative - neg: If true, the number is negative
@@ -130,7 +130,7 @@ Converts a decimal floating-point number into a byte buffer with the given forma
- precision: The number of digits after the decimal point - precision: The number of digits after the decimal point
- fmt: The format specifier (accepted values: 'f', 'F', 'e', 'E', 'g', 'G') - fmt: The format specifier (accepted values: 'f', 'F', 'e', 'E', 'g', 'G')
**Returns** **Returns**
- A byte slice containing the formatted decimal floating-point number - A byte slice containing the formatted decimal floating-point number
*/ */
format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: Decimal_Slice, precision: int, fmt: byte) -> []byte { format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: Decimal_Slice, precision: int, fmt: byte) -> []byte {
@@ -256,7 +256,7 @@ format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: Decimal_Slic
/* /*
Rounds the given decimal number to its shortest representation, considering the provided floating-point format Rounds the given decimal number to its shortest representation, considering the provided floating-point format
**Inputs** **Inputs**
- d: The decimal number to round - d: The decimal number to round
- mant: The mantissa of the floating-point number - mant: The mantissa of the floating-point number
- exp: The exponent of the floating-point number - exp: The exponent of the floating-point number
@@ -331,11 +331,11 @@ round_shortest :: proc(d: ^decimal.Decimal, mant: u64, exp: int, flt: ^Float_Inf
/* /*
Converts a decimal number to its floating-point representation with the given format and returns the resulting bits Converts a decimal number to its floating-point representation with the given format and returns the resulting bits
**Inputs** **Inputs**
- d: Pointer to the decimal number to convert - d: Pointer to the decimal number to convert
- info: Pointer to the Float_Info structure containing information about the floating-point format - info: Pointer to the Float_Info structure containing information about the floating-point format
**Returns** **Returns**
- b: The bits representing the floating-point number - b: The bits representing the floating-point number
- overflow: A boolean indicating whether an overflow occurred during conversion - overflow: A boolean indicating whether an overflow occurred during conversion
*/ */
+15 -15
View File
@@ -12,12 +12,12 @@ digits := "0123456789abcdefghijklmnopqrstuvwxyz"
/* /*
Determines whether the given unsigned 64-bit integer is a negative value by interpreting it as a signed integer with the specified bit size. Determines whether the given unsigned 64-bit integer is a negative value by interpreting it as a signed integer with the specified bit size.
**Inputs** **Inputs**
- x: The unsigned 64-bit integer to check for negativity - x: The unsigned 64-bit integer to check for negativity
- is_signed: A boolean indicating if the input should be treated as a signed integer - is_signed: A boolean indicating if the input should be treated as a signed integer
- bit_size: The bit size of the signed integer representation (8, 16, 32, or 64) - bit_size: The bit size of the signed integer representation (8, 16, 32, or 64)
**Returns** **Returns**
- u: The absolute value of the input integer - u: The absolute value of the input integer
- neg: A boolean indicating whether the input integer is negative - neg: A boolean indicating whether the input integer is negative
*/ */
@@ -48,9 +48,9 @@ is_integer_negative :: proc(x: u64, is_signed: bool, bit_size: int) -> (u: u64,
return return
} }
/* /*
Appends the string representation of an integer to a buffer with specified base, flags, and digit set. Writes the string representation of an integer to a buffer with specified base, flags, and digit set.
**Inputs** **Inputs**
- buf: The buffer to append the integer representation to - buf: The buffer to append the integer representation to
- x: The integer value to convert - x: The integer value to convert
- base: The base for the integer representation (2 <= base <= MAX_BASE) - base: The base for the integer representation (2 <= base <= MAX_BASE)
@@ -59,12 +59,12 @@ Appends the string representation of an integer to a buffer with specified base,
- digits: The digit set used for the integer representation - digits: The digit set used for the integer representation
- flags: The Int_Flags bit set to control integer formatting - flags: The Int_Flags bit set to control integer formatting
**Returns** **Returns**
- The string containing the integer representation appended to the buffer - The string containing the integer representation appended to the buffer
*/ */
append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string { write_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
if base < 2 || base > MAX_BASE { if base < 2 || base > MAX_BASE {
panic("strconv: illegal base passed to append_bits") panic("strconv: illegal base passed to write_bits")
} }
a: [129]byte a: [129]byte
@@ -106,12 +106,12 @@ append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: i
/* /*
Determines whether the given unsigned 128-bit integer is a negative value by interpreting it as a signed integer with the specified bit size. Determines whether the given unsigned 128-bit integer is a negative value by interpreting it as a signed integer with the specified bit size.
**Inputs** **Inputs**
- x: The unsigned 128-bit integer to check for negativity - x: The unsigned 128-bit integer to check for negativity
- is_signed: A boolean indicating if the input should be treated as a signed integer - is_signed: A boolean indicating if the input should be treated as a signed integer
- bit_size: The bit size of the signed integer representation (8, 16, 32, 64, or 128) - bit_size: The bit size of the signed integer representation (8, 16, 32, 64, or 128)
**Returns** **Returns**
- u: The absolute value of the input integer - u: The absolute value of the input integer
- neg: A boolean indicating whether the input integer is negative - neg: A boolean indicating whether the input integer is negative
*/ */
@@ -146,9 +146,9 @@ is_integer_negative_128 :: proc(x: u128, is_signed: bool, bit_size: int) -> (u:
return return
} }
/* /*
Appends the string representation of a 128-bit integer to a buffer with specified base, flags, and digit set. Writes the string representation of a 128-bit integer to a buffer with specified base, flags, and digit set.
**Inputs** **Inputs**
- buf: The buffer to append the integer representation to - buf: The buffer to append the integer representation to
- x: The 128-bit integer value to convert - x: The 128-bit integer value to convert
- base: The base for the integer representation (2 <= base <= MAX_BASE) - base: The base for the integer representation (2 <= base <= MAX_BASE)
@@ -157,12 +157,12 @@ Appends the string representation of a 128-bit integer to a buffer with specifie
- digits: The digit set used for the integer representation - digits: The digit set used for the integer representation
- flags: The Int_Flags bit set to control integer formatting - flags: The Int_Flags bit set to control integer formatting
**Returns** **Returns**
- The string containing the integer representation appended to the buffer - The string containing the integer representation written to the buffer
*/ */
append_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string { write_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
if base < 2 || base > MAX_BASE { if base < 2 || base > MAX_BASE {
panic("strconv: illegal base passed to append_bits") panic("strconv: illegal base passed to write_bits")
} }
a: [140]byte a: [140]byte
+138 -138
View File
@@ -5,8 +5,8 @@ import "decimal"
/* /*
Parses a boolean value from the input string Parses a boolean value from the input string
**Inputs** **Inputs**
- s: The input string - s: The input string
- true: "1", "t", "T", "true", "TRUE", "True" - true: "1", "t", "T", "true", "TRUE", "True"
- false: "0", "f", "F", "false", "FALSE", "False" - false: "0", "f", "F", "false", "FALSE", "False"
- n: An optional pointer to an int to store the length of the parsed substring (default: nil) - n: An optional pointer to an int to store the length of the parsed substring (default: nil)
@@ -386,7 +386,7 @@ Parses an unsigned integer value from the input string, using the specified base
- If base is not 0, it will be used for parsing regardless of any prefix in the input string - If base is not 0, it will be used for parsing regardless of any prefix in the input string
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strconv" import "core:strconv"
parse_uint_example :: proc() { parse_uint_example :: proc() {
@@ -399,14 +399,14 @@ Example:
n, ok = strconv.parse_uint("0xffff") // with prefix and inferred base n, ok = strconv.parse_uint("0xffff") // with prefix and inferred base
fmt.println(n,ok) fmt.println(n,ok)
} }
Output: Output:
1234 true 1234 true
65535 true 65535 true
65535 true 65535 true
**Returns** **Returns**
value: The parsed uint value value: The parsed uint value
ok: `false` if no appropriate value could be found; the value was negative; he input string contained more than just the number ok: `false` if no appropriate value could be found; the value was negative; he input string contained more than just the number
@@ -423,7 +423,7 @@ parse_uint :: proc(s: string, base := 0, n: ^int = nil) -> (value: uint, ok: boo
/* /*
Parses an integer value from a string in the given base, without any prefix Parses an integer value from a string in the given base, without any prefix
**Inputs** **Inputs**
- str: The input string containing the integer value - str: The input string containing the integer value
- base: The base (radix) to use for parsing the integer (1-16) - base: The base (radix) to use for parsing the integer (1-16)
- n: An optional pointer to an int to store the length of the parsed substring (default: nil) - n: An optional pointer to an int to store the length of the parsed substring (default: nil)
@@ -436,12 +436,12 @@ Example:
n, ok := strconv.parse_i128_of_base("-1234eeee", 10) n, ok := strconv.parse_i128_of_base("-1234eeee", 10)
fmt.println(n,ok) fmt.println(n,ok)
} }
Output: Output:
-1234 false -1234 false
**Returns** **Returns**
- value: The parsed i128 value - value: The parsed i128 value
- ok: false if no numeric value of the appropriate base could be found, or if the input string contained more than just the number. - ok: false if no numeric value of the appropriate base could be found, or if the input string contained more than just the number.
*/ */
@@ -491,7 +491,7 @@ parse_i128_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: i12
/* /*
Parses an integer value from a string in base 10, unless there's a prefix Parses an integer value from a string in base 10, unless there's a prefix
**Inputs** **Inputs**
- str: The input string containing the integer value - str: The input string containing the integer value
- n: An optional pointer to an int to store the length of the parsed substring (default: nil) - n: An optional pointer to an int to store the length of the parsed substring (default: nil)
@@ -506,13 +506,13 @@ Example:
n, ok = strconv.parse_i128_maybe_prefixed("0xeeee") n, ok = strconv.parse_i128_maybe_prefixed("0xeeee")
fmt.println(n, ok) fmt.println(n, ok)
} }
Output: Output:
1234 true 1234 true
61166 true 61166 true
**Returns** **Returns**
- value: The parsed i128 value - value: The parsed i128 value
- ok: `false` if a valid integer could not be found, or if the input string contained more than just the number. - ok: `false` if a valid integer could not be found, or if the input string contained more than just the number.
*/ */
@@ -574,7 +574,7 @@ parse_i128 :: proc{parse_i128_maybe_prefixed, parse_i128_of_base}
/* /*
Parses an unsigned integer value from a string in the given base, without any prefix Parses an unsigned integer value from a string in the given base, without any prefix
**Inputs** **Inputs**
- str: The input string containing the integer value - str: The input string containing the integer value
- base: The base (radix) to use for parsing the integer (1-16) - base: The base (radix) to use for parsing the integer (1-16)
- n: An optional pointer to an int to store the length of the parsed substring (default: nil) - n: An optional pointer to an int to store the length of the parsed substring (default: nil)
@@ -590,13 +590,13 @@ Example:
n, ok = strconv.parse_u128_of_base("5678eeee", 16) n, ok = strconv.parse_u128_of_base("5678eeee", 16)
fmt.println(n, ok) fmt.println(n, ok)
} }
Output: Output:
1234 false 1234 false
1450766062 true 1450766062 true
**Returns** **Returns**
- value: The parsed u128 value - value: The parsed u128 value
- ok: `false` if no numeric value of the appropriate base could be found, or if the input string contained more than just the number. - ok: `false` if no numeric value of the appropriate base could be found, or if the input string contained more than just the number.
*/ */
@@ -634,7 +634,7 @@ parse_u128_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: u12
/* /*
Parses an unsigned integer value from a string in base 10, unless there's a prefix Parses an unsigned integer value from a string in base 10, unless there's a prefix
**Inputs** **Inputs**
- str: The input string containing the integer value - str: The input string containing the integer value
- n: An optional pointer to an int to store the length of the parsed substring (default: nil) - n: An optional pointer to an int to store the length of the parsed substring (default: nil)
@@ -649,13 +649,13 @@ Example:
n, ok = strconv.parse_u128_maybe_prefixed("5678eeee") n, ok = strconv.parse_u128_maybe_prefixed("5678eeee")
fmt.println(n, ok) fmt.println(n, ok)
} }
Output: Output:
1234 true 1234 true
5678 false 5678 false
**Returns** **Returns**
- value: The parsed u128 value - value: The parsed u128 value
- ok: false if a valid integer could not be found, if the value was negative, or if the input string contained more than just the number. - ok: false if a valid integer could not be found, if the value was negative, or if the input string contained more than just the number.
*/ */
@@ -706,10 +706,10 @@ parse_u128 :: proc{parse_u128_maybe_prefixed, parse_u128_of_base}
/* /*
Converts a byte to lowercase Converts a byte to lowercase
**Inputs** **Inputs**
- ch: A byte character to be converted to lowercase. - ch: A byte character to be converted to lowercase.
**Returns** **Returns**
- A lowercase byte character. - A lowercase byte character.
*/ */
@(private) @(private)
@@ -717,7 +717,7 @@ lower :: #force_inline proc "contextless" (ch: byte) -> byte { return ('a' - 'A'
/* /*
Parses a 32-bit floating point number from a string Parses a 32-bit floating point number from a string
**Inputs** **Inputs**
- s: The input string containing a 32-bit floating point number. - s: The input string containing a 32-bit floating point number.
- n: An optional pointer to an int to store the length of the parsed substring (default: nil). - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
@@ -732,13 +732,13 @@ Example:
n, ok = strconv.parse_f32("5678e2") n, ok = strconv.parse_f32("5678e2")
fmt.printfln("%.3f %v", n, ok) fmt.printfln("%.3f %v", n, ok)
} }
Output: Output:
0.000 false 0.000 false
567800.000 true 567800.000 true
**Returns** **Returns**
- value: The parsed 32-bit floating point number. - value: The parsed 32-bit floating point number.
- ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number. - ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number.
*/ */
@@ -750,7 +750,7 @@ parse_f32 :: proc(s: string, n: ^int = nil) -> (value: f32, ok: bool) {
/* /*
Parses a 64-bit floating point number from a string Parses a 64-bit floating point number from a string
**Inputs** **Inputs**
- str: The input string containing a 64-bit floating point number. - str: The input string containing a 64-bit floating point number.
- n: An optional pointer to an int to store the length of the parsed substring (default: nil). - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
@@ -765,13 +765,13 @@ Example:
n, ok = strconv.parse_f64("5678e2") n, ok = strconv.parse_f64("5678e2")
fmt.printfln("%.3f %v", n, ok) fmt.printfln("%.3f %v", n, ok)
} }
Output: Output:
0.000 false 0.000 false
567800.000 true 567800.000 true
**Returns** **Returns**
- value: The parsed 64-bit floating point number. - value: The parsed 64-bit floating point number.
- ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number. - ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number.
*/ */
@@ -787,7 +787,7 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
/* /*
Parses a 32-bit floating point number from a string and returns the parsed number, the length of the parsed substring, and a boolean indicating whether the parsing was successful Parses a 32-bit floating point number from a string and returns the parsed number, the length of the parsed substring, and a boolean indicating whether the parsing was successful
**Inputs** **Inputs**
- str: The input string containing a 32-bit floating point number. - str: The input string containing a 32-bit floating point number.
Example: Example:
@@ -801,14 +801,14 @@ Example:
n, _, ok = strconv.parse_f32_prefix("5678e2") n, _, ok = strconv.parse_f32_prefix("5678e2")
fmt.printfln("%.3f %v", n, ok) fmt.printfln("%.3f %v", n, ok)
} }
Output: Output:
0.000 false 0.000 false
567800.000 true 567800.000 true
**Returns**
**Returns**
- value: The parsed 32-bit floating point number. - value: The parsed 32-bit floating point number.
- nr: The length of the parsed substring. - nr: The length of the parsed substring.
- ok: A boolean indicating whether the parsing was successful. - ok: A boolean indicating whether the parsing was successful.
@@ -822,7 +822,7 @@ parse_f32_prefix :: proc(str: string) -> (value: f32, nr: int, ok: bool) {
/* /*
Parses a 64-bit floating point number from a string and returns the parsed number, the length of the parsed substring, and a boolean indicating whether the parsing was successful Parses a 64-bit floating point number from a string and returns the parsed number, the length of the parsed substring, and a boolean indicating whether the parsing was successful
**Inputs** **Inputs**
- str: The input string containing a 64-bit floating point number. - str: The input string containing a 64-bit floating point number.
Example: Example:
@@ -846,7 +846,7 @@ Output:
1234.000 true 1234.000 true
13.370 true 13.370 true
**Returns** **Returns**
- value: The parsed 64-bit floating point number. - value: The parsed 64-bit floating point number.
- nr: The length of the parsed substring. - nr: The length of the parsed substring.
- ok: `false` if a base 10 float could not be found - ok: `false` if a base 10 float could not be found
@@ -1184,7 +1184,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
/* /*
Parses a 128-bit complex number from a string Parses a 128-bit complex number from a string
**Inputs** **Inputs**
- str: The input string containing a 128-bit complex number. - str: The input string containing a 128-bit complex number.
- n: An optional pointer to an int to store the length of the parsed substring (default: nil). - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
@@ -1200,13 +1200,13 @@ Example:
c, ok = strconv.parse_complex128("5+7i hellope", &n) c, ok = strconv.parse_complex128("5+7i hellope", &n)
fmt.printfln("%v %i %t", c, n, ok) fmt.printfln("%v %i %t", c, n, ok)
} }
Output: Output:
3+1i 4 true 3+1i 4 true
5+7i 4 false 5+7i 4 false
**Returns** **Returns**
- value: The parsed 128-bit complex number. - value: The parsed 128-bit complex number.
- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. - ok: `false` if a complex number could not be found, or if the input string contained more than just the number.
*/ */
@@ -1232,12 +1232,12 @@ parse_complex128 :: proc(str: string, n: ^int = nil) -> (value: complex128, ok:
} }
value = complex(real_value, imag_value) value = complex(real_value, imag_value)
return return
} }
/* /*
Parses a 64-bit complex number from a string Parses a 64-bit complex number from a string
**Inputs** **Inputs**
- str: The input string containing a 64-bit complex number. - str: The input string containing a 64-bit complex number.
- n: An optional pointer to an int to store the length of the parsed substring (default: nil). - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
@@ -1253,13 +1253,13 @@ Example:
c, ok = strconv.parse_complex64("5+7i hellope", &n) c, ok = strconv.parse_complex64("5+7i hellope", &n)
fmt.printfln("%v %i %t", c, n, ok) fmt.printfln("%v %i %t", c, n, ok)
} }
Output: Output:
3+1i 4 true 3+1i 4 true
5+7i 4 false 5+7i 4 false
**Returns** **Returns**
- value: The parsed 64-bit complex number. - value: The parsed 64-bit complex number.
- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. - ok: `false` if a complex number could not be found, or if the input string contained more than just the number.
*/ */
@@ -1271,7 +1271,7 @@ parse_complex64 :: proc(str: string, n: ^int = nil) -> (value: complex64, ok: bo
/* /*
Parses a 32-bit complex number from a string Parses a 32-bit complex number from a string
**Inputs** **Inputs**
- str: The input string containing a 32-bit complex number. - str: The input string containing a 32-bit complex number.
- n: An optional pointer to an int to store the length of the parsed substring (default: nil). - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
@@ -1287,13 +1287,13 @@ Example:
c, ok = strconv.parse_complex32("5+7i hellope", &n) c, ok = strconv.parse_complex32("5+7i hellope", &n)
fmt.printfln("%v %i %t", c, n, ok) fmt.printfln("%v %i %t", c, n, ok)
} }
Output: Output:
3+1i 4 true 3+1i 4 true
5+7i 4 false 5+7i 4 false
**Returns** **Returns**
- value: The parsed 32-bit complex number. - value: The parsed 32-bit complex number.
- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. - ok: `false` if a complex number could not be found, or if the input string contained more than just the number.
*/ */
@@ -1305,7 +1305,7 @@ parse_complex32 :: proc(str: string, n: ^int = nil) -> (value: complex32, ok: bo
/* /*
Parses a 256-bit quaternion from a string Parses a 256-bit quaternion from a string
**Inputs** **Inputs**
- str: The input string containing a 256-bit quaternion. - str: The input string containing a 256-bit quaternion.
- n: An optional pointer to an int to store the length of the parsed substring (default: nil). - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
@@ -1321,13 +1321,13 @@ Example:
q, ok = strconv.parse_quaternion256("1+2i+3j+4k hellope", &n) q, ok = strconv.parse_quaternion256("1+2i+3j+4k hellope", &n)
fmt.printfln("%v %i %t", q, n, ok) fmt.printfln("%v %i %t", q, n, ok)
} }
Output: Output:
1+2i+3j+4k 10 true 1+2i+3j+4k 10 true
1+2i+3j+4k 10 false 1+2i+3j+4k 10 false
**Returns** **Returns**
- value: The parsed 256-bit quaternion. - value: The parsed 256-bit quaternion.
- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. - ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion.
*/ */
@@ -1385,7 +1385,7 @@ parse_quaternion256 :: proc(str: string, n: ^int = nil) -> (value: quaternion256
/* /*
Parses a 128-bit quaternion from a string Parses a 128-bit quaternion from a string
**Inputs** **Inputs**
- str: The input string containing a 128-bit quaternion. - str: The input string containing a 128-bit quaternion.
- n: An optional pointer to an int to store the length of the parsed substring (default: nil). - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
@@ -1401,13 +1401,13 @@ Example:
q, ok = strconv.parse_quaternion128("1+2i+3j+4k hellope", &n) q, ok = strconv.parse_quaternion128("1+2i+3j+4k hellope", &n)
fmt.printfln("%v %i %t", q, n, ok) fmt.printfln("%v %i %t", q, n, ok)
} }
Output: Output:
1+2i+3j+4k 10 true 1+2i+3j+4k 10 true
1+2i+3j+4k 10 false 1+2i+3j+4k 10 false
**Returns** **Returns**
- value: The parsed 128-bit quaternion. - value: The parsed 128-bit quaternion.
- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. - ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion.
*/ */
@@ -1419,7 +1419,7 @@ parse_quaternion128 :: proc(str: string, n: ^int = nil) -> (value: quaternion128
/* /*
Parses a 64-bit quaternion from a string Parses a 64-bit quaternion from a string
**Inputs** **Inputs**
- str: The input string containing a 64-bit quaternion. - str: The input string containing a 64-bit quaternion.
- n: An optional pointer to an int to store the length of the parsed substring (default: nil). - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
@@ -1435,13 +1435,13 @@ Example:
q, ok = strconv.parse_quaternion64("1+2i+3j+4k hellope", &n) q, ok = strconv.parse_quaternion64("1+2i+3j+4k hellope", &n)
fmt.printfln("%v %i %t", q, n, ok) fmt.printfln("%v %i %t", q, n, ok)
} }
Output: Output:
1+2i+3j+4k 10 true 1+2i+3j+4k 10 true
1+2i+3j+4k 10 false 1+2i+3j+4k 10 false
**Returns** **Returns**
- value: The parsed 64-bit quaternion. - value: The parsed 64-bit quaternion.
- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. - ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion.
*/ */
@@ -1450,20 +1450,20 @@ parse_quaternion64 :: proc(str: string, n: ^int = nil) -> (value: quaternion64,
v, ok = parse_quaternion256(str, n) v, ok = parse_quaternion256(str, n)
return cast(quaternion64)v, ok return cast(quaternion64)v, ok
} }
/* /*
Appends a boolean value as a string to the given buffer Writes a boolean value as a string to the given buffer
**Inputs** **Inputs**
- buf: The buffer to append the boolean value to - buf: The buffer to write the boolean value to
- b: The boolean value to be appended - b: The boolean value to be written
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strconv" import "core:strconv"
append_bool_example :: proc() { write_bool_example :: proc() {
buf: [6]byte buf: [6]byte
result := strconv.append_bool(buf[:], true) result := strconv.write_bool(buf[:], true)
fmt.println(result, buf) fmt.println(result, buf)
} }
@@ -1471,10 +1471,10 @@ Output:
true [116, 114, 117, 101, 0, 0] true [116, 114, 117, 101, 0, 0]
**Returns** **Returns**
- The resulting string after appending the boolean value - The resulting string after writing the boolean value
*/ */
append_bool :: proc(buf: []byte, b: bool) -> string { write_bool :: proc(buf: []byte, b: bool) -> string {
n := 0 n := 0
if b { if b {
n = copy(buf, "true") n = copy(buf, "true")
@@ -1483,21 +1483,21 @@ append_bool :: proc(buf: []byte, b: bool) -> string {
} }
return string(buf[:n]) return string(buf[:n])
} }
/* /*
Appends an unsigned integer value as a string to the given buffer with the specified base Writes an unsigned integer value as a string to the given buffer with the specified base
**Inputs** **Inputs**
- buf: The buffer to append the unsigned integer value to - buf: The buffer to write the unsigned integer value to
- u: The unsigned integer value to be appended - u: The unsigned integer value to be written
- base: The base to use for converting the integer value - base: The base to use for converting the integer value
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strconv" import "core:strconv"
append_uint_example :: proc() { write_uint_example :: proc() {
buf: [4]byte buf: [4]byte
result := strconv.append_uint(buf[:], 42, 16) result := strconv.write_uint(buf[:], 42, 16)
fmt.println(result, buf) fmt.println(result, buf)
} }
@@ -1505,27 +1505,27 @@ Output:
2a [50, 97, 0, 0] 2a [50, 97, 0, 0]
**Returns** **Returns**
- The resulting string after appending the unsigned integer value - The resulting string after writing the unsigned integer value
*/ */
append_uint :: proc(buf: []byte, u: u64, base: int) -> string { write_uint :: proc(buf: []byte, u: u64, base: int) -> string {
return append_bits(buf, u, base, false, 8*size_of(uint), digits, nil) return write_bits(buf, u, base, false, 8*size_of(uint), digits, nil)
} }
/* /*
Appends a signed integer value as a string to the given buffer with the specified base Writes a signed integer value as a string to the given buffer with the specified base
**Inputs** **Inputs**
- buf: The buffer to append the signed integer value to - buf: The buffer to write the signed integer value to
- i: The signed integer value to be appended - i: The signed integer value to be written
- base: The base to use for converting the integer value - base: The base to use for converting the integer value
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strconv" import "core:strconv"
append_int_example :: proc() { write_int_example :: proc() {
buf: [4]byte buf: [4]byte
result := strconv.append_int(buf[:], -42, 10) result := strconv.write_int(buf[:], -42, 10)
fmt.println(result, buf) fmt.println(result, buf)
} }
@@ -1533,23 +1533,23 @@ Output:
-42 [45, 52, 50, 0] -42 [45, 52, 50, 0]
**Returns** **Returns**
- The resulting string after appending the signed integer value - The resulting string after writing the signed integer value
*/ */
append_int :: proc(buf: []byte, i: i64, base: int) -> string { write_int :: proc(buf: []byte, i: i64, base: int) -> string {
return append_bits(buf, u64(i), base, true, 8*size_of(int), digits, nil) return write_bits(buf, u64(i), base, true, 8*size_of(int), digits, nil)
} }
append_u128 :: proc(buf: []byte, u: u128, base: int) -> string { write_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
return append_bits_128(buf, u, base, false, 8*size_of(uint), digits, nil) return write_bits_128(buf, u, base, false, 8*size_of(uint), digits, nil)
} }
/* /*
Converts an integer value to a string and stores it in the given buffer Converts an integer value to a string and stores it in the given buffer
**Inputs** **Inputs**
- buf: The buffer to store the resulting string - buf: The buffer to store the resulting string
- i: The integer value to be converted - i: The integer value to be converted
@@ -1567,16 +1567,16 @@ Output:
42 [52, 50, 0, 0] 42 [52, 50, 0, 0]
**Returns** **Returns**
- The resulting string after converting the integer value - The resulting string after converting the integer value
*/ */
itoa :: proc(buf: []byte, i: int) -> string { itoa :: proc(buf: []byte, i: int) -> string {
return append_int(buf, i64(i), 10) return write_int(buf, i64(i), 10)
} }
/* /*
Converts a string to an integer value Converts a string to an integer value
**Inputs** **Inputs**
- s: The string to be converted - s: The string to be converted
Example: Example:
@@ -1591,17 +1591,17 @@ Output:
42 42
**Returns** **Returns**
- The resulting integer value - The resulting integer value
*/ */
atoi :: proc(s: string) -> int { atoi :: proc(s: string) -> int {
v, _ := parse_int(s) v, _ := parse_int(s)
return v return v
} }
/* /*
Converts a string to a float64 value Converts a string to a float64 value
**Inputs** **Inputs**
- s: The string to be converted - s: The string to be converted
Example: Example:
@@ -1616,21 +1616,21 @@ Output:
3.140 3.140
**Returns** **Returns**
- The resulting float64 value after converting the string - The resulting float64 value after converting the string
*/ */
atof :: proc(s: string) -> f64 { atof :: proc(s: string) -> f64 {
v, _ := parse_f64(s) v, _ := parse_f64(s)
return v return v
} }
// Alias to `append_float` // Alias to `write_float`
ftoa :: append_float ftoa :: write_float
/* /*
Appends a float64 value as a string to the given buffer with the specified format and precision Writes a float64 value as a string to the given buffer with the specified format and precision
**Inputs** **Inputs**
- buf: The buffer to append the float64 value to - buf: The buffer to write the float64 value to
- f: The float64 value to be appended - f: The float64 value to be written
- fmt: The byte specifying the format to use for the conversion - fmt: The byte specifying the format to use for the conversion
- prec: The precision to use for the conversion - prec: The precision to use for the conversion
- bit_size: The size of the float in bits (32 or 64) - bit_size: The size of the float in bits (32 or 64)
@@ -1639,9 +1639,9 @@ Example:
import "core:fmt" import "core:fmt"
import "core:strconv" import "core:strconv"
append_float_example :: proc() { write_float_example :: proc() {
buf: [8]byte buf: [8]byte
result := strconv.append_float(buf[:], 3.14159, 'f', 2, 64) result := strconv.write_float(buf[:], 3.14159, 'f', 2, 64)
fmt.println(result, buf) fmt.println(result, buf)
} }
@@ -1649,20 +1649,20 @@ Output:
+3.14 [43, 51, 46, 49, 52, 0, 0, 0] +3.14 [43, 51, 46, 49, 52, 0, 0, 0]
**Returns** **Returns**
- The resulting string after appending the float - The resulting string after writing the float
*/ */
append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string { write_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string {
return string(generic_ftoa(buf, f, fmt, prec, bit_size)) return string(generic_ftoa(buf, f, fmt, prec, bit_size))
} }
/* /*
Appends a quoted string representation of the input string to a given byte slice and returns the result as a string Writes a quoted string representation of the input string to a given byte slice and returns the result as a string
**Inputs** **Inputs**
- buf: The byte slice to which the quoted string will be appended - buf: The byte slice to which the quoted string will be written
- str: The input string to be quoted - str: The input string to be quoted
!! ISSUE !! NOT EXPECTED -- "\"hello\"" was expected !! ISSUE !! NOT EXPECTED -- "\"hello\"" was expected
Example: Example:
@@ -1678,8 +1678,8 @@ Output:
"'h''e''l''l''o'" [34, 39, 104, 39, 39, 101, 39, 39, 108, 39, 39, 108, 39, 39, 111, 39, 34, 0, 0, 0] "'h''e''l''l''o'" [34, 39, 104, 39, 39, 101, 39, 39, 108, 39, 39, 108, 39, 39, 111, 39, 34, 0, 0, 0]
**Returns** **Returns**
- The resulting string after appending the quoted string representation - The resulting string after writing the quoted string representation
*/ */
quote :: proc(buf: []byte, str: string) -> string { quote :: proc(buf: []byte, str: string) -> string {
write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) { write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) {
@@ -1719,10 +1719,10 @@ quote :: proc(buf: []byte, str: string) -> string {
return string(buf[:i]) return string(buf[:i])
} }
/* /*
Appends a quoted rune representation of the input rune to a given byte slice and returns the result as a string Writes a quoted rune representation of the input rune to a given byte slice and returns the result as a string
**Inputs** **Inputs**
- buf: The byte slice to which the quoted rune will be appended - buf: The byte slice to which the quoted rune will be written
- r: The input rune to be quoted - r: The input rune to be quoted
Example: Example:
@@ -1739,8 +1739,8 @@ Output:
'A' [39, 65, 39, 0] 'A' [39, 65, 39, 0]
**Returns** **Returns**
- The resulting string after appending the quoted rune representation - The resulting string after writing the quoted rune representation
*/ */
quote_rune :: proc(buf: []byte, r: rune) -> string { quote_rune :: proc(buf: []byte, r: rune) -> string {
write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) { write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) {
@@ -1783,7 +1783,7 @@ quote_rune :: proc(buf: []byte, r: rune) -> string {
if r < 32 { if r < 32 {
write_string(buf, &i, "\\x") write_string(buf, &i, "\\x")
b: [2]byte b: [2]byte
s := append_bits(b[:], u64(r), 16, true, 64, digits, nil) s := write_bits(b[:], u64(r), 16, true, 64, digits, nil)
switch len(s) { switch len(s) {
case 0: write_string(buf, &i, "00") case 0: write_string(buf, &i, "00")
case 1: write_rune(buf, &i, '0') case 1: write_rune(buf, &i, '0')
@@ -1800,11 +1800,11 @@ quote_rune :: proc(buf: []byte, r: rune) -> string {
/* /*
Unquotes a single character from the input string, considering the given quote character Unquotes a single character from the input string, considering the given quote character
**Inputs** **Inputs**
- str: The input string containing the character to unquote - str: The input string containing the character to unquote
- quote: The quote character to consider (e.g., '"') - quote: The quote character to consider (e.g., '"')
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strconv" import "core:strconv"
@@ -1815,12 +1815,12 @@ Example:
fmt.printf("r: <%v>, multiple_bytes:%v, tail_string:<%s>, success:%v\n",r, multiple_bytes, tail_string, success) fmt.printf("r: <%v>, multiple_bytes:%v, tail_string:<%s>, success:%v\n",r, multiple_bytes, tail_string, success)
} }
Output: Output:
Source: 'The' raven Source: 'The' raven
r: <'>, multiple_bytes:false, tail_string:<The' raven>, success:true r: <'>, multiple_bytes:false, tail_string:<The' raven>, success:true
**Returns** **Returns**
- r: The unquoted rune - r: The unquoted rune
- multiple_bytes: A boolean indicating if the rune has multiple bytes - multiple_bytes: A boolean indicating if the rune has multiple bytes
- tail_string: The remaining portion of the input string after unquoting the character - tail_string: The remaining portion of the input string after unquoting the character
@@ -1923,13 +1923,13 @@ unquote_char :: proc(str: string, quote: byte) -> (r: rune, multiple_bytes: bool
/* /*
Unquotes the input string considering any type of quote character and returns the unquoted string Unquotes the input string considering any type of quote character and returns the unquoted string
**Inputs** **Inputs**
- lit: The input string to unquote - lit: The input string to unquote
- allocator: (default: context.allocator) - allocator: (default: context.allocator)
WARNING: This procedure gives unexpected results if the quotes are not the first and last characters. WARNING: This procedure gives unexpected results if the quotes are not the first and last characters.
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strconv" import "core:strconv"
@@ -1947,10 +1947,10 @@ Example:
src="The raven \'Huginn\' is black." src="The raven \'Huginn\' is black."
s, allocated, ok = strconv.unquote_string(src) // Will produce undesireable results s, allocated, ok = strconv.unquote_string(src) // Will produce undesireable results
fmt.println(src) fmt.println(src)
fmt.printf("Unquoted: <%s>, alloc:%v, ok:%v\n", s, allocated, ok) fmt.printf("Unquoted: <%s>, alloc:%v, ok:%v\n", s, allocated, ok)
} }
Output: Output:
"The raven Huginn is black." "The raven Huginn is black."
Unquoted: <The raven Huginn is black.>, alloc:false, ok:true Unquoted: <The raven Huginn is black.>, alloc:false, ok:true
@@ -1961,7 +1961,7 @@ Output:
The raven 'Huginn' is black. The raven 'Huginn' is black.
Unquoted: <he raven 'Huginn' is black>, alloc:false, ok:true Unquoted: <he raven 'Huginn' is black>, alloc:false, ok:true
**Returns** **Returns**
- res: The resulting unquoted string - res: The resulting unquoted string
- allocated: A boolean indicating if the resulting string was allocated using the provided allocator - allocated: A boolean indicating if the resulting string was allocated using the provided allocator
- success: A boolean indicating whether the unquoting was successful - success: A boolean indicating whether the unquoting was successful
@@ -2002,7 +2002,7 @@ unquote_string :: proc(lit: string, allocator := context.allocator) -> (res: str
return s, false, true return s, false, true
} }
} }
context.allocator = allocator context.allocator = allocator
buf_len := 3*len(s) / 2 buf_len := 3*len(s) / 2
+6 -6
View File
@@ -675,7 +675,7 @@ Returns:
*/ */
write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_signed := false) -> (n: int) { write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_signed := false) -> (n: int) {
buf: [384]byte buf: [384]byte
s := strconv.append_float(buf[:], f, fmt, prec, bit_size) s := strconv.write_float(buf[:], f, fmt, prec, bit_size)
// If the result starts with a `+` then unless we always want signed results, // If the result starts with a `+` then unless we always want signed results,
// we skip it unless it's followed by an `I` (because of +Inf). // we skip it unless it's followed by an `I` (because of +Inf).
if !always_signed && (buf[0] == '+' && buf[1] != 'I') { if !always_signed && (buf[0] == '+' && buf[1] != 'I') {
@@ -699,7 +699,7 @@ Returns:
*/ */
write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: int) { write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: int) {
buf: [384]byte buf: [384]byte
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) s := strconv.write_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
if !always_signed && (buf[0] == '+' && buf[1] != 'I') { if !always_signed && (buf[0] == '+' && buf[1] != 'I') {
s = s[1:] s = s[1:]
} }
@@ -739,7 +739,7 @@ Output:
*/ */
write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: int) { write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: int) {
buf: [384]byte buf: [384]byte
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) s := strconv.write_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
if !always_signed && (buf[0] == '+' && buf[1] != 'I') { if !always_signed && (buf[0] == '+' && buf[1] != 'I') {
s = s[1:] s = s[1:]
} }
@@ -761,7 +761,7 @@ Returns:
*/ */
write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) { write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) {
buf: [384]byte buf: [384]byte
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) s := strconv.write_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
if !always_signed && (buf[0] == '+' && buf[1] != 'I') { if !always_signed && (buf[0] == '+' && buf[1] != 'I') {
s = s[1:] s = s[1:]
} }
@@ -782,7 +782,7 @@ Returns:
*/ */
write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) { write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
buf: [32]byte buf: [32]byte
s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil) s := strconv.write_bits(buf[:], i, base, false, 64, strconv.digits, nil)
return write_string(b, s) return write_string(b, s)
} }
/* /*
@@ -800,7 +800,7 @@ Returns:
*/ */
write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) { write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
buf: [32]byte buf: [32]byte
s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil) s := strconv.write_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
return write_string(b, s) return write_string(b, s)
} }
/* /*
+82 -64
View File
@@ -28,24 +28,7 @@ clone :: proc(s: string, allocator := context.allocator, loc := #caller_location
copy(c, s) copy(c, s)
return string(c), nil return string(c), nil
} }
/*
Clones a string safely (returns early with an allocation error on failure)
*Allocates Using Provided Allocator*
Inputs:
- s: The string to be cloned
- allocator: (default: context.allocator)
- loc: The caller location for debugging purposes (default: #caller_location)
Returns:
- res: The cloned string
- err: An allocator error if one occured, `nil` otherwise
*/
@(deprecated="Prefer clone. It now returns an optional allocator error")
clone_safe :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) {
return clone(s, allocator, loc)
}
/* /*
Clones a string and appends a null-byte to make it a cstring Clones a string and appends a null-byte to make it a cstring
@@ -66,6 +49,7 @@ clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #call
c[len(s)] = 0 c[len(s)] = 0
return cstring(&c[0]), nil return cstring(&c[0]), nil
} }
/* /*
Transmutes a raw pointer into a string. Non-allocating. Transmutes a raw pointer into a string. Non-allocating.
@@ -81,6 +65,7 @@ Returns:
string_from_ptr :: proc(ptr: ^byte, len: int) -> (res: string) { string_from_ptr :: proc(ptr: ^byte, len: int) -> (res: string) {
return transmute(string)mem.Raw_String{ptr, len} return transmute(string)mem.Raw_String{ptr, len}
} }
/* /*
Transmutes a raw pointer (null-terminated) into a string. Non-allocating. Searches for a null-byte from `0..<len`, otherwise `len` will be the end size Transmutes a raw pointer (null-terminated) into a string. Non-allocating. Searches for a null-byte from `0..<len`, otherwise `len` will be the end size
@@ -99,20 +84,7 @@ string_from_null_terminated_ptr :: proc "contextless" (ptr: [^]byte, len: int) -
s = truncate_to_byte(s, 0) s = truncate_to_byte(s, 0)
return s return s
} }
/*
Gets the raw byte pointer for the start of a string `str`
Inputs:
- str: The input string
Returns:
- res: A pointer to the start of the string's bytes
*/
@(deprecated="Prefer the builtin raw_data.")
ptr_from_string :: proc(str: string) -> (res: ^byte) {
d := transmute(mem.Raw_String)str
return d.data
}
/* /*
Converts a string `str` to a cstring Converts a string `str` to a cstring
@@ -128,6 +100,7 @@ unsafe_string_to_cstring :: proc(str: string) -> (res: cstring) {
d := transmute(mem.Raw_String)str d := transmute(mem.Raw_String)str
return cstring(d.data) return cstring(d.data)
} }
/* /*
Truncates a string `str` at the first occurrence of char/byte `b` Truncates a string `str` at the first occurrence of char/byte `b`
@@ -147,6 +120,7 @@ truncate_to_byte :: proc "contextless" (str: string, b: byte) -> (res: string) {
} }
return str[:n] return str[:n]
} }
/* /*
Truncates a string `str` at the first occurrence of rune `r` as a slice of the original, entire string if not found Truncates a string `str` at the first occurrence of rune `r` as a slice of the original, entire string if not found
@@ -164,6 +138,7 @@ truncate_to_rune :: proc(str: string, r: rune) -> (res: string) {
} }
return str[:n] return str[:n]
} }
/* /*
Clones a byte array `s` and appends a null-byte Clones a byte array `s` and appends a null-byte
@@ -184,6 +159,7 @@ clone_from_bytes :: proc(s: []byte, allocator := context.allocator, loc := #call
c[len(s)] = 0 c[len(s)] = 0
return string(c[:len(s)]), nil return string(c[:len(s)]), nil
} }
/* /*
Clones a cstring `s` as a string Clones a cstring `s` as a string
@@ -201,6 +177,7 @@ Returns:
clone_from_cstring :: proc(s: cstring, allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { clone_from_cstring :: proc(s: cstring, allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error {
return clone(string(s), allocator, loc) return clone(string(s), allocator, loc)
} }
/* /*
Clones a string from a byte pointer `ptr` and a byte length `len` Clones a string from a byte pointer `ptr` and a byte length `len`
@@ -222,6 +199,7 @@ clone_from_ptr :: proc(ptr: ^byte, len: int, allocator := context.allocator, loc
s := string_from_ptr(ptr, len) s := string_from_ptr(ptr, len)
return clone(s, allocator, loc) return clone(s, allocator, loc)
} }
// Overloaded procedure to clone from a string, `[]byte`, `cstring` or a `^byte` + length // Overloaded procedure to clone from a string, `[]byte`, `cstring` or a `^byte` + length
clone_from :: proc{ clone_from :: proc{
clone, clone,
@@ -229,6 +207,7 @@ clone_from :: proc{
clone_from_cstring, clone_from_cstring,
clone_from_ptr, clone_from_ptr,
} }
/* /*
Clones a string from a null-terminated cstring `ptr` and a byte length `len` Clones a string from a null-terminated cstring `ptr` and a byte length `len`
@@ -251,6 +230,7 @@ clone_from_cstring_bounded :: proc(ptr: cstring, len: int, allocator := context.
s = truncate_to_byte(s, 0) s = truncate_to_byte(s, 0)
return clone(s, allocator, loc) return clone(s, allocator, loc)
} }
/* /*
Compares two strings, returning a value representing which one comes first lexicographically. Compares two strings, returning a value representing which one comes first lexicographically.
-1 for `lhs`; 1 for `rhs`, or 0 if they are equal. -1 for `lhs`; 1 for `rhs`, or 0 if they are equal.
@@ -265,6 +245,7 @@ Returns:
compare :: proc "contextless" (lhs, rhs: string) -> (result: int) { compare :: proc "contextless" (lhs, rhs: string) -> (result: int) {
return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs) return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs)
} }
/* /*
Checks if rune `r` in the string `s` Checks if rune `r` in the string `s`
@@ -283,6 +264,7 @@ contains_rune :: proc(s: string, r: rune) -> (result: bool) {
} }
return false return false
} }
/* /*
Returns true when the string `substr` is contained inside the string `s` Returns true when the string `substr` is contained inside the string `s`
@@ -314,6 +296,7 @@ Output:
contains :: proc(s, substr: string) -> (res: bool) { contains :: proc(s, substr: string) -> (res: bool) {
return index(s, substr) >= 0 return index(s, substr) >= 0
} }
/* /*
Returns `true` when the string `s` contains any of the characters inside the string `chars` Returns `true` when the string `s` contains any of the characters inside the string `chars`
@@ -386,6 +369,7 @@ Output:
rune_count :: proc(s: string) -> (res: int) { rune_count :: proc(s: string) -> (res: int) {
return utf8.rune_count_in_string(s) return utf8.rune_count_in_string(s)
} }
/* /*
Returns whether the strings `u` and `v` are the same alpha characters, ignoring different casings Returns whether the strings `u` and `v` are the same alpha characters, ignoring different casings
Works with UTF-8 string content Works with UTF-8 string content
@@ -508,6 +492,7 @@ prefix_length :: proc "contextless" (a, b: string) -> (n: int) {
} }
return return
} }
/* /*
Returns the common prefix between strings `a` and `b` Returns the common prefix between strings `a` and `b`
@@ -540,6 +525,7 @@ Output:
common_prefix :: proc(a, b: string) -> string { common_prefix :: proc(a, b: string) -> string {
return a[:prefix_length(a, b)] return a[:prefix_length(a, b)]
} }
/* /*
Determines if a string `s` starts with a given `prefix` Determines if a string `s` starts with a given `prefix`
@@ -661,24 +647,7 @@ join :: proc(a: []string, sep: string, allocator := context.allocator, loc := #c
} }
return string(b), nil return string(b), nil
} }
/*
Joins a slice of strings `a` with a `sep` string, returns an error on allocation failure
*Allocates Using Provided Allocator*
Inputs:
- a: A slice of strings to join
- sep: The separator string
- allocator: (default is context.allocator)
Returns:
- str: A combined string from the slice of strings `a` separated with the `sep` string
- err: An allocator error if one occured, `nil` otherwise
*/
@(deprecated="Prefer join. It now returns an optional allocator error")
join_safe :: proc(a: []string, sep: string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) {
return join(a, sep, allocator)
}
/* /*
Returns a combined string from the slice of strings `a` without a separator Returns a combined string from the slice of strings `a` without a separator
@@ -723,22 +692,6 @@ concatenate :: proc(a: []string, allocator := context.allocator, loc := #caller_
} }
return string(b), nil return string(b), nil
} }
/*
Returns a combined string from the slice of strings `a` without a separator, or an error if allocation fails
*Allocates Using Provided Allocator*
Inputs:
- a: A slice of strings to concatenate
- allocator: (default is context.allocator)
Returns:
The concatenated string, and an error if allocation fails
*/
@(deprecated="Prefer concatenate. It now returns an optional allocator error")
concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) {
return concatenate(a, allocator)
}
/* /*
Returns a substring of the input string `s` with the specified rune offset and length Returns a substring of the input string `s` with the specified rune offset and length
@@ -901,6 +854,7 @@ _split :: proc(s_, sep: string, sep_save, n_: int, allocator := context.allocato
return res[:i+1], nil return res[:i+1], nil
} }
/* /*
Splits a string into parts based on a separator. Splits a string into parts based on a separator.
@@ -936,6 +890,7 @@ Output:
split :: proc(s, sep: string, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { split :: proc(s, sep: string, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error {
return _split(s, sep, 0, -1, allocator) return _split(s, sep, 0, -1, allocator)
} }
/* /*
Splits a string into parts based on a separator. If n < count of seperators, the remainder of the string is returned in the last entry. Splits a string into parts based on a separator. If n < count of seperators, the remainder of the string is returned in the last entry.
@@ -972,6 +927,7 @@ Output:
split_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { split_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error {
return _split(s, sep, 0, n, allocator) return _split(s, sep, 0, n, allocator)
} }
/* /*
Splits a string into parts after the separator, retaining it in the substrings. Splits a string into parts after the separator, retaining it in the substrings.
@@ -1007,6 +963,7 @@ Output:
split_after :: proc(s, sep: string, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { split_after :: proc(s, sep: string, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error {
return _split(s, sep, len(sep), -1, allocator) return _split(s, sep, len(sep), -1, allocator)
} }
/* /*
Splits a string into a total of `n` parts after the separator. Splits a string into a total of `n` parts after the separator.
@@ -1043,6 +1000,7 @@ Output:
split_after_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { split_after_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error {
return _split(s, sep, len(sep), n, allocator) return _split(s, sep, len(sep), n, allocator)
} }
/* /*
Searches for the first occurrence of `sep` in the given string and returns the substring Searches for the first occurrence of `sep` in the given string and returns the substring
up to (but not including) the separator, as well as a boolean indicating success. up to (but not including) the separator, as well as a boolean indicating success.
@@ -1083,6 +1041,7 @@ _split_iterator :: proc(s: ^string, sep: string, sep_save: int) -> (res: string,
} }
return return
} }
/* /*
Splits the input string by the byte separator in an iterator fashion. Splits the input string by the byte separator in an iterator fashion.
@@ -1129,6 +1088,7 @@ split_by_byte_iterator :: proc(s: ^string, sep: u8) -> (res: string, ok: bool) {
} }
return return
} }
/* /*
Splits the input string by the separator string in an iterator fashion. Splits the input string by the separator string in an iterator fashion.
@@ -1164,6 +1124,7 @@ Output:
split_iterator :: proc(s: ^string, sep: string) -> (res: string, ok: bool) { split_iterator :: proc(s: ^string, sep: string) -> (res: string, ok: bool) {
return _split_iterator(s, sep, 0) return _split_iterator(s, sep, 0)
} }
/* /*
Splits the input string after every separator string in an iterator fashion. Splits the input string after every separator string in an iterator fashion.
@@ -1199,6 +1160,7 @@ Output:
split_after_iterator :: proc(s: ^string, sep: string) -> (res: string, ok: bool) { split_after_iterator :: proc(s: ^string, sep: string) -> (res: string, ok: bool) {
return _split_iterator(s, sep, len(sep)) return _split_iterator(s, sep, len(sep))
} }
/* /*
Trims the carriage return character from the end of the input string. Trims the carriage return character from the end of the input string.
@@ -1220,6 +1182,7 @@ _trim_cr :: proc(s: string) -> (res: string) {
} }
return s return s
} }
/* /*
Splits the input string at every line break `\n`. Splits the input string at every line break `\n`.
@@ -1257,6 +1220,7 @@ split_lines :: proc(s: string, allocator := context.allocator) -> (res: []string
} }
return lines, nil return lines, nil
} }
/* /*
Splits the input string at every line break `\n` for `n` parts. Splits the input string at every line break `\n` for `n` parts.
@@ -1297,6 +1261,7 @@ split_lines_n :: proc(s: string, n: int, allocator := context.allocator) -> (res
} }
return lines, nil return lines, nil
} }
/* /*
Splits the input string at every line break `\n` leaving the `\n` in the resulting strings. Splits the input string at every line break `\n` leaving the `\n` in the resulting strings.
@@ -1336,6 +1301,7 @@ split_lines_after :: proc(s: string, allocator := context.allocator) -> (res: []
} }
return lines, nil return lines, nil
} }
/* /*
Splits the input string at every line break `\n` leaving the `\n` in the resulting strings. Splits the input string at every line break `\n` leaving the `\n` in the resulting strings.
Only runs for n parts. Only runs for n parts.
@@ -1377,6 +1343,7 @@ split_lines_after_n :: proc(s: string, n: int, allocator := context.allocator) -
} }
return lines, nil return lines, nil
} }
/* /*
Splits the input string at every line break `\n`. Splits the input string at every line break `\n`.
Returns the current split string every iteration until the string is consumed. Returns the current split string every iteration until the string is consumed.
@@ -1411,6 +1378,7 @@ split_lines_iterator :: proc(s: ^string) -> (line: string, ok: bool) {
line = _split_iterator(s, sep, 0) or_return line = _split_iterator(s, sep, 0) or_return
return _trim_cr(line), true return _trim_cr(line), true
} }
/* /*
Splits the input string at every line break `\n`. Splits the input string at every line break `\n`.
Returns the current split string with line breaks included every iteration until the string is consumed. Returns the current split string with line breaks included every iteration until the string is consumed.
@@ -1448,6 +1416,7 @@ split_lines_after_iterator :: proc(s: ^string) -> (line: string, ok: bool) {
line = _split_iterator(s, sep, len(sep)) or_return line = _split_iterator(s, sep, len(sep)) or_return
return _trim_cr(line), true return _trim_cr(line), true
} }
/* /*
Returns the byte offset of the first byte `c` in the string s it finds, -1 when not found. Returns the byte offset of the first byte `c` in the string s it finds, -1 when not found.
NOTE: Can't find UTF-8 based runes. NOTE: Can't find UTF-8 based runes.
@@ -1482,6 +1451,7 @@ Output:
index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) { index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) {
return #force_inline bytes.index_byte(transmute([]u8)s, c) return #force_inline bytes.index_byte(transmute([]u8)s, c)
} }
/* /*
Returns the byte offset of the last byte `c` in the string `s`, -1 when not found. Returns the byte offset of the last byte `c` in the string `s`, -1 when not found.
@@ -1517,6 +1487,7 @@ Output:
last_index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) { last_index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) {
return #force_inline bytes.last_index_byte(transmute([]u8)s, c) return #force_inline bytes.last_index_byte(transmute([]u8)s, c)
} }
/* /*
Returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found. Returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found.
Invalid runes return -1 Invalid runes return -1
@@ -1657,6 +1628,7 @@ index :: proc "contextless" (s, substr: string) -> (res: int) {
} }
return -1 return -1
} }
/* /*
Returns the last byte offset of the string `substr` in the string `s`, -1 when not found. Returns the last byte offset of the string `substr` in the string `s`, -1 when not found.
@@ -1734,6 +1706,7 @@ last_index :: proc(s, substr: string) -> (res: int) {
} }
return -1 return -1
} }
/* /*
Returns the index of any first char of `chars` found in `s`, -1 if not found. Returns the index of any first char of `chars` found in `s`, -1 if not found.
@@ -1797,6 +1770,7 @@ index_any :: proc(s, chars: string) -> (res: int) {
} }
return -1 return -1
} }
/* /*
Finds the last occurrence of any character in `chars` within `s`. Iterates in reverse. Finds the last occurrence of any character in `chars` within `s`. Iterates in reverse.
@@ -1878,6 +1852,7 @@ last_index_any :: proc(s, chars: string) -> (res: int) {
} }
return -1 return -1
} }
/* /*
Finds the first occurrence of any substring in `substrs` within `s` Finds the first occurrence of any substring in `substrs` within `s`
@@ -1919,6 +1894,7 @@ index_multi :: proc(s: string, substrs: []string) -> (idx: int, width: int) {
} }
return return
} }
/* /*
Counts the number of non-overlapping occurrences of `substr` in `s` Counts the number of non-overlapping occurrences of `substr` in `s`
@@ -1985,6 +1961,7 @@ count :: proc(s, substr: string) -> (res: int) {
} }
return n return n
} }
/* /*
Repeats the string `s` `count` times, concatenating the result Repeats the string `s` `count` times, concatenating the result
@@ -2030,6 +2007,7 @@ repeat :: proc(s: string, count: int, allocator := context.allocator, loc := #ca
} }
return string(b), nil return string(b), nil
} }
/* /*
Replaces all occurrences of `old` in `s` with `new` Replaces all occurrences of `old` in `s` with `new`
@@ -2063,9 +2041,11 @@ Output:
zzzz true zzzz true
*/ */
replace_all :: proc(s, old, new: string, allocator := context.allocator) -> (output: string, was_allocation: bool) { replace_all :: proc(s, old, new: string, allocator := context.allocator) -> (output: string, was_allocation: bool) {
return replace(s, old, new, -1, allocator) return replace(s, old, new, -1, allocator)
} }
/* /*
Replaces n instances of old in the string s with the new string Replaces n instances of old in the string s with the new string
@@ -2144,6 +2124,7 @@ replace :: proc(s, old, new: string, n: int, allocator := context.allocator, loc
output = string(t[0:w]) output = string(t[0:w])
return return
} }
/* /*
Removes the key string `n` times from the `s` string Removes the key string `n` times from the `s` string
@@ -2182,6 +2163,7 @@ Output:
remove :: proc(s, key: string, n: int, allocator := context.allocator) -> (output: string, was_allocation: bool) { remove :: proc(s, key: string, n: int, allocator := context.allocator) -> (output: string, was_allocation: bool) {
return replace(s, key, "", n, allocator) return replace(s, key, "", n, allocator)
} }
/* /*
Removes all the `key` string instances from the `s` string Removes all the `key` string instances from the `s` string
@@ -2217,6 +2199,7 @@ Output:
remove_all :: proc(s, key: string, allocator := context.allocator) -> (output: string, was_allocation: bool) { remove_all :: proc(s, key: string, allocator := context.allocator) -> (output: string, was_allocation: bool) {
return remove(s, key, -1, allocator) return remove(s, key, -1, allocator)
} }
// Returns true if is an ASCII space character ('\t', '\n', '\v', '\f', '\r', ' ') // Returns true if is an ASCII space character ('\t', '\n', '\v', '\f', '\r', ' ')
@(private) _ascii_space := [256]bool{'\t' = true, '\n' = true, '\v' = true, '\f' = true, '\r' = true, ' ' = true} @(private) _ascii_space := [256]bool{'\t' = true, '\n' = true, '\v' = true, '\f' = true, '\r' = true, ' ' = true}
@@ -2320,6 +2303,7 @@ index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> (res: int
} }
return -1 return -1
} }
// Same as `index_proc`, but the procedure p takes a raw pointer for state // Same as `index_proc`, but the procedure p takes a raw pointer for state
index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> (res: int) { index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> (res: int) {
for r, i in s { for r, i in s {
@@ -2329,6 +2313,7 @@ index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: r
} }
return -1 return -1
} }
// Finds the index of the *last* rune in the string s for which the procedure p returns the same value as truth // Finds the index of the *last* rune in the string s for which the procedure p returns the same value as truth
last_index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> (res: int) { last_index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> (res: int) {
// TODO(bill): Probably use Rabin-Karp Search // TODO(bill): Probably use Rabin-Karp Search
@@ -2341,6 +2326,7 @@ last_index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> (res
} }
return -1 return -1
} }
// Same as `index_proc_with_state`, runs through the string in reverse // Same as `index_proc_with_state`, runs through the string in reverse
last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> (res: int) { last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> (res: int) {
// TODO(bill): Probably use Rabin-Karp Search // TODO(bill): Probably use Rabin-Karp Search
@@ -2353,6 +2339,7 @@ last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta
} }
return -1 return -1
} }
/* /*
Trims the input string `s` from the left until the procedure `p` returns false Trims the input string `s` from the left until the procedure `p` returns false
@@ -2387,6 +2374,7 @@ trim_left_proc :: proc(s: string, p: proc(rune) -> bool) -> (res: string) {
} }
return s[i:] return s[i:]
} }
/* /*
Trims the input string `s` from the left until the procedure `p` with state returns false Trims the input string `s` from the left until the procedure `p` with state returns false
@@ -2405,6 +2393,7 @@ trim_left_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, stat
} }
return s[i:] return s[i:]
} }
/* /*
Trims the input string `s` from the right until the procedure `p` returns `false` Trims the input string `s` from the right until the procedure `p` returns `false`
@@ -2442,6 +2431,7 @@ trim_right_proc :: proc(s: string, p: proc(rune) -> bool) -> (res: string) {
} }
return s[0:i] return s[0:i]
} }
/* /*
Trims the input string `s` from the right until the procedure `p` with state returns `false` Trims the input string `s` from the right until the procedure `p` with state returns `false`
@@ -2463,6 +2453,7 @@ trim_right_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta
} }
return s[0:i] return s[0:i]
} }
// Procedure for `trim_*_proc` variants, which has a string rawptr cast + rune comparison // Procedure for `trim_*_proc` variants, which has a string rawptr cast + rune comparison
is_in_cutset :: proc(state: rawptr, r: rune) -> (res: bool) { is_in_cutset :: proc(state: rawptr, r: rune) -> (res: bool) {
cutset := (^string)(state)^ cutset := (^string)(state)^
@@ -2473,6 +2464,7 @@ is_in_cutset :: proc(state: rawptr, r: rune) -> (res: bool) {
} }
return false return false
} }
/* /*
Trims the cutset string from the `s` string Trims the cutset string from the `s` string
@@ -2490,6 +2482,7 @@ trim_left :: proc(s: string, cutset: string) -> (res: string) {
state := cutset state := cutset
return trim_left_proc_with_state(s, is_in_cutset, &state) return trim_left_proc_with_state(s, is_in_cutset, &state)
} }
/* /*
Trims the cutset string from the `s` string from the right Trims the cutset string from the `s` string from the right
@@ -2507,6 +2500,7 @@ trim_right :: proc(s: string, cutset: string) -> (res: string) {
state := cutset state := cutset
return trim_right_proc_with_state(s, is_in_cutset, &state) return trim_right_proc_with_state(s, is_in_cutset, &state)
} }
/* /*
Trims the cutset string from the `s` string, both from left and right Trims the cutset string from the `s` string, both from left and right
@@ -2520,6 +2514,7 @@ Returns:
trim :: proc(s: string, cutset: string) -> (res: string) { trim :: proc(s: string, cutset: string) -> (res: string) {
return trim_right(trim_left(s, cutset), cutset) return trim_right(trim_left(s, cutset), cutset)
} }
/* /*
Trims until a valid non-space rune from the left, "\t\txyz\t\t" -> "xyz\t\t" Trims until a valid non-space rune from the left, "\t\txyz\t\t" -> "xyz\t\t"
@@ -2532,6 +2527,7 @@ Returns:
trim_left_space :: proc(s: string) -> (res: string) { trim_left_space :: proc(s: string) -> (res: string) {
return trim_left_proc(s, is_space) return trim_left_proc(s, is_space)
} }
/* /*
Trims from the right until a valid non-space rune, "\t\txyz\t\t" -> "\t\txyz" Trims from the right until a valid non-space rune, "\t\txyz\t\t" -> "\t\txyz"
@@ -2544,6 +2540,7 @@ Returns:
trim_right_space :: proc(s: string) -> (res: string) { trim_right_space :: proc(s: string) -> (res: string) {
return trim_right_proc(s, is_space) return trim_right_proc(s, is_space)
} }
/* /*
Trims from both sides until a valid non-space rune, "\t\txyz\t\t" -> "xyz" Trims from both sides until a valid non-space rune, "\t\txyz\t\t" -> "xyz"
@@ -2556,6 +2553,7 @@ Returns:
trim_space :: proc(s: string) -> (res: string) { trim_space :: proc(s: string) -> (res: string) {
return trim_right_space(trim_left_space(s)) return trim_right_space(trim_left_space(s))
} }
/* /*
Trims null runes from the left, "\x00\x00testing\x00\x00" -> "testing\x00\x00" Trims null runes from the left, "\x00\x00testing\x00\x00" -> "testing\x00\x00"
@@ -2568,6 +2566,7 @@ Returns:
trim_left_null :: proc(s: string) -> (res: string) { trim_left_null :: proc(s: string) -> (res: string) {
return trim_left_proc(s, is_null) return trim_left_proc(s, is_null)
} }
/* /*
Trims null runes from the right, "\x00\x00testing\x00\x00" -> "\x00\x00testing" Trims null runes from the right, "\x00\x00testing\x00\x00" -> "\x00\x00testing"
@@ -2580,6 +2579,7 @@ Returns:
trim_right_null :: proc(s: string) -> (res: string) { trim_right_null :: proc(s: string) -> (res: string) {
return trim_right_proc(s, is_null) return trim_right_proc(s, is_null)
} }
/* /*
Trims null runes from both sides, "\x00\x00testing\x00\x00" -> "testing" Trims null runes from both sides, "\x00\x00testing\x00\x00" -> "testing"
@@ -2591,6 +2591,7 @@ Returns:
trim_null :: proc(s: string) -> (res: string) { trim_null :: proc(s: string) -> (res: string) {
return trim_right_null(trim_left_null(s)) return trim_right_null(trim_left_null(s))
} }
/* /*
Trims a `prefix` string from the start of the `s` string and returns the trimmed string Trims a `prefix` string from the start of the `s` string and returns the trimmed string
@@ -2623,6 +2624,7 @@ trim_prefix :: proc(s, prefix: string) -> (res: string) {
} }
return s return s
} }
/* /*
Trims a `suffix` string from the end of the `s` string and returns the trimmed string Trims a `suffix` string from the end of the `s` string and returns the trimmed string
@@ -2655,6 +2657,7 @@ trim_suffix :: proc(s, suffix: string) -> (res: string) {
} }
return s return s
} }
/* /*
Splits the input string `s` by all possible `substrs` and returns an allocated array of strings Splits the input string `s` by all possible `substrs` and returns an allocated array of strings
@@ -2727,6 +2730,7 @@ split_multi :: proc(s: string, substrs: []string, allocator := context.allocator
assert(len(results) == n) assert(len(results) == n)
return results[:], nil return results[:], nil
} }
/* /*
Splits the input string `s` by all possible `substrs` in an iterator fashion. The full string is returned if no match. Splits the input string `s` by all possible `substrs` in an iterator fashion. The full string is returned if no match.
@@ -2786,6 +2790,7 @@ split_multi_iterate :: proc(it: ^string, substrs: []string) -> (res: string, ok:
ok = true ok = true
return return
} }
/* /*
Replaces invalid UTF-8 characters in the input string with a specified replacement string. Adjacent invalid bytes are only replaced once. Replaces invalid UTF-8 characters in the input string with a specified replacement string. Adjacent invalid bytes are only replaced once.
@@ -2846,6 +2851,7 @@ scrub :: proc(s: string, replacement: string, allocator := context.allocator) ->
return to_string(b), nil return to_string(b), nil
} }
/* /*
Reverses the input string `s` Reverses the input string `s`
@@ -2889,6 +2895,7 @@ reverse :: proc(s: string, allocator := context.allocator, loc := #caller_locati
} }
return string(buf), nil return string(buf), nil
} }
/* /*
Expands the input string by replacing tab characters with spaces to align to a specified tab size Expands the input string by replacing tab characters with spaces to align to a specified tab size
@@ -2920,6 +2927,7 @@ Output:
abc1 abc2 abc3 abc1 abc2 abc3
*/ */
expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error {
if tab_size <= 0 { if tab_size <= 0 {
panic("tab size must be positive") panic("tab size must be positive")
@@ -2961,6 +2969,7 @@ expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) ->
return to_string(b), nil return to_string(b), nil
} }
/* /*
Splits the input string `str` by the separator `sep` string and returns 3 parts. The values are slices of the original string. Splits the input string `str` by the separator `sep` string and returns 3 parts. The values are slices of the original string.
@@ -3011,8 +3020,10 @@ partition :: proc(str, sep: string) -> (head, match, tail: string) {
tail = str[i+len(sep):] tail = str[i+len(sep):]
return return
} }
// Alias for centre_justify // Alias for centre_justify
center_justify :: centre_justify // NOTE(bill): Because Americans exist center_justify :: centre_justify // NOTE(bill): Because Americans exist
/* /*
Centers the input string within a field of specified length by adding pad string on both sides, if its length is less than the target length. Centers the input string within a field of specified length by adding pad string on both sides, if its length is less than the target length.
@@ -3048,6 +3059,7 @@ centre_justify :: proc(str: string, length: int, pad: string, allocator := conte
return to_string(b), nil return to_string(b), nil
} }
/* /*
Left-justifies the input string within a field of specified length by adding pad string on the right side, if its length is less than the target length. Left-justifies the input string within a field of specified length by adding pad string on the right side, if its length is less than the target length.
@@ -3082,6 +3094,7 @@ left_justify :: proc(str: string, length: int, pad: string, allocator := context
return to_string(b), nil return to_string(b), nil
} }
/* /*
Right-justifies the input string within a field of specified length by adding pad string on the left side, if its length is less than the target length. Right-justifies the input string within a field of specified length by adding pad string on the left side, if its length is less than the target length.
@@ -3116,6 +3129,7 @@ right_justify :: proc(str: string, length: int, pad: string, allocator := contex
return to_string(b), nil return to_string(b), nil
} }
/* /*
Writes a given pad string a specified number of times to an `io.Writer` Writes a given pad string a specified number of times to an `io.Writer`
@@ -3142,6 +3156,7 @@ write_pad_string :: proc(w: io.Writer, pad: string, pad_len, remains: int) {
p = p[width:] p = p[width:]
} }
} }
/* /*
Splits a string into a slice of substrings at each instance of one or more consecutive white space characters, as defined by `unicode.is_space` Splits a string into a slice of substrings at each instance of one or more consecutive white space characters, as defined by `unicode.is_space`
@@ -3203,6 +3218,7 @@ fields :: proc(s: string, allocator := context.allocator, loc := #caller_locatio
} }
return a, nil return a, nil
} }
/* /*
Splits a string into a slice of substrings at each run of unicode code points `r` satisfying the predicate `f(r)` Splits a string into a slice of substrings at each run of unicode code points `r` satisfying the predicate `f(r)`
@@ -3245,6 +3261,7 @@ fields_proc :: proc(s: string, f: proc(rune) -> bool, allocator := context.alloc
return substrings[:], nil return substrings[:], nil
} }
/* /*
Retrieves the first non-space substring from a mutable string reference and advances the reference. `s` is advanced from any space after the substring, or be an empty string if the substring was the remaining characters Retrieves the first non-space substring from a mutable string reference and advances the reference. `s` is advanced from any space after the substring, or be an empty string if the substring was the remaining characters
@@ -3283,6 +3300,7 @@ fields_iterator :: proc(s: ^string) -> (field: string, ok: bool) {
s^ = s[len(s):] s^ = s[len(s):]
return return
} }
/* /*
Computes the Levenshtein edit distance between two strings Computes the Levenshtein edit distance between two strings
@@ -3460,4 +3478,4 @@ substring_to :: proc(s: string, rune_end: int) -> (sub: string, ok: bool) {
} }
return internal_substring(s, -1, rune_end) return internal_substring(s, -1, rune_end)
} }
+107 -48
View File
@@ -7,6 +7,14 @@ import "core:mem"
import "core:sync" import "core:sync"
import "core:math/rand" import "core:math/rand"
when ODIN_TEST {
/*
Hook for testing _try_select_raw allowing the test harness to manipulate the
channels prior to the select actually operating on them.
*/
__try_select_raw_pause : proc() = nil
}
/* /*
Determines what operations `Chan` supports. Determines what operations `Chan` supports.
*/ */
@@ -75,6 +83,8 @@ Raw_Chan :: struct {
r_waiting: int, // guarded by `mutex` r_waiting: int, // guarded by `mutex`
w_waiting: int, // guarded by `mutex` w_waiting: int, // guarded by `mutex`
did_read: bool, // lets a sender know if the value was read
// Buffered // Buffered
queue: ^Raw_Queue, queue: ^Raw_Queue,
@@ -412,8 +422,8 @@ as_recv :: #force_inline proc "contextless" (c: $C/Chan($T, $D)) -> (r: Chan(T,
Sends the specified message, blocking the current thread if: Sends the specified message, blocking the current thread if:
- the channel is unbuffered - the channel is unbuffered
- the channel's buffer is full - the channel's buffer is full
until the channel is being read from. `send` will return until the channel is being read from or the channel is closed. `send` will
`false` when attempting to send on an already closed channel. return `false` when attempting to send on an already closed channel.
**Inputs** **Inputs**
- `c`: The channel - `c`: The channel
@@ -484,8 +494,9 @@ try_send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where
Reads a message from the channel, blocking the current thread if: Reads a message from the channel, blocking the current thread if:
- the channel is unbuffered - the channel is unbuffered
- the channel's buffer is empty - the channel's buffer is empty
until the channel is being written to. `recv` will return until the channel is being written to or the channel is closed. `recv` will
`false` when attempting to receive a message on an already closed channel. return `false` when attempting to receive a message on an already closed
channel.
**Inputs** **Inputs**
- `c`: The channel - `c`: The channel
@@ -558,8 +569,8 @@ try_recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D
Sends the specified message, blocking the current thread if: Sends the specified message, blocking the current thread if:
- the channel is unbuffered - the channel is unbuffered
- the channel's buffer is full - the channel's buffer is full
until the channel is being read from. `send_raw` will return until the channel is being read from or the channel is closed. `send_raw` will
`false` when attempting to send on an already closed channel. return `false` when attempting to send on an already closed channel.
Note: The message referenced by `msg_out` must match the size Note: The message referenced by `msg_out` must match the size
and alignment used when the `Raw_Chan` was created. and alignment used when the `Raw_Chan` was created.
@@ -619,12 +630,23 @@ send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
return false return false
} }
c.did_read = false
defer c.did_read = false
mem.copy(c.unbuffered_data, msg_in, int(c.msg_size)) mem.copy(c.unbuffered_data, msg_in, int(c.msg_size))
c.w_waiting += 1 c.w_waiting += 1
if c.r_waiting > 0 { if c.r_waiting > 0 {
sync.signal(&c.r_cond) sync.signal(&c.r_cond)
} }
sync.wait(&c.w_cond, &c.mutex) sync.wait(&c.w_cond, &c.mutex)
if c.closed && !c.did_read {
return false
}
ok = true ok = true
} }
return return
@@ -634,8 +656,9 @@ send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
Reads a message from the channel, blocking the current thread if: Reads a message from the channel, blocking the current thread if:
- the channel is unbuffered - the channel is unbuffered
- the channel's buffer is empty - the channel's buffer is empty
until the channel is being written to. `recv_raw` will return until the channel is being written to or the channel is closed. `recv_raw`
`false` when attempting to receive a message on an already closed channel. will return `false` when attempting to receive a message on an already closed
channel.
Note: The location pointed to by `msg_out` must match the size Note: The location pointed to by `msg_out` must match the size
and alignment used when the `Raw_Chan` was created. and alignment used when the `Raw_Chan` was created.
@@ -698,8 +721,7 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) {
} else if c.unbuffered_data != nil { // unbuffered } else if c.unbuffered_data != nil { // unbuffered
sync.guard(&c.mutex) sync.guard(&c.mutex)
for !c.closed && for !c.closed && c.w_waiting == 0 {
c.w_waiting == 0 {
c.r_waiting += 1 c.r_waiting += 1
sync.wait(&c.r_cond, &c.mutex) sync.wait(&c.r_cond, &c.mutex)
c.r_waiting -= 1 c.r_waiting -= 1
@@ -712,6 +734,7 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) {
mem.copy(msg_out, c.unbuffered_data, int(c.msg_size)) mem.copy(msg_out, c.unbuffered_data, int(c.msg_size))
c.w_waiting -= 1 c.w_waiting -= 1
c.did_read = true
sync.signal(&c.w_cond) sync.signal(&c.w_cond)
ok = true ok = true
} }
@@ -771,7 +794,7 @@ try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool)
} else if c.unbuffered_data != nil { // unbuffered } else if c.unbuffered_data != nil { // unbuffered
sync.guard(&c.mutex) sync.guard(&c.mutex)
if c.closed { if c.closed || c.r_waiting - c.w_waiting <= 0 {
return false return false
} }
@@ -835,7 +858,7 @@ try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool {
} else if c.unbuffered_data != nil { // unbuffered } else if c.unbuffered_data != nil { // unbuffered
sync.guard(&c.mutex) sync.guard(&c.mutex)
if c.closed || c.w_waiting == 0 { if c.closed || c.w_waiting - c.r_waiting <= 0 {
return false return false
} }
@@ -1038,8 +1061,9 @@ is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool {
} }
/* /*
Returns whether a message is ready to be read, i.e., Returns whether a message can be read without blocking the current
if a call to `recv` or `recv_raw` would block thread. Specifically, it checks if the channel is buffered and not full,
or if there is already a writer attempting to send a message.
**Inputs** **Inputs**
- `c`: The channel - `c`: The channel
@@ -1067,7 +1091,7 @@ can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool {
if is_buffered(c) { if is_buffered(c) {
return c.queue.len > 0 return c.queue.len > 0
} }
return c.w_waiting > 0 return c.w_waiting - c.r_waiting > 0
} }
@@ -1080,7 +1104,7 @@ or if there is already a reader waiting for a message.
- `c`: The channel - `c`: The channel
**Returns** **Returns**
- `true` if a message can be send, `false` otherwise - `true` if a message can be sent, `false` otherwise
Example: Example:
@@ -1102,18 +1126,30 @@ can_send :: proc "contextless" (c: ^Raw_Chan) -> bool {
if is_buffered(c) { if is_buffered(c) {
return c.queue.len < c.queue.cap return c.queue.len < c.queue.cap
} }
return c.w_waiting == 0 return c.r_waiting - c.w_waiting > 0
}
/*
Specifies the direction of the selected channel.
*/
Select_Status :: enum {
None,
Recv,
Send,
} }
/* /*
Attempts to either send or receive messages on the specified channels. Attempts to either send or receive messages on the specified channels without blocking.
`select_raw` first identifies which channels have messages ready to be received `try_select_raw` first identifies which channels have messages ready to be received
and which are available for sending. It then randomly selects one operation and which are available for sending. It then randomly selects one operation
(either a send or receive) to perform. (either a send or receive) to perform.
If no channels have messages ready, the procedure is a noop.
Note: Each message in `send_msgs` corresponds to the send channel at the same index in `sends`. Note: Each message in `send_msgs` corresponds to the send channel at the same index in `sends`.
If the message is nil, corresponding send channel will be skipped.
**Inputs** **Inputs**
- `recv`: A slice of channels to read from - `recv`: A slice of channels to read from
@@ -1145,18 +1181,18 @@ Example:
// where the value from the read should be stored // where the value from the read should be stored
received_value: int received_value: int
idx, ok := chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value) idx, ok := chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
fmt.println("SELECT: ", idx, ok) fmt.println("SELECT: ", idx, ok)
fmt.println("RECEIVED VALUE ", received_value) fmt.println("RECEIVED VALUE ", received_value)
idx, ok = chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value) idx, ok = chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
fmt.println("SELECT: ", idx, ok) fmt.println("SELECT: ", idx, ok)
fmt.println("RECEIVED VALUE ", received_value) fmt.println("RECEIVED VALUE ", received_value)
// closing of a channel also affects the select operation // closing of a channel also affects the select operation
chan.close(c) chan.close(c)
idx, ok = chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value) idx, ok = chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
fmt.println("SELECT: ", idx, ok) fmt.println("SELECT: ", idx, ok)
} }
@@ -1170,7 +1206,7 @@ Output:
*/ */
@(require_results) @(require_results)
select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, ok: bool) #no_bounds_check { try_select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, status: Select_Status) #no_bounds_check {
Select_Op :: struct { Select_Op :: struct {
idx: int, // local to the slice that was given idx: int, // local to the slice that was given
is_recv: bool, is_recv: bool,
@@ -1178,43 +1214,66 @@ select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []
candidate_count := builtin.len(recvs)+builtin.len(sends) candidate_count := builtin.len(recvs)+builtin.len(sends)
candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op))) candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op)))
count := 0
for c, i in recvs { try_loop: for {
if can_recv(c) { count := 0
candidates[count] = {
is_recv = true, for c, i in recvs {
idx = i, if can_recv(c) {
candidates[count] = {
is_recv = true,
idx = i,
}
count += 1
} }
count += 1
} }
}
for c, i in sends { for c, i in sends {
if can_send(c) { if i > builtin.len(send_msgs)-1 || send_msgs[i] == nil {
candidates[count] = { continue
is_recv = false, }
idx = i, if can_send(c) {
candidates[count] = {
is_recv = false,
idx = i,
}
count += 1
} }
count += 1
} }
}
if count == 0 { if count == 0 {
return return -1, .None
} }
select_idx = rand.int_max(count) if count > 0 else 0 when ODIN_TEST {
if __try_select_raw_pause != nil {
__try_select_raw_pause()
}
}
sel := candidates[select_idx] candidate_idx := rand.int_max(count) if count > 0 else 0
if sel.is_recv {
ok = recv_raw(recvs[sel.idx], recv_out) sel := candidates[candidate_idx]
} else { if sel.is_recv {
ok = send_raw(sends[sel.idx], send_msgs[sel.idx]) status = .Recv
if !try_recv_raw(recvs[sel.idx], recv_out) {
continue try_loop
}
} else {
status = .Send
if !try_send_raw(sends[sel.idx], send_msgs[sel.idx]) {
continue try_loop
}
}
return sel.idx, status
} }
return
} }
@(require_results, deprecated = "use try_select_raw")
select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, status: Select_Status) #no_bounds_check {
return try_select_raw(recvs, sends, send_msgs, recv_out)
}
/* /*
`Raw_Queue` is a non-thread-safe queue implementation designed to store messages `Raw_Queue` is a non-thread-safe queue implementation designed to store messages
+42 -18
View File
@@ -95,20 +95,16 @@ atomic_mutex_guard :: proc "contextless" (m: ^Atomic_Mutex) -> bool {
Atomic_RW_Mutex_State :: distinct uint Atomic_RW_Mutex_State :: distinct uint
Atomic_RW_Mutex_State_Half_Width :: size_of(Atomic_RW_Mutex_State)*8/2 Atomic_RW_Mutex_State_Is_Writing :: Atomic_RW_Mutex_State(1) << (size_of(Atomic_RW_Mutex_State)*8-1)
Atomic_RW_Mutex_State_Is_Writing :: Atomic_RW_Mutex_State(1) Atomic_RW_Mutex_State_Reader :: Atomic_RW_Mutex_State(1)
Atomic_RW_Mutex_State_Writer :: Atomic_RW_Mutex_State(1)<<1 Atomic_RW_Mutex_State_Reader_Mask :: ~Atomic_RW_Mutex_State_Is_Writing
Atomic_RW_Mutex_State_Reader :: Atomic_RW_Mutex_State(1)<<Atomic_RW_Mutex_State_Half_Width
Atomic_RW_Mutex_State_Writer_Mask :: Atomic_RW_Mutex_State(1<<(Atomic_RW_Mutex_State_Half_Width-1) - 1) << 1
Atomic_RW_Mutex_State_Reader_Mask :: Atomic_RW_Mutex_State(1<<(Atomic_RW_Mutex_State_Half_Width-1) - 1) << Atomic_RW_Mutex_State_Half_Width
// An Atomic_RW_Mutex is a reader/writer mutual exclusion lock // An Atomic_RW_Mutex is a reader/writer mutual exclusion lock.
// The lock can be held by any arbitrary number of readers or a single writer // The lock can be held by any arbitrary number of readers or a single writer.
// The zero value for an Atomic_RW_Mutex is an unlocked mutex // The zero value for an Atomic_RW_Mutex is an unlocked mutex.
// //
// An Atomic_RW_Mutex must not be copied after first use // An Atomic_RW_Mutex must not be copied after first use.
Atomic_RW_Mutex :: struct #no_copy { Atomic_RW_Mutex :: struct #no_copy {
state: Atomic_RW_Mutex_State, state: Atomic_RW_Mutex_State,
mutex: Atomic_Mutex, mutex: Atomic_Mutex,
@@ -118,11 +114,17 @@ Atomic_RW_Mutex :: struct #no_copy {
// atomic_rw_mutex_lock locks rw for writing (with a single writer) // atomic_rw_mutex_lock locks rw for writing (with a single writer)
// If the mutex is already locked for reading or writing, the mutex blocks until the mutex is available. // If the mutex is already locked for reading or writing, the mutex blocks until the mutex is available.
atomic_rw_mutex_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) { atomic_rw_mutex_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) {
_ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Writer)
atomic_mutex_lock(&rw.mutex) atomic_mutex_lock(&rw.mutex)
state := atomic_or(&rw.state, Atomic_RW_Mutex_State_Writer) state := atomic_or(&rw.state, Atomic_RW_Mutex_State_Is_Writing)
if state & Atomic_RW_Mutex_State_Reader_Mask != 0 { if state & Atomic_RW_Mutex_State_Reader_Mask != 0 {
// There's at least one reader, so wait for the last one to post the semaphore.
//
// Because we hold the exclusive lock, no more readers can come in
// during this time, which will prevent any situations where the last
// reader is pre-empted around the count turning zero, which would
// result in the potential for another reader to run amok after the
// other posts.
atomic_sema_wait(&rw.sema) atomic_sema_wait(&rw.sema)
} }
} }
@@ -138,10 +140,15 @@ atomic_rw_mutex_try_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) -> bool {
if atomic_mutex_try_lock(&rw.mutex) { if atomic_mutex_try_lock(&rw.mutex) {
state := atomic_load(&rw.state) state := atomic_load(&rw.state)
if state & Atomic_RW_Mutex_State_Reader_Mask == 0 { if state & Atomic_RW_Mutex_State_Reader_Mask == 0 {
_ = atomic_or(&rw.state, Atomic_RW_Mutex_State_Is_Writing) // Compare-and-exchange for absolute certainty that no one has come in to read.
return true _, ok := atomic_compare_exchange_strong(&rw.state, state, state | Atomic_RW_Mutex_State_Is_Writing)
if ok {
return true
}
} }
// A reader is active or came in while we have the lock, so we need to
// back out.
atomic_mutex_unlock(&rw.mutex) atomic_mutex_unlock(&rw.mutex)
} }
return false return false
@@ -150,16 +157,22 @@ atomic_rw_mutex_try_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) -> bool {
// atomic_rw_mutex_shared_lock locks rw for reading (with arbitrary number of readers) // atomic_rw_mutex_shared_lock locks rw for reading (with arbitrary number of readers)
atomic_rw_mutex_shared_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) { atomic_rw_mutex_shared_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) {
state := atomic_load(&rw.state) state := atomic_load(&rw.state)
for state & (Atomic_RW_Mutex_State_Is_Writing|Atomic_RW_Mutex_State_Writer_Mask) == 0 { for state & Atomic_RW_Mutex_State_Is_Writing == 0 {
ok: bool ok: bool
state, ok = atomic_compare_exchange_weak(&rw.state, state, state + Atomic_RW_Mutex_State_Reader) state, ok = atomic_compare_exchange_weak(&rw.state, state, state + Atomic_RW_Mutex_State_Reader)
if ok { if ok {
// We succesfully took the shared reader lock without any writers intervening.
return return
} }
} }
// A writer is active or came in while we were trying to get a shared
// reader lock, so now we must take the full lock in order to wait for the
// writer to give it up.
atomic_mutex_lock(&rw.mutex) atomic_mutex_lock(&rw.mutex)
// At this point, we have the lock, so we can add to the reader count.
_ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Reader) _ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Reader)
// Then we give up the lock to let other readers (or writers) come through.
atomic_mutex_unlock(&rw.mutex) atomic_mutex_unlock(&rw.mutex)
} }
@@ -169,6 +182,8 @@ atomic_rw_mutex_shared_unlock :: proc "contextless" (rw: ^Atomic_RW_Mutex) {
if (state & Atomic_RW_Mutex_State_Reader_Mask == Atomic_RW_Mutex_State_Reader) && if (state & Atomic_RW_Mutex_State_Reader_Mask == Atomic_RW_Mutex_State_Reader) &&
(state & Atomic_RW_Mutex_State_Is_Writing != 0) { (state & Atomic_RW_Mutex_State_Is_Writing != 0) {
// We were the last reader, so post to the writer with the lock who's
// waiting to continue.
atomic_sema_post(&rw.sema) atomic_sema_post(&rw.sema)
} }
} }
@@ -176,12 +191,21 @@ atomic_rw_mutex_shared_unlock :: proc "contextless" (rw: ^Atomic_RW_Mutex) {
// atomic_rw_mutex_try_shared_lock tries to lock rw for reading (with arbitrary number of readers) // atomic_rw_mutex_try_shared_lock tries to lock rw for reading (with arbitrary number of readers)
atomic_rw_mutex_try_shared_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) -> bool { atomic_rw_mutex_try_shared_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) -> bool {
state := atomic_load(&rw.state) state := atomic_load(&rw.state)
if state & (Atomic_RW_Mutex_State_Is_Writing|Atomic_RW_Mutex_State_Writer_Mask) == 0 { // NOTE: We need to check this in a for loop, because it is possible for
_, ok := atomic_compare_exchange_strong(&rw.state, state, state + Atomic_RW_Mutex_State_Reader) // another reader to change the underlying state which would cause our
// compare-and-exchange to fail.
for state & (Atomic_RW_Mutex_State_Is_Writing) == 0 {
ok: bool
state, ok = atomic_compare_exchange_weak(&rw.state, state, state + Atomic_RW_Mutex_State_Reader)
if ok { if ok {
return true return true
} }
} }
// A writer is active or came in during our lock attempt.
// We try to take the full lock, and if that succeeds (perhaps because the
// writer finished during the time since we failed our CAS), we increment
// the reader count and head on.
if atomic_mutex_try_lock(&rw.mutex) { if atomic_mutex_try_lock(&rw.mutex) {
_ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Reader) _ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Reader)
atomic_mutex_unlock(&rw.mutex) atomic_mutex_unlock(&rw.mutex)
@@ -82,7 +82,6 @@ Application_setActivationPolicy :: proc "c" (self: ^Application, activationPolic
// NOTE: this is technically deprecated but still actively used (Sokol, glfw, SDL, etc.) // NOTE: this is technically deprecated but still actively used (Sokol, glfw, SDL, etc.)
// and has no clear alternative although `activate` is what Apple tells you to use, // and has no clear alternative although `activate` is what Apple tells you to use,
// that does not work the same way. // that does not work the same way.
// @(deprecated="Use NSApplication method activate instead.")
@(objc_type=Application, objc_name="activateIgnoringOtherApps") @(objc_type=Application, objc_name="activateIgnoringOtherApps")
Application_activateIgnoringOtherApps :: proc "c" (self: ^Application, ignoreOtherApps: BOOL) { Application_activateIgnoringOtherApps :: proc "c" (self: ^Application, ignoreOtherApps: BOOL) {
msgSend(nil, self, "activateIgnoringOtherApps:", ignoreOtherApps) msgSend(nil, self, "activateIgnoringOtherApps:", ignoreOtherApps)
@@ -223,6 +223,11 @@ _Proc_Bsdinfo :: struct {
/*--==========================================================================--*/ /*--==========================================================================--*/
/* Get window size */
TIOCGWINSZ :: 0x40087468
/*--==========================================================================--*/
syscall_fsync :: #force_inline proc "contextless" (fildes: c.int) -> bool { syscall_fsync :: #force_inline proc "contextless" (fildes: c.int) -> bool {
return !(cast(bool)intrinsics.syscall(unix_offset_syscall(.fsync), uintptr(fildes))) return !(cast(bool)intrinsics.syscall(unix_offset_syscall(.fsync), uintptr(fildes)))
} }
@@ -275,6 +280,10 @@ syscall_lseek :: #force_inline proc "contextless" (fd: c.int, offset: i64, whenc
return cast(i64)intrinsics.syscall(unix_offset_syscall(.lseek), uintptr(fd), uintptr(offset), uintptr(whence)) return cast(i64)intrinsics.syscall(unix_offset_syscall(.lseek), uintptr(fd), uintptr(offset), uintptr(whence))
} }
syscall_ioctl :: #force_inline proc "contextless" (fd: c.int, request: u32, arg: rawptr) -> c.int {
return (cast(c.int)intrinsics.syscall(unix_offset_syscall(.ioctl), uintptr(fd), uintptr(request), uintptr(arg)))
}
syscall_gettid :: #force_inline proc "contextless" () -> u64 { syscall_gettid :: #force_inline proc "contextless" () -> u64 {
return cast(u64)intrinsics.syscall(unix_offset_syscall(.gettid)) return cast(u64)intrinsics.syscall(unix_offset_syscall(.gettid))
} }
+890 -890
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
package sys_freebsd
/* Get window size */
TIOCGWINSZ :: 0x40087468
+1
View File
@@ -34,6 +34,7 @@ init_platform :: proc() {
} else { } else {
os_version.platform = .MacOS os_version.platform = .MacOS
switch version.majorVersion { switch version.majorVersion {
case 26: ws(&b, "macOS Tahoe")
case 15: ws(&b, "macOS Sequoia") case 15: ws(&b, "macOS Sequoia")
case 14: ws(&b, "macOS Sonoma") case 14: ws(&b, "macOS Sonoma")
case 13: ws(&b, "macOS Ventura") case 13: ws(&b, "macOS Ventura")
+1 -1
View File
@@ -1,6 +1,6 @@
package sysinfo package sysinfo
when !(ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64) { when !(ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 || ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
#assert(false, "This package is unsupported on this architecture.") #assert(false, "This package is unsupported on this architecture.")
} }
+18 -15
View File
@@ -1618,36 +1618,39 @@ PER_HPUX :: 0x0010
PER_MASK :: 0x00ff PER_MASK :: 0x00ff
/* /*
Bits for access modes for shared memory Bits for SystemV IPC flags.
In this enum, access modes are common for any shared memory. Prefixed
entries (i.e. `IPC_` or `SHM_`) denote flags, where `IPC_` are common flags
for all SystemV IPC primitives, and `SHM_`, `SEM_` and `MSG_` are specific
to shared memory segments, semaphores and message queues respectively.
These bits overlap, because they are meant to be used within the
context of specific procedures. Creation flags, used for `*get` procedures,
and usage flags used by all other IPC procedures. Do not mix creation and
usage flags, as well as flags prefixed differently (excluding `IPC_`
prefix).
*/ */
IPC_Mode_Bits :: enum { IPC_Flags_Bits :: enum {
// Access modes for shared memory.
WROTH = 1, WROTH = 1,
RDOTH = 2, RDOTH = 2,
WRGRP = 4, WRGRP = 4,
RDGRP = 5, RDGRP = 5,
WRUSR = 7, WRUSR = 7,
RDUSR = 8, RDUSR = 8,
DEST = 9, // Creation flags for shared memory.
LOCKED = 10,
}
/*
Shared memory flags bits
*/
IPC_Flags_Bits :: enum {
IPC_CREAT = 9, IPC_CREAT = 9,
IPC_EXCL = 10, IPC_EXCL = 10,
IPC_NOWAIT = 11,
// Semaphore
SEM_UNDO = 9,
// Shared memory
SHM_HUGETLB = 11, SHM_HUGETLB = 11,
SHM_NORESERVE = 12, SHM_NORESERVE = 12,
// Usage flags for shared memory.
IPC_NOWAIT = 11,
SEM_UNDO = 9,
SHM_RDONLY = 12, SHM_RDONLY = 12,
SHM_RND = 13, SHM_RND = 13,
SHM_REMAP = 14, SHM_REMAP = 14,
SHM_EXEC = 15, SHM_EXEC = 15,
// Message queue
MSG_NOERROR = 12, MSG_NOERROR = 12,
MSG_EXCEPT = 13, MSG_EXCEPT = 13,
MSG_COPY = 14, MSG_COPY = 14,
+4 -1
View File
@@ -391,4 +391,7 @@ MAP_HUGE_256MB :: transmute(Map_Flags)(u32(28) << MAP_HUGE_SHIFT)
MAP_HUGE_512MB :: transmute(Map_Flags)(u32(29) << MAP_HUGE_SHIFT) MAP_HUGE_512MB :: transmute(Map_Flags)(u32(29) << MAP_HUGE_SHIFT)
MAP_HUGE_1GB :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT) MAP_HUGE_1GB :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT)
MAP_HUGE_2GB :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT) MAP_HUGE_2GB :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT)
MAP_HUGE_16GB :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT) MAP_HUGE_16GB :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT)
/* Get window size */
TIOCGWINSZ :: 0x5413
+1 -1
View File
@@ -1413,7 +1413,7 @@ umask :: proc "contextless" (mask: Mode) -> Mode {
Available since Linux 1.0. Available since Linux 1.0.
*/ */
gettimeofday :: proc "contextless" (tv: ^Time_Val) -> (Errno) { gettimeofday :: proc "contextless" (tv: ^Time_Val) -> (Errno) {
ret := syscall(SYS_gettimeofday, tv) ret := syscall(SYS_gettimeofday, tv, rawptr(nil))
return Errno(-ret) return Errno(-ret)
} }

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