Merge tag 'dev-2025-07'

This commit is contained in:
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/cgltf/src
gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64
./odin check vendor/sdl3 -vet -strict-style -disallow-do -target:netbsd_amd64 -no-entry-point
./odin check vendor/sdl3 -vet -strict-style -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/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/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64
./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 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 -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 -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 -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)
./odin check tests/benchmark -vet -strict-style -no-entry-point
@@ -63,11 +63,11 @@ jobs:
gmake -C vendor/stb/src
gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
./odin check vendor/sdl3 -vet -strict-style -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/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/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
./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 -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 -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 -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)
./odin check tests/benchmark -vet -strict-style -no-entry-point
ci:
@@ -75,7 +75,7 @@ jobs:
fail-fast: false
matrix:
# 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 }}
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
@@ -123,17 +123,17 @@ jobs:
- name: Odin run -debug
run: ./odin run examples/demo -debug
- name: Odin check examples/all
run: ./odin check examples/all -strict-style -vet -disallow-do
- name: Odin check vendor/sdl3
run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point
run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
- name: Odin check examples/all/sdl3
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
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
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
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
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
run: |
cd tests/issues
@@ -141,37 +141,43 @@ jobs:
- name: Run demo on WASI WASM32
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
if: matrix.os == 'macos-14'
- 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
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
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
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
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'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
- name: Odin check vendor/sdl3 for 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_i386
- name: Odin check examples/all/sdl3 for Linux arm64
if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
- name: Odin check vendor/sdl3 for FreeBSD amd64
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 examples/all/sdl3 for FreeBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
- name: Odin check vendor/sdl3 for OpenBSD 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 examples/all/sdl3 for OpenBSD amd64
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:
name: Windows Build, Check, and Test
@@ -202,38 +208,38 @@ jobs:
shell: cmd
run: |
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
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check examples/all -vet -strict-style -disallow-do
- name: Odin check vendor/sdl3
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
- name: Odin check examples/all/sdl3
shell: cmd
run: |
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
shell: cmd
run: |
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
shell: cmd
run: |
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
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
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
shell: cmd
run: |
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
shell: cmd
run: |
@@ -251,12 +257,6 @@ jobs:
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\documentation
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
shell: cmd
run: |
@@ -293,25 +293,25 @@ jobs:
make -C vendor/miniaudio/src
- 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
run: ./odin check vendor/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point
- name: Odin check examples/all/sdl3
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
run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross
- 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
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
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
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
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
demo.bin
libLLVM*.so*
*.a
# shared collection
shared/
+1
View File
@@ -169,6 +169,7 @@ type_is_union :: proc($T: typeid) -> bool ---
type_is_enum :: proc($T: typeid) -> bool ---
type_is_proc :: 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_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`.
@builtin
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 {
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`.
@builtin
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 {
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
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 {
return
}
@@ -667,6 +670,9 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcas
@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 {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil {
return
}
@@ -690,6 +696,9 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca
@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 {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil {
return
}
@@ -1,7 +1,7 @@
package runtime
import "base:intrinsics"
import "base:sanitizer"
// import "base:sanitizer"
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.capacity = uint(end - uintptr(block.base))
sanitizer.address_poison(block.base, block.capacity)
// sanitizer.address_poison(block.base, block.capacity)
// Should be zeroed
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) {
if block_to_free != nil {
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)
}
}
@@ -87,7 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
return
}
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
return
}
@@ -169,7 +169,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil {
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
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
}
@@ -234,7 +234,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
// grow data in-place, adjusting next allocation
block.used = uint(new_end)
data = block.base[start:new_end]
sanitizer.address_unpoison(data)
// sanitizer.address_unpoison(data)
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)
amount_to_zero := block.used-temp.used
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
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 {
#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 {
+282 -58
View File
@@ -4,7 +4,13 @@ import "base:builtin"
import "base: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) {
data: [dynamic]T,
len: uint,
@@ -13,18 +19,31 @@ Queue :: struct($T: typeid) {
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 {
if q.data.allocator.procedure == nil {
q.data.allocator = allocator
}
clear(q)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
data = nil,
len = 0,
cap = 0,
allocator = allocator,
}
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`.
// Any previous contents are not available.
/*
Initialize a `Queue` from a fixed `backing` slice into which modifications are
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 {
clear(q)
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
}
// 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 {
clear(q)
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
}
// 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)) {
delete(q.data)
}
// The length of the queue
/*
Return the length of the queue.
*/
len :: proc(q: $Q/Queue($T)) -> int {
return int(q.len)
}
// The current capacity of the queue
/*
Return the capacity of the queue.
*/
cap :: proc(q: $Q/Queue($T)) -> int {
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 {
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 {
if capacity > space(q^) {
return _grow(q, uint(capacity))
return _grow(q, uint(capacity))
}
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 {
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)
return q.data[idx]
}
front :: proc(q: ^$Q/Queue($T)) -> T {
return q.data[q.offset]
}
front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
return &q.data[q.offset]
}
/*
Get a pointer to the element at index `i`.
back :: proc(q: ^$Q/Queue($T)) -> T {
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
return q.data[idx]
}
back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
This will raise a bounds checking error if `i` is an invalid index.
*/
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))
idx := (uint(i)+q.offset)%builtin.len(q.data)
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) {
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)
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))
idx := (uint(i)+q.offset)%builtin.len(q.data)
/*
Get the element at the front of the queue.
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]
}
@(deprecated="Use `front_ptr` instead")
peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
runtime.bounds_check_error_loc(loc, 0, builtin.len(q.data))
idx := q.offset%builtin.len(q.data)
return &q.data[idx]
return front_ptr(q, loc)
}
@(deprecated="Use `back_ptr` instead")
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))
idx := (uint(q.len - 1)+q.offset)%builtin.len(q.data)
return &q.data[idx]
return back_ptr(q, loc)
}
// 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) {
if space(q^) == 0 {
_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
}
// 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) {
if space(q^) == 0 {
_grow(q) or_return
}
}
q.offset = uint(q.offset - 1 + builtin.len(q.data)) % builtin.len(q.data)
q.len += 1
q.data[q.offset] = elem
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) {
assert(condition=q.len > 0, loc=loc)
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
q.len -= 1
idx := (q.offset+uint(q.len))%builtin.len(q.data)
elem = q.data[idx]
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) {
if q.len > 0 {
q.len -= 1
@@ -169,15 +361,25 @@ pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
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) {
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]
q.offset = (q.offset+1)%builtin.len(q.data)
q.len -= 1
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) {
if q.len > 0 {
elem = q.data[q.offset]
@@ -188,13 +390,18 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
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) {
n := uint(builtin.len(elems))
if space(q^) < int(n) {
_grow(q, q.len + n) or_return
}
sz := uint(builtin.len(q.data))
insert_from := (q.offset + q.len) % sz
insert_to := n
@@ -207,19 +414,31 @@ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime
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) {
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 {
nu := uint(n)
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) {
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 {
q.len -= uint(n)
}
@@ -231,9 +450,14 @@ append_elem :: push_back
append_elems :: push_back_elems
push :: 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)) {
q.len = 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.
case f16:
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:] }
io.write_string(w, str) or_return
case f32:
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:] }
io.write_string(w, str) or_return
case f64:
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:] }
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:
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)
+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) }
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)
}
}
+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) {
switch i in a {
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:
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 {
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
@@ -286,7 +286,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
case runtime.Type_Info_Integer:
buf: [40]byte
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
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)
}
t := &Tokenizer{}
t := new(Tokenizer)
init(t, string(data), path, error_handler)
doc = new(Document)
@@ -403,6 +403,7 @@ destroy :: proc(doc: ^Document) {
}
delete(doc.strings_to_free)
free(doc.tokenizer)
free(doc)
}
+2 -2
View File
@@ -19,7 +19,7 @@ SUBTAG_NAME :: "name"
SUBTAG_POS :: "pos"
SUBTAG_REQUIRED :: "required"
SUBTAG_HIDDEN :: "hidden"
SUBTAG_VARIADIC :: "variadic"
SUBTAG_MANIFOLD :: "manifold"
SUBTAG_FILE :: "file"
SUBTAG_PERMS :: "perms"
SUBTAG_INDISTINCT :: "indistinct"
@@ -28,7 +28,7 @@ TAG_USAGE :: "usage"
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_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
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:
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.
- `hidden`: hide this flag from the usage documentation.
- `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.
- `perms`: for `os.Handle` types, file open permissions.
- `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`.
`variadic` may be given a number (`variadic=N`) above 1 to limit how many extra
arguments it consumes.
`manifold` may be given a number (`manifold=N`) above 1 to limit how many extra
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`.
@@ -160,7 +172,7 @@ at parse time.
--flag
--flag=argument
--flag argument
--flag argument repeating-argument
--flag argument (manifold-argument)
`-flag` may also be substituted for `--flag`.
+1 -1
View File
@@ -4,7 +4,7 @@ import "core:os"
Parse_Error_Reason :: enum {
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,
// The underlying type does not support the string value it is being set to.
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."`,
// (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."`,
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
+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)
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.
field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG)
field = reflect.struct_field_by_name(T, INTERNAL_OVERFLOW_FLAG)
} else {
return Parse_Error {
.Extra_Positional,
@@ -117,8 +117,8 @@ set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args
case runtime.Type_Info_Dynamic_Array:
future_args = 1
if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic {
// Variadic arrays may specify how many arguments they consume at once.
if length, is_manifold := get_struct_subtag(tag, SUBTAG_MANIFOLD); is_manifold {
// Manifold arrays may specify how many arguments they consume at once.
// Otherwise, they take everything that's left.
if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
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 `--`.
// Everything from now on will be treated as an argument.
future_args = max(int)
current_flag = INTERNAL_VARIADIC_FLAG
current_flag = INTERNAL_OVERFLOW_FLAG
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 {
case runtime.Type_Info_Map:
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.",
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)
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 {
fmt.assertf(value > 0,
"%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,
"%T.%s has `%s` set to 1. This has no effect.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
"%T.%s has `%s` set to 1. This is equivalent to not defining `%s`.",
model_type, field.name, SUBTAG_MANIFOLD, SUBTAG_MANIFOLD, loc = loc)
}
#partial switch specific_type_info in field.type.variant {
case runtime.Type_Info_Dynamic_Array:
fmt.assertf(style != .Odin,
"%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:
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 {
// Odin-style: `-flag`, `-flag:option`, `-map:key=value`
Odin,
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument`
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument (manifold-argument)`
Unix,
}
@@ -61,7 +61,7 @@ parse :: proc(
}
case .Unix:
// Support for `-flag argument (repeating-argument ...)`
// Support for `-flag argument (manifold-argument ...)`
future_args: int
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_required: bool,
is_boolean: bool,
is_variadic: bool,
variadic_length: int,
is_manifold: bool,
manifold_length: int,
}
//
// POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ...
//
sort_flags :: proc(i, j: Flag) -> slice.Ordering {
// `varg` goes to the end.
if i.name == INTERNAL_VARIADIC_FLAG {
// `overflow` goes to the end.
if i.name == INTERNAL_OVERFLOW_FLAG {
return .Greater
} else if j.name == INTERNAL_VARIADIC_FLAG {
} else if j.name == INTERNAL_OVERFLOW_FLAG {
return .Less
}
@@ -120,10 +120,10 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
flag.is_required = true
flag.required_min, flag.required_max, _ = parse_requirements(requirement)
}
if length_str, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic {
flag.is_variadic = true
if length_str, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
flag.is_manifold = true
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:
requirement_spec := describe_array_requirements(flag)
if flag.is_variadic || flag.name == INTERNAL_VARIADIC_FLAG {
if flag.variadic_length == 0 {
if flag.is_manifold || flag.name == INTERNAL_OVERFLOW_FLAG {
if flag.manifold_length == 0 {
flag.type_description = fmt.tprintf("<%v, ...>%s",
specific_type_info.elem.id,
requirement_spec)
} else {
flag.type_description = fmt.tprintf("<%v, %i at once>%s",
specific_type_info.elem.id,
flag.variadic_length,
flag.manifold_length,
requirement_spec)
}
} 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)
} else if flag.is_boolean {
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)
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
}
strings.write_byte(&builder, ' ')
if flag.name == INTERNAL_VARIADIC_FLAG {
if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, "...")
continue
}
@@ -252,7 +252,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
strings.write_byte(&builder, '\t')
if flag.name == INTERNAL_VARIADIC_FLAG {
if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, flag.type_description)
} else {
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
if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
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
defer fi.zero = prev_zero
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
if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
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 {
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
str := strconv.append_float(buf[:], amt, 'f', prec, 64)
str := strconv.write_float(buf[:], amt, 'f', prec, 64)
// Add the unit at the end.
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
// 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 {
// 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) {
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)
}
write_i64 :: proc(w: Writer, i: i64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
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)
}
@@ -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) {
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)
}
write_i128 :: proc(w: Writer, i: i128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
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)
}
write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: Error) {
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]
if s[1] == '+' || 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) {
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]
if s[1] == '+' || 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) {
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]
if s[1] == '+' || 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
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) {
case 0:
write_string(w, "00", &n) or_return
+2 -3
View File
@@ -160,6 +160,5 @@ destroy :: proc {
int_destroy :: proc(integers: ..^Int)
*/
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) {
internal_swap(dest, x)
return nil
return internal_clamp(dest)
}
internal_swap(x, y)
}
internal_swap(dest, x)
return err
return internal_clamp(dest)
}
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.
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) {
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
}
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
f := &Int{}
defer internal_int_destroy(f)
internal_int_gcd(f, &z.a, &z.b) or_return
if !internal_int_equals_digit(f, 1) {
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
defer if has_sign {
f = -builtin.abs(f)
}
exp := alen - blen
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) {
exact = false
}
if has_sign {
f = -builtin.abs(f)
}
return
}
+10 -5
View File
@@ -103,7 +103,7 @@ round :: proc(x: $T/Fixed($Backing, $Fraction_Width)) -> Backing {
}
@(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
x := x
@@ -124,16 +124,16 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
when size_of(Backing) < 16 {
T :: u64
append_uint :: strconv.append_uint
write_uint :: strconv.write_uint
} else {
T :: u128
append_uint :: strconv.append_u128
write_uint :: strconv.write_u128
}
integer := T(x.i) >> Fraction_Width
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)
if fraction != 0 {
buf[i] = '.'
@@ -155,7 +155,7 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
@(require_results)
to_string :: proc(x: $T/Fixed($Backing, $Fraction_Width), allocator := context.allocator) -> string {
buf: [48]byte
s := append(buf[:], x)
s := write(buf[:], x)
str := make([]byte, len(s), allocator)
copy(str, s)
return string(str)
@@ -294,3 +294,8 @@ _power_of_two_table := [129]string{
"85070591730234615865843651857942052864",
"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
import "base:runtime"
import "base:sanitizer"
// import "base:sanitizer"
/*
Rollback stack default block size.
@@ -134,7 +134,7 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
stack.head.next_block = nil
stack.head.last_alloc = nil
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)
}
res := ptr[:size]
sanitizer.address_unpoison(res)
// sanitizer.address_unpoison(res)
return res, nil
}
return nil, .Out_Of_Memory
@@ -338,7 +338,7 @@ rb_resize_bytes_non_zeroed :: proc(
block.offset += cast(uintptr)size - cast(uintptr)old_size
}
res := (ptr)[:size]
sanitizer.address_unpoison(res)
// sanitizer.address_unpoison(res)
#no_bounds_check return res, nil
}
}
+15 -14
View File
@@ -10,7 +10,7 @@
package mem_tlsf
import "base:intrinsics"
import "base:sanitizer"
// import "base:sanitizer"
import "base:runtime"
// 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
}
sanitizer.address_poison(new_pool_buf)
// sanitizer.address_poison(new_pool_buf)
// Allocate a new link in the `control.pool` tracking structure.
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)
}
@(private, require_results, no_sanitize_address)
@(private, require_results)
alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
res, err = alloc_bytes_non_zeroed(control, size, align)
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) {
assert(control != nil)
// `size` is currently ignored
@@ -276,7 +277,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
block := block_from_ptr(ptr)
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 = block_merge_prev(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)
res = ([^]byte)(ptr)[:new_size]
sanitizer.address_unpoison(res)
// sanitizer.address_unpoison(res)
if 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)
}
@(private, require_results, no_sanitize_address)
@(private, require_results)
align_up :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two")
return (x + (align - 1)) &~ (align - 1)
}
@(private, require_results, no_sanitize_address)
@(private, require_results)
align_down :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two")
return x - (x & (align - 1))
}
@(private, require_results, no_sanitize_address)
@(private, require_results)
align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
assert(0 == (align & (align - 1)), "must align to a power of two")
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.
@(private, require_results, no_sanitize_address)
@(private, require_results)
adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
if size == 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.
@(private, require_results, no_sanitize_address)
@(private, require_results)
adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
if size == 0 {
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
// 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) {
if size < SMALL_BLOCK_SIZE {
// Store small blocks in first list.
@@ -550,7 +551,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
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) {
rounded = 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)
@(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) {
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_mark_as_used(block)
res = ([^]byte)(block_to_ptr(block))[:size]
sanitizer.address_unpoison(res)
// sanitizer.address_unpoison(res)
}
return
}
+12 -12
View File
@@ -3,7 +3,7 @@ package mem_virtual
import "core:mem"
import "core:sync"
import "base:sanitizer"
// import "base:sanitizer"
Arena_Kind :: enum uint {
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 {
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
}
@@ -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.total_used = 0
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
}
@@ -82,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
arena.kind = .Buffer
sanitizer.address_poison(buffer[:])
// sanitizer.address_poison(buffer[:])
block_base := raw_data(buffer)
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
}
sanitizer.address_unpoison(data)
// sanitizer.address_unpoison(data)
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])
}
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
} else if pos == 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.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)
}
}
@@ -219,9 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil {
curr_block_used := int(arena.curr_block.used)
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)
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
case .Static, .Buffer:
@@ -349,7 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
if size < old_size {
// shrink data in-place
data = old_data[:size]
sanitizer.address_poison(old_data[size:old_size])
// sanitizer.address_poison(old_data[size:old_size])
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
arena.total_used += block.used - prev_used
data = block.base[start:new_end]
sanitizer.address_unpoison(data)
// sanitizer.address_unpoison(data)
return
}
}
@@ -374,7 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
return
}
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
case .Query_Features:
set := (^mem.Allocator_Mode_Set)(old_memory)
+5 -5
View File
@@ -2,7 +2,7 @@ package mem_virtual
import "core:mem"
import "base:intrinsics"
import "base:sanitizer"
// import "base:sanitizer"
import "base:runtime"
_ :: runtime
@@ -22,7 +22,7 @@ reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Erro
@(no_sanitize_address)
commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
sanitizer.address_unpoison(data, size)
// sanitizer.address_unpoison(data, size)
return _commit(data, size)
}
@@ -35,13 +35,13 @@ reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: All
@(no_sanitize_address)
decommit :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_poison(data, size)
// sanitizer.address_poison(data, size)
_decommit(data, size)
}
@(no_sanitize_address)
release :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_unpoison(data, size)
// sanitizer.address_unpoison(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]
block.used += size
sanitizer.address_unpoison(data)
// sanitizer.address_unpoison(data)
return
}
+4
View File
@@ -64,6 +64,7 @@ Network_Error :: union #shared_nil {
UDP_Recv_Error,
Shutdown_Error,
Interfaces_Error,
Socket_Info_Error,
Socket_Option_Error,
Set_Blocking_Error,
Parse_Endpoint_Error,
@@ -260,6 +261,9 @@ DNS_Configuration :: struct {
resolv_conf: 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,
// so we don't have to do it each call.
name_servers: []Endpoint,
+29 -47
View File
@@ -22,69 +22,43 @@ package net
Haesbaert: Security fixes
*/
@(require) import "base:runtime"
import "core:mem"
import "core:strings"
import "core:time"
import "core:os"
import "core:math/rand"
/*
Default configuration for DNS resolution.
*/
@(require) import "core:sync"
dns_config_initialized: sync.Once
when ODIN_OS == .Windows {
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
resolv_conf = "",
hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts",
dns_configuration := DNS_Configuration{
resolv_conf = "",
hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts",
}
} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
resolv_conf = "/etc/resolv.conf",
hosts_file = "/etc/hosts",
dns_configuration := DNS_Configuration{
resolv_conf = "/etc/resolv.conf",
hosts_file = "/etc/hosts",
}
} else {
#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() {
/*
Resolve %ENVIRONMENT% placeholders in their paths.
*/
dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf)
dns_configuration.hosts_file, _ = replace_environment_path(dns_configuration.hosts_file)
}
@(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
when ODIN_OS == .Windows {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator)
copy(dns_configuration.hosts_file_buf[:], val)
dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)])
}
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.
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`.
*/
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)
}
@@ -219,6 +196,9 @@ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocat
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) {
when ODIN_OS == .Windows {
sync.once_do(&dns_config_initialized, init_dns_configuration)
}
context.allocator = allocator
if type != .SRV {
@@ -440,6 +420,8 @@ load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (
splits := strings.fields(line)
defer delete(splits)
(len(splits) >= 2) or_continue
ip_str := splits[0]
addr := parse_address(ip_str)
if addr == nil {
@@ -886,4 +868,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator
xid = hdr.id
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[:])
}
}
+17
View File
@@ -246,6 +246,23 @@ Shutdown_Error :: enum i32 {
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 {
None,
// 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 {
#partial switch posix.errno() {
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 {
assert(errno != nil)
_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 {
assert(errno != nil)
_last_error = errno
+7
View File
@@ -18,3 +18,10 @@ _last_platform_error_string :: proc() -> string {
_set_last_platform_error :: proc(err: i32) {
_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 {
#partial switch win.System_Error(win.WSAGetLastError()) {
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.
*/
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)
}
/*
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) {
return _accept_tcp(socket, options)
}
+15 -2
View File
@@ -137,11 +137,24 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_
}
@(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_len := posix.socklen_t(size_of(addr))
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
}
+16 -2
View File
@@ -140,12 +140,26 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
}
@(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
errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr)
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
}
+15 -2
View File
@@ -218,11 +218,24 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
}
@(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
errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
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
}
+16 -2
View File
@@ -177,11 +177,25 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
}
@(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
sockaddrlen := c.int(size_of(sockaddr))
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
}
+1 -1
View File
@@ -125,7 +125,7 @@ percent_encode :: proc(s: string, allocator := context.allocator) -> string {
bytes, n := utf8.encode_rune(ch)
for byte in bytes[:n] {
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_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)
p.expr_level += 1
expr := parse_expr(p, false)
skip_possible_newline(p)
p.expr_level -= 1
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
for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
docs := p.lead_comment
name := parse_ident(p)
expect_token(p, .Colon)
type := parse_type(p)
@@ -2932,6 +2935,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
if p.curr_tok.kind == .String {
tag = expect_token(p, .String)
}
ok := allow_token(p, .Comma)
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.bit_size = bit_size
field.tag = tag
field.docs = docs
field.comments = p.line_comment
append(&fields, field)
allow_token(p, .Comma) or_break
if !ok {
break
}
}
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:
x := expr
cond := parse_expr(p, lhs)
skip_possible_newline(p)
else_tok := expect_token(p, .Else)
y := parse_expr(p, lhs)
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
}
if !is_using && is_blank_ident(import_name) {
error(p, import_name.pos, "illegal import name: '_'")
}
path := expect_token_after(p, .String, "import")
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
// NOTE: the value will be allocated with the supplied allocator
@(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 == "" {
return
}
@@ -29,17 +29,54 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
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
// 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: the value will be allocated with the supplied allocator
@(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)
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 :: proc(key, value: string) -> Error {
k := win32.utf8_to_wstring(key)
+4
View File
@@ -35,6 +35,9 @@ General_Error :: enum u32 {
File_Is_Pipe,
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 .File_Is_Pipe: return "file is pipe"
case .Not_Dir: return "file is not directory"
case .Env_Var_Not_Found: return "environment variable not found"
}
case io.Error:
switch e {
+54 -1
View File
@@ -4,6 +4,7 @@ import "base:intrinsics"
import "base:runtime"
import "core:io"
import "core:strconv"
import "core:strings"
import "core:unicode/utf8"
@@ -57,7 +58,7 @@ write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) {
if r < 32 {
if wrap(write_string(f, "\\x"), &n, &err) { return }
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) {
case 0: if wrap(write_string(f, "00"), &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 {
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
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
// To distinguish between an empty value and an unset value, use lookup_env
// NOTE: the value will be allocated with the supplied allocator
@(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)
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
// Otherwise the returned value will be empty and the boolean will be false
// NOTE: the value will be allocated with the supplied allocator
@(require_results)
lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
return _lookup_env(key, allocator)
lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
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
// Returns Error on failure
set_env :: proc(key, value: string) -> Error {
@@ -45,4 +68,55 @@ environ :: proc(allocator: runtime.Allocator) -> ([]string, Error) {
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
}
_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 {
_build_env()
}
@@ -53,6 +53,23 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
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 {
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
_build_env()
+31 -1
View File
@@ -7,7 +7,7 @@ import "base:runtime"
import "core:strings"
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 == "" {
return
}
@@ -26,6 +26,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
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) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
+29 -1
View File
@@ -67,7 +67,7 @@ delete_string_if_not_original :: proc(str: string) {
}
@(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 {
return
}
@@ -79,6 +79,34 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
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)
_set_env :: proc(key, value: string) -> (err: Error) {
build_env() or_return
+31 -1
View File
@@ -4,7 +4,7 @@ package os2
import win32 "core:sys/windows"
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 == "" {
return
}
@@ -36,6 +36,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
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 {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
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,
No_HOME_Variable,
Env_Var_Not_Found,
Unsupported,
}
@@ -59,20 +62,22 @@ error_string :: proc(ferr: Error) -> string {
case General_Error:
switch e {
case .None: return ""
case .Permission_Denied: return "permission denied"
case .Exist: return "file already exists"
case .Not_Exist: return "file does not exist"
case .Closed: return "file already closed"
case .Timeout: return "i/o timeout"
case .Broken_Pipe: return "Broken pipe"
case .No_Size: return "file has no definite size"
case .Invalid_File: return "invalid file"
case .Invalid_Dir: return "invalid directory"
case .Invalid_Path: return "invalid path"
case .Invalid_Callback: return "invalid callback"
case .Invalid_Command: return "invalid command"
case .Unsupported: return "unsupported"
case .Pattern_Has_Separator: return "pattern has separator"
case .Permission_Denied: return "permission denied"
case .Exist: return "file already exists"
case .Not_Exist: return "file does not exist"
case .Closed: return "file already closed"
case .Timeout: return "i/o timeout"
case .Broken_Pipe: return "Broken pipe"
case .No_Size: return "file has no definite size"
case .Invalid_File: return "invalid file"
case .Invalid_Dir: return "invalid directory"
case .Invalid_Path: return "invalid path"
case .Invalid_Callback: return "invalid callback"
case .Invalid_Command: return "invalid command"
case .Unsupported: return "unsupported"
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:
switch e {
+1
View File
@@ -269,6 +269,7 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error
return
}
@(no_sanitize_memory)
_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
// TODO: Identify 0-sized "pseudo" files and return No_Size. This would
// 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 wrap(write_string(f, "\\x"), &n, &err) { return }
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) {
case 0: if wrap(write_string(f, "00"), &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"
@(require_results)
user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
#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
}
// ```
// Windows: C:\Users\Alice
// macOS: /Users/Alice
// Linux: /home/alice
// ```
@(require_results)
user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
env := "HOME"
#partial switch ODIN_OS {
case .Windows:
env = "USERPROFILE"
}
if v := get_env(env, allocator); v != "" {
return v, nil
}
return "", .Invalid_Path
return _user_home_dir(allocator)
}
// 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)
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)
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)
if cstr == nil {
return "", false
@@ -1066,11 +1067,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
}
@(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)
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 {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1196,7 +1225,7 @@ _processor_core_count :: proc() -> int {
return 1
}
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for _, i in res {
@@ -1205,6 +1234,11 @@ _alloc_command_line_arguments :: proc() -> []string {
return res
}
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
result := _unix_socket(c.int(domain), c.int(type), c.int(protocol))
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
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -674,7 +674,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -688,7 +688,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
s: OS_Stat = ---
result := _unix_fstat(fd, &s)
@@ -827,10 +827,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
}
@(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)
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)
if cstr == nil {
return "", false
@@ -839,11 +839,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
}
@(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)
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)
get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator
@@ -936,7 +964,7 @@ _processor_core_count :: proc() -> int {
}
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ {
@@ -944,3 +972,8 @@ _alloc_command_line_arguments :: proc() -> []string {
}
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
args := _alloc_command_line_arguments()
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ {
@@ -325,7 +325,12 @@ _alloc_command_line_arguments :: proc() -> []string {
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) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -339,7 +344,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -353,7 +358,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized
s: OS_Stat = ---
@@ -463,9 +468,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
}
@(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)
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)
if cstr == nil {
return "", false
@@ -474,11 +480,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
}
@(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)
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)
_processor_core_count :: proc() -> int {
info: haiku.system_info
+24
View File
@@ -249,3 +249,27 @@ exit :: proc "contextless" (code: int) -> ! {
current_thread_id :: proc "contextless" () -> int {
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
}
@(require_results)
@(require_results, no_sanitize_memory)
file_size :: proc(fd: Handle) -> (i64, Error) {
// deliberately uninitialized; the syscall fills this buffer for us
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
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -808,7 +808,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -822,7 +822,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized; the syscall fills this buffer for us
s: OS_Stat = ---
@@ -946,7 +946,7 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
}
@(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)
path_str := strings.clone_to_cstring(key, context.temp_allocator)
// 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)
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)
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 {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1069,7 +1097,7 @@ _processor_core_count :: proc() -> int {
return int(_unix_get_nprocs())
}
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ {
@@ -1078,6 +1106,11 @@ _alloc_command_line_arguments :: proc() -> []string {
return res
}
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
@(require_results)
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
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
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -736,7 +736,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -750,7 +750,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
s: OS_Stat = ---
result := _unix_fstat(fd, &s)
@@ -874,10 +874,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
}
@(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)
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)
if cstr == nil {
return "", false
@@ -886,11 +886,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
}
@(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)
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)
get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator
@@ -986,7 +1014,7 @@ _processor_core_count :: proc() -> int {
return 1
}
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ {
@@ -994,3 +1022,8 @@ _alloc_command_line_arguments :: proc() -> []string {
}
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
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -653,7 +653,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -667,7 +667,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized
s: OS_Stat = ---
@@ -787,9 +787,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
}
@(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)
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)
if cstr == nil {
return "", false
@@ -798,11 +799,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
}
@(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)
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)
get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator
@@ -885,7 +914,7 @@ _processor_core_count :: proc() -> int {
return int(_sysconf(_SC_NPROCESSORS_ONLN))
}
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ {
@@ -893,3 +922,8 @@ _alloc_command_line_arguments :: proc() -> []string {
}
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()
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> (args: []string) {
args = make([]string, len(runtime.args__))
for &arg, i in args {
@@ -36,6 +36,11 @@ _alloc_command_line_arguments :: proc() -> (args: []string) {
return
}
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
// WASI works with "preopened" directories, the environment retrieves directories
// (for example with `wasmtime --dir=. module.wasm`) and those given directories
// are the only ones accessible by the application.
@@ -239,3 +244,27 @@ exit :: proc "contextless" (code: int) -> ! {
runtime._cleanup_runtime_contextless()
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 {
arg_count: i32
arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count)
@@ -215,6 +215,14 @@ _alloc_command_line_arguments :: proc() -> []string {
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
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 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)
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))
}
@(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) {
return Interface{
collection = rawptr(s),
@@ -80,31 +72,6 @@ reverse_sort :: proc(it: Interface) {
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 {
n := it->len()
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) {
assert(f != nil)
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) {
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) {
assert(f != nil)
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)
}
// @(deprecated="use sort.sort_slice or slice.sort")
quick_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
a := array
n := len(a)
@@ -420,7 +379,6 @@ _log2 :: proc(x: int) -> int {
return res
}
// @(deprecated="use sort.sort or slice.sort_by")
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) {
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)
}
// @(deprecated="use sort.sort_slice or slice.sort")
merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
merge :: proc(a: A, start, mid, end: int) {
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)
}
// @(deprecated="use sort.sort or slice.sort_by")
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 {
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) {
sift :: proc(a: A, pi: int, n: int) #no_bounds_check {
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.
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
- s: The input string representing the floating-point number
**Returns**
**Returns**
- ok: A boolean indicating whether the parsing was successful
*/
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.
**Inputs**
**Inputs**
- buf: A byte slice buffer to hold the resulting string
- a: The struct to be converted to a string
**Returns**
**Returns**
- A string representation of the Decimal
*/
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.
**Inputs**
**Inputs**
- a: Pointer to the Decimal struct to be trimmed
*/
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.**
**Inputs**
**Inputs**
- a: Where the result will be stored
- idx: The value to be assigned to the Decimal
*/
@@ -190,11 +190,11 @@ assign :: proc(a: ^Decimal, idx: u64) {
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.**
**Inputs**
**Inputs**
- a: The Decimal struct to be shifted
- 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`
**Inputs**
**Inputs**
- a: The Decimal to be modified
- 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
**Inputs**
**Inputs**
- a: The Decimal to be modified
- 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
**Inputs**
**Inputs**
- a: The Decimal to check
- 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
**Inputs**
**Inputs**
- a: The Decimal to be modified
- 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
**Inputs**
**Inputs**
- a: The Decimal to be modified
- 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
**Inputs**
**Inputs**
- a: The Decimal value to be rounded down
- 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
**Inputs**
**Inputs**
- a: A pointer to the Decimal value to extract the rounded integer part from
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.
**Inputs**
**Inputs**
buf: A byte slice to store the resulting string
val: The floating-point value to be converted
@@ -40,7 +40,7 @@ Example:
bit_size := 64
result := strconv.generic_ftoa(buf[:], val, fmt, precision, bit_size) -> "3.14"
**Returns**
**Returns**
- A byte slice containing the formatted string
*/
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
**Inputs**
**Inputs**
- buf: The byte buffer to store the formatted number
- shortest: If true, generates the shortest representation of the number
- 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
- fmt: The format specifier (accepted values: 'f', 'F', 'e', 'E', 'g', 'G')
**Returns**
**Returns**
- 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 {
@@ -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
**Inputs**
**Inputs**
- d: The decimal number to round
- mant: The mantissa 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
**Inputs**
**Inputs**
- d: Pointer to the decimal number to convert
- info: Pointer to the Float_Info structure containing information about the floating-point format
**Returns**
**Returns**
- b: The bits representing the floating-point number
- 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.
**Inputs**
**Inputs**
- 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
- 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
- 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
}
/*
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
- x: The integer value to convert
- 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
- flags: The Int_Flags bit set to control integer formatting
**Returns**
**Returns**
- 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 {
panic("strconv: illegal base passed to append_bits")
panic("strconv: illegal base passed to write_bits")
}
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.
**Inputs**
**Inputs**
- 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
- 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
- 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
}
/*
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
- x: The 128-bit integer value to convert
- 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
- flags: The Int_Flags bit set to control integer formatting
**Returns**
- The string containing the integer representation appended to the buffer
**Returns**
- 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 {
panic("strconv: illegal base passed to append_bits")
panic("strconv: illegal base passed to write_bits")
}
a: [140]byte
+138 -138
View File
@@ -5,8 +5,8 @@ import "decimal"
/*
Parses a boolean value from the input string
**Inputs**
- s: The input string
**Inputs**
- s: The input string
- true: "1", "t", "T", "true", "TRUE", "True"
- false: "0", "f", "F", "false", "FALSE", "False"
- 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
Example:
import "core:fmt"
import "core:strconv"
parse_uint_example :: proc() {
@@ -399,14 +399,14 @@ Example:
n, ok = strconv.parse_uint("0xffff") // with prefix and inferred base
fmt.println(n,ok)
}
Output:
1234 true
65535 true
65535 true
**Returns**
**Returns**
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
@@ -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
**Inputs**
**Inputs**
- str: The input string containing the integer value
- 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)
@@ -436,12 +436,12 @@ Example:
n, ok := strconv.parse_i128_of_base("-1234eeee", 10)
fmt.println(n,ok)
}
Output:
-1234 false
**Returns**
**Returns**
- 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.
*/
@@ -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
**Inputs**
**Inputs**
- 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)
@@ -506,13 +506,13 @@ Example:
n, ok = strconv.parse_i128_maybe_prefixed("0xeeee")
fmt.println(n, ok)
}
Output:
1234 true
61166 true
**Returns**
**Returns**
- 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.
*/
@@ -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
**Inputs**
**Inputs**
- str: The input string containing the integer value
- 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)
@@ -590,13 +590,13 @@ Example:
n, ok = strconv.parse_u128_of_base("5678eeee", 16)
fmt.println(n, ok)
}
Output:
1234 false
1450766062 true
**Returns**
**Returns**
- 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.
*/
@@ -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
**Inputs**
**Inputs**
- 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)
@@ -649,13 +649,13 @@ Example:
n, ok = strconv.parse_u128_maybe_prefixed("5678eeee")
fmt.println(n, ok)
}
Output:
1234 true
5678 false
**Returns**
**Returns**
- 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.
*/
@@ -706,10 +706,10 @@ parse_u128 :: proc{parse_u128_maybe_prefixed, parse_u128_of_base}
/*
Converts a byte to lowercase
**Inputs**
**Inputs**
- ch: A byte character to be converted to lowercase.
**Returns**
**Returns**
- A lowercase byte character.
*/
@(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
**Inputs**
**Inputs**
- 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).
@@ -732,13 +732,13 @@ Example:
n, ok = strconv.parse_f32("5678e2")
fmt.printfln("%.3f %v", n, ok)
}
Output:
0.000 false
567800.000 true
**Returns**
**Returns**
- 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.
*/
@@ -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
**Inputs**
**Inputs**
- 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).
@@ -765,13 +765,13 @@ Example:
n, ok = strconv.parse_f64("5678e2")
fmt.printfln("%.3f %v", n, ok)
}
Output:
0.000 false
567800.000 true
**Returns**
**Returns**
- 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.
*/
@@ -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
**Inputs**
**Inputs**
- str: The input string containing a 32-bit floating point number.
Example:
@@ -801,14 +801,14 @@ Example:
n, _, ok = strconv.parse_f32_prefix("5678e2")
fmt.printfln("%.3f %v", n, ok)
}
Output:
0.000 false
567800.000 true
**Returns**
**Returns**
- value: The parsed 32-bit floating point number.
- nr: The length of the parsed substring.
- 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
**Inputs**
**Inputs**
- str: The input string containing a 64-bit floating point number.
Example:
@@ -846,7 +846,7 @@ Output:
1234.000 true
13.370 true
**Returns**
**Returns**
- value: The parsed 64-bit floating point number.
- nr: The length of the parsed substring.
- 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
**Inputs**
**Inputs**
- 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).
@@ -1200,13 +1200,13 @@ Example:
c, ok = strconv.parse_complex128("5+7i hellope", &n)
fmt.printfln("%v %i %t", c, n, ok)
}
Output:
3+1i 4 true
5+7i 4 false
**Returns**
**Returns**
- 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.
*/
@@ -1232,12 +1232,12 @@ parse_complex128 :: proc(str: string, n: ^int = nil) -> (value: complex128, ok:
}
value = complex(real_value, imag_value)
return
return
}
/*
Parses a 64-bit complex number from a string
**Inputs**
**Inputs**
- 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).
@@ -1253,13 +1253,13 @@ Example:
c, ok = strconv.parse_complex64("5+7i hellope", &n)
fmt.printfln("%v %i %t", c, n, ok)
}
Output:
3+1i 4 true
5+7i 4 false
**Returns**
**Returns**
- 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.
*/
@@ -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
**Inputs**
**Inputs**
- 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).
@@ -1287,13 +1287,13 @@ Example:
c, ok = strconv.parse_complex32("5+7i hellope", &n)
fmt.printfln("%v %i %t", c, n, ok)
}
Output:
3+1i 4 true
5+7i 4 false
**Returns**
**Returns**
- 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.
*/
@@ -1305,7 +1305,7 @@ parse_complex32 :: proc(str: string, n: ^int = nil) -> (value: complex32, ok: bo
/*
Parses a 256-bit quaternion from a string
**Inputs**
**Inputs**
- 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).
@@ -1321,13 +1321,13 @@ Example:
q, ok = strconv.parse_quaternion256("1+2i+3j+4k hellope", &n)
fmt.printfln("%v %i %t", q, n, ok)
}
Output:
1+2i+3j+4k 10 true
1+2i+3j+4k 10 false
**Returns**
**Returns**
- 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.
*/
@@ -1385,7 +1385,7 @@ parse_quaternion256 :: proc(str: string, n: ^int = nil) -> (value: quaternion256
/*
Parses a 128-bit quaternion from a string
**Inputs**
**Inputs**
- 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).
@@ -1401,13 +1401,13 @@ Example:
q, ok = strconv.parse_quaternion128("1+2i+3j+4k hellope", &n)
fmt.printfln("%v %i %t", q, n, ok)
}
Output:
1+2i+3j+4k 10 true
1+2i+3j+4k 10 false
**Returns**
**Returns**
- 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.
*/
@@ -1419,7 +1419,7 @@ parse_quaternion128 :: proc(str: string, n: ^int = nil) -> (value: quaternion128
/*
Parses a 64-bit quaternion from a string
**Inputs**
**Inputs**
- 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).
@@ -1435,13 +1435,13 @@ Example:
q, ok = strconv.parse_quaternion64("1+2i+3j+4k hellope", &n)
fmt.printfln("%v %i %t", q, n, ok)
}
Output:
1+2i+3j+4k 10 true
1+2i+3j+4k 10 false
**Returns**
**Returns**
- 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.
*/
@@ -1450,20 +1450,20 @@ parse_quaternion64 :: proc(str: string, n: ^int = nil) -> (value: quaternion64,
v, ok = parse_quaternion256(str, n)
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**
- buf: The buffer to append the boolean value to
- b: The boolean value to be appended
**Inputs**
- buf: The buffer to write the boolean value to
- b: The boolean value to be written
Example:
import "core:fmt"
import "core:strconv"
append_bool_example :: proc() {
write_bool_example :: proc() {
buf: [6]byte
result := strconv.append_bool(buf[:], true)
result := strconv.write_bool(buf[:], true)
fmt.println(result, buf)
}
@@ -1471,10 +1471,10 @@ Output:
true [116, 114, 117, 101, 0, 0]
**Returns**
- The resulting string after appending the boolean value
**Returns**
- 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
if b {
n = copy(buf, "true")
@@ -1483,21 +1483,21 @@ append_bool :: proc(buf: []byte, b: bool) -> string {
}
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**
- buf: The buffer to append the unsigned integer value to
- u: The unsigned integer value to be appended
**Inputs**
- buf: The buffer to write the unsigned integer value to
- u: The unsigned integer value to be written
- base: The base to use for converting the integer value
Example:
import "core:fmt"
import "core:strconv"
append_uint_example :: proc() {
write_uint_example :: proc() {
buf: [4]byte
result := strconv.append_uint(buf[:], 42, 16)
result := strconv.write_uint(buf[:], 42, 16)
fmt.println(result, buf)
}
@@ -1505,27 +1505,27 @@ Output:
2a [50, 97, 0, 0]
**Returns**
- The resulting string after appending the unsigned integer value
**Returns**
- The resulting string after writing the unsigned integer value
*/
append_uint :: proc(buf: []byte, u: u64, base: int) -> string {
return append_bits(buf, u, base, false, 8*size_of(uint), digits, nil)
write_uint :: proc(buf: []byte, u: u64, base: int) -> string {
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**
- buf: The buffer to append the signed integer value to
- i: The signed integer value to be appended
**Inputs**
- buf: The buffer to write the signed integer value to
- i: The signed integer value to be written
- base: The base to use for converting the integer value
Example:
import "core:fmt"
import "core:strconv"
append_int_example :: proc() {
write_int_example :: proc() {
buf: [4]byte
result := strconv.append_int(buf[:], -42, 10)
result := strconv.write_int(buf[:], -42, 10)
fmt.println(result, buf)
}
@@ -1533,23 +1533,23 @@ Output:
-42 [45, 52, 50, 0]
**Returns**
- The resulting string after appending the signed integer value
**Returns**
- The resulting string after writing the signed integer value
*/
append_int :: proc(buf: []byte, i: i64, base: int) -> string {
return append_bits(buf, u64(i), base, true, 8*size_of(int), digits, nil)
write_int :: proc(buf: []byte, i: i64, base: int) -> string {
return write_bits(buf, u64(i), base, true, 8*size_of(int), digits, nil)
}
append_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
return append_bits_128(buf, u, base, false, 8*size_of(uint), digits, nil)
write_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
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
**Inputs**
**Inputs**
- buf: The buffer to store the resulting string
- i: The integer value to be converted
@@ -1567,16 +1567,16 @@ Output:
42 [52, 50, 0, 0]
**Returns**
**Returns**
- The resulting string after converting the integer value
*/
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
**Inputs**
**Inputs**
- s: The string to be converted
Example:
@@ -1591,17 +1591,17 @@ Output:
42
**Returns**
**Returns**
- The resulting integer value
*/
atoi :: proc(s: string) -> int {
v, _ := parse_int(s)
return v
}
/*
/*
Converts a string to a float64 value
**Inputs**
**Inputs**
- s: The string to be converted
Example:
@@ -1616,21 +1616,21 @@ Output:
3.140
**Returns**
**Returns**
- The resulting float64 value after converting the string
*/
atof :: proc(s: string) -> f64 {
v, _ := parse_f64(s)
return v
}
// Alias to `append_float`
ftoa :: append_float
/*
Appends a float64 value as a string to the given buffer with the specified format and precision
// Alias to `write_float`
ftoa :: write_float
/*
Writes a float64 value as a string to the given buffer with the specified format and precision
**Inputs**
- buf: The buffer to append the float64 value to
- f: The float64 value to be appended
**Inputs**
- buf: The buffer to write the float64 value to
- f: The float64 value to be written
- fmt: The byte specifying the format 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)
@@ -1639,9 +1639,9 @@ Example:
import "core:fmt"
import "core:strconv"
append_float_example :: proc() {
write_float_example :: proc() {
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)
}
@@ -1649,20 +1649,20 @@ Output:
+3.14 [43, 51, 46, 49, 52, 0, 0, 0]
**Returns**
- The resulting string after appending the float
**Returns**
- 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))
}
/*
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**
- buf: The byte slice to which the quoted string will be appended
**Inputs**
- buf: The byte slice to which the quoted string will be written
- str: The input string to be quoted
!! ISSUE !! NOT EXPECTED -- "\"hello\"" was expected
!! ISSUE !! NOT EXPECTED -- "\"hello\"" was expected
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]
**Returns**
- The resulting string after appending the quoted string representation
**Returns**
- The resulting string after writing the quoted string representation
*/
quote :: proc(buf: []byte, str: string) -> string {
write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) {
@@ -1719,10 +1719,10 @@ quote :: proc(buf: []byte, str: string) -> string {
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**
- buf: The byte slice to which the quoted rune will be appended
**Inputs**
- buf: The byte slice to which the quoted rune will be written
- r: The input rune to be quoted
Example:
@@ -1739,8 +1739,8 @@ Output:
'A' [39, 65, 39, 0]
**Returns**
- The resulting string after appending the quoted rune representation
**Returns**
- The resulting string after writing the quoted rune representation
*/
quote_rune :: proc(buf: []byte, r: rune) -> string {
write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) {
@@ -1783,7 +1783,7 @@ quote_rune :: proc(buf: []byte, r: rune) -> string {
if r < 32 {
write_string(buf, &i, "\\x")
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) {
case 0: write_string(buf, &i, "00")
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
**Inputs**
**Inputs**
- str: The input string containing the character to unquote
- quote: The quote character to consider (e.g., '"')
Example:
Example:
import "core:fmt"
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)
}
Output:
Output:
Source: 'The' raven
r: <'>, multiple_bytes:false, tail_string:<The' raven>, success:true
**Returns**
**Returns**
- r: The unquoted rune
- multiple_bytes: A boolean indicating if the rune has multiple bytes
- 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
**Inputs**
**Inputs**
- lit: The input string to unquote
- allocator: (default: context.allocator)
WARNING: This procedure gives unexpected results if the quotes are not the first and last characters.
Example:
Example:
import "core:fmt"
import "core:strconv"
@@ -1947,10 +1947,10 @@ Example:
src="The raven \'Huginn\' is black."
s, allocated, ok = strconv.unquote_string(src) // Will produce undesireable results
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."
Unquoted: <The raven Huginn is black.>, alloc:false, ok:true
@@ -1961,7 +1961,7 @@ Output:
The raven 'Huginn' is black.
Unquoted: <he raven 'Huginn' is black>, alloc:false, ok:true
**Returns**
**Returns**
- res: The resulting unquoted string
- allocated: A boolean indicating if the resulting string was allocated using the provided allocator
- 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
}
}
context.allocator = allocator
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) {
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,
// we skip it unless it's followed by an `I` (because of +Inf).
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) {
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') {
s = s[1:]
}
@@ -739,7 +739,7 @@ Output:
*/
write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: int) {
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') {
s = s[1:]
}
@@ -761,7 +761,7 @@ Returns:
*/
write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) {
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') {
s = s[1:]
}
@@ -782,7 +782,7 @@ Returns:
*/
write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
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)
}
/*
@@ -800,7 +800,7 @@ Returns:
*/
write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
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)
}
/*
+82 -64
View File
@@ -28,24 +28,7 @@ clone :: proc(s: string, allocator := context.allocator, loc := #caller_location
copy(c, s)
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
@@ -66,6 +49,7 @@ clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #call
c[len(s)] = 0
return cstring(&c[0]), nil
}
/*
Transmutes a raw pointer into a string. Non-allocating.
@@ -81,6 +65,7 @@ Returns:
string_from_ptr :: proc(ptr: ^byte, len: int) -> (res: string) {
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
@@ -99,20 +84,7 @@ string_from_null_terminated_ptr :: proc "contextless" (ptr: [^]byte, len: int) -
s = truncate_to_byte(s, 0)
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
@@ -128,6 +100,7 @@ unsafe_string_to_cstring :: proc(str: string) -> (res: cstring) {
d := transmute(mem.Raw_String)str
return cstring(d.data)
}
/*
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]
}
/*
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]
}
/*
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
return string(c[:len(s)]), nil
}
/*
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 {
return clone(string(s), allocator, loc)
}
/*
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)
return clone(s, allocator, loc)
}
// Overloaded procedure to clone from a string, `[]byte`, `cstring` or a `^byte` + length
clone_from :: proc{
clone,
@@ -229,6 +207,7 @@ clone_from :: proc{
clone_from_cstring,
clone_from_ptr,
}
/*
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)
return clone(s, allocator, loc)
}
/*
Compares two strings, returning a value representing which one comes first lexicographically.
-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) {
return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs)
}
/*
Checks if rune `r` in the string `s`
@@ -283,6 +264,7 @@ contains_rune :: proc(s: string, r: rune) -> (result: bool) {
}
return false
}
/*
Returns true when the string `substr` is contained inside the string `s`
@@ -314,6 +296,7 @@ Output:
contains :: proc(s, substr: string) -> (res: bool) {
return index(s, substr) >= 0
}
/*
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) {
return utf8.rune_count_in_string(s)
}
/*
Returns whether the strings `u` and `v` are the same alpha characters, ignoring different casings
Works with UTF-8 string content
@@ -508,6 +492,7 @@ prefix_length :: proc "contextless" (a, b: string) -> (n: int) {
}
return
}
/*
Returns the common prefix between strings `a` and `b`
@@ -540,6 +525,7 @@ Output:
common_prefix :: proc(a, b: string) -> string {
return a[:prefix_length(a, b)]
}
/*
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
}
/*
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
@@ -723,22 +692,6 @@ concatenate :: proc(a: []string, allocator := context.allocator, loc := #caller_
}
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
@@ -901,6 +854,7 @@ _split :: proc(s_, sep: string, sep_save, n_: int, allocator := context.allocato
return res[:i+1], nil
}
/*
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 {
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.
@@ -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 {
return _split(s, sep, 0, n, allocator)
}
/*
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 {
return _split(s, sep, len(sep), -1, allocator)
}
/*
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 {
return _split(s, sep, len(sep), n, allocator)
}
/*
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.
@@ -1083,6 +1041,7 @@ _split_iterator :: proc(s: ^string, sep: string, sep_save: int) -> (res: string,
}
return
}
/*
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
}
/*
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) {
return _split_iterator(s, sep, 0)
}
/*
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) {
return _split_iterator(s, sep, len(sep))
}
/*
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
}
/*
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
}
/*
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
}
/*
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
}
/*
Splits the input string at every line break `\n` leaving the `\n` in the resulting strings.
Only runs for n parts.
@@ -1377,6 +1343,7 @@ split_lines_after_n :: proc(s: string, n: int, allocator := context.allocator) -
}
return lines, nil
}
/*
Splits the input string at every line break `\n`.
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
return _trim_cr(line), true
}
/*
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.
@@ -1448,6 +1416,7 @@ split_lines_after_iterator :: proc(s: ^string) -> (line: string, ok: bool) {
line = _split_iterator(s, sep, len(sep)) or_return
return _trim_cr(line), true
}
/*
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.
@@ -1482,6 +1451,7 @@ Output:
index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) {
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.
@@ -1517,6 +1487,7 @@ Output:
last_index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) {
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.
Invalid runes return -1
@@ -1657,6 +1628,7 @@ index :: proc "contextless" (s, substr: string) -> (res: int) {
}
return -1
}
/*
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
}
/*
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
}
/*
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
}
/*
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
}
/*
Counts the number of non-overlapping occurrences of `substr` in `s`
@@ -1985,6 +1961,7 @@ count :: proc(s, substr: string) -> (res: int) {
}
return n
}
/*
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
}
/*
Replaces all occurrences of `old` in `s` with `new`
@@ -2063,9 +2041,11 @@ Output:
zzzz true
*/
replace_all :: proc(s, old, new: string, allocator := context.allocator) -> (output: string, was_allocation: bool) {
return replace(s, old, new, -1, allocator)
}
/*
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])
return
}
/*
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) {
return replace(s, key, "", n, allocator)
}
/*
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) {
return remove(s, key, -1, allocator)
}
// 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}
@@ -2320,6 +2303,7 @@ index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> (res: int
}
return -1
}
// 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) {
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
}
// 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) {
// 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
}
// 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) {
// 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
}
/*
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:]
}
/*
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:]
}
/*
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]
}
/*
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]
}
// Procedure for `trim_*_proc` variants, which has a string rawptr cast + rune comparison
is_in_cutset :: proc(state: rawptr, r: rune) -> (res: bool) {
cutset := (^string)(state)^
@@ -2473,6 +2464,7 @@ is_in_cutset :: proc(state: rawptr, r: rune) -> (res: bool) {
}
return false
}
/*
Trims the cutset string from the `s` string
@@ -2490,6 +2482,7 @@ trim_left :: proc(s: string, cutset: string) -> (res: string) {
state := cutset
return trim_left_proc_with_state(s, is_in_cutset, &state)
}
/*
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
return trim_right_proc_with_state(s, is_in_cutset, &state)
}
/*
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) {
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"
@@ -2532,6 +2527,7 @@ Returns:
trim_left_space :: proc(s: string) -> (res: string) {
return trim_left_proc(s, is_space)
}
/*
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) {
return trim_right_proc(s, is_space)
}
/*
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) {
return trim_right_space(trim_left_space(s))
}
/*
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) {
return trim_left_proc(s, is_null)
}
/*
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) {
return trim_right_proc(s, is_null)
}
/*
Trims null runes from both sides, "\x00\x00testing\x00\x00" -> "testing"
@@ -2591,6 +2591,7 @@ Returns:
trim_null :: proc(s: string) -> (res: string) {
return trim_right_null(trim_left_null(s))
}
/*
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
}
/*
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
}
/*
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)
return results[:], nil
}
/*
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
return
}
/*
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
}
/*
Reverses the input string `s`
@@ -2889,6 +2895,7 @@ reverse :: proc(s: string, allocator := context.allocator, loc := #caller_locati
}
return string(buf), nil
}
/*
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
*/
expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error {
if tab_size <= 0 {
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
}
/*
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):]
return
}
// Alias for centre_justify
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.
@@ -3048,6 +3059,7 @@ centre_justify :: proc(str: string, length: int, pad: string, allocator := conte
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.
@@ -3082,6 +3094,7 @@ left_justify :: proc(str: string, length: int, pad: string, allocator := context
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.
@@ -3116,6 +3129,7 @@ right_justify :: proc(str: string, length: int, pad: string, allocator := contex
return to_string(b), nil
}
/*
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:]
}
}
/*
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
}
/*
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
}
/*
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):]
return
}
/*
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)
}
}
+107 -48
View File
@@ -7,6 +7,14 @@ import "core:mem"
import "core:sync"
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.
*/
@@ -75,6 +83,8 @@ Raw_Chan :: struct {
r_waiting: int, // guarded by `mutex`
w_waiting: int, // guarded by `mutex`
did_read: bool, // lets a sender know if the value was read
// Buffered
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:
- the channel is unbuffered
- the channel's buffer is full
until the channel is being read from. `send` will return
`false` when attempting to send on an already closed channel.
until the channel is being read from or the channel is closed. `send` will
return `false` when attempting to send on an already closed channel.
**Inputs**
- `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:
- the channel is unbuffered
- the channel's buffer is empty
until the channel is being written to. `recv` will return
`false` when attempting to receive a message on an already closed channel.
until the channel is being written to or the channel is closed. `recv` will
return `false` when attempting to receive a message on an already closed
channel.
**Inputs**
- `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:
- the channel is unbuffered
- the channel's buffer is full
until the channel is being read from. `send_raw` will return
`false` when attempting to send on an already closed channel.
until the channel is being read from or the channel is closed. `send_raw` will
return `false` when attempting to send on an already closed channel.
Note: The message referenced by `msg_out` must match the size
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
}
c.did_read = false
defer c.did_read = false
mem.copy(c.unbuffered_data, msg_in, int(c.msg_size))
c.w_waiting += 1
if c.r_waiting > 0 {
sync.signal(&c.r_cond)
}
sync.wait(&c.w_cond, &c.mutex)
if c.closed && !c.did_read {
return false
}
ok = true
}
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:
- the channel is unbuffered
- the channel's buffer is empty
until the channel is being written to. `recv_raw` will return
`false` when attempting to receive a message on an already closed channel.
until the channel is being written to or the channel is closed. `recv_raw`
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
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
sync.guard(&c.mutex)
for !c.closed &&
c.w_waiting == 0 {
for !c.closed && c.w_waiting == 0 {
c.r_waiting += 1
sync.wait(&c.r_cond, &c.mutex)
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))
c.w_waiting -= 1
c.did_read = true
sync.signal(&c.w_cond)
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
sync.guard(&c.mutex)
if c.closed {
if c.closed || c.r_waiting - c.w_waiting <= 0 {
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
sync.guard(&c.mutex)
if c.closed || c.w_waiting == 0 {
if c.closed || c.w_waiting - c.r_waiting <= 0 {
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.,
if a call to `recv` or `recv_raw` would block
Returns whether a message can be read without blocking the current
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**
- `c`: The channel
@@ -1067,7 +1091,7 @@ can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool {
if is_buffered(c) {
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
**Returns**
- `true` if a message can be send, `false` otherwise
- `true` if a message can be sent, `false` otherwise
Example:
@@ -1102,18 +1126,30 @@ can_send :: proc "contextless" (c: ^Raw_Chan) -> bool {
if is_buffered(c) {
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
(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`.
If the message is nil, corresponding send channel will be skipped.
**Inputs**
- `recv`: A slice of channels to read from
@@ -1145,18 +1181,18 @@ Example:
// where the value from the read should be stored
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("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("RECEIVED VALUE ", received_value)
// closing of a channel also affects the select operation
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)
}
@@ -1170,7 +1206,7 @@ Output:
*/
@(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 {
idx: int, // local to the slice that was given
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)
candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op)))
count := 0
for c, i in recvs {
if can_recv(c) {
candidates[count] = {
is_recv = true,
idx = i,
try_loop: for {
count := 0
for c, i in recvs {
if can_recv(c) {
candidates[count] = {
is_recv = true,
idx = i,
}
count += 1
}
count += 1
}
}
for c, i in sends {
if can_send(c) {
candidates[count] = {
is_recv = false,
idx = i,
for c, i in sends {
if i > builtin.len(send_msgs)-1 || send_msgs[i] == nil {
continue
}
if can_send(c) {
candidates[count] = {
is_recv = false,
idx = i,
}
count += 1
}
count += 1
}
}
if count == 0 {
return
}
if count == 0 {
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]
if sel.is_recv {
ok = recv_raw(recvs[sel.idx], recv_out)
} else {
ok = send_raw(sends[sel.idx], send_msgs[sel.idx])
candidate_idx := rand.int_max(count) if count > 0 else 0
sel := candidates[candidate_idx]
if sel.is_recv {
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
+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_Half_Width :: size_of(Atomic_RW_Mutex_State)*8/2
Atomic_RW_Mutex_State_Is_Writing :: Atomic_RW_Mutex_State(1)
Atomic_RW_Mutex_State_Writer :: Atomic_RW_Mutex_State(1)<<1
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
Atomic_RW_Mutex_State_Is_Writing :: Atomic_RW_Mutex_State(1) << (size_of(Atomic_RW_Mutex_State)*8-1)
Atomic_RW_Mutex_State_Reader :: Atomic_RW_Mutex_State(1)
Atomic_RW_Mutex_State_Reader_Mask :: ~Atomic_RW_Mutex_State_Is_Writing
// 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 zero value for an Atomic_RW_Mutex is an unlocked mutex
// 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 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 {
state: Atomic_RW_Mutex_State,
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)
// 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_add(&rw.state, Atomic_RW_Mutex_State_Writer)
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 {
// 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)
}
}
@@ -138,10 +140,15 @@ atomic_rw_mutex_try_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) -> bool {
if atomic_mutex_try_lock(&rw.mutex) {
state := atomic_load(&rw.state)
if state & Atomic_RW_Mutex_State_Reader_Mask == 0 {
_ = atomic_or(&rw.state, Atomic_RW_Mutex_State_Is_Writing)
return true
// Compare-and-exchange for absolute certainty that no one has come in to read.
_, 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)
}
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 :: proc "contextless" (rw: ^Atomic_RW_Mutex) {
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
state, ok = atomic_compare_exchange_weak(&rw.state, state, state + Atomic_RW_Mutex_State_Reader)
if ok {
// We succesfully took the shared reader lock without any writers intervening.
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)
// At this point, we have the lock, so we can add to the reader count.
_ = 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)
}
@@ -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) &&
(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)
}
}
@@ -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 :: proc "contextless" (rw: ^Atomic_RW_Mutex) -> bool {
state := atomic_load(&rw.state)
if state & (Atomic_RW_Mutex_State_Is_Writing|Atomic_RW_Mutex_State_Writer_Mask) == 0 {
_, ok := atomic_compare_exchange_strong(&rw.state, state, state + Atomic_RW_Mutex_State_Reader)
// NOTE: We need to check this in a for loop, because it is possible for
// 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 {
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) {
_ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Reader)
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.)
// and has no clear alternative although `activate` is what Apple tells you to use,
// that does not work the same way.
// @(deprecated="Use NSApplication method activate instead.")
@(objc_type=Application, objc_name="activateIgnoringOtherApps")
Application_activateIgnoringOtherApps :: proc "c" (self: ^Application, ignoreOtherApps: BOOL) {
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 {
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))
}
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 {
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 {
os_version.platform = .MacOS
switch version.majorVersion {
case 26: ws(&b, "macOS Tahoe")
case 15: ws(&b, "macOS Sequoia")
case 14: ws(&b, "macOS Sonoma")
case 13: ws(&b, "macOS Ventura")
+1 -1
View File
@@ -1,6 +1,6 @@
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.")
}
+18 -15
View File
@@ -1618,36 +1618,39 @@ PER_HPUX :: 0x0010
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,
RDOTH = 2,
WRGRP = 4,
RDGRP = 5,
WRUSR = 7,
RDUSR = 8,
DEST = 9,
LOCKED = 10,
}
/*
Shared memory flags bits
*/
IPC_Flags_Bits :: enum {
// Creation flags for shared memory.
IPC_CREAT = 9,
IPC_EXCL = 10,
IPC_NOWAIT = 11,
// Semaphore
SEM_UNDO = 9,
// Shared memory
SHM_HUGETLB = 11,
SHM_NORESERVE = 12,
// Usage flags for shared memory.
IPC_NOWAIT = 11,
SEM_UNDO = 9,
SHM_RDONLY = 12,
SHM_RND = 13,
SHM_REMAP = 14,
SHM_EXEC = 15,
// Message queue
MSG_NOERROR = 12,
MSG_EXCEPT = 13,
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_1GB :: transmute(Map_Flags)(u32(30) << 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.
*/
gettimeofday :: proc "contextless" (tv: ^Time_Val) -> (Errno) {
ret := syscall(SYS_gettimeofday, tv)
ret := syscall(SYS_gettimeofday, tv, rawptr(nil))
return Errno(-ret)
}

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